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:
parent
afe07fe090
commit
d49ce751ba
@ -2147,7 +2147,7 @@ function OnAllChunksAvailable()</pre> 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 =
|
||||
{
|
||||
|
@ -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_LastStreamedChunkZ(std::numeric_limits<decltype(m_LastStreamedChunkZ)>::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<int>(m_BlockDigAnimSpeed * a_Dt);
|
||||
m_BlockDigAnimStage += static_cast<int>(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;
|
||||
|
@ -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<int> 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;
|
||||
|
@ -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<cClientHandle> & 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<cClientHandle> & a_Client) :
|
||||
m_CurrentWindow = m_InventoryWindow;
|
||||
m_InventoryWindow->OpenedByPlayer(*this);
|
||||
|
||||
SetMaxHealth(MAX_HEALTH);
|
||||
m_Health = MAX_HEALTH;
|
||||
|
||||
LoadFromDisk();
|
||||
UpdateCapabilities();
|
||||
|
||||
m_LastGroundHeight = static_cast<float>(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 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
|
||||
{
|
||||
m_IsFlying = false;
|
||||
m_IsFlightCapable = false;
|
||||
}
|
||||
|
||||
// Visible:
|
||||
m_IsVisible = !IsGameModeSpectator();
|
||||
|
||||
// Clear the current dragging item of spectators:
|
||||
if (IsGameModeSpectator())
|
||||
{
|
||||
m_DraggingItem.Empty();
|
||||
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<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;
|
||||
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)
|
||||
{
|
||||
SetPosX(JSON_PlayerPosition[0].asDouble());
|
||||
SetPosY(JSON_PlayerPosition[1].asDouble());
|
||||
SetPosZ(JSON_PlayerPosition[2].asDouble());
|
||||
m_LastPosition = GetPosition();
|
||||
SetPosition(PlayerPosition[0].asDouble(), PlayerPosition[1].asDouble(), PlayerPosition[2].asDouble());
|
||||
}
|
||||
|
||||
Json::Value & JSON_PlayerRotation = Root["rotation"];
|
||||
if (JSON_PlayerRotation.size() == 3)
|
||||
else
|
||||
{
|
||||
SetYaw (static_cast<float>(JSON_PlayerRotation[0].asDouble()));
|
||||
SetPitch (static_cast<float>(JSON_PlayerRotation[1].asDouble()));
|
||||
SetRoll (static_cast<float>(JSON_PlayerRotation[2].asDouble()));
|
||||
SetPosition(Vector3d(0.5, 0.5, 0.5) + Vector3i(m_World->GetSpawnX(), m_World->GetSpawnY(), m_World->GetSpawnZ()));
|
||||
}
|
||||
|
||||
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_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<eGameMode>(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())
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#include "Globals.h"
|
||||
#include "BioGen.h"
|
||||
#include <iostream>
|
||||
#include "IntGen.h"
|
||||
#include "ProtIntGen.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:
|
||||
#if 0
|
||||
|
||||
#include <iostream>
|
||||
|
||||
class cBioGenPerfTest
|
||||
{
|
||||
public:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<float>(0.05 * Player->GetFlyingMaxSpeed()));
|
||||
Pkt.WriteBEFloat(static_cast<float>(0.1 * Player->GetNormalMaxSpeed()));
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user