summaryrefslogtreecommitdiffstats
path: root/src/Entities/Player.cpp
diff options
context:
space:
mode:
authorTiger Wang <ziwei.tiger@outlook.com>2021-02-06 19:37:03 +0100
committerTiger Wang <ziwei.tiger@outlook.com>2021-02-07 23:36:15 +0100
commitbe2bf999c280d6892b38272d3f783b3462f0b745 (patch)
tree6567e7f011e50dd8e6f65f7dd933a1d7dc34ea93 /src/Entities/Player.cpp
parentAdds playerlist header and footer broadcasting (1.8-1.13) (diff)
downloadcuberite-be2bf999c280d6892b38272d3f783b3462f0b745.tar
cuberite-be2bf999c280d6892b38272d3f783b3462f0b745.tar.gz
cuberite-be2bf999c280d6892b38272d3f783b3462f0b745.tar.bz2
cuberite-be2bf999c280d6892b38272d3f783b3462f0b745.tar.lz
cuberite-be2bf999c280d6892b38272d3f783b3462f0b745.tar.xz
cuberite-be2bf999c280d6892b38272d3f783b3462f0b745.tar.zst
cuberite-be2bf999c280d6892b38272d3f783b3462f0b745.zip
Diffstat (limited to 'src/Entities/Player.cpp')
-rw-r--r--src/Entities/Player.cpp843
1 files changed, 424 insertions, 419 deletions
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index d20795643..e3994e88c 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -179,47 +179,6 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client) :
-void cPlayer::AddKnownItem(const cItem & a_Item)
-{
- if (a_Item.m_ItemType < 0)
- {
- return;
- }
-
- auto Response = m_KnownItems.insert(a_Item.CopyOne());
- if (!Response.second)
- {
- // The item was already known, bail out:
- return;
- }
-
- // Process the recipes that got unlocked by this newly-known item:
- auto Recipes = cRoot::Get()->GetCraftingRecipes()->FindNewRecipesForItem(a_Item, m_KnownItems);
- for (const auto & RecipeId : Recipes)
- {
- AddKnownRecipe(RecipeId);
- }
-}
-
-
-
-
-
-void cPlayer::AddKnownRecipe(UInt32 a_RecipeId)
-{
- auto Response = m_KnownRecipes.insert(a_RecipeId);
- if (!Response.second)
- {
- // The recipe was already known, bail out:
- return;
- }
- m_ClientHandle->SendUnlockRecipe(a_RecipeId);
-}
-
-
-
-
-
cPlayer::~cPlayer(void)
{
LOGD("Deleting cPlayer \"%s\" at %p, ID %d", GetName().c_str(), static_cast<void *>(this), GetUniqueID());
@@ -238,302 +197,6 @@ cPlayer::~cPlayer(void)
-void cPlayer::OnAddToWorld(cWorld & a_World)
-{
- Super::OnAddToWorld(a_World);
-
- // Update world name tracking:
- m_CurrentWorldName = m_World->GetName();
-
- // Fix to stop the player falling through the world, until we get serversided collision detection:
- FreezeInternal(GetPosition(), false);
-
- // Set capabilities based on new world:
- SetCapabilities();
-
- // Send contents of the inventory window:
- m_ClientHandle->SendWholeInventory(*m_CurrentWindow);
-
- // Send health (the respawn packet, which understandably resets health, is also used for world travel...):
- m_ClientHandle->SendHealth();
-
- // Send experience, similar story with the respawn packet:
- m_ClientHandle->SendExperience();
-
- // Send hotbar active slot (also reset by respawn):
- m_ClientHandle->SendHeldItemChange(m_Inventory.GetEquippedSlotNum());
-
- // Update player team:
- UpdateTeam();
-
- // Send scoreboard data:
- m_World->GetScoreBoard().SendTo(*m_ClientHandle);
-
- // Update the view distance:
- m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance());
-
- // Send current weather of target world:
- m_ClientHandle->SendWeather(a_World.GetWeather());
-
- // Send time:
- m_ClientHandle->SendTimeUpdate(a_World.GetWorldAge(), a_World.GetTimeOfDay(), a_World.IsDaylightCycleEnabled());
-
- // Finally, deliver the notification hook:
- cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*this);
-}
-
-
-
-
-
-void cPlayer::OnRemoveFromWorld(cWorld & a_World)
-{
- Super::OnRemoveFromWorld(a_World);
-
- // Remove any references to this player pointer by windows in the old world:
- CloseWindow(false);
-
- // Remove the client handle from the world:
- m_World->RemoveClientFromChunks(m_ClientHandle.get());
-
- if (m_ClientHandle->IsDestroyed()) // Note: checking IsWorldChangeScheduled not appropriate here since we can disconnecting while having a scheduled warp
- {
- // Disconnecting, do the necessary cleanup.
- // This isn't in the destructor to avoid crashing accessing destroyed objects during shutdown.
-
- if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this))
- {
- cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str()));
- LOGINFO("Player %s has left the game", GetName().c_str());
- }
-
- // Remove ourself from everyone's lists:
- cRoot::Get()->BroadcastPlayerListsRemovePlayer(*this);
-
- // Atomically decrement player count (in world thread):
- cRoot::Get()->GetServer()->PlayerDestroyed();
-
- // We're just disconnecting. The remaining code deals with going through portals, so bail:
- return;
- }
-
- const auto DestinationDimension = m_WorldChangeInfo.m_NewWorld->GetDimension();
-
- // Award relevant achievements:
- if (DestinationDimension == dimEnd)
- {
- AwardAchievement(Statistic::AchTheEnd);
- }
- else if (DestinationDimension == dimNether)
- {
- AwardAchievement(Statistic::AchPortal);
- }
-
- // Clear sent chunk lists from the clienthandle:
- m_ClientHandle->RemoveFromWorld();
-
- // The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
- m_ClientHandle->InvalidateCachedSentChunk();
-
- // Clientside warp start:
- m_ClientHandle->SendRespawn(DestinationDimension, false);
-}
-
-
-
-
-
-void cPlayer::SpawnOn(cClientHandle & a_Client)
-{
- if (!m_bVisible || (m_ClientHandle.get() == (&a_Client)))
- {
- return;
- }
-
- LOGD("Spawing %s on %s", GetName().c_str(), a_Client.GetUsername().c_str());
-
- a_Client.SendPlayerSpawn(*this);
- a_Client.SendEntityHeadLook(*this);
- a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem());
- a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots());
- a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings());
- a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate());
- a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet());
-}
-
-
-
-
-
-void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
-{
- m_ClientHandle->Tick(a_Dt.count());
-
- if (m_ClientHandle->IsDestroyed())
- {
- Destroy();
- return;
- }
-
- if (!m_ClientHandle->IsPlaying())
- {
- // We're not yet in the game, ignore everything:
- return;
- }
-
- m_Stats.AddValue(Statistic::PlayOneMinute);
- m_Stats.AddValue(Statistic::TimeSinceDeath);
-
- if (IsCrouched())
- {
- m_Stats.AddValue(Statistic::SneakTime);
- }
-
- // Handle the player detach, when the player is in spectator mode
- if (
- (IsGameModeSpectator()) &&
- (m_AttachedTo != nullptr) &&
- (
- (m_AttachedTo->IsDestroyed()) || // Watching entity destruction
- (m_AttachedTo->GetHealth() <= 0) || // Watching entity dead
- (IsCrouched()) // Or the player wants to be detached
- )
- )
- {
- Detach();
- }
-
- if (!a_Chunk.IsValid())
- {
- // Players are ticked even if the parent chunk is invalid.
- // We've processed as much as we can, bail:
- return;
- }
-
- ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid()));
- ASSERT(a_Chunk.IsValid());
-
- // Handle a frozen player:
- TickFreezeCode();
-
- if (
- m_IsFrozen || // Don't do Tick updates if frozen
- IsWorldChangeScheduled() // If we're about to change worlds (e.g. respawn), abort processing all world interactions (GH #3939)
- )
- {
- return;
- }
-
- Super::Tick(a_Dt, a_Chunk);
-
- // Handle charging the bow:
- if (m_IsChargingBow)
- {
- m_BowCharge += 1;
- }
-
- BroadcastMovementUpdate(m_ClientHandle.get());
-
- if (m_Health > 0) // make sure player is alive
- {
- m_World->CollectPickupsByPlayer(*this);
-
- if ((m_EatingFinishTick >= 0) && (m_EatingFinishTick <= m_World->GetWorldAge()))
- {
- FinishEating();
- }
-
- HandleFood();
- }
-
- if (m_IsFishing)
- {
- HandleFloater();
- }
-
- // Update items (e.g. Maps)
- m_Inventory.UpdateItems();
-
- // Send Player List (Once per m_LastPlayerListTime/1000 ms)
- if (m_LastPlayerListTime + PLAYER_LIST_TIME_MS <= std::chrono::steady_clock::now())
- {
- m_World->BroadcastPlayerListUpdatePing(*this);
- m_LastPlayerListTime = std::chrono::steady_clock::now();
- }
-
- if (m_TicksUntilNextSave == 0)
- {
- SaveToDisk();
- m_TicksUntilNextSave = PLAYER_INVENTORY_SAVE_INTERVAL;
- }
- else
- {
- m_TicksUntilNextSave--;
- }
-}
-
-
-
-
-
-void cPlayer::TickFreezeCode()
-{
- if (m_IsFrozen)
- {
- if ((!m_IsManuallyFrozen) && (GetClientHandle()->IsPlayerChunkSent()))
- {
- // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent
- Unfreeze();
-
- // Pull the player out of any solids that might have loaded on them.
- PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk()));
- if (RelSuccess)
- {
- int NewY = Rel.y;
- if (NewY < 0)
- {
- NewY = 0;
- }
- while (NewY < cChunkDef::Height - 2)
- {
- // If we find a position with enough space for the player
- if (
- !cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY, Rel.z)) &&
- !cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY + 1, Rel.z))
- )
- {
- // If the found position is not the same as the original
- if (NewY != Rel.y)
- {
- SetPosition(GetPosition().x, NewY, GetPosition().z);
- GetClientHandle()->SendPlayerPosition();
- }
- break;
- }
- ++NewY;
- }
- }
- }
- else if (GetWorld()->GetWorldAge() % 100 == 0)
- {
- // Despite the client side freeze, the player may be able to move a little by
- // Jumping or canceling flight. Re-freeze every now and then
- FreezeInternal(GetPosition(), m_IsManuallyFrozen);
- }
- }
- else
- {
- if (!GetClientHandle()->IsPlayerChunkSent() || (!GetParentChunk()->IsValid()))
- {
- FreezeInternal(GetPosition(), false);
- }
- }
-}
-
-
-
-
-
int cPlayer::CalcLevelFromXp(int a_XpTotal)
{
// level 0 to 15
@@ -1092,69 +755,6 @@ void cPlayer::SetFlying(bool a_IsFlying)
-void cPlayer::ApplyArmorDamage(int a_DamageBlocked)
-{
- short ArmorDamage = static_cast<short>(std::max(a_DamageBlocked / 4, 1));
-
- for (int i = 0; i < 4; i++)
- {
- UseItem(cInventory::invArmorOffset + i, ArmorDamage);
- }
-}
-
-
-
-
-
-bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
-{
- if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin))
- {
- if (IsGameModeCreative() || IsGameModeSpectator())
- {
- // No damage / health in creative or spectator mode if not void or plugin damage
- return false;
- }
- }
-
- if ((a_TDI.Attacker != nullptr) && (a_TDI.Attacker->IsPlayer()))
- {
- cPlayer * Attacker = static_cast<cPlayer *>(a_TDI.Attacker);
-
- if ((m_Team != nullptr) && (m_Team == Attacker->m_Team))
- {
- if (!m_Team->AllowsFriendlyFire())
- {
- // Friendly fire is disabled
- return false;
- }
- }
- }
-
- if (Super::DoTakeDamage(a_TDI))
- {
- // Any kind of damage adds food exhaustion
- AddFoodExhaustion(0.3f);
- m_ClientHandle->SendHealth();
-
- // Tell the wolves
- if (a_TDI.Attacker != nullptr)
- {
- if (a_TDI.Attacker->IsPawn())
- {
- NotifyNearbyWolves(static_cast<cPawn*>(a_TDI.Attacker), true);
- }
- }
- m_Stats.AddValue(Statistic::DamageTaken, FloorC<cStatManager::StatValue>(a_TDI.FinalDamage * 10 + 0.5));
- return true;
- }
- return false;
-}
-
-
-
-
-
void cPlayer::NotifyNearbyWolves(cPawn * a_Opponent, bool a_IsPlayerInvolved)
{
ASSERT(a_Opponent != nullptr);
@@ -1888,22 +1488,6 @@ void cPlayer::ForceSetSpeed(const Vector3d & a_Speed)
-void cPlayer::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ)
-{
- if (m_IsFrozen)
- {
- // Do not set speed to a frozen client
- return;
- }
- Super::DoSetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ);
- // Send the speed to the client so he actualy moves
- m_ClientHandle->SendEntityVelocity(*this);
-}
-
-
-
-
-
void cPlayer::SetVisible(bool a_bVisible)
{
// Need to Check if the player or other players are in gamemode spectator, but will break compatibility
@@ -3211,15 +2795,436 @@ bool cPlayer::CanInstantlyMine(BLOCKTYPE a_Block)
-float cPlayer::GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower)
+void cPlayer::AddKnownItem(const cItem & a_Item)
+{
+ if (a_Item.m_ItemType < 0)
+ {
+ return;
+ }
+
+ auto Response = m_KnownItems.insert(a_Item.CopyOne());
+ if (!Response.second)
+ {
+ // The item was already known, bail out:
+ return;
+ }
+
+ // Process the recipes that got unlocked by this newly-known item:
+ auto Recipes = cRoot::Get()->GetCraftingRecipes()->FindNewRecipesForItem(a_Item, m_KnownItems);
+ for (const auto & RecipeId : Recipes)
+ {
+ AddKnownRecipe(RecipeId);
+ }
+}
+
+
+
+
+
+void cPlayer::AddKnownRecipe(UInt32 a_RecipeId)
+{
+ auto Response = m_KnownRecipes.insert(a_RecipeId);
+ if (!Response.second)
+ {
+ // The recipe was already known, bail out:
+ return;
+ }
+ m_ClientHandle->SendUnlockRecipe(a_RecipeId);
+}
+
+
+
+
+
+void cPlayer::TickFreezeCode()
+{
+ if (m_IsFrozen)
+ {
+ if ((!m_IsManuallyFrozen) && (GetClientHandle()->IsPlayerChunkSent()))
+ {
+ // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent
+ Unfreeze();
+
+ // Pull the player out of any solids that might have loaded on them.
+ PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk()));
+ if (RelSuccess)
+ {
+ int NewY = Rel.y;
+ if (NewY < 0)
+ {
+ NewY = 0;
+ }
+ while (NewY < cChunkDef::Height - 2)
+ {
+ // If we find a position with enough space for the player
+ if (
+ !cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY, Rel.z)) &&
+ !cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY + 1, Rel.z))
+ )
+ {
+ // If the found position is not the same as the original
+ if (NewY != Rel.y)
+ {
+ SetPosition(GetPosition().x, NewY, GetPosition().z);
+ GetClientHandle()->SendPlayerPosition();
+ }
+ break;
+ }
+ ++NewY;
+ }
+ }
+ }
+ else if (GetWorld()->GetWorldAge() % 100 == 0)
+ {
+ // Despite the client side freeze, the player may be able to move a little by
+ // Jumping or canceling flight. Re-freeze every now and then
+ FreezeInternal(GetPosition(), m_IsManuallyFrozen);
+ }
+ }
+ else
+ {
+ if (!GetClientHandle()->IsPlayerChunkSent() || (!GetParentChunk()->IsValid()))
+ {
+ FreezeInternal(GetPosition(), false);
+ }
+ }
+}
+
+
+
+
+
+void cPlayer::ApplyArmorDamage(int a_DamageBlocked)
+{
+ short ArmorDamage = static_cast<short>(std::max(a_DamageBlocked / 4, 1));
+
+ for (int i = 0; i < 4; i++)
+ {
+ UseItem(cInventory::invArmorOffset + i, ArmorDamage);
+ }
+}
+
+
+
+
+
+void cPlayer::BroadcastMovementUpdate(const cClientHandle * a_Exclude)
+{
+ if (!m_IsFrozen && m_Speed.SqrLength() > 0.001)
+ {
+ // If the player is not frozen, has a non-zero speed,
+ // send the speed to the client so he is forced to move so:
+ m_ClientHandle->SendEntityVelocity(*this);
+ }
+
+ // Since we do no physics processing for players, speed will otherwise never decrease:
+ m_Speed.Set(0, 0, 0);
+
+ Super::BroadcastMovementUpdate(a_Exclude);
+}
+
+
+
+
+
+bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
+{
+ // Filters out damage for creative mode / friendly fire.
+
+ if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin))
+ {
+ if (IsGameModeCreative() || IsGameModeSpectator())
+ {
+ // No damage / health in creative or spectator mode if not void or plugin damage
+ return false;
+ }
+ }
+
+ if ((a_TDI.Attacker != nullptr) && (a_TDI.Attacker->IsPlayer()))
+ {
+ cPlayer * Attacker = static_cast<cPlayer *>(a_TDI.Attacker);
+
+ if ((m_Team != nullptr) && (m_Team == Attacker->m_Team))
+ {
+ if (!m_Team->AllowsFriendlyFire())
+ {
+ // Friendly fire is disabled
+ return false;
+ }
+ }
+ }
+
+ if (Super::DoTakeDamage(a_TDI))
+ {
+ // Any kind of damage adds food exhaustion
+ AddFoodExhaustion(0.3f);
+ m_ClientHandle->SendHealth();
+
+ // Tell the wolves
+ if (a_TDI.Attacker != nullptr)
+ {
+ if (a_TDI.Attacker->IsPawn())
+ {
+ NotifyNearbyWolves(static_cast<cPawn*>(a_TDI.Attacker), true);
+ }
+ }
+ m_Stats.AddValue(Statistic::DamageTaken, FloorC<cStatManager::StatValue>(a_TDI.FinalDamage * 10 + 0.5));
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+float cPlayer::GetEnchantmentBlastKnockbackReduction()
{
if (
IsGameModeSpectator() ||
(IsGameModeCreative() && !IsOnGround())
)
{
- return 0; // No impact from explosion
+ return 1; // No impact from explosion
}
- return Super::GetExplosionExposureRate(a_ExplosionPosition, a_ExlosionPower) / 30.0f;
+ return Super::GetEnchantmentBlastKnockbackReduction();
+}
+
+
+
+
+
+void cPlayer::OnAddToWorld(cWorld & a_World)
+{
+ Super::OnAddToWorld(a_World);
+
+ // Update world name tracking:
+ m_CurrentWorldName = m_World->GetName();
+
+ // Fix to stop the player falling through the world, until we get serversided collision detection:
+ FreezeInternal(GetPosition(), false);
+
+ // Set capabilities based on new world:
+ SetCapabilities();
+
+ // Send contents of the inventory window:
+ m_ClientHandle->SendWholeInventory(*m_CurrentWindow);
+
+ // Send health (the respawn packet, which understandably resets health, is also used for world travel...):
+ m_ClientHandle->SendHealth();
+
+ // Send experience, similar story with the respawn packet:
+ m_ClientHandle->SendExperience();
+
+ // Send hotbar active slot (also reset by respawn):
+ m_ClientHandle->SendHeldItemChange(m_Inventory.GetEquippedSlotNum());
+
+ // Update player team:
+ UpdateTeam();
+
+ // Send scoreboard data:
+ m_World->GetScoreBoard().SendTo(*m_ClientHandle);
+
+ // Update the view distance:
+ m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance());
+
+ // Send current weather of target world:
+ m_ClientHandle->SendWeather(a_World.GetWeather());
+
+ // Send time:
+ m_ClientHandle->SendTimeUpdate(a_World.GetWorldAge(), a_World.GetTimeOfDay(), a_World.IsDaylightCycleEnabled());
+
+ // Finally, deliver the notification hook:
+ cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*this);
+}
+
+
+
+
+
+void cPlayer::OnRemoveFromWorld(cWorld & a_World)
+{
+ Super::OnRemoveFromWorld(a_World);
+
+ // Remove any references to this player pointer by windows in the old world:
+ CloseWindow(false);
+
+ // Remove the client handle from the world:
+ m_World->RemoveClientFromChunks(m_ClientHandle.get());
+
+ if (m_ClientHandle->IsDestroyed()) // Note: checking IsWorldChangeScheduled not appropriate here since we can disconnecting while having a scheduled warp
+ {
+ // Disconnecting, do the necessary cleanup.
+ // This isn't in the destructor to avoid crashing accessing destroyed objects during shutdown.
+
+ if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this))
+ {
+ cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str()));
+ LOGINFO("Player %s has left the game", GetName().c_str());
+ }
+
+ // Remove ourself from everyone's lists:
+ cRoot::Get()->BroadcastPlayerListsRemovePlayer(*this);
+
+ // Atomically decrement player count (in world thread):
+ cRoot::Get()->GetServer()->PlayerDestroyed();
+
+ // We're just disconnecting. The remaining code deals with going through portals, so bail:
+ return;
+ }
+
+ const auto DestinationDimension = m_WorldChangeInfo.m_NewWorld->GetDimension();
+
+ // Award relevant achievements:
+ if (DestinationDimension == dimEnd)
+ {
+ AwardAchievement(Statistic::AchTheEnd);
+ }
+ else if (DestinationDimension == dimNether)
+ {
+ AwardAchievement(Statistic::AchPortal);
+ }
+
+ // Clear sent chunk lists from the clienthandle:
+ m_ClientHandle->RemoveFromWorld();
+
+ // The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
+ m_ClientHandle->InvalidateCachedSentChunk();
+
+ // Clientside warp start:
+ m_ClientHandle->SendRespawn(DestinationDimension, false);
+}
+
+
+
+
+
+void cPlayer::SpawnOn(cClientHandle & a_Client)
+{
+ if (!m_bVisible || (m_ClientHandle.get() == (&a_Client)))
+ {
+ return;
+ }
+
+ LOGD("Spawing %s on %s", GetName().c_str(), a_Client.GetUsername().c_str());
+
+ a_Client.SendPlayerSpawn(*this);
+ a_Client.SendEntityHeadLook(*this);
+ a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem());
+ a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots());
+ a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings());
+ a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate());
+ a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet());
+}
+
+
+
+
+
+void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
+{
+ m_ClientHandle->Tick(a_Dt.count());
+
+ if (m_ClientHandle->IsDestroyed())
+ {
+ Destroy();
+ return;
+ }
+
+ if (!m_ClientHandle->IsPlaying())
+ {
+ // We're not yet in the game, ignore everything:
+ return;
+ }
+
+ m_Stats.AddValue(Statistic::PlayOneMinute);
+ m_Stats.AddValue(Statistic::TimeSinceDeath);
+
+ if (IsCrouched())
+ {
+ m_Stats.AddValue(Statistic::SneakTime);
+ }
+
+ // Handle the player detach, when the player is in spectator mode
+ if (
+ (IsGameModeSpectator()) &&
+ (m_AttachedTo != nullptr) &&
+ (
+ (m_AttachedTo->IsDestroyed()) || // Watching entity destruction
+ (m_AttachedTo->GetHealth() <= 0) || // Watching entity dead
+ (IsCrouched()) // Or the player wants to be detached
+ )
+ )
+ {
+ Detach();
+ }
+
+ if (!a_Chunk.IsValid())
+ {
+ // Players are ticked even if the parent chunk is invalid.
+ // We've processed as much as we can, bail:
+ return;
+ }
+
+ ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid()));
+ ASSERT(a_Chunk.IsValid());
+
+ // Handle a frozen player:
+ TickFreezeCode();
+
+ if (
+ m_IsFrozen || // Don't do Tick updates if frozen
+ IsWorldChangeScheduled() // If we're about to change worlds (e.g. respawn), abort processing all world interactions (GH #3939)
+ )
+ {
+ return;
+ }
+
+ Super::Tick(a_Dt, a_Chunk);
+
+ // Handle charging the bow:
+ if (m_IsChargingBow)
+ {
+ m_BowCharge += 1;
+ }
+
+ BroadcastMovementUpdate(m_ClientHandle.get());
+
+ if (m_Health > 0) // make sure player is alive
+ {
+ m_World->CollectPickupsByPlayer(*this);
+
+ if ((m_EatingFinishTick >= 0) && (m_EatingFinishTick <= m_World->GetWorldAge()))
+ {
+ FinishEating();
+ }
+
+ HandleFood();
+ }
+
+ if (m_IsFishing)
+ {
+ HandleFloater();
+ }
+
+ // Update items (e.g. Maps)
+ m_Inventory.UpdateItems();
+
+ // Send Player List (Once per m_LastPlayerListTime/1000 ms)
+ if (m_LastPlayerListTime + PLAYER_LIST_TIME_MS <= std::chrono::steady_clock::now())
+ {
+ m_World->BroadcastPlayerListUpdatePing(*this);
+ m_LastPlayerListTime = std::chrono::steady_clock::now();
+ }
+
+ if (m_TicksUntilNextSave == 0)
+ {
+ SaveToDisk();
+ m_TicksUntilNextSave = PLAYER_INVENTORY_SAVE_INTERVAL;
+ }
+ else
+ {
+ m_TicksUntilNextSave--;
+ }
}