1
0
This commit is contained in:
Mattes D 2016-09-05 15:05:54 +02:00
commit f7029eddd4
47 changed files with 1286 additions and 197 deletions

View File

@ -3,6 +3,7 @@ Many people have contributed to Cuberite, and this list attempts to broadcast at
BasedDoge (Donated AlchemistVillage prefabs) BasedDoge (Donated AlchemistVillage prefabs)
bearbin (Alexander Harkness) bearbin (Alexander Harkness)
beeduck beeduck
bibo38
birkett (Anthony Birkett) birkett (Anthony Birkett)
derouinw derouinw
Diusrex Diusrex

View File

@ -2582,6 +2582,7 @@ end
GetMinNetherPortalWidth = { Params = "", Return = "number", Notes = "Returns the minimum width for a nether portal" }, GetMinNetherPortalWidth = { Params = "", Return = "number", Notes = "Returns the minimum width for a nether portal" },
GetName = { Params = "", Return = "string", Notes = "Returns the name of the world, as specified in the settings.ini file." }, GetName = { Params = "", Return = "string", Notes = "Returns the name of the world, as specified in the settings.ini file." },
GetNumChunks = { Params = "", Return = "number", Notes = "Returns the number of chunks currently loaded." }, GetNumChunks = { Params = "", Return = "number", Notes = "Returns the number of chunks currently loaded." },
GetNumUnusedDirtyChunks = { Params = "", Return = "number", Notes = "Returns the number of unused dirty chunks. That's the number of chunks that we can save and then unload." },
GetScoreBoard = { Params = "", Return = "{{cScoreBoard}}", Notes = "Returns the {{cScoreBoard|ScoreBoard}} object used by this world. " }, GetScoreBoard = { Params = "", Return = "{{cScoreBoard}}", Notes = "Returns the {{cScoreBoard|ScoreBoard}} object used by this world. " },
GetSeed = { Params = "", Return = "number", Notes = "Returns the seed of the world." }, GetSeed = { Params = "", Return = "number", Notes = "Returns the seed of the world." },
GetSignLines = { Params = "BlockX, BlockY, BlockZ", Return = "IsValid, [Line1, Line2, Line3, Line4]", Notes = "Returns true and the lines of a sign at the specified coords, or false if there is no sign at the coords." }, GetSignLines = { Params = "BlockX, BlockY, BlockZ", Return = "IsValid, [Line1, Line2, Line3, Line4]", Notes = "Returns true and the lines of a sign at the specified coords, or false if there is no sign at the coords." },

View File

@ -38,8 +38,7 @@ public:
return; return;
} }
a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_WATER, 0); a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_WATER, 0);
// This is called later than the real destroying of this ice block
} }
} }

View File

@ -223,6 +223,19 @@ bool cChunk::CanUnload(void)
bool cChunk::CanUnloadAfterSaving(void)
{
return
m_LoadedByClient.empty() && // The chunk is not used by any client
m_IsDirty && // The chunk is dirty
(m_StayCount == 0) && // The chunk is not in a ChunkStay
(m_Presence != cpQueued) ; // The chunk is not queued for loading / generating (otherwise multi-load / multi-gen could occur)
}
void cChunk::MarkSaving(void) void cChunk::MarkSaving(void)
{ {
m_IsSaving = true; m_IsSaving = true;

View File

@ -112,6 +112,9 @@ public:
bool CanUnload(void); bool CanUnload(void);
/** Returns true if the chunk could have been unloaded if it weren't dirty */
bool CanUnloadAfterSaving(void);
bool IsLightValid(void) const {return m_IsLightValid; } bool IsLightValid(void) const {return m_IsLightValid; }
/* /*

View File

@ -2704,11 +2704,28 @@ void cChunkMap::SaveAllChunks(void)
int cChunkMap::GetNumChunks(void) size_t cChunkMap::GetNumChunks(void)
{ {
cCSLock Lock(m_CSChunks); cCSLock Lock(m_CSChunks);
return static_cast<int>(m_Chunks.size()); // TODO: change return value to unsigned type return m_Chunks.size();
}
size_t cChunkMap::GetNumUnusedDirtyChunks(void)
{
cCSLock Lock(m_CSChunks);
size_t res = 0;
for (const auto & Chunk : m_Chunks)
{
if (Chunk.second->IsValid() && Chunk.second->CanUnloadAfterSaving())
{
res += 1;
}
}
return res;
} }

View File

@ -388,7 +388,10 @@ public:
cWorld * GetWorld(void) { return m_World; } cWorld * GetWorld(void) { return m_World; }
int GetNumChunks(void); size_t GetNumChunks(void);
/** Returns the number of unused dirty chunks. Those are chunks that we can save and then unload */
size_t GetNumUnusedDirtyChunks(void);
void ChunkValidated(void); // Called by chunks that have become valid void ChunkValidated(void); // Called by chunks that have become valid

View File

@ -62,6 +62,7 @@ int cClientHandle::s_ClientCount = 0;
// cClientHandle: // cClientHandle:
cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) :
m_LastSentDimension(dimNotSet),
m_CurrentViewDistance(a_ViewDistance), m_CurrentViewDistance(a_ViewDistance),
m_RequestedViewDistance(a_ViewDistance), m_RequestedViewDistance(a_ViewDistance),
m_IPString(a_IPString), m_IPString(a_IPString),
@ -368,6 +369,7 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
// Return a server login packet // Return a server login packet
m_Protocol->SendLogin(*m_Player, *World); m_Protocol->SendLogin(*m_Player, *World);
m_LastSentDimension = World->GetDimension();
// Send Weather if raining: // Send Weather if raining:
if ((World->GetWeather() == 1) || (World->GetWeather() == 2)) if ((World->GetWeather() == 1) || (World->GetWeather() == 2))
@ -601,7 +603,6 @@ void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunk
// Removes the client from all chunks. Used when switching worlds or destroying the player
void cClientHandle::RemoveFromAllChunks() void cClientHandle::RemoveFromAllChunks()
{ {
cWorld * World = m_Player->GetWorld(); cWorld * World = m_Player->GetWorld();
@ -1267,7 +1268,7 @@ void cClientHandle::HandleBlockDigFinished(int a_BlockX, int a_BlockY, int a_Blo
BlockHandler(a_OldBlock)->OnDestroyedByPlayer(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ); BlockHandler(a_OldBlock)->OnDestroyedByPlayer(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ);
World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, a_BlockX, a_BlockY, a_BlockZ, a_OldBlock, this); World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, a_BlockX, a_BlockY, a_BlockZ, a_OldBlock, this);
// This call would remove the water, placed from the ice block handler // This call would remove the water, placed from the ice block handler
if (a_OldBlock != E_BLOCK_ICE) if (!((a_OldBlock == E_BLOCK_ICE) && (ChunkInterface.GetBlock(a_BlockX, a_BlockY, a_BlockZ) == E_BLOCK_WATER)))
{ {
World->DigBlock(a_BlockX, a_BlockY, a_BlockZ); World->DigBlock(a_BlockX, a_BlockY, a_BlockZ);
} }
@ -1886,7 +1887,7 @@ void cClientHandle::RemoveFromWorld(void)
} }
for (auto && Chunk : Chunks) for (auto && Chunk : Chunks)
{ {
m_Protocol->SendUnloadChunk(Chunk.m_ChunkX, Chunk.m_ChunkZ); SendUnloadChunk(Chunk.m_ChunkX, Chunk.m_ChunkZ);
} // for itr - Chunks[] } // for itr - Chunks[]
// Here, we set last streamed values to bogus ones so everything is resent // Here, we set last streamed values to bogus ones so everything is resent
@ -2704,7 +2705,21 @@ void cClientHandle::SendResetTitle()
void cClientHandle::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) void cClientHandle::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks)
{ {
m_Protocol->SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks); // If a_ShouldIgnoreDimensionChecks is true, we must be traveling to the same dimension
ASSERT((!a_ShouldIgnoreDimensionChecks) || (a_Dimension == m_LastSentDimension));
if ((!a_ShouldIgnoreDimensionChecks) && (a_Dimension == m_LastSentDimension))
{
// The client goes crazy if we send a respawn packet with the dimension of the current world
// So we send a temporary one first.
// This is not needed when the player dies, hence the a_ShouldIgnoreDimensionChecks flag.
// a_ShouldIgnoreDimensionChecks is true only at cPlayer::respawn, which is called after
// the player dies.
eDimension TemporaryDimension = (a_Dimension == dimOverworld) ? dimNether : dimOverworld;
m_Protocol->SendRespawn(TemporaryDimension);
}
m_Protocol->SendRespawn(a_Dimension);
m_LastSentDimension = a_Dimension;
} }

View File

@ -125,7 +125,8 @@ public: // tolua_export
/** Remove all loaded chunks that are no longer in range */ /** Remove all loaded chunks that are no longer in range */
void UnloadOutOfRangeChunks(void); void UnloadOutOfRangeChunks(void);
// Removes the client from all chunks. Used when switching worlds or destroying the player /** Removes the client from all chunks. Used when destroying the player.
When switching worlds, RemoveFromWorld does this function's job so it isn't called. */
void RemoveFromAllChunks(void); void RemoveFromAllChunks(void);
inline bool IsLoggedIn(void) const { return (m_State >= csAuthenticating); } inline bool IsLoggedIn(void) const { return (m_State >= csAuthenticating); }
@ -369,6 +370,9 @@ public: // tolua_export
bool IsPlayerChunkSent(); bool IsPlayerChunkSent();
private: private:
/** The dimension that was last sent to a player in a Respawn or Login packet.
Used to avoid Respawning into the same dimension, which confuses the client. */
eDimension m_LastSentDimension;
friend class cServer; // Needs access to SetSelf() friend class cServer; // Needs access to SetSelf()

View File

@ -180,6 +180,11 @@ void cArrowEntity::CollectedBy(cPlayer & a_Dest)
void cArrowEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cArrowEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
m_Timer += a_Dt; m_Timer += a_Dt;
if (m_bIsCollected) if (m_bIsCollected)

View File

@ -102,6 +102,11 @@ void cBoat::OnRightClicked(cPlayer & a_Player)
void cBoat::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cBoat::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
BroadcastMovementUpdate(); BroadcastMovementUpdate();
SetSpeed(GetSpeed() * 0.97); // Slowly decrease the speed SetSpeed(GetSpeed() * 0.97); // Slowly decrease the speed

View File

@ -1433,19 +1433,22 @@ bool cEntity::DetectPortal()
} }
m_PortalCooldownData.m_TicksDelayed = 0; m_PortalCooldownData.m_TicksDelayed = 0;
// Nether portal in the nether
if (GetWorld()->GetDimension() == dimNether) if (GetWorld()->GetDimension() == dimNether)
{ {
if (GetWorld()->GetLinkedOverworldName().empty()) if (GetWorld()->GetLinkedOverworldName().empty())
{ {
return false; return false;
} }
cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn
if (IsPlayer()) if (IsPlayer())
{ {
// Send a respawn packet before world is loaded / generated so the client isn't left in limbo // Send a respawn packet before world is loaded / generated so the client isn't left in limbo
(reinterpret_cast<cPlayer *>(this))->GetClientHandle()->SendRespawn(dimOverworld); (reinterpret_cast<cPlayer *>(this))->GetClientHandle()->SendRespawn(DestionationDim);
} }
Vector3d TargetPos = GetPosition(); Vector3d TargetPos = GetPosition();
@ -1454,23 +1457,30 @@ bool cEntity::DetectPortal()
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
LOGD("Jumping nether -> overworld"); LOGD("Jumping %s -> %s", DimensionToString(dimNether).c_str(), DimensionToString(DestionationDim).c_str());
new cNetherPortalScanner(this, TargetWorld, TargetPos, 256); new cNetherPortalScanner(this, TargetWorld, TargetPos, 256);
return true; return true;
} }
// Nether portal in the overworld
else else
{ {
if (GetWorld()->GetLinkedNetherWorldName().empty()) if (GetWorld()->GetLinkedNetherWorldName().empty())
{ {
return false; return false;
} }
cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true; m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer()) if (IsPlayer())
{ {
reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterPortal); if (DestionationDim == dimNether)
reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimNether); {
reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterPortal);
}
reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
} }
Vector3d TargetPos = GetPosition(); Vector3d TargetPos = GetPosition();
@ -1479,7 +1489,7 @@ bool cEntity::DetectPortal()
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName()); cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
LOGD("Jumping overworld -> nether"); LOGD("Jumping %s -> %s", DimensionToString(dimOverworld).c_str(), DimensionToString(DestionationDim).c_str());
new cNetherPortalScanner(this, TargetWorld, TargetPos, 128); new cNetherPortalScanner(this, TargetWorld, TargetPos, 128);
return true; return true;
} }
@ -1491,6 +1501,7 @@ bool cEntity::DetectPortal()
return false; return false;
} }
// End portal in the end
if (GetWorld()->GetDimension() == dimEnd) if (GetWorld()->GetDimension() == dimEnd)
{ {
@ -1498,37 +1509,55 @@ bool cEntity::DetectPortal()
{ {
return false; return false;
} }
cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true; m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer()) if (IsPlayer())
{ {
cPlayer * Player = reinterpret_cast<cPlayer *>(this); cPlayer * Player = reinterpret_cast<cPlayer *>(this);
Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z); if (Player->GetBedWorld() == DestinationWorld)
Player->GetClientHandle()->SendRespawn(dimOverworld); {
Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z);
}
else
{
Player->TeleportToCoords(DestinationWorld->GetSpawnX(), DestinationWorld->GetSpawnY(), DestinationWorld->GetSpawnZ());
}
Player->GetClientHandle()->SendRespawn(DestionationDim);
} }
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName()); cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() 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); return MoveToWorld(TargetWorld, false);
} }
// End portal in the overworld
else else
{ {
if (GetWorld()->GetLinkedEndWorldName().empty()) if (GetWorld()->GetLinkedEndWorldName().empty())
{ {
return false; return false;
} }
cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName());
eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true; m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer()) if (IsPlayer())
{ {
reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterTheEnd); if (DestionationDim == dimEnd)
reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimEnd); {
reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterTheEnd);
}
reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
} }
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName()); cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start() 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());
return MoveToWorld(TargetWorld, false); return MoveToWorld(TargetWorld, false);
} }

View File

@ -65,6 +65,11 @@ void cFireworkEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_C
void cFireworkEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cFireworkEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (m_TicksToExplosion <= 0) if (m_TicksToExplosion <= 0)
{ {

View File

@ -1264,6 +1264,11 @@ void cMinecartWithFurnace::OnRightClicked(cPlayer & a_Player)
void cMinecartWithFurnace::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cMinecartWithFurnace::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (m_IsFueled) if (m_IsFueled)
{ {

View File

@ -111,7 +111,11 @@ void cPawn::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), Callback); m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), Callback);
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
HandleFalling(); HandleFalling();
} }

View File

@ -116,6 +116,11 @@ void cPickup::SpawnOn(cClientHandle & a_Client)
void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
BroadcastMovementUpdate(); // Notify clients of position BroadcastMovementUpdate(); // Notify clients of position
m_Timer += a_Dt; m_Timer += a_Dt;

View File

@ -893,6 +893,15 @@ void cPlayer::SetBedPos(const Vector3i & a_Pos, cWorld * a_World)
cWorld * cPlayer::GetBedWorld()
{
return m_SpawnWorld;
}
void cPlayer::SetFlying(bool a_IsFlying) void cPlayer::SetFlying(bool a_IsFlying)
{ {
if (a_IsFlying == m_IsFlying) if (a_IsFlying == m_IsFlying)
@ -1771,64 +1780,71 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d
return false; return false;
} }
// Ask the plugins if the player is allowed to changing the world // Ask the plugins if the player is allowed to change the world
if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World))
{ {
// A Plugin doesn't allow the player to changing the world // A Plugin doesn't allow the player to change the world
return false; return false;
} }
// The clienthandle caches the coords of the chunk we're standing at. Invalidate this. GetWorld()->QueueTask([this, a_World, a_ShouldSendRespawn, a_NewPosition](cWorld & a_OldWorld)
GetClientHandle()->InvalidateCachedSentChunk();
// Prevent further ticking in this world
SetIsTicking(false);
// Tell others we are gone
GetWorld()->BroadcastDestroyEntity(*this);
// Remove player from world
GetWorld()->RemovePlayer(this, false);
// Set position to the new position
SetPosition(a_NewPosition);
FreezeInternal(a_NewPosition, false);
// Stop all mobs from targeting this player
StopEveryoneFromTargetingMe();
// Send the respawn packet:
if (a_ShouldSendRespawn && (m_ClientHandle != nullptr))
{ {
m_ClientHandle->SendRespawn(a_World->GetDimension()); // The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
} GetClientHandle()->InvalidateCachedSentChunk();
// Update the view distance. // Prevent further ticking in this world
m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance()); SetIsTicking(false);
// Send current weather of target world to player // Tell others we are gone
if (a_World->GetDimension() == dimOverworld) GetWorld()->BroadcastDestroyEntity(*this);
{
m_ClientHandle->SendWeather(a_World->GetWeather());
}
// Broadcast the player into the new world. // Remove player from world
a_World->BroadcastSpawnEntity(*this); GetWorld()->RemovePlayer(this, false);
// Set position to the new position
SetPosition(a_NewPosition);
FreezeInternal(a_NewPosition, false);
// Stop all mobs from targeting this player
StopEveryoneFromTargetingMe();
cClientHandle * ch = this->GetClientHandle();
if (ch != nullptr)
{
// Send the respawn packet:
if (a_ShouldSendRespawn)
{
m_ClientHandle->SendRespawn(a_World->GetDimension());
}
// Update the view distance.
ch->SetViewDistance(m_ClientHandle->GetRequestedViewDistance());
// Send current weather of target world to player
if (a_World->GetDimension() == dimOverworld)
{
ch->SendWeather(a_World->GetWeather());
}
}
// Broadcast the player into the new world.
a_World->BroadcastSpawnEntity(*this);
// Queue add to new world and removal from the old one
SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
cChunk * ParentChunk = this->GetParentChunk();
// Queue add to new world and removal from the old one
cChunk * ParentChunk = GetParentChunk();
cWorld * OldWorld = GetWorld();
SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
OldWorld->QueueTask([this, ParentChunk, a_World](cWorld & a_OldWorld)
{
LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ", LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ",
this->GetName().c_str(), this->GetName().c_str(),
a_OldWorld.GetName().c_str(), a_World->GetName().c_str(), a_OldWorld.GetName().c_str(), a_World->GetName().c_str(),
ParentChunk->GetPosX(), ParentChunk->GetPosZ() ParentChunk->GetPosX(), ParentChunk->GetPosZ()
); );
ParentChunk->RemoveEntity(this); ParentChunk->RemoveEntity(this);
a_World->AddPlayer(this, &a_OldWorld); // New world will appropriate and announce client at his next tick a_World->AddPlayer(this, &a_OldWorld); // New world will take over and announce client at its next tick
}); });
return true; return true;
} }

View File

@ -467,6 +467,9 @@ public:
// tolua_end // tolua_end
// TODO lua export GetBedPos and GetBedWorld
cWorld * GetBedWorld();
/** Update movement-related statistics. */ /** Update movement-related statistics. */
void UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIsOnGround); void UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIsOnGround);

View File

@ -369,6 +369,11 @@ AString cProjectileEntity::GetMCAClassName(void) const
void cProjectileEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cProjectileEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
BroadcastMovementUpdate(); BroadcastMovementUpdate();
} }

View File

@ -57,6 +57,11 @@ void cTNTEntity::Explode(void)
void cTNTEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cTNTEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
BroadcastMovementUpdate(); BroadcastMovementUpdate();
m_FuseTicks -= 1; m_FuseTicks -= 1;

View File

@ -52,6 +52,11 @@ void cAggressiveMonster::EventSeePlayer(cEntity * a_Entity, cChunk & a_Chunk)
void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (m_EMState == CHASING) if (m_EMState == CHASING)
{ {

View File

@ -19,6 +19,11 @@ cCaveSpider::cCaveSpider(void) :
void cCaveSpider::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cCaveSpider::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
m_EMPersonality = (GetWorld()->GetTimeOfDay() < (12000 + 1000)) ? PASSIVE : AGGRESSIVE; m_EMPersonality = (GetWorld()->GetTimeOfDay() < (12000 + 1000)) ? PASSIVE : AGGRESSIVE;
} }

View File

@ -23,6 +23,11 @@ cChicken::cChicken(void) :
void cChicken::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cChicken::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (IsBaby()) if (IsBaby())
{ {

View File

@ -26,6 +26,11 @@ cCreeper::cCreeper(void) :
void cCreeper::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cCreeper::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if ((GetTarget() == nullptr) || (!TargetIsInRange() && !m_BurnedWithFlintAndSteel)) if ((GetTarget() == nullptr) || (!TargetIsInRange() && !m_BurnedWithFlintAndSteel))
{ {

View File

@ -189,6 +189,11 @@ bool cEnderman::CheckLight()
void cEnderman::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cEnderman::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
// TODO take damage in rain // TODO take damage in rain

View File

@ -35,6 +35,11 @@ cHorse::cHorse(int Type, int Color, int Style, int TameTimes) :
void cHorse::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cHorse::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (!m_bIsMouthOpen) if (!m_bIsMouthOpen)
{ {

View File

@ -231,6 +231,11 @@ void cMonster::StopMovingToPosition()
void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
GET_AND_VERIFY_CURRENT_CHUNK(Chunk, POSX_TOINT, POSZ_TOINT); GET_AND_VERIFY_CURRENT_CHUNK(Chunk, POSX_TOINT, POSZ_TOINT);
ASSERT((GetTarget() == nullptr) || (GetTarget()->IsPawn() && (GetTarget()->GetWorld() == GetWorld()))); ASSERT((GetTarget() == nullptr) || (GetTarget()->IsPawn() && (GetTarget()->GetWorld() == GetWorld())));

View File

@ -81,6 +81,11 @@ void cPassiveMonster::Destroyed()
void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (m_EMState == ESCAPING) if (m_EMState == ESCAPING)
{ {

View File

@ -85,6 +85,11 @@ void cPig::OnRightClicked(cPlayer & a_Player)
void cPig::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cPig::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
// If the attachee player is holding a carrot-on-stick, let them drive this pig: // If the attachee player is holding a carrot-on-stick, let them drive this pig:
if (m_bIsSaddled && (m_Attachee != nullptr)) if (m_bIsSaddled && (m_Attachee != nullptr))

View File

@ -88,6 +88,11 @@ void cSheep::OnRightClicked(cPlayer & a_Player)
void cSheep::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cSheep::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
int PosX = POSX_TOINT; int PosX = POSX_TOINT;
int PosY = POSY_TOINT - 1; int PosY = POSY_TOINT - 1;
int PosZ = POSZ_TOINT; int PosZ = POSZ_TOINT;

View File

@ -30,6 +30,11 @@ void cSnowGolem::GetDrops(cItems & a_Drops, cEntity * a_Killer)
void cSnowGolem::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cSnowGolem::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (IsBiomeNoDownfall(m_World->GetBiomeAt(POSX_TOINT, POSZ_TOINT))) if (IsBiomeNoDownfall(m_World->GetBiomeAt(POSX_TOINT, POSZ_TOINT)))
{ {
TakeDamage(*this); TakeDamage(*this);

View File

@ -54,6 +54,11 @@ bool cVillager::DoTakeDamage(TakeDamageInfo & a_TDI)
void cVillager::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cVillager::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (m_ActionCountDown > -1) if (m_ActionCountDown > -1)
{ {

View File

@ -69,6 +69,11 @@ bool cWither::DoTakeDamage(TakeDamageInfo & a_TDI)
void cWither::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) void cWither::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{ {
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (m_WitherInvulnerableTicks > 0) if (m_WitherInvulnerableTicks > 0)
{ {

View File

@ -263,6 +263,12 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
super::Tick(a_Dt, a_Chunk); super::Tick(a_Dt, a_Chunk);
} }
if (!IsTicking())
{
// The base class tick destroyed us
return;
}
if (GetTarget() == nullptr) if (GetTarget() == nullptr)
{ {
cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance)); cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance));

View File

@ -9,6 +9,7 @@ SET (SRCS
Packetizer.cpp Packetizer.cpp
Protocol18x.cpp Protocol18x.cpp
Protocol19x.cpp Protocol19x.cpp
Protocol110x.cpp
ProtocolRecognizer.cpp ProtocolRecognizer.cpp
) )
@ -20,12 +21,14 @@ SET (HDRS
Protocol.h Protocol.h
Protocol18x.h Protocol18x.h
Protocol19x.h Protocol19x.h
Protocol110x.h
ProtocolRecognizer.h ProtocolRecognizer.h
) )
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set_source_files_properties(Protocol19x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum -Wno-error=switch") set_source_files_properties(Protocol19x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum -Wno-error=switch")
set_source_files_properties(Protocol18x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum -Wno-error=switch") set_source_files_properties(Protocol18x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch-enum -Wno-error=switch")
set_source_files_properties(Protocol110x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch")
endif() endif()
if (NOT MSVC) if (NOT MSVC)

View File

@ -114,7 +114,7 @@ public:
virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) = 0; virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) = 0;
virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) = 0; virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) = 0;
virtual void SendResetTitle (void) = 0; virtual void SendResetTitle (void) = 0;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) = 0; virtual void SendRespawn (eDimension a_Dimension) = 0;
virtual void SendExperience (void) = 0; virtual void SendExperience (void) = 0;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) = 0; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) = 0;
virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) = 0; virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) = 0;

View File

@ -0,0 +1,879 @@
// Protocol110x.cpp
/*
Implements the 1.10.x protocol classes:
- cProtocol1100
- release 1.10.0 protocol (#210)
(others may be added later in the future for the 1.10 release series)
*/
#include "Globals.h"
#include "Protocol110x.h"
#include "Packetizer.h"
#include "../Root.h"
#include "../Server.h"
#include "../Entities/Boat.h"
#include "../Entities/ExpOrb.h"
#include "../Entities/Minecart.h"
#include "../Entities/FallingBlock.h"
#include "../Entities/Painting.h"
#include "../Entities/Pickup.h"
#include "../Entities/Player.h"
#include "../Entities/ItemFrame.h"
#include "../Entities/ArrowEntity.h"
#include "../Entities/FireworkEntity.h"
#include "../Entities/SplashPotionEntity.h"
#include "../Mobs/IncludeAllMonsters.h"
#include "Bindings/PluginManager.h"
// The disabled error is intended, since the Metadata have overlapping indexes
// based on the type of the Entity.
//
// IMPORTANT: The enum is used to automate the sequential counting of the
// Metadata indexes. Adding a new enum value causes the following values to
// increase their index. Therefore the ordering of the enum values is VERY important!
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wduplicate-enum"
namespace Metadata
{
enum Metadata_Index
{
// Entity
ENTITY_FLAGS,
ENTITY_AIR,
ENTITY_CUSTOM_NAME,
ENTITY_CUSTOM_NAME_VISIBLE,
ENTITY_SILENT,
ENTITY_NO_GRAVITY,
_ENTITY_NEXT, // Used by descendants
// Potion
POTION_THROWN = _ENTITY_NEXT,
// FallingBlock
FALLING_BLOCK_POSITION = _ENTITY_NEXT,
// AreaEffectCloud
AREA_EFFECT_CLOUD_RADIUS = _ENTITY_NEXT,
AREA_EFFECT_CLOUD_COLOR,
AREA_EFFECT_CLOUD_SINGLE_POINT_EFFECT,
AREA_EFFECT_CLOUD_PARTICLE_ID,
AREA_EFFECT_CLOUD_PARTICLE_PARAMETER1,
AREA_EFFECT_CLOUD_PARTICLE_PARAMETER2,
// Arrow
ARROW_CRITICAL = _ENTITY_NEXT,
_ARROW_NEXT,
// TippedArrow
TIPPED_ARROW_COLOR = _ARROW_NEXT,
// Boat
BOAT_LAST_HIT_TIME = _ENTITY_NEXT,
BOAT_FORWARD_DIRECTION,
BOAT_DAMAGE_TAKEN,
BOAT_TYPE,
BOAT_RIGHT_PADDLE_TURNING,
BOAT_LEFT_PADDLE_TURNING,
// EnderCrystal
ENDER_CRYSTAL_BEAM_TARGET = _ENTITY_NEXT,
ENDER_CRYSTAL_SHOW_BOTTOM,
// Fireball
_FIREBALL_NEXT = _ENTITY_NEXT,
// WitherSkull
WITHER_SKULL_INVULNERABLE = _FIREBALL_NEXT,
// Fireworks
FIREWORK_INFO = _ENTITY_NEXT,
// Hanging
_HANGING_NEXT = _ENTITY_NEXT,
// ItemFrame
ITEM_FRAME_ITEM = _HANGING_NEXT,
ITEM_FRAME_ROTATION,
// Item
ITEM_ITEM = _ENTITY_NEXT,
// Living
LIVING_ACTIVE_HAND = _ENTITY_NEXT,
LIVING_HEALTH,
LIVING_POTION_EFFECT_COLOR,
LIVING_POTION_EFFECT_AMBIENT,
LIVING_NUMBER_OF_ARROWS,
_LIVING_NEXT,
// Player
PLAYER_ADDITIONAL_HEARTHS = _LIVING_NEXT,
PLAYER_SCORE,
PLAYER_DISPLAYED_SKIN_PARTS,
PLAYER_MAIN_HAND,
// ArmorStand
ARMOR_STAND_STATUS = _LIVING_NEXT,
ARMOR_STAND_HEAD_ROTATION,
ARMOR_STAND_BODY_ROTATION,
ARMOR_STAND_LEFT_ARM_ROTATION,
ARMOR_STAND_RIGHT_ARM_ROTATION,
ARMOR_STAND_LEFT_LEG_ROTATION,
ARMOR_STAND_RIGHT_LEG_ROTATION,
// Insentient
INSENTIENT_STATUS = _LIVING_NEXT,
_INSENTIENT_NEXT,
// Ambient
_AMBIENT_NEXT = _INSENTIENT_NEXT,
// Bat
BAT_HANGING = _AMBIENT_NEXT,
// Creature
_CREATURE_NEXT = _INSENTIENT_NEXT,
// Ageable
AGEABLE_BABY = _CREATURE_NEXT,
_AGEABLE_NEXT,
// PolarBear
POLAR_BEAR_STANDING = _AGEABLE_NEXT,
// Animal
_ANIMAL_NEXT = _AGEABLE_NEXT,
// Horse
HORSE_STATUS = _ANIMAL_NEXT,
HORSE_TYPE,
HORSE_VARIANT,
HORSE_OWNER,
HORSE_ARMOR,
// Pig
PIG_HAS_SADDLE = _ANIMAL_NEXT,
// Rabbit
RABBIT_TYPE = _ANIMAL_NEXT,
// Sheep
SHEEP_STATUS = _ANIMAL_NEXT,
// TameableAnimal
TAMEABLE_ANIMAL_STATUS = _ANIMAL_NEXT,
TAMEABLE_ANIMAL_OWNER,
_TAMEABLE_NEXT,
// Ocelot
OCELOT_TYPE = _TAMEABLE_NEXT,
// Wolf
WOLF_DAMAGE_TAKEN = _TAMEABLE_NEXT,
WOLF_BEGGING,
WOLF_COLLAR_COLOR,
// Villager
VILLAGER_PROFESSION = _AGEABLE_NEXT,
// Golem
_GOLEM_NEXT = _CREATURE_NEXT,
// IronGolem
IRON_GOLEM_PLAYER_CREATED = _GOLEM_NEXT,
// Shulker
SHULKER_FACING_DIRECTION = _GOLEM_NEXT,
SHULKER_ATTACHMENT_FALLING_BLOCK_POSITION,
SHULKER_SHIELD_HEIGHT,
// Monster
_MONSTER_NEXT = _CREATURE_NEXT,
// Blaze
BLAZE_ON_FIRE = _MONSTER_NEXT,
// Creeper
CREEPER_STATE = _MONSTER_NEXT,
CREEPER_POWERED,
CREEPER_IGNITED,
// Guardian
GUARDIAN_STATUS = _MONSTER_NEXT,
GUARDIAN_TARGET,
// Skeleton
SKELETON_TYPE = _MONSTER_NEXT,
SKELETON_ARMS_SWINGING,
// Spider
SPIDER_CLIMBING = _MONSTER_NEXT,
// Witch
WITCH_AGGRESIVE = _MONSTER_NEXT,
// Wither
WITHER_FIRST_HEAD_TARGET = _MONSTER_NEXT,
WITHER_SECOND_HEAD_TARGET,
WITHER_THIRD_HEAD_TARGET,
WITHER_INVULNERABLE_TIMER,
// Zombie
ZOMBIE_IS_BABY = _MONSTER_NEXT,
ZOMBIE_TYPE,
ZOMBIE_CONVERTING,
ZOMBIE_HANDS_RISED_UP,
// Enderman
ENDERMAN_CARRIED_BLOCK = _MONSTER_NEXT,
ENDERMAN_SCREAMING,
// EnderDragon
ENDER_DRAGON_DRAGON_PHASE = _INSENTIENT_NEXT,
// Flying
_FLYING_NEXT = _INSENTIENT_NEXT,
// Ghast
GHAST_ATTACKING = _FLYING_NEXT,
// Slime
SLIME_SIZE = _INSENTIENT_NEXT,
// Minecart
MINECART_SHAKING_POWER = _ENTITY_NEXT,
MINECART_SHAKING_DIRECTION,
MINECART_SHAKING_MULTIPLIER,
MINECART_BLOCK_ID_META,
MINECART_BLOCK_Y,
MINECART_SHOW_BLOCK,
_MINECART_NEXT,
// MinecartCommandBlock
MINECART_COMMAND_BLOCK_COMMAND = _MINECART_NEXT,
MINECART_COMMAND_BLOCK_LAST_OUTPUT,
// MinecartFurnace
MINECART_FURNACE_POWERED = _MINECART_NEXT,
// TNTPrimed
TNT_PRIMED_FUSE_TIME = _ENTITY_NEXT,
};
}
#pragma clang diagnostic pop // Restore ignored clang errors
cProtocol1100::cProtocol1100(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) :
super(a_Client, a_ServerAddress, a_ServerPort, a_State)
{
}
void cProtocol1100::SendSoundEffect(const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch)
{
ASSERT(m_State == 3); // In game mode?
cPacketizer Pkt(*this, 0x19); // Named sound effect packet
Pkt.WriteString(a_SoundName);
Pkt.WriteVarInt32(0); // Master sound category (may want to be changed to a parameter later)
Pkt.WriteBEInt32(FloorC(a_X * 8.0));
Pkt.WriteBEInt32(FloorC(a_Y * 8.0));
Pkt.WriteBEInt32(FloorC(a_Z * 8.0));
Pkt.WriteBEFloat(a_Volume);
Pkt.WriteBEFloat(a_Pitch);
}
void cProtocol1100::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer)
{
cServer * Server = cRoot::Get()->GetServer();
AString ServerDescription = Server->GetDescription();
int NumPlayers = Server->GetNumPlayers();
int MaxPlayers = Server->GetMaxPlayers();
AString Favicon = Server->GetFaviconData();
cRoot::Get()->GetPluginManager()->CallHookServerPing(*m_Client, ServerDescription, NumPlayers, MaxPlayers, Favicon);
// Version:
Json::Value Version;
Version["name"] = "Cuberite 1.10";
Version["protocol"] = 210;
// Players:
Json::Value Players;
Players["online"] = NumPlayers;
Players["max"] = MaxPlayers;
// TODO: Add "sample"
// Description:
Json::Value Description;
Description["text"] = ServerDescription.c_str();
// Create the response:
Json::Value ResponseValue;
ResponseValue["version"] = Version;
ResponseValue["players"] = Players;
ResponseValue["description"] = Description;
if (!Favicon.empty())
{
ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str());
}
Json::StyledWriter Writer;
AString Response = Writer.write(ResponseValue);
cPacketizer Pkt(*this, 0x00); // Response packet
Pkt.WriteString(Response);
}
void cProtocol1100::WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity)
{
using namespace Metadata;
// Common metadata:
Int8 Flags = 0;
if (a_Entity.IsOnFire())
{
Flags |= 0x01;
}
if (a_Entity.IsCrouched())
{
Flags |= 0x02;
}
if (a_Entity.IsSprinting())
{
Flags |= 0x08;
}
if (a_Entity.IsRclking())
{
Flags |= 0x10;
}
if (a_Entity.IsInvisible())
{
Flags |= 0x20;
}
a_Pkt.WriteBEUInt8(ENTITY_FLAGS); // Index
a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); // Type
a_Pkt.WriteBEInt8(Flags);
switch (a_Entity.GetEntityType())
{
case cEntity::etPlayer:
{
auto & Player = reinterpret_cast<const cPlayer &>(a_Entity);
// TODO Set player custom name to their name.
// Then it's possible to move the custom name of mobs to the entities
// and to remove the "special" player custom name.
a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME);
a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING);
a_Pkt.WriteString(Player.GetName());
a_Pkt.WriteBEUInt8(LIVING_HEALTH);
a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT);
a_Pkt.WriteBEFloat(static_cast<float>(Player.GetHealth()));
break;
}
case cEntity::etPickup:
{
a_Pkt.WriteBEUInt8(ITEM_ITEM);
a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM);
WriteItem(a_Pkt, reinterpret_cast<const cPickup &>(a_Entity).GetItem());
break;
}
case cEntity::etMinecart:
{
a_Pkt.WriteBEUInt8(MINECART_SHAKING_POWER);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
// The following expression makes Minecarts shake more with less health or higher damage taken
auto & Minecart = reinterpret_cast<const cMinecart &>(a_Entity);
auto maxHealth = a_Entity.GetMaxHealth();
auto curHealth = a_Entity.GetHealth();
a_Pkt.WriteVarInt32(static_cast<UInt32>((maxHealth - curHealth) * Minecart.LastDamage() * 4));
a_Pkt.WriteBEUInt8(MINECART_SHAKING_DIRECTION); // (doesn't seem to effect anything)
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(1);
a_Pkt.WriteBEUInt8(MINECART_SHAKING_MULTIPLIER); // or damage taken
a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT);
a_Pkt.WriteBEFloat(static_cast<float>(Minecart.LastDamage() + 10));
if (Minecart.GetPayload() == cMinecart::mpNone)
{
auto & RideableMinecart = reinterpret_cast<const cRideableMinecart &>(Minecart);
const cItem & MinecartContent = RideableMinecart.GetContent();
if (!MinecartContent.IsEmpty())
{
a_Pkt.WriteBEUInt8(MINECART_BLOCK_ID_META);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
int Content = MinecartContent.m_ItemType;
Content |= MinecartContent.m_ItemDamage << 8;
a_Pkt.WriteVarInt32(static_cast<UInt32>(Content));
a_Pkt.WriteBEUInt8(MINECART_BLOCK_Y);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(static_cast<UInt32>(RideableMinecart.GetBlockHeight()));
a_Pkt.WriteBEUInt8(MINECART_SHOW_BLOCK);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(true);
}
}
else if (Minecart.GetPayload() == cMinecart::mpFurnace)
{
a_Pkt.WriteBEUInt8(MINECART_FURNACE_POWERED);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(reinterpret_cast<const cMinecartWithFurnace &>(Minecart).IsFueled());
}
break;
} // case etMinecart
case cEntity::etProjectile:
{
auto & Projectile = reinterpret_cast<const cProjectileEntity &>(a_Entity);
switch (Projectile.GetProjectileKind())
{
case cProjectileEntity::pkArrow:
{
a_Pkt.WriteBEUInt8(ARROW_CRITICAL);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE);
a_Pkt.WriteBEInt8(reinterpret_cast<const cArrowEntity &>(Projectile).IsCritical() ? 1 : 0);
break;
}
case cProjectileEntity::pkFirework:
{
a_Pkt.WriteBEUInt8(FIREWORK_INFO); // Firework item used for this firework
a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM);
WriteItem(a_Pkt, reinterpret_cast<const cFireworkEntity &>(Projectile).GetItem());
break;
}
case cProjectileEntity::pkSplashPotion:
{
a_Pkt.WriteBEUInt8(POTION_THROWN); // Potion item which was thrown
a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM);
WriteItem(a_Pkt, reinterpret_cast<const cSplashPotionEntity &>(Projectile).GetItem());
}
default:
{
break;
}
}
break;
} // case etProjectile
case cEntity::etMonster:
{
WriteMobMetadata(a_Pkt, reinterpret_cast<const cMonster &>(a_Entity));
break;
}
case cEntity::etBoat:
{
auto & Boat = reinterpret_cast<const cBoat &>(a_Entity);
a_Pkt.WriteBEInt8(BOAT_LAST_HIT_TIME);
a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteBEInt32(Boat.GetLastDamage());
a_Pkt.WriteBEInt8(BOAT_FORWARD_DIRECTION);
a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteBEInt32(Boat.GetForwardDirection());
a_Pkt.WriteBEInt8(BOAT_DAMAGE_TAKEN);
a_Pkt.WriteBEInt8(METADATA_TYPE_FLOAT);
a_Pkt.WriteBEFloat(Boat.GetDamageTaken());
a_Pkt.WriteBEInt8(BOAT_TYPE);
a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteBEInt32(Boat.GetType());
a_Pkt.WriteBEInt8(BOAT_RIGHT_PADDLE_TURNING);
a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Boat.IsRightPaddleUsed());
a_Pkt.WriteBEInt8(BOAT_LEFT_PADDLE_TURNING);
a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Boat.IsLeftPaddleUsed());
break;
} // case etBoat
case cEntity::etItemFrame:
{
auto & Frame = reinterpret_cast<const cItemFrame &>(a_Entity);
a_Pkt.WriteBEUInt8(ITEM_FRAME_ITEM);
a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM);
WriteItem(a_Pkt, Frame.GetItem());
a_Pkt.WriteBEUInt8(ITEM_FRAME_ROTATION);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(Frame.GetItemRotation());
break;
} // case etItemFrame
default:
{
break;
}
}
}
void cProtocol1100::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob)
{
using namespace Metadata;
// Living Enitiy Metadata
if (a_Mob.HasCustomName())
{
// TODO: As of 1.9 _all_ entities can have custom names; should this be moved up?
a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME);
a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING);
a_Pkt.WriteString(a_Mob.GetCustomName());
a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME_VISIBLE); // Custom name always visible
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(a_Mob.IsCustomNameAlwaysVisible());
}
a_Pkt.WriteBEUInt8(LIVING_HEALTH);
a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT);
a_Pkt.WriteBEFloat(static_cast<float>(a_Mob.GetHealth()));
switch (a_Mob.GetMobType())
{
case mtBat:
{
auto & Bat = reinterpret_cast<const cBat &>(a_Mob);
a_Pkt.WriteBEUInt8(BAT_HANGING);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE);
a_Pkt.WriteBEInt8(Bat.IsHanging() ? 1 : 0);
break;
} // case mtBat
case mtCreeper:
{
auto & Creeper = reinterpret_cast<const cCreeper &>(a_Mob);
a_Pkt.WriteBEUInt8(CREEPER_STATE); // (idle or "blowing")
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(Creeper.IsBlowing() ? 1 : static_cast<UInt32>(-1));
a_Pkt.WriteBEUInt8(CREEPER_POWERED);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Creeper.IsCharged());
a_Pkt.WriteBEUInt8(CREEPER_IGNITED);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Creeper.IsBurnedWithFlintAndSteel());
break;
} // case mtCreeper
case mtEnderman:
{
auto & Enderman = reinterpret_cast<const cEnderman &>(a_Mob);
a_Pkt.WriteBEUInt8(ENDERMAN_CARRIED_BLOCK);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BLOCKID);
UInt32 Carried = 0;
Carried |= static_cast<UInt32>(Enderman.GetCarriedBlock() << 4);
Carried |= Enderman.GetCarriedMeta();
a_Pkt.WriteVarInt32(Carried);
a_Pkt.WriteBEUInt8(ENDERMAN_SCREAMING);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Enderman.IsScreaming());
break;
} // case mtEnderman
case mtGhast:
{
auto & Ghast = reinterpret_cast<const cGhast &>(a_Mob);
a_Pkt.WriteBEUInt8(GHAST_ATTACKING);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Ghast.IsCharging());
break;
} // case mtGhast
case mtHorse:
{
auto & Horse = reinterpret_cast<const cHorse &>(a_Mob);
Int8 Flags = 0;
if (Horse.IsTame())
{
Flags |= 0x02;
}
if (Horse.IsSaddled())
{
Flags |= 0x04;
}
if (Horse.IsChested())
{
Flags |= 0x08;
}
if (Horse.IsEating())
{
Flags |= 0x20;
}
if (Horse.IsRearing())
{
Flags |= 0x40;
}
if (Horse.IsMthOpen())
{
Flags |= 0x80;
}
a_Pkt.WriteBEUInt8(HORSE_STATUS);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE);
a_Pkt.WriteBEInt8(Flags);
a_Pkt.WriteBEUInt8(HORSE_TYPE);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(static_cast<UInt32>(Horse.GetHorseType()));
a_Pkt.WriteBEUInt8(HORSE_VARIANT); // Color / style
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
int Appearance = 0;
Appearance = Horse.GetHorseColor();
Appearance |= Horse.GetHorseStyle() << 8;
a_Pkt.WriteVarInt32(static_cast<UInt32>(Appearance));
a_Pkt.WriteBEUInt8(HORSE_ARMOR);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(static_cast<UInt32>(Horse.GetHorseArmour()));
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Horse.IsBaby());
break;
} // case mtHorse
case mtMagmaCube:
{
auto & MagmaCube = reinterpret_cast<const cMagmaCube &>(a_Mob);
a_Pkt.WriteBEUInt8(SLIME_SIZE);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(static_cast<UInt32>(MagmaCube.GetSize()));
break;
} // case mtMagmaCube
case mtOcelot:
{
auto & Ocelot = reinterpret_cast<const cOcelot &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Ocelot.IsBaby());
break;
} // case mtOcelot
case mtCow:
{
auto & Cow = reinterpret_cast<const cCow &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Cow.IsBaby());
break;
} // case mtCow
case mtChicken:
{
auto & Chicken = reinterpret_cast<const cChicken &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Chicken.IsBaby());
break;
} // case mtChicken
case mtPig:
{
auto & Pig = reinterpret_cast<const cPig &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Pig.IsBaby());
a_Pkt.WriteBEUInt8(PIG_HAS_SADDLE);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Pig.IsSaddled());
break;
} // case mtPig
case mtSheep:
{
auto & Sheep = reinterpret_cast<const cSheep &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Sheep.IsBaby());
a_Pkt.WriteBEUInt8(SHEEP_STATUS);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE);
Int8 SheepMetadata = 0;
SheepMetadata = static_cast<Int8>(Sheep.GetFurColor());
if (Sheep.IsSheared())
{
SheepMetadata |= 0x10;
}
a_Pkt.WriteBEInt8(SheepMetadata);
break;
} // case mtSheep
case mtRabbit:
{
auto & Rabbit = reinterpret_cast<const cRabbit &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Rabbit.IsBaby());
a_Pkt.WriteBEUInt8(RABBIT_TYPE);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(static_cast<UInt32>(Rabbit.GetRabbitType()));
break;
} // case mtRabbit
case mtSkeleton:
{
auto & Skeleton = reinterpret_cast<const cSkeleton &>(a_Mob);
a_Pkt.WriteBEUInt8(SKELETON_TYPE);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(Skeleton.IsWither() ? 1 : 0);
break;
} // case mtSkeleton
case mtSlime:
{
auto & Slime = reinterpret_cast<const cSlime &>(a_Mob);
a_Pkt.WriteBEUInt8(SLIME_SIZE);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(static_cast<UInt32>(Slime.GetSize()));
break;
} // case mtSlime
case mtVillager:
{
auto & Villager = reinterpret_cast<const cVillager &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Villager.IsBaby());
a_Pkt.WriteBEUInt8(VILLAGER_PROFESSION);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(static_cast<UInt32>(Villager.GetVilType()));
break;
} // case mtVillager
case mtWitch:
{
auto & Witch = reinterpret_cast<const cWitch &>(a_Mob);
a_Pkt.WriteBEUInt8(WITCH_AGGRESIVE);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Witch.IsAngry());
break;
} // case mtWitch
case mtWither:
{
auto & Wither = reinterpret_cast<const cWither &>(a_Mob);
a_Pkt.WriteBEUInt8(WITHER_INVULNERABLE_TIMER);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(Wither.GetWitherInvulnerableTicks());
// TODO: Use boss bar packet for health
break;
} // case mtWither
case mtWolf:
{
auto & Wolf = reinterpret_cast<const cWolf &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Wolf.IsBaby());
Int8 WolfStatus = 0;
if (Wolf.IsSitting())
{
WolfStatus |= 0x1;
}
if (Wolf.IsAngry())
{
WolfStatus |= 0x2;
}
if (Wolf.IsTame())
{
WolfStatus |= 0x4;
}
a_Pkt.WriteBEUInt8(TAMEABLE_ANIMAL_STATUS);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE);
a_Pkt.WriteBEInt8(WolfStatus);
a_Pkt.WriteBEUInt8(WOLF_DAMAGE_TAKEN);
a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT);
a_Pkt.WriteBEFloat(static_cast<float>(a_Mob.GetHealth())); // TODO Not use the current health
a_Pkt.WriteBEUInt8(WOLF_BEGGING);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Wolf.IsBegging());
a_Pkt.WriteBEUInt8(WOLF_COLLAR_COLOR);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(static_cast<UInt32>(Wolf.GetCollarColor()));
break;
} // case mtWolf
case mtZombie:
{
auto & Zombie = reinterpret_cast<const cZombie &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Zombie.IsBaby());
a_Pkt.WriteBEUInt8(ZOMBIE_TYPE);
a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT);
a_Pkt.WriteVarInt32(Zombie.IsVillagerZombie() ? 1 : 0); // TODO: This actually encodes the zombie villager profession, but that isn't implemented yet.
a_Pkt.WriteBEUInt8(ZOMBIE_CONVERTING);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(Zombie.IsConverting());
break;
} // case mtZombie
case mtZombiePigman:
{
auto & ZombiePigman = reinterpret_cast<const cZombiePigman &>(a_Mob);
a_Pkt.WriteBEUInt8(AGEABLE_BABY);
a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL);
a_Pkt.WriteBool(ZombiePigman.IsBaby());
break;
} // case mtZombiePigman
} // switch (a_Mob.GetType())
}

View File

@ -0,0 +1,34 @@
// Protocol110x.h
/*
Declares the 1.10.x protocol classes:
- cProtocol1100
- release 1.10.0 protocol (#210)
(others may be added later in the future for the 1.10 release series)
*/
#pragma once
#include "Protocol19x.h"
class cProtocol1100 :
public cProtocol194
{
typedef cProtocol194 super;
public:
cProtocol1100(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State);
virtual void SendSoundEffect(const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) override;
virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override;
protected:
virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) override;
virtual void WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) override;
};

View File

@ -107,8 +107,7 @@ cProtocol180::cProtocol180(cClientHandle * a_Client, const AString & a_ServerAdd
m_ServerPort(a_ServerPort), m_ServerPort(a_ServerPort),
m_State(a_State), m_State(a_State),
m_ReceivedData(32 KiB), m_ReceivedData(32 KiB),
m_IsEncrypted(false), m_IsEncrypted(false)
m_LastSentDimension(dimNotSet)
{ {
// BungeeCord handling: // BungeeCord handling:
@ -626,7 +625,6 @@ void cProtocol180::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
Pkt.WriteString("default"); // Level type - wtf? Pkt.WriteString("default"); // Level type - wtf?
Pkt.WriteBool(false); // Reduced Debug Info - wtf? Pkt.WriteBool(false); // Reduced Debug Info - wtf?
} }
m_LastSentDimension = a_World.GetDimension();
// Send the spawn position: // Send the spawn position:
{ {
@ -1084,13 +1082,8 @@ void cProtocol180::SendResetTitle(void)
void cProtocol180::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) void cProtocol180::SendRespawn(eDimension a_Dimension)
{ {
if ((m_LastSentDimension == a_Dimension) && !a_ShouldIgnoreDimensionChecks)
{
// Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do (unless we are respawning from death)
return;
}
cPacketizer Pkt(*this, 0x07); // Respawn packet cPacketizer Pkt(*this, 0x07); // Respawn packet
cPlayer * Player = m_Client->GetPlayer(); cPlayer * Player = m_Client->GetPlayer();
@ -1098,7 +1091,6 @@ void cProtocol180::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimens
Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal) Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal)
Pkt.WriteBEUInt8(static_cast<Byte>(Player->GetEffectiveGameMode())); Pkt.WriteBEUInt8(static_cast<Byte>(Player->GetEffectiveGameMode()));
Pkt.WriteString("default"); Pkt.WriteString("default");
m_LastSentDimension = a_Dimension;
} }

View File

@ -110,7 +110,7 @@ public:
virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override; virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override;
virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override; virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override;
virtual void SendResetTitle (void) override; virtual void SendResetTitle (void) override;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override; virtual void SendRespawn (eDimension a_Dimension) override;
virtual void SendSoundEffect (const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) override; virtual void SendSoundEffect (const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) override;
virtual void SendExperience (void) override; virtual void SendExperience (void) override;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override;
@ -177,11 +177,6 @@ protected:
/** The logfile where the comm is logged, when g_ShouldLogComm is true */ /** The logfile where the comm is logged, when g_ShouldLogComm is true */
cFile m_CommLogFile; cFile m_CommLogFile;
/** The dimension that was last sent to a player in a Respawn or Login packet.
Used to avoid Respawning into the same dimension, which confuses the client. */
eDimension m_LastSentDimension;
/** Adds the received (unencrypted) data to m_ReceivedData, parses complete packets */ /** Adds the received (unencrypted) data to m_ReceivedData, parses complete packets */
void AddReceivedData(const char * a_Data, size_t a_Size); void AddReceivedData(const char * a_Data, size_t a_Size);

View File

@ -117,8 +117,7 @@ cProtocol190::cProtocol190(cClientHandle * a_Client, const AString & a_ServerAdd
m_ServerPort(a_ServerPort), m_ServerPort(a_ServerPort),
m_State(a_State), m_State(a_State),
m_ReceivedData(32 KiB), m_ReceivedData(32 KiB),
m_IsEncrypted(false), m_IsEncrypted(false)
m_LastSentDimension(dimNotSet)
{ {
// BungeeCord handling: // BungeeCord handling:
@ -640,7 +639,6 @@ void cProtocol190::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
Pkt.WriteString("default"); // Level type - wtf? Pkt.WriteString("default"); // Level type - wtf?
Pkt.WriteBool(false); // Reduced Debug Info - wtf? Pkt.WriteBool(false); // Reduced Debug Info - wtf?
} }
m_LastSentDimension = a_World.GetDimension();
// Send the spawn position: // Send the spawn position:
{ {
@ -741,7 +739,7 @@ void cProtocol190::SendPickupSpawn(const cPickup & a_Pickup)
{ {
ASSERT(m_State == 3); // In game mode? ASSERT(m_State == 3); // In game mode?
{ { // TODO Use SendSpawnObject
cPacketizer Pkt(*this, 0x00); // Spawn Object packet cPacketizer Pkt(*this, 0x00); // Spawn Object packet
Pkt.WriteVarInt32(a_Pickup.GetUniqueID()); Pkt.WriteVarInt32(a_Pickup.GetUniqueID());
// TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now. // TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now.
@ -759,14 +757,7 @@ void cProtocol190::SendPickupSpawn(const cPickup & a_Pickup)
Pkt.WriteBEInt16(0); Pkt.WriteBEInt16(0);
} }
{ SendEntityMetadata(a_Pickup);
cPacketizer Pkt(*this, 0x39); // Entity Metadata packet
Pkt.WriteVarInt32(a_Pickup.GetUniqueID());
Pkt.WriteBEUInt8(5); // Index 5: Item
Pkt.WriteBEUInt8(METADATA_TYPE_ITEM);
WriteItem(Pkt, a_Pickup.GetItem());
Pkt.WriteBEUInt8(0xff); // End of metadata
}
} }
@ -1059,12 +1050,7 @@ void cProtocol190::SendPlayerSpawn(const cPlayer & a_Player)
Pkt.WriteBEDouble(a_Player.GetPosZ()); Pkt.WriteBEDouble(a_Player.GetPosZ());
Pkt.WriteByteAngle(a_Player.GetYaw()); Pkt.WriteByteAngle(a_Player.GetYaw());
Pkt.WriteByteAngle(a_Player.GetPitch()); Pkt.WriteByteAngle(a_Player.GetPitch());
Pkt.WriteBEUInt8(6); // Start metadata - Index 6: Health WriteEntityMetadata(Pkt, a_Player);
Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT);
Pkt.WriteBEFloat(static_cast<float>(a_Player.GetHealth()));
Pkt.WriteBEUInt8(2); // Index 2: Custom name
Pkt.WriteBEUInt8(METADATA_TYPE_STRING);
Pkt.WriteString(a_Player.GetName());
Pkt.WriteBEUInt8(0xff); // Metadata: end Pkt.WriteBEUInt8(0xff); // Metadata: end
} }
@ -1110,21 +1096,14 @@ void cProtocol190::SendResetTitle(void)
void cProtocol190::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) void cProtocol190::SendRespawn(eDimension a_Dimension)
{ {
if ((m_LastSentDimension == a_Dimension) && !a_ShouldIgnoreDimensionChecks)
{
// Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do (unless we are respawning from death)
return;
}
cPacketizer Pkt(*this, 0x33); // Respawn packet cPacketizer Pkt(*this, 0x33); // Respawn packet
cPlayer * Player = m_Client->GetPlayer(); cPlayer * Player = m_Client->GetPlayer();
Pkt.WriteBEInt32(static_cast<Int32>(a_Dimension)); Pkt.WriteBEInt32(static_cast<Int32>(a_Dimension));
Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal) Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal)
Pkt.WriteBEUInt8(static_cast<Byte>(Player->GetEffectiveGameMode())); Pkt.WriteBEUInt8(static_cast<Byte>(Player->GetEffectiveGameMode()));
Pkt.WriteString("default"); Pkt.WriteString("default");
m_LastSentDimension = a_Dimension;
} }
@ -3529,7 +3508,22 @@ void cProtocol190::WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_En
switch (a_Entity.GetEntityType()) switch (a_Entity.GetEntityType())
{ {
case cEntity::etPlayer: break; // TODO? case cEntity::etPlayer:
{
auto & Player = reinterpret_cast<const cPlayer &>(a_Entity);
// TODO Set player custom name to their name.
// Then it's possible to move the custom name of mobs to the entities
// and to remove the "special" player custom name.
a_Pkt.WriteBEUInt8(2); // Index 2: Custom name
a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING);
a_Pkt.WriteString(Player.GetName());
a_Pkt.WriteBEUInt8(6); // Start metadata - Index 6: Health
a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT);
a_Pkt.WriteBEFloat(static_cast<float>(Player.GetHealth()));
break;
}
case cEntity::etPickup: case cEntity::etPickup:
{ {
a_Pkt.WriteBEUInt8(5); // Index 5: Item a_Pkt.WriteBEUInt8(5); // Index 5: Item
@ -4058,7 +4052,6 @@ void cProtocol191::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
Pkt.WriteString("default"); // Level type - wtf? Pkt.WriteString("default"); // Level type - wtf?
Pkt.WriteBool(false); // Reduced Debug Info - wtf? Pkt.WriteBool(false); // Reduced Debug Info - wtf?
} }
m_LastSentDimension = a_World.GetDimension();
// Send the spawn position: // Send the spawn position:
{ {
@ -4377,8 +4370,3 @@ void cProtocol194::SendUpdateSign(int a_BlockX, int a_BlockY, int a_BlockZ, cons
Writer.Finish(); Writer.Finish();
Pkt.WriteBuf(Writer.GetResult().data(), Writer.GetResult().size()); Pkt.WriteBuf(Writer.GetResult().data(), Writer.GetResult().size());
} }

View File

@ -116,7 +116,7 @@ public:
virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override; virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override;
virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override; virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override;
virtual void SendResetTitle (void) override; virtual void SendResetTitle (void) override;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override; virtual void SendRespawn (eDimension a_Dimension) override;
virtual void SendSoundEffect (const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) override; virtual void SendSoundEffect (const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) override;
virtual void SendExperience (void) override; virtual void SendExperience (void) override;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override;
@ -183,11 +183,6 @@ protected:
/** The logfile where the comm is logged, when g_ShouldLogComm is true */ /** The logfile where the comm is logged, when g_ShouldLogComm is true */
cFile m_CommLogFile; cFile m_CommLogFile;
/** The dimension that was last sent to a player in a Respawn or Login packet.
Used to avoid Respawning into the same dimension, which confuses the client. */
eDimension m_LastSentDimension;
/** Adds the received (unencrypted) data to m_ReceivedData, parses complete packets */ /** Adds the received (unencrypted) data to m_ReceivedData, parses complete packets */
void AddReceivedData(const char * a_Data, size_t a_Size); void AddReceivedData(const char * a_Data, size_t a_Size);
@ -264,10 +259,10 @@ protected:
void WriteItem(cPacketizer & a_Pkt, const cItem & a_Item); void WriteItem(cPacketizer & a_Pkt, const cItem & a_Item);
/** Writes the metadata for the specified entity, not including the terminating 0xff. */ /** Writes the metadata for the specified entity, not including the terminating 0xff. */
void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity); virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity);
/** Writes the mob-specific metadata for the specified mob */ /** Writes the mob-specific metadata for the specified mob */
void WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob); virtual void WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob);
/** Writes the entity properties for the specified entity, including the Count field. */ /** Writes the entity properties for the specified entity, including the Count field. */
void WriteEntityProperties(cPacketizer & a_Pkt, const cEntity & a_Entity); void WriteEntityProperties(cPacketizer & a_Pkt, const cEntity & a_Entity);

View File

@ -9,6 +9,7 @@
#include "ProtocolRecognizer.h" #include "ProtocolRecognizer.h"
#include "Protocol18x.h" #include "Protocol18x.h"
#include "Protocol19x.h" #include "Protocol19x.h"
#include "Protocol110x.h"
#include "Packetizer.h" #include "Packetizer.h"
#include "../ClientHandle.h" #include "../ClientHandle.h"
#include "../Root.h" #include "../Root.h"
@ -47,11 +48,12 @@ AString cProtocolRecognizer::GetVersionTextFromInt(int a_ProtocolVersion)
{ {
switch (a_ProtocolVersion) switch (a_ProtocolVersion)
{ {
case PROTO_VERSION_1_8_0: return "1.8"; case PROTO_VERSION_1_8_0: return "1.8";
case PROTO_VERSION_1_9_0: return "1.9"; case PROTO_VERSION_1_9_0: return "1.9";
case PROTO_VERSION_1_9_1: return "1.9.1"; case PROTO_VERSION_1_9_1: return "1.9.1";
case PROTO_VERSION_1_9_2: return "1.9.2"; case PROTO_VERSION_1_9_2: return "1.9.2";
case PROTO_VERSION_1_9_4: return "1.9.4"; case PROTO_VERSION_1_9_4: return "1.9.4";
case PROTO_VERSION_1_10_0: return "1.10";
} }
ASSERT(!"Unknown protocol version"); ASSERT(!"Unknown protocol version");
return Printf("Unknown protocol (%d)", a_ProtocolVersion); return Printf("Unknown protocol (%d)", a_ProtocolVersion);
@ -635,10 +637,10 @@ void cProtocolRecognizer::SendResetTitle(void)
void cProtocolRecognizer::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) void cProtocolRecognizer::SendRespawn(eDimension a_Dimension)
{ {
ASSERT(m_Protocol != nullptr); ASSERT(m_Protocol != nullptr);
m_Protocol->SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks); m_Protocol->SendRespawn(a_Dimension);
} }
@ -1047,6 +1049,11 @@ bool cProtocolRecognizer::TryRecognizeLengthedProtocol(UInt32 a_PacketLengthRema
m_Protocol = new cProtocol194(m_Client, ServerAddress, ServerPort, NextState); m_Protocol = new cProtocol194(m_Client, ServerAddress, ServerPort, NextState);
return true; return true;
} }
case PROTO_VERSION_1_10_0:
{
m_Protocol = new cProtocol1100(m_Client, ServerAddress, ServerPort, NextState);
return true;
}
default: default:
{ {
LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))", LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))",

View File

@ -18,8 +18,8 @@
// Adjust these if a new protocol is added or an old one is removed: // Adjust these if a new protocol is added or an old one is removed:
#define MCS_CLIENT_VERSIONS "1.8.x, 1.9.x" #define MCS_CLIENT_VERSIONS "1.8.x, 1.9.x, 1.10.x"
#define MCS_PROTOCOL_VERSIONS "47, 107, 108, 109, 110" #define MCS_PROTOCOL_VERSIONS "47, 107, 108, 109, 110, 210"
@ -33,11 +33,12 @@ class cProtocolRecognizer :
public: public:
enum enum
{ {
PROTO_VERSION_1_8_0 = 47, PROTO_VERSION_1_8_0 = 47,
PROTO_VERSION_1_9_0 = 107, PROTO_VERSION_1_9_0 = 107,
PROTO_VERSION_1_9_1 = 108, PROTO_VERSION_1_9_1 = 108,
PROTO_VERSION_1_9_2 = 109, PROTO_VERSION_1_9_2 = 109,
PROTO_VERSION_1_9_4 = 110, PROTO_VERSION_1_9_4 = 110,
PROTO_VERSION_1_10_0 = 210,
} ; } ;
cProtocolRecognizer(cClientHandle * a_Client); cProtocolRecognizer(cClientHandle * a_Client);
@ -100,7 +101,7 @@ public:
virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override; virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override;
virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override; virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override;
virtual void SendResetTitle (void) override; virtual void SendResetTitle (void) override;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override; virtual void SendRespawn (eDimension a_Dimension) override;
virtual void SendExperience (void) override; virtual void SendExperience (void) override;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override;
virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override;

View File

@ -361,55 +361,6 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn
m_WorldsByName[ DefaultWorldName ] = m_pDefaultWorld; m_WorldsByName[ DefaultWorldName ] = m_pDefaultWorld;
auto Worlds = a_Settings.GetValues("Worlds"); auto Worlds = a_Settings.GetValues("Worlds");
// Fix servers that have default world configs created prior to #2815. See #2810.
// This can probably be removed several years after 2016
// We start by inspecting the world linkage and determining if it's the default one
if ((DefaultWorldName == "world") && (Worlds.size() == 1))
{
auto DefaultWorldIniFile= cpp14::make_unique<cIniFile>();
if (DefaultWorldIniFile->ReadFile("world/world.ini"))
{
AString NetherName = DefaultWorldIniFile->GetValue("LinkedWorlds", "NetherWorldName", "");
AString EndName = DefaultWorldIniFile->GetValue("LinkedWorlds", "EndWorldName", "");
if ((NetherName.compare("world_nether") == 0) && (EndName.compare("world_end") == 0))
{
// This is a default world linkage config, see if the nether and end are in settings.ini
// If both of them are not in settings.ini, then this is a pre-#2815 default config
// so we add them to settings.ini
// Note that if only one of them is not in settings.ini, it's nondefault and we don't touch it
bool NetherInSettings = false;
bool EndInSettings = false;
for (auto WorldNameValue : Worlds)
{
AString ValueName = WorldNameValue.first;
if (ValueName.compare("World") != 0)
{
continue;
}
AString WorldName = WorldNameValue.second;
if (WorldName.compare("world_nether") == 0)
{
NetherInSettings = true;
}
else if (WorldName.compare("world_end") == 0)
{
EndInSettings = true;
}
}
if ((!NetherInSettings) && (!EndInSettings))
{
a_Settings.AddValue("Worlds", "World", "world_nether");
a_Settings.AddValue("Worlds", "World", "world_end");
Worlds = a_Settings.GetValues("Worlds"); // Refresh the Worlds list so that the rest of the function works as usual
LOG("The server detected an old default config with bad world linkages. This has been autofixed by adding \"world_nether\" and \"world_end\" to settings.ini. If you do not want this autofix to trigger, please remove the nether and / or end from settings.ini and from world/world.ini");
}
}
}
}
// Then load the other worlds // Then load the other worlds
if (Worlds.size() <= 0) if (Worlds.size() <= 0)
{ {

View File

@ -149,7 +149,7 @@ cWorld::cWorld(const AString & a_WorldName, eDimension a_Dimension, const AStrin
m_WorldAge(0), m_WorldAge(0),
m_TimeOfDay(0), m_TimeOfDay(0),
m_LastTimeUpdate(0), m_LastTimeUpdate(0),
m_LastUnload(0), m_LastChunkCheck(0),
m_LastSave(0), m_LastSave(0),
m_SkyDarkness(0), m_SkyDarkness(0),
m_GameMode(gmNotSet), m_GameMode(gmNotSet),
@ -453,6 +453,13 @@ void cWorld::Start(void)
// The presence of a configuration value overrides everything // The presence of a configuration value overrides everything
// If no configuration value is found, GetDimension() is written to file and the variable is written to again to ensure that cosmic rays haven't sneakily changed its value // If no configuration value is found, GetDimension() is written to file and the variable is written to again to ensure that cosmic rays haven't sneakily changed its value
m_Dimension = StringToDimension(IniFile.GetValueSet("General", "Dimension", DimensionToString(GetDimension()))); m_Dimension = StringToDimension(IniFile.GetValueSet("General", "Dimension", DimensionToString(GetDimension())));
int UnusedDirtyChunksCap = IniFile.GetValueSetI("General", "UnusedChunkCap", 1000);
if (UnusedDirtyChunksCap < 0)
{
UnusedDirtyChunksCap *= -1;
IniFile.SetValueI("General", "UnusedChunkCap", UnusedDirtyChunksCap);
}
m_UnusedDirtyChunksCap = static_cast<size_t>(UnusedDirtyChunksCap);
m_BroadcastDeathMessages = IniFile.GetValueSetB("Broadcasting", "BroadcastDeathMessages", true); m_BroadcastDeathMessages = IniFile.GetValueSetB("Broadcasting", "BroadcastDeathMessages", true);
m_BroadcastAchievementMessages = IniFile.GetValueSetB("Broadcasting", "BroadcastAchievementMessages", true); m_BroadcastAchievementMessages = IniFile.GetValueSetB("Broadcasting", "BroadcastAchievementMessages", true);
@ -1057,16 +1064,22 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La
TickWeather(static_cast<float>(a_Dt.count())); TickWeather(static_cast<float>(a_Dt.count()));
if (m_WorldAge - m_LastSave > std::chrono::minutes(5)) // Save each 5 minutes if (m_WorldAge - m_LastChunkCheck > std::chrono::seconds(10))
{
SaveAllChunks();
}
if (m_WorldAge - m_LastUnload > std::chrono::seconds(10)) // Unload every 10 seconds
{ {
// Unload every 10 seconds
UnloadUnusedChunks(); UnloadUnusedChunks();
}
if (m_WorldAge - m_LastSave > std::chrono::minutes(5))
{
// Save every 5 minutes
SaveAllChunks();
}
else if (GetNumUnusedDirtyChunks() > m_UnusedDirtyChunksCap)
{
// Save if we have too many dirty unused chunks
SaveAllChunks();
}
}
} }
@ -2964,7 +2977,7 @@ bool cWorld::HasChunkAnyClients(int a_ChunkX, int a_ChunkZ) const
void cWorld::UnloadUnusedChunks(void) void cWorld::UnloadUnusedChunks(void)
{ {
m_LastUnload = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge); m_LastChunkCheck = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
m_ChunkMap->UnloadUnusedChunks(); m_ChunkMap->UnloadUnusedChunks();
} }
@ -3578,7 +3591,7 @@ unsigned int cWorld::GetNumPlayers(void)
int cWorld::GetNumChunks(void) const size_t cWorld::GetNumChunks(void) const
{ {
return m_ChunkMap->GetNumChunks(); return m_ChunkMap->GetNumChunks();
} }
@ -3587,6 +3600,15 @@ int cWorld::GetNumChunks(void) const
size_t cWorld::GetNumUnusedDirtyChunks(void) const
{
return m_ChunkMap->GetNumUnusedDirtyChunks();
}
void cWorld::GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue) void cWorld::GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue)
{ {
m_ChunkMap->GetChunkStats(a_NumValid, a_NumDirty); m_ChunkMap->GetChunkStats(a_NumValid, a_NumDirty);

View File

@ -683,7 +683,10 @@ public:
void ScheduleTask(int a_DelayTicks, std::function<void(cWorld &)> a_Task); void ScheduleTask(int a_DelayTicks, std::function<void(cWorld &)> a_Task);
/** Returns the number of chunks loaded */ /** Returns the number of chunks loaded */
int GetNumChunks() const; // tolua_export size_t GetNumChunks() const; // tolua_export
/** Returns the number of unused dirty chunks. That's the number of chunks that we can save and then unload. */
size_t GetNumUnusedDirtyChunks(void) const; // tolua_export
/** Returns the number of chunks loaded and dirty, and in the lighting queue */ /** Returns the number of chunks loaded and dirty, and in the lighting queue */
void GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue); void GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue);
@ -851,6 +854,11 @@ private:
} ; } ;
/** The maximum number of allowed unused dirty chunks for this world.
Loaded from config, enforced every 10 seconds by freeing some unused dirty chunks
if this was exceeded. */
size_t m_UnusedDirtyChunksCap;
AString m_WorldName; AString m_WorldName;
/** The name of the overworld that portals in this world should link to. /** The name of the overworld that portals in this world should link to.
@ -889,7 +897,7 @@ private:
std::chrono::milliseconds m_WorldAge; std::chrono::milliseconds m_WorldAge;
std::chrono::milliseconds m_TimeOfDay; std::chrono::milliseconds m_TimeOfDay;
cTickTimeLong m_LastTimeUpdate; // The tick in which the last time update has been sent. cTickTimeLong m_LastTimeUpdate; // The tick in which the last time update has been sent.
cTickTimeLong m_LastUnload; // The last WorldAge (in ticks) in which unloading was triggerred cTickTimeLong m_LastChunkCheck; // The last WorldAge (in ticks) in which unloading and possibly saving was triggered
cTickTimeLong m_LastSave; // The last WorldAge (in ticks) in which save-all was triggerred cTickTimeLong m_LastSave; // The last WorldAge (in ticks) in which save-all was triggerred
std::map<cMonster::eFamily, cTickTimeLong> m_LastSpawnMonster; // The last WorldAge (in ticks) in which a monster was spawned (for each megatype of monster) // MG TODO : find a way to optimize without creating unmaintenability (if mob IDs are becoming unrowed) std::map<cMonster::eFamily, cTickTimeLong> m_LastSpawnMonster; // The last WorldAge (in ticks) in which a monster was spawned (for each megatype of monster) // MG TODO : find a way to optimize without creating unmaintenability (if mob IDs are becoming unrowed)