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