World: Intelligently perform automatic transfer or deletion of tiles in setChunk(), depending on the context

tiles may be deleted in the following circumstances:
1) the target block in the new chunk doesn't expect a tile
2) the target block in the new chunk expects a different type of tile (responsibility of the plugin developer to create the new tile)
3) there's already a tile in the target chunk which conflicts with the old one

In all other cases, the tile will be transferred.

This resolves a large number of unintentional bugs caused by world editors replacing chunks without setting the deleteTilesAndEntities parameter to false (even the core itself does it).

closes #4520
This commit is contained in:
Dylan K. Taylor 2021-10-28 23:46:35 +01:00
parent c66790b6a6
commit eb75df6f8e
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D

View File

@ -2124,23 +2124,36 @@ class World implements ChunkManager{
return isset($this->chunkLock[World::chunkHash($chunkX, $chunkZ)]); return isset($this->chunkLock[World::chunkHash($chunkX, $chunkZ)]);
} }
/** public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk) : void{
* @param bool $deleteTiles Whether to delete tiles on the old chunk, or transfer them to the new one
*/
public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk, bool $deleteTiles = true) : void{
$chunkHash = World::chunkHash($chunkX, $chunkZ); $chunkHash = World::chunkHash($chunkX, $chunkZ);
$oldChunk = $this->loadChunk($chunkX, $chunkZ); $oldChunk = $this->loadChunk($chunkX, $chunkZ);
if($oldChunk !== null and $oldChunk !== $chunk){ if($oldChunk !== null and $oldChunk !== $chunk){
if($deleteTiles){ $deletedTiles = 0;
foreach($oldChunk->getTiles() as $tile){ $transferredTiles = 0;
$tile->close(); foreach($oldChunk->getTiles() as $oldTile){
} $tilePosition = $oldTile->getPosition();
}else{ $localX = $tilePosition->getFloorX() & Chunk::COORD_MASK;
foreach($oldChunk->getTiles() as $tile){ $localY = $tilePosition->getFloorY();
$chunk->addTile($tile); $localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK;
$oldChunk->removeTile($tile);
$newBlock = BlockFactory::getInstance()->fromFullBlock($chunk->getFullBlock($localX, $localY, $localZ));
$expectedTileClass = $newBlock->getIdInfo()->getTileClass();
if(
$expectedTileClass === null || //new block doesn't expect a tile
!($oldTile instanceof $expectedTileClass) || //new block expects a different tile
(($newTile = $chunk->getTile($localX, $localY, $localZ)) !== null && $newTile !== $oldTile) //new chunk already has a different tile
){
$oldTile->close();
$deletedTiles++;
}else{
$transferredTiles++;
$chunk->addTile($oldTile);
$oldChunk->removeTile($oldTile);
} }
} }
if($deletedTiles > 0 || $transferredTiles > 0){
$this->logger->debug("Replacement of chunk $chunkX $chunkZ caused deletion of $deletedTiles obsolete/conflicted tiles, and transfer of $transferredTiles");
}
} }
$this->chunks[$chunkHash] = $chunk; $this->chunks[$chunkHash] = $chunk;
@ -2830,11 +2843,11 @@ class World implements ChunkManager{
} }
$oldChunk = $this->loadChunk($x, $z); $oldChunk = $this->loadChunk($x, $z);
$this->setChunk($x, $z, $chunk, false); $this->setChunk($x, $z, $chunk);
foreach($adjacentChunks as $adjacentChunkHash => $adjacentChunk){ foreach($adjacentChunks as $adjacentChunkHash => $adjacentChunk){
World::getXZ($adjacentChunkHash, $xAdjacentChunk, $zAdjacentChunk); World::getXZ($adjacentChunkHash, $xAdjacentChunk, $zAdjacentChunk);
$this->setChunk($xAdjacentChunk, $zAdjacentChunk, $adjacentChunk, false); $this->setChunk($xAdjacentChunk, $zAdjacentChunk, $adjacentChunk);
} }
if(($oldChunk === null or !$oldChunk->isPopulated()) and $chunk->isPopulated()){ if(($oldChunk === null or !$oldChunk->isPopulated()) and $chunk->isPopulated()){