2014-05-11 07:57:06 -04:00
|
|
|
|
2021-05-04 11:11:56 -04:00
|
|
|
// StatisticsSerializer.cpp
|
2014-05-11 07:57:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
#include "Globals.h"
|
2021-05-04 11:11:56 -04:00
|
|
|
#include "StatisticsManager.h"
|
|
|
|
#include "StatisticsSerializer.h"
|
2020-08-12 04:54:36 -04:00
|
|
|
#include "NamespaceSerializer.h"
|
|
|
|
|
|
|
|
#include <json/json.h>
|
2014-05-11 07:57:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-05-04 11:11:56 -04:00
|
|
|
namespace StatisticsSerializer
|
2014-05-11 07:57:06 -04:00
|
|
|
{
|
2020-10-05 06:27:14 -04:00
|
|
|
static auto MakeStatisticsDirectory(const std::string & WorldPath, std::string && FileName)
|
2020-08-20 18:27:08 -04:00
|
|
|
{
|
|
|
|
// Even though stats are shared between worlds, they are (usually) saved
|
|
|
|
// inside the folder of the default world.
|
2014-05-13 07:53:15 -04:00
|
|
|
|
2020-08-20 18:27:08 -04:00
|
|
|
// Path to the world's statistics folder.
|
|
|
|
const auto Path = WorldPath + cFile::GetPathSeparator() + "stats";
|
2020-08-19 08:09:39 -04:00
|
|
|
|
2020-08-20 18:27:08 -04:00
|
|
|
// Ensure that the directory exists.
|
|
|
|
cFile::CreateFolder(Path);
|
2014-05-11 07:57:06 -04:00
|
|
|
|
2020-08-20 18:27:08 -04:00
|
|
|
return Path + cFile::GetPathSeparator() + std::move(FileName) + ".json";
|
|
|
|
}
|
2014-05-11 07:57:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-05-03 16:07:09 -04:00
|
|
|
static void SaveStatToJSON(const StatisticsManager & Manager, Json::Value & a_Out)
|
2020-08-20 18:27:08 -04:00
|
|
|
{
|
2021-05-03 16:07:09 -04:00
|
|
|
if (Manager.Custom.empty())
|
2020-08-20 18:27:08 -04:00
|
|
|
{
|
2021-05-03 16:07:09 -04:00
|
|
|
// Avoid saving "custom": null to disk:
|
|
|
|
return;
|
|
|
|
}
|
2020-08-20 18:27:08 -04:00
|
|
|
|
2021-05-03 16:07:09 -04:00
|
|
|
auto & Custom = a_Out["custom"];
|
|
|
|
for (const auto & [Statistic, Value] : Manager.Custom)
|
|
|
|
{
|
|
|
|
Custom[NamespaceSerializer::From(Statistic).data()] = Value;
|
|
|
|
}
|
2020-08-20 18:27:08 -04:00
|
|
|
}
|
2014-05-11 07:57:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-05-03 16:07:09 -04:00
|
|
|
static void LoadLegacyFromJSON(StatisticsManager & Manager, const Json::Value & In)
|
2020-08-20 18:27:08 -04:00
|
|
|
{
|
2020-10-05 06:27:14 -04:00
|
|
|
// Upgrade mapping from pre-1.13 names. TODO: remove on 2020-09-18
|
2021-05-03 16:07:09 -04:00
|
|
|
static const std::unordered_map<std::string_view, CustomStatistic> LegacyMapping
|
2020-10-05 06:27:14 -04:00
|
|
|
{
|
2021-05-03 16:07:09 -04:00
|
|
|
{ "achievement.openInventory", CustomStatistic::AchOpenInventory },
|
|
|
|
{ "achievement.mineWood", CustomStatistic::AchMineWood },
|
|
|
|
{ "achievement.buildWorkBench", CustomStatistic::AchBuildWorkBench },
|
|
|
|
{ "achievement.buildPickaxe", CustomStatistic::AchBuildPickaxe },
|
|
|
|
{ "achievement.buildFurnace", CustomStatistic::AchBuildFurnace },
|
|
|
|
{ "achievement.acquireIron", CustomStatistic::AchAcquireIron },
|
|
|
|
{ "achievement.buildHoe", CustomStatistic::AchBuildHoe },
|
|
|
|
{ "achievement.makeBread", CustomStatistic::AchMakeBread },
|
|
|
|
{ "achievement.bakeCake", CustomStatistic::AchBakeCake },
|
|
|
|
{ "achievement.buildBetterPickaxe", CustomStatistic::AchBuildBetterPickaxe },
|
|
|
|
{ "achievement.cookFish", CustomStatistic::AchCookFish },
|
|
|
|
{ "achievement.onARail", CustomStatistic::AchOnARail },
|
|
|
|
{ "achievement.buildSword", CustomStatistic::AchBuildSword },
|
|
|
|
{ "achievement.killEnemy", CustomStatistic::AchKillEnemy },
|
|
|
|
{ "achievement.killCow", CustomStatistic::AchKillCow },
|
|
|
|
{ "achievement.flyPig", CustomStatistic::AchFlyPig },
|
|
|
|
{ "achievement.snipeSkeleton", CustomStatistic::AchSnipeSkeleton },
|
|
|
|
{ "achievement.diamonds", CustomStatistic::AchDiamonds },
|
|
|
|
{ "achievement.portal", CustomStatistic::AchPortal },
|
|
|
|
{ "achievement.ghast", CustomStatistic::AchGhast },
|
|
|
|
{ "achievement.blazeRod", CustomStatistic::AchBlazeRod },
|
|
|
|
{ "achievement.potion", CustomStatistic::AchPotion },
|
|
|
|
{ "achievement.theEnd", CustomStatistic::AchTheEnd },
|
|
|
|
{ "achievement.theEnd2", CustomStatistic::AchTheEnd2 },
|
|
|
|
{ "achievement.enchantments", CustomStatistic::AchEnchantments },
|
|
|
|
{ "achievement.overkill", CustomStatistic::AchOverkill },
|
|
|
|
{ "achievement.bookcase", CustomStatistic::AchBookcase },
|
|
|
|
{ "achievement.exploreAllBiomes", CustomStatistic::AchExploreAllBiomes },
|
|
|
|
{ "achievement.spawnWither", CustomStatistic::AchSpawnWither },
|
|
|
|
{ "achievement.killWither", CustomStatistic::AchKillWither },
|
|
|
|
{ "achievement.fullBeacon", CustomStatistic::AchFullBeacon },
|
|
|
|
{ "achievement.breedCow", CustomStatistic::AchBreedCow },
|
|
|
|
{ "achievement.diamondsToYou", CustomStatistic::AchDiamondsToYou },
|
|
|
|
{ "stat.animalsBred", CustomStatistic::AnimalsBred },
|
|
|
|
{ "stat.boatOneCm", CustomStatistic::BoatOneCm },
|
|
|
|
{ "stat.climbOneCm", CustomStatistic::ClimbOneCm },
|
|
|
|
{ "stat.crouchOneCm", CustomStatistic::CrouchOneCm },
|
|
|
|
{ "stat.damageDealt", CustomStatistic::DamageDealt },
|
|
|
|
{ "stat.damageTaken", CustomStatistic::DamageTaken },
|
|
|
|
{ "stat.deaths", CustomStatistic::Deaths },
|
|
|
|
{ "stat.drop", CustomStatistic::Drop },
|
|
|
|
{ "stat.fallOneCm", CustomStatistic::FallOneCm },
|
|
|
|
{ "stat.fishCaught", CustomStatistic::FishCaught },
|
|
|
|
{ "stat.flyOneCm", CustomStatistic::FlyOneCm },
|
|
|
|
{ "stat.horseOneCm", CustomStatistic::HorseOneCm },
|
|
|
|
{ "stat.jump", CustomStatistic::Jump },
|
|
|
|
{ "stat.leaveGame", CustomStatistic::LeaveGame },
|
|
|
|
{ "stat.minecartOneCm", CustomStatistic::MinecartOneCm },
|
|
|
|
{ "stat.mobKills", CustomStatistic::MobKills },
|
|
|
|
{ "stat.pigOneCm", CustomStatistic::PigOneCm },
|
|
|
|
{ "stat.playerKills", CustomStatistic::PlayerKills },
|
|
|
|
{ "stat.playOneMinute", CustomStatistic::PlayOneMinute },
|
|
|
|
{ "stat.sprintOneCm", CustomStatistic::SprintOneCm },
|
|
|
|
{ "stat.swimOneCm", CustomStatistic::SwimOneCm },
|
|
|
|
{ "stat.talkedToVillager", CustomStatistic::TalkedToVillager },
|
|
|
|
{ "stat.timeSinceDeath", CustomStatistic::TimeSinceDeath },
|
|
|
|
{ "stat.tradedWithVillager", CustomStatistic::TradedWithVillager },
|
|
|
|
{ "stat.walkOneCm", CustomStatistic::WalkOneCm },
|
|
|
|
{ "stat.diveOneCm", CustomStatistic::WalkUnderWaterOneCm },
|
|
|
|
{ "stat.armorCleaned", CustomStatistic::CleanArmor },
|
|
|
|
{ "stat.bannerCleaned", CustomStatistic::CleanBanner },
|
|
|
|
{ "stat.cakeSlicesEaten", CustomStatistic::EatCakeSlice },
|
|
|
|
{ "stat.itemEnchanted", CustomStatistic::EnchantItem },
|
|
|
|
{ "stat.cauldronFilled", CustomStatistic::FillCauldron },
|
|
|
|
{ "stat.dispenserInspected", CustomStatistic::InspectDispenser },
|
|
|
|
{ "stat.dropperInspected", CustomStatistic::InspectDropper },
|
|
|
|
{ "stat.hopperInspected", CustomStatistic::InspectHopper },
|
|
|
|
{ "stat.beaconInteraction", CustomStatistic::InteractWithBeacon },
|
|
|
|
{ "stat.brewingstandInteraction", CustomStatistic::InteractWithBrewingstand },
|
|
|
|
{ "stat.craftingTableInteraction", CustomStatistic::InteractWithCraftingTable },
|
|
|
|
{ "stat.furnaceInteraction", CustomStatistic::InteractWithFurnace },
|
|
|
|
{ "stat.chestOpened", CustomStatistic::OpenChest },
|
|
|
|
{ "stat.enderchestOpened", CustomStatistic::OpenEnderchest },
|
|
|
|
{ "stat.noteblockPlayed", CustomStatistic::PlayNoteblock },
|
|
|
|
{ "stat.recordPlayed", CustomStatistic::PlayRecord },
|
|
|
|
{ "stat.flowerPotted", CustomStatistic::PotFlower },
|
|
|
|
{ "stat.trappedChestTriggered", CustomStatistic::TriggerTrappedChest },
|
|
|
|
{ "stat.noteblockTuned", CustomStatistic::TuneNoteblock },
|
|
|
|
{ "stat.cauldronUsed", CustomStatistic::UseCauldron },
|
|
|
|
{ "stat.aviateOneCm", CustomStatistic::AviateOneCm },
|
|
|
|
{ "stat.sleepInBed", CustomStatistic::SleepInBed },
|
|
|
|
{ "stat.sneakTime", CustomStatistic::SneakTime }
|
2020-10-05 06:27:14 -04:00
|
|
|
};
|
|
|
|
|
2020-08-20 18:27:08 -04:00
|
|
|
for (auto Entry = In.begin(); Entry != In.end(); ++Entry)
|
|
|
|
{
|
|
|
|
const auto & Key = Entry.key().asString();
|
|
|
|
const auto FindResult = LegacyMapping.find(Key);
|
2014-05-11 07:57:06 -04:00
|
|
|
|
2020-08-20 18:27:08 -04:00
|
|
|
if ((FindResult != LegacyMapping.end()) && Entry->isInt())
|
|
|
|
{
|
2021-05-03 16:07:09 -04:00
|
|
|
Manager.Custom[FindResult->second] = Entry->asUInt();
|
2020-08-20 18:27:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-05-11 07:57:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-05-03 16:07:09 -04:00
|
|
|
static void LoadCustomStatFromJSON(StatisticsManager & Manager, const Json::Value & a_In)
|
2014-05-11 07:57:06 -04:00
|
|
|
{
|
2020-08-20 18:27:08 -04:00
|
|
|
for (auto it = a_In.begin(); it != a_In.end(); ++it)
|
2014-05-11 07:57:06 -04:00
|
|
|
{
|
2020-08-20 18:27:08 -04:00
|
|
|
const auto & Key = it.key().asString();
|
|
|
|
const auto StatInfo = NamespaceSerializer::SplitNamespacedID(Key);
|
|
|
|
if (StatInfo.first == NamespaceSerializer::Namespace::Unknown)
|
|
|
|
{
|
|
|
|
// Ignore non-Vanilla, non-Cuberite namespaces for now:
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto & StatName = StatInfo.second;
|
|
|
|
try
|
|
|
|
{
|
2021-05-03 16:07:09 -04:00
|
|
|
Manager.Custom[NamespaceSerializer::ToCustomStatistic(StatName)] = it->asUInt();
|
2020-08-20 18:27:08 -04:00
|
|
|
}
|
2020-10-05 06:27:14 -04:00
|
|
|
catch (const std::out_of_range &)
|
2020-08-20 18:27:08 -04:00
|
|
|
{
|
|
|
|
FLOGWARNING("Invalid statistic type \"{}\"", StatName);
|
|
|
|
}
|
2020-10-05 06:27:14 -04:00
|
|
|
catch (const Json::LogicError &)
|
2020-08-20 18:27:08 -04:00
|
|
|
{
|
|
|
|
FLOGWARNING("Invalid statistic value for type \"{}\"", StatName);
|
|
|
|
}
|
2014-05-11 07:57:06 -04:00
|
|
|
}
|
2020-08-20 18:27:08 -04:00
|
|
|
}
|
2014-05-11 07:57:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-05-03 16:07:09 -04:00
|
|
|
void Load(StatisticsManager & Manager, const std::string & WorldPath, std::string && FileName)
|
2020-08-19 08:09:39 -04:00
|
|
|
{
|
2020-08-20 18:27:08 -04:00
|
|
|
Json::Value Root;
|
2020-08-21 13:15:34 -04:00
|
|
|
InputFileStream(MakeStatisticsDirectory(WorldPath, std::move(FileName))) >> Root;
|
2020-08-19 08:09:39 -04:00
|
|
|
|
2020-08-20 18:27:08 -04:00
|
|
|
LoadLegacyFromJSON(Manager, Root);
|
|
|
|
LoadCustomStatFromJSON(Manager, Root["stats"]["custom"]);
|
2020-08-19 08:09:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-05-03 16:07:09 -04:00
|
|
|
void Save(const StatisticsManager & Manager, const std::string & WorldPath, std::string && FileName)
|
2014-05-11 07:57:06 -04:00
|
|
|
{
|
2020-08-20 18:27:08 -04:00
|
|
|
Json::Value Root;
|
2014-05-11 07:57:06 -04:00
|
|
|
|
2020-08-20 18:27:08 -04:00
|
|
|
SaveStatToJSON(Manager, Root["stats"]);
|
|
|
|
Root["DataVersion"] = NamespaceSerializer::DataVersion();
|
|
|
|
|
2020-08-21 13:15:34 -04:00
|
|
|
OutputFileStream(MakeStatisticsDirectory(WorldPath, std::move(FileName))) << Root;
|
2014-05-11 07:57:06 -04:00
|
|
|
}
|
|
|
|
}
|