Miscellaneous fixes (#5320)

* Protocol: update Abilities flags

+ Add Spectator handling

* BioGen: move <iostream> 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
This commit is contained in:
Tiger Wang 2021-11-11 21:02:29 +00:00 committed by GitHub
parent afe07fe090
commit d49ce751ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 146 additions and 223 deletions

View File

@ -2147,7 +2147,7 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
Type = "boolean", 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 = GrowTree =
{ {

View File

@ -76,6 +76,7 @@ cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) :
m_LastStreamedChunkX(std::numeric_limits<decltype(m_LastStreamedChunkX)>::max()), // bogus chunk coords to force streaming upon login m_LastStreamedChunkX(std::numeric_limits<decltype(m_LastStreamedChunkX)>::max()), // bogus chunk coords to force streaming upon login
m_LastStreamedChunkZ(std::numeric_limits<decltype(m_LastStreamedChunkZ)>::max()), m_LastStreamedChunkZ(std::numeric_limits<decltype(m_LastStreamedChunkZ)>::max()),
m_TicksSinceLastPacket(0), m_TicksSinceLastPacket(0),
m_TimeSinceLastUnloadCheck(0),
m_Ping(1000), m_Ping(1000),
m_PingID(1), m_PingID(1),
m_BlockDigAnimStage(-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() void cClientHandle::ProcessProtocolOut()
{ {
decltype(m_OutgoingData) OutgoingData; decltype(m_OutgoingData) OutgoingData;
@ -394,7 +425,7 @@ void cClientHandle::FinishAuthenticate()
} }
catch (const std::exception & Oops) 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."); Kick("Contact an operator.\n\nYour player's save files could not be parsed.\nTo avoid data loss you are prevented from joining.");
return; return;
} }
@ -632,8 +663,6 @@ void cClientHandle::UnloadOutOfRangeChunks(void)
m_Player->GetWorld()->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this); m_Player->GetWorld()->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this);
SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ); 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: // Only allow enchantment if the player has sufficient levels and lapis to enchant:
if ((m_Player->GetCurrentXp() >= XpRequired) && (LapisStack.m_ItemCount >= LapisRequired)) if ((m_Player->GetCurrentXp() >= XpRequired) && (LapisStack.m_ItemCount >= LapisRequired))
{ {
/** We need to reduce the player's level by the number of lapis required. // 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. */ // However we need to keep the resulting percentage filled the same.
const auto TargetLevel = m_Player->GetXpLevel() - LapisRequired; const auto TargetLevel = m_Player->GetXpLevel() - LapisRequired;
const auto CurrentFillPercent = m_Player->GetXpPercentage(); 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); cItem EnchantedItem = Window->m_SlotArea->SelectEnchantedOption(a_Enchantment);
// Set the item slot to our new enchanted item: // 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; 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()) if (IsDestroyed())
{ {
return; return;
@ -2156,16 +2176,24 @@ void cClientHandle::Tick(float a_Dt)
StreamNextChunks(); StreamNextChunks();
// Unload all chunks that are out of the view distance (every 5 seconds): // 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(); 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: // Handle block break animation:
if (m_BlockDigAnimStage > -1) if (m_BlockDigAnimStage > -1)
{ {
int lastAnimVal = m_BlockDigAnimStage; int lastAnimVal = m_BlockDigAnimStage;
m_BlockDigAnimStage += static_cast<int>(m_BlockDigAnimSpeed * a_Dt); m_BlockDigAnimStage += static_cast<int>(m_BlockDigAnimSpeed * a_Dt.count());
if (m_BlockDigAnimStage > 9000) if (m_BlockDigAnimStage > 9000)
{ {
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 // The client goes crazy if we send a respawn packet with the dimension of the current world
// So we send a temporary one first. // So we send a temporary one first.
// This is not needed when the player dies, hence the a_ShouldIgnoreDimensionChecks flag. // This is not needed when the player dies, hence the a_IsRespawningFromDeath flag.
// a_ShouldIgnoreDimensionChecks is true only at cPlayer::Respawn, which is called after // a_IsRespawningFromDeath is true only at cPlayer::Respawn, which is called after the player dies.
// the player dies.
eDimension TemporaryDimension = (a_Dimension == dimOverworld) ? dimNether : dimOverworld; // First send a temporary dimension to placate the client:
m_Protocol->SendRespawn(TemporaryDimension); m_Protocol->SendRespawn((a_Dimension == dimOverworld) ? dimNether : dimOverworld);
} }
m_Protocol->SendRespawn(a_Dimension); 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) void cClientHandle::OnLinkCreated(cTCPLinkPtr a_Link)
{ {
m_Link = a_Link; m_Link = a_Link;

View File

@ -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);
void ProxyInit(const AString & a_IPString, const cUUID & a_UUID, const Json::Value & a_Properties); 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. */ /** Flushes all buffered outgoing data to the network. */
void ProcessProtocolOut(); void ProcessProtocolOut();
@ -133,7 +137,7 @@ public: // tolua_export
inline bool IsLoggedIn(void) const { return (m_State >= csAuthenticating); } inline bool IsLoggedIn(void) const { return (m_State >= csAuthenticating); }
/** Called while the client is being ticked from the world via its cPlayer object */ /** 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 */ /** Called while the client is being ticked from the cServer object */
void ServerTick(float a_Dt); void ServerTick(float a_Dt);
@ -208,7 +212,7 @@ public: // tolua_export
void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID); void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID);
void SendResourcePack (const AString & a_ResourcePackUrl); void SendResourcePack (const AString & a_ResourcePackUrl);
void SendResetTitle (void); // tolua_export 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 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 SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode);
void SendSetSubTitle (const cCompositeChat & a_SubTitle); // tolua_export 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. /** 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. 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. 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. */ 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; cPlayer * m_Player;
/** This is an optimization which saves you an iteration of m_SentChunks if you just want to know /** 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_LastStreamedChunkX;
int m_LastStreamedChunkZ; 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()) */ /** Number of ticks since the last network packet was received (increased in Tick(), reset in OnReceivedData()) */
std::atomic<int> m_TicksSinceLastPacket; std::atomic<int> m_TicksSinceLastPacket;
/** The time since UnloadOutOfRangeChunks was last called. */
std::chrono::milliseconds m_TimeSinceLastUnloadCheck;
/** Duration of the last completed client ping. */ /** Duration of the last completed client ping. */
std::chrono::steady_clock::duration m_Ping; std::chrono::steady_clock::duration m_Ping;
@ -604,10 +609,6 @@ private:
Only succeeds if a_NewState > m_State, otherwise returns false. */ Only succeeds if a_NewState > m_State, otherwise returns false. */
bool SetState(eState a_NewState); 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: // cTCPLink::cCallbacks overrides:
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override; virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
virtual void OnReceivedData(const char * a_Data, size_t a_Length) override; virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;

View File

@ -66,6 +66,9 @@ const int cPlayer::MAX_FOOD_LEVEL = 20;
// 6000 ticks or 5 minutes // 6000 ticks or 5 minutes
#define PLAYER_INVENTORY_SAVE_INTERVAL 6000 #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_TO_LEVEL15 255
#define XP_PER_LEVEL_TO15 17 #define XP_PER_LEVEL_TO15 17
#define XP_TO_LEVEL30 825 #define XP_TO_LEVEL30 825
@ -114,29 +117,19 @@ cPlayer::BodyStanceGliding::BodyStanceGliding(cPlayer & a_Player) :
cPlayer::cPlayer(const std::shared_ptr<cClientHandle> & a_Client) : cPlayer::cPlayer(const std::shared_ptr<cClientHandle> & a_Client) :
Super(etPlayer, 0.6f, 1.8f), Super(etPlayer, 0.6f, 1.8f),
m_BodyStance(BodyStanceStanding(*this)), m_BodyStance(BodyStanceStanding(*this)),
m_FoodLevel(MAX_FOOD_LEVEL),
m_FoodSaturationLevel(5.0),
m_FoodTickTimer(0),
m_FoodExhaustionLevel(0.0),
m_Inventory(*this), m_Inventory(*this),
m_EnderChestContents(9, 3), m_EnderChestContents(9, 3),
m_DefaultWorldPath(cRoot::Get()->GetDefaultWorld()->GetDataPath()), m_DefaultWorldPath(cRoot::Get()->GetDefaultWorld()->GetDataPath()),
m_GameMode(eGameMode_NotSet),
m_ClientHandle(a_Client), m_ClientHandle(a_Client),
m_NormalMaxSpeed(1.0), m_NormalMaxSpeed(1.0),
m_SprintingMaxSpeed(1.3), m_SprintingMaxSpeed(1.3),
m_FlyingMaxSpeed(1.0), m_FlyingMaxSpeed(1.0),
m_IsChargingBow(false), m_IsChargingBow(false),
m_IsFishing(false), m_IsFishing(false),
m_IsFlightCapable(false),
m_IsFlying(false),
m_IsFrozen(false), m_IsFrozen(false),
m_IsLeftHanded(false), m_IsLeftHanded(false),
m_IsTeleporting(false), m_IsTeleporting(false),
m_IsVisible(true),
m_EatingFinishTick(-1), m_EatingFinishTick(-1),
m_LifetimeTotalXp(0),
m_CurrentXp(0),
m_BowCharge(0), m_BowCharge(0),
m_FloaterID(cEntity::INVALID_ID), m_FloaterID(cEntity::INVALID_ID),
m_Team(nullptr), m_Team(nullptr),
@ -150,13 +143,9 @@ cPlayer::cPlayer(const std::shared_ptr<cClientHandle> & a_Client) :
m_CurrentWindow = m_InventoryWindow; m_CurrentWindow = m_InventoryWindow;
m_InventoryWindow->OpenedByPlayer(*this); m_InventoryWindow->OpenedByPlayer(*this);
SetMaxHealth(MAX_HEALTH);
m_Health = MAX_HEALTH;
LoadFromDisk(); LoadFromDisk();
UpdateCapabilities(); SetMaxHealth(MAX_HEALTH);
UpdateCapabilities(); // Only required for plugins listening to HOOK_SPAWNING_ENTITY to not read uninitialised variables.
m_LastGroundHeight = static_cast<float>(GetPosY());
} }
@ -382,7 +371,6 @@ void cPlayer::SetFoodLevel(int a_FoodLevel)
if (cRoot::Get()->GetPluginManager()->CallHookPlayerFoodLevelChange(*this, FoodLevel)) if (cRoot::Get()->GetPluginManager()->CallHookPlayerFoodLevelChange(*this, FoodLevel))
{ {
m_FoodSaturationLevel = 5.0;
return; return;
} }
@ -943,8 +931,8 @@ void cPlayer::Respawn(void)
// Reset food level: // Reset food level:
m_FoodLevel = MAX_FOOD_LEVEL; m_FoodLevel = MAX_FOOD_LEVEL;
m_FoodSaturationLevel = 5.0; m_FoodSaturationLevel = RESPAWN_FOOD_SATURATION;
m_FoodExhaustionLevel = 0.0; m_FoodExhaustionLevel = 0;
// Reset Experience // Reset Experience
m_CurrentXp = 0; m_CurrentXp = 0;
@ -1341,24 +1329,23 @@ void cPlayer::SetGameMode(eGameMode a_GameMode)
void cPlayer::UpdateCapabilities() void cPlayer::UpdateCapabilities()
{ {
// Fly ability: if (IsGameModeCreative())
if (IsGameModeCreative() || IsGameModeSpectator())
{ {
m_IsFlightCapable = true; m_IsFlightCapable = true;
m_IsVisible = true;
}
else if (IsGameModeSpectator())
{
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.
} }
else else
{ {
m_IsFlying = false;
m_IsFlightCapable = false; m_IsFlightCapable = false;
} m_IsFlying = false;
m_IsVisible = true;
// Visible:
m_IsVisible = !IsGameModeSpectator();
// Clear the current dragging item of spectators:
if (IsGameModeSpectator())
{
m_DraggingItem.Empty();
} }
} }
@ -1801,79 +1788,54 @@ void cPlayer::LoadFromDisk()
{ {
LoadRank(); 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<int>(m_World->GetSpawnX()), static_cast<int>(m_World->GetSpawnY()), static_cast<int>(m_World->GetSpawnZ()));
SetPosition(WorldSpawn);
SetRespawnPosition(WorldSpawn, *m_World);
m_Inventory.Clear();
m_EnchantmentSeed = GetRandomProvider().RandInt<unsigned int>(); // Use a random number to seed the enchantment generator
FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}", GetName(), GetPosition());
}
bool cPlayer::LoadFromFile(const AString & a_FileName)
{
Json::Value Root; Json::Value Root;
const auto & UUID = GetUUID();
const auto & FileName = GetUUIDFileName(UUID);
try try
{ {
// Load the data from the file and parse: // Load the data from the save file and parse:
InputFileStream(a_FileName) >> Root; InputFileStream(FileName) >> Root;
}
catch (const Json::Exception & Oops) // Load the player stats.
{ // We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
// Parse failure: StatisticsSerializer::Load(m_Stats, m_DefaultWorldPath, UUID.ToLongString());
throw std::runtime_error(Oops.what());
} }
catch (const InputFileStream::failure &) 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: // Save file exists but unreadable:
return false; 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: m_CurrentWorldName = Root.get("world", cRoot::Get()->GetDefaultWorld()->GetName()).asString();
Json::Value & JSON_PlayerPosition = Root["position"]; m_World = cRoot::Get()->GetWorld(m_CurrentWorldName);
if (JSON_PlayerPosition.size() == 3)
if (const auto & PlayerPosition = Root["position"]; PlayerPosition.size() == 3)
{ {
SetPosX(JSON_PlayerPosition[0].asDouble()); SetPosition(PlayerPosition[0].asDouble(), PlayerPosition[1].asDouble(), PlayerPosition[2].asDouble());
SetPosY(JSON_PlayerPosition[1].asDouble());
SetPosZ(JSON_PlayerPosition[2].asDouble());
m_LastPosition = GetPosition();
} }
else
Json::Value & JSON_PlayerRotation = Root["rotation"];
if (JSON_PlayerRotation.size() == 3)
{ {
SetYaw (static_cast<float>(JSON_PlayerRotation[0].asDouble())); SetPosition(Vector3d(0.5, 0.5, 0.5) + Vector3i(m_World->GetSpawnX(), m_World->GetSpawnY(), m_World->GetSpawnZ()));
SetPitch (static_cast<float>(JSON_PlayerRotation[1].asDouble()));
SetRoll (static_cast<float>(JSON_PlayerRotation[2].asDouble()));
} }
m_Health = Root.get("health", 0).asFloat(); if (const auto & PlayerRotation = Root["rotation"]; PlayerRotation.size() == 3)
{
SetYaw (PlayerRotation[0].asDouble());
SetPitch(PlayerRotation[1].asDouble());
SetRoll (PlayerRotation[2].asDouble());
}
m_Health = Root.get("health", MAX_HEALTH).asFloat();
m_AirLevel = Root.get("air", MAX_AIR_LEVEL).asInt(); m_AirLevel = Root.get("air", MAX_AIR_LEVEL).asInt();
m_FoodLevel = Root.get("food", MAX_FOOD_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_FoodTickTimer = Root.get("foodTickTimer", 0).asInt();
m_FoodExhaustionLevel = Root.get("foodExhaustion", 0).asDouble(); m_FoodExhaustionLevel = Root.get("foodExhaustion", 0).asDouble();
m_LifetimeTotalXp = Root.get("xpTotal", 0).asInt(); m_LifetimeTotalXp = Root.get("xpTotal", 0).asInt();
@ -1903,47 +1865,20 @@ bool cPlayer::LoadFromFile(const AString & a_FileName)
m_GameMode = static_cast<eGameMode>(Root.get("gamemode", eGameMode_NotSet).asInt()); m_GameMode = static_cast<eGameMode>(Root.get("gamemode", eGameMode_NotSet).asInt());
if (m_GameMode == eGameMode_Creative)
{
m_IsFlightCapable = true;
}
m_Inventory.LoadFromJson(Root["inventory"]); m_Inventory.LoadFromJson(Root["inventory"]);
m_Inventory.SetEquippedSlotNum(Root.get("equippedItemSlot", 0).asInt());
int equippedSlotNum = Root.get("equippedItemSlot", 0).asInt();
m_Inventory.SetEquippedSlotNum(equippedSlotNum);
cEnderChestEntity::LoadFromJson(Root["enderchestinventory"], m_EnderChestContents); 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.x = Root.get("SpawnX", m_World->GetSpawnX()).asInt();
m_RespawnPosition.y = Root.get("SpawnY", m_World->GetSpawnY()).asInt(); m_RespawnPosition.y = Root.get("SpawnY", m_World->GetSpawnY()).asInt();
m_RespawnPosition.z = Root.get("SpawnZ", m_World->GetSpawnZ()).asInt(); m_RespawnPosition.z = Root.get("SpawnZ", m_World->GetSpawnZ()).asInt();
m_IsRespawnPointForced = Root.get("SpawnForced", true).asBool(); 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 FLOGD("Player \"{0}\" with save file \"{1}\" is spawning at {2:.2f} in world \"{3}\"",
{ GetName(), FileName, GetPosition(), m_World->GetName()
// 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()
); );
return true;
} }
@ -3181,7 +3116,7 @@ void cPlayer::SpawnOn(cClientHandle & a_Client)
void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) 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()) if (m_ClientHandle->IsDestroyed())
{ {

View File

@ -419,15 +419,10 @@ public:
/** Saves all player data, such as inventory, to JSON. */ /** Saves all player data, such as inventory, to JSON. */
void SaveToDisk(void); 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(). */ 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(); 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; } const AString & GetLoadedWorldName() const { return m_CurrentWorldName; }
/** Opens the inventory of any tame horse the player is riding. /** Opens the inventory of any tame horse the player is riding.

View File

@ -5,7 +5,6 @@
#include "Globals.h" #include "Globals.h"
#include "BioGen.h" #include "BioGen.h"
#include <iostream>
#include "IntGen.h" #include "IntGen.h"
#include "ProtIntGen.h" #include "ProtIntGen.h"
#include "../IniFile.h" #include "../IniFile.h"
@ -1198,6 +1197,8 @@ std::unique_ptr<cBiomeGen> cBiomeGen::CreateBiomeGen(cIniFile & a_IniFile, int a
// Change to 1 to enable the perf test: // Change to 1 to enable the perf test:
#if 0 #if 0
#include <iostream>
class cBioGenPerfTest class cBioGenPerfTest
{ {
public: public:

View File

@ -128,21 +128,18 @@ public:
case E_BLOCK_BEETROOTS: 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) if (a_World.GrowPlantAt(a_BlockPos, 1) <= 0)
{ {
// Fix GH #4805 (bonemeal should only advance growth, not spawn produce):
return false; return false;
} }
a_World.BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockPos, 0); a_World.BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockPos, 0);
if (GetRandomProvider().RandBool(0.25))
// 75% chance of 1-stage growth:
if (!GetRandomProvider().RandBool(0.75))
{ {
// Hit the 25%, rollback: // 75% chance of 1-stage growth, but we hit the 25%, rollback:
a_World.GrowPlantAt(a_BlockPos, -1); a_World.GrowPlantAt(a_BlockPos, -1);
} }
return true; return true;
} // case beetroots } // case beetroots

View File

@ -241,7 +241,6 @@ namespace Explodinator
Currently missing conduits from 1.13 */ Currently missing conduits from 1.13 */
static bool BlockAlwaysDrops(const BLOCKTYPE a_Block) static bool BlockAlwaysDrops(const BLOCKTYPE a_Block)
{ {
// If it's a Shulker box
if (IsBlockShulkerBox(a_Block)) if (IsBlockShulkerBox(a_Block))
{ {
return true; return true;

View File

@ -90,9 +90,6 @@ void cProtocol_1_14::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
// cPacketizer Pkt(*this, pktDifficulty); // cPacketizer Pkt(*this, pktDifficulty);
// Pkt.WriteBEInt8(1); // Pkt.WriteBEInt8(1);
} }
// Send player abilities:
SendPlayerAbilities();
} }

View File

@ -934,13 +934,12 @@ void cProtocol_1_8_0::SendPlayerAbilities(void)
{ {
ASSERT(m_State == 3); // In game mode? ASSERT(m_State == 3); // In game mode?
cPacketizer Pkt(*this, pktPlayerAbilities);
Byte Flags = 0; Byte Flags = 0;
cPlayer * Player = m_Client->GetPlayer(); const cPlayer * Player = m_Client->GetPlayer();
if (Player->IsGameModeCreative())
if (Player->IsGameModeCreative() || Player->IsGameModeSpectator())
{ {
Flags |= 0x01; Flags |= 0x01; // Invulnerability.
Flags |= 0x08; // Godmode, used for creative
} }
if (Player->IsFlying()) if (Player->IsFlying())
{ {
@ -950,6 +949,12 @@ void cProtocol_1_8_0::SendPlayerAbilities(void)
{ {
Flags |= 0x04; Flags |= 0x04;
} }
if (Player->IsGameModeCreative())
{
Flags |= 0x08; // Godmode: creative instant break.
}
cPacketizer Pkt(*this, pktPlayerAbilities);
Pkt.WriteBEUInt8(Flags); Pkt.WriteBEUInt8(Flags);
Pkt.WriteBEFloat(static_cast<float>(0.05 * Player->GetFlyingMaxSpeed())); Pkt.WriteBEFloat(static_cast<float>(0.05 * Player->GetFlyingMaxSpeed()));
Pkt.WriteBEFloat(static_cast<float>(0.1 * Player->GetNormalMaxSpeed())); Pkt.WriteBEFloat(static_cast<float>(0.1 * Player->GetNormalMaxSpeed()));

View File

@ -1028,6 +1028,12 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La
BroadcastPlayerListUpdatePing(); BroadcastPlayerListUpdatePing();
} }
// Process all clients' buffered actions:
for (const auto Player : m_Players)
{
Player->GetClientHandle()->ProcessProtocolIn();
}
TickQueuedChunkDataSets(); TickQueuedChunkDataSets();
TickQueuedBlocks(); TickQueuedBlocks();
m_ChunkMap.Tick(a_Dt); m_ChunkMap.Tick(a_Dt);
@ -1234,7 +1240,7 @@ void cWorld::TickQueuedEntityAdditions(void)
decltype(m_EntitiesToAdd) EntitiesToAdd; decltype(m_EntitiesToAdd) EntitiesToAdd;
{ {
cCSLock Lock(m_CSEntitiesToAdd); cCSLock Lock(m_CSEntitiesToAdd);
EntitiesToAdd = std::move(m_EntitiesToAdd); std::swap(EntitiesToAdd, m_EntitiesToAdd);
} }
// Ensures m_Players manipulation happens under the chunkmap lock. // Ensures m_Players manipulation happens under the chunkmap lock.

View File

@ -55,26 +55,15 @@ static void LoadCustomStatFromJSON(StatisticsManager & Manager, const Json::Valu
for (auto it = a_In.begin(); it != a_In.end(); ++it) for (auto it = a_In.begin(); it != a_In.end(); ++it)
{ {
const auto & Key = it.key().asString(); const auto & Key = it.key().asString();
const auto StatInfo = NamespaceSerializer::SplitNamespacedID(Key); const auto & [Namespace, Name] = NamespaceSerializer::SplitNamespacedID(Key);
if (StatInfo.first == NamespaceSerializer::Namespace::Unknown)
if (Namespace == NamespaceSerializer::Namespace::Unknown)
{ {
// Ignore non-Vanilla, non-Cuberite namespaces for now: // Ignore non-Vanilla, non-Cuberite namespaces for now:
continue; continue;
} }
const auto & StatName = StatInfo.second; Manager.Custom[NamespaceSerializer::ToCustomStatistic(Name)] = it->asUInt();
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);
}
} }
} }