From 40b929c02ea05ea046d890829a7f1b05b2717e25 Mon Sep 17 00:00:00 2001 From: ElNounch Date: Sun, 28 Aug 2016 07:18:32 +0200 Subject: Fix breaking ice in survival not giving a water's source (#3356) Fix #3355 --- src/Blocks/BlockIce.h | 3 +-- src/ClientHandle.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'src') 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/ClientHandle.cpp b/src/ClientHandle.cpp index 7dbf5a0a4..c44b8195b 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -1267,7 +1267,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); } -- cgit v1.2.3 From d5b3fbcadba903528e616af37354f1935ca8ea41 Mon Sep 17 00:00:00 2001 From: LogicParrot Date: Sun, 28 Aug 2016 12:42:34 +0300 Subject: Fixed SendUnloadChunk bug (#3353) --- src/ClientHandle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index c44b8195b..882c7f283 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -1886,7 +1886,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 -- cgit v1.2.3 From c088f7ff0a336703fb19038eef36f736a4e388f7 Mon Sep 17 00:00:00 2001 From: LogicParrot Date: Sat, 27 Aug 2016 09:37:54 +0300 Subject: Proper respawn packets on dimension travel --- src/ClientHandle.cpp | 18 +++++++++++++- src/ClientHandle.h | 3 +++ src/Entities/Entity.cpp | 47 ++++++++++++++++++++++++++++++------- src/Entities/Player.cpp | 9 +++++++ src/Entities/Player.h | 3 +++ src/Protocol/Protocol.h | 2 +- src/Protocol/Protocol18x.cpp | 12 ++-------- src/Protocol/Protocol18x.h | 7 +----- src/Protocol/Protocol19x.cpp | 14 ++--------- src/Protocol/Protocol19x.h | 7 +----- src/Protocol/ProtocolRecognizer.cpp | 4 ++-- src/Protocol/ProtocolRecognizer.h | 2 +- 12 files changed, 80 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 882c7f283..6febbfc3a 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)) @@ -2704,7 +2706,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..da59bdea8 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -369,6 +369,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/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(this))->GetClientHandle()->SendRespawn(dimOverworld); + (reinterpret_cast(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(this)->AwardAchievement(achEnterPortal); - reinterpret_cast(this)->GetClientHandle()->SendRespawn(dimNether); + if (DestionationDim == dimNether) + { + reinterpret_cast(this)->AwardAchievement(achEnterPortal); + } + + reinterpret_cast(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(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(this)->AwardAchievement(achEnterTheEnd); - reinterpret_cast(this)->GetClientHandle()->SendRespawn(dimEnd); + if (DestionationDim == dimEnd) + { + reinterpret_cast(this)->AwardAchievement(achEnterTheEnd); + } + reinterpret_cast(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/Player.cpp b/src/Entities/Player.cpp index 50bec3608..f28258969 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) 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/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/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(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..456ca8a91 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: { @@ -1110,21 +1108,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(a_Dimension)); Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal) Pkt.WriteBEUInt8(static_cast(Player->GetEffectiveGameMode())); Pkt.WriteString("default"); - m_LastSentDimension = a_Dimension; } @@ -4058,7 +4049,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: { diff --git a/src/Protocol/Protocol19x.h b/src/Protocol/Protocol19x.h index 2bf8df4f5..9124a5422 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); diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index 4e950bb7f..8b05ce768 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -635,10 +635,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); } diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index ec8f562a7..5f0fa2dcb 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -100,7 +100,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; -- cgit v1.2.3 From dd8daaf63e01d11b7b82bcfde2247910bdd46c6d Mon Sep 17 00:00:00 2001 From: LogicParrot Date: Mon, 29 Aug 2016 17:27:38 +0300 Subject: Remove settings.ini world migration code (#3360) --- src/Root.cpp | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) (limited to 'src') 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(); - 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) { -- cgit v1.2.3 From 61078e8402dc289cca0ace12933a688660d10b03 Mon Sep 17 00:00:00 2001 From: bibo38 Date: Fri, 2 Sep 2016 19:22:06 +0200 Subject: Added support for the Minecraft 1.10 protocol(#210) (#3348) * Added support for the Minecraft 1.10 protocol(#210) * Fixed the Clang compilation errors * Fixed wrong sound pitch value and fixed SendPlayerSpawn Metadata value. * Prefixed each enum item with the appropriate class name. --- src/Protocol/CMakeLists.txt | 7 +- src/Protocol/Protocol110x.cpp | 879 ++++++++++++++++++++++++++++++++++++ src/Protocol/Protocol110x.h | 34 ++ src/Protocol/Protocol19x.cpp | 40 +- src/Protocol/Protocol19x.h | 4 +- src/Protocol/ProtocolRecognizer.cpp | 17 +- src/Protocol/ProtocolRecognizer.h | 15 +- 7 files changed, 959 insertions(+), 37 deletions(-) create mode 100644 src/Protocol/Protocol110x.cpp create mode 100644 src/Protocol/Protocol110x.h (limited to 'src') 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/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(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(Player.GetHealth())); + break; + } + case cEntity::etPickup: + { + a_Pkt.WriteBEUInt8(ITEM_ITEM); + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, reinterpret_cast(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(a_Entity); + auto maxHealth = a_Entity.GetMaxHealth(); + auto curHealth = a_Entity.GetHealth(); + a_Pkt.WriteVarInt32(static_cast((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(Minecart.LastDamage() + 10)); + + if (Minecart.GetPayload() == cMinecart::mpNone) + { + auto & RideableMinecart = reinterpret_cast(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(Content)); + + a_Pkt.WriteBEUInt8(MINECART_BLOCK_Y); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(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(Minecart).IsFueled()); + } + break; + } // case etMinecart + + case cEntity::etProjectile: + { + auto & Projectile = reinterpret_cast(a_Entity); + switch (Projectile.GetProjectileKind()) + { + case cProjectileEntity::pkArrow: + { + a_Pkt.WriteBEUInt8(ARROW_CRITICAL); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(reinterpret_cast(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(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(Projectile).GetItem()); + } + default: + { + break; + } + } + break; + } // case etProjectile + + case cEntity::etMonster: + { + WriteMobMetadata(a_Pkt, reinterpret_cast(a_Entity)); + break; + } + + case cEntity::etBoat: + { + auto & Boat = reinterpret_cast(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(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(a_Mob.GetHealth())); + + switch (a_Mob.GetMobType()) + { + case mtBat: + { + auto & Bat = reinterpret_cast(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(a_Mob); + a_Pkt.WriteBEUInt8(CREEPER_STATE); // (idle or "blowing") + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Creeper.IsBlowing() ? 1 : static_cast(-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(a_Mob); + a_Pkt.WriteBEUInt8(ENDERMAN_CARRIED_BLOCK); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BLOCKID); + UInt32 Carried = 0; + Carried |= static_cast(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(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(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(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(Appearance)); + + a_Pkt.WriteBEUInt8(HORSE_ARMOR); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(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(a_Mob); + a_Pkt.WriteBEUInt8(SLIME_SIZE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(MagmaCube.GetSize())); + break; + } // case mtMagmaCube + + case mtOcelot: + { + auto & Ocelot = reinterpret_cast(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(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(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(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(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(Sheep.GetFurColor()); + if (Sheep.IsSheared()) + { + SheepMetadata |= 0x10; + } + a_Pkt.WriteBEInt8(SheepMetadata); + break; + } // case mtSheep + + case mtRabbit: + { + auto & Rabbit = reinterpret_cast(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(Rabbit.GetRabbitType())); + break; + } // case mtRabbit + + case mtSkeleton: + { + auto & Skeleton = reinterpret_cast(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(a_Mob); + a_Pkt.WriteBEUInt8(SLIME_SIZE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast(Slime.GetSize())); + break; + } // case mtSlime + + case mtVillager: + { + auto & Villager = reinterpret_cast(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(Villager.GetVilType())); + break; + } // case mtVillager + + case mtWitch: + { + auto & Witch = reinterpret_cast(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(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(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(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(Wolf.GetCollarColor())); + break; + } // case mtWolf + + case mtZombie: + { + auto & Zombie = reinterpret_cast(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(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/Protocol19x.cpp b/src/Protocol/Protocol19x.cpp index 456ca8a91..6791da8cd 100644 --- a/src/Protocol/Protocol19x.cpp +++ b/src/Protocol/Protocol19x.cpp @@ -739,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. @@ -757,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); } @@ -1057,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(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 } @@ -3520,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(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(Player.GetHealth())); + break; + } case cEntity::etPickup: { a_Pkt.WriteBEUInt8(5); // Index 5: Item @@ -4367,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 9124a5422..79180e3a7 100644 --- a/src/Protocol/Protocol19x.h +++ b/src/Protocol/Protocol19x.h @@ -259,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 8b05ce768..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); @@ -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 5f0fa2dcb..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); -- cgit v1.2.3 From 5625598afac8db3b35d790b8d1228997b228f228 Mon Sep 17 00:00:00 2001 From: LogicParrot Date: Sat, 3 Sep 2016 10:39:52 +0300 Subject: Improve cPlayer::DoMoveToWorld (#3113) --- src/ClientHandle.cpp | 1 - src/ClientHandle.h | 3 +- src/Entities/Player.cpp | 79 +++++++++++++++++++++++++++---------------------- 3 files changed, 45 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 6febbfc3a..303583769 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -603,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(); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index da59bdea8..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); } diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index f28258969..889aef778 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1780,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; } -- cgit v1.2.3 From 90be4e7efdb455dc4bf4e150c403586a5c73d3f1 Mon Sep 17 00:00:00 2001 From: LogicParrot Date: Sat, 3 Sep 2016 14:31:27 +0300 Subject: Entities now bail out of ticks if destroyed (#3363) --- src/Entities/ArrowEntity.cpp | 5 +++++ src/Entities/Boat.cpp | 5 +++++ src/Entities/FireworkEntity.cpp | 5 +++++ src/Entities/Minecart.cpp | 5 +++++ src/Entities/Pawn.cpp | 6 +++++- src/Entities/Pickup.cpp | 5 +++++ src/Entities/ProjectileEntity.cpp | 5 +++++ src/Entities/TNTEntity.cpp | 5 +++++ src/Mobs/AggressiveMonster.cpp | 5 +++++ src/Mobs/CaveSpider.cpp | 5 +++++ src/Mobs/Chicken.cpp | 5 +++++ src/Mobs/Creeper.cpp | 5 +++++ src/Mobs/Enderman.cpp | 5 +++++ src/Mobs/Horse.cpp | 5 +++++ src/Mobs/Monster.cpp | 5 +++++ src/Mobs/PassiveMonster.cpp | 5 +++++ src/Mobs/Pig.cpp | 5 +++++ src/Mobs/Sheep.cpp | 5 +++++ src/Mobs/SnowGolem.cpp | 5 +++++ src/Mobs/Villager.cpp | 5 +++++ src/Mobs/Wither.cpp | 5 +++++ src/Mobs/Wolf.cpp | 6 ++++++ 22 files changed, 111 insertions(+), 1 deletion(-) (limited to 'src') 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/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/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(m_SightDistance)); -- cgit v1.2.3 From 7e9e7f79110e398a4d2b07de3093eaccd551f013 Mon Sep 17 00:00:00 2001 From: LogicParrot Date: Sat, 3 Sep 2016 18:38:29 +0300 Subject: Configurable dirty unused chunk cap to avoid RAM overuse (#3359) Configurable dirty unused chunk cap to avoid RAM overuse --- src/Chunk.cpp | 13 +++++++++++++ src/Chunk.h | 3 +++ src/ChunkMap.cpp | 21 +++++++++++++++++++-- src/ChunkMap.h | 5 ++++- src/World.cpp | 42 ++++++++++++++++++++++++++++++++---------- src/World.h | 12 ++++++++++-- 6 files changed, 81 insertions(+), 15 deletions(-) (limited to 'src') 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(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/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(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(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(m_WorldAge); + m_LastChunkCheck = std::chrono::duration_cast(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 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 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) -- cgit v1.2.3