summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMattes D <github@xoft.cz>2016-09-05 15:05:54 +0200
committerMattes D <github@xoft.cz>2016-09-05 15:05:54 +0200
commitf7029eddd4120f672c59a4b3a4395832f4de8863 (patch)
tree81c93ca7c5c03ebdde2401e86dd8c02665710384 /src
parentPluginLua: Removed unneeded assignment. (diff)
parentConfigurable dirty unused chunk cap to avoid RAM overuse (#3359) (diff)
downloadcuberite-f7029eddd4120f672c59a4b3a4395832f4de8863.tar
cuberite-f7029eddd4120f672c59a4b3a4395832f4de8863.tar.gz
cuberite-f7029eddd4120f672c59a4b3a4395832f4de8863.tar.bz2
cuberite-f7029eddd4120f672c59a4b3a4395832f4de8863.tar.lz
cuberite-f7029eddd4120f672c59a4b3a4395832f4de8863.tar.xz
cuberite-f7029eddd4120f672c59a4b3a4395832f4de8863.tar.zst
cuberite-f7029eddd4120f672c59a4b3a4395832f4de8863.zip
Diffstat (limited to 'src')
-rw-r--r--src/Blocks/BlockIce.h3
-rw-r--r--src/Chunk.cpp13
-rw-r--r--src/Chunk.h3
-rw-r--r--src/ChunkMap.cpp21
-rw-r--r--src/ChunkMap.h5
-rw-r--r--src/ClientHandle.cpp23
-rw-r--r--src/ClientHandle.h6
-rw-r--r--src/Entities/ArrowEntity.cpp5
-rw-r--r--src/Entities/Boat.cpp5
-rw-r--r--src/Entities/Entity.cpp47
-rw-r--r--src/Entities/FireworkEntity.cpp5
-rw-r--r--src/Entities/Minecart.cpp5
-rw-r--r--src/Entities/Pawn.cpp6
-rw-r--r--src/Entities/Pickup.cpp5
-rw-r--r--src/Entities/Player.cpp88
-rw-r--r--src/Entities/Player.h3
-rw-r--r--src/Entities/ProjectileEntity.cpp5
-rw-r--r--src/Entities/TNTEntity.cpp5
-rw-r--r--src/Mobs/AggressiveMonster.cpp5
-rw-r--r--src/Mobs/CaveSpider.cpp5
-rw-r--r--src/Mobs/Chicken.cpp5
-rw-r--r--src/Mobs/Creeper.cpp5
-rw-r--r--src/Mobs/Enderman.cpp5
-rw-r--r--src/Mobs/Horse.cpp5
-rw-r--r--src/Mobs/Monster.cpp5
-rw-r--r--src/Mobs/PassiveMonster.cpp5
-rw-r--r--src/Mobs/Pig.cpp5
-rw-r--r--src/Mobs/Sheep.cpp5
-rw-r--r--src/Mobs/SnowGolem.cpp5
-rw-r--r--src/Mobs/Villager.cpp5
-rw-r--r--src/Mobs/Wither.cpp5
-rw-r--r--src/Mobs/Wolf.cpp6
-rw-r--r--src/Protocol/CMakeLists.txt7
-rw-r--r--src/Protocol/Protocol.h2
-rw-r--r--src/Protocol/Protocol110x.cpp879
-rw-r--r--src/Protocol/Protocol110x.h34
-rw-r--r--src/Protocol/Protocol18x.cpp12
-rw-r--r--src/Protocol/Protocol18x.h7
-rw-r--r--src/Protocol/Protocol19x.cpp54
-rw-r--r--src/Protocol/Protocol19x.h11
-rw-r--r--src/Protocol/ProtocolRecognizer.cpp21
-rw-r--r--src/Protocol/ProtocolRecognizer.h17
-rw-r--r--src/Root.cpp49
-rw-r--r--src/World.cpp42
-rw-r--r--src/World.h12
45 files changed, 1279 insertions, 192 deletions
diff --git a/src/Blocks/BlockIce.h b/src/Blocks/BlockIce.h
index aae190036..633b00e51 100644
--- a/src/Blocks/BlockIce.h
+++ b/src/Blocks/BlockIce.h
@@ -38,8 +38,7 @@ public:
return;
}
- a_ChunkInterface.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_WATER, 0);
- // This is called later than the real destroying of this ice block
+ a_ChunkInterface.SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_WATER, 0);
}
}
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index 06d5eb319..d833feea5 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -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)
{
m_IsSaving = true;
diff --git a/src/Chunk.h b/src/Chunk.h
index 925680fdd..54e4a9502 100644
--- a/src/Chunk.h
+++ b/src/Chunk.h
@@ -112,6 +112,9 @@ public:
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; }
/*
diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp
index 7c4162b25..a16b08f15 100644
--- a/src/ChunkMap.cpp
+++ b/src/ChunkMap.cpp
@@ -2704,11 +2704,28 @@ void cChunkMap::SaveAllChunks(void)
-int cChunkMap::GetNumChunks(void)
+size_t cChunkMap::GetNumChunks(void)
{
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;
}
diff --git a/src/ChunkMap.h b/src/ChunkMap.h
index 328b0f74c..ff8f82f91 100644
--- a/src/ChunkMap.h
+++ b/src/ChunkMap.h
@@ -388,7 +388,10 @@ public:
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
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index 7dbf5a0a4..303583769 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -62,6 +62,7 @@ int cClientHandle::s_ClientCount = 0;
// cClientHandle:
cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) :
+ m_LastSentDimension(dimNotSet),
m_CurrentViewDistance(a_ViewDistance),
m_RequestedViewDistance(a_ViewDistance),
m_IPString(a_IPString),
@@ -368,6 +369,7 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
// Return a server login packet
m_Protocol->SendLogin(*m_Player, *World);
+ m_LastSentDimension = World->GetDimension();
// Send Weather if raining:
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()
{
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);
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
- 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);
}
@@ -1886,7 +1887,7 @@ void cClientHandle::RemoveFromWorld(void)
}
for (auto && Chunk : Chunks)
{
- m_Protocol->SendUnloadChunk(Chunk.m_ChunkX, Chunk.m_ChunkZ);
+ SendUnloadChunk(Chunk.m_ChunkX, Chunk.m_ChunkZ);
} // for itr - Chunks[]
// 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)
{
- 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;
}
diff --git a/src/ClientHandle.h b/src/ClientHandle.h
index c49de647f..7d829653b 100644
--- a/src/ClientHandle.h
+++ b/src/ClientHandle.h
@@ -125,7 +125,8 @@ public: // tolua_export
/** Remove all loaded chunks that are no longer in range */
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);
inline bool IsLoggedIn(void) const { return (m_State >= csAuthenticating); }
@@ -369,6 +370,9 @@ public: // tolua_export
bool IsPlayerChunkSent();
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()
diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp
index 59d742f8d..366592549 100644
--- a/src/Entities/ArrowEntity.cpp
+++ b/src/Entities/ArrowEntity.cpp
@@ -180,6 +180,11 @@ void cArrowEntity::CollectedBy(cPlayer & a_Dest)
void cArrowEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
m_Timer += a_Dt;
if (m_bIsCollected)
diff --git a/src/Entities/Boat.cpp b/src/Entities/Boat.cpp
index 330e54740..f9b83eee5 100644
--- a/src/Entities/Boat.cpp
+++ b/src/Entities/Boat.cpp
@@ -102,6 +102,11 @@ void cBoat::OnRightClicked(cPlayer & a_Player)
void cBoat::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
BroadcastMovementUpdate();
SetSpeed(GetSpeed() * 0.97); // Slowly decrease the speed
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index 2adbc3142..b2fa56143 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -1433,19 +1433,22 @@ bool cEntity::DetectPortal()
}
m_PortalCooldownData.m_TicksDelayed = 0;
+ // Nether portal in the nether
if (GetWorld()->GetDimension() == dimNether)
{
if (GetWorld()->GetLinkedOverworldName().empty())
{
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
- (reinterpret_cast<cPlayer *>(this))->GetClientHandle()->SendRespawn(dimOverworld);
+ (reinterpret_cast<cPlayer *>(this))->GetClientHandle()->SendRespawn(DestionationDim);
}
Vector3d TargetPos = GetPosition();
@@ -1454,23 +1457,30 @@ bool cEntity::DetectPortal()
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
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);
return true;
}
+ // Nether portal in the overworld
else
{
if (GetWorld()->GetLinkedNetherWorldName().empty())
{
return false;
}
+ cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
+ eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer())
{
- reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterPortal);
- reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimNether);
+ if (DestionationDim == dimNether)
+ {
+ reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterPortal);
+ }
+
+ reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
}
Vector3d TargetPos = GetPosition();
@@ -1479,7 +1489,7 @@ bool cEntity::DetectPortal()
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
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);
return true;
}
@@ -1491,6 +1501,7 @@ bool cEntity::DetectPortal()
return false;
}
+ // End portal in the end
if (GetWorld()->GetDimension() == dimEnd)
{
@@ -1498,37 +1509,55 @@ bool cEntity::DetectPortal()
{
return false;
}
+ cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
+ eDimension DestionationDim = DestinationWorld->GetDimension();
+
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer())
{
cPlayer * Player = reinterpret_cast<cPlayer *>(this);
- Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z);
- Player->GetClientHandle()->SendRespawn(dimOverworld);
+ if (Player->GetBedWorld() == DestinationWorld)
+ {
+ 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());
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
else
{
if (GetWorld()->GetLinkedEndWorldName().empty())
{
return false;
}
+ cWorld * DestinationWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName());
+ eDimension DestionationDim = DestinationWorld->GetDimension();
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer())
{
- reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterTheEnd);
- reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimEnd);
+ if (DestionationDim == dimEnd)
+ {
+ reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterTheEnd);
+ }
+ reinterpret_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());
return MoveToWorld(TargetWorld, false);
}
diff --git a/src/Entities/FireworkEntity.cpp b/src/Entities/FireworkEntity.cpp
index 552549b7c..b0ba4e6c5 100644
--- a/src/Entities/FireworkEntity.cpp
+++ b/src/Entities/FireworkEntity.cpp
@@ -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)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if (m_TicksToExplosion <= 0)
{
diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp
index 3b58a1ef9..43291bdc8 100644
--- a/src/Entities/Minecart.cpp
+++ b/src/Entities/Minecart.cpp
@@ -1264,6 +1264,11 @@ void cMinecartWithFurnace::OnRightClicked(cPlayer & a_Player)
void cMinecartWithFurnace::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if (m_IsFueled)
{
diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp
index 04425dd51..dbcaba591 100644
--- a/src/Entities/Pawn.cpp
+++ b/src/Entities/Pawn.cpp
@@ -111,7 +111,11 @@ void cPawn::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), Callback);
super::Tick(a_Dt, a_Chunk);
-
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
HandleFalling();
}
diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp
index 69bb981e6..12d535f84 100644
--- a/src/Entities/Pickup.cpp
+++ b/src/Entities/Pickup.cpp
@@ -116,6 +116,11 @@ void cPickup::SpawnOn(cClientHandle & a_Client)
void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
BroadcastMovementUpdate(); // Notify clients of position
m_Timer += a_Dt;
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 50bec3608..889aef778 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -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)
{
if (a_IsFlying == m_IsFlying)
@@ -1771,64 +1780,71 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d
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))
{
- // A Plugin doesn't allow the player to changing the world
+ // A Plugin doesn't allow the player to change the world
return false;
}
- // The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
- GetClientHandle()->InvalidateCachedSentChunk();
+ GetWorld()->QueueTask([this, a_World, a_ShouldSendRespawn, a_NewPosition](cWorld & a_OldWorld)
+ {
+ // The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
+ GetClientHandle()->InvalidateCachedSentChunk();
- // Prevent further ticking in this world
- SetIsTicking(false);
+ // Prevent further ticking in this world
+ SetIsTicking(false);
- // Tell others we are gone
- GetWorld()->BroadcastDestroyEntity(*this);
+ // Tell others we are gone
+ GetWorld()->BroadcastDestroyEntity(*this);
- // Remove player from world
- GetWorld()->RemovePlayer(this, false);
+ // Remove player from world
+ GetWorld()->RemovePlayer(this, false);
- // Set position to the new position
- SetPosition(a_NewPosition);
- FreezeInternal(a_NewPosition, false);
+ // Set position to the new position
+ SetPosition(a_NewPosition);
+ FreezeInternal(a_NewPosition, false);
- // Stop all mobs from targeting this player
- StopEveryoneFromTargetingMe();
+ // Stop all mobs from targeting this player
+ StopEveryoneFromTargetingMe();
- // Send the respawn packet:
- if (a_ShouldSendRespawn && (m_ClientHandle != nullptr))
- {
- m_ClientHandle->SendRespawn(a_World->GetDimension());
- }
+ cClientHandle * ch = this->GetClientHandle();
+ if (ch != nullptr)
+ {
+ // Send the respawn packet:
+ if (a_ShouldSendRespawn)
+ {
+ m_ClientHandle->SendRespawn(a_World->GetDimension());
+ }
- // Update the view distance.
- m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance());
- // Send current weather of target world to player
- if (a_World->GetDimension() == dimOverworld)
- {
- m_ClientHandle->SendWeather(a_World->GetWeather());
- }
+ // Update the view distance.
+ ch->SetViewDistance(m_ClientHandle->GetRequestedViewDistance());
- // Broadcast the player into the new world.
- a_World->BroadcastSpawnEntity(*this);
+ // 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) ",
this->GetName().c_str(),
a_OldWorld.GetName().c_str(), a_World->GetName().c_str(),
ParentChunk->GetPosX(), ParentChunk->GetPosZ()
);
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;
}
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index f6e9da45e..25796ee50 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -467,6 +467,9 @@ public:
// tolua_end
+ // TODO lua export GetBedPos and GetBedWorld
+ cWorld * GetBedWorld();
+
/** Update movement-related statistics. */
void UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIsOnGround);
diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp
index c4f705668..2f90a56cb 100644
--- a/src/Entities/ProjectileEntity.cpp
+++ b/src/Entities/ProjectileEntity.cpp
@@ -369,6 +369,11 @@ AString cProjectileEntity::GetMCAClassName(void) const
void cProjectileEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
BroadcastMovementUpdate();
}
diff --git a/src/Entities/TNTEntity.cpp b/src/Entities/TNTEntity.cpp
index 4d533ebe4..6784f19f5 100644
--- a/src/Entities/TNTEntity.cpp
+++ b/src/Entities/TNTEntity.cpp
@@ -57,6 +57,11 @@ void cTNTEntity::Explode(void)
void cTNTEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
BroadcastMovementUpdate();
m_FuseTicks -= 1;
diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp
index c67f01b8f..109ad274c 100644
--- a/src/Mobs/AggressiveMonster.cpp
+++ b/src/Mobs/AggressiveMonster.cpp
@@ -52,6 +52,11 @@ void cAggressiveMonster::EventSeePlayer(cEntity * a_Entity, cChunk & a_Chunk)
void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if (m_EMState == CHASING)
{
diff --git a/src/Mobs/CaveSpider.cpp b/src/Mobs/CaveSpider.cpp
index 2a4975126..9f2524c1b 100644
--- a/src/Mobs/CaveSpider.cpp
+++ b/src/Mobs/CaveSpider.cpp
@@ -19,6 +19,11 @@ cCaveSpider::cCaveSpider(void) :
void cCaveSpider::Tick(std::chrono::milliseconds a_Dt, cChunk & 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;
}
diff --git a/src/Mobs/Chicken.cpp b/src/Mobs/Chicken.cpp
index 5393a8a35..2c9e86e85 100644
--- a/src/Mobs/Chicken.cpp
+++ b/src/Mobs/Chicken.cpp
@@ -23,6 +23,11 @@ cChicken::cChicken(void) :
void cChicken::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if (IsBaby())
{
diff --git a/src/Mobs/Creeper.cpp b/src/Mobs/Creeper.cpp
index 47d294a30..2e7d35ed3 100644
--- a/src/Mobs/Creeper.cpp
+++ b/src/Mobs/Creeper.cpp
@@ -26,6 +26,11 @@ cCreeper::cCreeper(void) :
void cCreeper::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if ((GetTarget() == nullptr) || (!TargetIsInRange() && !m_BurnedWithFlintAndSteel))
{
diff --git a/src/Mobs/Enderman.cpp b/src/Mobs/Enderman.cpp
index ccfd44110..2ff547c3c 100644
--- a/src/Mobs/Enderman.cpp
+++ b/src/Mobs/Enderman.cpp
@@ -189,6 +189,11 @@ bool cEnderman::CheckLight()
void cEnderman::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
// TODO take damage in rain
diff --git a/src/Mobs/Horse.cpp b/src/Mobs/Horse.cpp
index dd40f1da2..ce4121a45 100644
--- a/src/Mobs/Horse.cpp
+++ b/src/Mobs/Horse.cpp
@@ -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)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if (!m_bIsMouthOpen)
{
diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp
index 98c22e299..acd8f0145 100644
--- a/src/Mobs/Monster.cpp
+++ b/src/Mobs/Monster.cpp
@@ -231,6 +231,11 @@ void cMonster::StopMovingToPosition()
void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & 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);
ASSERT((GetTarget() == nullptr) || (GetTarget()->IsPawn() && (GetTarget()->GetWorld() == GetWorld())));
diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp
index 071352532..42884fb56 100644
--- a/src/Mobs/PassiveMonster.cpp
+++ b/src/Mobs/PassiveMonster.cpp
@@ -81,6 +81,11 @@ void cPassiveMonster::Destroyed()
void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if (m_EMState == ESCAPING)
{
diff --git a/src/Mobs/Pig.cpp b/src/Mobs/Pig.cpp
index b67b29d87..6b420b235 100644
--- a/src/Mobs/Pig.cpp
+++ b/src/Mobs/Pig.cpp
@@ -85,6 +85,11 @@ void cPig::OnRightClicked(cPlayer & a_Player)
void cPig::Tick(std::chrono::milliseconds a_Dt, cChunk & 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 (m_bIsSaddled && (m_Attachee != nullptr))
diff --git a/src/Mobs/Sheep.cpp b/src/Mobs/Sheep.cpp
index 7bca03e7e..b0fc68d44 100644
--- a/src/Mobs/Sheep.cpp
+++ b/src/Mobs/Sheep.cpp
@@ -88,6 +88,11 @@ void cSheep::OnRightClicked(cPlayer & a_Player)
void cSheep::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
int PosX = POSX_TOINT;
int PosY = POSY_TOINT - 1;
int PosZ = POSZ_TOINT;
diff --git a/src/Mobs/SnowGolem.cpp b/src/Mobs/SnowGolem.cpp
index 6afe3fda0..b4089d179 100644
--- a/src/Mobs/SnowGolem.cpp
+++ b/src/Mobs/SnowGolem.cpp
@@ -30,6 +30,11 @@ void cSnowGolem::GetDrops(cItems & a_Drops, cEntity * a_Killer)
void cSnowGolem::Tick(std::chrono::milliseconds a_Dt, cChunk & 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)))
{
TakeDamage(*this);
diff --git a/src/Mobs/Villager.cpp b/src/Mobs/Villager.cpp
index 41807e335..4e762a55a 100644
--- a/src/Mobs/Villager.cpp
+++ b/src/Mobs/Villager.cpp
@@ -54,6 +54,11 @@ bool cVillager::DoTakeDamage(TakeDamageInfo & a_TDI)
void cVillager::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if (m_ActionCountDown > -1)
{
diff --git a/src/Mobs/Wither.cpp b/src/Mobs/Wither.cpp
index 6ef81ce1b..19399953e 100644
--- a/src/Mobs/Wither.cpp
+++ b/src/Mobs/Wither.cpp
@@ -69,6 +69,11 @@ bool cWither::DoTakeDamage(TakeDamageInfo & a_TDI)
void cWither::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
if (m_WitherInvulnerableTicks > 0)
{
diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp
index da21468ca..e62ec6c30 100644
--- a/src/Mobs/Wolf.cpp
+++ b/src/Mobs/Wolf.cpp
@@ -263,6 +263,12 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
super::Tick(a_Dt, a_Chunk);
}
+ if (!IsTicking())
+ {
+ // The base class tick destroyed us
+ return;
+ }
+
if (GetTarget() == nullptr)
{
cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance));
diff --git a/src/Protocol/CMakeLists.txt b/src/Protocol/CMakeLists.txt
index f3282c93f..13afb76f4 100644
--- a/src/Protocol/CMakeLists.txt
+++ b/src/Protocol/CMakeLists.txt
@@ -9,6 +9,7 @@ SET (SRCS
Packetizer.cpp
Protocol18x.cpp
Protocol19x.cpp
+ Protocol110x.cpp
ProtocolRecognizer.cpp
)
@@ -20,12 +21,14 @@ SET (HDRS
Protocol.h
Protocol18x.h
Protocol19x.h
+ Protocol110x.h
ProtocolRecognizer.h
)
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(Protocol18x.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(Protocol110x.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch")
endif()
if (NOT MSVC)
diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h
index a00923394..1da2a6fd7 100644
--- a/src/Protocol/Protocol.h
+++ b/src/Protocol/Protocol.h
@@ -114,7 +114,7 @@ public:
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 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 SendExperienceOrb (const cExpOrb & a_ExpOrb) = 0;
virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) = 0;
diff --git a/src/Protocol/Protocol110x.cpp b/src/Protocol/Protocol110x.cpp
new file mode 100644
index 000000000..a117d8750
--- /dev/null
+++ b/src/Protocol/Protocol110x.cpp
@@ -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())
+}
diff --git a/src/Protocol/Protocol110x.h b/src/Protocol/Protocol110x.h
new file mode 100644
index 000000000..64ef8acb5
--- /dev/null
+++ b/src/Protocol/Protocol110x.h
@@ -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;
+};
diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp
index 88a0757f2..c1018324f 100644
--- a/src/Protocol/Protocol18x.cpp
+++ b/src/Protocol/Protocol18x.cpp
@@ -107,8 +107,7 @@ cProtocol180::cProtocol180(cClientHandle * a_Client, const AString & a_ServerAdd
m_ServerPort(a_ServerPort),
m_State(a_State),
m_ReceivedData(32 KiB),
- m_IsEncrypted(false),
- m_LastSentDimension(dimNotSet)
+ m_IsEncrypted(false)
{
// BungeeCord handling:
@@ -626,7 +625,6 @@ void cProtocol180::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
Pkt.WriteString("default"); // Level type - wtf?
Pkt.WriteBool(false); // Reduced Debug Info - wtf?
}
- m_LastSentDimension = a_World.GetDimension();
// 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
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(static_cast<Byte>(Player->GetEffectiveGameMode()));
Pkt.WriteString("default");
- m_LastSentDimension = a_Dimension;
}
diff --git a/src/Protocol/Protocol18x.h b/src/Protocol/Protocol18x.h
index 08a51f342..b8f9675ba 100644
--- a/src/Protocol/Protocol18x.h
+++ b/src/Protocol/Protocol18x.h
@@ -110,7 +110,7 @@ public:
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 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 SendExperience (void) 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 */
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 */
void AddReceivedData(const char * a_Data, size_t a_Size);
diff --git a/src/Protocol/Protocol19x.cpp b/src/Protocol/Protocol19x.cpp
index d8c86cf6b..6791da8cd 100644
--- a/src/Protocol/Protocol19x.cpp
+++ b/src/Protocol/Protocol19x.cpp
@@ -117,8 +117,7 @@ cProtocol190::cProtocol190(cClientHandle * a_Client, const AString & a_ServerAdd
m_ServerPort(a_ServerPort),
m_State(a_State),
m_ReceivedData(32 KiB),
- m_IsEncrypted(false),
- m_LastSentDimension(dimNotSet)
+ m_IsEncrypted(false)
{
// BungeeCord handling:
@@ -640,7 +639,6 @@ void cProtocol190::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
Pkt.WriteString("default"); // Level type - wtf?
Pkt.WriteBool(false); // Reduced Debug Info - wtf?
}
- m_LastSentDimension = a_World.GetDimension();
// Send the spawn position:
{
@@ -741,7 +739,7 @@ void cProtocol190::SendPickupSpawn(const cPickup & a_Pickup)
{
ASSERT(m_State == 3); // In game mode?
- {
+ { // TODO Use SendSpawnObject
cPacketizer Pkt(*this, 0x00); // Spawn Object packet
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.
@@ -759,14 +757,7 @@ void cProtocol190::SendPickupSpawn(const cPickup & a_Pickup)
Pkt.WriteBEInt16(0);
}
- {
- 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
- }
+ SendEntityMetadata(a_Pickup);
}
@@ -1059,12 +1050,7 @@ void cProtocol190::SendPlayerSpawn(const cPlayer & a_Player)
Pkt.WriteBEDouble(a_Player.GetPosZ());
Pkt.WriteByteAngle(a_Player.GetYaw());
Pkt.WriteByteAngle(a_Player.GetPitch());
- Pkt.WriteBEUInt8(6); // Start metadata - Index 6: Health
- 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());
+ WriteEntityMetadata(Pkt, a_Player);
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
cPlayer * Player = m_Client->GetPlayer();
Pkt.WriteBEInt32(static_cast<Int32>(a_Dimension));
Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal)
Pkt.WriteBEUInt8(static_cast<Byte>(Player->GetEffectiveGameMode()));
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())
{
- 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:
{
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.WriteBool(false); // Reduced Debug Info - wtf?
}
- m_LastSentDimension = a_World.GetDimension();
// Send the spawn position:
{
@@ -4377,8 +4370,3 @@ void cProtocol194::SendUpdateSign(int a_BlockX, int a_BlockY, int a_BlockZ, cons
Writer.Finish();
Pkt.WriteBuf(Writer.GetResult().data(), Writer.GetResult().size());
}
-
-
-
-
-
diff --git a/src/Protocol/Protocol19x.h b/src/Protocol/Protocol19x.h
index 2bf8df4f5..79180e3a7 100644
--- a/src/Protocol/Protocol19x.h
+++ b/src/Protocol/Protocol19x.h
@@ -116,7 +116,7 @@ public:
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 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 SendExperience (void) 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 */
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 */
void AddReceivedData(const char * a_Data, size_t a_Size);
@@ -264,10 +259,10 @@ protected:
void WriteItem(cPacketizer & a_Pkt, const cItem & a_Item);
/** 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 */
- 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. */
void WriteEntityProperties(cPacketizer & a_Pkt, const cEntity & a_Entity);
diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp
index 4e950bb7f..ca0d05c51 100644
--- a/src/Protocol/ProtocolRecognizer.cpp
+++ b/src/Protocol/ProtocolRecognizer.cpp
@@ -9,6 +9,7 @@
#include "ProtocolRecognizer.h"
#include "Protocol18x.h"
#include "Protocol19x.h"
+#include "Protocol110x.h"
#include "Packetizer.h"
#include "../ClientHandle.h"
#include "../Root.h"
@@ -47,11 +48,12 @@ AString cProtocolRecognizer::GetVersionTextFromInt(int a_ProtocolVersion)
{
switch (a_ProtocolVersion)
{
- case PROTO_VERSION_1_8_0: return "1.8";
- case PROTO_VERSION_1_9_0: return "1.9";
- 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_4: return "1.9.4";
+ case PROTO_VERSION_1_8_0: return "1.8";
+ case PROTO_VERSION_1_9_0: return "1.9";
+ 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_4: return "1.9.4";
+ case PROTO_VERSION_1_10_0: return "1.10";
}
ASSERT(!"Unknown protocol version");
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);
- 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);
return true;
}
+ case PROTO_VERSION_1_10_0:
+ {
+ m_Protocol = new cProtocol1100(m_Client, ServerAddress, ServerPort, NextState);
+ return true;
+ }
default:
{
LOGINFO("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))",
diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h
index ec8f562a7..6390b6289 100644
--- a/src/Protocol/ProtocolRecognizer.h
+++ b/src/Protocol/ProtocolRecognizer.h
@@ -18,8 +18,8 @@
// 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_PROTOCOL_VERSIONS "47, 107, 108, 109, 110"
+#define MCS_CLIENT_VERSIONS "1.8.x, 1.9.x, 1.10.x"
+#define MCS_PROTOCOL_VERSIONS "47, 107, 108, 109, 110, 210"
@@ -33,11 +33,12 @@ class cProtocolRecognizer :
public:
enum
{
- PROTO_VERSION_1_8_0 = 47,
- PROTO_VERSION_1_9_0 = 107,
- PROTO_VERSION_1_9_1 = 108,
- PROTO_VERSION_1_9_2 = 109,
- PROTO_VERSION_1_9_4 = 110,
+ PROTO_VERSION_1_8_0 = 47,
+ PROTO_VERSION_1_9_0 = 107,
+ PROTO_VERSION_1_9_1 = 108,
+ PROTO_VERSION_1_9_2 = 109,
+ PROTO_VERSION_1_9_4 = 110,
+ PROTO_VERSION_1_10_0 = 210,
} ;
cProtocolRecognizer(cClientHandle * a_Client);
@@ -100,7 +101,7 @@ public:
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 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 SendExperienceOrb (const cExpOrb & a_ExpOrb) override;
virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override;
diff --git a/src/Root.cpp b/src/Root.cpp
index 7ce36f65b..09c6a74d3 100644
--- a/src/Root.cpp
+++ b/src/Root.cpp
@@ -361,55 +361,6 @@ void cRoot::LoadWorlds(cSettingsRepositoryInterface & a_Settings, bool a_IsNewIn
m_WorldsByName[ DefaultWorldName ] = m_pDefaultWorld;
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
if (Worlds.size() <= 0)
{
diff --git a/src/World.cpp b/src/World.cpp
index c10cb52e9..d47d0832a 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -149,7 +149,7 @@ cWorld::cWorld(const AString & a_WorldName, eDimension a_Dimension, const AStrin
m_WorldAge(0),
m_TimeOfDay(0),
m_LastTimeUpdate(0),
- m_LastUnload(0),
+ m_LastChunkCheck(0),
m_LastSave(0),
m_SkyDarkness(0),
m_GameMode(gmNotSet),
@@ -453,6 +453,13 @@ void cWorld::Start(void)
// 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
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_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()));
- if (m_WorldAge - m_LastSave > std::chrono::minutes(5)) // Save each 5 minutes
- {
- SaveAllChunks();
- }
-
- if (m_WorldAge - m_LastUnload > std::chrono::seconds(10)) // Unload every 10 seconds
+ if (m_WorldAge - m_LastChunkCheck > std::chrono::seconds(10))
{
+ // Unload every 10 seconds
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)
{
- m_LastUnload = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
+ m_LastChunkCheck = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
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();
}
@@ -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)
{
m_ChunkMap->GetChunkStats(a_NumValid, a_NumDirty);
diff --git a/src/World.h b/src/World.h
index a00e181b6..a33831eb9 100644
--- a/src/World.h
+++ b/src/World.h
@@ -683,7 +683,10 @@ public:
void ScheduleTask(int a_DelayTicks, std::function<void(cWorld &)> a_Task);
/** 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 */
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;
/** 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_TimeOfDay;
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
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)