Merge branch 'stable' into next-minor

This commit is contained in:
Dylan K. Taylor 2022-01-01 17:55:17 +00:00
commit e04dfe96af
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
7 changed files with 83 additions and 20 deletions

View File

@ -1576,3 +1576,18 @@ Released 16th December 2021.
- Fixed player arm swing animation not being shown when attacks were cancelled by attack cooldown.
- Fixed being unable to use `/deop` to de-op a player whose name appeared in `ops.txt` with uppercase letters in it.
- Added a check for valid tile class in `BlockIdentifier`.
# 4.0.4
Released 1st January 2022.
## General
- Improved performance of loading chests and other containers from world saves.
- Improved performance of loading player inventories from saved data.
## Fixes
- Fixed a crash that could occur when a chunk failed to be prepared for chunk sending.
- Fixed fall damage when sprinting down stairs.
- Fixed message length limit for chat (now 512 instead of 255, and accounts for UTF-8).
- Fixed incorrect message being displayed when trying to sleep in a bed which is too far away.
- Fixed missing space between `Kicked by admin.` and `Reason` when using `/kick` to kick a player.
- Fixed client-side performance issue of entities with very large scale.

View File

@ -48,11 +48,14 @@ trait ContainerTrait{
$inventory = $this->getRealInventory();
$listeners = $inventory->getListeners()->toArray();
$inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
$inventory->clearAll();
$newContents = [];
/** @var CompoundTag $itemNBT */
foreach($inventoryTag as $itemNBT){
$inventory->setItem($itemNBT->getByte("Slot"), Item::nbtDeserialize($itemNBT));
$newContents[$itemNBT->getByte("Slot")] = Item::nbtDeserialize($itemNBT);
}
$inventory->setContents($newContents);
$inventory->getListeners()->add(...$listeners);
}

View File

@ -1614,7 +1614,7 @@ abstract class Entity{
$properties->setString(EntityMetadataProperties::SCORE_TAG, $this->scoreTag);
$properties->setByte(EntityMetadataProperties::COLOR, 0);
$properties->setGenericFlag(EntityMetadataFlags::AFFECTED_BY_GRAVITY, true);
$properties->setGenericFlag(EntityMetadataFlags::AFFECTED_BY_GRAVITY, $this->gravityEnabled);
$properties->setGenericFlag(EntityMetadataFlags::CAN_CLIMB, $this->canClimb);
$properties->setGenericFlag(EntityMetadataFlags::CAN_SHOW_NAMETAG, $this->nameTagVisible);
$properties->setGenericFlag(EntityMetadataFlags::HAS_COLLISION, true);

View File

@ -217,6 +217,19 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$this->uuid = Uuid::uuid3(Uuid::NIL, ((string) $this->getId()) . $this->skin->getSkinData() . $this->getNameTag());
}
/**
* @param Item[] $items
* @phpstan-param array<int, Item> $items
*/
private static function populateInventoryFromListTag(Inventory $inventory, array $items) : void{
$listeners = $inventory->getListeners()->toArray();
$inventory->getListeners()->clear();
$inventory->setContents($items);
$inventory->getListeners()->add(...$listeners);
}
protected function initEntity(CompoundTag $nbt) : void{
parent::initEntity($nbt);
@ -247,10 +260,8 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$inventoryTag = $nbt->getListTag("Inventory");
if($inventoryTag !== null){
$armorListeners = $this->armorInventory->getListeners()->toArray();
$this->armorInventory->getListeners()->clear();
$inventoryListeners = $this->inventory->getListeners()->toArray();
$this->inventory->getListeners()->clear();
$inventoryItems = [];
$armorInventoryItems = [];
/** @var CompoundTag $item */
foreach($inventoryTag as $i => $item){
@ -258,14 +269,14 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
if($slot >= 0 and $slot < 9){ //Hotbar
//Old hotbar saving stuff, ignore it
}elseif($slot >= 100 and $slot < 104){ //Armor
$this->armorInventory->setItem($slot - 100, Item::nbtDeserialize($item));
$armorInventoryItems[$slot - 100] = Item::nbtDeserialize($item);
}elseif($slot >= 9 and $slot < $this->inventory->getSize() + 9){
$this->inventory->setItem($slot - 9, Item::nbtDeserialize($item));
$inventoryItems[$slot - 9] = Item::nbtDeserialize($item);
}
}
$this->armorInventory->getListeners()->add(...$armorListeners);
$this->inventory->getListeners()->add(...$inventoryListeners);
self::populateInventoryFromListTag($this->inventory, $inventoryItems);
self::populateInventoryFromListTag($this->armorInventory, $armorInventoryItems);
}
$offHand = $nbt->getCompoundTag("OffHandItem");
if($offHand !== null){
@ -279,10 +290,13 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$enderChestInventoryTag = $nbt->getListTag("EnderChestInventory");
if($enderChestInventoryTag !== null){
$enderChestInventoryItems = [];
/** @var CompoundTag $item */
foreach($enderChestInventoryTag as $i => $item){
$this->enderInventory->setItem($item->getByte("Slot"), Item::nbtDeserialize($item));
$enderChestInventoryItems[$item->getByte("Slot")] = Item::nbtDeserialize($item);
}
self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems);
}
$this->inventory->setHeldItemIndex($nbt->getInt("SelectedInventorySlot", 0));

View File

@ -71,10 +71,10 @@ class SimpleInventory extends BaseInventory{
protected function internalSetContents(array $items) : void{
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
if(!isset($items[$i])){
$this->clear($i);
if(!isset($items[$i]) || $items[$i]->isNull()){
$this->slots[$i] = null;
}else{
$this->setItem($i, $items[$i]);
$this->slots[$i] = clone $items[$i];
}
}
}

View File

@ -162,6 +162,12 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/** Max length of a chat message (UTF-8 codepoints, not bytes) */
private const MAX_CHAT_CHAR_LENGTH = 512;
/**
* Max length of a chat message in bytes. This is a theoretical maximum (if every character was 4 bytes).
* Since mb_strlen() is O(n), it gets very slow with large messages. Checking byte length with strlen() is O(1) and
* is a useful heuristic to filter out oversized messages.
*/
private const MAX_CHAT_BYTE_LENGTH = self::MAX_CHAT_CHAR_LENGTH * 4;
private const MAX_REACH_DISTANCE_CREATIVE = 13;
private const MAX_REACH_DISTANCE_SURVIVAL = 7;
private const MAX_REACH_DISTANCE_ENTITY_INTERACTION = 8;
@ -1107,6 +1113,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$bb->minY = $this->location->y - 0.2;
$bb->maxY = $this->location->y + 0.2;
//we're already at the new position at this point; check if there are blocks we might have landed on between
//the old and new positions (running down stairs necessitates this)
$bb = $bb->addCoord(-$dx, -$dy, -$dz);
$this->onGround = $this->isCollided = count($this->getWorld()->getCollisionBlocks($bb, true)) > 0;
}
}
@ -1347,7 +1357,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$message = TextFormat::clean($message, false);
foreach(explode("\n", $message) as $messagePart){
if(trim($messagePart) !== "" and mb_strlen($messagePart, 'UTF-8') <= self::MAX_CHAT_CHAR_LENGTH and $this->messageCounter-- > 0){
if(trim($messagePart) !== "" and strlen($messagePart) <= self::MAX_CHAT_BYTE_LENGTH and mb_strlen($messagePart, 'UTF-8') <= self::MAX_CHAT_CHAR_LENGTH and $this->messageCounter-- > 0){
if(strpos($messagePart, './') === 0){
$messagePart = substr($messagePart, 1);
}

View File

@ -263,12 +263,33 @@ final class Filesystem{
throw new \RuntimeException("Failed to write to temporary file $temporaryFileName (possibly out of free disk space)");
}
//TODO: the @ prevents us receiving the actual error message, but right now it's necessary since we can't assume
//that the error handler has been set :(
$renameTemporaryFileResult = $context !== null ?
rename($temporaryFileName, $fileName, $context) :
rename($temporaryFileName, $fileName);
@rename($temporaryFileName, $fileName, $context) :
@rename($temporaryFileName, $fileName);
if(!$renameTemporaryFileResult){
throw new \RuntimeException("Failed to move temporary file contents into target file");
/*
* The following code works around a bug in Windows where rename() will periodically decide to give us a
* spurious "Access is denied (code: 5)" error. As far as I could determine, the fault comes from Windows
* itself, but since I couldn't reliably reproduce the issue it's very hard to debug.
*
* The following code can be used to test. Usually it will fail anywhere before 100,000 iterations.
*
* for($i = 0; $i < 10_000_000; ++$i){
* file_put_contents('ops.txt.0.tmp', 'some data ' . $i, 0);
* if(!rename('ops.txt.0.tmp', 'ops.txt')){
* throw new \Error("something weird happened");
* }
* }
*/
$copyTemporaryFileResult = $context !== null ?
copy($temporaryFileName, $fileName, $context) :
copy($temporaryFileName, $fileName);
if(!$copyTemporaryFileResult){
throw new \RuntimeException("Failed to move temporary file contents into target file");
}
@unlink($temporaryFileName);
}
}
}