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)]);
}
/**
* @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{
public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk) : void{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
$oldChunk = $this->loadChunk($chunkX, $chunkZ);
if($oldChunk !== null and $oldChunk !== $chunk){
if($deleteTiles){
foreach($oldChunk->getTiles() as $tile){
$tile->close();
}
}else{
foreach($oldChunk->getTiles() as $tile){
$chunk->addTile($tile);
$oldChunk->removeTile($tile);
$deletedTiles = 0;
$transferredTiles = 0;
foreach($oldChunk->getTiles() as $oldTile){
$tilePosition = $oldTile->getPosition();
$localX = $tilePosition->getFloorX() & Chunk::COORD_MASK;
$localY = $tilePosition->getFloorY();
$localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK;
$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;
@ -2830,11 +2843,11 @@ class World implements ChunkManager{
}
$oldChunk = $this->loadChunk($x, $z);
$this->setChunk($x, $z, $chunk, false);
$this->setChunk($x, $z, $chunk);
foreach($adjacentChunks as $adjacentChunkHash => $adjacentChunk){
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()){