mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-21 00:07:30 +00:00
Merge branch 'stable' into next-minor
This commit is contained in:
commit
e04dfe96af
@ -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.
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user