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 367d38449..45784d409 100644 --- a/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php +++ b/src/data/bedrock/block/convert/BlockObjectToStateSerializer.php @@ -225,6 +225,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/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/world/World.php b/src/world/World.php index 3a7d0c538..afd01c628 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2047,6 +2047,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); @@ -2769,6 +2778,11 @@ class World implements ChunkManager{ throw new AssumptionFailedError("Found two different entities sharing entity ID " . $entity->getId()); } } + if(!EntityFactory::getInstance()->isRegistered($entity::class)){ + //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; @@ -2870,6 +2884,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;