1
0

Player data loading cleanup

* Kick player if data were corrupt to avoid making it worse
This commit is contained in:
Tiger Wang
2021-03-28 22:34:54 +01:00
parent 222d9957a1
commit 8a30a4a7b8
6 changed files with 106 additions and 210 deletions

View File

@@ -37,23 +37,6 @@
namespace
{
/** Returns the old Offline UUID generated before becoming vanilla compliant. */
cUUID GetOldStyleOfflineUUID(const AString & a_PlayerName)
{
// Use lowercase username
auto BaseUUID = cUUID::GenerateVersion3(StrToLower(a_PlayerName)).ToRaw();
// Clobber a full nibble around the variant bits
BaseUUID[8] = (BaseUUID[8] & 0x0f) | 0x80;
cUUID Ret;
Ret.FromRaw(BaseUUID);
return Ret;
}
/** Returns the folder for the player data based on the UUID given.
This can be used both for online and offline UUIDs. */
AString GetUUIDFolderName(const cUUID & a_Uuid)
@@ -115,7 +98,6 @@ cPlayer::cPlayer(const std::shared_ptr<cClientHandle> & a_Client) :
m_bIsInBed(false),
m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL),
m_bIsTeleporting(false),
m_UUID((a_Client != nullptr) ? a_Client->GetUUID() : cUUID{}),
m_SkinParts(0),
m_MainHand(mhRight)
{
@@ -129,22 +111,7 @@ cPlayer::cPlayer(const std::shared_ptr<cClientHandle> & a_Client) :
m_Health = MAX_HEALTH;
cWorld * World = nullptr;
if (!LoadFromDisk(World))
{
m_Inventory.Clear();
SetPosX(World->GetSpawnX());
SetPosY(World->GetSpawnY());
SetPosZ(World->GetSpawnZ());
// This is a new player. Set the player spawn point to the spawn point of the default world
SetBedPos(Vector3i(static_cast<int>(World->GetSpawnX()), static_cast<int>(World->GetSpawnY()), static_cast<int>(World->GetSpawnZ())), World);
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()
);
}
LoadFromDisk(World);
m_LastGroundHeight = static_cast<float>(GetPosY());
m_Stance = GetPosY() + 1.62;
@@ -1734,70 +1701,30 @@ void cPlayer::TossPickup(const cItem & a_Item)
bool cPlayer::LoadFromDisk(cWorldPtr & a_World)
void cPlayer::LoadFromDisk(cWorldPtr & a_World)
{
LoadRank();
const auto & UUID = GetUUID();
// Load from the UUID file:
if (LoadFromFile(GetUUIDFileName(m_UUID), a_World))
if (LoadFromFile(GetUUIDFileName(UUID), a_World))
{
return true;
return;
}
// Check for old offline UUID filename, if it exists migrate to new filename
cUUID OfflineUUID = cClientHandle::GenerateOfflineUUID(GetName());
auto OldFilename = GetUUIDFileName(GetOldStyleOfflineUUID(GetName()));
auto NewFilename = GetUUIDFileName(m_UUID);
// Only move if there isn't already a new file
if (!cFile::IsFile(NewFilename) && cFile::IsFile(OldFilename))
{
cFile::CreateFolderRecursive(GetUUIDFolderName(m_UUID)); // Ensure folder exists to move to
if (
cFile::Rename(OldFilename, NewFilename) &&
(m_UUID == OfflineUUID) &&
LoadFromFile(NewFilename, a_World)
)
{
return true;
}
}
// Player not found:
a_World = cRoot::Get()->GetDefaultWorld();
LOG("Player \"%s\" (%s) data not found, resetting to defaults", GetName().c_str(), UUID.ToShortString().c_str());
// Load from the offline UUID file, if allowed:
const char * OfflineUsage = " (unused)";
if (cRoot::Get()->GetServer()->ShouldLoadOfflinePlayerData())
{
OfflineUsage = "";
if (LoadFromFile(GetUUIDFileName(OfflineUUID), a_World))
{
return true;
}
}
const Vector3i WorldSpawn(static_cast<int>(a_World->GetSpawnX()), static_cast<int>(a_World->GetSpawnY()), static_cast<int>(a_World->GetSpawnZ()));
SetPosition(WorldSpawn);
SetBedPos(WorldSpawn, a_World);
// Load from the old-style name-based file, if allowed:
if (cRoot::Get()->GetServer()->ShouldLoadNamedPlayerData())
{
AString OldStyleFileName = Printf("players/%s.json", GetName().c_str());
if (LoadFromFile(OldStyleFileName, a_World))
{
// Save in new format and remove the old file
if (SaveToDisk())
{
cFile::Delete(OldStyleFileName);
}
return true;
}
}
m_Inventory.Clear();
m_EnchantmentSeed = GetRandomProvider().RandInt<unsigned int>(); // Use a random number to seed the enchantment generator
// None of the files loaded successfully
LOG("Player data file not found for %s (%s, offline %s%s), will be reset to defaults.",
GetName().c_str(), m_UUID.ToShortString().c_str(), OfflineUUID.ToShortString().c_str(), OfflineUsage
);
if (a_World == nullptr)
{
a_World = cRoot::Get()->GetDefaultWorld();
}
return false;
FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}", GetName(), GetPosition());
}
@@ -1806,35 +1733,31 @@ bool cPlayer::LoadFromDisk(cWorldPtr & a_World)
bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
{
// Load the data from the file:
cFile f;
if (!f.Open(a_FileName, cFile::fmRead))
{
// This is a new player whom we haven't seen yet, bail out, let them have the defaults
return false;
}
AString buffer;
if (f.ReadRestOfFile(buffer) != f.GetSize())
{
LOGWARNING("Cannot read player data from file \"%s\"", a_FileName.c_str());
return false;
}
f.Close();
Json::Value Root;
// Parse the JSON format:
Json::Value root;
AString ParseError;
if (!JsonUtils::ParseString(buffer, root, &ParseError))
try
{
FLOGWARNING(
"Cannot parse player data in file \"{0}\":\n {1}",
a_FileName, ParseError
);
return false;
// 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());
}
catch (const InputFileStream::failure &)
{
if (errno == ENOENT)
{
// This is a new player whom we haven't seen yet, bail out, let them have the defaults:
return false;
}
throw;
}
// Load the player data:
Json::Value & JSON_PlayerPosition = root["position"];
Json::Value & JSON_PlayerPosition = Root["position"];
if (JSON_PlayerPosition.size() == 3)
{
SetPosX(JSON_PlayerPosition[0].asDouble());
@@ -1843,7 +1766,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
m_LastPosition = GetPosition();
}
Json::Value & JSON_PlayerRotation = root["rotation"];
Json::Value & JSON_PlayerRotation = Root["rotation"];
if (JSON_PlayerRotation.size() == 3)
{
SetYaw (static_cast<float>(JSON_PlayerRotation[0].asDouble()));
@@ -1851,18 +1774,18 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
SetRoll (static_cast<float>(JSON_PlayerRotation[2].asDouble()));
}
m_Health = root.get("health", 0).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_FoodTickTimer = root.get("foodTickTimer", 0).asInt();
m_FoodExhaustionLevel = root.get("foodExhaustion", 0).asDouble();
m_LifetimeTotalXp = root.get("xpTotal", 0).asInt();
m_CurrentXp = root.get("xpCurrent", 0).asInt();
m_IsFlying = root.get("isflying", 0).asBool();
m_EnchantmentSeed = root.get("enchantmentSeed", GetRandomProvider().RandInt<unsigned int>()).asUInt();
m_Health = Root.get("health", 0).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_FoodTickTimer = Root.get("foodTickTimer", 0).asInt();
m_FoodExhaustionLevel = Root.get("foodExhaustion", 0).asDouble();
m_LifetimeTotalXp = Root.get("xpTotal", 0).asInt();
m_CurrentXp = Root.get("xpCurrent", 0).asInt();
m_IsFlying = Root.get("isflying", 0).asBool();
m_EnchantmentSeed = Root.get("enchantmentSeed", GetRandomProvider().RandInt<unsigned int>()).asUInt();
Json::Value & JSON_KnownItems = root["knownItems"];
Json::Value & JSON_KnownItems = Root["knownItems"];
for (UInt32 i = 0; i < JSON_KnownItems.size(); i++)
{
cItem Item;
@@ -1872,7 +1795,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
const auto & RecipeNameMap = cRoot::Get()->GetCraftingRecipes()->GetRecipeNameMap();
Json::Value & JSON_KnownRecipes = root["knownRecipes"];
Json::Value & JSON_KnownRecipes = Root["knownRecipes"];
for (UInt32 i = 0; i < JSON_KnownRecipes.size(); i++)
{
auto RecipeId = RecipeNameMap.find(JSON_KnownRecipes[i].asString());
@@ -1882,21 +1805,21 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
}
}
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_CanFly = true;
}
m_Inventory.LoadFromJson(root["inventory"]);
m_Inventory.LoadFromJson(Root["inventory"]);
int equippedSlotNum = 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_CurrentWorldName = Root.get("world", "world").asString();
a_World = cRoot::Get()->GetWorld(GetLoadedWorldName());
if (a_World == nullptr)
{
@@ -1904,10 +1827,10 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
}
m_LastBedPos.x = root.get("SpawnX", a_World->GetSpawnX()).asInt();
m_LastBedPos.y = root.get("SpawnY", a_World->GetSpawnY()).asInt();
m_LastBedPos.z = root.get("SpawnZ", a_World->GetSpawnZ()).asInt();
m_SpawnWorldName = root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString();
m_LastBedPos.x = Root.get("SpawnX", a_World->GetSpawnX()).asInt();
m_LastBedPos.y = Root.get("SpawnY", a_World->GetSpawnY()).asInt();
m_LastBedPos.z = Root.get("SpawnZ", a_World->GetSpawnZ()).asInt();
m_SpawnWorldName = Root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString();
try
{
@@ -1960,9 +1883,10 @@ void cPlayer::OpenHorseInventory()
bool cPlayer::SaveToDisk()
void cPlayer::SaveToDisk()
{
cFile::CreateFolderRecursive(GetUUIDFolderName(m_UUID));
const auto & UUID = GetUUID();
cFile::CreateFolderRecursive(GetUUIDFolderName(UUID));
// create the JSON data
Json::Value JSON_PlayerPosition;
@@ -2023,22 +1947,22 @@ bool cPlayer::SaveToDisk()
root["gamemode"] = static_cast<int>(m_GameMode);
auto JsonData = JsonUtils::WriteStyledString(root);
AString SourceFile = GetUUIDFileName(m_UUID);
AString SourceFile = GetUUIDFileName(UUID);
cFile f;
if (!f.Open(SourceFile, cFile::fmWrite))
{
LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot open file. Player will lose their progress.",
LOGWARNING("Error writing player \"%s\" to file \"%s\": cannot open file. Player will lose their progress",
GetName().c_str(), SourceFile.c_str()
);
return false;
return;
}
if (f.Write(JsonData.c_str(), JsonData.size()) != static_cast<int>(JsonData.size()))
{
LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot save data. Player will lose their progress. ",
LOGWARNING("Error writing player \"%s\" to file \"%s\": cannot save data. Player will lose their progress",
GetName().c_str(), SourceFile.c_str()
);
return false;
return;
}
try
@@ -2050,11 +1974,8 @@ bool cPlayer::SaveToDisk()
}
catch (...)
{
LOGWARNING("Could not save stats for player %s", GetName().c_str());
return false;
LOGWARNING("Error writing player \"%s\" statistics to file", GetName().c_str());
}
return true;
}
@@ -2316,9 +2237,11 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIs
void cPlayer::LoadRank(void)
{
// Load the values from cRankManager:
const auto & UUID = GetUUID();
cRankManager * RankMgr = cRoot::Get()->GetRankManager();
m_Rank = RankMgr->GetPlayerRankName(m_UUID);
// Load the values from cRankManager:
m_Rank = RankMgr->GetPlayerRankName(UUID);
if (m_Rank.empty())
{
m_Rank = RankMgr->GetDefaultRank();
@@ -2326,10 +2249,10 @@ void cPlayer::LoadRank(void)
else
{
// Update the name:
RankMgr->UpdatePlayerName(m_UUID, GetName());
RankMgr->UpdatePlayerName(UUID, GetName());
}
m_Permissions = RankMgr->GetPlayerPermissions(m_UUID);
m_Restrictions = RankMgr->GetPlayerRestrictions(m_UUID);
m_Permissions = RankMgr->GetPlayerPermissions(UUID);
m_Restrictions = RankMgr->GetPlayerRestrictions(UUID);
RankMgr->GetRankVisuals(m_Rank, m_MsgPrefix, m_MsgSuffix, m_MsgNameColorCode);
// Break up the individual permissions on each dot, into m_SplitPermissions:
@@ -2469,6 +2392,15 @@ bool cPlayer::DoesPlacingBlocksIntersectEntity(const sSetBlockVector & a_Blocks)
const cUUID & cPlayer::GetUUID(void) const
{
return m_ClientHandle->GetUUID();
}
bool cPlayer::PlaceBlocks(const sSetBlockVector & a_Blocks)
{
if (DoesPlacingBlocksIntersectEntity(a_Blocks))

View File

@@ -379,21 +379,18 @@ public:
void SetVisible( bool a_bVisible); // tolua_export
bool IsVisible(void) const { return m_bVisible; } // tolua_export
/** Saves all player data, such as inventory, to JSON */
bool SaveToDisk(void);
/** Saves all player data, such as inventory, to JSON. */
void SaveToDisk(void);
typedef cWorld * cWorldPtr;
/** Loads the player data from the disk file
Sets a_World to the world where the player will spawn, based on the stored world name or the default world by calling LoadFromFile()
Returns true on success, false on failure
*/
bool LoadFromDisk(cWorldPtr & a_World);
/** Loads the player data from the disk file.
Sets a_World to the world where the player will spawn, based on the stored world name or the default world by calling LoadFromFile(). */
void LoadFromDisk(cWorldPtr & a_World);
/** Loads the player data from the specified file
Sets a_World to the world where the player will spawn, based on the stored world name or the default world
Returns true on success, false on failure
*/
/** Loads the player data from the specified file.
Sets a_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, cWorldPtr & a_World);
const AString & GetLoadedWorldName() const { return m_CurrentWorldName; }
@@ -499,7 +496,7 @@ public:
bool DoesPlacingBlocksIntersectEntity(const sSetBlockVector & a_Blocks);
/** Returns the UUID that has been read from the client, or nil if not available. */
const cUUID & GetUUID(void) const { return m_UUID; } // Exported in ManualBindings.cpp
const cUUID & GetUUID(void) const; // Exported in ManualBindings.cpp
// tolua_begin
@@ -507,7 +504,6 @@ public:
virtual bool CanFly(void) const { return m_CanFly; }
/** (Re)loads the rank and permissions from the cRankManager.
Expects the m_UUID member to be valid.
Loads the m_Rank, m_Permissions, m_MsgPrefix, m_MsgSuffix and m_MsgNameColorCode members. */
void LoadRank(void);
@@ -718,10 +714,6 @@ private:
*/
bool m_bIsTeleporting;
/** The UUID of the player, as read from the ClientHandle.
If no ClientHandle is given, the UUID is nil. */
cUUID m_UUID;
AString m_CustomName;
/** Displayed skin part bit mask */