Merge branch 'master' into mcpe-1.2.5

This commit is contained in:
Dylan K. Taylor 2017-10-31 21:52:16 +00:00
commit dcdea6a1f4
25 changed files with 249 additions and 225 deletions

View File

@ -762,6 +762,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true; return true;
} }
/**
* {@inheritdoc}
*
* If null is given, will additionally send the skin to the player itself as well as its viewers.
*/
public function sendSkin(array $targets = null) : void{
parent::sendSkin($targets ?? $this->server->getOnlinePlayers());
}
/** /**
* Gets the player IP address * Gets the player IP address
* *
@ -1988,24 +1997,14 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true; return true;
} }
/* Mojang, some stupid reason, send every single model for every single skin in the selected skin-pack.
* Not only that, they are pretty-printed. This decode/encode is to get rid of the pretty-print, which cuts down
* significantly on the amount of wasted bytes.
* TODO: find out what model crap can be safely dropped from the packet (unless it gets fixed first)
*/
$geometryJsonEncoded = base64_decode($packet->clientData["SkinGeometry"] ?? "");
if($geometryJsonEncoded !== ""){
$geometryJsonEncoded = json_encode(json_decode($geometryJsonEncoded));
}
$skin = new Skin( $skin = new Skin(
$packet->clientData["SkinId"], $packet->clientData["SkinId"],
base64_decode($packet->clientData["SkinData"] ?? ""), base64_decode($packet->clientData["SkinData"] ?? ""),
base64_decode($packet->clientData["CapeData"] ?? ""), base64_decode($packet->clientData["CapeData"] ?? ""),
$packet->clientData["SkinGeometryName"], $packet->clientData["SkinGeometryName"],
$geometryJsonEncoded base64_decode($packet->clientData["SkinGeometry"] ?? "")
); );
$skin->debloatGeometryData();
if(!$skin->isValid()){ if(!$skin->isValid()){
$this->close("", "disconnectionScreen.invalidSkin"); $this->close("", "disconnectionScreen.invalidSkin");
@ -2911,7 +2910,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true; return true;
} }
$tile = $this->level->getTile($this->temporalVector->setComponents($packet->x, $packet->y, $packet->z)); $tile = $this->level->getTileAt($packet->x, $packet->y, $packet->z);
if($tile instanceof ItemFrame){ if($tile instanceof ItemFrame){
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $tile->getBlock(), null, 5 - $tile->getBlock()->getDamage(), PlayerInteractEvent::LEFT_CLICK_BLOCK); $ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $tile->getBlock(), null, 5 - $tile->getBlock()->getDamage(), PlayerInteractEvent::LEFT_CLICK_BLOCK);
$this->server->getPluginManager()->callEvent($ev); $this->server->getPluginManager()->callEvent($ev);

View File

@ -410,6 +410,12 @@ namespace pocketmine {
return -1; return -1;
} }
/**
* @param int $start
* @param array|null $trace
*
* @return array
*/
function getTrace($start = 0, $trace = null){ function getTrace($start = 0, $trace = null){
if($trace === null){ if($trace === null){
if(function_exists("xdebug_get_function_stack")){ if(function_exists("xdebug_get_function_stack")){

View File

@ -2156,6 +2156,10 @@ class Server{
} }
} }
/**
* @param \Throwable $e
* @param array|null $trace
*/
public function exceptionHandler(\Throwable $e, $trace = null){ public function exceptionHandler(\Throwable $e, $trace = null){
if($e === null){ if($e === null){
return; return;

View File

@ -126,16 +126,20 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
} }
$this->skin = $skin; $this->skin = $skin;
$this->skin->debloatGeometryData();
} }
/** /**
* @param Player[] $targets * Sends the human's skin to the specified list of players. If null is given for targets, the skin will be sent to
* all viewers.
*
* @param Player[]|null $targets
*/ */
public function sendSkin(array $targets) : void{ public function sendSkin(array $targets = null) : void{
$pk = new PlayerSkinPacket(); $pk = new PlayerSkinPacket();
$pk->uuid = $this->getUniqueId(); $pk->uuid = $this->getUniqueId();
$pk->skin = $this->skin; $pk->skin = $this->skin;
$this->server->broadcastPacket($targets, $pk); $this->server->broadcastPacket($targets ?? $this->hasSpawned, $pk);
} }
public function jump(){ public function jump(){

View File

@ -87,4 +87,17 @@ class Skin{
return $this->geometryData; return $this->geometryData;
} }
/**
* Hack to cut down on network overhead due to skins, by un-pretty-printing geometry JSON.
*
* Mojang, some stupid reason, send every single model for every single skin in the selected skin-pack.
* Not only that, they are pretty-printed.
* TODO: find out what model crap can be safely dropped from the packet (unless it gets fixed first)
*/
public function debloatGeometryData() : void{
if($this->geometryData !== ""){
$this->geometryData = (string) json_encode(json_decode($this->geometryData));
}
}
} }

View File

@ -26,6 +26,7 @@ namespace pocketmine\inventory;
use pocketmine\event\Timings; use pocketmine\event\Timings;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\item\ItemFactory; use pocketmine\item\ItemFactory;
use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\CraftingDataPacket; use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\Server; use pocketmine\Server;
use pocketmine\utils\Config; use pocketmine\utils\Config;
@ -35,19 +36,18 @@ use pocketmine\utils\UUID;
class CraftingManager{ class CraftingManager{
/** @var CraftingRecipe[] */ /** @var CraftingRecipe[] */
public $recipes = []; protected $recipes = [];
/** @var ShapedRecipe[][] */ /** @var ShapedRecipe[][] */
protected $shapedRecipes = []; protected $shapedRecipes = [];
/** @var ShapelessRecipe[][] */ /** @var ShapelessRecipe[][] */
protected $shapelessRecipes = []; protected $shapelessRecipes = [];
/** @var FurnaceRecipe[] */ /** @var FurnaceRecipe[] */
public $furnaceRecipes = []; protected $furnaceRecipes = [];
private static $RECIPE_COUNT = 0; private static $RECIPE_COUNT = 0;
/** @var CraftingDataPacket */ /** @var BatchPacket */
private $craftingDataCache; private $craftingDataCache;
public function __construct(){ public function __construct(){
@ -113,16 +113,21 @@ class CraftingManager{
$pk->encode(); $pk->encode();
$this->craftingDataCache = $pk; $batch = new BatchPacket();
$batch->addPacket($pk);
$batch->setCompressionLevel(Server::getInstance()->networkCompressionLevel);
$batch->encode();
$this->craftingDataCache = $batch;
Timings::$craftingDataCacheRebuildTimer->stopTiming(); Timings::$craftingDataCacheRebuildTimer->stopTiming();
} }
/** /**
* Returns a CraftingDataPacket for sending to players. Rebuilds the cache if it is outdated. * Returns a pre-compressed CraftingDataPacket for sending to players. Rebuilds the cache if it is not found.
* *
* @return CraftingDataPacket * @return BatchPacket
*/ */
public function getCraftingDataPacket() : CraftingDataPacket{ public function getCraftingDataPacket() : BatchPacket{
if($this->craftingDataCache === null){ if($this->craftingDataCache === null){
$this->buildCraftingDataCache(); $this->buildCraftingDataCache();
} }
@ -230,7 +235,7 @@ class CraftingManager{
/** @var Item[] $row */ /** @var Item[] $row */
foreach($map as $y => $row){ foreach($map as $y => $row){
foreach($row as $x => $item){ foreach($row as $x => $item){
$item = clone $item; $map[$y][$x] = clone $item;
} }
} }

View File

@ -26,6 +26,14 @@ namespace pocketmine\inventory;
use pocketmine\utils\UUID; use pocketmine\utils\UUID;
class MultiRecipe{ class MultiRecipe{
const TYPE_REPAIR_ITEM = "00000000-0000-0000-0000-000000000001";
const TYPE_MAP_EXTENDING = "D392B075-4BA1-40AE-8789-AF868D56F6CE";
const TYPE_MAP_CLONING = "85939755-BA10-4D9D-A4CC-EFB7A8E943C4";
const TYPE_MAP_UPGRADING = "AECD2294-4B94-434B-8667-4499BB2C9327";
const TYPE_BOOK_CLONING = "D1CA6B84-338E-4F2F-9C6B-76CC8B4BD98D";
const TYPE_BANNER_DUPLICATE = "B5C5D105-75A2-4076-AF2B-923EA2BF4BF0";
const TYPE_BANNER_ADD_PATTERN = "D81AAEAF-E172-4440-9225-868DF030D27B";
const TYPE_FIREWORKS = "00000000-0000-0000-0000-000000000002";
private $uuid; private $uuid;

View File

@ -211,7 +211,7 @@ class Explosion{
$this->level->setBlockIdAt($block->x, $block->y, $block->z, 0); $this->level->setBlockIdAt($block->x, $block->y, $block->z, 0);
$t = $this->level->getTile($block); $t = $this->level->getTileAt($block->x, $block->y, $block->z);
if($t instanceof Tile){ if($t instanceof Tile){
if($yieldDrops and $t instanceof Container){ if($yieldDrops and $t instanceof Container){
if($t instanceof Chest){ if($t instanceof Chest){

View File

@ -1962,17 +1962,33 @@ class Level implements ChunkManager, Metadatable{
} }
/** /**
* Returns the Tile in a position, or null if not found * Returns the Tile in a position, or null if not found.
*
* Note: This method wraps getTileAt(). If you're guaranteed to be passing integers, and you're using this method
* in performance-sensitive code, consider using getTileAt() instead of this method for better performance.
* *
* @param Vector3 $pos * @param Vector3 $pos
* *
* @return Tile|null * @return Tile|null
*/ */
public function getTile(Vector3 $pos){ public function getTile(Vector3 $pos) : ?Tile{
$chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false); return $this->getTileAt((int) floor($pos->x), (int) floor($pos->y), (int) floor($pos->z));
}
/**
* Returns the tile at the specified x,y,z coordinates, or null if it does not exist.
*
* @param int $x
* @param int $y
* @param int $z
*
* @return Tile|null
*/
public function getTileAt(int $x, int $y, int $z) : ?Tile{
$chunk = $this->getChunk($x >> 4, $z >> 4);
if($chunk !== null){ if($chunk !== null){
return $chunk->getTile($pos->x & 0x0f, $pos->y, $pos->z & 0x0f); return $chunk->getTile($x & 0x0f, $y, $z & 0x0f);
} }
return null; return null;

View File

@ -54,13 +54,9 @@ class Anvil extends McRegion{
if($subChunk->isEmpty()){ if($subChunk->isEmpty()){
continue; continue;
} }
$nbt->Sections[++$subChunks] = new CompoundTag("", [ $tag = $this->serializeSubChunk($subChunk);
new ByteTag("Y", $y), $tag->setByte("Y", $y);
new ByteArrayTag("Blocks", ChunkUtils::reorderByteArray($subChunk->getBlockIdArray())), //Generic in-memory chunks are currently always XZY $nbt->Sections[++$subChunks] = $tag;
new ByteArrayTag("Data", ChunkUtils::reorderNibbleArray($subChunk->getBlockDataArray())),
new ByteArrayTag("SkyLight", ChunkUtils::reorderNibbleArray($subChunk->getBlockSkyLightArray(), "\xff")),
new ByteArrayTag("BlockLight", ChunkUtils::reorderNibbleArray($subChunk->getBlockLightArray()))
]);
} }
$nbt->Biomes = new ByteArrayTag("Biomes", $chunk->getBiomeIdArray()); $nbt->Biomes = new ByteArrayTag("Biomes", $chunk->getBiomeIdArray());
@ -94,6 +90,15 @@ class Anvil extends McRegion{
return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL);
} }
protected function serializeSubChunk(SubChunk $subChunk) : CompoundTag{
return new CompoundTag("", [
new ByteArrayTag("Blocks", ChunkUtils::reorderByteArray($subChunk->getBlockIdArray())), //Generic in-memory chunks are currently always XZY
new ByteArrayTag("Data", ChunkUtils::reorderNibbleArray($subChunk->getBlockDataArray())),
new ByteArrayTag("SkyLight", ChunkUtils::reorderNibbleArray($subChunk->getBlockSkyLightArray(), "\xff")),
new ByteArrayTag("BlockLight", ChunkUtils::reorderNibbleArray($subChunk->getBlockLightArray()))
]);
}
public function nbtDeserialize(string $data){ public function nbtDeserialize(string $data){
$nbt = new NBT(NBT::BIG_ENDIAN); $nbt = new NBT(NBT::BIG_ENDIAN);
try{ try{
@ -111,12 +116,7 @@ class Anvil extends McRegion{
if($chunk->Sections instanceof ListTag){ if($chunk->Sections instanceof ListTag){
foreach($chunk->Sections as $subChunk){ foreach($chunk->Sections as $subChunk){
if($subChunk instanceof CompoundTag){ if($subChunk instanceof CompoundTag){
$subChunks[$subChunk->Y->getValue()] = new SubChunk( $subChunks[$subChunk->Y->getValue()] = $this->deserializeSubChunk($subChunk);
ChunkUtils::reorderByteArray($subChunk->Blocks->getValue()),
ChunkUtils::reorderNibbleArray($subChunk->Data->getValue()),
ChunkUtils::reorderNibbleArray($subChunk->SkyLight->getValue(), "\xff"),
ChunkUtils::reorderNibbleArray($subChunk->BlockLight->getValue())
);
} }
} }
} }
@ -148,6 +148,15 @@ class Anvil extends McRegion{
} }
} }
protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk{
return new SubChunk(
ChunkUtils::reorderByteArray($subChunk->Blocks->getValue()),
ChunkUtils::reorderNibbleArray($subChunk->Data->getValue()),
ChunkUtils::reorderNibbleArray($subChunk->SkyLight->getValue(), "\xff"),
ChunkUtils::reorderNibbleArray($subChunk->BlockLight->getValue())
);
}
public static function getProviderName() : string{ public static function getProviderName() : string{
return "anvil"; return "anvil";
} }

View File

@ -40,108 +40,22 @@ class PMAnvil extends Anvil{
const REGION_FILE_EXTENSION = "mcapm"; const REGION_FILE_EXTENSION = "mcapm";
public function nbtSerialize(Chunk $chunk) : string{ protected function serializeSubChunk(SubChunk $subChunk) : CompoundTag{
$nbt = new CompoundTag("Level", []); return new CompoundTag("", [
$nbt->xPos = new IntTag("xPos", $chunk->getX()); new ByteArrayTag("Blocks", $subChunk->getBlockIdArray()),
$nbt->zPos = new IntTag("zPos", $chunk->getZ()); new ByteArrayTag("Data", $subChunk->getBlockDataArray()),
new ByteArrayTag("SkyLight", $subChunk->getBlockSkyLightArray()),
$nbt->V = new ByteTag("V", 1); new ByteArrayTag("BlockLight", $subChunk->getBlockLightArray())
$nbt->LastUpdate = new LongTag("LastUpdate", 0); //TODO ]);
$nbt->InhabitedTime = new LongTag("InhabitedTime", 0); //TODO
$nbt->TerrainPopulated = new ByteTag("TerrainPopulated", $chunk->isPopulated() ? 1 : 0);
$nbt->LightPopulated = new ByteTag("LightPopulated", $chunk->isLightPopulated() ? 1 : 0);
$nbt->Sections = new ListTag("Sections", [], NBT::TAG_Compound);
$subChunks = -1;
foreach($chunk->getSubChunks() as $y => $subChunk){
if($subChunk->isEmpty()){
continue;
}
$nbt->Sections[++$subChunks] = new CompoundTag("", [
new ByteTag("Y", $y),
new ByteArrayTag("Blocks", $subChunk->getBlockIdArray()),
new ByteArrayTag("Data", $subChunk->getBlockDataArray()),
new ByteArrayTag("SkyLight", $subChunk->getBlockSkyLightArray()),
new ByteArrayTag("BlockLight", $subChunk->getBlockLightArray())
]);
}
$nbt->Biomes = new ByteArrayTag("Biomes", $chunk->getBiomeIdArray());
$nbt->HeightMap = new IntArrayTag("HeightMap", $chunk->getHeightMapArray());
$entities = [];
foreach($chunk->getEntities() as $entity){
if($entity->canSaveWithChunk() and !$entity->isClosed()){
$entity->saveNBT();
$entities[] = $entity->namedtag;
}
}
$nbt->Entities = new ListTag("Entities", $entities, NBT::TAG_Compound);
$tiles = [];
foreach($chunk->getTiles() as $tile){
$tile->saveNBT();
$tiles[] = $tile->namedtag;
}
$nbt->TileEntities = new ListTag("TileEntities", $tiles, NBT::TAG_Compound);
//TODO: TileTicks
$writer = new NBT(NBT::BIG_ENDIAN);
$nbt->setName("Level");
$writer->setData(new CompoundTag("", [$nbt]));
return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL);
} }
public function nbtDeserialize(string $data){ protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk{
$nbt = new NBT(NBT::BIG_ENDIAN); return new SubChunk(
try{ $subChunk->Blocks->getValue(),
$nbt->readCompressed($data); $subChunk->Data->getValue(),
$subChunk->SkyLight->getValue(),
$chunk = $nbt->getData(); $subChunk->BlockLight->getValue()
);
if(!isset($chunk->Level) or !($chunk->Level instanceof CompoundTag)){
throw new ChunkException("Invalid NBT format");
}
$chunk = $chunk->Level;
$subChunks = [];
if($chunk->Sections instanceof ListTag){
foreach($chunk->Sections as $subChunk){
if($subChunk instanceof CompoundTag){
$subChunks[$subChunk->Y->getValue()] = new SubChunk(
$subChunk->Blocks->getValue(),
$subChunk->Data->getValue(),
$subChunk->SkyLight->getValue(),
$subChunk->BlockLight->getValue()
);
}
}
}
$result = new Chunk(
$chunk["xPos"],
$chunk["zPos"],
$subChunks,
isset($chunk->Entities) ? $chunk->Entities->getValue() : [],
isset($chunk->TileEntities) ? $chunk->TileEntities->getValue() : [],
isset($chunk->Biomes) ? $chunk->Biomes->getValue() : "",
isset($chunk->HeightMap) ? $chunk->HeightMap->getValue() : []
);
$result->setLightPopulated(isset($chunk->LightPopulated) ? ((bool) $chunk->LightPopulated->getValue()) : false);
$result->setPopulated(isset($chunk->TerrainPopulated) ? ((bool) $chunk->TerrainPopulated->getValue()) : false);
$result->setGenerated(true);
return $result;
}catch(\Throwable $e){
MainLogger::getLogger()->logException($e);
return null;
}
} }
public static function getProviderName() : string{ public static function getProviderName() : string{

View File

@ -151,22 +151,24 @@ class CompoundTag extends NamedTag implements \ArrayAccess{
/** /**
* Returns the value of the child tag with the specified name, or $default if the tag doesn't exist. If the child * Returns the value of the child tag with the specified name, or $default if the tag doesn't exist. If the child
* tag is not of type $expectedType, an exception will be thrown. * tag is not of type $expectedType, an exception will be thrown, unless a default is given and $badTagDefault is
* true.
* *
* @param string $name * @param string $name
* @param string $expectedClass * @param string $expectedClass
* @param mixed $default * @param mixed $default
* @param bool $badTagDefault Return the specified default if the tag is not of the expected type.
* *
* @return mixed * @return mixed
*/ */
public function getTagValue(string $name, string $expectedClass, $default = null){ public function getTagValue(string $name, string $expectedClass, $default = null, bool $badTagDefault = false){
$tag = $this->getTag($name, $expectedClass); $tag = $this->getTag($name, $badTagDefault ? NamedTag::class : $expectedClass);
if($tag !== null){ if($tag instanceof $expectedClass){
return $tag->getValue(); return $tag->getValue();
} }
if($default === null){ if($default === null){
throw new \RuntimeException("Tag with name \"$name\" not found and no default value given"); throw new \RuntimeException("Tag with name \"$name\" " . ($tag !== null ? "not of expected type" : "not found") . " and no valid default value given");
} }
return $default; return $default;
@ -179,91 +181,100 @@ class CompoundTag extends NamedTag implements \ArrayAccess{
/** /**
* @param string $name * @param string $name
* @param int|null $default * @param int|null $default
* @param bool $badTagDefault
* *
* @return int * @return int
*/ */
public function getByte(string $name, ?int $default = null) : int{ public function getByte(string $name, ?int $default = null, bool $badTagDefault = false) : int{
return $this->getTagValue($name, ByteTag::class, $default); return $this->getTagValue($name, ByteTag::class, $default, $badTagDefault);
} }
/** /**
* @param string $name * @param string $name
* @param int|null $default * @param int|null $default
* @param bool $badTagDefault
* *
* @return int * @return int
*/ */
public function getShort(string $name, ?int $default = null) : int{ public function getShort(string $name, ?int $default = null, bool $badTagDefault = false) : int{
return $this->getTagValue($name, ShortTag::class, $default); return $this->getTagValue($name, ShortTag::class, $default, $badTagDefault);
} }
/** /**
* @param string $name * @param string $name
* @param int|null $default * @param int|null $default
* @param bool $badTagDefault
* *
* @return int * @return int
*/ */
public function getInt(string $name, ?int $default = null) : int{ public function getInt(string $name, ?int $default = null, bool $badTagDefault = false) : int{
return $this->getTagValue($name, IntTag::class, $default); return $this->getTagValue($name, IntTag::class, $default, $badTagDefault);
} }
/** /**
* @param string $name * @param string $name
* @param int|null $default * @param int|null $default
* @param bool $badTagDefault
* *
* @return int * @return int
*/ */
public function getLong(string $name, ?int $default = null) : int{ public function getLong(string $name, ?int $default = null, bool $badTagDefault = false) : int{
return $this->getTagValue($name, LongTag::class, $default); return $this->getTagValue($name, LongTag::class, $default, $badTagDefault);
} }
/** /**
* @param string $name * @param string $name
* @param float|null $default * @param float|null $default
* @param bool $badTagDefault
* *
* @return float * @return float
*/ */
public function getFloat(string $name, ?float $default = null) : float{ public function getFloat(string $name, ?float $default = null, bool $badTagDefault = false) : float{
return $this->getTagValue($name, FloatTag::class, $default); return $this->getTagValue($name, FloatTag::class, $default, $badTagDefault);
} }
/** /**
* @param string $name * @param string $name
* @param float|null $default * @param float|null $default
* @param bool $badTagDefault
* *
* @return float * @return float
*/ */
public function getDouble(string $name, ?float $default = null) : float{ public function getDouble(string $name, ?float $default = null, bool $badTagDefault = false) : float{
return $this->getTagValue($name, DoubleTag::class, $default); return $this->getTagValue($name, DoubleTag::class, $default, $badTagDefault);
} }
/** /**
* @param string $name * @param string $name
* @param null|string $default * @param string|null $default
* @param bool $badTagDefault
* *
* @return string * @return string
*/ */
public function getByteArray(string $name, ?string $default = null) : string{ public function getByteArray(string $name, ?string $default = null, bool $badTagDefault = false) : string{
return $this->getTagValue($name, ByteArrayTag::class, $default); return $this->getTagValue($name, ByteArrayTag::class, $default, $badTagDefault);
} }
/** /**
* @param string $name * @param string $name
* @param null|string $default * @param string|null $default
* @param bool $badTagDefault
* *
* @return string * @return string
*/ */
public function getString(string $name, ?string $default = null) : string{ public function getString(string $name, ?string $default = null, bool $badTagDefault = false) : string{
return $this->getTagValue($name, StringTag::class, $default); return $this->getTagValue($name, StringTag::class, $default, $badTagDefault);
} }
/** /**
* @param string $name * @param string $name
* @param int[]|null $default * @param int[]|null $default
* @param bool $badTagDefault
* *
* @return int[] * @return int[]
*/ */
public function getIntArray(string $name, ?array $default = null) : array{ public function getIntArray(string $name, ?array $default = null, bool $badTagDefault = false) : array{
return $this->getTagValue($name, IntArrayTag::class, $default); return $this->getTagValue($name, IntArrayTag::class, $default, $badTagDefault);
} }
/** /**
@ -273,11 +284,12 @@ class CompoundTag extends NamedTag implements \ArrayAccess{
* @param string $name Name of the tag to set * @param string $name Name of the tag to set
* @param string $tagClass Class that extends NamedTag * @param string $tagClass Class that extends NamedTag
* @param mixed $value Value to set. This should be compatible with the specified tag type. * @param mixed $value Value to set. This should be compatible with the specified tag type.
* @param bool $force Force set the value even if the existing tag is not the correct type (overwrite the old tag)
*/ */
public function setTagValue(string $name, string $tagClass, $value) : void{ public function setTagValue(string $name, string $tagClass, $value, bool $force) : void{
assert(is_a($tagClass, NamedTag::class, true)); assert(is_a($tagClass, NamedTag::class, true));
$tag = $this->getTag($name, $tagClass); $tag = $this->getTag($name, $force ? NamedTag::class : $tagClass);
if($tag !== null){ if($tag instanceof $tagClass){
$tag->setValue($value); $tag->setValue($value);
}else{ }else{
$this->setTag(new $tagClass($name, $value)); $this->setTag(new $tagClass($name, $value));
@ -291,73 +303,82 @@ class CompoundTag extends NamedTag implements \ArrayAccess{
/** /**
* @param string $name * @param string $name
* @param int $value * @param int $value
* @param bool $force
*/ */
public function setByte(string $name, int $value) : void{ public function setByte(string $name, int $value, bool $force = false) : void{
$this->setTagValue($name, ByteTag::class, $value); $this->setTagValue($name, ByteTag::class, $value, $force);
} }
/** /**
* @param string $name * @param string $name
* @param int $value * @param int $value
* @param bool $force
*/ */
public function setShort(string $name, int $value) : void{ public function setShort(string $name, int $value, bool $force = false) : void{
$this->setTagValue($name, ShortTag::class, $value); $this->setTagValue($name, ShortTag::class, $value, $force);
} }
/** /**
* @param string $name * @param string $name
* @param int $value * @param int $value
* @param bool $force
*/ */
public function setInt(string $name, int $value) : void{ public function setInt(string $name, int $value, bool $force = false) : void{
$this->setTagValue($name, IntTag::class, $value); $this->setTagValue($name, IntTag::class, $value, $force);
} }
/** /**
* @param string $name * @param string $name
* @param int $value * @param int $value
* @param bool $force
*/ */
public function setLong(string $name, int $value) : void{ public function setLong(string $name, int $value, bool $force = false) : void{
$this->setTagValue($name, LongTag::class, $value); $this->setTagValue($name, LongTag::class, $value, $force);
} }
/** /**
* @param string $name * @param string $name
* @param float $value * @param float $value
* @param bool $force
*/ */
public function setFloat(string $name, float $value) : void{ public function setFloat(string $name, float $value, bool $force = false) : void{
$this->setTagValue($name, FloatTag::class, $value); $this->setTagValue($name, FloatTag::class, $value, $force);
} }
/** /**
* @param string $name * @param string $name
* @param float $value * @param float $value
* @param bool $force
*/ */
public function setDouble(string $name, float $value) : void{ public function setDouble(string $name, float $value, bool $force = false) : void{
$this->setTagValue($name, DoubleTag::class, $value); $this->setTagValue($name, DoubleTag::class, $value, $force);
} }
/** /**
* @param string $name * @param string $name
* @param string $value * @param string $value
* @param bool $force
*/ */
public function setByteArray(string $name, string $value) : void{ public function setByteArray(string $name, string $value, bool $force = false) : void{
$this->setTagValue($name, ByteArrayTag::class, $value); $this->setTagValue($name, ByteArrayTag::class, $value, $force);
} }
/** /**
* @param string $name * @param string $name
* @param string $value * @param string $value
* @param bool $force
*/ */
public function setString(string $name, string $value) : void{ public function setString(string $name, string $value, bool $force = false) : void{
$this->setTagValue($name, StringTag::class, $value); $this->setTagValue($name, StringTag::class, $value, $force);
} }
/** /**
* @param string $name * @param string $name
* @param int[] $value * @param int[] $value
* @param bool $force
*/ */
public function setIntArray(string $name, array $value) : void{ public function setIntArray(string $name, array $value, bool $force = false) : void{
$this->setTagValue($name, IntArrayTag::class, $value); $this->setTagValue($name, IntArrayTag::class, $value, $force);
} }

View File

@ -31,17 +31,17 @@ class EntityPickRequestPacket extends DataPacket{
const NETWORK_ID = ProtocolInfo::ENTITY_PICK_REQUEST_PACKET; const NETWORK_ID = ProtocolInfo::ENTITY_PICK_REQUEST_PACKET;
/** @var int */ /** @var int */
public $entityTypeId; public $entityUniqueId;
/** @var int */ /** @var int */
public $hotbarSlot; public $hotbarSlot;
protected function decodePayload(){ protected function decodePayload(){
$this->entityTypeId = $this->getLLong(); $this->entityUniqueId = $this->getLLong();
$this->hotbarSlot = $this->getByte(); $this->hotbarSlot = $this->getByte();
} }
protected function encodePayload(){ protected function encodePayload(){
$this->putLLong($this->entityTypeId); $this->putLLong($this->entityUniqueId);
$this->putByte($this->hotbarSlot); $this->putByte($this->hotbarSlot);
} }

View File

@ -45,5 +45,6 @@ interface WindowTypes{
const STRUCTURE_EDITOR = 14; const STRUCTURE_EDITOR = 14;
const TRADING = 15; const TRADING = 15;
const COMMAND_BLOCK = 16; const COMMAND_BLOCK = 16;
const JUKEBOX = 17;
} }

View File

@ -35,8 +35,8 @@ class Bed extends Spawnable{
const TAG_COLOR = "color"; const TAG_COLOR = "color";
public function __construct(Level $level, CompoundTag $nbt){ public function __construct(Level $level, CompoundTag $nbt){
if(!$nbt->hasTag(self::TAG_COLOR, ByteTag::class)){ if(!$nbt->hasTag(self::TAG_COLOR, ByteTag::class)){ //TODO: check PC format
$nbt->setTag(new ByteTag(self::TAG_COLOR, 14)); //default to old red $nbt->setByte(self::TAG_COLOR, 14, true); //default to old red
} }
parent::__construct($level, $nbt); parent::__construct($level, $nbt);
} }

View File

@ -136,7 +136,7 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
*/ */
public function getPair() : ?Chest{ public function getPair() : ?Chest{
if($this->isPaired()){ if($this->isPaired()){
$tile = $this->getLevel()->getTile(new Vector3($this->namedtag->getInt(self::TAG_PAIRX), $this->y, $this->namedtag->getInt(self::TAG_PAIRZ))); $tile = $this->getLevel()->getTileAt($this->namedtag->getInt(self::TAG_PAIRX), $this->y, $this->namedtag->getInt(self::TAG_PAIRZ));
if($tile instanceof Chest){ if($tile instanceof Chest){
return $tile; return $tile;
} }
@ -152,8 +152,8 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
$this->createPair($tile); $this->createPair($tile);
$this->spawnToAll(); $this->onChanged();
$tile->spawnToAll(); $tile->onChanged();
$this->checkPairing(); $this->checkPairing();
return true; return true;
@ -175,12 +175,12 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
$tile = $this->getPair(); $tile = $this->getPair();
$this->namedtag->removeTag(self::TAG_PAIRX, self::TAG_PAIRZ); $this->namedtag->removeTag(self::TAG_PAIRX, self::TAG_PAIRZ);
$this->spawnToAll(); $this->onChanged();
if($tile instanceof Chest){ if($tile instanceof Chest){
$tile->namedtag->removeTag(self::TAG_PAIRX, self::TAG_PAIRZ); $tile->namedtag->removeTag(self::TAG_PAIRX, self::TAG_PAIRZ);
$tile->checkPairing(); $tile->checkPairing();
$tile->spawnToAll(); $tile->onChanged();
} }
$this->checkPairing(); $this->checkPairing();

View File

@ -37,11 +37,12 @@ class FlowerPot extends Spawnable{
const TAG_ITEM_DATA = "mData"; const TAG_ITEM_DATA = "mData";
public function __construct(Level $level, CompoundTag $nbt){ public function __construct(Level $level, CompoundTag $nbt){
//TODO: check PC format
if(!$nbt->hasTag(self::TAG_ITEM, ShortTag::class)){ if(!$nbt->hasTag(self::TAG_ITEM, ShortTag::class)){
$nbt->setTag(new ShortTag(self::TAG_ITEM, 0)); $nbt->setShort(self::TAG_ITEM, 0, true);
} }
if(!$nbt->hasTag(self::TAG_ITEM_DATA, IntTag::class)){ if(!$nbt->hasTag(self::TAG_ITEM_DATA, IntTag::class)){
$nbt->setTag(new IntTag(self::TAG_ITEM_DATA, 0)); $nbt->setInt(self::TAG_ITEM_DATA, 0, true);
} }
parent::__construct($level, $nbt); parent::__construct($level, $nbt);
} }

View File

@ -54,7 +54,7 @@ class Furnace extends Spawnable implements InventoryHolder, Container, Nameable{
public function __construct(Level $level, CompoundTag $nbt){ public function __construct(Level $level, CompoundTag $nbt){
if(!$nbt->hasTag(self::TAG_BURN_TIME, ShortTag::class) or $nbt->getShort(self::TAG_BURN_TIME) < 0){ if(!$nbt->hasTag(self::TAG_BURN_TIME, ShortTag::class) or $nbt->getShort(self::TAG_BURN_TIME) < 0){
$nbt->setTag(new ShortTag(self::TAG_BURN_TIME, 0)); $nbt->setShort(self::TAG_BURN_TIME, 0, true);
} }
if( if(
@ -62,16 +62,16 @@ class Furnace extends Spawnable implements InventoryHolder, Container, Nameable{
$nbt->getShort(self::TAG_COOK_TIME) < 0 or $nbt->getShort(self::TAG_COOK_TIME) < 0 or
($nbt->getShort(self::TAG_BURN_TIME) === 0 and $nbt->getShort(self::TAG_COOK_TIME) > 0) ($nbt->getShort(self::TAG_BURN_TIME) === 0 and $nbt->getShort(self::TAG_COOK_TIME) > 0)
){ ){
$nbt->setTag(new ShortTag(self::TAG_COOK_TIME, 0)); $nbt->setShort(self::TAG_COOK_TIME, 0, true);
} }
if(!$nbt->hasTag(self::TAG_MAX_TIME, ShortTag::class)){ if(!$nbt->hasTag(self::TAG_MAX_TIME, ShortTag::class)){
$nbt->setTag(new ShortTag(self::TAG_MAX_TIME, $nbt->getShort(self::TAG_BURN_TIME))); $nbt->setShort(self::TAG_MAX_TIME, $nbt->getShort(self::TAG_BURN_TIME), true);
$nbt->removeTag(self::TAG_BURN_TICKS); $nbt->removeTag(self::TAG_BURN_TICKS);
} }
if(!$nbt->getTag(self::TAG_BURN_TICKS, ShortTag::class)){ if(!$nbt->getTag(self::TAG_BURN_TICKS, ShortTag::class)){
$nbt->setTag(new ShortTag(self::TAG_BURN_TICKS, 0)); $nbt->setShort(self::TAG_BURN_TICKS, 0, true);
} }
parent::__construct($level, $nbt); parent::__construct($level, $nbt);

View File

@ -39,11 +39,11 @@ class ItemFrame extends Spawnable{
public function __construct(Level $level, CompoundTag $nbt){ public function __construct(Level $level, CompoundTag $nbt){
if(!$nbt->hasTag(self::TAG_ITEM_ROTATION, ByteTag::class)){ if(!$nbt->hasTag(self::TAG_ITEM_ROTATION, ByteTag::class)){
$nbt->setTag(new ByteTag(self::TAG_ITEM_ROTATION, 0)); $nbt->setByte(self::TAG_ITEM_ROTATION, 0, true);
} }
if(!$nbt->hasTag(self::TAG_ITEM_DROP_CHANCE, FloatTag::class)){ if(!$nbt->hasTag(self::TAG_ITEM_DROP_CHANCE, FloatTag::class)){
$nbt->setTag(new FloatTag(self::TAG_ITEM_DROP_CHANCE, 1.0)); $nbt->setFloat(self::TAG_ITEM_DROP_CHANCE, 1.0, true);
} }
parent::__construct($level, $nbt); parent::__construct($level, $nbt);

View File

@ -38,15 +38,17 @@ class Skull extends Spawnable{
const TYPE_CREEPER = 4; const TYPE_CREEPER = 4;
const TYPE_DRAGON = 5; const TYPE_DRAGON = 5;
const TAG_SKULL_TYPE = "SkullType"; const TAG_SKULL_TYPE = "SkullType"; //TAG_Byte
const TAG_ROT = "Rot"; const TAG_ROT = "Rot"; //TAG_Byte
const TAG_MOUTH_MOVING = "MouthMoving"; //TAG_Byte
const TAG_MOUTH_TICK_COUNT = "MouthTickCount"; //TAG_Int
public function __construct(Level $level, CompoundTag $nbt){ public function __construct(Level $level, CompoundTag $nbt){
if(!$nbt->hasTag(self::TAG_SKULL_TYPE, ByteTag::class)){ if(!$nbt->hasTag(self::TAG_SKULL_TYPE, ByteTag::class)){
$nbt->setTag(new ByteTag(self::TAG_SKULL_TYPE, 0)); $nbt->setByte(self::TAG_SKULL_TYPE, 0, true);
} }
if(!$nbt->hasTag(self::TAG_ROT, ByteTag::class)){ if(!$nbt->hasTag(self::TAG_ROT, ByteTag::class)){
$nbt->setTag(new ByteTag(self::TAG_ROT, 0)); $nbt->setByte(self::TAG_ROT, 0, true);
} }
parent::__construct($level, $nbt); parent::__construct($level, $nbt);
} }

View File

@ -164,8 +164,7 @@ class Binary{
/** /**
* Reads a 16-bit signed little-endian number * Reads a 16-bit signed little-endian number
* *
* @param $str * @param string $str
*
* @return int * @return int
*/ */
public static function readSignedLShort(string $str) : int{ public static function readSignedLShort(string $str) : int{
@ -175,8 +174,7 @@ class Binary{
/** /**
* Writes a 16-bit signed/unsigned little-endian number * Writes a 16-bit signed/unsigned little-endian number
* *
* @param $value * @param int $value
*
* @return string * @return string
*/ */
public static function writeLShort(int $value) : string{ public static function writeLShort(int $value) : string{
@ -379,12 +377,11 @@ class Binary{
/** /**
* Reads an 8-byte integer. * Reads an 8-byte integer.
* *
* @param string $x * @param string $str
* @return int * @return int
*/ */
public static function readLong(string $x) : int{ public static function readLong(string $str) : int{
$int = unpack("N*", $x); return unpack("J", $str)[1];
return ($int[1] << 32) | $int[2];
} }
/** /**
@ -394,7 +391,7 @@ class Binary{
* @return string * @return string
*/ */
public static function writeLong(int $value) : string{ public static function writeLong(int $value) : string{
return pack("NN", $value >> 32, $value & 0xFFFFFFFF); return pack("J", $value);
} }
/** /**
@ -404,7 +401,7 @@ class Binary{
* @return int * @return int
*/ */
public static function readLLong(string $str) : int{ public static function readLLong(string $str) : int{
return self::readLong(strrev($str)); return unpack("P", $str)[1];
} }
/** /**
@ -414,7 +411,7 @@ class Binary{
* @return string * @return string
*/ */
public static function writeLLong(int $value) : string{ public static function writeLLong(int $value) : string{
return strrev(self::writeLong($value)); return pack("P", $value);
} }

View File

@ -342,7 +342,7 @@ class Config{
} }
$base = $value; $base = $value;
$this->nestedCache[$key] = $value; $this->nestedCache = [];
} }
/** /**
@ -376,6 +376,26 @@ class Config{
return $this->nestedCache[$key] = $base; return $this->nestedCache[$key] = $base;
} }
public function removeNested(string $key) : void{
$this->nestedCache = [];
$vars = explode(".", $key);
$currentNode =& $this->config;
while(count($vars) > 0){
$nodeName = array_shift($vars);
if(isset($currentNode[$nodeName])){
if(empty($vars)){ //final node
unset($currentNode[$nodeName]);
}elseif(is_array($currentNode[$nodeName])){
$currentNode =& $currentNode[$nodeName];
}
}else{
break;
}
}
}
/** /**
* @param $k * @param $k
* @param mixed $default * @param mixed $default

View File

@ -119,6 +119,10 @@ class MainLogger extends \AttachableThreadedLogger{
$this->logDebug = $logDebug; $this->logDebug = $logDebug;
} }
/**
* @param \Throwable $e
* @param array|null $trace
*/
public function logException(\Throwable $e, $trace = null){ public function logException(\Throwable $e, $trace = null){
if($trace === null){ if($trace === null){
$trace = $e->getTrace(); $trace = $e->getTrace();

@ -1 +1 @@
Subproject commit 9142e7decec23b302ab470a55e5ed160df4878db Subproject commit a10881b0078559f646b570a12304dba71658357c

@ -1 +1 @@
Subproject commit 4e28d74c9aafdf51b10bc274ddcf78fcb3b317d4 Subproject commit 3446bcc774bcc278430e2cecd9d46a47f491656f