Player: Fixed a multitude of bugs with respawning

the following things are changed:
- Player->getSpawn() no longer returns a safe spawn by default. Instead, if the player doesn't have a spawn set, it returns the world's stored spawn directly. This allows consistent behaviour of locating safe respawn positions without double calculation of safe spawn position, and also fixes crash issues during the login sequence if the player's spawn position referred to ungenerated terrain.
- Player->respawn() is now asynchronous, using the promise returned by orderChunkPopulation() to complete respawn after the terrain is generated. This allows consistently selecting a safe respawn position and fixes crashes when respawning if the spawn location was in ungenerated terrain.

There remains a problem that ragequit respawns are still only handled right after PlayerJoinEvent, which leads to the original spawn terrain being sent to the player, which is obviously very wasteful. However, that's a problem for a later commit.
This commit is contained in:
Dylan K. Taylor 2021-05-11 19:15:31 +01:00
parent 094c949e86
commit ab0500ae4f
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D

View File

@ -810,6 +810,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
if($this->getHealth() <= 0){
$this->logger->debug("Quit while dead, forcing respawn");
$this->respawn();
}
}
@ -907,7 +908,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}else{
$world = $this->server->getWorldManager()->getDefaultWorld();
return $world->getSafeSpawn();
return $world->getSpawnLocation();
}
}
@ -2059,16 +2060,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$nbt->setInt("SpawnZ", $spawn->getFloorZ());
}
if(!$this->isAlive()){
$spawn = $this->getSpawn();
//hack for respawn after quit
$nbt->setTag("Pos", new ListTag([
new DoubleTag($spawn->getFloorX()),
new DoubleTag($spawn->getFloorY()),
new DoubleTag($spawn->getFloorZ())
]));
}
$nbt->setInt("playerGameType", $this->gamemode->getMagicNumber());
$nbt->setLong("firstPlayed", $this->firstPlayed);
$nbt->setLong("lastPlayed", (int) floor(microtime(true) * 1000));
@ -2133,31 +2124,47 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return;
}
$ev = new PlayerRespawnEvent($this, $this->getSpawn());
$ev->call();
$this->logger->debug("Waiting for spawn terrain generation for respawn");
$spawn = $this->getSpawn();
$spawn->getWorld()->orderChunkPopulation($spawn->getFloorX() >> 4, $spawn->getFloorZ() >> 4, null)->onCompletion(
function() use ($spawn) : void{
if(!$this->isConnected()){
return;
}
$this->logger->debug("Spawn terrain generation done, completing respawn");
$spawn = $spawn->getWorld()->getSafeSpawn($spawn);
$ev = new PlayerRespawnEvent($this, $spawn);
$ev->call();
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getWorld());
$this->teleport($realSpawn);
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getWorld());
$this->teleport($realSpawn);
$this->setSprinting(false);
$this->setSneaking(false);
$this->setSprinting(false);
$this->setSneaking(false);
$this->extinguish();
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
$this->deadTicks = 0;
$this->noDamageTicks = 60;
$this->extinguish();
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
$this->deadTicks = 0;
$this->noDamageTicks = 60;
$this->effectManager->clear();
$this->setHealth($this->getMaxHealth());
$this->effectManager->clear();
$this->setHealth($this->getMaxHealth());
foreach($this->attributeMap->getAll() as $attr){
$attr->resetToDefault();
}
foreach($this->attributeMap->getAll() as $attr){
$attr->resetToDefault();
}
$this->spawnToAll();
$this->scheduleUpdate();
$this->spawnToAll();
$this->scheduleUpdate();
$this->getNetworkSession()->onServerRespawn();
$this->getNetworkSession()->onServerRespawn();
},
function() : void{
if($this->isConnected()){
$this->disconnect("Unable to find a respawn position");
}
}
);
}
protected function applyPostDamageEffects(EntityDamageEvent $source) : void{