From 8a30a4a7b8a7ebbdbccb6995fd2f5c0f8011bace Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Sun, 28 Mar 2021 22:34:54 +0100 Subject: Player data loading cleanup * Kick player if data were corrupt to avoid making it worse --- src/Entities/Player.cpp | 226 +++++++++++++++++------------------------------- 1 file changed, 79 insertions(+), 147 deletions(-) (limited to 'src/Entities/Player.cpp') diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 59291e40d..9497d326b 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -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 & 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 & 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(World->GetSpawnX()), static_cast(World->GetSpawnY()), static_cast(World->GetSpawnZ())), World); - - m_EnchantmentSeed = GetRandomProvider().RandInt(); // Use a random number to seed the enchantment generator - - FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}", - GetName(), GetPosition() - ); - } + LoadFromDisk(World); m_LastGroundHeight = static_cast(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(); - // Load from the UUID file: - if (LoadFromFile(GetUUIDFileName(m_UUID), a_World)) - { - return true; - } + const auto & UUID = GetUUID(); - // 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)) + // Load from the UUID file: + if (LoadFromFile(GetUUIDFileName(UUID), a_World)) { - 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; - } + return; } - // 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; - } - } + // 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 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; - } - } + const Vector3i WorldSpawn(static_cast(a_World->GetSpawnX()), static_cast(a_World->GetSpawnY()), static_cast(a_World->GetSpawnZ())); + SetPosition(WorldSpawn); + SetBedPos(WorldSpawn, a_World); - // 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 - ); + m_Inventory.Clear(); + m_EnchantmentSeed = GetRandomProvider().RandInt(); // Use a random number to seed the enchantment generator - 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)) + Json::Value Root; + + try { - // This is a new player whom we haven't seen yet, bail out, let them have the defaults - return false; + // Load the data from the file and parse: + InputFileStream(a_FileName) >> Root; } - AString buffer; - if (f.ReadRestOfFile(buffer) != f.GetSize()) + catch (const Json::Exception & Oops) { - LOGWARNING("Cannot read player data from file \"%s\"", a_FileName.c_str()); - return false; + // Parse failure: + throw std::runtime_error(Oops.what()); } - f.Close(); - - // Parse the JSON format: - Json::Value root; - AString ParseError; - if (!JsonUtils::ParseString(buffer, root, &ParseError)) + catch (const InputFileStream::failure &) { - FLOGWARNING( - "Cannot parse player data in file \"{0}\":\n {1}", - a_FileName, ParseError - ); - return false; + 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(JSON_PlayerRotation[0].asDouble())); @@ -1851,18 +1774,18 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World) SetRoll (static_cast(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()).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()).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(root.get("gamemode", eGameMode_NotSet).asInt()); + m_GameMode = static_cast(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(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(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)) -- cgit v1.2.3