diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 6199ad7a9..83d568878 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -8,7 +8,7 @@ on: jobs: build: name: Update Docker Hub images - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Set up Docker Buildx diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml index 131c0dde2..303f61ccf 100644 --- a/.github/workflows/draft-release-pr-check.yml +++ b/.github/workflows/draft-release-pr-check.yml @@ -24,7 +24,7 @@ permissions: jobs: check-intent: name: Check release trigger - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 outputs: valid: ${{ steps.validate.outputs.DEV_BUILD == 'false' }} @@ -43,7 +43,7 @@ jobs: #don't do these checks if this isn't a release - we don't want to generate unnecessary failed statuses if: needs.check-intent.outputs.valid == 'true' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index d2e9eb0d0..02cdeec6f 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -23,7 +23,7 @@ env: jobs: skip: name: Check whether to ignore this tag - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 outputs: skip: ${{ steps.exists.outputs.exists == 'true' }} @@ -54,7 +54,7 @@ jobs: needs: [check] if: needs.check.outputs.valid == 'true' && github.ref_type != 'tag' #can't do post-commit for a tag - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Generate access token @@ -79,7 +79,7 @@ jobs: needs: [check] if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/main-php-matrix.yml b/.github/workflows/main-php-matrix.yml index e26f7c318..015a33188 100644 --- a/.github/workflows/main-php-matrix.yml +++ b/.github/workflows/main-php-matrix.yml @@ -15,7 +15,7 @@ on: type: number image: description: 'Runner image to use' - default: 'ubuntu-20.04' + default: 'ubuntu-22.04' type: string jobs: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 571868747..051a3a790 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,7 +20,7 @@ jobs: codestyle: name: Code Style checks - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -40,7 +40,7 @@ jobs: shellcheck: name: ShellCheck - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false diff --git a/changelogs/5.27.md b/changelogs/5.27.md index f3923d466..07bc8c26e 100644 --- a/changelogs/5.27.md +++ b/changelogs/5.27.md @@ -16,3 +16,9 @@ If you're upgrading from 5.25.x directly to 5.27.0, please also read the followi ## General - Aded support for Minecraft: Bedrock Edition 1.21.70. - Removed support for earlier versions. + +# 5.27.1 +Released 6th April 2025. + +## Fixes +- Updated RakLib to get ping timestamp handling fixes. diff --git a/composer.json b/composer.json index 4d047540a..583116c0f 100644 --- a/composer.json +++ b/composer.json @@ -45,14 +45,14 @@ "pocketmine/log": "^0.4.0", "pocketmine/math": "~1.0.0", "pocketmine/nbt": "~1.1.0", - "pocketmine/raklib": "~1.1.0", + "pocketmine/raklib": "~1.1.2", "pocketmine/raklib-ipc": "~1.0.0", "pocketmine/snooze": "^0.5.0", "ramsey/uuid": "~4.7.0", "symfony/filesystem": "~6.4.0" }, "require-dev": { - "phpstan/phpstan": "2.1.8", + "phpstan/phpstan": "2.1.11", "phpstan/phpstan-phpunit": "^2.0.0", "phpstan/phpstan-strict-rules": "^2.0.0", "phpunit/phpunit": "^10.5.24" diff --git a/composer.lock b/composer.lock index 405a92414..23f312317 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": "28b4de9a23a293646dbad2707cdfd9e0", + "content-hash": "818c679a25da8e6b466bc454ad48dec3", "packages": [ { "name": "adhocore/json-comment", @@ -471,16 +471,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.24.1", + "version": "2.24.2", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5" + "reference": "2a00c44c52bce98e7a43aa31517df78cbb2ba23b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5", - "reference": "8f48cbe1fb5835a8bb573bed00ef04c65c26c7e5", + "url": "https://api.github.com/repos/pmmp/Language/zipball/2a00c44c52bce98e7a43aa31517df78cbb2ba23b", + "reference": "2a00c44c52bce98e7a43aa31517df78cbb2ba23b", "shasum": "" }, "type": "library", @@ -488,9 +488,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.24.1" + "source": "https://github.com/pmmp/Language/tree/2.24.2" }, - "time": "2025-03-16T19:04:15+00:00" + "time": "2025-04-03T01:23:27+00:00" }, { "name": "pocketmine/log", @@ -618,16 +618,16 @@ }, { "name": "pocketmine/raklib", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/pmmp/RakLib.git", - "reference": "be2783be516bf6e2872ff5c81fb9048596617b97" + "reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/RakLib/zipball/be2783be516bf6e2872ff5c81fb9048596617b97", - "reference": "be2783be516bf6e2872ff5c81fb9048596617b97", + "url": "https://api.github.com/repos/pmmp/RakLib/zipball/4145a31cd812fe8931c3c9c691fcd2ded2f47e7f", + "reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f", "shasum": "" }, "require": { @@ -639,8 +639,8 @@ "pocketmine/log": "^0.3.0 || ^0.4.0" }, "require-dev": { - "phpstan/phpstan": "1.10.1", - "phpstan/phpstan-strict-rules": "^1.0" + "phpstan/phpstan": "2.1.0", + "phpstan/phpstan-strict-rules": "^2.0" }, "type": "library", "autoload": { @@ -655,9 +655,9 @@ "description": "A RakNet server implementation written in PHP", "support": { "issues": "https://github.com/pmmp/RakLib/issues", - "source": "https://github.com/pmmp/RakLib/tree/1.1.1" + "source": "https://github.com/pmmp/RakLib/tree/1.1.2" }, - "time": "2024-03-04T14:02:14+00:00" + "time": "2025-04-06T03:38:21+00:00" }, { "name": "pocketmine/raklib-ipc", @@ -742,16 +742,16 @@ }, { "name": "ramsey/collection", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -812,9 +812,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "time": "2025-03-02T04:48:29+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", @@ -1373,16 +1373,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.8", + "version": "2.1.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f" + "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f", - "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", "shasum": "" }, "require": { @@ -1427,20 +1427,20 @@ "type": "github" } ], - "time": "2025-03-09T09:30:48+00:00" + "time": "2025-03-24T13:45:00+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.4", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "d09e152f403c843998d7a52b5d87040c937525dd" + "reference": "6b92469f8a7995e626da3aa487099617b8dfa260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d09e152f403c843998d7a52b5d87040c937525dd", - "reference": "d09e152f403c843998d7a52b5d87040c937525dd", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6b92469f8a7995e626da3aa487099617b8dfa260", + "reference": "6b92469f8a7995e626da3aa487099617b8dfa260", "shasum": "" }, "require": { @@ -1451,7 +1451,9 @@ "phpunit/phpunit": "<7.0" }, "require-dev": { + "nikic/php-parser": "^5", "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6" }, @@ -1476,9 +1478,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.4" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.6" }, - "time": "2025-01-22T13:07:38+00:00" + "time": "2025-03-26T12:47:06+00:00" }, { "name": "phpstan/phpstan-strict-rules", diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 0f776dc1d..44238dba3 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.27.1"; + public const BASE_VERSION = "5.27.2"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; diff --git a/src/block/Water.php b/src/block/Water.php index b711ab5a1..44759783a 100644 --- a/src/block/Water.php +++ b/src/block/Water.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\entity\Entity; +use pocketmine\event\entity\EntityExtinguishEvent; use pocketmine\world\sound\BucketEmptyWaterSound; use pocketmine\world\sound\BucketFillWaterSound; use pocketmine\world\sound\Sound; @@ -53,7 +54,7 @@ class Water extends Liquid{ public function onEntityInside(Entity $entity) : bool{ $entity->resetFallDistance(); if($entity->isOnFire()){ - $entity->extinguish(); + $entity->extinguish(EntityExtinguishEvent::CAUSE_WATER); } return true; } diff --git a/src/block/WaterCauldron.php b/src/block/WaterCauldron.php index e470aa6cb..8129f2960 100644 --- a/src/block/WaterCauldron.php +++ b/src/block/WaterCauldron.php @@ -27,6 +27,7 @@ use pocketmine\block\tile\Cauldron as TileCauldron; use pocketmine\block\utils\DyeColor; use pocketmine\color\Color; use pocketmine\entity\Entity; +use pocketmine\event\entity\EntityExtinguishEvent; use pocketmine\item\Armor; use pocketmine\item\Banner; use pocketmine\item\Dye; @@ -183,7 +184,7 @@ final class WaterCauldron extends FillableCauldron{ public function onEntityInside(Entity $entity) : bool{ if($entity->isOnFire()){ - $entity->extinguish(); + $entity->extinguish(EntityExtinguishEvent::CAUSE_WATER_CAULDRON); //TODO: particles $this->position->getWorld()->setBlock($this->position, $this->withFillLevel($this->getFillLevel() - self::ENTITY_EXTINGUISH_USE_AMOUNT)); diff --git a/src/block/tile/TileFactory.php b/src/block/tile/TileFactory.php index 515dd8c63..26e0af6a5 100644 --- a/src/block/tile/TileFactory.php +++ b/src/block/tile/TileFactory.php @@ -114,6 +114,13 @@ final class TileFactory{ $this->saveNames[$className] = reset($saveNames); } + /** + * @phpstan-param class-string $class + */ + public function isRegistered(string $class) : bool{ + return isset($this->saveNames[$class]); + } + /** * @internal * @throws SavedDataLoadingException diff --git a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php index 93695ab55..7be53facf 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -224,6 +224,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{ return $this->cache[$stateId] ??= $this->serializeBlock(RuntimeBlockStateRegistry::getInstance()->fromStateId($stateId)); } + public function isRegistered(Block $block) : bool{ + return isset($this->serializers[$block->getTypeId()]); + } + /** * @phpstan-template TBlockType of Block * @phpstan-param TBlockType $block diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 4d0d879db..f89186d19 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -31,6 +31,7 @@ use pocketmine\block\Water; use pocketmine\entity\animation\Animation; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDespawnEvent; +use pocketmine\event\entity\EntityExtinguishEvent; use pocketmine\event\entity\EntityMotionEvent; use pocketmine\event\entity\EntityRegainHealthEvent; use pocketmine\event\entity\EntitySpawnEvent; @@ -60,6 +61,7 @@ use pocketmine\player\Player; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; +use pocketmine\utils\Limits; use pocketmine\utils\Utils; use pocketmine\VersionInfo; use pocketmine\world\format\Chunk; @@ -76,6 +78,7 @@ use function floatval; use function floor; use function fmod; use function get_class; +use function min; use function sin; use function spl_object_id; use const M_PI_2; @@ -700,16 +703,26 @@ abstract class Entity{ * @throws \InvalidArgumentException */ public function setFireTicks(int $fireTicks) : void{ - if($fireTicks < 0 || $fireTicks > 0x7fff){ - throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks"); + if($fireTicks < 0){ + throw new \InvalidArgumentException("Fire ticks cannot be negative"); } + + //Since the max value is not externally obvious or intuitive, many plugins use this without being aware that + //reasonably large values are not accepted. We even have such usages within PM itself. It doesn't make sense + //to force all those calls to be aware of this limitation, as it's not a functional limit but a limitation of + //the Mojang save format. Truncating this to the max acceptable value is the next best thing we can do. + $fireTicks = min($fireTicks, Limits::INT16_MAX); + if(!$this->isFireProof()){ $this->fireTicks = $fireTicks; $this->networkPropertiesDirty = true; } } - public function extinguish() : void{ + public function extinguish(int $cause = EntityExtinguishEvent::CAUSE_CUSTOM) : void{ + $ev = new EntityExtinguishEvent($this, $cause); + $ev->call(); + $this->fireTicks = 0; $this->networkPropertiesDirty = true; } @@ -720,7 +733,7 @@ abstract class Entity{ protected function doOnFireTick(int $tickDiff = 1) : bool{ if($this->isFireProof() && $this->isOnFire()){ - $this->extinguish(); + $this->extinguish(EntityExtinguishEvent::CAUSE_FIRE_PROOF); return false; } @@ -731,7 +744,7 @@ abstract class Entity{ } if(!$this->isOnFire()){ - $this->extinguish(); + $this->extinguish(EntityExtinguishEvent::CAUSE_TICKING); }else{ return true; } diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index 03d9c03e6..970fd986f 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -219,6 +219,13 @@ final class EntityFactory{ $this->saveNames[$className] = reset($saveNames); } + /** + * @phpstan-param class-string $class + */ + public function isRegistered(string $class) : bool{ + return isset($this->saveNames[$class]); + } + /** * Creates an entity from data stored on a chunk. * diff --git a/src/event/entity/EntityExtinguishEvent.php b/src/event/entity/EntityExtinguishEvent.php new file mode 100644 index 000000000..b39d12231 --- /dev/null +++ b/src/event/entity/EntityExtinguishEvent.php @@ -0,0 +1,53 @@ + + */ +class EntityExtinguishEvent extends EntityEvent{ + public const CAUSE_CUSTOM = 0; + public const CAUSE_WATER = 1; + public const CAUSE_WATER_CAULDRON = 2; + public const CAUSE_RESPAWN = 3; + public const CAUSE_FIRE_PROOF = 4; + public const CAUSE_TICKING = 5; + public const CAUSE_RAIN = 6; + public const CAUSE_POWDER_SNOW = 7; + + public function __construct( + Entity $entity, + private int $cause + ){ + $this->entity = $entity; + } + + public function getCause() : int{ + return $this->cause; + } +} diff --git a/src/item/enchantment/AvailableEnchantmentRegistry.php b/src/item/enchantment/AvailableEnchantmentRegistry.php index eed7bff52..2d8dafa4b 100644 --- a/src/item/enchantment/AvailableEnchantmentRegistry.php +++ b/src/item/enchantment/AvailableEnchantmentRegistry.php @@ -28,6 +28,7 @@ use pocketmine\item\enchantment\ItemEnchantmentTags as Tags; use pocketmine\item\enchantment\VanillaEnchantments as Enchantments; use pocketmine\item\Item; use pocketmine\utils\SingletonTrait; +use pocketmine\utils\Utils; use function array_filter; use function array_values; use function count; @@ -129,6 +130,7 @@ final class AvailableEnchantmentRegistry{ if(!$this->isRegistered($enchantment)){ throw new \LogicException("Cannot set primary item tags for non-registered enchantment"); } + Utils::validateArrayValueType($tags, fn(string $v) => 1); $this->primaryItemTags[spl_object_id($enchantment)] = array_values($tags); } @@ -152,6 +154,7 @@ final class AvailableEnchantmentRegistry{ if(!$this->isRegistered($enchantment)){ throw new \LogicException("Cannot set secondary item tags for non-registered enchantment"); } + Utils::validateArrayValueType($tags, fn(string $v) => 1); $this->secondaryItemTags[spl_object_id($enchantment)] = array_values($tags); } diff --git a/src/player/Player.php b/src/player/Player.php index bf5d38c0d..f6726776c 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -46,6 +46,7 @@ use pocketmine\entity\projectile\Arrow; use pocketmine\entity\Skin; use pocketmine\event\entity\EntityDamageByEntityEvent; use pocketmine\event\entity\EntityDamageEvent; +use pocketmine\event\entity\EntityExtinguishEvent; use pocketmine\event\inventory\InventoryCloseEvent; use pocketmine\event\inventory\InventoryOpenEvent; use pocketmine\event\player\PlayerBedEnterEvent; @@ -2547,7 +2548,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->setSneaking(false); $this->setFlying(false); - $this->extinguish(); + $this->extinguish(EntityExtinguishEvent::CAUSE_RESPAWN); $this->setAirSupplyTicks($this->getMaxAirSupplyTicks()); $this->deadTicks = 0; $this->noDamageTicks = 60; diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 046296cf4..800bd0183 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -584,7 +584,7 @@ final class Utils{ /** * @phpstan-template TMemberType * @phpstan-param array $array - * @phpstan-param \Closure(TMemberType) : void $validator + * @phpstan-param \Closure(TMemberType) : mixed $validator */ public static function validateArrayValueType(array $array, \Closure $validator) : void{ foreach(Utils::promoteKeys($array) as $k => $v){ diff --git a/src/world/World.php b/src/world/World.php index 82740276d..c7a9775fb 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2028,6 +2028,15 @@ class World implements ChunkManager{ throw new WorldException("Cannot set a block in un-generated terrain"); } + //TODO: this computes state ID twice (we do it again in writeStateToWorld()). Not great for performance :( + $stateId = $block->getStateId(); + if(!$this->blockStateRegistry->hasStateId($stateId)){ + throw new \LogicException("Block state ID not known to RuntimeBlockStateRegistry (probably not registered)"); + } + if(!GlobalBlockStateHandlers::getSerializer()->isRegistered($block)){ + throw new \LogicException("Block not registered with GlobalBlockStateHandlers serializer"); + } + $this->timings->setBlock->startTiming(); $this->unlockChunk($chunkX, $chunkZ, null); @@ -2750,6 +2759,11 @@ class World implements ChunkManager{ throw new AssumptionFailedError("Found two different entities sharing entity ID " . $entity->getId()); } } + if(!EntityFactory::getInstance()->isRegistered($entity::class) && !$entity instanceof Player){ + //canSaveWithChunk is mutable, so that means it could be toggled after adding the entity and cause a crash + //later on. Better we just force all entities to have a save ID, even if it might not be needed. + throw new \LogicException("Entity " . $entity::class . " is not registered for a save ID in EntityFactory"); + } $pos = $entity->getPosition()->asVector3(); $this->entitiesByChunk[World::chunkHash($pos->getFloorX() >> Chunk::COORD_BIT_SIZE, $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE)][$entity->getId()] = $entity; $this->entityLastKnownPositions[$entity->getId()] = $pos; @@ -2851,6 +2865,9 @@ class World implements ChunkManager{ if(!$this->isInWorld($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ())){ throw new \InvalidArgumentException("Tile position is outside the world bounds"); } + if(!TileFactory::getInstance()->isRegistered($tile::class)){ + throw new \LogicException("Tile " . $tile::class . " is not registered for a save ID in TileFactory"); + } $chunkX = $pos->getFloorX() >> Chunk::COORD_BIT_SIZE; $chunkZ = $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE;