summaryrefslogtreecommitdiffstats
path: root/src/Entities
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Entities/Entity.cpp86
-rw-r--r--src/Entities/Entity.h12
-rw-r--r--src/Entities/Player.cpp458
-rw-r--r--src/Entities/Player.h55
4 files changed, 244 insertions, 367 deletions
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index f07eab415..a185b8f69 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -1183,12 +1183,6 @@ void cEntity::ApplyFriction(Vector3d & a_Speed, double a_SlowdownMultiplier, flo
void cEntity::TickBurning(cChunk & a_Chunk)
{
- // If we're about to change worlds, then we can't accurately determine whether we're in lava (#3939)
- if (IsWorldChangeScheduled())
- {
- return;
- }
-
// Remember the current burning state:
bool HasBeenBurning = (m_TicksLeftBurning > 0);
@@ -1359,12 +1353,6 @@ void cEntity::DetectMagma(void)
bool cEntity::DetectPortal()
{
- // If somebody scheduled a world change, do nothing.
- if (IsWorldChangeScheduled())
- {
- return true;
- }
-
if (GetWorld()->GetDimension() == dimOverworld)
{
if (GetWorld()->GetLinkedNetherWorldName().empty() && GetWorld()->GetLinkedEndWorldName().empty())
@@ -1380,7 +1368,7 @@ bool cEntity::DetectPortal()
}
int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT;
- if ((Y > 0) && (Y < cChunkDef::Height))
+ if (cChunkDef::IsValidHeight(Y))
{
switch (GetWorld()->GetBlock(X, Y, Z))
{
@@ -1413,24 +1401,16 @@ bool cEntity::DetectPortal()
{
return false;
}
- cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
- eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn
- if (IsPlayer())
- {
- // Send a respawn packet before world is loaded / generated so the client isn't left in limbo
- (static_cast<cPlayer *>(this))->GetClientHandle()->SendRespawn(DestionationDim);
- }
-
Vector3d TargetPos = GetPosition();
TargetPos.x *= 8.0;
TargetPos.z *= 8.0;
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
- LOGD("Jumping %s -> %s", DimensionToString(dimNether).c_str(), DimensionToString(DestionationDim).c_str());
+ LOGD("Jumping %s -> %s", DimensionToString(dimNether).c_str(), DimensionToString(TargetWorld->GetDimension()).c_str());
new cNetherPortalScanner(*this, *TargetWorld, TargetPos, cChunkDef::Height);
return true;
}
@@ -1441,28 +1421,16 @@ bool cEntity::DetectPortal()
{
return false;
}
- cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
- eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
- if (IsPlayer())
- {
- if (DestionationDim == dimNether)
- {
- static_cast<cPlayer *>(this)->AwardAchievement(Statistic::AchPortal);
- }
-
- static_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
- }
-
Vector3d TargetPos = GetPosition();
TargetPos.x /= 8.0;
TargetPos.z /= 8.0;
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
- LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(DestionationDim).c_str());
+ LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(TargetWorld->GetDimension()).c_str());
new cNetherPortalScanner(*this, *TargetWorld, TargetPos, (cChunkDef::Height / 2));
return true;
}
@@ -1483,34 +1451,26 @@ bool cEntity::DetectPortal()
// End portal in the end
if (GetWorld()->GetDimension() == dimEnd)
{
-
if (GetWorld()->GetLinkedOverworldName().empty())
{
return false;
}
- cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
- eDimension DestionationDim = DestinationWorld->GetDimension();
-
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
+ cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
+ ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
+ LOGD("Jumping %s -> %s", DimensionToString(dimEnd).c_str(), DimensionToString(TargetWorld->GetDimension()).c_str());
+
if (IsPlayer())
{
cPlayer * Player = static_cast<cPlayer *>(this);
- if (Player->GetBedWorld() == DestinationWorld)
- {
- Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z);
- }
- else
+ if (Player->GetBedWorld() == TargetWorld)
{
- Player->TeleportToCoords(DestinationWorld->GetSpawnX(), DestinationWorld->GetSpawnY(), DestinationWorld->GetSpawnZ());
+ return MoveToWorld(*TargetWorld, Player->GetLastBedPos());
}
- Player->GetClientHandle()->SendRespawn(DestionationDim);
}
- cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
- ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
- LOGD("Jumping %s -> %s", DimensionToString(dimEnd).c_str(), DimensionToString(DestionationDim).c_str());
return MoveToWorld(*TargetWorld, false);
}
// End portal in the overworld
@@ -1520,23 +1480,12 @@ bool cEntity::DetectPortal()
{
return false;
}
- cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName());
- eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
- if (IsPlayer())
- {
- if (DestionationDim == dimEnd)
- {
- static_cast<cPlayer *>(this)->AwardAchievement(Statistic::AchTheEnd);
- }
- static_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
- }
-
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
- LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(DestionationDim).c_str());
+ LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(TargetWorld->GetDimension()).c_str());
return MoveToWorld(*TargetWorld, false);
}
@@ -1559,13 +1508,14 @@ void cEntity::DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo)
{
ASSERT(a_WorldChangeInfo.m_NewWorld != nullptr);
+ // Reset portal cooldown:
if (a_WorldChangeInfo.m_SetPortalCooldown)
{
m_PortalCooldownData.m_TicksDelayed = 0;
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
}
- if (GetWorld() == a_WorldChangeInfo.m_NewWorld)
+ if (m_World == a_WorldChangeInfo.m_NewWorld)
{
// Moving to same world, don't need to remove from world
SetPosition(a_WorldChangeInfo.m_NewPosition);
@@ -1578,23 +1528,19 @@ void cEntity::DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo)
GetChunkX(), GetChunkZ()
);
- // If entity is attached to another entity, detach, to prevent client side effects
- Detach();
-
// Stop ticking, in preperation for detaching from this world.
SetIsTicking(false);
// Remove from the old world
+ const auto OldWorld = m_World;
auto Self = m_World->RemoveEntity(*this);
- // Update entity before calling hook
+ // Update entity:
ResetPosition(a_WorldChangeInfo.m_NewPosition);
SetWorld(a_WorldChangeInfo.m_NewWorld);
- cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, *m_World);
-
// Don't do anything after adding as the old world's CS no longer protects us
- a_WorldChangeInfo.m_NewWorld->AddEntity(std::move(Self));
+ a_WorldChangeInfo.m_NewWorld->AddEntity(std::move(Self), OldWorld);
}
@@ -1614,7 +1560,7 @@ bool cEntity::MoveToWorld(cWorld & a_World, Vector3d a_NewPosition, bool a_SetPo
// Create new world change info
// (The last warp command always takes precedence)
- m_WorldChangeInfo = { &a_World, a_NewPosition, a_SetPortalCooldown, a_ShouldSendRespawn };
+ m_WorldChangeInfo = { &a_World, a_NewPosition, a_SetPortalCooldown };
if (OldWorld != nullptr)
{
diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h
index 85cf35661..cbefc764c 100644
--- a/src/Entities/Entity.h
+++ b/src/Entities/Entity.h
@@ -81,7 +81,6 @@ protected:
cWorld * m_NewWorld;
Vector3d m_NewPosition;
bool m_SetPortalCooldown;
- bool m_SendRespawn;
};
public:
@@ -173,7 +172,7 @@ public:
/** Spawns the entity in the world; returns true if spawned, false if not (plugin disallowed).
Adds the entity to the world. */
- virtual bool Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld);
+ bool Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld);
/** Called when the entity is added to a world.
e.g after first spawning or after successfuly moving between worlds.
@@ -469,13 +468,6 @@ public:
/** Teleports to the coordinates specified */
virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ);
- /** Schedules a MoveToWorld call to occur on the next Tick of the entity */
- [[deprecated]] void ScheduleMoveToWorld(cWorld & a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = true)
- {
- LOGWARNING("ScheduleMoveToWorld is deprecated, use MoveToWorld instead");
- MoveToWorld(a_World, a_NewPosition, a_ShouldSetPortalCooldown, a_ShouldSendRespawn);
- }
-
bool MoveToWorld(cWorld & a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = true);
bool MoveToWorld(cWorld & a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition)
@@ -718,7 +710,7 @@ protected:
/** Handles the moving of this entity between worlds.
Should handle degenerate cases such as moving to the same world. */
- virtual void DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo);
+ void DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo);
/** Applies friction to an entity
@param a_Speed The speed vector to apply changes to
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 391a5ce71..d20795643 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -86,7 +86,7 @@ const int cPlayer::EATING_TICKS = 30;
-cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName) :
+cPlayer::cPlayer(const cClientHandlePtr & a_Client) :
Super(etPlayer, 0.6, 1.8),
m_bVisible(true),
m_FoodLevel(MAX_FOOD_LEVEL),
@@ -96,10 +96,8 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName
m_Stance(0.0),
m_Inventory(*this),
m_EnderChestContents(9, 3),
- m_CurrentWindow(nullptr),
- m_InventoryWindow(nullptr),
+ m_DefaultWorldPath(cRoot::Get()->GetDefaultWorld()->GetDataPath()),
m_GameMode(eGameMode_NotSet),
- m_IP(""),
m_ClientHandle(a_Client),
m_IsFrozen(false),
m_NormalMaxSpeed(1.0),
@@ -113,7 +111,6 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName
m_EatingFinishTick(-1),
m_LifetimeTotalXp(0),
m_CurrentXp(0),
- m_bDirtyExperience(false),
m_IsChargingBow(false),
m_BowCharge(0),
m_FloaterID(cEntity::INVALID_ID),
@@ -122,11 +119,10 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName
m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL),
m_bIsTeleporting(false),
m_UUID((a_Client != nullptr) ? a_Client->GetUUID() : cUUID{}),
- m_CustomName(""),
m_SkinParts(0),
m_MainHand(mhRight)
{
- ASSERT(a_PlayerName.length() <= 16); // Otherwise this player could crash many clients...
+ ASSERT(GetName().length() <= 16); // Otherwise this player could crash many clients...
m_InventoryWindow = new cInventoryWindow(*this);
m_CurrentWindow = m_InventoryWindow;
@@ -136,7 +132,6 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName
m_Health = MAX_HEALTH;
m_LastPlayerListTime = std::chrono::steady_clock::now();
- m_PlayerName = a_PlayerName;
cWorld * World = nullptr;
if (!LoadFromDisk(World))
@@ -149,19 +144,16 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName
// This is a new player. Set the player spawn point to the spawn point of the default world
SetBedPos(Vector3i(static_cast<int>(World->GetSpawnX()), static_cast<int>(World->GetSpawnY()), static_cast<int>(World->GetSpawnZ())), World);
- SetWorld(World); // Use default world
-
m_EnchantmentSeed = GetRandomProvider().RandInt<unsigned int>(); // Use a random number to seed the enchantment generator
FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}",
- a_PlayerName, GetPosition()
+ GetName(), GetPosition()
);
}
m_LastGroundHeight = static_cast<float>(GetPosY());
m_Stance = GetPosY() + 1.62;
-
if (m_GameMode == gmNotSet)
{
if (World->IsGameModeCreative())
@@ -187,34 +179,6 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName
-bool cPlayer::Initialize(OwnedEntity a_Self, cWorld & a_World)
-{
- UNUSED(a_World);
- ASSERT(GetWorld() != nullptr);
- ASSERT(GetParentChunk() == nullptr);
- GetWorld()->AddPlayer(std::unique_ptr<cPlayer>(static_cast<cPlayer *>(a_Self.release())));
-
- cPluginManager::Get()->CallHookSpawnedEntity(*GetWorld(), *this);
-
- if (m_KnownRecipes.empty())
- {
- m_ClientHandle->SendInitRecipes(0);
- }
- else
- {
- for (const auto KnownRecipe : m_KnownRecipes)
- {
- m_ClientHandle->SendInitRecipes(KnownRecipe);
- }
- }
-
- return true;
-}
-
-
-
-
-
void cPlayer::AddKnownItem(const cItem & a_Item)
{
if (a_Item.m_ItemType < 0)
@@ -258,20 +222,14 @@ void cPlayer::AddKnownRecipe(UInt32 a_RecipeId)
cPlayer::~cPlayer(void)
{
- 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());
- }
-
LOGD("Deleting cPlayer \"%s\" at %p, ID %d", GetName().c_str(), static_cast<void *>(this), GetUniqueID());
- SaveToDisk();
+ // "Times ragequit":
+ m_Stats.AddValue(Statistic::LeaveGame);
- m_ClientHandle = nullptr;
+ SaveToDisk();
delete m_InventoryWindow;
- m_InventoryWindow = nullptr;
LOGD("Player %p deleted", static_cast<void *>(this));
}
@@ -280,9 +238,105 @@ 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);
}
@@ -313,30 +367,23 @@ void cPlayer::SpawnOn(cClientHandle & a_Client)
void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
- if (m_ClientHandle != nullptr)
- {
- if (m_ClientHandle->IsDestroyed())
- {
- // This should not happen, because destroying a client will remove it from the world, but just in case
- ASSERT(!"Player ticked whilst in the process of destruction!");
- m_ClientHandle = nullptr;
- return;
- }
+ m_ClientHandle->Tick(a_Dt.count());
- if (!m_ClientHandle->IsPlaying())
- {
- // We're not yet in the game, ignore everything
- return;
- }
- }
- else
+ if (m_ClientHandle->IsDestroyed())
{
- ASSERT(!"Player ticked whilst in the process of destruction!");
+ 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);
@@ -356,16 +403,27 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
Detach();
}
- // Handle a frozen player
- TickFreezeCode();
- if (m_IsFrozen)
+ 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((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:
@@ -374,12 +432,6 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
m_BowCharge += 1;
}
- // Handle updating experience
- if (m_bDirtyExperience)
- {
- SendExperience();
- }
-
BroadcastMovementUpdate(m_ClientHandle.get());
if (m_Health > 0) // make sure player is alive
@@ -504,6 +556,15 @@ int cPlayer::CalcLevelFromXp(int a_XpTotal)
+const std::set<UInt32> & cPlayer::GetKnownRecipes() const
+{
+ return m_KnownRecipes;
+}
+
+
+
+
+
int cPlayer::XpForLevel(int a_Level)
{
// level 0 to 15
@@ -558,8 +619,8 @@ bool cPlayer::SetCurrentExperience(int a_CurrentXp)
m_CurrentXp = a_CurrentXp;
- // Set experience to be updated
- m_bDirtyExperience = true;
+ // Update experience:
+ m_ClientHandle->SendExperience();
return true;
}
@@ -591,8 +652,8 @@ int cPlayer::DeltaExperience(int a_Xp_delta)
LOGD("Player \"%s\" gained / lost %d experience, total is now: %d", GetName().c_str(), a_Xp_delta, m_CurrentXp);
- // Set experience to be updated
- m_bDirtyExperience = true;
+ // Set experience to be updated:
+ m_ClientHandle->SendExperience();
return m_CurrentXp;
}
@@ -661,7 +722,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
void cPlayer::Heal(int a_Health)
{
Super::Heal(a_Health);
- SendHealth();
+ m_ClientHandle->SendHealth();
}
@@ -679,7 +740,7 @@ void cPlayer::SetFoodLevel(int a_FoodLevel)
}
m_FoodLevel = FoodLevel;
- SendHealth();
+ m_ClientHandle->SendHealth();
}
@@ -807,43 +868,6 @@ void cPlayer::AbortEating(void)
-void cPlayer::SendHealth(void)
-{
- if (m_ClientHandle != nullptr)
- {
- m_ClientHandle->SendHealth();
- }
-}
-
-
-
-
-
-void cPlayer::SendHotbarActiveSlot(void)
-{
- if (m_ClientHandle != nullptr)
- {
- m_ClientHandle->SendHeldItemChange(m_Inventory.GetEquippedSlotNum());
- }
-}
-
-
-
-
-
-void cPlayer::SendExperience(void)
-{
- if (m_ClientHandle != nullptr)
- {
- m_ClientHandle->SendExperience();
- m_bDirtyExperience = false;
- }
-}
-
-
-
-
-
void cPlayer::ClearInventoryPaintSlots(void)
{
// Clear the list of slots that are being inventory-painted. Used by cWindow only
@@ -1007,7 +1031,7 @@ void cPlayer::SetCustomName(const AString & a_CustomName)
}
m_World->BroadcastPlayerListAddPlayer(*this);
- m_World->BroadcastSpawnEntity(*this, GetClientHandle());
+ m_World->BroadcastSpawnEntity(*this, m_ClientHandle.get());
}
@@ -1017,7 +1041,7 @@ void cPlayer::SetCustomName(const AString & a_CustomName)
void cPlayer::SetBedPos(const Vector3i & a_Pos)
{
m_LastBedPos = a_Pos;
- m_SpawnWorld = m_World;
+ m_SpawnWorldName = m_World->GetName();
}
@@ -1028,7 +1052,7 @@ void cPlayer::SetBedPos(const Vector3i & a_Pos, cWorld * a_World)
{
m_LastBedPos = a_Pos;
ASSERT(a_World != nullptr);
- m_SpawnWorld = a_World;
+ m_SpawnWorldName = a_World->GetName();
}
@@ -1037,7 +1061,12 @@ void cPlayer::SetBedPos(const Vector3i & a_Pos, cWorld * a_World)
cWorld * cPlayer::GetBedWorld()
{
- return m_SpawnWorld;
+ if (const auto World = cRoot::Get()->GetWorld(m_SpawnWorldName); World != nullptr)
+ {
+ return World;
+ }
+
+ return cRoot::Get()->GetDefaultWorld();
}
@@ -1106,7 +1135,7 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
{
// Any kind of damage adds food exhaustion
AddFoodExhaustion(0.3f);
- SendHealth();
+ m_ClientHandle->SendHealth();
// Tell the wolves
if (a_TDI.Attacker != nullptr)
@@ -1296,17 +1325,16 @@ void cPlayer::Respawn(void)
m_LifetimeTotalXp = 0;
// ToDo: send score to client? How?
- m_ClientHandle->SendRespawn(m_SpawnWorld->GetDimension(), true);
-
// Extinguish the fire:
StopBurning();
- if (GetWorld() != m_SpawnWorld)
+ if (const auto BedWorld = GetBedWorld(); m_World != BedWorld)
{
- MoveToWorld(*m_SpawnWorld, GetLastBedPos(), false, false);
+ MoveToWorld(*BedWorld, GetLastBedPos(), false, false);
}
else
{
+ m_ClientHandle->SendRespawn(m_World->GetDimension(), true);
TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z);
}
@@ -1380,6 +1408,15 @@ bool cPlayer::CanMobsTarget(void) const
+AString cPlayer::GetIP(void) const
+{
+ return m_ClientHandle->GetIPString();
+}
+
+
+
+
+
void cPlayer::SetTeam(cTeam * a_Team)
{
if (m_Team == a_Team)
@@ -1600,6 +1637,15 @@ void cPlayer::SendAboveActionBarMessage(const cCompositeChat & a_Message)
+const AString & cPlayer::GetName(void) const
+{
+ return m_ClientHandle->GetUsername();
+}
+
+
+
+
+
void cPlayer::SetGameMode(eGameMode a_GameMode)
{
if ((a_GameMode < gmMin) || (a_GameMode >= gmMax))
@@ -1671,15 +1717,6 @@ void cPlayer::SetCapabilities()
-void cPlayer::SetIP(const AString & a_IP)
-{
- m_IP = a_IP;
-}
-
-
-
-
-
void cPlayer::AwardAchievement(const Statistic a_Ach)
{
// Check if the prerequisites are met:
@@ -1753,7 +1790,7 @@ void cPlayer::Unfreeze()
GetClientHandle()->SendPlayerMaxSpeed();
m_IsFrozen = false;
- BroadcastMovementUpdate(GetClientHandle());
+ BroadcastMovementUpdate(m_ClientHandle.get());
GetClientHandle()->SendPlayerPosition();
}
@@ -1819,6 +1856,29 @@ Vector3d cPlayer::GetThrowSpeed(double a_SpeedCoeff) const
+eGameMode cPlayer::GetEffectiveGameMode(void) const
+{
+ // Since entities' m_World aren't set until Initialize, but cClientHandle sends the player's gamemode early
+ // the below block deals with m_World being nullptr when called.
+
+ auto World = m_World;
+
+ if (World == nullptr)
+ {
+ World = cRoot::Get()->GetDefaultWorld();
+ }
+ else if (IsWorldChangeScheduled())
+ {
+ World = m_WorldChangeInfo.m_NewWorld;
+ }
+
+ return (m_GameMode == gmNotSet) ? World->GetGameMode() : m_GameMode;
+}
+
+
+
+
+
void cPlayer::ForceSetSpeed(const Vector3d & a_Speed)
{
SetSpeed(a_Speed);
@@ -2099,79 +2159,6 @@ void cPlayer::TossPickup(const cItem & a_Item)
-void cPlayer::DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo)
-{
- ASSERT(a_WorldChangeInfo.m_NewWorld != nullptr);
-
- // Reset portal cooldown
- if (a_WorldChangeInfo.m_SetPortalCooldown)
- {
- m_PortalCooldownData.m_TicksDelayed = 0;
- m_PortalCooldownData.m_ShouldPreventTeleportation = true;
- }
-
- if (m_World == a_WorldChangeInfo.m_NewWorld)
- {
- // Moving to same world, don't need to remove from world
- SetPosition(a_WorldChangeInfo.m_NewPosition);
- return;
- }
-
- LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ",
- GetName(), GetWorld()->GetName(), a_WorldChangeInfo.m_NewWorld->GetName(),
- GetChunkX(), GetChunkZ()
- );
-
- // Stop all mobs from targeting this player
- StopEveryoneFromTargetingMe();
-
- // If player is attached to entity, detach, to prevent client side effects
- Detach();
-
- // Prevent further ticking in this world
- SetIsTicking(false);
-
- // Remove from the old world
- auto & OldWorld = *GetWorld();
- auto Self = OldWorld.RemovePlayer(*this);
-
- ResetPosition(a_WorldChangeInfo.m_NewPosition);
- FreezeInternal(a_WorldChangeInfo.m_NewPosition, false);
- SetWorld(a_WorldChangeInfo.m_NewWorld); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
-
- // Set capabilities based on new world
- SetCapabilities();
-
- cClientHandle * ch = GetClientHandle();
- if (ch != nullptr)
- {
- // The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
- ch->InvalidateCachedSentChunk();
-
- // Send the respawn packet:
- if (a_WorldChangeInfo.m_SendRespawn)
- {
- ch->SendRespawn(a_WorldChangeInfo.m_NewWorld->GetDimension());
- }
-
- // Update the view distance.
- ch->SetViewDistance(ch->GetRequestedViewDistance());
-
- // Send current weather of target world to player
- if (a_WorldChangeInfo.m_NewWorld->GetDimension() == dimOverworld)
- {
- ch->SendWeather(a_WorldChangeInfo.m_NewWorld->GetWeather());
- }
- }
-
- // New world will take over and announce client at its next tick
- a_WorldChangeInfo.m_NewWorld->AddPlayer(std::move(Self), &OldWorld);
-}
-
-
-
-
-
bool cPlayer::LoadFromDisk(cWorldPtr & a_World)
{
LoadRank();
@@ -2334,7 +2321,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents);
- m_LoadedWorldName = root.get("world", "world").asString();
+ m_CurrentWorldName = root.get("world", "world").asString();
a_World = cRoot::Get()->GetWorld(GetLoadedWorldName());
if (a_World == nullptr)
{
@@ -2345,18 +2332,13 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
m_LastBedPos.x = root.get("SpawnX", a_World->GetSpawnX()).asInt();
m_LastBedPos.y = root.get("SpawnY", a_World->GetSpawnY()).asInt();
m_LastBedPos.z = root.get("SpawnZ", a_World->GetSpawnZ()).asInt();
- AString SpawnWorldName = root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString();
- m_SpawnWorld = cRoot::Get()->GetWorld(SpawnWorldName);
- if (m_SpawnWorld == nullptr)
- {
- m_SpawnWorld = cRoot::Get()->GetDefaultWorld();
- }
+ m_SpawnWorldName = root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString();
try
{
// Load the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
- StatSerializer::Load(m_Stats, cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetUUID().ToLongString());
+ StatSerializer::Load(m_Stats, m_DefaultWorldPath, GetUUID().ToLongString());
}
catch (...)
{
@@ -2460,27 +2442,10 @@ bool cPlayer::SaveToDisk()
root["SpawnX"] = GetLastBedPos().x;
root["SpawnY"] = GetLastBedPos().y;
root["SpawnZ"] = GetLastBedPos().z;
- root["SpawnWorld"] = m_SpawnWorld->GetName();
+ root["SpawnWorld"] = m_SpawnWorldName;
root["enchantmentSeed"] = m_EnchantmentSeed;
-
- if (m_World != nullptr)
- {
- root["world"] = m_World->GetName();
- if (m_GameMode == m_World->GetGameMode())
- {
- root["gamemode"] = static_cast<int>(eGameMode_NotSet);
- }
- else
- {
- root["gamemode"] = static_cast<int>(m_GameMode);
- }
- }
- else
- {
- // This happens if the player is saved to new format after loading from the old format
- root["world"] = m_LoadedWorldName;
- root["gamemode"] = static_cast<int>(eGameMode_NotSet);
- }
+ root["world"] = m_CurrentWorldName;
+ root["gamemode"] = static_cast<int>(m_GameMode);
auto JsonData = JsonUtils::WriteStyledString(root);
AString SourceFile = GetUUIDFileName(m_UUID);
@@ -2505,7 +2470,8 @@ bool cPlayer::SaveToDisk()
{
// Save the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
- StatSerializer::Save(m_Stats, cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetUUID().ToLongString());
+ // TODO: save together with player.dat, not in some other place.
+ StatSerializer::Save(m_Stats, m_DefaultWorldPath, GetUUID().ToLongString());
}
catch (...)
{
@@ -2787,7 +2753,7 @@ void cPlayer::LoadRank(void)
else
{
// Update the name:
- RankMgr->UpdatePlayerName(m_UUID, m_PlayerName);
+ RankMgr->UpdatePlayerName(m_UUID, GetName());
}
m_Permissions = RankMgr->GetPlayerPermissions(m_UUID);
m_Restrictions = RankMgr->GetPlayerRestrictions(m_UUID);
@@ -3062,16 +3028,6 @@ void cPlayer::Detach()
-void cPlayer::RemoveClientHandle(void)
-{
- ASSERT(m_ClientHandle != nullptr);
- m_ClientHandle.reset();
-}
-
-
-
-
-
AString cPlayer::GetUUIDFileName(const cUUID & a_UUID)
{
AString UUID = a_UUID.ToLongString();
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index 1e7a17e4f..ba3c345ed 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -48,12 +48,11 @@ public:
CLASS_PROTODEF(cPlayer)
- cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName);
-
- virtual bool Initialize(OwnedEntity a_Self, cWorld & a_World) override;
+ cPlayer(const cClientHandlePtr & a_Client);
virtual ~cPlayer() override;
+ virtual void OnAddToWorld(cWorld & a_World) override;
virtual void OnRemoveFromWorld(cWorld & a_World) override;
virtual void SpawnOn(cClientHandle & a_Client) override;
@@ -123,6 +122,9 @@ public:
// tolua_end
+ /** Gets the set of IDs for recipes this player has discovered. */
+ const std::set<UInt32> & GetKnownRecipes() const;
+
/** Starts charging the equipped bow */
void StartChargingBow(void);
@@ -187,7 +189,7 @@ public:
eGameMode GetGameMode(void) const { return m_GameMode; }
/** Returns the current effective gamemode (inherited gamemode is resolved before returning) */
- eGameMode GetEffectiveGameMode(void) const { return (m_GameMode == gmNotSet) ? m_World->GetGameMode() : m_GameMode; }
+ eGameMode GetEffectiveGameMode(void) const;
/** Sets the gamemode for the player.
The gamemode may be gmNotSet, in that case the player inherits the world's gamemode.
@@ -219,7 +221,7 @@ public:
/** Returns true if the player can be targeted by Mobs */
bool CanMobsTarget(void) const;
- AString GetIP(void) const { return m_IP; } // tolua_export
+ AString GetIP(void) const; // tolua_export
/** Returns the associated team, nullptr if none */
cTeam * GetTeam(void) { return m_Team; } // tolua_export
@@ -243,8 +245,6 @@ public:
If the achievement has been already awarded to the player, this method will just increment the stat counter. */
void AwardAchievement(Statistic a_Ach);
- void SetIP(const AString & a_IP);
-
/** Forces the player to move in the given direction.
@deprecated Use SetSpeed instead. */
void ForceSetSpeed(const Vector3d & a_Speed); // tolua_export
@@ -263,7 +263,6 @@ public:
/** Closes the current window if it matches the specified ID, resets current window to m_InventoryWindow */
void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true);
- /** Returns the raw client handle associated with the player. */
cClientHandle * GetClientHandle(void) const { return m_ClientHandle.get(); }
// tolua_end
@@ -275,9 +274,6 @@ public:
/** Permute the seed for enchanting related PRNGs, don't use this for other purposes. */
void PermuteEnchantmentSeed();
- /** Returns the SharedPtr to client handle associated with the player. */
- cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; }
-
// tolua_begin
void SendMessage (const AString & a_Message);
@@ -294,8 +290,7 @@ public:
void SendSystemMessage (const cCompositeChat & a_Message);
void SendAboveActionBarMessage(const cCompositeChat & a_Message);
- const AString & GetName(void) const { return m_PlayerName; }
- void SetName(const AString & a_Name) { m_PlayerName = a_Name; }
+ const AString & GetName(void) const;
// tolua_end
@@ -434,7 +429,7 @@ public:
*/
bool LoadFromFile(const AString & a_FileName, cWorldPtr & a_World);
- const AString & GetLoadedWorldName() { return m_LoadedWorldName; }
+ const AString & GetLoadedWorldName() const { return m_CurrentWorldName; }
/** Opens the inventory of any tame horse the player is riding.
If the player is not riding a horse or if the horse is untamed, does nothing. */
@@ -452,13 +447,6 @@ public:
equipped item is enchanted. */
void UseItem(int a_SlotNumber, short a_Damage = 1);
- void SendHealth(void);
-
- // Send current active hotbar slot
- void SendHotbarActiveSlot(void);
-
- void SendExperience(void);
-
/** In UI windows, get the item that the player is dragging */
cItem & GetDraggingItem(void) {return m_DraggingItem; } // tolua_export
@@ -598,10 +586,6 @@ public:
virtual void AttachTo(cEntity * a_AttachTo) override;
virtual void Detach(void) override;
- /** Called by cClientHandle when the client is being destroyed.
- The player removes its m_ClientHandle ownership so that the ClientHandle gets deleted. */
- void RemoveClientHandle(void);
-
/** Returns the progress mined per tick for the block a_Block as a fraction
(1 would be completely mined)
Depends on hardness values so check those are correct.
@@ -647,9 +631,6 @@ protected:
AString m_MsgPrefix, m_MsgSuffix;
AString m_MsgNameColorCode;
- AString m_PlayerName;
- AString m_LoadedWorldName;
-
/** Xp Level stuff */
enum
{
@@ -687,11 +668,18 @@ protected:
/** The player's last saved bed position */
Vector3i m_LastBedPos;
- /** The world which the player respawns in upon death */
- cWorld * m_SpawnWorld;
+ /** The name of the world which the player respawns in upon death.
+ This is stored as a string to enable SaveToDisk to not touch cRoot, and thus can be safely called in the player's destructor. */
+ std::string m_SpawnWorldName;
+
+ /** The name of the world which the player currently resides in.
+ Stored in addition to m_World to allow SaveToDisk to be safely called in the player's destructor. */
+ std::string m_CurrentWorldName;
+
+ /** The save path of the default world. */
+ std::string m_DefaultWorldPath;
eGameMode m_GameMode;
- AString m_IP;
/** The item being dragged by the cursor while in a UI window */
cItem m_DraggingItem;
@@ -738,9 +726,6 @@ protected:
int m_CurrentXp;
unsigned int m_EnchantmentSeed;
- // flag saying we need to send a xp update to client
- bool m_bDirtyExperience;
-
bool m_IsChargingBow;
int m_BowCharge;
@@ -781,8 +766,6 @@ protected:
/** List of known items as Ids */
std::set<cItem, cItem::sItemCompare> m_KnownItems;
- virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override;
-
/** Sets the speed and sends it to the client, so that they are forced to move so. */
virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override;