1
0
Fork 0

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",
},
},
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 =
{

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_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;

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, 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;

View File

@ -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())
{

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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;

View File

@ -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();
}

View File

@ -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()));

View File

@ -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.

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)
{
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();
}
}