Player: fixed broken behaviour of entity spawning on chunk send, closes #3355

This commit is contained in:
Dylan K. Taylor 2020-05-05 18:55:13 +01:00
parent c2b438ccb6
commit 9cf410d484
3 changed files with 74 additions and 16 deletions

View File

@ -1528,7 +1528,10 @@ abstract class Entity{
public function spawnTo(Player $player) : void{
$id = spl_object_id($player);
if(!isset($this->hasSpawned[$id]) and $player->isUsingChunk($this->location->getFloorX() >> 4, $this->location->getFloorZ() >> 4)){
//TODO: this will cause some visible lag during chunk resends; if the player uses a spawn egg in a chunk, the
//created entity won't be visible until after the resend arrives. However, this is better than possibly crashing
//the player by sending them entities too early.
if(!isset($this->hasSpawned[$id]) and $player->hasReceivedChunk($this->location->getFloorX() >> 4, $this->location->getFloorZ() >> 4)){
$this->hasSpawned[$id] = $player;
$this->sendSpawnPacket($player);

View File

@ -189,7 +189,10 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
/** @var GameMode */
protected $gamemode;
/** @var bool[] chunkHash => bool (true = sent, false = needs sending) */
/**
* @var UsedChunkStatus[] chunkHash => status
* @phpstan-var array<int, UsedChunkStatus>
*/
protected $usedChunks = [];
/** @var bool[] chunkHash => dummy */
protected $loadQueue = [];
@ -286,7 +289,7 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
//load the spawn chunk so we can see the terrain
$world->registerChunkLoader($this, $spawn->getFloorX() >> 4, $spawn->getFloorZ() >> 4, true);
$world->registerChunkListener($this, $spawn->getFloorX() >> 4, $spawn->getFloorZ() >> 4);
$this->usedChunks[World::chunkHash($spawn->getFloorX() >> 4, $spawn->getFloorZ() >> 4)] = false;
$this->usedChunks[World::chunkHash($spawn->getFloorX() >> 4, $spawn->getFloorZ() >> 4)] = UsedChunkStatus::NEEDED();
if($namedtag === null){
$namedtag = EntityFactory::createBaseNBT($spawn);
@ -717,7 +720,7 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
$oldWorld = $this->location->getWorld();
if(parent::switchWorld($targetWorld)){
if($oldWorld !== null){
foreach($this->usedChunks as $index => $d){
foreach($this->usedChunks as $index => $status){
World::getXZ($index, $X, $Z);
$this->unloadChunk($X, $Z, $oldWorld);
}
@ -778,7 +781,7 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
++$count;
$this->usedChunks[$index] = false;
$this->usedChunks[$index] = UsedChunkStatus::NEEDED();
$this->getWorld()->registerChunkLoader($this, $X, $Z, true);
$this->getWorld()->registerChunkListener($this, $X, $Z);
@ -787,20 +790,20 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
}
unset($this->loadQueue[$index]);
$this->usedChunks[$index] = true;
$this->usedChunks[$index] = UsedChunkStatus::REQUESTED();
$this->networkSession->startUsingChunk($X, $Z, function(int $chunkX, int $chunkZ) : void{
$this->networkSession->startUsingChunk($X, $Z, function(int $chunkX, int $chunkZ) use ($index) : void{
$this->usedChunks[$index] = UsedChunkStatus::SENT();
if($this->spawned){
$this->spawnEntitiesOnChunk($chunkX, $chunkZ);
}elseif($this->spawnChunkLoadCount++ === $this->spawnThreshold){
$this->spawned = true;
foreach($this->usedChunks as $chunkHash => $hasSent){
if(!$hasSent){
continue;
foreach($this->usedChunks as $chunkHash => $status){
if($status->equals(UsedChunkStatus::SENT())){
World::getXZ($chunkHash, $_x, $_z);
$this->spawnEntitiesOnChunk($_x, $_z);
}
World::getXZ($chunkHash, $_x, $_z);
$this->spawnEntitiesOnChunk($_x, $_z);
}
$this->networkSession->onTerrainReady();
@ -894,13 +897,13 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
$unloadChunks = $this->usedChunks;
foreach($this->selectChunks() as $hash){
if(!isset($this->usedChunks[$hash]) or $this->usedChunks[$hash] === false){
if(!isset($this->usedChunks[$hash]) or $this->usedChunks[$hash]->equals(UsedChunkStatus::NEEDED())){
$newOrder[$hash] = true;
}
unset($unloadChunks[$hash]);
}
foreach($unloadChunks as $index => $bool){
foreach($unloadChunks as $index => $status){
World::getXZ($index, $X, $Z);
$this->unloadChunk($X, $Z);
}
@ -917,6 +920,11 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
return isset($this->usedChunks[World::chunkHash($chunkX, $chunkZ)]);
}
public function hasReceivedChunk(int $chunkX, int $chunkZ) : bool{
$status = $this->usedChunks[World::chunkHash($chunkX, $chunkZ)] ?? null;
return $status !== null and $status->equals(UsedChunkStatus::SENT());
}
public function doChunkRequests() : void{
if($this->nextChunkOrderRun !== PHP_INT_MAX and $this->nextChunkOrderRun-- <= 0){
$this->nextChunkOrderRun = PHP_INT_MAX;
@ -1982,7 +1990,7 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
$this->hiddenPlayers = [];
if($this->location->isValid()){
foreach($this->usedChunks as $index => $d){
foreach($this->usedChunks as $index => $status){
World::getXZ($index, $chunkX, $chunkZ);
$this->unloadChunk($chunkX, $chunkZ);
}
@ -2342,7 +2350,7 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
public function onChunkChanged(Chunk $chunk) : void{
if(isset($this->usedChunks[$hash = World::chunkHash($chunk->getX(), $chunk->getZ())])){
$this->usedChunks[$hash] = false;
$this->usedChunks[$hash] = UsedChunkStatus::NEEDED();
$this->nextChunkOrderRun = 0;
}
}

View File

@ -0,0 +1,47 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\player;
use pocketmine\utils\EnumTrait;
/**
* This doc-block is generated automatically, do not modify it manually.
* This must be regenerated whenever registry members are added, removed or changed.
* @see RegistryTrait::_generateMethodAnnotations()
*
* @method static self NEEDED()
* @method static self REQUESTED()
* @method static self SENT()
*/
final class UsedChunkStatus{
use EnumTrait;
protected static function setup() : void{
self::registerAll(
new self("NEEDED"),
new self("REQUESTED"),
new self("SENT")
);
}
}