From d49ce751ba6f8f2eb171633cea30a1119935f8c7 Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Thu, 11 Nov 2021 21:02:29 +0000 Subject: Miscellaneous fixes (#5320) * Protocol: update Abilities flags + Add Spectator handling * BioGen: move include * ClientHandle: rename Respawn packet dimension check flag * Make it clearer what it's doing. * ClientHandle: move ProcessProtocolIn calls to World * Player: remove some redundant initialisation * Player: UpdateCapabilities enables flight for spectators * Produce growth: improve comments * ClientHandle: run unload checks using delta time * Fix forgotten initialisation of time member --- Server/Plugins/APIDump/Classes/World.lua | 2 +- src/ClientHandle.cpp | 108 ++++++++++--------- src/ClientHandle.h | 23 ++-- src/Entities/Player.cpp | 167 +++++++++--------------------- src/Entities/Player.h | 7 +- src/Generating/BioGen.cpp | 3 +- src/Items/ItemDye.h | 11 +- src/Physics/Explodinator.cpp | 1 - src/Protocol/Protocol_1_14.cpp | 3 - src/Protocol/Protocol_1_8.cpp | 15 ++- src/World.cpp | 8 +- src/WorldStorage/StatisticsSerializer.cpp | 19 +--- 12 files changed, 145 insertions(+), 222 deletions(-) diff --git a/Server/Plugins/APIDump/Classes/World.lua b/Server/Plugins/APIDump/Classes/World.lua index 149c70423..2a9e3d4e8 100644 --- a/Server/Plugins/APIDump/Classes/World.lua +++ b/Server/Plugins/APIDump/Classes/World.lua @@ -2147,7 +2147,7 @@ function OnAllChunksAvailable() All return values from the callbacks are i Type = "boolean", }, }, - Notes = "Grows the plant at the specified coords to its full. Returns true if the plant was grown, false if not.", + Notes = "Grows the plant at the specified coords to maturity. Returns true if the plant was grown, false if not.", }, GrowTree = { diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 4745ff7cd..a42212002 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -76,6 +76,7 @@ cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : m_LastStreamedChunkX(std::numeric_limits::max()), // bogus chunk coords to force streaming upon login m_LastStreamedChunkZ(std::numeric_limits::max()), m_TicksSinceLastPacket(0), + m_TimeSinceLastUnloadCheck(0), m_Ping(1000), m_PingID(1), m_BlockDigAnimStage(-1), @@ -243,6 +244,36 @@ void cClientHandle::ProxyInit(const AString & a_IPString, const cUUID & a_UUID, +void cClientHandle::ProcessProtocolIn(void) +{ + // Process received network data: + decltype(m_IncomingData) IncomingData; + { + cCSLock Lock(m_CSIncomingData); + + // Bail out when nothing was received: + if (m_IncomingData.empty()) + { + return; + } + + std::swap(IncomingData, m_IncomingData); + } + + try + { + m_Protocol.HandleIncomingData(*this, IncomingData); + } + catch (const std::exception & Oops) + { + Kick(Oops.what()); + } +} + + + + + void cClientHandle::ProcessProtocolOut() { decltype(m_OutgoingData) OutgoingData; @@ -394,7 +425,7 @@ void cClientHandle::FinishAuthenticate() } catch (const std::exception & Oops) { - LOGWARNING("Error reading player \"%s\": %s", GetUsername().c_str(), Oops.what()); + LOGWARNING("Player \"%s\" save or statistics file loading failed: %s", GetUsername().c_str(), Oops.what()); Kick("Contact an operator.\n\nYour player's save files could not be parsed.\nTo avoid data loss you are prevented from joining."); return; } @@ -632,8 +663,6 @@ void cClientHandle::UnloadOutOfRangeChunks(void) m_Player->GetWorld()->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this); SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ); } - - m_LastUnloadCheck = m_Player->GetWorld()->GetWorldAge(); } @@ -815,8 +844,8 @@ void cClientHandle::HandleEnchantItem(UInt8 a_WindowID, UInt8 a_Enchantment) // Only allow enchantment if the player has sufficient levels and lapis to enchant: if ((m_Player->GetCurrentXp() >= XpRequired) && (LapisStack.m_ItemCount >= LapisRequired)) { - /** We need to reduce the player's level by the number of lapis required. - However we need to keep the resulting percentage filled the same. */ + // We need to reduce the player's level by the number of lapis required. + // However we need to keep the resulting percentage filled the same. const auto TargetLevel = m_Player->GetXpLevel() - LapisRequired; const auto CurrentFillPercent = m_Player->GetXpPercentage(); @@ -842,7 +871,7 @@ void cClientHandle::HandleEnchantItem(UInt8 a_WindowID, UInt8 a_Enchantment) } } - // Retrieve the enchanted item corresponding to our chosen option (top, middle, bottom) + // The enchanted item corresponding to our chosen option (top, middle, bottom). cItem EnchantedItem = Window->m_SlotArea->SelectEnchantedOption(a_Enchantment); // Set the item slot to our new enchanted item: @@ -2071,19 +2100,10 @@ bool cClientHandle::CheckBlockInteractionsRate(void) -void cClientHandle::Tick(float a_Dt) +void cClientHandle::Tick(std::chrono::milliseconds a_Dt) { using namespace std::chrono_literals; - // anticheat fastbreak - if (m_HasStartedDigging) - { - BLOCKTYPE Block = m_Player->GetWorld()->GetBlock({ m_LastDigBlockX, m_LastDigBlockY, m_LastDigBlockZ }); - m_BreakProgress += m_Player->GetMiningProgressPerTick(Block); - } - - ProcessProtocolIn(); - if (IsDestroyed()) { return; @@ -2156,16 +2176,24 @@ void cClientHandle::Tick(float a_Dt) StreamNextChunks(); // Unload all chunks that are out of the view distance (every 5 seconds): - if ((m_Player->GetWorld()->GetWorldAge() - m_LastUnloadCheck) > 5s) + if ((m_TimeSinceLastUnloadCheck += a_Dt) > 5s) { UnloadOutOfRangeChunks(); + m_TimeSinceLastUnloadCheck = 0s; + } + + // anticheat fastbreak + if (m_HasStartedDigging) + { + BLOCKTYPE Block = m_Player->GetWorld()->GetBlock({ m_LastDigBlockX, m_LastDigBlockY, m_LastDigBlockZ }); + m_BreakProgress += m_Player->GetMiningProgressPerTick(Block); } // Handle block break animation: if (m_BlockDigAnimStage > -1) { int lastAnimVal = m_BlockDigAnimStage; - m_BlockDigAnimStage += static_cast(m_BlockDigAnimSpeed * a_Dt); + m_BlockDigAnimStage += static_cast(m_BlockDigAnimSpeed * a_Dt.count()); if (m_BlockDigAnimStage > 9000) { m_BlockDigAnimStage = 9000; @@ -2903,17 +2931,17 @@ void cClientHandle::SendResetTitle() -void cClientHandle::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) +void cClientHandle::SendRespawn(const eDimension a_Dimension, const bool a_IsRespawningFromDeath) { - if (!a_ShouldIgnoreDimensionChecks && (a_Dimension == m_Player->GetWorld()->GetDimension())) + if (!a_IsRespawningFromDeath && (a_Dimension == m_Player->GetWorld()->GetDimension())) { // 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); + // This is not needed when the player dies, hence the a_IsRespawningFromDeath flag. + // a_IsRespawningFromDeath is true only at cPlayer::Respawn, which is called after the player dies. + + // First send a temporary dimension to placate the client: + m_Protocol->SendRespawn((a_Dimension == dimOverworld) ? dimNether : dimOverworld); } m_Protocol->SendRespawn(a_Dimension); @@ -3383,36 +3411,6 @@ bool cClientHandle::SetState(eState a_NewState) -void cClientHandle::ProcessProtocolIn(void) -{ - // Process received network data: - decltype(m_IncomingData) IncomingData; - { - cCSLock Lock(m_CSIncomingData); - - // Bail out when nothing was received: - if (m_IncomingData.empty()) - { - return; - } - - std::swap(IncomingData, m_IncomingData); - } - - try - { - m_Protocol.HandleIncomingData(*this, IncomingData); - } - catch (const std::exception & Oops) - { - Kick(Oops.what()); - } -} - - - - - void cClientHandle::OnLinkCreated(cTCPLinkPtr a_Link) { m_Link = a_Link; diff --git a/src/ClientHandle.h b/src/ClientHandle.h index f454e53bd..86a5248c5 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -105,6 +105,10 @@ public: // tolua_export void ProxyInit(const AString & a_IPString, const cUUID & a_UUID); void ProxyInit(const AString & a_IPString, const cUUID & a_UUID, const Json::Value & a_Properties); + /** Processes the data in the network input buffer. + Called by both cWorld::Tick() and ServerTick(). */ + void ProcessProtocolIn(void); + /** Flushes all buffered outgoing data to the network. */ void ProcessProtocolOut(); @@ -133,7 +137,7 @@ public: // tolua_export inline bool IsLoggedIn(void) const { return (m_State >= csAuthenticating); } /** Called while the client is being ticked from the world via its cPlayer object */ - void Tick(float a_Dt); + void Tick(std::chrono::milliseconds a_Dt); /** Called while the client is being ticked from the cServer object */ void ServerTick(float a_Dt); @@ -208,7 +212,7 @@ public: // tolua_export void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID); void SendResourcePack (const AString & a_ResourcePackUrl); void SendResetTitle (void); // tolua_export - void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks); + void SendRespawn (eDimension a_Dimension, bool a_IsRespawningFromDeath); void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode); void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode); void SendSetSubTitle (const cCompositeChat & a_SubTitle); // tolua_export @@ -464,8 +468,9 @@ private: /** A pointer to a World-owned player object, created in FinishAuthenticate when authentication succeeds. The player should only be accessed from the tick thread of the World that owns him. - After the player object is handed off to the World, lifetime is managed automatically, guaranteed to outlast this client handle. - The player self-destructs some time after the client handle enters the Destroyed state. */ + After the player object is handed off to the World, its lifetime is managed automatically, and strongly owns this client handle. + The player self-destructs some time after the client handle enters the Destroyed state. + We are therefore guaranteed that while m_State < Destroyed, that is when when we need to access m_Player, m_Player is valid. */ cPlayer * m_Player; /** This is an optimization which saves you an iteration of m_SentChunks if you just want to know @@ -483,12 +488,12 @@ private: int m_LastStreamedChunkX; int m_LastStreamedChunkZ; - /** The last time UnloadOutOfRangeChunks was called. */ - cTickTimeLong m_LastUnloadCheck; - /** Number of ticks since the last network packet was received (increased in Tick(), reset in OnReceivedData()) */ std::atomic m_TicksSinceLastPacket; + /** The time since UnloadOutOfRangeChunks was last called. */ + std::chrono::milliseconds m_TimeSinceLastUnloadCheck; + /** Duration of the last completed client ping. */ std::chrono::steady_clock::duration m_Ping; @@ -604,10 +609,6 @@ private: Only succeeds if a_NewState > m_State, otherwise returns false. */ bool SetState(eState a_NewState); - /** Processes the data in the network input buffer. - Called by both Tick() and ServerTick(). */ - void ProcessProtocolIn(void); - // cTCPLink::cCallbacks overrides: virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; virtual void OnReceivedData(const char * a_Data, size_t a_Length) override; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 24b9c19af..e1caef7f9 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -66,6 +66,9 @@ const int cPlayer::MAX_FOOD_LEVEL = 20; // 6000 ticks or 5 minutes #define PLAYER_INVENTORY_SAVE_INTERVAL 6000 +// Food saturation for newly-joined or just-respawned players. +#define RESPAWN_FOOD_SATURATION 5 + #define XP_TO_LEVEL15 255 #define XP_PER_LEVEL_TO15 17 #define XP_TO_LEVEL30 825 @@ -114,29 +117,19 @@ cPlayer::BodyStanceGliding::BodyStanceGliding(cPlayer & a_Player) : cPlayer::cPlayer(const std::shared_ptr & a_Client) : Super(etPlayer, 0.6f, 1.8f), m_BodyStance(BodyStanceStanding(*this)), - m_FoodLevel(MAX_FOOD_LEVEL), - m_FoodSaturationLevel(5.0), - m_FoodTickTimer(0), - m_FoodExhaustionLevel(0.0), m_Inventory(*this), m_EnderChestContents(9, 3), m_DefaultWorldPath(cRoot::Get()->GetDefaultWorld()->GetDataPath()), - m_GameMode(eGameMode_NotSet), m_ClientHandle(a_Client), m_NormalMaxSpeed(1.0), m_SprintingMaxSpeed(1.3), m_FlyingMaxSpeed(1.0), m_IsChargingBow(false), m_IsFishing(false), - m_IsFlightCapable(false), - m_IsFlying(false), m_IsFrozen(false), m_IsLeftHanded(false), m_IsTeleporting(false), - m_IsVisible(true), m_EatingFinishTick(-1), - m_LifetimeTotalXp(0), - m_CurrentXp(0), m_BowCharge(0), m_FloaterID(cEntity::INVALID_ID), m_Team(nullptr), @@ -150,13 +143,9 @@ cPlayer::cPlayer(const std::shared_ptr & a_Client) : m_CurrentWindow = m_InventoryWindow; m_InventoryWindow->OpenedByPlayer(*this); - SetMaxHealth(MAX_HEALTH); - m_Health = MAX_HEALTH; - LoadFromDisk(); - UpdateCapabilities(); - - m_LastGroundHeight = static_cast(GetPosY()); + SetMaxHealth(MAX_HEALTH); + UpdateCapabilities(); // Only required for plugins listening to HOOK_SPAWNING_ENTITY to not read uninitialised variables. } @@ -382,7 +371,6 @@ void cPlayer::SetFoodLevel(int a_FoodLevel) if (cRoot::Get()->GetPluginManager()->CallHookPlayerFoodLevelChange(*this, FoodLevel)) { - m_FoodSaturationLevel = 5.0; return; } @@ -943,8 +931,8 @@ void cPlayer::Respawn(void) // Reset food level: m_FoodLevel = MAX_FOOD_LEVEL; - m_FoodSaturationLevel = 5.0; - m_FoodExhaustionLevel = 0.0; + m_FoodSaturationLevel = RESPAWN_FOOD_SATURATION; + m_FoodExhaustionLevel = 0; // Reset Experience m_CurrentXp = 0; @@ -1341,24 +1329,23 @@ void cPlayer::SetGameMode(eGameMode a_GameMode) void cPlayer::UpdateCapabilities() { - // Fly ability: - if (IsGameModeCreative() || IsGameModeSpectator()) + if (IsGameModeCreative()) { m_IsFlightCapable = true; + m_IsVisible = true; } - else + else if (IsGameModeSpectator()) { - m_IsFlying = false; - m_IsFlightCapable = false; + m_DraggingItem.Empty(); // Clear the current dragging item of spectators. + m_IsFlightCapable = true; + m_IsFlying = true; // Spectators are always in flight mode. + m_IsVisible = false; // Spectators are invisible. } - - // Visible: - m_IsVisible = !IsGameModeSpectator(); - - // Clear the current dragging item of spectators: - if (IsGameModeSpectator()) + else { - m_DraggingItem.Empty(); + m_IsFlightCapable = false; + m_IsFlying = false; + m_IsVisible = true; } } @@ -1801,79 +1788,54 @@ void cPlayer::LoadFromDisk() { LoadRank(); - const auto & UUID = GetUUID(); - - // Load from the UUID file: - if (LoadFromFile(GetUUIDFileName(UUID))) - { - return; - } - - // Player not found: - m_World = cRoot::Get()->GetDefaultWorld(); - LOG("Player \"%s\" (%s) data not found, resetting to defaults", GetName().c_str(), UUID.ToShortString().c_str()); - - const Vector3i WorldSpawn(static_cast(m_World->GetSpawnX()), static_cast(m_World->GetSpawnY()), static_cast(m_World->GetSpawnZ())); - SetPosition(WorldSpawn); - SetRespawnPosition(WorldSpawn, *m_World); - - m_Inventory.Clear(); - m_EnchantmentSeed = GetRandomProvider().RandInt(); // Use a random number to seed the enchantment generator - - FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}", GetName(), GetPosition()); -} - - - - - -bool cPlayer::LoadFromFile(const AString & a_FileName) -{ Json::Value Root; + const auto & UUID = GetUUID(); + const auto & FileName = GetUUIDFileName(UUID); try { - // Load the data from the file and parse: - InputFileStream(a_FileName) >> Root; - } - catch (const Json::Exception & Oops) - { - // Parse failure: - throw std::runtime_error(Oops.what()); + // Load the data from the save file and parse: + InputFileStream(FileName) >> Root; + + // Load the player stats. + // We use the default world name (like bukkit) because stats are shared between dimensions / worlds. + StatisticsSerializer::Load(m_Stats, m_DefaultWorldPath, UUID.ToLongString()); } catch (const InputFileStream::failure &) { - if (errno == ENOENT) + if (errno != ENOENT) { - // This is a new player whom we haven't seen yet, bail out, let them have the defaults: - return false; + // Save file exists but unreadable: + throw; } - throw; + // This is a new player whom we haven't seen yet with no save file, let them have the defaults: + LOG("Player \"%s\" (%s) save or statistics file not found, resetting to defaults", GetName().c_str(), UUID.ToShortString().c_str()); } - // Load the player data: - Json::Value & JSON_PlayerPosition = Root["position"]; - if (JSON_PlayerPosition.size() == 3) + m_CurrentWorldName = Root.get("world", cRoot::Get()->GetDefaultWorld()->GetName()).asString(); + m_World = cRoot::Get()->GetWorld(m_CurrentWorldName); + + if (const auto & PlayerPosition = Root["position"]; PlayerPosition.size() == 3) + { + SetPosition(PlayerPosition[0].asDouble(), PlayerPosition[1].asDouble(), PlayerPosition[2].asDouble()); + } + else { - SetPosX(JSON_PlayerPosition[0].asDouble()); - SetPosY(JSON_PlayerPosition[1].asDouble()); - SetPosZ(JSON_PlayerPosition[2].asDouble()); - m_LastPosition = GetPosition(); + SetPosition(Vector3d(0.5, 0.5, 0.5) + Vector3i(m_World->GetSpawnX(), m_World->GetSpawnY(), m_World->GetSpawnZ())); } - Json::Value & JSON_PlayerRotation = Root["rotation"]; - if (JSON_PlayerRotation.size() == 3) + if (const auto & PlayerRotation = Root["rotation"]; PlayerRotation.size() == 3) { - SetYaw (static_cast(JSON_PlayerRotation[0].asDouble())); - SetPitch (static_cast(JSON_PlayerRotation[1].asDouble())); - SetRoll (static_cast(JSON_PlayerRotation[2].asDouble())); + SetYaw (PlayerRotation[0].asDouble()); + SetPitch(PlayerRotation[1].asDouble()); + SetRoll (PlayerRotation[2].asDouble()); } - m_Health = Root.get("health", 0).asFloat(); + m_Health = Root.get("health", MAX_HEALTH).asFloat(); m_AirLevel = Root.get("air", MAX_AIR_LEVEL).asInt(); m_FoodLevel = Root.get("food", MAX_FOOD_LEVEL).asInt(); - m_FoodSaturationLevel = Root.get("foodSaturation", MAX_FOOD_LEVEL).asDouble(); + m_FoodSaturationLevel = Root.get("foodSaturation", RESPAWN_FOOD_SATURATION).asDouble(); m_FoodTickTimer = Root.get("foodTickTimer", 0).asInt(); m_FoodExhaustionLevel = Root.get("foodExhaustion", 0).asDouble(); m_LifetimeTotalXp = Root.get("xpTotal", 0).asInt(); @@ -1903,47 +1865,20 @@ bool cPlayer::LoadFromFile(const AString & a_FileName) m_GameMode = static_cast(Root.get("gamemode", eGameMode_NotSet).asInt()); - if (m_GameMode == eGameMode_Creative) - { - m_IsFlightCapable = true; - } - m_Inventory.LoadFromJson(Root["inventory"]); - - int equippedSlotNum = Root.get("equippedItemSlot", 0).asInt(); - m_Inventory.SetEquippedSlotNum(equippedSlotNum); + m_Inventory.SetEquippedSlotNum(Root.get("equippedItemSlot", 0).asInt()); cEnderChestEntity::LoadFromJson(Root["enderchestinventory"], m_EnderChestContents); - m_CurrentWorldName = Root.get("world", "world").asString(); - m_World = cRoot::Get()->GetWorld(m_CurrentWorldName); - if (m_World == nullptr) - { - m_World = cRoot::Get()->GetDefaultWorld(); - } - m_RespawnPosition.x = Root.get("SpawnX", m_World->GetSpawnX()).asInt(); m_RespawnPosition.y = Root.get("SpawnY", m_World->GetSpawnY()).asInt(); m_RespawnPosition.z = Root.get("SpawnZ", m_World->GetSpawnZ()).asInt(); m_IsRespawnPointForced = Root.get("SpawnForced", true).asBool(); - m_SpawnWorldName = Root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString(); + m_SpawnWorldName = Root.get("SpawnWorld", m_World->GetName()).asString(); - try - { - // Load the player stats. - // We use the default world name (like bukkit) because stats are shared between dimensions / worlds. - StatisticsSerializer::Load(m_Stats, m_DefaultWorldPath, GetUUID().ToLongString()); - } - catch (...) - { - LOGWARNING("Failed loading player statistics"); - } - - FLOGD("Player {0} was read from file \"{1}\", spawning at {2:.2f} in world \"{3}\"", - GetName(), a_FileName, GetPosition(), m_World->GetName() + FLOGD("Player \"{0}\" with save file \"{1}\" is spawning at {2:.2f} in world \"{3}\"", + GetName(), FileName, GetPosition(), m_World->GetName() ); - - return true; } @@ -3181,7 +3116,7 @@ void cPlayer::SpawnOn(cClientHandle & a_Client) void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { - m_ClientHandle->Tick(a_Dt.count()); + m_ClientHandle->Tick(a_Dt); if (m_ClientHandle->IsDestroyed()) { diff --git a/src/Entities/Player.h b/src/Entities/Player.h index d1dfffa0b..87eaad2fe 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -419,15 +419,10 @@ public: /** Saves all player data, such as inventory, to JSON. */ void SaveToDisk(void); - /** Loads the player data from the disk file. + /** Loads the player data from the save file. Sets m_World to the world where the player will spawn, based on the stored world name or the default world by calling LoadFromFile(). */ void LoadFromDisk(); - /** Loads the player data from the specified file. - Sets m_World to the world where the player will spawn, based on the stored world name or the default world. - Returns true on success, false if the player wasn't found, and excepts with base std::runtime_error if the data couldn't be read or parsed. */ - bool LoadFromFile(const AString & a_FileName); - const AString & GetLoadedWorldName() const { return m_CurrentWorldName; } /** Opens the inventory of any tame horse the player is riding. diff --git a/src/Generating/BioGen.cpp b/src/Generating/BioGen.cpp index 74547800f..5e8f5dddc 100644 --- a/src/Generating/BioGen.cpp +++ b/src/Generating/BioGen.cpp @@ -5,7 +5,6 @@ #include "Globals.h" #include "BioGen.h" -#include #include "IntGen.h" #include "ProtIntGen.h" #include "../IniFile.h" @@ -1198,6 +1197,8 @@ std::unique_ptr cBiomeGen::CreateBiomeGen(cIniFile & a_IniFile, int a // Change to 1 to enable the perf test: #if 0 +#include + class cBioGenPerfTest { public: diff --git a/src/Items/ItemDye.h b/src/Items/ItemDye.h index 683e8a73f..38cc2e4b2 100644 --- a/src/Items/ItemDye.h +++ b/src/Items/ItemDye.h @@ -128,21 +128,18 @@ public: case E_BLOCK_BEETROOTS: { + // Fix GH #4805. + // Bonemeal should only advance growth, not spawn produce, and should not be consumed if plant at maturity: if (a_World.GrowPlantAt(a_BlockPos, 1) <= 0) { - // Fix GH #4805 (bonemeal should only advance growth, not spawn produce): return false; } - a_World.BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockPos, 0); - - // 75% chance of 1-stage growth: - if (!GetRandomProvider().RandBool(0.75)) + if (GetRandomProvider().RandBool(0.25)) { - // Hit the 25%, rollback: + // 75% chance of 1-stage growth, but we hit the 25%, rollback: a_World.GrowPlantAt(a_BlockPos, -1); } - return true; } // case beetroots diff --git a/src/Physics/Explodinator.cpp b/src/Physics/Explodinator.cpp index b220508ce..72d050c59 100644 --- a/src/Physics/Explodinator.cpp +++ b/src/Physics/Explodinator.cpp @@ -241,7 +241,6 @@ namespace Explodinator Currently missing conduits from 1.13 */ static bool BlockAlwaysDrops(const BLOCKTYPE a_Block) { - // If it's a Shulker box if (IsBlockShulkerBox(a_Block)) { return true; diff --git a/src/Protocol/Protocol_1_14.cpp b/src/Protocol/Protocol_1_14.cpp index 0d0bc49a0..f38227edd 100644 --- a/src/Protocol/Protocol_1_14.cpp +++ b/src/Protocol/Protocol_1_14.cpp @@ -90,9 +90,6 @@ void cProtocol_1_14::SendLogin(const cPlayer & a_Player, const cWorld & a_World) // cPacketizer Pkt(*this, pktDifficulty); // Pkt.WriteBEInt8(1); } - - // Send player abilities: - SendPlayerAbilities(); } diff --git a/src/Protocol/Protocol_1_8.cpp b/src/Protocol/Protocol_1_8.cpp index d29126e4e..e2e8ca499 100644 --- a/src/Protocol/Protocol_1_8.cpp +++ b/src/Protocol/Protocol_1_8.cpp @@ -934,13 +934,12 @@ void cProtocol_1_8_0::SendPlayerAbilities(void) { ASSERT(m_State == 3); // In game mode? - cPacketizer Pkt(*this, pktPlayerAbilities); Byte Flags = 0; - cPlayer * Player = m_Client->GetPlayer(); - if (Player->IsGameModeCreative()) + const cPlayer * Player = m_Client->GetPlayer(); + + if (Player->IsGameModeCreative() || Player->IsGameModeSpectator()) { - Flags |= 0x01; - Flags |= 0x08; // Godmode, used for creative + Flags |= 0x01; // Invulnerability. } if (Player->IsFlying()) { @@ -950,6 +949,12 @@ void cProtocol_1_8_0::SendPlayerAbilities(void) { Flags |= 0x04; } + if (Player->IsGameModeCreative()) + { + Flags |= 0x08; // Godmode: creative instant break. + } + + cPacketizer Pkt(*this, pktPlayerAbilities); Pkt.WriteBEUInt8(Flags); Pkt.WriteBEFloat(static_cast(0.05 * Player->GetFlyingMaxSpeed())); Pkt.WriteBEFloat(static_cast(0.1 * Player->GetNormalMaxSpeed())); diff --git a/src/World.cpp b/src/World.cpp index 3b52bd9aa..94fc0ba00 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1028,6 +1028,12 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La BroadcastPlayerListUpdatePing(); } + // Process all clients' buffered actions: + for (const auto Player : m_Players) + { + Player->GetClientHandle()->ProcessProtocolIn(); + } + TickQueuedChunkDataSets(); TickQueuedBlocks(); m_ChunkMap.Tick(a_Dt); @@ -1234,7 +1240,7 @@ void cWorld::TickQueuedEntityAdditions(void) decltype(m_EntitiesToAdd) EntitiesToAdd; { cCSLock Lock(m_CSEntitiesToAdd); - EntitiesToAdd = std::move(m_EntitiesToAdd); + std::swap(EntitiesToAdd, m_EntitiesToAdd); } // Ensures m_Players manipulation happens under the chunkmap lock. diff --git a/src/WorldStorage/StatisticsSerializer.cpp b/src/WorldStorage/StatisticsSerializer.cpp index df7aa9895..5143e174d 100644 --- a/src/WorldStorage/StatisticsSerializer.cpp +++ b/src/WorldStorage/StatisticsSerializer.cpp @@ -55,26 +55,15 @@ static void LoadCustomStatFromJSON(StatisticsManager & Manager, const Json::Valu for (auto it = a_In.begin(); it != a_In.end(); ++it) { const auto & Key = it.key().asString(); - const auto StatInfo = NamespaceSerializer::SplitNamespacedID(Key); - if (StatInfo.first == NamespaceSerializer::Namespace::Unknown) + const auto & [Namespace, Name] = NamespaceSerializer::SplitNamespacedID(Key); + + if (Namespace == NamespaceSerializer::Namespace::Unknown) { // Ignore non-Vanilla, non-Cuberite namespaces for now: continue; } - const auto & StatName = StatInfo.second; - try - { - Manager.Custom[NamespaceSerializer::ToCustomStatistic(StatName)] = it->asUInt(); - } - catch (const std::out_of_range &) - { - FLOGWARNING("Invalid statistic type \"{}\"", StatName); - } - catch (const Json::LogicError &) - { - FLOGWARNING("Invalid statistic value for type \"{}\"", StatName); - } + Manager.Custom[NamespaceSerializer::ToCustomStatistic(Name)] = it->asUInt(); } } -- cgit v1.2.3