From 371eccd0079a1916c3c0802390920cd878631553 Mon Sep 17 00:00:00 2001 From: Max <43801744+ItsMax123@users.noreply.github.com> Date: Tue, 7 May 2024 07:02:50 -0400 Subject: [PATCH 01/29] Make access modifier consistent with parent abstract class (#6341) --- src/item/VanillaItems.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index bbd0dfc01..5115ee48a 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -591,12 +591,12 @@ final class VanillaItems{ } }); self::register("squid_spawn_egg", new class(new IID(Ids::SQUID_SPAWN_EGG), "Squid Spawn Egg") extends SpawnEgg{ - public function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{ + protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{ return new Squid(Location::fromObject($pos, $world, $yaw, $pitch)); } }); self::register("villager_spawn_egg", new class(new IID(Ids::VILLAGER_SPAWN_EGG), "Villager Spawn Egg") extends SpawnEgg{ - public function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{ + protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity{ return new Villager(Location::fromObject($pos, $world, $yaw, $pitch)); } }); From b342c497d1f985079f6632930eefc0fd64362ac8 Mon Sep 17 00:00:00 2001 From: ipad54 Date: Sun, 23 Jun 2024 13:27:52 +0300 Subject: [PATCH 02/29] Added 1.21 banner patterns. --- src/block/utils/BannerPatternType.php | 2 ++ src/data/bedrock/BannerPatternTypeIdMap.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/block/utils/BannerPatternType.php b/src/block/utils/BannerPatternType.php index cd63226a7..9b1963603 100644 --- a/src/block/utils/BannerPatternType.php +++ b/src/block/utils/BannerPatternType.php @@ -81,10 +81,12 @@ enum BannerPatternType{ case DIAGONAL_RIGHT; case DIAGONAL_UP_LEFT; case DIAGONAL_UP_RIGHT; + case FLOW; case FLOWER; case GLOBE; case GRADIENT; case GRADIENT_UP; + case GUSTER; case HALF_HORIZONTAL; case HALF_HORIZONTAL_BOTTOM; case HALF_VERTICAL; diff --git a/src/data/bedrock/BannerPatternTypeIdMap.php b/src/data/bedrock/BannerPatternTypeIdMap.php index d1884350f..7d4353d4f 100644 --- a/src/data/bedrock/BannerPatternTypeIdMap.php +++ b/src/data/bedrock/BannerPatternTypeIdMap.php @@ -56,9 +56,11 @@ final class BannerPatternTypeIdMap{ BannerPatternType::DIAGONAL_UP_LEFT => "ld", BannerPatternType::DIAGONAL_UP_RIGHT => "rud", BannerPatternType::FLOWER => "flo", + BannerPatternType::FLOW => "flw", BannerPatternType::GLOBE => "glb", BannerPatternType::GRADIENT => "gra", BannerPatternType::GRADIENT_UP => "gru", + BannerPatternType::GUSTER => "gus", BannerPatternType::HALF_HORIZONTAL => "hh", BannerPatternType::HALF_HORIZONTAL_BOTTOM => "hhb", BannerPatternType::HALF_VERTICAL => "vh", From 2ffc38c835466ee9b202448b456fb99860aac9c3 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Sun, 7 Jul 2024 23:01:34 +0300 Subject: [PATCH 03/29] Implement campfire & soul campfire (#4696) --- src/block/BlockTypeIds.php | 4 +- src/block/Campfire.php | 277 ++++++++++++++++++ src/block/SoulCampfire.php | 48 +++ src/block/VanillaBlocks.php | 8 + src/block/inventory/CampfireInventory.php | 40 +++ src/block/tile/Campfire.php | 143 +++++++++ src/block/tile/TileFactory.php | 2 +- .../CraftingManagerFromDataHelper.php | 3 +- src/crafting/FurnaceType.php | 6 + .../convert/BlockObjectToStateSerializer.php | 12 + .../BlockStateToObjectDeserializer.php | 10 + .../ItemSerializerDeserializerRegistrar.php | 2 + src/event/block/CampfireCookEvent.php | 63 ++++ src/item/StringToItemParser.php | 2 + src/network/mcpe/cache/CraftingDataCache.php | 2 + src/world/sound/CampfireSound.php | 35 +++ tests/phpstan/configs/actual-problems.neon | 5 + .../block_factory_consistency_check.json | 2 + 18 files changed, 661 insertions(+), 3 deletions(-) create mode 100644 src/block/Campfire.php create mode 100644 src/block/SoulCampfire.php create mode 100644 src/block/inventory/CampfireInventory.php create mode 100644 src/block/tile/Campfire.php create mode 100644 src/event/block/CampfireCookEvent.php create mode 100644 src/world/sound/CampfireSound.php diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index dec31eff1..59c358489 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -745,8 +745,10 @@ final class BlockTypeIds{ public const PITCHER_PLANT = 10715; public const PITCHER_CROP = 10716; public const DOUBLE_PITCHER_CROP = 10717; + public const CAMPFIRE = 10718; + public const SOUL_CAMPFIRE = 10719; - public const FIRST_UNUSED_BLOCK_ID = 10718; + public const FIRST_UNUSED_BLOCK_ID = 10720; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/Campfire.php b/src/block/Campfire.php new file mode 100644 index 000000000..ce759ee87 --- /dev/null +++ b/src/block/Campfire.php @@ -0,0 +1,277 @@ + ticks + * @phpstan-var array + */ + protected array $cookingTimes = []; + + protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ + $this->encodeFacingState($w); + $this->encodeLitState($w); + } + + public function readStateFromWorld() : Block{ + parent::readStateFromWorld(); + $tile = $this->position->getWorld()->getTile($this->position); + if($tile instanceof TileCampfire){ + $this->inventory = $tile->getInventory(); + $this->cookingTimes = $tile->getCookingTimes(); + }else{ + $this->inventory = new CampfireInventory($this->position); + } + + return $this; + } + + public function writeStateToWorld() : void{ + parent::writeStateToWorld(); + $tile = $this->position->getWorld()->getTile($this->position); + if($tile instanceof TileCampfire){ + $tile->setCookingTimes($this->cookingTimes); + } + } + + public function hasEntityCollision() : bool{ + return true; + } + + public function getLightLevel() : int{ + return $this->lit ? 15 : 0; + } + + public function isAffectedBySilkTouch() : bool{ + return true; + } + + public function getDropsForCompatibleTool(Item $item) : array{ + return [ + VanillaItems::CHARCOAL()->setCount(2) + ]; + } + + public function getSupportType(int $facing) : SupportType{ + return SupportType::NONE; + } + + protected function recalculateCollisionBoxes() : array{ + return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)]; + } + + public function getInventory() : CampfireInventory{ + return $this->inventory; + } + + protected function getFurnaceType() : FurnaceType{ + return FurnaceType::CAMPFIRE; + } + + protected function getEntityCollisionDamage() : int{ + return 1; + } + + /** + * Sets the number of ticks during the item in the given slot has been cooked. + */ + public function setCookingTime(int $slot, int $time) : void{ + if($slot < 0 || $slot > 3){ + throw new \InvalidArgumentException("Slot must be in range 0-3"); + } + if($time < 0 || $time > $this->getFurnaceType()->getCookDurationTicks()){ + throw new \InvalidArgumentException("CookingTime must be in range 0-" . $this->getFurnaceType()->getCookDurationTicks()); + } + $this->cookingTimes[$slot] = $time; + } + + /** + * Returns the number of ticks during the item in the given slot has been cooked. + */ + public function getCookingTime(int $slot) : int{ + return $this->cookingTimes[$slot] ?? 0; + } + + public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ + if($this->getSide(Facing::DOWN) instanceof Campfire){ + return false; + } + if($player !== null){ + $this->facing = $player->getHorizontalFacing(); + } + $this->lit = true; + return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); + } + + public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ + if(!$this->lit){ + if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE){ + $item->pop(); + $this->ignite(); + $this->position->getWorld()->addSound($this->position, new BlazeShootSound()); + return true; + }elseif($item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){ + if($item instanceof Durable){ + $item->applyDamage(1); + } + $this->ignite(); + return true; + } + }elseif($item instanceof Shovel){ + $item->applyDamage(1); + $this->extinguish(); + return true; + } + + if($this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($this->getFurnaceType())->match($item) !== null){ + $ingredient = clone $item; + $ingredient->setCount(1); + if(count($this->inventory->addItem($ingredient)) === 0){ + $item->pop(); + $this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound()); + return true; + } + } + return false; + } + + public function onNearbyBlockChange() : void{ + if($this->lit && $this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::WATER){ + $this->extinguish(); + //TODO: Waterlogging + } + } + + public function onEntityInside(Entity $entity) : bool{ + if(!$this->lit){ + if($entity->isOnFire()){ + $this->ignite(); + return false; + } + }elseif($entity instanceof Living){ + $entity->attack(new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, $this->getEntityCollisionDamage())); + } + return true; + } + + public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{ + if($this->lit && $projectile instanceof SplashPotion && $projectile->getPotionType() === PotionType::WATER){ + $this->extinguish(); + } + } + + public function onScheduledUpdate() : void{ + if($this->lit){ + $items = $this->inventory->getContents(); + $furnaceType = $this->getFurnaceType(); + $maxCookDuration = $furnaceType->getCookDurationTicks(); + foreach($items as $slot => $item){ + $this->setCookingTime($slot, min($maxCookDuration, $this->getCookingTime($slot) + self::UPDATE_INTERVAL_TICKS)); + if($this->getCookingTime($slot) >= $maxCookDuration){ + $result = + ($recipe = $this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($furnaceType)->match($item)) instanceof FurnaceRecipe ? + $recipe->getResult() : + VanillaItems::AIR(); + + $ev = new CampfireCookEvent($this, $slot, $item, $result); + $ev->call(); + + if ($ev->isCancelled()){ + continue; + } + + $this->inventory->setItem($slot, VanillaItems::AIR()); + $this->setCookingTime($slot, 0); + $this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $ev->getResult()); + } + } + if(count($items) > 0){ + $this->position->getWorld()->setBlock($this->position, $this); + } + if(mt_rand(1, 6) === 1){ + $this->position->getWorld()->addSound($this->position, $furnaceType->getCookSound()); + } + $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS); + } + } + + private function extinguish() : void{ + $this->position->getWorld()->addSound($this->position, new FireExtinguishSound()); + $this->position->getWorld()->setBlock($this->position, $this->setLit(false)); + } + + private function ignite() : void{ + $this->position->getWorld()->addSound($this->position, new FlintSteelSound()); + $this->position->getWorld()->setBlock($this->position, $this->setLit(true)); + $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS); + } +} diff --git a/src/block/SoulCampfire.php b/src/block/SoulCampfire.php new file mode 100644 index 000000000..a9c8fc918 --- /dev/null +++ b/src/block/SoulCampfire.php @@ -0,0 +1,48 @@ +lit ? 10 : 0; + } + + public function getDropsForCompatibleTool(Item $item) : array{ + return [ + VanillaBlocks::SOUL_SOIL()->asItem() + ]; + } + + protected function getEntityCollisionDamage() : int{ + return 2; + } + + protected function getFurnaceType() : FurnaceType{ + return FurnaceType::SOUL_CAMPFIRE; + } +} diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 9c0e7d3b7..7733f4359 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -36,6 +36,7 @@ use pocketmine\block\tile\Bed as TileBed; use pocketmine\block\tile\Bell as TileBell; use pocketmine\block\tile\BlastFurnace as TileBlastFurnace; use pocketmine\block\tile\BrewingStand as TileBrewingStand; +use pocketmine\block\tile\Campfire as TileCampfire; use pocketmine\block\tile\Cauldron as TileCauldron; use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\tile\ChiseledBookshelf as TileChiseledBookshelf; @@ -154,6 +155,7 @@ use function strtolower; * @method static CakeWithCandle CAKE_WITH_CANDLE() * @method static CakeWithDyedCandle CAKE_WITH_DYED_CANDLE() * @method static Opaque CALCITE() + * @method static Campfire CAMPFIRE() * @method static Candle CANDLE() * @method static Carpet CARPET() * @method static Carrot CARROTS() @@ -685,6 +687,7 @@ use function strtolower; * @method static Slab SMOOTH_STONE_SLAB() * @method static Snow SNOW() * @method static SnowLayer SNOW_LAYER() + * @method static SoulCampfire SOUL_CAMPFIRE() * @method static SoulFire SOUL_FIRE() * @method static Lantern SOUL_LANTERN() * @method static SoulSand SOUL_SAND() @@ -826,6 +829,11 @@ final class VanillaBlocks{ self::register("brown_mushroom", new BrownMushroom(new BID(Ids::BROWN_MUSHROOM), "Brown Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS]))); self::register("cactus", new Cactus(new BID(Ids::CACTUS), "Cactus", new Info(new BreakInfo(0.4), [Tags::POTTABLE_PLANTS]))); self::register("cake", new Cake(new BID(Ids::CAKE), "Cake", new Info(new BreakInfo(0.5)))); + + $campfireBreakInfo = new Info(BreakInfo::axe(2.0)); + self::register("campfire", new Campfire(new BID(Ids::CAMPFIRE, TileCampfire::class), "Campfire", $campfireBreakInfo)); + self::register("soul_campfire", new SoulCampfire(new BID(Ids::SOUL_CAMPFIRE, TileCampfire::class), "Soul Campfire", $campfireBreakInfo)); + self::register("carrots", new Carrot(new BID(Ids::CARROTS), "Carrot Block", new Info(BreakInfo::instant()))); $chestBreakInfo = new Info(BreakInfo::axe(2.5)); diff --git a/src/block/inventory/CampfireInventory.php b/src/block/inventory/CampfireInventory.php new file mode 100644 index 000000000..ae762473e --- /dev/null +++ b/src/block/inventory/CampfireInventory.php @@ -0,0 +1,40 @@ +holder = $holder; + parent::__construct(4); + } + + public function getMaxStackSize() : int{ + return 1; + } +} diff --git a/src/block/tile/Campfire.php b/src/block/tile/Campfire.php new file mode 100644 index 000000000..ad4a193d7 --- /dev/null +++ b/src/block/tile/Campfire.php @@ -0,0 +1,143 @@ + */ + private array $cookingTimes = []; + + public function __construct(World $world, Vector3 $pos){ + parent::__construct($world, $pos); + $this->inventory = new CampfireInventory($this->position); + $this->inventory->getListeners()->add(CallbackInventoryListener::onAnyChange( + static function(Inventory $unused) use ($world, $pos) : void{ + $block = $world->getBlock($pos); + if($block instanceof BlockCampfire){ + $world->setBlock($pos, $block); + } + }) + ); + } + + public function getInventory() : CampfireInventory{ + return $this->inventory; + } + + public function getRealInventory() : CampfireInventory{ + return $this->inventory; + } + + /** + * @return int[] + * @phpstan-return array + */ + public function getCookingTimes() : array{ + return $this->cookingTimes; + } + + /** + * @param int[] $cookingTimes + * @phpstan-param array $cookingTimes + */ + public function setCookingTimes(array $cookingTimes) : void{ + $this->cookingTimes = $cookingTimes; + } + + public function readSaveData(CompoundTag $nbt) : void{ + $items = []; + $listeners = $this->inventory->getListeners()->toArray(); + $this->inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization + + foreach([ + [0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME], + [1, self::TAG_SECOND_INPUT_ITEM, self::TAG_SECOND_COOKING_TIME], + [2, self::TAG_THIRD_INPUT_ITEM, self::TAG_THIRD_COOKING_TIME], + [3, self::TAG_FOURTH_INPUT_ITEM, self::TAG_FOURTH_COOKING_TIME], + ] as [$slot, $itemTag, $cookingTimeTag]){ + if(($tag = $nbt->getTag($itemTag)) instanceof CompoundTag){ + $items[$slot] = Item::nbtDeserialize($tag); + } + if(($tag = $nbt->getTag($cookingTimeTag)) instanceof IntTag){ + $this->cookingTimes[$slot] = $tag->getValue(); + } + } + $this->inventory->setContents($items); + $this->inventory->getListeners()->add(...$listeners); + } + + protected function writeSaveData(CompoundTag $nbt) : void{ + foreach([ + [0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME], + [1, self::TAG_SECOND_INPUT_ITEM, self::TAG_SECOND_COOKING_TIME], + [2, self::TAG_THIRD_INPUT_ITEM, self::TAG_THIRD_COOKING_TIME], + [3, self::TAG_FOURTH_INPUT_ITEM, self::TAG_FOURTH_COOKING_TIME], + ] as [$slot, $itemTag, $cookingTimeTag]){ + $item = $this->inventory->getItem($slot); + if(!$item->isNull()){ + $nbt->setTag($itemTag, $item->nbtSerialize()); + if(isset($this->cookingTimes[$slot])){ + $nbt->setInt($cookingTimeTag, $this->cookingTimes[$slot]); + } + } + } + } + + protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ + foreach([ + 0 => self::TAG_FIRST_INPUT_ITEM, + 1 => self::TAG_SECOND_INPUT_ITEM, + 2 => self::TAG_THIRD_INPUT_ITEM, + 3 => self::TAG_FOURTH_INPUT_ITEM + ] as $slot => $tag){ + $item = $this->inventory->getItem($slot); + if(!$item->isNull()){ + $nbt->setTag($tag, TypeConverter::getInstance()->getItemTranslator()->toNetworkNbt($item)); + } + } + } +} diff --git a/src/block/tile/TileFactory.php b/src/block/tile/TileFactory.php index 6e87b72ac..515dd8c63 100644 --- a/src/block/tile/TileFactory.php +++ b/src/block/tile/TileFactory.php @@ -57,6 +57,7 @@ final class TileFactory{ $this->register(Bell::class, ["Bell", "minecraft:bell"]); $this->register(BlastFurnace::class, ["BlastFurnace", "minecraft:blast_furnace"]); $this->register(BrewingStand::class, ["BrewingStand", "minecraft:brewing_stand"]); + $this->register(Campfire::class, ["Campfire", "minecraft:campfire"]); $this->register(Cauldron::class, ["Cauldron", "minecraft:cauldron"]); $this->register(Chest::class, ["Chest", "minecraft:chest"]); $this->register(ChiseledBookshelf::class, ["ChiseledBookshelf", "minecraft:chiseled_bookshelf"]); @@ -79,7 +80,6 @@ final class TileFactory{ $this->register(MobHead::class, ["Skull", "minecraft:skull"]); $this->register(GlowingItemFrame::class, ["GlowItemFrame"]); - //TODO: Campfire //TODO: ChalkboardBlock //TODO: ChemistryTable //TODO: CommandBlock diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index 6e5bbcd1f..616c2a4bd 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -275,7 +275,8 @@ final class CraftingManagerFromDataHelper{ "furnace" => FurnaceType::FURNACE, "blast_furnace" => FurnaceType::BLAST_FURNACE, "smoker" => FurnaceType::SMOKER, - //TODO: campfire + "campfire" => FurnaceType::CAMPFIRE, + "soul_campfire" => FurnaceType::SOUL_CAMPFIRE, default => null }; if($furnaceType === null){ diff --git a/src/crafting/FurnaceType.php b/src/crafting/FurnaceType.php index 0ce5b72ce..89834c821 100644 --- a/src/crafting/FurnaceType.php +++ b/src/crafting/FurnaceType.php @@ -25,6 +25,7 @@ namespace pocketmine\crafting; use pocketmine\utils\LegacyEnumShimTrait; use pocketmine\world\sound\BlastFurnaceSound; +use pocketmine\world\sound\CampfireSound; use pocketmine\world\sound\FurnaceSound; use pocketmine\world\sound\SmokerSound; use pocketmine\world\sound\Sound; @@ -35,8 +36,10 @@ use function spl_object_id; * These are retained for backwards compatibility only. * * @method static FurnaceType BLAST_FURNACE() + * @method static FurnaceType CAMPFIRE() * @method static FurnaceType FURNACE() * @method static FurnaceType SMOKER() + * @method static FurnaceType SOUL_CAMPFIRE() * * @phpstan-type TMetadata array{0: int, 1: Sound} */ @@ -46,6 +49,8 @@ enum FurnaceType{ case FURNACE; case BLAST_FURNACE; case SMOKER; + case CAMPFIRE; + case SOUL_CAMPFIRE; /** * @phpstan-return TMetadata @@ -58,6 +63,7 @@ enum FurnaceType{ self::FURNACE => [200, new FurnaceSound()], self::BLAST_FURNACE => [100, new BlastFurnaceSound()], self::SMOKER => [100, new SmokerSound()], + self::CAMPFIRE, self::SOUL_CAMPFIRE => [600, new CampfireSound()] }; } diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index ab98e75f3..a8ca150ba 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -43,6 +43,7 @@ use pocketmine\block\Cactus; use pocketmine\block\Cake; use pocketmine\block\CakeWithCandle; use pocketmine\block\CakeWithDyedCandle; +use pocketmine\block\Campfire; use pocketmine\block\Candle; use pocketmine\block\Carpet; use pocketmine\block\Carrot; @@ -124,6 +125,7 @@ use pocketmine\block\SimplePressurePlate; use pocketmine\block\Slab; use pocketmine\block\SmallDripleaf; use pocketmine\block\SnowLayer; +use pocketmine\block\SoulCampfire; use pocketmine\block\Sponge; use pocketmine\block\StainedGlass; use pocketmine\block\StainedGlassPane; @@ -1160,6 +1162,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return Writer::create(Ids::CAKE) ->writeInt(StateNames::BITE_COUNTER, $block->getBites()); }); + $this->map(Blocks::CAMPFIRE(), function(Campfire $block) : Writer{ + return Writer::create(Ids::CAMPFIRE) + ->writeCardinalHorizontalFacing($block->getFacing()) + ->writeBool(StateNames::EXTINGUISHED, !$block->isLit()); + }); $this->map(Blocks::CARROTS(), fn(Carrot $block) => Helper::encodeCrops($block, new Writer(Ids::CARROTS))); $this->map(Blocks::CARVED_PUMPKIN(), function(CarvedPumpkin $block) : Writer{ return Writer::create(Ids::CARVED_PUMPKIN) @@ -1638,6 +1645,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeBool(StateNames::COVERED_BIT, false) ->writeInt(StateNames::HEIGHT, $block->getLayers() - 1); }); + $this->map(Blocks::SOUL_CAMPFIRE(), function(SoulCampfire $block) : Writer{ + return Writer::create(Ids::SOUL_CAMPFIRE) + ->writeCardinalHorizontalFacing($block->getFacing()) + ->writeBool(StateNames::EXTINGUISHED, !$block->isLit()); + }); $this->map(Blocks::SOUL_FIRE(), function() : Writer{ return Writer::create(Ids::SOUL_FIRE) ->writeInt(StateNames::AGE, 0); //useless for soul fire, we don't track it diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 4c9ec4b79..40478f197 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -1039,6 +1039,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return Blocks::CAKE() ->setBites($in->readBoundedInt(StateNames::BITE_COUNTER, 0, 6)); }); + $this->map(Ids::CAMPFIRE, function(Reader $in) : Block{ + return Blocks::CAMPFIRE() + ->setFacing($in->readCardinalHorizontalFacing()) + ->setLit(!$in->readBool(StateNames::EXTINGUISHED)); + }); $this->map(Ids::CARROTS, fn(Reader $in) => Helper::decodeCrops(Blocks::CARROTS(), $in)); $this->map(Ids::CARVED_PUMPKIN, function(Reader $in) : Block{ return Blocks::CARVED_PUMPKIN() @@ -1525,6 +1530,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $in->ignored(StateNames::COVERED_BIT); //seems to be useless return Blocks::SNOW_LAYER()->setLayers($in->readBoundedInt(StateNames::HEIGHT, 0, 7) + 1); }); + $this->map(Ids::SOUL_CAMPFIRE, function(Reader $in) : Block{ + return Blocks::SOUL_CAMPFIRE() + ->setFacing($in->readCardinalHorizontalFacing()) + ->setLit(!$in->readBool(StateNames::EXTINGUISHED)); + }); $this->map(Ids::SOUL_FIRE, function(Reader $in) : Block{ $in->ignored(StateNames::AGE); //this is useless for soul fire, since it doesn't have the logic associated return Blocks::SOUL_FIRE(); diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index 7a720559e..de9b5ae5e 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -133,6 +133,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Block(Ids::BIRCH_DOOR, Blocks::BIRCH_DOOR()); $this->map1to1Block(Ids::BREWING_STAND, Blocks::BREWING_STAND()); $this->map1to1Block(Ids::CAKE, Blocks::CAKE()); + $this->map1to1Block(Ids::CAMPFIRE, Blocks::CAMPFIRE()); $this->map1to1Block(Ids::CAULDRON, Blocks::CAULDRON()); $this->map1to1Block(Ids::CHAIN, Blocks::CHAIN()); $this->map1to1Block(Ids::CHERRY_DOOR, Blocks::CHERRY_DOOR()); @@ -148,6 +149,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->map1to1Block(Ids::MANGROVE_DOOR, Blocks::MANGROVE_DOOR()); $this->map1to1Block(Ids::NETHER_WART, Blocks::NETHER_WART()); $this->map1to1Block(Ids::REPEATER, Blocks::REDSTONE_REPEATER()); + $this->map1to1Block(Ids::SOUL_CAMPFIRE, Blocks::SOUL_CAMPFIRE()); $this->map1to1Block(Ids::SPRUCE_DOOR, Blocks::SPRUCE_DOOR()); $this->map1to1Block(Ids::SUGAR_CANE, Blocks::SUGARCANE()); $this->map1to1Block(Ids::WARPED_DOOR, Blocks::WARPED_DOOR()); diff --git a/src/event/block/CampfireCookEvent.php b/src/event/block/CampfireCookEvent.php new file mode 100644 index 000000000..3762f5848 --- /dev/null +++ b/src/event/block/CampfireCookEvent.php @@ -0,0 +1,63 @@ +input = clone $input; + } + + public function getCampfire() : Campfire{ + return $this->campfire; + } + + public function getSlot() : int{ + return $this->slot; + } + + public function getInput() : Item{ + return $this->input; + } + + public function getResult() : Item{ + return $this->result; + } + + public function setResult(Item $result) : void{ + $this->result = $result; + } +} diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 91fd2ab4a..6a1c1d2e3 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -205,6 +205,7 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("cake", fn() => Blocks::CAKE()); $result->registerBlock("cake_block", fn() => Blocks::CAKE()); $result->registerBlock("calcite", fn() => Blocks::CALCITE()); + $result->registerBlock("campfire", fn() => Blocks::CAMPFIRE()); $result->registerBlock("candle", fn() => Blocks::CANDLE()); $result->registerBlock("carpet", fn() => Blocks::CARPET()); $result->registerBlock("carrot_block", fn() => Blocks::CARROTS()); @@ -1003,6 +1004,7 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("snow", fn() => Blocks::SNOW()); $result->registerBlock("snow_block", fn() => Blocks::SNOW()); $result->registerBlock("snow_layer", fn() => Blocks::SNOW_LAYER()); + $result->registerBlock("soul_campfire", fn() => Blocks::SOUL_CAMPFIRE()); $result->registerBlock("soul_lantern", fn() => Blocks::SOUL_LANTERN()); $result->registerBlock("soul_sand", fn() => Blocks::SOUL_SAND()); $result->registerBlock("soul_soil", fn() => Blocks::SOUL_SOIL()); diff --git a/src/network/mcpe/cache/CraftingDataCache.php b/src/network/mcpe/cache/CraftingDataCache.php index 29f37590e..14523f74c 100644 --- a/src/network/mcpe/cache/CraftingDataCache.php +++ b/src/network/mcpe/cache/CraftingDataCache.php @@ -130,6 +130,8 @@ final class CraftingDataCache{ FurnaceType::FURNACE => FurnaceRecipeBlockName::FURNACE, FurnaceType::BLAST_FURNACE => FurnaceRecipeBlockName::BLAST_FURNACE, FurnaceType::SMOKER => FurnaceRecipeBlockName::SMOKER, + FurnaceType::CAMPFIRE => FurnaceRecipeBlockName::CAMPFIRE, + FurnaceType::SOUL_CAMPFIRE => FurnaceRecipeBlockName::SOUL_CAMPFIRE }; foreach($manager->getFurnaceRecipeManager($furnaceType)->getAll() as $recipe){ $input = $converter->coreRecipeIngredientToNet($recipe->getInput())->getDescriptor(); diff --git a/src/world/sound/CampfireSound.php b/src/world/sound/CampfireSound.php new file mode 100644 index 000000000..7e01342ef --- /dev/null +++ b/src/world/sound/CampfireSound.php @@ -0,0 +1,35 @@ + Date: Wed, 10 Jul 2024 19:15:06 +0300 Subject: [PATCH 04/29] Implement ICopper interface for blocks with common properties (#6390) --- src/block/Copper.php | 3 +- src/block/CopperSlab.php | 3 +- src/block/CopperStairs.php | 3 +- src/block/utils/ICopper.php | 38 +++++++++++++++++++ .../convert/BlockStateDeserializerHelper.php | 12 +++--- 5 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 src/block/utils/ICopper.php diff --git a/src/block/Copper.php b/src/block/Copper.php index 1da253fa4..8e678bf42 100644 --- a/src/block/Copper.php +++ b/src/block/Copper.php @@ -24,7 +24,8 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\CopperTrait; +use pocketmine\block\utils\ICopper; -class Copper extends Opaque{ +class Copper extends Opaque implements ICopper{ use CopperTrait; } diff --git a/src/block/CopperSlab.php b/src/block/CopperSlab.php index 088ace11e..4194cd854 100644 --- a/src/block/CopperSlab.php +++ b/src/block/CopperSlab.php @@ -24,7 +24,8 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\CopperTrait; +use pocketmine\block\utils\ICopper; -class CopperSlab extends Slab{ +class CopperSlab extends Slab implements ICopper{ use CopperTrait; } diff --git a/src/block/CopperStairs.php b/src/block/CopperStairs.php index b16d49ec1..dd8f44f7a 100644 --- a/src/block/CopperStairs.php +++ b/src/block/CopperStairs.php @@ -24,7 +24,8 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\CopperTrait; +use pocketmine\block\utils\ICopper; -class CopperStairs extends Stair{ +class CopperStairs extends Stair implements ICopper{ use CopperTrait; } diff --git a/src/block/utils/ICopper.php b/src/block/utils/ICopper.php new file mode 100644 index 000000000..a749efe63 --- /dev/null +++ b/src/block/utils/ICopper.php @@ -0,0 +1,38 @@ +setOxidation($oxidation); $block->setWaxed(false); return $block; } /** - * @phpstan-template TBlock of Copper|CopperSlab|CopperStairs + * @phpstan-template TBlock of ICopper * * @phpstan-param TBlock $block * @phpstan-return TBlock */ - public static function decodeWaxedCopper(Copper|CopperSlab|CopperStairs $block, CopperOxidation $oxidation) : Copper|CopperSlab|CopperStairs{ + public static function decodeWaxedCopper(ICopper $block, CopperOxidation $oxidation) : ICopper{ $block->setOxidation($oxidation); $block->setWaxed(true); return $block; From d6c48fd3a22b8dae0e2d2a5f1f920a40c2abd837 Mon Sep 17 00:00:00 2001 From: IvanCraft623 <57236932+IvanCraft623@users.noreply.github.com> Date: Fri, 12 Jul 2024 07:24:43 -0500 Subject: [PATCH 05/29] Implement new 1.21 paintings (#6393) --- src/entity/object/PaintingMotive.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/entity/object/PaintingMotive.php b/src/entity/object/PaintingMotive.php index a456630fc..00114c508 100644 --- a/src/entity/object/PaintingMotive.php +++ b/src/entity/object/PaintingMotive.php @@ -37,9 +37,11 @@ class PaintingMotive{ new PaintingMotive(1, 1, "Aztec2"), new PaintingMotive(1, 1, "Bomb"), new PaintingMotive(1, 1, "Kebab"), + new PaintingMotive(1, 1, "meditative"), new PaintingMotive(1, 1, "Plant"), new PaintingMotive(1, 1, "Wasteland"), new PaintingMotive(1, 2, "Graham"), + new PaintingMotive(1, 2, "prairie_ride"), new PaintingMotive(1, 2, "Wanderer"), new PaintingMotive(2, 1, "Courbet"), new PaintingMotive(2, 1, "Creebet"), @@ -47,8 +49,10 @@ class PaintingMotive{ new PaintingMotive(2, 1, "Sea"), new PaintingMotive(2, 1, "Sunset"), new PaintingMotive(2, 2, "Bust"), + new PaintingMotive(2, 2, "baroque"), new PaintingMotive(2, 2, "Earth"), new PaintingMotive(2, 2, "Fire"), + new PaintingMotive(2, 2, "humble"), new PaintingMotive(2, 2, "Match"), new PaintingMotive(2, 2, "SkullAndRoses"), new PaintingMotive(2, 2, "Stage"), @@ -56,12 +60,28 @@ class PaintingMotive{ new PaintingMotive(2, 2, "Water"), new PaintingMotive(2, 2, "Wind"), new PaintingMotive(2, 2, "Wither"), + new PaintingMotive(3, 3, "bouquet"), + new PaintingMotive(3, 3, "cavebird"), + new PaintingMotive(3, 3, "cotan"), + new PaintingMotive(3, 3, "endboss"), + new PaintingMotive(3, 3, "fern"), + new PaintingMotive(3, 3, "owlemons"), + new PaintingMotive(3, 3, "sunflowers"), + new PaintingMotive(3, 3, "tides"), + new PaintingMotive(3, 4, "backyard"), + new PaintingMotive(3, 4, "pond"), + new PaintingMotive(4, 2, "changing"), new PaintingMotive(4, 2, "Fighters"), + new PaintingMotive(4, 2, "finding"), + new PaintingMotive(4, 2, "lowmist"), + new PaintingMotive(4, 2, "passage"), new PaintingMotive(4, 3, "DonkeyKong"), new PaintingMotive(4, 3, "Skeleton"), new PaintingMotive(4, 4, "BurningSkull"), + new PaintingMotive(4, 4, "orb"), new PaintingMotive(4, 4, "Pigscene"), - new PaintingMotive(4, 4, "Pointer") + new PaintingMotive(4, 4, "Pointer"), + new PaintingMotive(4, 4, "unpacked") ] as $motive){ self::registerMotive($motive); } From 787afb6b0007c919f22bd5b955973e3de13223a7 Mon Sep 17 00:00:00 2001 From: IvanCraft623 <57236932+IvanCraft623@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:34:46 -0500 Subject: [PATCH 06/29] Implement all new 1.21 tuff blocks (#6391) --- src/block/BlockTypeIds.php | 15 +++++++- src/block/VanillaBlocks.php | 36 ++++++++++++++++++- .../convert/BlockObjectToStateSerializer.php | 13 +++++++ .../BlockStateToObjectDeserializer.php | 13 +++++++ src/item/StringToItemParser.php | 13 +++++++ .../block_factory_consistency_check.json | 13 +++++++ 6 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index 59c358489..29f4e650d 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -747,8 +747,21 @@ final class BlockTypeIds{ public const DOUBLE_PITCHER_CROP = 10717; public const CAMPFIRE = 10718; public const SOUL_CAMPFIRE = 10719; + public const TUFF_SLAB = 10720; + public const TUFF_STAIRS = 10721; + public const TUFF_WALL = 10722; + public const CHISELED_TUFF = 10723; + public const TUFF_BRICKS = 10724; + public const TUFF_BRICK_SLAB = 10725; + public const TUFF_BRICK_STAIRS = 10726; + public const TUFF_BRICK_WALL = 10727; + public const CHISELED_TUFF_BRICKS = 10728; + public const POLISHED_TUFF = 10729; + public const POLISHED_TUFF_SLAB = 10730; + public const POLISHED_TUFF_STAIRS = 10731; + public const POLISHED_TUFF_WALL = 10732; - public const FIRST_UNUSED_BLOCK_ID = 10720; + public const FIRST_UNUSED_BLOCK_ID = 10733; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 7733f4359..13c7e869a 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -188,6 +188,8 @@ use function strtolower; * @method static Opaque CHISELED_RED_SANDSTONE() * @method static Opaque CHISELED_SANDSTONE() * @method static Opaque CHISELED_STONE_BRICKS() + * @method static Opaque CHISELED_TUFF() + * @method static Opaque CHISELED_TUFF_BRICKS() * @method static ChorusFlower CHORUS_FLOWER() * @method static ChorusPlant CHORUS_PLANT() * @method static Clay CLAY() @@ -609,6 +611,10 @@ use function strtolower; * @method static Opaque POLISHED_GRANITE() * @method static Slab POLISHED_GRANITE_SLAB() * @method static Stair POLISHED_GRANITE_STAIRS() + * @method static Opaque POLISHED_TUFF() + * @method static Slab POLISHED_TUFF_SLAB() + * @method static Stair POLISHED_TUFF_STAIRS() + * @method static Wall POLISHED_TUFF_WALL() * @method static Flower POPPY() * @method static Potato POTATOES() * @method static PotionCauldron POTION_CAULDRON() @@ -738,6 +744,13 @@ use function strtolower; * @method static Tripwire TRIPWIRE() * @method static TripwireHook TRIPWIRE_HOOK() * @method static Opaque TUFF() + * @method static Opaque TUFF_BRICKS() + * @method static Slab TUFF_BRICK_SLAB() + * @method static Stair TUFF_BRICK_STAIRS() + * @method static Wall TUFF_BRICK_WALL() + * @method static Slab TUFF_SLAB() + * @method static Stair TUFF_STAIRS() + * @method static Wall TUFF_WALL() * @method static NetherVines TWISTING_VINES() * @method static UnderwaterTorch UNDERWATER_TORCH() * @method static Vine VINES() @@ -1269,6 +1282,7 @@ final class VanillaBlocks{ self::registerBlocksR17(); self::registerBlocksR18(); self::registerMudBlocks(); + self::registerTuffBlocks(); self::registerCraftingTables(); self::registerChorusBlocks(); @@ -1576,7 +1590,6 @@ final class VanillaBlocks{ self::register("amethyst_cluster", new AmethystCluster(new BID(Ids::AMETHYST_CLUSTER), "Amethyst Cluster", $amethystInfo)); self::register("calcite", new Opaque(new BID(Ids::CALCITE), "Calcite", new Info(BreakInfo::pickaxe(0.75, ToolTier::WOOD)))); - self::register("tuff", new Opaque(new BID(Ids::TUFF), "Tuff", new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0)))); self::register("raw_copper", new Opaque(new BID(Ids::RAW_COPPER), "Raw Copper Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0)))); self::register("raw_gold", new Opaque(new BID(Ids::RAW_GOLD), "Raw Gold Block", new Info(BreakInfo::pickaxe(5, ToolTier::IRON, 30.0)))); @@ -1667,6 +1680,27 @@ final class VanillaBlocks{ self::register("mud_brick_wall", new Wall(new BID(Ids::MUD_BRICK_WALL), "Mud Brick Wall", $mudBricksBreakInfo)); } + private static function registerTuffBlocks() : void{ + $tuffBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0)); + + self::register("tuff", new Opaque(new BID(Ids::TUFF), "Tuff", $tuffBreakInfo)); + self::register("tuff_slab", new Slab(new BID(Ids::TUFF_SLAB), "Tuff", $tuffBreakInfo)); + self::register("tuff_stairs", new Stair(new BID(Ids::TUFF_STAIRS), "Tuff Stairs", $tuffBreakInfo)); + self::register("tuff_wall", new Wall(new BID(Ids::TUFF_WALL), "Tuff Wall", $tuffBreakInfo)); + self::register("chiseled_tuff", new Opaque(new BID(Ids::CHISELED_TUFF), "Chiseled Tuff", $tuffBreakInfo)); + + self::register("tuff_bricks", new Opaque(new BID(Ids::TUFF_BRICKS), "Tuff Bricks", $tuffBreakInfo)); + self::register("tuff_brick_slab", new Slab(new BID(Ids::TUFF_BRICK_SLAB), "Tuff Brick", $tuffBreakInfo)); + self::register("tuff_brick_stairs", new Stair(new BID(Ids::TUFF_BRICK_STAIRS), "Tuff Brick Stairs", $tuffBreakInfo)); + self::register("tuff_brick_wall", new Wall(new BID(Ids::TUFF_BRICK_WALL), "Tuff Brick Wall", $tuffBreakInfo)); + self::register("chiseled_tuff_bricks", new Opaque(new BID(Ids::CHISELED_TUFF_BRICKS), "Chiseled Tuff Bricks", $tuffBreakInfo)); + + self::register("polished_tuff", new Opaque(new BID(Ids::POLISHED_TUFF), "Polished Tuff", $tuffBreakInfo)); + self::register("polished_tuff_slab", new Slab(new BID(Ids::POLISHED_TUFF_SLAB), "Polished Tuff", $tuffBreakInfo)); + self::register("polished_tuff_stairs", new Stair(new BID(Ids::POLISHED_TUFF_STAIRS), "Polished Tuff Stairs", $tuffBreakInfo)); + self::register("polished_tuff_wall", new Wall(new BID(Ids::POLISHED_TUFF_WALL), "Polished Tuff Wall", $tuffBreakInfo)); + } + private static function registerCauldronBlocks() : void{ $cauldronBreakInfo = new Info(BreakInfo::pickaxe(2, ToolTier::WOOD)); diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index a8ca150ba..1d271b7a5 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -785,6 +785,8 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSimple(Blocks::CHISELED_DEEPSLATE(), Ids::CHISELED_DEEPSLATE); $this->mapSimple(Blocks::CHISELED_NETHER_BRICKS(), Ids::CHISELED_NETHER_BRICKS); $this->mapSimple(Blocks::CHISELED_POLISHED_BLACKSTONE(), Ids::CHISELED_POLISHED_BLACKSTONE); + $this->mapSimple(Blocks::CHISELED_TUFF(), Ids::CHISELED_TUFF); + $this->mapSimple(Blocks::CHISELED_TUFF_BRICKS(), Ids::CHISELED_TUFF_BRICKS); $this->mapSimple(Blocks::CHORUS_PLANT(), Ids::CHORUS_PLANT); $this->mapSimple(Blocks::CLAY(), Ids::CLAY); $this->mapSimple(Blocks::COAL(), Ids::COAL_BLOCK); @@ -997,6 +999,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSimple(Blocks::POLISHED_DEEPSLATE(), Ids::POLISHED_DEEPSLATE); $this->mapSimple(Blocks::POLISHED_DIORITE(), Ids::POLISHED_DIORITE); $this->mapSimple(Blocks::POLISHED_GRANITE(), Ids::POLISHED_GRANITE); + $this->mapSimple(Blocks::POLISHED_TUFF(), Ids::POLISHED_TUFF); $this->mapSimple(Blocks::QUARTZ_BRICKS(), Ids::QUARTZ_BRICKS); $this->mapSimple(Blocks::RAW_COPPER(), Ids::RAW_COPPER_BLOCK); $this->mapSimple(Blocks::RAW_GOLD(), Ids::RAW_GOLD_BLOCK); @@ -1023,6 +1026,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapSimple(Blocks::TINTED_GLASS(), Ids::TINTED_GLASS); $this->mapSimple(Blocks::TORCHFLOWER(), Ids::TORCHFLOWER); $this->mapSimple(Blocks::TUFF(), Ids::TUFF); + $this->mapSimple(Blocks::TUFF_BRICKS(), Ids::TUFF_BRICKS); $this->mapSimple(Blocks::WARPED_WART_BLOCK(), Ids::WARPED_WART_BLOCK); $this->mapSimple(Blocks::WARPED_ROOTS(), Ids::WARPED_ROOTS); $this->mapSimple(Blocks::WITHER_ROSE(), Ids::WITHER_ROSE); @@ -1539,6 +1543,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ $this->mapStairs(Blocks::POLISHED_DIORITE_STAIRS(), Ids::POLISHED_DIORITE_STAIRS); $this->map(Blocks::POLISHED_GRANITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_POLISHED_GRANITE)); $this->mapStairs(Blocks::POLISHED_GRANITE_STAIRS(), Ids::POLISHED_GRANITE_STAIRS); + $this->mapSlab(Blocks::POLISHED_TUFF_SLAB(), Ids::POLISHED_TUFF_SLAB, Ids::POLISHED_TUFF_DOUBLE_SLAB); + $this->mapStairs(Blocks::POLISHED_TUFF_STAIRS(), Ids::POLISHED_TUFF_STAIRS); + $this->map(Blocks::POLISHED_TUFF_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::POLISHED_TUFF_WALL))); $this->map(Blocks::POTATOES(), fn(Potato $block) => Helper::encodeCrops($block, new Writer(Ids::POTATOES))); $this->map(Blocks::POWERED_RAIL(), function(PoweredRail $block) : Writer{ return Writer::create(Ids::GOLDEN_RAIL) @@ -1715,6 +1722,12 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ->writeBool(StateNames::POWERED_BIT, $block->isPowered()) ->writeLegacyHorizontalFacing($block->getFacing()); }); + $this->mapSlab(Blocks::TUFF_BRICK_SLAB(), Ids::TUFF_BRICK_SLAB, Ids::TUFF_BRICK_DOUBLE_SLAB); + $this->mapStairs(Blocks::TUFF_BRICK_STAIRS(), Ids::TUFF_BRICK_STAIRS); + $this->map(Blocks::TUFF_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::TUFF_BRICK_WALL))); + $this->mapSlab(Blocks::TUFF_SLAB(), Ids::TUFF_SLAB, Ids::TUFF_DOUBLE_SLAB); + $this->mapStairs(Blocks::TUFF_STAIRS(), Ids::TUFF_STAIRS); + $this->map(Blocks::TUFF_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::TUFF_WALL))); $this->map(Blocks::TWISTING_VINES(), function(NetherVines $block) : Writer{ return Writer::create(Ids::TWISTING_VINES) ->writeInt(StateNames::TWISTING_VINES_AGE, $block->getAge()); diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 40478f197..afe0fc225 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -673,6 +673,8 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple(Ids::CHISELED_DEEPSLATE, fn() => Blocks::CHISELED_DEEPSLATE()); $this->mapSimple(Ids::CHISELED_NETHER_BRICKS, fn() => Blocks::CHISELED_NETHER_BRICKS()); $this->mapSimple(Ids::CHISELED_POLISHED_BLACKSTONE, fn() => Blocks::CHISELED_POLISHED_BLACKSTONE()); + $this->mapSimple(Ids::CHISELED_TUFF, fn() => Blocks::CHISELED_TUFF()); + $this->mapSimple(Ids::CHISELED_TUFF_BRICKS, fn() => Blocks::CHISELED_TUFF_BRICKS()); $this->mapSimple(Ids::CHORUS_PLANT, fn() => Blocks::CHORUS_PLANT()); $this->mapSimple(Ids::CLAY, fn() => Blocks::CLAY()); $this->mapSimple(Ids::COAL_BLOCK, fn() => Blocks::COAL()); @@ -880,6 +882,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple(Ids::POLISHED_DEEPSLATE, fn() => Blocks::POLISHED_DEEPSLATE()); $this->mapSimple(Ids::POLISHED_DIORITE, fn() => Blocks::POLISHED_DIORITE()); $this->mapSimple(Ids::POLISHED_GRANITE, fn() => Blocks::POLISHED_GRANITE()); + $this->mapSimple(Ids::POLISHED_TUFF, fn() => Blocks::POLISHED_TUFF()); $this->mapSimple(Ids::QUARTZ_BRICKS, fn() => Blocks::QUARTZ_BRICKS()); $this->mapSimple(Ids::QUARTZ_ORE, fn() => Blocks::NETHER_QUARTZ_ORE()); $this->mapSimple(Ids::RAW_COPPER_BLOCK, fn() => Blocks::RAW_COPPER()); @@ -907,6 +910,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapSimple(Ids::TINTED_GLASS, fn() => Blocks::TINTED_GLASS()); $this->mapSimple(Ids::TORCHFLOWER, fn() => Blocks::TORCHFLOWER()); $this->mapSimple(Ids::TUFF, fn() => Blocks::TUFF()); + $this->mapSimple(Ids::TUFF_BRICKS, fn() => Blocks::TUFF_BRICKS()); $this->mapSimple(Ids::UNDYED_SHULKER_BOX, fn() => Blocks::SHULKER_BOX()); $this->mapSimple(Ids::WARPED_WART_BLOCK, fn() => Blocks::WARPED_WART_BLOCK()); $this->mapSimple(Ids::WARPED_ROOTS, fn() => Blocks::WARPED_ROOTS()); @@ -1385,6 +1389,9 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->map(Ids::POLISHED_DEEPSLATE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::POLISHED_DEEPSLATE_WALL(), $in)); $this->mapStairs(Ids::POLISHED_DIORITE_STAIRS, fn() => Blocks::POLISHED_DIORITE_STAIRS()); $this->mapStairs(Ids::POLISHED_GRANITE_STAIRS, fn() => Blocks::POLISHED_GRANITE_STAIRS()); + $this->mapSlab(Ids::POLISHED_TUFF_SLAB, Ids::POLISHED_TUFF_DOUBLE_SLAB, fn() => Blocks::POLISHED_TUFF_SLAB()); + $this->mapStairs(Ids::POLISHED_TUFF_STAIRS, fn() => Blocks::POLISHED_TUFF_STAIRS()); + $this->map(Ids::POLISHED_TUFF_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::POLISHED_TUFF_WALL(), $in)); $this->map(Ids::PORTAL, function(Reader $in) : Block{ return Blocks::NETHER_PORTAL() ->setAxis(match($value = $in->readString(StateNames::PORTAL_AXIS)){ @@ -1628,6 +1635,12 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setFacing($in->readLegacyHorizontalFacing()) ->setPowered($in->readBool(StateNames::POWERED_BIT)); }); + $this->mapSlab(Ids::TUFF_BRICK_SLAB, Ids::TUFF_BRICK_DOUBLE_SLAB, fn() => Blocks::TUFF_BRICK_SLAB()); + $this->mapStairs(Ids::TUFF_BRICK_STAIRS, fn() => Blocks::TUFF_BRICK_STAIRS()); + $this->map(Ids::TUFF_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::TUFF_BRICK_WALL(), $in)); + $this->mapSlab(Ids::TUFF_SLAB, Ids::TUFF_DOUBLE_SLAB, fn() => Blocks::TUFF_SLAB()); + $this->mapStairs(Ids::TUFF_STAIRS, fn() => Blocks::TUFF_STAIRS()); + $this->map(Ids::TUFF_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::TUFF_WALL(), $in)); $this->map(Ids::TWISTING_VINES, function(Reader $in) : Block{ return Blocks::TWISTING_VINES() ->setAge($in->readBoundedInt(StateNames::TWISTING_VINES_AGE, 0, 25)); diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 6a1c1d2e3..9f5db6950 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -240,6 +240,8 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("chiseled_red_sandstone", fn() => Blocks::CHISELED_RED_SANDSTONE()); $result->registerBlock("chiseled_sandstone", fn() => Blocks::CHISELED_SANDSTONE()); $result->registerBlock("chiseled_stone_bricks", fn() => Blocks::CHISELED_STONE_BRICKS()); + $result->registerBlock("chiseled_tuff", fn() => Blocks::CHISELED_TUFF()); + $result->registerBlock("chiseled_tuff_bricks", fn() => Blocks::CHISELED_TUFF_BRICKS()); $result->registerBlock("chorus_flower", fn() => Blocks::CHORUS_FLOWER()); $result->registerBlock("chorus_plant", fn() => Blocks::CHORUS_PLANT()); $result->registerBlock("clay_block", fn() => Blocks::CLAY()); @@ -898,6 +900,10 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("polished_granite", fn() => Blocks::POLISHED_GRANITE()); $result->registerBlock("polished_granite_slab", fn() => Blocks::POLISHED_GRANITE_SLAB()); $result->registerBlock("polished_granite_stairs", fn() => Blocks::POLISHED_GRANITE_STAIRS()); + $result->registerBlock("polished_tuff", fn() => Blocks::POLISHED_TUFF()); + $result->registerBlock("polished_tuff_slab", fn() => Blocks::POLISHED_TUFF_SLAB()); + $result->registerBlock("polished_tuff_stairs", fn() => Blocks::POLISHED_TUFF_STAIRS()); + $result->registerBlock("polished_tuff_wall", fn() => Blocks::POLISHED_TUFF_WALL()); $result->registerBlock("poppy", fn() => Blocks::POPPY()); $result->registerBlock("portal", fn() => Blocks::NETHER_PORTAL()); $result->registerBlock("portal_block", fn() => Blocks::NETHER_PORTAL()); @@ -1098,6 +1104,13 @@ final class StringToItemParser extends StringToTParser{ $result->registerBlock("trunk", fn() => Blocks::OAK_PLANKS()); $result->registerBlock("trunk2", fn() => Blocks::ACACIA_LOG()->setStripped(false)); $result->registerBlock("tuff", fn() => Blocks::TUFF()); + $result->registerBlock("tuff_bricks", fn() => Blocks::TUFF_BRICKS()); + $result->registerBlock("tuff_brick_slab", fn() => Blocks::TUFF_BRICK_SLAB()); + $result->registerBlock("tuff_brick_stairs", fn() => Blocks::TUFF_BRICK_STAIRS()); + $result->registerBlock("tuff_brick_wall", fn() => Blocks::TUFF_BRICK_WALL()); + $result->registerBlock("tuff_slab", fn() => Blocks::TUFF_SLAB()); + $result->registerBlock("tuff_stairs", fn() => Blocks::TUFF_STAIRS()); + $result->registerBlock("tuff_wall", fn() => Blocks::TUFF_WALL()); $result->registerBlock("twisting_vines", fn() => Blocks::TWISTING_VINES()); $result->registerBlock("underwater_tnt", fn() => Blocks::TNT()->setWorksUnderwater(true)); $result->registerBlock("underwater_torch", fn() => Blocks::UNDERWATER_TORCH()); diff --git a/tests/phpunit/block/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json index b40bfa9e3..97c24b52e 100644 --- a/tests/phpunit/block/block_factory_consistency_check.json +++ b/tests/phpunit/block/block_factory_consistency_check.json @@ -112,6 +112,8 @@ "CHISELED_RED_SANDSTONE": 1, "CHISELED_SANDSTONE": 1, "CHISELED_STONE_BRICKS": 1, + "CHISELED_TUFF": 1, + "CHISELED_TUFF_BRICKS": 1, "CHORUS_FLOWER": 6, "CHORUS_PLANT": 1, "CLAY": 1, @@ -533,6 +535,10 @@ "POLISHED_GRANITE": 1, "POLISHED_GRANITE_SLAB": 3, "POLISHED_GRANITE_STAIRS": 8, + "POLISHED_TUFF": 1, + "POLISHED_TUFF_SLAB": 3, + "POLISHED_TUFF_STAIRS": 8, + "POLISHED_TUFF_WALL": 162, "POPPY": 1, "POTATOES": 8, "POTION_CAULDRON": 6, @@ -662,6 +668,13 @@ "TRIPWIRE": 16, "TRIPWIRE_HOOK": 16, "TUFF": 1, + "TUFF_BRICKS": 1, + "TUFF_BRICK_SLAB": 3, + "TUFF_BRICK_STAIRS": 8, + "TUFF_BRICK_WALL": 162, + "TUFF_SLAB": 3, + "TUFF_STAIRS": 8, + "TUFF_WALL": 162, "TWISTING_VINES": 26, "UNDERWATER_TORCH": 5, "VINES": 16, From bdb5845cecc777ded4e37071a9ae976a8dcbe0d1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 5 Aug 2024 22:33:30 +0100 Subject: [PATCH 07/29] Allow name flattening rules where multiple old values map to the same new ID this allows more compaction in certain cases, such as tallgrass recently. instead of blacklisting any mapping which reuses the same flattened infix, we select the flatten property which produces the smallest number of distinct rules, which produces the most compact schema possible. this change also permits potentially flattening other types of properties such as for corals (live/dead and type), although only one may be selected at a time. --- tools/generate-blockstate-upgrade-schema.php | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index 54984d459..299d97d05 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -42,6 +42,7 @@ use function array_key_last; use function array_keys; use function array_map; use function array_shift; +use function array_unique; use function array_values; use function count; use function dirname; @@ -423,6 +424,10 @@ function processRemappedStates(array $upgradeTable) : array{ $filter = $pair->old->getStates(); foreach($unchangedStatesByNewName[$pair->new->getName()] as $unchangedPropertyName){ + if($unchangedPropertyName === $propertyName){ + $notFlattenedProperties[$propertyName] = true; + continue 2; + } unset($filter[$unchangedPropertyName]); } unset($filter[$propertyName]); @@ -436,26 +441,31 @@ function processRemappedStates(array $upgradeTable) : array{ $notFlattenedProperties[$propertyName] = true; continue; } - foreach(Utils::stringifyKeys($valuesToIds) as $otherRawValue => $otherNewId){ - if($otherRawValue === $rawValue){ - continue; - } - if($otherNewId === $pair->new->getName()){ - //this old value maps to the same new ID as another old value - bad candidate for flattening - $notFlattenedProperties[$propertyName] = true; - continue 2; - } - } } $candidateFlattenedValues[$propertyName][$rawFilter][$rawValue] = $pair->new->getName(); } } + foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){ + foreach($filters as $valuesToIds){ + if(count(array_unique($valuesToIds)) === 1){ + //this property doesn't influence the new ID + $notFlattenedProperties[$propertyName] = true; + continue 2; + } + } + } foreach(Utils::stringifyKeys($notFlattenedProperties) as $propertyName => $_){ unset($candidateFlattenedValues[$propertyName]); } $flattenedProperties = buildFlattenPropertyRules($candidateFlattenedValues); $flattenProperty = array_key_first($flattenedProperties); + //Properties with fewer rules take up less space for the same result + foreach(Utils::stringifyKeys($flattenedProperties) as $propertyName => $rules){ + if(count($rules) < count($flattenedProperties[$flattenProperty])){ + $flattenProperty = $propertyName; + } + } $list = []; From be2437ac6e6cd851f0cd282f54983cb2e9fcba6f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 5 Aug 2024 22:38:02 +0100 Subject: [PATCH 08/29] Support for flattening TAG_Byte and TAG_Int properties this allows optimisation in upcoming versions. --- .../BlockStateUpgradeSchemaFlattenedName.php | 10 ++++- .../upgrade/BlockStateUpgradeSchemaUtils.php | 33 ++++++++++---- .../block/upgrade/BlockStateUpgrader.php | 23 +++++++--- ...ckStateUpgradeSchemaModelFlattenedName.php | 7 ++- tools/generate-blockstate-upgrade-schema.php | 45 ++++++++++++++++--- 5 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php index 1c95dd9c7..e2f7f798d 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\upgrade; +use pocketmine\nbt\tag\ByteTag; +use pocketmine\nbt\tag\IntTag; +use pocketmine\nbt\tag\StringTag; use function ksort; use const SORT_STRING; @@ -31,12 +34,14 @@ final class BlockStateUpgradeSchemaFlattenedName{ /** * @param string[] $flattenedValueRemaps * @phpstan-param array $flattenedValueRemaps + * @phpstan-param class-string|class-string|class-string|null $flattenedPropertyType */ public function __construct( public string $prefix, public string $flattenedProperty, public string $suffix, - public array $flattenedValueRemaps + public array $flattenedValueRemaps, + public ?string $flattenedPropertyType = null ){ ksort($this->flattenedValueRemaps, SORT_STRING); } @@ -45,6 +50,7 @@ final class BlockStateUpgradeSchemaFlattenedName{ return $this->prefix === $that->prefix && $this->flattenedProperty === $that->flattenedProperty && $this->suffix === $that->suffix && - $this->flattenedValueRemaps === $that->flattenedValueRemaps; + $this->flattenedValueRemaps === $that->flattenedValueRemaps && + $this->flattenedPropertyType === $that->flattenedPropertyType; } } diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php index 832631490..d66b7e68c 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php @@ -157,18 +157,29 @@ final class BlockStateUpgradeSchemaUtils{ foreach(Utils::stringifyKeys($model->remappedStates ?? []) as $oldBlockName => $remaps){ foreach($remaps as $remap){ - if(isset($remap->newName) === isset($remap->newFlattenedName)){ + if(isset($remap->newName)){ + $remapName = $remap->newName; + }elseif(isset($remap->newFlattenedName)){ + $flattenRule = $remap->newFlattenedName; + $remapName = new BlockStateUpgradeSchemaFlattenedName( + $flattenRule->prefix, + $flattenRule->flattenedProperty, + $flattenRule->suffix, + $flattenRule->flattenedValueRemaps ?? [], + match($flattenRule->flattenedPropertyType){ + "string", null => StringTag::class, + "int" => IntTag::class, + "byte" => ByteTag::class, + default => throw new \UnexpectedValueException("Unexpected flattened property type $flattenRule->flattenedPropertyType, expected 'string', 'int' or 'byte'") + } + ); + }else{ throw new \UnexpectedValueException("Expected exactly one of 'newName' or 'newFlattenedName' properties to be set"); } $result->remappedStates[$oldBlockName][] = new BlockStateUpgradeSchemaBlockRemap( array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->oldState ?? []), - $remap->newName ?? new BlockStateUpgradeSchemaFlattenedName( - $remap->newFlattenedName->prefix, - $remap->newFlattenedName->flattenedProperty, - $remap->newFlattenedName->suffix, - $remap->newFlattenedName->flattenedValueRemaps ?? [], - ), + $remapName, array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []), $remap->copiedState ?? [] ); @@ -303,7 +314,13 @@ final class BlockStateUpgradeSchemaUtils{ $remap->newName->prefix, $remap->newName->flattenedProperty, $remap->newName->suffix, - $remap->newName->flattenedValueRemaps + $remap->newName->flattenedValueRemaps, + match($remap->newName->flattenedPropertyType){ + StringTag::class => null, //omit for TAG_String, as this is the common case + ByteTag::class => "byte", + IntTag::class => "int", + default => throw new \LogicException("Unexpected tag type " . $remap->newName->flattenedPropertyType . " in flattened property type") + } ), array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState), $remap->copiedState diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index 4a305d8bc..ddb1e1359 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -24,10 +24,14 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\upgrade; use pocketmine\data\bedrock\block\BlockStateData; +use pocketmine\nbt\tag\ByteTag; +use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\Tag; +use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; use function count; +use function get_class; use function is_string; use function ksort; use function max; @@ -141,14 +145,21 @@ final class BlockStateUpgrader{ $newName = $remap->newName; }else{ $flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null; - if($flattenedValue instanceof StringTag){ - $embedValue = $remap->newName->flattenedValueRemaps[$flattenedValue->getValue()] ?? $flattenedValue->getValue(); - $newName = sprintf("%s%s%s", $remap->newName->prefix, $embedValue, $remap->newName->suffix); - unset($oldState[$remap->newName->flattenedProperty]); - }else{ - //flattened property is not a TAG_String, so this transformation is not applicable + $expectedType = $remap->newName->flattenedPropertyType; + if(!$flattenedValue instanceof $expectedType){ + //flattened property is not of the expected type, so this transformation is not applicable continue; } + $embedKey = match(get_class($flattenedValue)){ + StringTag::class => $flattenedValue->getValue(), + ByteTag::class => (string) $flattenedValue->getValue(), + IntTag::class => (string) $flattenedValue->getValue(), + //flattenedPropertyType is always one of these three types, but PHPStan doesn't know that + default => throw new AssumptionFailedError("flattenedPropertyType should be one of these three types, but have " . get_class($flattenedValue)), + }; + $embedValue = $remap->newName->flattenedValueRemaps[$embedKey] ?? $embedKey; + $newName = sprintf("%s%s%s", $remap->newName->prefix, $embedValue, $remap->newName->suffix); + unset($oldState[$remap->newName->flattenedProperty]); } $newState = $remap->newState; diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php index 001192f47..6d03bbc12 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php @@ -31,6 +31,7 @@ final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializab public string $prefix; /** @required */ public string $flattenedProperty; + public ?string $flattenedPropertyType = null; /** @required */ public string $suffix; /** @@ -43,11 +44,12 @@ final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializab * @param string[] $flattenedValueRemaps * @phpstan-param array $flattenedValueRemaps */ - public function __construct(string $prefix, string $flattenedProperty, string $suffix, array $flattenedValueRemaps){ + public function __construct(string $prefix, string $flattenedProperty, string $suffix, array $flattenedValueRemaps, ?string $flattenedPropertyType = null){ $this->prefix = $prefix; $this->flattenedProperty = $flattenedProperty; $this->suffix = $suffix; $this->flattenedValueRemaps = $flattenedValueRemaps; + $this->flattenedPropertyType = $flattenedPropertyType; } /** @@ -58,6 +60,9 @@ final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializab if(count($this->flattenedValueRemaps) === 0){ unset($result["flattenedValueRemaps"]); } + if($this->flattenedPropertyType === null){ + unset($result["flattenedPropertyType"]); + } return $result; } } diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index 299d97d05..d2d0a8c41 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -30,6 +30,8 @@ use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenedName; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap; use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\tag\ByteTag; +use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\Tag; use pocketmine\nbt\TreeRoot; @@ -48,7 +50,10 @@ use function count; use function dirname; use function file_put_contents; use function fwrite; +use function get_class; +use function get_debug_type; use function implode; +use function is_numeric; use function json_encode; use function ksort; use function min; @@ -312,11 +317,13 @@ function findCommonSuffix(array $strings) : string{ /** * @param string[][][] $candidateFlattenedValues * @phpstan-param array>> $candidateFlattenedValues + * @param string[] $candidateFlattenPropertyTypes + * @phpstan-param array|class-string|class-string> $candidateFlattenPropertyTypes * * @return BlockStateUpgradeSchemaFlattenedName[][] * @phpstan-return array> */ -function buildFlattenPropertyRules(array $candidateFlattenedValues) : array{ +function buildFlattenPropertyRules(array $candidateFlattenedValues, array $candidateFlattenPropertyTypes) : array{ $flattenPropertyRules = []; foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){ foreach(Utils::stringifyKeys($filters) as $filter => $valueToId){ @@ -340,11 +347,26 @@ function buildFlattenPropertyRules(array $candidateFlattenedValues) : array{ } } + $allNumeric = true; + if(count($valueMap) > 0){ + foreach(Utils::stringifyKeys($valueMap) as $value => $newValue){ + if(!is_numeric($value)){ + $allNumeric = false; + break; + } + } + if($allNumeric){ + //add a dummy key to force the JSON to be an object and not a list + $valueMap["dummy"] = "map_not_list"; + } + } + $flattenPropertyRules[$propertyName][$filter] = new BlockStateUpgradeSchemaFlattenedName( $idPrefix, $propertyName, $idSuffix, - $valueMap + $valueMap, + $candidateFlattenPropertyTypes[$propertyName], ); } } @@ -407,16 +429,25 @@ function processRemappedStates(array $upgradeTable) : array{ $notFlattenedProperties = []; $candidateFlattenedValues = []; + $candidateFlattenedPropertyTypes = []; foreach($upgradeTable as $pair){ foreach(Utils::stringifyKeys($pair->old->getStates()) as $propertyName => $propertyValue){ if(isset($notFlattenedProperties[$propertyName])){ continue; } - if(!$propertyValue instanceof StringTag){ + if(!$propertyValue instanceof StringTag && !$propertyValue instanceof IntTag && !$propertyValue instanceof ByteTag){ $notFlattenedProperties[$propertyName] = true; continue; } - $rawValue = $propertyValue->getValue(); + $previousType = $candidateFlattenedPropertyTypes[$propertyName] ?? null; + if($previousType !== null && $previousType !== get_class($propertyValue)){ + //mismatched types for the same property name - this has never happened so far, but it's not impossible + $notFlattenedProperties[$propertyName] = true; + continue; + } + $candidateFlattenedPropertyTypes[$propertyName] = get_class($propertyValue); + + $rawValue = (string) $propertyValue->getValue(); if($rawValue === ""){ $notFlattenedProperties[$propertyName] = true; continue; @@ -458,7 +489,7 @@ function processRemappedStates(array $upgradeTable) : array{ unset($candidateFlattenedValues[$propertyName]); } - $flattenedProperties = buildFlattenPropertyRules($candidateFlattenedValues); + $flattenedProperties = buildFlattenPropertyRules($candidateFlattenedValues, $candidateFlattenedPropertyTypes); $flattenProperty = array_key_first($flattenedProperties); //Properties with fewer rules take up less space for the same result foreach(Utils::stringifyKeys($flattenedProperties) as $propertyName => $rules){ @@ -485,8 +516,8 @@ function processRemappedStates(array $upgradeTable) : array{ ksort($cleanedNewState); if($flattenProperty !== null){ $flattenedValue = $cleanedOldState[$flattenProperty] ?? null; - if(!$flattenedValue instanceof StringTag){ - throw new AssumptionFailedError("This should always be a TAG_String ($newName $flattenProperty)"); + if(!$flattenedValue instanceof StringTag && !$flattenedValue instanceof IntTag && !$flattenedValue instanceof ByteTag){ + throw new AssumptionFailedError("Non-flattenable type of tag ($newName $flattenProperty) but have " . get_debug_type($flattenedValue)); } unset($cleanedOldState[$flattenProperty]); } From d0d7a995fb60a493462323208ff2b887003cef44 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 5 Aug 2024 22:38:32 +0100 Subject: [PATCH 09/29] Add a TODO in BlockStateUpgrader this issue can be worked around by adding a dummy schema, but it's a bit clunky. --- src/data/bedrock/block/upgrade/BlockStateUpgrader.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index ddb1e1359..b0612585c 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -83,6 +83,8 @@ final class BlockStateUpgrader{ * version doesn't tell us which of the schemas have already been applied. * If there's only one schema for a version (the norm), we can safely assume it's already been applied if * the version is the same, and skip over it. + * TODO: this causes issues when testing isolated schemas since there will only be one schema for a version. + * The second check should be disabled for that case. */ if($version > $resultVersion || (count($schemaList) === 1 && $version === $resultVersion)){ continue; From 2aa64dc15ef7bf0b35fb233e8d5348c258d9034d Mon Sep 17 00:00:00 2001 From: IvanCraft623 Date: Mon, 5 Aug 2024 17:13:23 -0500 Subject: [PATCH 10/29] Simplify phpstan-doc type hint for better readability --- .../block/upgrade/BlockStateUpgradeSchemaFlattenedName.php | 2 +- tools/generate-blockstate-upgrade-schema.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php index e2f7f798d..8259f690d 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php @@ -34,7 +34,7 @@ final class BlockStateUpgradeSchemaFlattenedName{ /** * @param string[] $flattenedValueRemaps * @phpstan-param array $flattenedValueRemaps - * @phpstan-param class-string|class-string|class-string|null $flattenedPropertyType + * @phpstan-param ?class-string $flattenedPropertyType */ public function __construct( public string $prefix, diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index d2d0a8c41..43bcc71f1 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -318,7 +318,7 @@ function findCommonSuffix(array $strings) : string{ * @param string[][][] $candidateFlattenedValues * @phpstan-param array>> $candidateFlattenedValues * @param string[] $candidateFlattenPropertyTypes - * @phpstan-param array|class-string|class-string> $candidateFlattenPropertyTypes + * @phpstan-param array> $candidateFlattenPropertyTypes * * @return BlockStateUpgradeSchemaFlattenedName[][] * @phpstan-return array> From c4a2b6494d9293e7473461464087a1f423ca055b Mon Sep 17 00:00:00 2001 From: zSALLAZAR <59490940+zSALLAZAR@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:12:47 +0200 Subject: [PATCH 11/29] Implement Aqua Affinity enchantment (#6408) --- src/data/bedrock/EnchantmentIdMap.php | 1 + src/item/enchantment/AvailableEnchantmentRegistry.php | 1 + src/item/enchantment/StringToEnchantmentParser.php | 1 + src/item/enchantment/VanillaEnchantments.php | 10 ++++++++++ 4 files changed, 13 insertions(+) diff --git a/src/data/bedrock/EnchantmentIdMap.php b/src/data/bedrock/EnchantmentIdMap.php index ae2460cfe..e3d652b19 100644 --- a/src/data/bedrock/EnchantmentIdMap.php +++ b/src/data/bedrock/EnchantmentIdMap.php @@ -43,6 +43,7 @@ final class EnchantmentIdMap{ $this->register(EnchantmentIds::PROJECTILE_PROTECTION, VanillaEnchantments::PROJECTILE_PROTECTION()); $this->register(EnchantmentIds::THORNS, VanillaEnchantments::THORNS()); $this->register(EnchantmentIds::RESPIRATION, VanillaEnchantments::RESPIRATION()); + $this->register(EnchantmentIds::AQUA_AFFINITY, VanillaEnchantments::AQUA_AFFINITY()); $this->register(EnchantmentIds::SHARPNESS, VanillaEnchantments::SHARPNESS()); //TODO: smite, bane of arthropods (these don't make sense now because their applicable mobs don't exist yet) diff --git a/src/item/enchantment/AvailableEnchantmentRegistry.php b/src/item/enchantment/AvailableEnchantmentRegistry.php index 2c6f421ed..cae94c666 100644 --- a/src/item/enchantment/AvailableEnchantmentRegistry.php +++ b/src/item/enchantment/AvailableEnchantmentRegistry.php @@ -56,6 +56,7 @@ final class AvailableEnchantmentRegistry{ $this->register(Enchantments::PROJECTILE_PROTECTION(), [Tags::ARMOR], []); $this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]); $this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []); + $this->register(Enchantments::AQUA_AFFINITY(), [Tags::HELMET], []); $this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []); $this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []); $this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []); diff --git a/src/item/enchantment/StringToEnchantmentParser.php b/src/item/enchantment/StringToEnchantmentParser.php index cd57eb203..47a750ff2 100644 --- a/src/item/enchantment/StringToEnchantmentParser.php +++ b/src/item/enchantment/StringToEnchantmentParser.php @@ -52,6 +52,7 @@ final class StringToEnchantmentParser extends StringToTParser{ $result->register("protection", fn() => VanillaEnchantments::PROTECTION()); $result->register("punch", fn() => VanillaEnchantments::PUNCH()); $result->register("respiration", fn() => VanillaEnchantments::RESPIRATION()); + $result->register("aqua_affinity", fn() => VanillaEnchantments::AQUA_AFFINITY()); $result->register("sharpness", fn() => VanillaEnchantments::SHARPNESS()); $result->register("silk_touch", fn() => VanillaEnchantments::SILK_TOUCH()); $result->register("swift_sneak", fn() => VanillaEnchantments::SWIFT_SNEAK()); diff --git a/src/item/enchantment/VanillaEnchantments.php b/src/item/enchantment/VanillaEnchantments.php index 779098c77..19ce39716 100644 --- a/src/item/enchantment/VanillaEnchantments.php +++ b/src/item/enchantment/VanillaEnchantments.php @@ -33,6 +33,7 @@ use pocketmine\utils\RegistryTrait; * @see build/generate-registry-annotations.php * @generate-registry-docblock * + * @method static Enchantment AQUA_AFFINITY() * @method static ProtectionEnchantment BLAST_PROTECTION() * @method static Enchantment EFFICIENCY() * @method static ProtectionEnchantment FEATHER_FALLING() @@ -144,6 +145,15 @@ final class VanillaEnchantments{ fn(int $level) : int => 10 * $level, 30 )); + self::register("AQUA_AFFINITY", new Enchantment( + KnownTranslationFactory::enchantment_waterWorker(), + Rarity::RARE, + 0, + 0, + 1, + null, + 40 + )); self::register("SHARPNESS", new SharpnessEnchantment( KnownTranslationFactory::enchantment_damage_all(), From 26761c2b87608afb6b2bbdfa5daa96bda53adcb9 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 9 Aug 2024 12:37:08 +0100 Subject: [PATCH 12/29] Blockstate schema tool now supports testing schemas as well as generating them this is useful when making changes to the generator, since regenerated schemas can now be tested for validity. This helps to find bugs in the generator. --- ...hp => blockstate-upgrade-schema-utils.php} | 70 +++++++++++++++---- 1 file changed, 55 insertions(+), 15 deletions(-) rename tools/{generate-blockstate-upgrade-schema.php => blockstate-upgrade-schema-utils.php} (90%) diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/blockstate-upgrade-schema-utils.php similarity index 90% rename from tools/generate-blockstate-upgrade-schema.php rename to tools/blockstate-upgrade-schema-utils.php index 43bcc71f1..c8dafcb67 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -21,9 +21,10 @@ declare(strict_types=1); -namespace pocketmine\tools\generate_blockstate_upgrade_schema; +namespace pocketmine\tools\blockstate_upgrade_schema_utils; use pocketmine\data\bedrock\block\BlockStateData; +use pocketmine\data\bedrock\block\upgrade\BlockStateUpgrader; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchema; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaBlockRemap; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenedName; @@ -607,32 +608,71 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad return $result; } +/** + * @param BlockStateMapping[][] $upgradeTable + * @phpstan-param array> $upgradeTable + */ +function testBlockStateUpgradeSchema(array $upgradeTable, BlockStateUpgradeSchema $schema) : bool{ + //TODO: HACK! + //the upgrader won't apply the schema if it's the same version and there's only one schema with a matching version + //ID (for performance reasons), which is a problem for testing isolated schemas + //add a dummy schema to bypass this optimization + $dummySchema = new BlockStateUpgradeSchema($schema->maxVersionMajor, $schema->maxVersionMinor, $schema->maxVersionPatch, $schema->maxVersionRevision, $schema->getSchemaId() + 1); + $upgrader = new BlockStateUpgrader([$schema, $dummySchema]); + + foreach($upgradeTable as $mappingsByOldName){ + foreach($mappingsByOldName as $mapping){ + $expectedNewState = $mapping->new; + + $actualNewState = $upgrader->upgrade($mapping->old); + + if(!$expectedNewState->equals($actualNewState)){ + \GlobalLogger::get()->error("Expected: " . $expectedNewState->toNbt()); + \GlobalLogger::get()->error("Actual: " . $actualNewState->toNbt()); + return false; + } + } + } + + return true; +} + /** * @param string[] $argv */ function main(array $argv) : int{ - if(count($argv) !== 3){ - fwrite(STDERR, "Required arguments: input file path, output file path\n"); + if(count($argv) !== 4 || ($argv[1] !== "generate" && $argv[1] !== "test")){ + fwrite(STDERR, "Required arguments: \n"); return 1; } - $input = $argv[1]; - $output = $argv[2]; + $mode = $argv[1]; + $upgradeTableFile = $argv[2]; + $schemaFile = $argv[3]; - $table = loadUpgradeTable($input, false); + $table = loadUpgradeTable($upgradeTableFile, false); ksort($table, SORT_STRING); - $diff = generateBlockStateUpgradeSchema($table); - if($diff->isEmpty()){ - \GlobalLogger::get()->warning("All states appear to be the same! No schema generated."); - return 0; + if($mode === "generate"){ + $diff = generateBlockStateUpgradeSchema($table); + if($diff->isEmpty()){ + \GlobalLogger::get()->warning("All states appear to be the same! No schema generated."); + return 0; + } + file_put_contents( + $schemaFile, + json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n" + ); + \GlobalLogger::get()->info("Schema file $schemaFile generated successfully."); + }else{ + $schema = BlockStateUpgradeSchemaUtils::loadSchemaFromString(Filesystem::fileGetContents($schemaFile), 0); + if(!testBlockStateUpgradeSchema($table, $schema)){ + \GlobalLogger::get()->error("Schema $schemaFile does not produce the results predicted by $upgradeTableFile"); + return 1; + } + \GlobalLogger::get()->info("Schema $schemaFile is valid according to $upgradeTableFile"); } - file_put_contents( - $output, - json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n" - ); - \GlobalLogger::get()->info("Schema file $output generated successfully."); return 0; } From 33dc995cc748cd6967c32a65844d88d28df45037 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 9 Aug 2024 13:12:46 +0100 Subject: [PATCH 13/29] blockstate-upgrade-schema-utils: added a command to update old schemas to a newer format this is useful when the generator was updated with new features & optimisations, to reduce the size and/or improve readability of existing schemas. --- tools/blockstate-upgrade-schema-utils.php | 137 ++++++++++++++++------ 1 file changed, 104 insertions(+), 33 deletions(-) diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index c8dafcb67..a9069d429 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -36,6 +36,7 @@ use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\Tag; use pocketmine\nbt\TreeRoot; +use pocketmine\network\mcpe\convert\BlockStateDictionary; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; @@ -90,18 +91,18 @@ function encodeProperty(Tag $tag) : string{ } /** + * @param TreeRoot[] $oldNewStateList + * @phpstan-param list $oldNewStateList + * * @return BlockStateMapping[][] * @phpstan-return array> */ -function loadUpgradeTable(string $file, bool $reverse) : array{ - $contents = Filesystem::fileGetContents($file); - $data = (new NetworkNbtSerializer())->readMultiple($contents); - +function buildUpgradeTableFromData(array $oldNewStateList, bool $reverse) : array{ $result = []; - for($i = 0; isset($data[$i]); $i += 2){ - $oldTag = $data[$i]->mustGetCompoundTag(); - $newTag = $data[$i + 1]->mustGetCompoundTag(); + for($i = 0; isset($oldNewStateList[$i]); $i += 2){ + $oldTag = $oldNewStateList[$i]->mustGetCompoundTag(); + $newTag = $oldNewStateList[$i + 1]->mustGetCompoundTag(); $old = BlockStateData::fromNbt($reverse ? $newTag : $oldTag); $new = BlockStateData::fromNbt($reverse ? $oldTag : $newTag); @@ -114,6 +115,17 @@ function loadUpgradeTable(string $file, bool $reverse) : array{ return $result; } +/** + * @return BlockStateMapping[][] + * @phpstan-return array> + */ +function loadUpgradeTableFromFile(string $file, bool $reverse) : array{ + $contents = Filesystem::fileGetContents($file); + $data = (new NetworkNbtSerializer())->readMultiple($contents); + + return buildUpgradeTableFromData($data, $reverse); +} + /** * @param BlockStateData[] $states * @phpstan-param array $states @@ -640,41 +652,100 @@ function testBlockStateUpgradeSchema(array $upgradeTable, BlockStateUpgradeSchem /** * @param string[] $argv */ -function main(array $argv) : int{ - if(count($argv) !== 4 || ($argv[1] !== "generate" && $argv[1] !== "test")){ - fwrite(STDERR, "Required arguments: \n"); - return 1; - } - - $mode = $argv[1]; +function cmdGenerate(array $argv) : int{ $upgradeTableFile = $argv[2]; $schemaFile = $argv[3]; - $table = loadUpgradeTable($upgradeTableFile, false); + $table = loadUpgradeTableFromFile($upgradeTableFile, false); ksort($table, SORT_STRING); - if($mode === "generate"){ - $diff = generateBlockStateUpgradeSchema($table); - if($diff->isEmpty()){ - \GlobalLogger::get()->warning("All states appear to be the same! No schema generated."); - return 0; - } - file_put_contents( - $schemaFile, - json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n" - ); - \GlobalLogger::get()->info("Schema file $schemaFile generated successfully."); - }else{ - $schema = BlockStateUpgradeSchemaUtils::loadSchemaFromString(Filesystem::fileGetContents($schemaFile), 0); - if(!testBlockStateUpgradeSchema($table, $schema)){ - \GlobalLogger::get()->error("Schema $schemaFile does not produce the results predicted by $upgradeTableFile"); - return 1; - } - \GlobalLogger::get()->info("Schema $schemaFile is valid according to $upgradeTableFile"); + $diff = generateBlockStateUpgradeSchema($table); + if($diff->isEmpty()){ + \GlobalLogger::get()->warning("All states appear to be the same! No schema generated."); + return 0; } + file_put_contents( + $schemaFile, + json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n" + ); + \GlobalLogger::get()->info("Schema file $schemaFile generated successfully."); + return 0; +} + +/** + * @param string[] $argv + */ +function cmdTest(array $argv) : int{ + $upgradeTableFile = $argv[2]; + $schemaFile = $argv[3]; + + $table = loadUpgradeTableFromFile($upgradeTableFile, false); + + ksort($table, SORT_STRING); + + $schema = BlockStateUpgradeSchemaUtils::loadSchemaFromString(Filesystem::fileGetContents($schemaFile), 0); + if(!testBlockStateUpgradeSchema($table, $schema)){ + \GlobalLogger::get()->error("Schema $schemaFile does not produce the results predicted by $upgradeTableFile"); + return 1; + } + \GlobalLogger::get()->info("Schema $schemaFile is valid according to $upgradeTableFile"); return 0; } +/** + * @param string[] $argv + */ +function cmdUpdate(array $argv) : int{ + [, , $oldSchemaFile, $oldPaletteFile, $newSchemaFile] = $argv; + + $palette = BlockStateDictionary::loadPaletteFromString(Filesystem::fileGetContents($oldPaletteFile)); + $schema = BlockStateUpgradeSchemaUtils::loadSchemaFromString(Filesystem::fileGetContents($oldSchemaFile), 0); + //TODO: HACK! + //the upgrader won't apply the schema if it's the same version and there's only one schema with a matching version + //ID (for performance reasons), which is a problem for testing isolated schemas + //add a dummy schema to bypass this optimization + $dummySchema = new BlockStateUpgradeSchema($schema->maxVersionMajor, $schema->maxVersionMinor, $schema->maxVersionPatch, $schema->maxVersionRevision, $schema->getSchemaId() + 1); + $upgrader = new BlockStateUpgrader([$schema, $dummySchema]); + + $tags = []; + foreach($palette as $stateData){ + $tags[] = new TreeRoot($stateData->toNbt()); + $tags[] = new TreeRoot($upgrader->upgrade($stateData)->toNbt()); + } + + $upgradeTable = buildUpgradeTableFromData($tags, false); + $newSchema = generateBlockStateUpgradeSchema($upgradeTable); + file_put_contents( + $newSchemaFile, + json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($newSchema), JSON_PRETTY_PRINT) . "\n" + ); + \GlobalLogger::get()->info("Schema file $newSchemaFile updated to new format (from $oldSchemaFile) successfully."); + return 0; +} + +/** + * @param string[] $argv + */ +function main(array $argv) : int{ + $options = [ + "generate" => [["palette upgrade table file", "schema output file"], cmdGenerate(...)], + "test" => [["palette upgrade table file", "schema output file"], cmdTest(...)], + "update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)] + ]; + + $selected = $argv[1] ?? null; + if($selected === null || !isset($options[$selected])){ + fwrite(STDERR, "Available commands:\n"); + foreach($options as $command => [$args, $callback]){ + fwrite(STDERR, " - $command " . implode(" ", array_map(fn(string $a) => "<$a>", $args)) . "\n"); + } + return 1; + } + + $callback = $options[$selected][1]; + return $callback($argv); +} + exit(main($argv)); From 13f5cc9f873a887d87dcfc0d894700f07752fbc8 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:11:22 +0300 Subject: [PATCH 14/29] ChiseledBookshelf: fixed function visibility (#6421) --- src/block/tile/ChiseledBookshelf.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index f9d61e7eb..6455208fe 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -59,7 +59,7 @@ class ChiseledBookshelf extends Tile implements Container{ $this->loadItems($nbt); } - public function writeSaveData(CompoundTag $nbt) : void{ + protected function writeSaveData(CompoundTag $nbt) : void{ $this->saveItems($nbt); } From 8cb2e577a1aca3f99d6504e48f34cbe75249c2ae Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Tue, 3 Sep 2024 02:02:06 +0300 Subject: [PATCH 15/29] Implement missing last interacted slot property in chiseled bookshelf (#6440) --- src/block/ChiseledBookshelf.php | 40 ++++++++++++++++++++++++++++ src/block/tile/ChiseledBookshelf.php | 22 +++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/block/ChiseledBookshelf.php b/src/block/ChiseledBookshelf.php index 89340a8f3..73c4861bf 100644 --- a/src/block/ChiseledBookshelf.php +++ b/src/block/ChiseledBookshelf.php @@ -48,11 +48,32 @@ class ChiseledBookshelf extends Opaque{ */ private array $slots = []; + private ?ChiseledBookshelfSlot $lastInteractedSlot = null; + protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{ $w->horizontalFacing($this->facing); $w->enumSet($this->slots, ChiseledBookshelfSlot::cases()); } + public function readStateFromWorld() : Block{ + $tile = $this->position->getWorld()->getTile($this->position); + if($tile instanceof TileChiseledBookshelf){ + $this->lastInteractedSlot = $tile->getLastInteractedSlot(); + }else{ + $this->lastInteractedSlot = null; + } + return $this; + } + + public function writeStateToWorld() : void{ + parent::writeStateToWorld(); + + $tile = $this->position->getWorld()->getTile($this->position); + if($tile instanceof TileChiseledBookshelf){ + $tile->setLastInteractedSlot($this->lastInteractedSlot); + } + } + /** * Returns whether the given slot is displayed as occupied. * This doesn't guarantee that there is or isn't a book in the bookshelf's inventory. @@ -92,6 +113,23 @@ class ChiseledBookshelf extends Opaque{ return $this->slots; } + /** + * Returns the last slot interacted by a player or null if no slot has been interacted with yet. + */ + public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{ + return $this->lastInteractedSlot; + } + + /** + * Sets the last slot interacted by a player. + * + * @return $this + */ + public function setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : self{ + $this->lastInteractedSlot = $lastInteractedSlot; + return $this; + } + public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($face !== $this->facing){ return false; @@ -112,10 +150,12 @@ class ChiseledBookshelf extends Opaque{ $returnedItems[] = $inventory->getItem($slot->value); $inventory->clear($slot->value); $this->setSlot($slot, false); + $this->lastInteractedSlot = $slot; }elseif($item instanceof WritableBookBase || $item instanceof Book || $item instanceof EnchantedBook){ //TODO: type tags like blocks would be better for this $inventory->setItem($slot->value, $item->pop()); $this->setSlot($slot, true); + $this->lastInteractedSlot = $slot; }else{ return true; } diff --git a/src/block/tile/ChiseledBookshelf.php b/src/block/tile/ChiseledBookshelf.php index 6455208fe..06175e27f 100644 --- a/src/block/tile/ChiseledBookshelf.php +++ b/src/block/tile/ChiseledBookshelf.php @@ -40,8 +40,12 @@ use function count; class ChiseledBookshelf extends Tile implements Container{ use ContainerTrait; + private const TAG_LAST_INTERACTED_SLOT = "LastInteractedSlot"; //TAG_Int + private SimpleInventory $inventory; + private ?ChiseledBookshelfSlot $lastInteractedSlot = null; + public function __construct(World $world, Vector3 $pos){ parent::__construct($world, $pos); $this->inventory = new SimpleInventory(count(ChiseledBookshelfSlot::cases())); @@ -55,12 +59,30 @@ class ChiseledBookshelf extends Tile implements Container{ return $this->inventory; } + public function getLastInteractedSlot() : ?ChiseledBookshelfSlot{ + return $this->lastInteractedSlot; + } + + public function setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : void{ + $this->lastInteractedSlot = $lastInteractedSlot; + } + public function readSaveData(CompoundTag $nbt) : void{ $this->loadItems($nbt); + + $lastInteractedSlot = $nbt->getInt(self::TAG_LAST_INTERACTED_SLOT, 0); + if($lastInteractedSlot !== 0){ + $this->lastInteractedSlot = ChiseledBookshelfSlot::tryFrom($lastInteractedSlot - 1); + } } protected function writeSaveData(CompoundTag $nbt) : void{ $this->saveItems($nbt); + + $nbt->setInt(self::TAG_LAST_INTERACTED_SLOT, $this->lastInteractedSlot !== null ? + $this->lastInteractedSlot->value + 1 : + 0 + ); } protected function loadItems(CompoundTag $tag) : void{ From f6e2a1ecce9a78ab1cb59d3a73472da657f3bea6 Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Mon, 9 Sep 2024 09:48:38 +0200 Subject: [PATCH 16/29] Validate transaction slots (#6304) --- src/inventory/ArmorInventory.php | 23 ++++++++++ src/inventory/BaseInventory.php | 11 ++++- src/inventory/SlotValidatedInventory.php | 46 +++++++++++++++++++ .../transaction/action/SlotChangeAction.php | 9 ++++ .../validator/CallbackSlotValidator.php | 44 ++++++++++++++++++ .../action/validator/SlotValidator.php | 38 +++++++++++++++ 6 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/inventory/SlotValidatedInventory.php create mode 100644 src/inventory/transaction/action/validator/CallbackSlotValidator.php create mode 100644 src/inventory/transaction/action/validator/SlotValidator.php diff --git a/src/inventory/ArmorInventory.php b/src/inventory/ArmorInventory.php index dcb3c04cb..0b3ae5b7b 100644 --- a/src/inventory/ArmorInventory.php +++ b/src/inventory/ArmorInventory.php @@ -23,8 +23,13 @@ declare(strict_types=1); namespace pocketmine\inventory; +use pocketmine\block\BlockTypeIds; use pocketmine\entity\Living; +use pocketmine\inventory\transaction\action\validator\CallbackSlotValidator; +use pocketmine\inventory\transaction\TransactionValidationException; +use pocketmine\item\Armor; use pocketmine\item\Item; +use pocketmine\item\ItemBlock; class ArmorInventory extends SimpleInventory{ public const SLOT_HEAD = 0; @@ -36,6 +41,8 @@ class ArmorInventory extends SimpleInventory{ protected Living $holder ){ parent::__construct(4); + + $this->validators->add(new CallbackSlotValidator($this->validate(...))); } public function getHolder() : Living{ @@ -73,4 +80,20 @@ class ArmorInventory extends SimpleInventory{ public function setBoots(Item $boots) : void{ $this->setItem(self::SLOT_FEET, $boots); } + + private function validate(Inventory $inventory, Item $item, int $slot) : ?TransactionValidationException{ + if($item instanceof Armor){ + if($item->getArmorSlot() !== $slot){ + return new TransactionValidationException("Armor item is in wrong slot"); + } + }else{ + if(!($slot === ArmorInventory::SLOT_HEAD && $item instanceof ItemBlock && ( + $item->getBlock()->getTypeId() === BlockTypeIds::CARVED_PUMPKIN || + $item->getBlock()->getTypeId() === BlockTypeIds::MOB_HEAD + ))){ + return new TransactionValidationException("Item is not accepted in an armor slot"); + } + } + return null; + } } diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 254e44b1e..522c827a4 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -36,8 +36,10 @@ use function spl_object_id; /** * This class provides everything needed to implement an inventory, minus the underlying storage system. + * + * @phpstan-import-type SlotValidators from SlotValidatedInventory */ -abstract class BaseInventory implements Inventory{ +abstract class BaseInventory implements Inventory, SlotValidatedInventory{ protected int $maxStackSize = Inventory::MAX_STACK; /** @var Player[] */ protected array $viewers = []; @@ -46,9 +48,12 @@ abstract class BaseInventory implements Inventory{ * @phpstan-var ObjectSet */ protected ObjectSet $listeners; + /** @phpstan-var SlotValidators */ + protected ObjectSet $validators; public function __construct(){ $this->listeners = new ObjectSet(); + $this->validators = new ObjectSet(); } public function getMaxStackSize() : int{ @@ -398,4 +403,8 @@ abstract class BaseInventory implements Inventory{ public function getListeners() : ObjectSet{ return $this->listeners; } + + public function getSlotValidators() : ObjectSet{ + return $this->validators; + } } diff --git a/src/inventory/SlotValidatedInventory.php b/src/inventory/SlotValidatedInventory.php new file mode 100644 index 000000000..f30ebf8a0 --- /dev/null +++ b/src/inventory/SlotValidatedInventory.php @@ -0,0 +1,46 @@ + + */ +interface SlotValidatedInventory{ + /** + * Returns a set of validators that will be used to determine whether an item can be placed in a particular slot. + * All validators need to return null for the transaction to be allowed. + * If one of the validators returns an exception, the transaction will be cancelled. + * + * There is no guarantee that the validators will be called in any particular order. + * + * @phpstan-return SlotValidators + */ + public function getSlotValidators() : ObjectSet; +} diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 453f0c4d2..68c3dba1b 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\inventory\transaction\action; use pocketmine\inventory\Inventory; +use pocketmine\inventory\SlotValidatedInventory; use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\TransactionValidationException; use pocketmine\item\Item; @@ -74,6 +75,14 @@ class SlotChangeAction extends InventoryAction{ if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){ throw new TransactionValidationException("Target item exceeds inventory max stack size"); } + if($this->inventory instanceof SlotValidatedInventory && !$this->targetItem->isNull()){ + foreach($this->inventory->getSlotValidators() as $validator){ + $ret = $validator->validate($this->inventory, $this->targetItem, $this->inventorySlot); + if($ret !== null){ + throw new TransactionValidationException("Target item is not accepted by the inventory at slot #" . $this->inventorySlot . ": " . $ret->getMessage(), 0, $ret); + } + } + } } /** diff --git a/src/inventory/transaction/action/validator/CallbackSlotValidator.php b/src/inventory/transaction/action/validator/CallbackSlotValidator.php new file mode 100644 index 000000000..1670dc623 --- /dev/null +++ b/src/inventory/transaction/action/validator/CallbackSlotValidator.php @@ -0,0 +1,44 @@ +validate)($inventory, $item, $slot); + } +} diff --git a/src/inventory/transaction/action/validator/SlotValidator.php b/src/inventory/transaction/action/validator/SlotValidator.php new file mode 100644 index 000000000..1b78c91f7 --- /dev/null +++ b/src/inventory/transaction/action/validator/SlotValidator.php @@ -0,0 +1,38 @@ + Date: Tue, 24 Sep 2024 21:25:10 -0500 Subject: [PATCH 17/29] Implement new 1.21 copper blocks (#6366) Added the following new blocks: - All types of Copper Bulb - All types of Copper Door - All types of Copper Trapdoor - All types of Chiseled Copper - All types of Copper Grate --- src/block/BlockTypeIds.php | 7 +- src/block/CopperBulb.php | 69 +++++++++++++ src/block/CopperDoor.php | 53 ++++++++++ src/block/CopperGrate.php | 33 +++++++ src/block/CopperTrapdoor.php | 44 +++++++++ src/block/VanillaBlocks.php | 12 +++ .../convert/BlockObjectToStateSerializer.php | 99 +++++++++++++++++++ .../BlockStateToObjectDeserializer.php | 72 ++++++++++++++ .../ItemSerializerDeserializerRegistrar.php | 28 ++++++ src/item/StringToItemParser.php | 5 + tests/phpstan/configs/phpstan-bugs.neon | 10 ++ .../block_factory_consistency_check.json | 5 + 12 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 src/block/CopperBulb.php create mode 100644 src/block/CopperDoor.php create mode 100644 src/block/CopperGrate.php create mode 100644 src/block/CopperTrapdoor.php diff --git a/src/block/BlockTypeIds.php b/src/block/BlockTypeIds.php index 29f4e650d..3914a4b74 100644 --- a/src/block/BlockTypeIds.php +++ b/src/block/BlockTypeIds.php @@ -760,8 +760,13 @@ final class BlockTypeIds{ public const POLISHED_TUFF_SLAB = 10730; public const POLISHED_TUFF_STAIRS = 10731; public const POLISHED_TUFF_WALL = 10732; + public const COPPER_BULB = 10733; + public const COPPER_DOOR = 10734; + public const COPPER_TRAPDOOR = 10735; + public const CHISELED_COPPER = 10736; + public const COPPER_GRATE = 10737; - public const FIRST_UNUSED_BLOCK_ID = 10733; + public const FIRST_UNUSED_BLOCK_ID = 10738; private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID; diff --git a/src/block/CopperBulb.php b/src/block/CopperBulb.php new file mode 100644 index 000000000..223c63527 --- /dev/null +++ b/src/block/CopperBulb.php @@ -0,0 +1,69 @@ +encodeLitState($w); + $w->bool($this->powered); + } + + /** @return $this */ + public function togglePowered(bool $powered) : self{ + if($powered === $this->powered){ + return $this; + } + if ($powered) { + $this->setLit(!$this->lit); + } + $this->setPowered($powered); + return $this; + } + + public function getLightLevel() : int{ + if ($this->lit) { + return match($this->oxidation){ + CopperOxidation::NONE => 15, + CopperOxidation::EXPOSED => 12, + CopperOxidation::WEATHERED => 8, + CopperOxidation::OXIDIZED => 4, + }; + } + + return 0; + } +} diff --git a/src/block/CopperDoor.php b/src/block/CopperDoor.php new file mode 100644 index 000000000..d53be2323 --- /dev/null +++ b/src/block/CopperDoor.php @@ -0,0 +1,53 @@ +isSneaking() && $this->onInteractCopper($item, $face, $clickVector, $player, $returnedItems)) { + //copy copper properties to other half + $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP); + $world = $this->position->getWorld(); + if ($other instanceof CopperDoor) { + $other->setOxidation($this->oxidation); + $other->setWaxed($this->waxed); + $world->setBlock($other->position, $other); + } + return true; + } + + return parent::onInteract($item, $face, $clickVector, $player, $returnedItems); + } +} diff --git a/src/block/CopperGrate.php b/src/block/CopperGrate.php new file mode 100644 index 000000000..fb59d846e --- /dev/null +++ b/src/block/CopperGrate.php @@ -0,0 +1,33 @@ +isSneaking() && $this->onInteractCopper($item, $face, $clickVector, $player, $returnedItems)) { + return true; + } + + return parent::onInteract($item, $face, $clickVector, $player, $returnedItems); + } +} diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index 13c7e869a..60540dfb8 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -181,6 +181,7 @@ use function strtolower; * @method static Wood CHERRY_WOOD() * @method static Chest CHEST() * @method static ChiseledBookshelf CHISELED_BOOKSHELF() + * @method static Copper CHISELED_COPPER() * @method static Opaque CHISELED_DEEPSLATE() * @method static Opaque CHISELED_NETHER_BRICKS() * @method static Opaque CHISELED_POLISHED_BLACKSTONE() @@ -209,7 +210,11 @@ use function strtolower; * @method static Concrete CONCRETE() * @method static ConcretePowder CONCRETE_POWDER() * @method static Copper COPPER() + * @method static CopperBulb COPPER_BULB() + * @method static CopperDoor COPPER_DOOR() + * @method static CopperGrate COPPER_GRATE() * @method static CopperOre COPPER_ORE() + * @method static CopperTrapdoor COPPER_TRAPDOOR() * @method static Coral CORAL() * @method static CoralBlock CORAL_BLOCK() * @method static FloorCoralFan CORAL_FAN() @@ -1642,9 +1647,16 @@ final class VanillaBlocks{ self::register("lightning_rod", new LightningRod(new BID(Ids::LIGHTNING_ROD), "Lightning Rod", $copperBreakInfo)); self::register("copper", new Copper(new BID(Ids::COPPER), "Copper Block", $copperBreakInfo)); + self::register("chiseled_copper", new Copper(new BID(Ids::CHISELED_COPPER), "Chiseled Copper", $copperBreakInfo)); + self::register("copper_grate", new CopperGrate(new BID(Ids::COPPER_GRATE), "Copper Grate", $copperBreakInfo)); self::register("cut_copper", new Copper(new BID(Ids::CUT_COPPER), "Cut Copper Block", $copperBreakInfo)); self::register("cut_copper_slab", new CopperSlab(new BID(Ids::CUT_COPPER_SLAB), "Cut Copper Slab", $copperBreakInfo)); self::register("cut_copper_stairs", new CopperStairs(new BID(Ids::CUT_COPPER_STAIRS), "Cut Copper Stairs", $copperBreakInfo)); + self::register("copper_bulb", new CopperBulb(new BID(Ids::COPPER_BULB), "Copper Bulb", $copperBreakInfo)); + + $copperDoorBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0)); + self::register("copper_door", new CopperDoor(new BID(Ids::COPPER_DOOR), "Copper Door", $copperDoorBreakInfo)); + self::register("copper_trapdoor", new CopperTrapdoor(new BID(Ids::COPPER_TRAPDOOR), "Copper Trapdoor", $copperDoorBreakInfo)); $candleBreakInfo = new Info(new BreakInfo(0.1)); self::register("candle", new Candle(new BID(Ids::CANDLE), "Candle", $candleBreakInfo)); diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 0768d21c3..f85e84551 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -58,8 +58,12 @@ use pocketmine\block\CocoaBlock; use pocketmine\block\Concrete; use pocketmine\block\ConcretePowder; use pocketmine\block\Copper; +use pocketmine\block\CopperBulb; +use pocketmine\block\CopperDoor; +use pocketmine\block\CopperGrate; use pocketmine\block\CopperSlab; use pocketmine\block\CopperStairs; +use pocketmine\block\CopperTrapdoor; use pocketmine\block\Coral; use pocketmine\block\CoralBlock; use pocketmine\block\DaylightSensor; @@ -1255,6 +1259,40 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER) ); }); + $this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{ + $oxidation = $block->getOxidation(); + return new Writer($block->isWaxed() ? + Helper::selectCopperId($oxidation, + Ids::WAXED_CHISELED_COPPER, + Ids::WAXED_EXPOSED_CHISELED_COPPER, + Ids::WAXED_WEATHERED_CHISELED_COPPER, + Ids::WAXED_OXIDIZED_CHISELED_COPPER + ) : + Helper::selectCopperId($oxidation, + Ids::CHISELED_COPPER, + Ids::EXPOSED_CHISELED_COPPER, + Ids::WEATHERED_CHISELED_COPPER, + Ids::OXIDIZED_CHISELED_COPPER + ) + ); + }); + $this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{ + $oxidation = $block->getOxidation(); + return new Writer($block->isWaxed() ? + Helper::selectCopperId($oxidation, + Ids::WAXED_COPPER_GRATE, + Ids::WAXED_EXPOSED_COPPER_GRATE, + Ids::WAXED_WEATHERED_COPPER_GRATE, + Ids::WAXED_OXIDIZED_COPPER_GRATE + ) : + Helper::selectCopperId($oxidation, + Ids::COPPER_GRATE, + Ids::EXPOSED_COPPER_GRATE, + Ids::WEATHERED_COPPER_GRATE, + Ids::OXIDIZED_COPPER_GRATE + ) + ); + }); $this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{ $oxidation = $block->getOxidation(); return new Writer($block->isWaxed() ? @@ -1322,6 +1360,67 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ ) ); }); + $this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{ + $oxidation = $block->getOxidation(); + return Writer::create($block->isWaxed() ? + Helper::selectCopperId($oxidation, + Ids::WAXED_COPPER_BULB, + Ids::WAXED_EXPOSED_COPPER_BULB, + Ids::WAXED_WEATHERED_COPPER_BULB, + Ids::WAXED_OXIDIZED_COPPER_BULB) : + Helper::selectCopperId($oxidation, + Ids::COPPER_BULB, + Ids::EXPOSED_COPPER_BULB, + Ids::WEATHERED_COPPER_BULB, + Ids::OXIDIZED_COPPER_BULB + )) + ->writeBool(StateNames::LIT, $block->isLit()) + ->writeBool(StateNames::POWERED_BIT, $block->isPowered()); + }); + $this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{ + $oxidation = $block->getOxidation(); + return Helper::encodeDoor( + $block, + new Writer($block->isWaxed() ? + Helper::selectCopperId( + $oxidation, + Ids::WAXED_COPPER_DOOR, + Ids::WAXED_EXPOSED_COPPER_DOOR, + Ids::WAXED_WEATHERED_COPPER_DOOR, + Ids::WAXED_OXIDIZED_COPPER_DOOR + ) : + Helper::selectCopperId( + $oxidation, + Ids::COPPER_DOOR, + Ids::EXPOSED_COPPER_DOOR, + Ids::WEATHERED_COPPER_DOOR, + Ids::OXIDIZED_COPPER_DOOR + ) + ) + ); + }); + $this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{ + $oxidation = $block->getOxidation(); + return Helper::encodeTrapdoor( + $block, + new Writer($block->isWaxed() ? + Helper::selectCopperId( + $oxidation, + Ids::WAXED_COPPER_TRAPDOOR, + Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, + Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, + Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR + ) : + Helper::selectCopperId( + $oxidation, + Ids::COPPER_TRAPDOOR, + Ids::EXPOSED_COPPER_TRAPDOOR, + Ids::WEATHERED_COPPER_TRAPDOOR, + Ids::OXIDIZED_COPPER_TRAPDOOR + ) + ) + ); + }); $this->map(Blocks::COCOA_POD(), function(CocoaBlock $block) : Writer{ return Writer::create(Ids::COCOA) ->writeInt(StateNames::AGE, $block->getAge()) diff --git a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php index 49365799c..19b5372a0 100644 --- a/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php +++ b/src/data/bedrock/block/convert/BlockStateToObjectDeserializer.php @@ -1156,6 +1156,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ return $block; }); + $this->map(Ids::CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE)); $this->map(Ids::CHISELED_QUARTZ_BLOCK, function(Reader $in) : Block{ return Blocks::CHISELED_QUARTZ() ->setAxis($in->readPillarAxis()); @@ -1187,6 +1188,14 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setFacing(Facing::opposite($in->readLegacyHorizontalFacing())) ); $this->map(Ids::COPPER_BLOCK, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::NONE)); + $this->map(Ids::COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in)); + $this->map(Ids::COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE)); + $this->map(Ids::COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in)); $this->map(Ids::CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE)); $this->mapSlab(Ids::CUT_COPPER_SLAB, Ids::DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE)); $this->mapStairs(Ids::CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE)); @@ -1245,9 +1254,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ ->setFacing($in->readCardinalHorizontalFacing()); }); $this->map(Ids::EXPOSED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::EXPOSED)); + $this->map(Ids::EXPOSED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED)); + $this->map(Ids::EXPOSED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED)); $this->map(Ids::EXPOSED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED)); $this->mapSlab(Ids::EXPOSED_CUT_COPPER_SLAB, Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED)); $this->mapStairs(Ids::EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED)); + $this->map(Ids::EXPOSED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in)); + $this->map(Ids::EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in)); $this->map(Ids::FARMLAND, function(Reader $in) : Block{ return Blocks::FARMLAND() ->setWetness($in->readBoundedInt(StateNames::MOISTURIZED_AMOUNT, 0, 7)); @@ -1401,9 +1419,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ $this->mapStairs(Ids::NORMAL_STONE_STAIRS, fn() => Blocks::STONE_STAIRS()); $this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE)->setAxis($in->readPillarAxis())); $this->map(Ids::OXIDIZED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED)); + $this->map(Ids::OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED)); + $this->map(Ids::OXIDIZED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED)); $this->map(Ids::OXIDIZED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED)); $this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED)); $this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED)); + $this->map(Ids::OXIDIZED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in)); + $this->map(Ids::OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in)); $this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT)->setAxis($in->readPillarAxis())); $this->mapSlab(Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB, fn() => Blocks::FAKE_WOODEN_SLAB()); $this->map(Ids::PINK_PETALS, function(Reader $in) : Block{ @@ -1677,25 +1704,70 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{ }); $this->map(Ids::WATER, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::WATER(), $in)); $this->map(Ids::WAXED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::NONE)); + $this->map(Ids::WAXED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE)); + $this->map(Ids::WAXED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE)); $this->map(Ids::WAXED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE)); $this->mapSlab(Ids::WAXED_CUT_COPPER_SLAB, Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE)); $this->mapStairs(Ids::WAXED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE)); + $this->map(Ids::WAXED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WAXED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in)); + $this->map(Ids::WAXED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in)); $this->map(Ids::WAXED_EXPOSED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::EXPOSED)); + $this->map(Ids::WAXED_EXPOSED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED)); + $this->map(Ids::WAXED_EXPOSED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED)); $this->map(Ids::WAXED_EXPOSED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED)); $this->mapSlab(Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED)); $this->mapStairs(Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED)); + $this->map(Ids::WAXED_EXPOSED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WAXED_EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in)); + $this->map(Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in)); $this->map(Ids::WAXED_OXIDIZED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED)); + $this->map(Ids::WAXED_OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED)); + $this->map(Ids::WAXED_OXIDIZED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED)); $this->map(Ids::WAXED_OXIDIZED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED)); $this->mapSlab(Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED)); $this->mapStairs(Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED)); + $this->map(Ids::WAXED_OXIDIZED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WAXED_OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in)); + $this->map(Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in)); $this->map(Ids::WAXED_WEATHERED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::WEATHERED)); + $this->map(Ids::WAXED_WEATHERED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED)); + $this->map(Ids::WAXED_WEATHERED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED)); $this->map(Ids::WAXED_WEATHERED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED)); $this->mapSlab(Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED)); $this->mapStairs(Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED)); + $this->map(Ids::WAXED_WEATHERED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WAXED_WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in)); + $this->map(Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in)); $this->map(Ids::WEATHERED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::WEATHERED)); + $this->map(Ids::WEATHERED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED)); + $this->map(Ids::WEATHERED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED)); $this->map(Ids::WEATHERED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED)); $this->mapSlab(Ids::WEATHERED_CUT_COPPER_SLAB, Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED)); $this->mapStairs(Ids::WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED)); + $this->map(Ids::WEATHERED_COPPER_BULB, function(Reader $in) : Block{ + return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED) + ->setLit($in->readBool(StateNames::LIT)) + ->setPowered($in->readBool(StateNames::POWERED_BIT)); + }); + $this->map(Ids::WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in)); + $this->map(Ids::WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in)); $this->map(Ids::WEEPING_VINES, function(Reader $in) : Block{ return Blocks::WEEPING_VINES() ->setAge($in->readBoundedInt(StateNames::WEEPING_VINES_AGE, 0, 25)); diff --git a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php index de9b5ae5e..d06b41626 100644 --- a/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php +++ b/src/data/bedrock/item/ItemSerializerDeserializerRegistrar.php @@ -25,7 +25,9 @@ namespace pocketmine\data\bedrock\item; use pocketmine\block\Bed; use pocketmine\block\Block; +use pocketmine\block\CopperDoor; use pocketmine\block\MobHead; +use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\DyeColor; use pocketmine\block\VanillaBlocks as Blocks; use pocketmine\data\bedrock\CompoundTypeIds; @@ -56,6 +58,7 @@ final class ItemSerializerDeserializerRegistrar{ $this->register1to1BlockWithMetaMappings(); $this->register1to1ItemWithMetaMappings(); $this->register1ToNItemMappings(); + $this->registerMiscBlockMappings(); $this->registerMiscItemMappings(); } @@ -538,4 +541,29 @@ final class ItemSerializerDeserializerRegistrar{ } $this->serializer?->map(Items::DYE(), fn(Dye $item) => new Data(DyeColorIdMap::getInstance()->toItemId($item->getColor()))); } + + /** + * Registers serializers and deserializers for PocketMine-MP blockitems that don't fit any other pattern. + * Ideally we want to get rid of this completely, if possible. + * + * Most of these are single PocketMine-MP blocks which map to multiple IDs depending on their properties, which is + * complex to implement in a generic way. + */ + private function registerMiscBlockMappings() : void{ + $copperDoorStateIdMap = []; + foreach ([ + [Ids::COPPER_DOOR, CopperOxidation::NONE, false], + [Ids::EXPOSED_COPPER_DOOR, CopperOxidation::EXPOSED, false], + [Ids::WEATHERED_COPPER_DOOR, CopperOxidation::WEATHERED, false], + [Ids::OXIDIZED_COPPER_DOOR, CopperOxidation::OXIDIZED, false], + [Ids::WAXED_COPPER_DOOR, CopperOxidation::NONE, true], + [Ids::WAXED_EXPOSED_COPPER_DOOR, CopperOxidation::EXPOSED, true], + [Ids::WAXED_WEATHERED_COPPER_DOOR, CopperOxidation::WEATHERED, true], + [Ids::WAXED_OXIDIZED_COPPER_DOOR, CopperOxidation::OXIDIZED, true] + ] as [$id, $oxidation, $waxed]) { + $copperDoorStateIdMap[$oxidation->value][$waxed ? 1 : 0] = $id; + $this->deserializer?->mapBlock($id, fn() => Blocks::COPPER_DOOR()->setOxidation($oxidation)->setWaxed($waxed)); + } + $this->serializer?->mapBlock(Blocks::COPPER_DOOR(), fn(CopperDoor $block) => new Data($copperDoorStateIdMap[$block->getOxidation()->value][$block->isWaxed() ? 1 : 0])); + } } diff --git a/src/item/StringToItemParser.php b/src/item/StringToItemParser.php index 9f5db6950..ee0f1f5c5 100644 --- a/src/item/StringToItemParser.php +++ b/src/item/StringToItemParser.php @@ -98,9 +98,14 @@ final class StringToItemParser extends StringToTParser{ foreach(["" => false, "waxed_" => true] as $waxedPrefix => $waxed){ $register = fn(string $name, \Closure $callback) => $result->registerBlock($waxedPrefix . $oxPrefix . $name, $callback); $register("copper_block", fn() => Blocks::COPPER()->setOxidation($oxidation)->setWaxed($waxed)); + $register("chiseled_copper", fn() => Blocks::CHISELED_COPPER()->setOxidation($oxidation)->setWaxed($waxed)); + $register("copper_grate", fn() => Blocks::COPPER_GRATE()->setOxidation($oxidation)->setWaxed($waxed)); $register("cut_copper_block", fn() => Blocks::CUT_COPPER()->setOxidation($oxidation)->setWaxed($waxed)); $register("cut_copper_stairs", fn() => Blocks::CUT_COPPER_STAIRS()->setOxidation($oxidation)->setWaxed($waxed)); $register("cut_copper_slab", fn() => Blocks::CUT_COPPER_SLAB()->setOxidation($oxidation)->setWaxed($waxed)); + $register("copper_bulb", fn() => Blocks::COPPER_BULB()->setOxidation($oxidation)->setWaxed($waxed)); + $register("copper_door", fn() => Blocks::COPPER_DOOR()->setOxidation($oxidation)->setWaxed($waxed)); + $register("copper_trapdoor", fn() => Blocks::COPPER_TRAPDOOR()->setOxidation($oxidation)->setWaxed($waxed)); } } diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index de38903bd..0ead377ba 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -10,6 +10,16 @@ parameters: count: 1 path: ../../../src/block/DoubleTallGrass.php + - + message: "#^Method pocketmine\\\\block\\\\CopperDoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" + count: 1 + path: ../../../src/block/utils/CopperTrait.php + + - + message: "#^Method pocketmine\\\\block\\\\CopperTrapdoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" + count: 1 + path: ../../../src/block/utils/CopperTrait.php + - message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#" count: 1 diff --git a/tests/phpunit/block/block_factory_consistency_check.json b/tests/phpunit/block/block_factory_consistency_check.json index 97c24b52e..79804d8cb 100644 --- a/tests/phpunit/block/block_factory_consistency_check.json +++ b/tests/phpunit/block/block_factory_consistency_check.json @@ -105,6 +105,7 @@ "CHERRY_WOOD": 6, "CHEST": 4, "CHISELED_BOOKSHELF": 256, + "CHISELED_COPPER": 8, "CHISELED_DEEPSLATE": 1, "CHISELED_NETHER_BRICKS": 1, "CHISELED_POLISHED_BLACKSTONE": 1, @@ -133,7 +134,11 @@ "CONCRETE": 16, "CONCRETE_POWDER": 16, "COPPER": 8, + "COPPER_BULB": 32, + "COPPER_DOOR": 256, + "COPPER_GRATE": 8, "COPPER_ORE": 1, + "COPPER_TRAPDOOR": 128, "CORAL": 10, "CORAL_BLOCK": 10, "CORAL_FAN": 20, From f6e6f15c639bc09e8384416f1111eb12eff2f0b1 Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:28:17 +0300 Subject: [PATCH 18/29] Implemented a proper way to handle items cooldown (#6405) --- src/item/ChorusFruit.php | 4 +++ src/item/EnderPearl.php | 4 +++ src/item/Item.php | 14 +++++++++ src/item/ItemCooldownTags.php | 45 +++++++++++++++++++++++++++++ src/network/mcpe/NetworkSession.php | 10 +++++++ src/player/Player.php | 13 ++++++--- 6 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/item/ItemCooldownTags.php diff --git a/src/item/ChorusFruit.php b/src/item/ChorusFruit.php index c12724d7c..e10c51957 100644 --- a/src/item/ChorusFruit.php +++ b/src/item/ChorusFruit.php @@ -88,4 +88,8 @@ class ChorusFruit extends Food{ public function getCooldownTicks() : int{ return 20; } + + public function getCooldownTag() : ?string{ + return ItemCooldownTags::CHORUS_FRUIT; + } } diff --git a/src/item/EnderPearl.php b/src/item/EnderPearl.php index 76bcb358e..7109d3ae0 100644 --- a/src/item/EnderPearl.php +++ b/src/item/EnderPearl.php @@ -45,4 +45,8 @@ class EnderPearl extends ProjectileItem{ public function getCooldownTicks() : int{ return 20; } + + public function getCooldownTag() : ?string{ + return ItemCooldownTags::ENDER_PEARL; + } } diff --git a/src/item/Item.php b/src/item/Item.php index 1a74345b5..205f15e13 100644 --- a/src/item/Item.php +++ b/src/item/Item.php @@ -654,6 +654,20 @@ class Item implements \JsonSerializable{ return 0; } + /** + * Returns a tag that identifies a group of items that should have cooldown at the same time + * regardless of their state or type. + * When cooldown starts, any other items with the same cooldown tag can't be used until the cooldown expires. + * Such behaviour can be seen in goat horns and shields. + * + * If tag is null, item state id will be used to store cooldown. + * + * @see ItemCooldownTags + */ + public function getCooldownTag() : ?string{ + return null; + } + /** * Compares an Item to this Item and check if they match. * diff --git a/src/item/ItemCooldownTags.php b/src/item/ItemCooldownTags.php new file mode 100644 index 000000000..f0ef6d169 --- /dev/null +++ b/src/item/ItemCooldownTags.php @@ -0,0 +1,45 @@ +sendDataPacket(OpenSignPacket::create(BlockPosition::fromVector3($signPosition), $frontSide)); } + public function onItemCooldownChanged(Item $item, int $ticks) : void{ + $this->sendDataPacket(PlayerStartItemCooldownPacket::create( + GlobalItemDataHandlers::getSerializer()->serializeType($item)->getName(), + $ticks + )); + } + public function tick() : void{ if(!$this->isConnected()){ $this->dispose(); diff --git a/src/player/Player.php b/src/player/Player.php index d442c6a3b..8ae206e1a 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -283,7 +283,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ protected string $locale = "en_US"; protected int $startAction = -1; - /** @var int[] ID => ticks map */ + + /** + * @phpstan-var array + * @var int[] stateId|cooldownTag => ticks map + */ protected array $usedItemsCooldown = []; private int $lastEmoteTick = 0; @@ -697,7 +701,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ */ public function getItemCooldownExpiry(Item $item) : int{ $this->checkItemCooldowns(); - return $this->usedItemsCooldown[$item->getStateId()] ?? 0; + return $this->usedItemsCooldown[$item->getCooldownTag() ?? $item->getStateId()] ?? 0; } /** @@ -705,7 +709,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ */ public function hasItemCooldown(Item $item) : bool{ $this->checkItemCooldowns(); - return isset($this->usedItemsCooldown[$item->getStateId()]); + return isset($this->usedItemsCooldown[$item->getCooldownTag() ?? $item->getStateId()]); } /** @@ -714,7 +718,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ public function resetItemCooldown(Item $item, ?int $ticks = null) : void{ $ticks = $ticks ?? $item->getCooldownTicks(); if($ticks > 0){ - $this->usedItemsCooldown[$item->getStateId()] = $this->server->getTick() + $ticks; + $this->usedItemsCooldown[$item->getCooldownTag() ?? $item->getStateId()] = $this->server->getTick() + $ticks; + $this->getNetworkSession()->onItemCooldownChanged($item, $ticks); } } From c8f567b0937c406097b9cc26fc26b5cd4c7ea21d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 17 Oct 2024 20:24:57 +0100 Subject: [PATCH 19/29] Fix missing arg count check --- tools/blockstate-upgrade-schema-utils.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index a9069d429..cd782b9c4 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -745,6 +745,10 @@ function main(array $argv) : int{ } $callback = $options[$selected][1]; + if(count($argv) !== count($options[$selected][0]) + 2){ + fwrite(STDERR, "Usage: {$argv[0]} $selected " . implode(" ", array_map(fn(string $a) => "<$a>", $options[$selected][0])) . "\n"); + return 1; + } return $callback($argv); } From 7e343617b9641875c1c6cea58957d94034d0cdde Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:34:42 +0300 Subject: [PATCH 20/29] Rename ICopper to CopperMaterial (#6470) --- src/block/Copper.php | 4 ++-- src/block/CopperBulb.php | 4 ++-- src/block/CopperDoor.php | 4 ++-- src/block/CopperGrate.php | 4 ++-- src/block/CopperSlab.php | 4 ++-- src/block/CopperStairs.php | 4 ++-- src/block/CopperTrapdoor.php | 4 ++-- src/block/utils/{ICopper.php => CopperMaterial.php} | 6 +++--- .../block/convert/BlockStateDeserializerHelper.php | 10 +++++----- 9 files changed, 22 insertions(+), 22 deletions(-) rename src/block/utils/{ICopper.php => CopperMaterial.php} (85%) diff --git a/src/block/Copper.php b/src/block/Copper.php index 8e678bf42..d285e6ec0 100644 --- a/src/block/Copper.php +++ b/src/block/Copper.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperTrait; -use pocketmine\block\utils\ICopper; -class Copper extends Opaque implements ICopper{ +class Copper extends Opaque implements CopperMaterial{ use CopperTrait; } diff --git a/src/block/CopperBulb.php b/src/block/CopperBulb.php index 223c63527..97fc209fe 100644 --- a/src/block/CopperBulb.php +++ b/src/block/CopperBulb.php @@ -23,14 +23,14 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperOxidation; use pocketmine\block\utils\CopperTrait; -use pocketmine\block\utils\ICopper; use pocketmine\block\utils\LightableTrait; use pocketmine\block\utils\PoweredByRedstoneTrait; use pocketmine\data\runtime\RuntimeDataDescriber; -class CopperBulb extends Opaque implements ICopper{ +class CopperBulb extends Opaque implements CopperMaterial{ use CopperTrait; use PoweredByRedstoneTrait; use LightableTrait{ diff --git a/src/block/CopperDoor.php b/src/block/CopperDoor.php index d53be2323..82a611206 100644 --- a/src/block/CopperDoor.php +++ b/src/block/CopperDoor.php @@ -23,14 +23,14 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperTrait; -use pocketmine\block\utils\ICopper; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; -class CopperDoor extends Door implements ICopper{ +class CopperDoor extends Door implements CopperMaterial{ use CopperTrait{ onInteract as onInteractCopper; } diff --git a/src/block/CopperGrate.php b/src/block/CopperGrate.php index fb59d846e..d646d1333 100644 --- a/src/block/CopperGrate.php +++ b/src/block/CopperGrate.php @@ -23,10 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperTrait; -use pocketmine\block\utils\ICopper; -class CopperGrate extends Transparent implements ICopper{ +class CopperGrate extends Transparent implements CopperMaterial{ use CopperTrait; //TODO: waterlogging! diff --git a/src/block/CopperSlab.php b/src/block/CopperSlab.php index 4194cd854..cc1838e29 100644 --- a/src/block/CopperSlab.php +++ b/src/block/CopperSlab.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperTrait; -use pocketmine\block\utils\ICopper; -class CopperSlab extends Slab implements ICopper{ +class CopperSlab extends Slab implements CopperMaterial{ use CopperTrait; } diff --git a/src/block/CopperStairs.php b/src/block/CopperStairs.php index dd8f44f7a..ecb224319 100644 --- a/src/block/CopperStairs.php +++ b/src/block/CopperStairs.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperTrait; -use pocketmine\block\utils\ICopper; -class CopperStairs extends Stair implements ICopper{ +class CopperStairs extends Stair implements CopperMaterial{ use CopperTrait; } diff --git a/src/block/CopperTrapdoor.php b/src/block/CopperTrapdoor.php index ab743af44..e7d56fa0c 100644 --- a/src/block/CopperTrapdoor.php +++ b/src/block/CopperTrapdoor.php @@ -23,13 +23,13 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperTrait; -use pocketmine\block\utils\ICopper; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; -class CopperTrapdoor extends Trapdoor implements ICopper{ +class CopperTrapdoor extends Trapdoor implements CopperMaterial{ use CopperTrait{ onInteract as onInteractCopper; } diff --git a/src/block/utils/ICopper.php b/src/block/utils/CopperMaterial.php similarity index 85% rename from src/block/utils/ICopper.php rename to src/block/utils/CopperMaterial.php index a749efe63..6df22620b 100644 --- a/src/block/utils/ICopper.php +++ b/src/block/utils/CopperMaterial.php @@ -26,13 +26,13 @@ namespace pocketmine\block\utils; /** * Represents copper blocks that have oxidized and waxed variations. */ -interface ICopper{ +interface CopperMaterial{ public function getOxidation() : CopperOxidation; - public function setOxidation(CopperOxidation $oxidation) : ICopper; + public function setOxidation(CopperOxidation $oxidation) : CopperMaterial; public function isWaxed() : bool; - public function setWaxed(bool $waxed) : ICopper; + public function setWaxed(bool $waxed) : CopperMaterial; } diff --git a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php index c0807c8a6..e183589c9 100644 --- a/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php +++ b/src/data/bedrock/block/convert/BlockStateDeserializerHelper.php @@ -45,8 +45,8 @@ use pocketmine\block\Slab; use pocketmine\block\Stair; use pocketmine\block\Stem; use pocketmine\block\Trapdoor; +use pocketmine\block\utils\CopperMaterial; use pocketmine\block\utils\CopperOxidation; -use pocketmine\block\utils\ICopper; use pocketmine\block\utils\SlabType; use pocketmine\block\VanillaBlocks; use pocketmine\block\Wall; @@ -98,24 +98,24 @@ final class BlockStateDeserializerHelper{ } /** - * @phpstan-template TBlock of ICopper + * @phpstan-template TBlock of CopperMaterial * * @phpstan-param TBlock $block * @phpstan-return TBlock */ - public static function decodeCopper(ICopper $block, CopperOxidation $oxidation) : ICopper{ + public static function decodeCopper(CopperMaterial $block, CopperOxidation $oxidation) : CopperMaterial{ $block->setOxidation($oxidation); $block->setWaxed(false); return $block; } /** - * @phpstan-template TBlock of ICopper + * @phpstan-template TBlock of CopperMaterial * * @phpstan-param TBlock $block * @phpstan-return TBlock */ - public static function decodeWaxedCopper(ICopper $block, CopperOxidation $oxidation) : ICopper{ + public static function decodeWaxedCopper(CopperMaterial $block, CopperOxidation $oxidation) : CopperMaterial{ $block->setOxidation($oxidation); $block->setWaxed(true); return $block; From 7f9e79c83e1130f3cf74c33917a33ff6fd7a074b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 24 Oct 2024 13:30:24 +0100 Subject: [PATCH 21/29] Automatically test new schemas to ensure they produce the results predicted by the input file --- tools/blockstate-upgrade-schema-utils.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index a779ab9c3..c8dfb0ab2 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -692,6 +692,13 @@ function cmdGenerate(array $argv) : int{ \GlobalLogger::get()->warning("All states appear to be the same! No schema generated."); return 0; } + + if(!testBlockStateUpgradeSchema($table, $diff)){ + \GlobalLogger::get()->error("Generated schema does not produce the results expected by $upgradeTableFile"); + \GlobalLogger::get()->error("This is probably a bug in the schema generation code. Please report this to the developers."); + return 1; + } + file_put_contents( $schemaFile, json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n" @@ -744,6 +751,13 @@ function cmdUpdate(array $argv) : int{ $upgradeTable = buildUpgradeTableFromData($tags, false); $newSchema = generateBlockStateUpgradeSchema($upgradeTable); + + if(!testBlockStateUpgradeSchema($upgradeTable, $newSchema)){ + \GlobalLogger::get()->error("Updated schema does not produce the expected results!"); + \GlobalLogger::get()->error("This is probably a bug in the schema generation code. Please report this to the developers."); + return 1; + } + file_put_contents( $newSchemaFile, json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($newSchema), JSON_PRETTY_PRINT) . "\n" From acbfb0a3e9a8e2d4be25836d7cd359461c7e4b73 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 24 Oct 2024 13:30:55 +0100 Subject: [PATCH 22/29] Support for updating a batch of schemas using BlockPaletteArchive --- tools/blockstate-upgrade-schema-utils.php | 50 ++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index c8dfb0ab2..b73954aff 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -41,6 +41,7 @@ use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; +use Symfony\Component\Filesystem\Path; use function array_key_first; use function array_key_last; use function array_keys; @@ -50,15 +51,19 @@ use function array_unique; use function array_values; use function count; use function dirname; +use function file_exists; use function file_put_contents; use function fwrite; use function get_class; use function get_debug_type; use function implode; +use function is_dir; use function is_numeric; use function json_encode; use function ksort; use function min; +use function preg_match; +use function scandir; use function sort; use function strlen; use function strrev; @@ -766,6 +771,48 @@ function cmdUpdate(array $argv) : int{ return 0; } +/** + * @param string[] $argv + */ +function cmdUpdateAll(array $argv) : int{ + $oldPaletteFilenames = [ + '1.9.0' => '1.09.0', + '1.19.50' => '1.19.50.23_beta', + '1.19.60' => '1.19.60.26_beta', + '1.19.70' => '1.19.70.26_beta', + '1.19.80' => '1.19.80.24_beta', + ]; + $schemaDir = $argv[2]; + $paletteArchiveDir = $argv[3]; + + $schemaFileNames = scandir($schemaDir); + if($schemaFileNames === false){ + \GlobalLogger::get()->error("Failed to read schema directory $schemaDir"); + return 1; + } + foreach($schemaFileNames as $file){ + $schemaFile = Path::join($schemaDir, $file); + if(!file_exists($schemaFile) || is_dir($schemaFile)){ + continue; + } + + if(preg_match('/^\d{4}_(.+?)_to_(.+?).json/', $file, $matches) !== 1){ + continue; + } + $oldPaletteFile = Path::join($paletteArchiveDir, ($oldPaletteFilenames[$matches[1]] ?? $matches[1]) . '.nbt'); + + //a bit clunky but it avoids having to make yet another function + //TODO: perhaps in the future we should write the result to a tmpfile until all schemas are updated, + //and then copy the results into place at the end + if(cmdUpdate([$argv[0], "update", $schemaFile, $oldPaletteFile, $schemaFile]) !== 0){ + return 1; + } + } + + \GlobalLogger::get()->info("All schemas updated successfully."); + return 0; +} + /** * @param string[] $argv */ @@ -773,7 +820,8 @@ function main(array $argv) : int{ $options = [ "generate" => [["palette upgrade table file", "schema output file"], cmdGenerate(...)], "test" => [["palette upgrade table file", "schema output file"], cmdTest(...)], - "update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)] + "update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)], + "update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)] ]; $selected = $argv[1] ?? null; From 22718c4971460fae5b4d98fabff199da77c6acf0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 24 Oct 2024 16:12:28 +0100 Subject: [PATCH 23/29] Add support for specialized flattenedProperties in schema format --- .../block/upgrade/BlockStateUpgradeSchema.php | 8 + .../BlockStateUpgradeSchemaBlockRemap.php | 6 +- ...=> BlockStateUpgradeSchemaFlattenInfo.php} | 2 +- .../upgrade/BlockStateUpgradeSchemaUtils.php | 71 +++++--- .../block/upgrade/BlockStateUpgrader.php | 62 +++++-- .../model/BlockStateUpgradeSchemaModel.php | 6 + ...BlockStateUpgradeSchemaModelBlockRemap.php | 6 +- ...ockStateUpgradeSchemaModelFlattenInfo.php} | 2 +- .../block/upgrade/BlockStateUpgraderTest.php | 19 ++ tools/blockstate-upgrade-schema-utils.php | 170 +++++++++++++----- 10 files changed, 251 insertions(+), 101 deletions(-) rename src/data/bedrock/block/upgrade/{BlockStateUpgradeSchemaFlattenedName.php => BlockStateUpgradeSchemaFlattenInfo.php} (97%) rename src/data/bedrock/block/upgrade/model/{BlockStateUpgradeSchemaModelFlattenedName.php => BlockStateUpgradeSchemaModelFlattenInfo.php} (95%) diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php index 6d280ecf7..f8894cfd2 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchema.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\upgrade; +use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenInfo as FlattenInfo; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap as ValueRemap; use pocketmine\nbt\tag\Tag; use function count; @@ -58,6 +59,12 @@ final class BlockStateUpgradeSchema{ */ public array $remappedPropertyValues = []; + /** + * @var FlattenInfo[] + * @phpstan-var array + */ + public array $flattenedProperties = []; + /** * @var BlockStateUpgradeSchemaBlockRemap[][] * @phpstan-var array> @@ -93,6 +100,7 @@ final class BlockStateUpgradeSchema{ $this->removedProperties, $this->renamedProperties, $this->remappedPropertyValues, + $this->flattenedProperties, $this->remappedStates, ] as $list){ if(count($list) !== 0){ diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php index 611ad04e2..676afbaf4 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php @@ -40,7 +40,7 @@ final class BlockStateUpgradeSchemaBlockRemap{ */ public function __construct( public array $oldState, - public string|BlockStateUpgradeSchemaFlattenedName $newName, + public string|BlockStateUpgradeSchemaFlattenInfo $newName, public array $newState, public array $copiedState ){} @@ -48,8 +48,8 @@ final class BlockStateUpgradeSchemaBlockRemap{ public function equals(self $that) : bool{ $sameName = $this->newName === $that->newName || ( - $this->newName instanceof BlockStateUpgradeSchemaFlattenedName && - $that->newName instanceof BlockStateUpgradeSchemaFlattenedName && + $this->newName instanceof BlockStateUpgradeSchemaFlattenInfo && + $that->newName instanceof BlockStateUpgradeSchemaFlattenInfo && $this->newName->equals($that->newName) ); if(!$sameName){ diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php similarity index 97% rename from src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php rename to src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php index 8259f690d..4a14a1291 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenInfo.php @@ -29,7 +29,7 @@ use pocketmine\nbt\tag\StringTag; use function ksort; use const SORT_STRING; -final class BlockStateUpgradeSchemaFlattenedName{ +final class BlockStateUpgradeSchemaFlattenInfo{ /** * @param string[] $flattenedValueRemaps diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php index d66b7e68c..08eba8978 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php @@ -25,7 +25,7 @@ namespace pocketmine\data\bedrock\block\upgrade; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModel; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelBlockRemap; -use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelFlattenedName; +use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelFlattenInfo; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelTag; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelValueRemap; use pocketmine\nbt\tag\ByteTag; @@ -155,24 +155,17 @@ final class BlockStateUpgradeSchemaUtils{ } } + foreach(Utils::stringifyKeys($model->flattenedProperties ?? []) as $blockName => $flattenRule){ + $result->flattenedProperties[$blockName] = self::jsonModelToFlattenRule($flattenRule); + } + foreach(Utils::stringifyKeys($model->remappedStates ?? []) as $oldBlockName => $remaps){ foreach($remaps as $remap){ if(isset($remap->newName)){ $remapName = $remap->newName; }elseif(isset($remap->newFlattenedName)){ $flattenRule = $remap->newFlattenedName; - $remapName = new BlockStateUpgradeSchemaFlattenedName( - $flattenRule->prefix, - $flattenRule->flattenedProperty, - $flattenRule->suffix, - $flattenRule->flattenedValueRemaps ?? [], - match($flattenRule->flattenedPropertyType){ - "string", null => StringTag::class, - "int" => IntTag::class, - "byte" => ByteTag::class, - default => throw new \UnexpectedValueException("Unexpected flattened property type $flattenRule->flattenedPropertyType, expected 'string', 'int' or 'byte'") - } - ); + $remapName = self::jsonModelToFlattenRule($flattenRule); }else{ throw new \UnexpectedValueException("Expected exactly one of 'newName' or 'newFlattenedName' properties to be set"); } @@ -265,6 +258,36 @@ final class BlockStateUpgradeSchemaUtils{ $model->remappedPropertyValues = $modelDedupMapping; } + private static function flattenRuleToJsonModel(BlockStateUpgradeSchemaFlattenInfo $flattenRule) : BlockStateUpgradeSchemaModelFlattenInfo{ + return new BlockStateUpgradeSchemaModelFlattenInfo( + $flattenRule->prefix, + $flattenRule->flattenedProperty, + $flattenRule->suffix, + $flattenRule->flattenedValueRemaps, + match($flattenRule->flattenedPropertyType){ + StringTag::class => null, //omit for TAG_String, as this is the common case + ByteTag::class => "byte", + IntTag::class => "int", + default => throw new \LogicException("Unexpected tag type " . $flattenRule->flattenedPropertyType . " in flattened property type") + } + ); + } + + private static function jsonModelToFlattenRule(BlockStateUpgradeSchemaModelFlattenInfo $flattenRule) : BlockStateUpgradeSchemaFlattenInfo{ + return new BlockStateUpgradeSchemaFlattenInfo( + $flattenRule->prefix, + $flattenRule->flattenedProperty, + $flattenRule->suffix, + $flattenRule->flattenedValueRemaps ?? [], + match ($flattenRule->flattenedPropertyType) { + "string", null => StringTag::class, + "int" => IntTag::class, + "byte" => ByteTag::class, + default => throw new \UnexpectedValueException("Unexpected flattened property type $flattenRule->flattenedPropertyType, expected 'string', 'int' or 'byte'") + } + ); + } + public static function toJsonModel(BlockStateUpgradeSchema $schema) : BlockStateUpgradeSchemaModel{ $result = new BlockStateUpgradeSchemaModel(); $result->maxVersionMajor = $schema->maxVersionMajor; @@ -303,25 +326,19 @@ final class BlockStateUpgradeSchemaUtils{ self::buildRemappedValuesIndex($schema, $result); + foreach(Utils::stringifyKeys($schema->flattenedProperties) as $blockName => $flattenRule){ + $result->flattenedProperties[$blockName] = self::flattenRuleToJsonModel($flattenRule); + } + if(isset($result->flattenedProperties)){ + ksort($result->flattenedProperties); + } + foreach(Utils::stringifyKeys($schema->remappedStates) as $oldBlockName => $remaps){ $keyedRemaps = []; foreach($remaps as $remap){ $modelRemap = new BlockStateUpgradeSchemaModelBlockRemap( array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->oldState), - is_string($remap->newName) ? - $remap->newName : - new BlockStateUpgradeSchemaModelFlattenedName( - $remap->newName->prefix, - $remap->newName->flattenedProperty, - $remap->newName->suffix, - $remap->newName->flattenedValueRemaps, - match($remap->newName->flattenedPropertyType){ - StringTag::class => null, //omit for TAG_String, as this is the common case - ByteTag::class => "byte", - IntTag::class => "int", - default => throw new \LogicException("Unexpected tag type " . $remap->newName->flattenedPropertyType . " in flattened property type") - } - ), + is_string($remap->newName) ? $remap->newName : self::flattenRuleToJsonModel($remap->newName), array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState), $remap->copiedState ); diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index b0612585c..e95f3c80f 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -110,10 +110,21 @@ final class BlockStateUpgrader{ } $oldName = $blockStateData->getName(); - $newName = $schema->renamedIds[$oldName] ?? null; + $states = $blockStateData->getStates(); + + if(isset($schema->renamedIds[$oldName]) && isset($schema->flattenedProperties[$oldName])){ + //TODO: this probably ought to be validated when the schema is constructed + throw new AssumptionFailedError("Both renamedIds and flattenedProperties are set for the same block ID \"$oldName\" - don't know what to do"); + } + if(isset($schema->renamedIds[$oldName])){ + $newName = $schema->renamedIds[$oldName] ?? null; + }elseif(isset($schema->flattenedProperties[$oldName])){ + [$newName, $states] = $this->applyPropertyFlattened($schema->flattenedProperties[$oldName], $oldName, $states); + }else{ + $newName = null; + } $stateChanges = 0; - $states = $blockStateData->getStates(); $states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges); $states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges); @@ -146,22 +157,9 @@ final class BlockStateUpgrader{ if(is_string($remap->newName)){ $newName = $remap->newName; }else{ - $flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null; - $expectedType = $remap->newName->flattenedPropertyType; - if(!$flattenedValue instanceof $expectedType){ - //flattened property is not of the expected type, so this transformation is not applicable - continue; - } - $embedKey = match(get_class($flattenedValue)){ - StringTag::class => $flattenedValue->getValue(), - ByteTag::class => (string) $flattenedValue->getValue(), - IntTag::class => (string) $flattenedValue->getValue(), - //flattenedPropertyType is always one of these three types, but PHPStan doesn't know that - default => throw new AssumptionFailedError("flattenedPropertyType should be one of these three types, but have " . get_class($flattenedValue)), - }; - $embedValue = $remap->newName->flattenedValueRemaps[$embedKey] ?? $embedKey; - $newName = sprintf("%s%s%s", $remap->newName->prefix, $embedValue, $remap->newName->suffix); - unset($oldState[$remap->newName->flattenedProperty]); + //yes, overwriting $oldState here is intentional, although we probably don't actually need it anyway + //it shouldn't make any difference unless the flattened property appears in copiedState for some reason + [$newName, $oldState] = $this->applyPropertyFlattened($remap->newName, $oldName, $oldState); } $newState = $remap->newState; @@ -279,4 +277,32 @@ final class BlockStateUpgrader{ return $states; } + + /** + * @param Tag[] $states + * @phpstan-param array $states + * + * @return (string|Tag[])[] + * @phpstan-return array{0: string, 1: array} + */ + private function applyPropertyFlattened(BlockStateUpgradeSchemaFlattenInfo $flattenInfo, string $oldName, array $states) : array{ + $flattenedValue = $states[$flattenInfo->flattenedProperty] ?? null; + $expectedType = $flattenInfo->flattenedPropertyType; + if(!$flattenedValue instanceof $expectedType){ + //flattened property is not of the expected type, so this transformation is not applicable + return [$oldName, $states]; + } + $embedKey = match(get_class($flattenedValue)){ + StringTag::class => $flattenedValue->getValue(), + ByteTag::class => (string) $flattenedValue->getValue(), + IntTag::class => (string) $flattenedValue->getValue(), + //flattenedPropertyType is always one of these three types, but PHPStan doesn't know that + default => throw new AssumptionFailedError("flattenedPropertyType should be one of these three types, but have " . get_class($flattenedValue)), + }; + $embedValue = $flattenInfo->flattenedValueRemaps[$embedKey] ?? $embedKey; + $newName = sprintf("%s%s%s", $flattenInfo->prefix, $embedValue, $flattenInfo->suffix); + unset($states[$flattenInfo->flattenedProperty]); + + return [$newName, $states]; + } } diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModel.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModel.php index 1a4a14c87..7d91438e4 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModel.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModel.php @@ -75,6 +75,12 @@ final class BlockStateUpgradeSchemaModel implements \JsonSerializable{ */ public array $remappedPropertyValuesIndex; + /** + * @var BlockStateUpgradeSchemaModelFlattenInfo[] + * @phpstan-var array + */ + public array $flattenedProperties; + /** * @var BlockStateUpgradeSchemaModelBlockRemap[][] * @phpstan-var array> diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php index 0f518479e..6accf1f02 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php @@ -43,7 +43,7 @@ final class BlockStateUpgradeSchemaModelBlockRemap{ * Either this or newName must be present * Due to technical limitations of jsonmapper, we can't use a union type here */ - public BlockStateUpgradeSchemaModelFlattenedName $newFlattenedName; + public BlockStateUpgradeSchemaModelFlattenInfo $newFlattenedName; /** * @var BlockStateUpgradeSchemaModelTag[]|null @@ -67,9 +67,9 @@ final class BlockStateUpgradeSchemaModelBlockRemap{ * @phpstan-param array $newState * @phpstan-param list $copiedState */ - public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenedName $newNameRule, array $newState, array $copiedState){ + public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenInfo $newNameRule, array $newState, array $copiedState){ $this->oldState = count($oldState) === 0 ? null : $oldState; - if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenedName){ + if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenInfo){ $this->newFlattenedName = $newNameRule; }else{ $this->newName = $newNameRule; diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php similarity index 95% rename from src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php rename to src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php index 6d03bbc12..6da590287 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenInfo.php @@ -25,7 +25,7 @@ namespace pocketmine\data\bedrock\block\upgrade\model; use function count; -final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializable{ +final class BlockStateUpgradeSchemaModelFlattenInfo implements \JsonSerializable{ /** @required */ public string $prefix; diff --git a/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php b/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php index 4d4d321ec..91afd8ed9 100644 --- a/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php +++ b/tests/phpunit/data/bedrock/block/upgrade/BlockStateUpgraderTest.php @@ -24,8 +24,10 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\upgrade; use PHPUnit\Framework\TestCase; +use pocketmine\block\Block; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\nbt\tag\IntTag; +use pocketmine\nbt\tag\StringTag; use const PHP_INT_MAX; class BlockStateUpgraderTest extends TestCase{ @@ -210,6 +212,23 @@ class BlockStateUpgraderTest extends TestCase{ self::assertSame($upgradedStateData->getState(self::TEST_PROPERTY_2)?->getValue(), $valueAfter); } + public function testFlattenProperty() : void{ + $schema = $this->getNewSchema(); + $schema->flattenedProperties[self::TEST_BLOCK] = new BlockStateUpgradeSchemaFlattenInfo( + "minecraft:", + "test", + "_suffix", + [], + StringTag::class + ); + + $stateData = new BlockStateData(self::TEST_BLOCK, ["test" => new StringTag("value1")], 0); + $upgradedStateData = $this->upgrade($stateData, fn() => $stateData); + + self::assertSame("minecraft:value1_suffix", $upgradedStateData->getName()); + self::assertEmpty($upgradedStateData->getStates()); + } + /** * @phpstan-return \Generator */ diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index b73954aff..e1a5e7c3b 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -27,7 +27,7 @@ use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgrader; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchema; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaBlockRemap; -use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenedName; +use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenInfo; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap; use pocketmine\nbt\LittleEndianNbtSerializer; @@ -183,6 +183,11 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra $removedProperties = []; $renamedProperties = []; + $uniqueNewIds = []; + foreach($upgradeTable as $pair){ + $uniqueNewIds[$pair->new->getName()] = $pair->new->getName(); + } + foreach(Utils::stringifyKeys($newProperties) as $newPropertyName => $newPropertyValues){ if(count($newPropertyValues) === 1){ $newPropertyValue = $newPropertyValues[array_key_first($newPropertyValues)]; @@ -278,6 +283,61 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra } } + if(count($uniqueNewIds) > 1){ + //detect possible flattening + $flattenedProperty = null; + $flattenedPropertyType = null; + $flattenedPropertyMap = []; + foreach($removedProperties as $removedProperty){ + $valueMap = []; + foreach($upgradeTable as $pair){ + $oldValue = $pair->old->getState($removedProperty); + if($oldValue === null){ + throw new AssumptionFailedError("We already checked that all states had consistent old properties"); + } + //TODO: lots of similar logic to the remappedStates builder below + if(!$oldValue instanceof ByteTag && !$oldValue instanceof IntTag && !$oldValue instanceof StringTag){ + //unknown property type - bad candidate for flattening + continue 2; + } + if($flattenedPropertyType === null){ + $flattenedPropertyType = get_class($oldValue); + }elseif(!$oldValue instanceof $flattenedPropertyType){ + //property type mismatch - bad candidate for flattening + continue 2; + } + + $rawValue = (string) $oldValue->getValue(); + $existingNewId = $valueMap[$rawValue] ?? null; + if($existingNewId !== null && $existingNewId !== $pair->new->getName()){ + //this property value is associated with multiple new IDs - bad candidate for flattening + continue 2; + } + $valueMap[$rawValue] = $pair->new->getName(); + } + + if($flattenedProperty !== null){ + //found multiple candidates for flattening - fallback to remappedStates + return false; + } + //we found a suitable candidate + $flattenedProperty = $removedProperty; + $flattenedPropertyMap = $valueMap; + break; + } + + if($flattenedProperty === null){ + //can't figure out how the new IDs are related to the old states - fallback to remappedStates + return false; + } + if($flattenedPropertyType === null){ + throw new AssumptionFailedError("This should never happen at this point"); + } + + $result->flattenedProperties[$oldName] = buildFlattenPropertyRule($flattenedPropertyMap, $flattenedProperty, $flattenedPropertyType); + unset($removedProperties[$flattenedProperty]); + } + //finally, write the results to the schema if(count($remappedPropertyValues) !== 0){ @@ -332,60 +392,69 @@ function findCommonSuffix(array $strings) : string{ return strrev(findCommonPrefix($reversed)); } +/** + * @param string[] $valueToId + * @phpstan-param array $valueToId + * @phpstan-param class-string $propertyType + */ +function buildFlattenPropertyRule(array $valueToId, string $propertyName, string $propertyType) : BlockStateUpgradeSchemaFlattenInfo{ + $ids = array_values($valueToId); + + //TODO: this is a bit too enthusiastic. For example, when flattening the old "stone", it will see that + //"granite", "andesite", "stone" etc all have "e" as a common suffix, which works, but looks a bit daft. + //This also causes more remaps to be generated than necessary, since some of the values are already + //contained in the new ID. + $idPrefix = findCommonPrefix($ids); + $idSuffix = findCommonSuffix($ids); + if(strlen($idSuffix) < 2){ + $idSuffix = ""; + } + + $valueMap = []; + foreach(Utils::stringifyKeys($valueToId) as $value => $newId){ + $newValue = substr($newId, strlen($idPrefix), $idSuffix !== "" ? -strlen($idSuffix) : null); + if($newValue !== $value){ + $valueMap[$value] = $newValue; + } + } + + $allNumeric = true; + if(count($valueMap) > 0){ + foreach(Utils::stringifyKeys($valueMap) as $value => $newValue){ + if(!is_numeric($value)){ + $allNumeric = false; + break; + } + } + if($allNumeric){ + //add a dummy key to force the JSON to be an object and not a list + $valueMap["dummy"] = "map_not_list"; + } + } + + return new BlockStateUpgradeSchemaFlattenInfo( + $idPrefix, + $propertyName, + $idSuffix, + $valueMap, + $propertyType, + ); +} + /** * @param string[][][] $candidateFlattenedValues * @phpstan-param array>> $candidateFlattenedValues * @param string[] $candidateFlattenPropertyTypes * @phpstan-param array> $candidateFlattenPropertyTypes * - * @return BlockStateUpgradeSchemaFlattenedName[][] - * @phpstan-return array> + * @return BlockStateUpgradeSchemaFlattenInfo[][] + * @phpstan-return array> */ function buildFlattenPropertyRules(array $candidateFlattenedValues, array $candidateFlattenPropertyTypes) : array{ $flattenPropertyRules = []; foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){ foreach(Utils::stringifyKeys($filters) as $filter => $valueToId){ - $ids = array_values($valueToId); - - //TODO: this is a bit too enthusiastic. For example, when flattening the old "stone", it will see that - //"granite", "andesite", "stone" etc all have "e" as a common suffix, which works, but looks a bit daft. - //This also causes more remaps to be generated than necessary, since some of the values are already - //contained in the new ID. - $idPrefix = findCommonPrefix($ids); - $idSuffix = findCommonSuffix($ids); - if(strlen($idSuffix) < 2){ - $idSuffix = ""; - } - - $valueMap = []; - foreach(Utils::stringifyKeys($valueToId) as $value => $newId){ - $newValue = substr($newId, strlen($idPrefix), $idSuffix !== "" ? -strlen($idSuffix) : null); - if($newValue !== $value){ - $valueMap[$value] = $newValue; - } - } - - $allNumeric = true; - if(count($valueMap) > 0){ - foreach(Utils::stringifyKeys($valueMap) as $value => $newValue){ - if(!is_numeric($value)){ - $allNumeric = false; - break; - } - } - if($allNumeric){ - //add a dummy key to force the JSON to be an object and not a list - $valueMap["dummy"] = "map_not_list"; - } - } - - $flattenPropertyRules[$propertyName][$filter] = new BlockStateUpgradeSchemaFlattenedName( - $idPrefix, - $propertyName, - $idSuffix, - $valueMap, - $candidateFlattenPropertyTypes[$propertyName], - ); + $flattenPropertyRules[$propertyName][$filter] = buildFlattenPropertyRule($valueToId, $propertyName, $candidateFlattenPropertyTypes[$propertyName]); } } ksort($flattenPropertyRules, SORT_STRING); @@ -642,10 +711,15 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad throw new \RuntimeException("States with the same ID should be fully consistent"); } }else{ - //block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap - //even if some of the states stay under the same ID, the compression techniques used by this function - //implicitly rely on knowing the full set of old states and their new transformations - $result->remappedStates[$oldName] = processRemappedStates($blockStateMappings); + //try processing this as a regular state group first + //if a property was flattened into the ID, the remaining states will normally be consistent + //if not we fall back to remap states and state filters + if(!processStateGroup($oldName, $blockStateMappings, $result)){ + //block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap + //even if some of the states stay under the same ID, the compression techniques used by this function + //implicitly rely on knowing the full set of old states and their new transformations + $result->remappedStates[$oldName] = processRemappedStates($blockStateMappings); + } } } From d01203d7c4d48be9c7a92dbfa385ac8844ea1841 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 24 Oct 2024 18:50:28 +0100 Subject: [PATCH 24/29] Reduce code duplication --- tools/blockstate-upgrade-schema-utils.php | 81 +++++++++++------------ 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/tools/blockstate-upgrade-schema-utils.php b/tools/blockstate-upgrade-schema-utils.php index e1a5e7c3b..b7a9a4169 100644 --- a/tools/blockstate-upgrade-schema-utils.php +++ b/tools/blockstate-upgrade-schema-utils.php @@ -295,25 +295,9 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra if($oldValue === null){ throw new AssumptionFailedError("We already checked that all states had consistent old properties"); } - //TODO: lots of similar logic to the remappedStates builder below - if(!$oldValue instanceof ByteTag && !$oldValue instanceof IntTag && !$oldValue instanceof StringTag){ - //unknown property type - bad candidate for flattening + if(!checkFlattenPropertySuitability($oldValue, $flattenedPropertyType, $pair->new->getName(), $valueMap)){ continue 2; } - if($flattenedPropertyType === null){ - $flattenedPropertyType = get_class($oldValue); - }elseif(!$oldValue instanceof $flattenedPropertyType){ - //property type mismatch - bad candidate for flattening - continue 2; - } - - $rawValue = (string) $oldValue->getValue(); - $existingNewId = $valueMap[$rawValue] ?? null; - if($existingNewId !== null && $existingNewId !== $pair->new->getName()){ - //this property value is associated with multiple new IDs - bad candidate for flattening - continue 2; - } - $valueMap[$rawValue] = $pair->new->getName(); } if($flattenedProperty !== null){ @@ -392,6 +376,37 @@ function findCommonSuffix(array $strings) : string{ return strrev(findCommonPrefix($reversed)); } +/** + * @param string[] $valueToIdMap + * @phpstan-param ?class-string $expectedType + * @phpstan-param-out class-string $expectedType + * @phpstan-param array $valueToIdMap + * @phpstan-param-out array $valueToIdMap + */ +function checkFlattenPropertySuitability(Tag $oldValue, ?string &$expectedType, string $actualNewId, array &$valueToIdMap) : bool{ + //TODO: lots of similar logic to the remappedStates builder below + if(!$oldValue instanceof ByteTag && !$oldValue instanceof IntTag && !$oldValue instanceof StringTag){ + //unknown property type - bad candidate for flattening + return false; + } + if($expectedType === null){ + $expectedType = get_class($oldValue); + }elseif(!$oldValue instanceof $expectedType){ + //property type mismatch - bad candidate for flattening + return false; + } + + $rawValue = (string) $oldValue->getValue(); + $existingNewId = $valueToIdMap[$rawValue] ?? null; + if($existingNewId !== null && $existingNewId !== $actualNewId){ + //this property value is associated with multiple new IDs - bad candidate for flattening + return false; + } + $valueToIdMap[$rawValue] = $actualNewId; + + return true; +} + /** * @param string[] $valueToId * @phpstan-param array $valueToId @@ -522,23 +537,6 @@ function processRemappedStates(array $upgradeTable) : array{ if(isset($notFlattenedProperties[$propertyName])){ continue; } - if(!$propertyValue instanceof StringTag && !$propertyValue instanceof IntTag && !$propertyValue instanceof ByteTag){ - $notFlattenedProperties[$propertyName] = true; - continue; - } - $previousType = $candidateFlattenedPropertyTypes[$propertyName] ?? null; - if($previousType !== null && $previousType !== get_class($propertyValue)){ - //mismatched types for the same property name - this has never happened so far, but it's not impossible - $notFlattenedProperties[$propertyName] = true; - continue; - } - $candidateFlattenedPropertyTypes[$propertyName] = get_class($propertyValue); - - $rawValue = (string) $propertyValue->getValue(); - if($rawValue === ""){ - $notFlattenedProperties[$propertyName] = true; - continue; - } $filter = $pair->old->getStates(); foreach($unchangedStatesByNewName[$pair->new->getName()] as $unchangedPropertyName){ @@ -551,16 +549,13 @@ function processRemappedStates(array $upgradeTable) : array{ unset($filter[$propertyName]); $rawFilter = encodeOrderedProperties($filter); - if(isset($candidateFlattenedValues[$propertyName][$rawFilter])){ - $valuesToIds = $candidateFlattenedValues[$propertyName][$rawFilter]; - $existingNewId = $valuesToIds[$rawValue] ?? null; - if($existingNewId !== null && $existingNewId !== $pair->new->getName()){ - //this old value is associated with multiple new IDs - bad candidate for flattening - $notFlattenedProperties[$propertyName] = true; - continue; - } + $candidateFlattenedValues[$propertyName][$rawFilter] ??= []; + $expectedType = $candidateFlattenedPropertyTypes[$propertyName] ?? null; + if(!checkFlattenPropertySuitability($propertyValue, $expectedType, $pair->new->getName(), $candidateFlattenedValues[$propertyName][$rawFilter])){ + $notFlattenedProperties[$propertyName] = true; + continue; } - $candidateFlattenedValues[$propertyName][$rawFilter][$rawValue] = $pair->new->getName(); + $candidateFlattenedPropertyTypes[$propertyName] = $expectedType; } } foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){ From c0b74b03419038f34bb25fa0c5dc05440bf86c5b Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Sun, 3 Nov 2024 14:05:46 +0000 Subject: [PATCH 25/29] Update BlockStateUpgrader.php --- src/data/bedrock/block/upgrade/BlockStateUpgrader.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index e95f3c80f..2dce762b8 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -157,9 +157,8 @@ final class BlockStateUpgrader{ if(is_string($remap->newName)){ $newName = $remap->newName; }else{ - //yes, overwriting $oldState here is intentional, although we probably don't actually need it anyway - //it shouldn't make any difference unless the flattened property appears in copiedState for some reason - [$newName, $oldState] = $this->applyPropertyFlattened($remap->newName, $oldName, $oldState); + //discard flatten modifications to state - the remap newState and copiedState will take care of it + [$newName, ] = $this->applyPropertyFlattened($remap->newName, $oldName, $oldState); } $newState = $remap->newState; From c63d0ef1b6187ab7bbffb70625d509620c1e3ae7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 3 Nov 2024 14:39:41 +0000 Subject: [PATCH 26/29] Fix dodgy ignored PHPStan error --- src/network/mcpe/InventoryManager.php | 1 + tests/phpstan/configs/actual-problems.neon | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index c0969b61b..e4c303121 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -364,6 +364,7 @@ class InventoryManager{ FurnaceType::FURNACE => WindowTypes::FURNACE, FurnaceType::BLAST_FURNACE => WindowTypes::BLAST_FURNACE, FurnaceType::SMOKER => WindowTypes::SMOKER, + FurnaceType::CAMPFIRE, FurnaceType::SOUL_CAMPFIRE => throw new \LogicException("Campfire inventory cannot be displayed to a player") }, $inv instanceof EnchantInventory => WindowTypes::ENCHANTMENT, $inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND, diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 2d0e6d398..cc647da80 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -580,11 +580,6 @@ parameters: count: 1 path: ../../../src/network/mcpe/ChunkRequestTask.php - - - message: "#^Match expression does not handle remaining values\\: pocketmine\\\\crafting\\\\FurnaceType\\:\\:CAMPFIRE\\|pocketmine\\\\crafting\\\\FurnaceType\\:\\:SOUL_CAMPFIRE$#" - count: 1 - path: ../../../src/network/mcpe/InventoryManager.php - - message: "#^Cannot call method doFirstSpawn\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#" count: 1 From 72fc1386319f481328d4c9b4f7efe899767ecf81 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 3 Nov 2024 14:43:15 +0000 Subject: [PATCH 27/29] Regenerate PHPStan baselines --- tests/phpstan/configs/actual-problems.neon | 25 ---------------------- tests/phpstan/configs/phpstan-bugs.neon | 14 ++++++------ 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index cc647da80..e778cf004 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -785,11 +785,6 @@ parameters: count: 1 path: ../../../src/resourcepacks/ZippedResourcePack.php - - - message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int given\\.$#" - count: 1 - path: ../../../src/resourcepacks/ZippedResourcePack.php - - message: "#^Property pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:\\$fileResource \\(resource\\) does not accept resource\\|false\\.$#" count: 1 @@ -1035,11 +1030,6 @@ parameters: count: 1 path: ../../../src/world/format/io/region/RegionLoader.php - - - message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int given\\.$#" - count: 1 - path: ../../../src/world/format/io/region/RegionLoader.php - - message: "#^Parameter \\#2 \\$size of function ftruncate expects int\\<0, max\\>, int given\\.$#" count: 1 @@ -1190,18 +1180,3 @@ parameters: count: 1 path: ../../../src/world/light/SkyLightUpdate.php - - - message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#" - count: 1 - path: ../../phpunit/block/BlockTest.php - - - - message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#" - count: 1 - path: ../../phpunit/block/regenerate_consistency_check.php - - - - message: "#^Parameter \\#1 \\$logFile of class pocketmine\\\\utils\\\\MainLogger constructor expects string, string\\|false given\\.$#" - count: 1 - path: ../../phpunit/scheduler/AsyncPoolTest.php - diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 0ead377ba..0fc3defda 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -5,20 +5,20 @@ parameters: count: 1 path: ../../../src/block/CakeWithCandle.php - - - message: "#^Method pocketmine\\\\block\\\\DoubleTallGrass\\:\\:traitGetDropsForIncompatibleTool\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: ../../../src/block/DoubleTallGrass.php - - message: "#^Method pocketmine\\\\block\\\\CopperDoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" count: 1 - path: ../../../src/block/utils/CopperTrait.php + path: ../../../src/block/CopperDoor.php - message: "#^Method pocketmine\\\\block\\\\CopperTrapdoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#" count: 1 - path: ../../../src/block/utils/CopperTrait.php + path: ../../../src/block/CopperTrapdoor.php + + - + message: "#^Method pocketmine\\\\block\\\\DoubleTallGrass\\:\\:traitGetDropsForIncompatibleTool\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: ../../../src/block/DoubleTallGrass.php - message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#" From 84464cde4f6f3506e0b55e136d83592de2ce1fa0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 3 Nov 2024 14:44:50 +0000 Subject: [PATCH 28/29] Update BedrockBlockUpgradeSchema --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 9ecbad32f..ed5bb582f 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "composer-runtime-api": "^2.0", "adhocore/json-comment": "~1.2.0", "pocketmine/netresearch-jsonmapper": "~v4.4.999", - "pocketmine/bedrock-block-upgrade-schema": "~4.5.0+bedrock-1.21.40", + "pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40", "pocketmine/bedrock-data": "~2.14.0+bedrock-1.21.40", "pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40", "pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.40", diff --git a/composer.lock b/composer.lock index d4cb38ddc..b2f3e7858 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5c5882370131d2ae3a043819c05e6f9c", + "content-hash": "b2fbf6e7a9d650341dc71fa4dd124681", "packages": [ { "name": "adhocore/json-comment", @@ -127,16 +127,16 @@ }, { "name": "pocketmine/bedrock-block-upgrade-schema", - "version": "4.5.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git", - "reference": "7943b894e050d68dd21b5c7fa609827a4e2e30f1" + "reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/7943b894e050d68dd21b5c7fa609827a4e2e30f1", - "reference": "7943b894e050d68dd21b5c7fa609827a4e2e30f1", + "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52", + "reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52", "shasum": "" }, "type": "library", @@ -147,9 +147,9 @@ "description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves", "support": { "issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues", - "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/4.5.0" + "source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0" }, - "time": "2024-10-23T16:15:24+00:00" + "time": "2024-11-03T14:13:50+00:00" }, { "name": "pocketmine/bedrock-data", From 96b12bddc1149d9a8610c6bffc555609790b70e8 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 3 Nov 2024 15:24:43 +0000 Subject: [PATCH 29/29] Prepare 5.21.0 release --- changelogs/5.21.md | 103 ++++++++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 +- 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.21.md diff --git a/changelogs/5.21.md b/changelogs/5.21.md new file mode 100644 index 000000000..b8131a3c8 --- /dev/null +++ b/changelogs/5.21.md @@ -0,0 +1,103 @@ +# 5.21.0 +Released 3rd November 2024. + +This is a minor feature release, including gameplay features and minor internals improvements. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## Gameplay +- Added the following new blocks: + - Campfire + - Chiseled Copper + - Chiseled Tuff + - Chiseled Tuff Bricks + - Copper Bulb + - Copper Door + - Copper Grate + - Copper Trapdoor + - Polished Tuff, Slabs, Stairs and Walls + - Soul Campfire + - Tuff Bricks, Slabs, Stairs and Walls + - Tuff Slab, Stairs and Walls +- Added the following new types of painting: + - backyard + - baroque + - bouquet + - cavebird + - changing + - cotan + - endboss + - fern + - finding + - humble + - lowmist + - meditative + - orb + - owlemons + - passage + - pond + - prairie_ride + - sunflowers + - tides + - unpacked +- Armor slots are now properly restricted (on the server side) to only contain the appropriate type of armor or headwear. +- Implemented Aqua Affinity enchantment. Since the server doesn't currently enforce any movement restrictions in water, this enchantment works based on client-side behaviour only. + +## API +### `pocketmine\block` +- The following new API methods have been added: + - `public ChiseledBookshelf->getLastInteractedSlot() : ?ChiseledBookshelfSlot` + - `public ChiseledBookshelf->setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : $this` +- The following new classes have been added: + - `utils\CopperMaterial` - interface implemented by all copper-like blocks with oxidation and waxed properties + - `CopperBulb` + - `CopperDoor` + - `CopperGrate` + - `CopperTrapdoor` + - `SoulCampfire` + - `Campfire` +- The following enums have new cases: + - `utils\BannerPatternType` has new cases `FLOW` and `GUSTER` + +### `pocketmine\crafting` +- The following enums have new cases: + - `FurnaceType` has new cases `CAMPFIRE` and `SOUL_CAMPFIRE` + +### `pocketmine\event` +- The following new classes have been added: + - `block\CampfireCookEvent` - called when a campfire finishes cooking an item + +### `pocketmine\inventory` +- Added support for slot validators, which permit restricting the types of items a player can put into an inventory slot. + - The following new classes have been added: + - `transaction\action\SlotValidator` - interface + - `transaction\action\CallbackSlotValidator` - class allowing a closure to be used for slot content validation + - `SlotValidatedInventory` - implemented by inventories which support the use of slot validators + +### `pocketmine\item` +- The following new API methods have been added: + - `public Item->getCooldownTag() : ?string` - returns the cooldown group this item belongs to, used for ensuring that, for example, different types of goat horns all respect a general horn cooldown +- The following new classes have been added: + - `ItemCooldownTags` - list of cooldown group tags used by PocketMine-MP + +### `pocketmine\world\sound` +- The following new classes have been added + - `CampfireSound` - sound made by campfires while lit + +## Tools +- `tools/blockstate-upgrade-schema-utils.php` (formerly `generate-blockstate-upgrade-schema.php`) has several improvements: + - Support for generating `flattenedProperties` rules as per [BedrockBlockUpgradeSchema 5.0.0](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/5.0.0) + - Improved criteria for flattened property selection to minimize the amount of rules required + - Several subcommands are now available: + - `generate` - generates a schema from provided data + - `update` - regenerates an existing schema in a newer format + - `update-all` - regenerates a folder of existing schemas in a newer format (useful for updating `BedrockBlockUpgradeSchema` en masse) + - `test` - verifies that a schema produces the results expected by provided data + +## Internals +- Fixed incorrect visibility of `createEntity` in spawn eggs. +- Added support for newer `BedrockBlockUpgradeSchema` in `BlockStateUpgrader`. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 5de1f5a05..4dc9ea7f9 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.20.2"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.21.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /**