1
0

Add Statistics and Achievements for newer Network standards

This commit is contained in:
12xx12 2020-08-12 09:54:36 +01:00 committed by Tiger Wang
parent 47f7727b7f
commit 7d0813ce8c
31 changed files with 1057 additions and 517 deletions

View File

@ -206,7 +206,7 @@ void cBeaconEntity::UpdateBeacon(void)
(std::abs(Distance.z) <= 20)
)
{
a_Player.AwardAchievement(eStatistic::achFullBeacon);
a_Player.AwardAchievement(Statistic::AchFullBeacon);
}
return false;
}

View File

@ -544,7 +544,7 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI)
}
}
Player->GetStatManager().AddValue(statDamageDealt, static_cast<StatValue>(floor(a_TDI.FinalDamage * 10 + 0.5)));
Player->GetStatManager().AddValue(Statistic::DamageDealt, FloorC<cStatManager::StatValue>(a_TDI.FinalDamage * 10 + 0.5));
}
m_Health -= a_TDI.FinalDamage;
@ -1426,7 +1426,7 @@ bool cEntity::DetectPortal()
{
if (DestionationDim == dimNether)
{
static_cast<cPlayer *>(this)->AwardAchievement(achEnterPortal);
static_cast<cPlayer *>(this)->AwardAchievement(Statistic::AchPortal);
}
static_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
@ -1505,7 +1505,7 @@ bool cEntity::DetectPortal()
{
if (DestionationDim == dimEnd)
{
static_cast<cPlayer *>(this)->AwardAchievement(achEnterTheEnd);
static_cast<cPlayer *>(this)->AwardAchievement(Statistic::AchTheEnd);
}
static_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(DestionationDim);
}

View File

@ -242,10 +242,10 @@ bool cPickup::CollectedBy(cPlayer & a_Dest)
// Check achievements
switch (m_Item.m_ItemType)
{
case E_BLOCK_LOG: a_Dest.AwardAchievement(achMineWood); break;
case E_ITEM_LEATHER: a_Dest.AwardAchievement(achKillCow); break;
case E_ITEM_DIAMOND: a_Dest.AwardAchievement(achDiamonds); break;
case E_ITEM_BLAZE_ROD: a_Dest.AwardAchievement(achBlazeRod); break;
case E_BLOCK_LOG: a_Dest.AwardAchievement(Statistic::AchMineWood); break;
case E_ITEM_LEATHER: a_Dest.AwardAchievement(Statistic::AchKillCow); break;
case E_ITEM_DIAMOND: a_Dest.AwardAchievement(Statistic::AchDiamonds); break;
case E_ITEM_BLAZE_ROD: a_Dest.AwardAchievement(Statistic::AchBlazeRod); break;
default: break;
}

View File

@ -334,7 +334,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
}
m_Stats.AddValue(statMinutesPlayed, 1);
m_Stats.AddValue(Statistic::PlayOneMinute, 1);
// Handle the player detach, when the player is in spectator mode
if (
@ -742,7 +742,7 @@ void cPlayer::TossItems(const cItems & a_Items)
return;
}
m_Stats.AddValue(statItemsDropped, static_cast<StatValue>(a_Items.Size()));
m_Stats.AddValue(Statistic::Drop, static_cast<cStatManager::StatValue>(a_Items.Size()));
const auto Speed = (GetLookVector() + Vector3d(0, 0.2, 0)) * 6; // A dash of height and a dollop of speed
const auto Position = GetEyePosition() - Vector3d(0, 0.2, 0); // Correct for eye-height weirdness
@ -1110,7 +1110,7 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
NotifyNearbyWolves(static_cast<cPawn*>(a_TDI.Attacker), true);
}
}
m_Stats.AddValue(statDamageTaken, FloorC<StatValue>(a_TDI.FinalDamage * 10 + 0.5));
m_Stats.AddValue(Statistic::DamageTaken, FloorC<cStatManager::StatValue>(a_TDI.FinalDamage * 10 + 0.5));
return true;
}
return false;
@ -1168,7 +1168,7 @@ void cPlayer::KilledBy(TakeDamageInfo & a_TDI)
{
Pickups.Add(cItem(E_ITEM_RED_APPLE));
}
m_Stats.AddValue(statItemsDropped, static_cast<StatValue>(Pickups.Size()));
m_Stats.AddValue(Statistic::Drop, static_cast<cStatManager::StatValue>(Pickups.Size()));
m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10);
SaveToDisk(); // Save it, yeah the world is a tough place !
@ -1234,7 +1234,7 @@ void cPlayer::KilledBy(TakeDamageInfo & a_TDI)
}
}
m_Stats.AddValue(statDeaths);
m_Stats.AddValue(Statistic::Deaths);
m_World->GetScoreBoard().AddPlayerScore(GetName(), cObjective::otDeathCount, 1);
}
@ -1249,7 +1249,7 @@ void cPlayer::Killed(cEntity * a_Victim)
if (a_Victim->IsPlayer())
{
m_Stats.AddValue(statPlayerKills);
m_Stats.AddValue(Statistic::PlayerKills);
ScoreBoard.AddPlayerScore(GetName(), cObjective::otPlayerKillCount, 1);
}
@ -1257,10 +1257,10 @@ void cPlayer::Killed(cEntity * a_Victim)
{
if (static_cast<cMonster *>(a_Victim)->GetMobFamily() == cMonster::mfHostile)
{
AwardAchievement(achKillMonster);
AwardAchievement(Statistic::AchKillEnemy);
}
m_Stats.AddValue(statMobKills);
m_Stats.AddValue(Statistic::MobKills);
}
ScoreBoard.AddPlayerScore(GetName(), cObjective::otTotalKillCount, 1);
@ -1671,43 +1671,32 @@ void cPlayer::SetIP(const AString & a_IP)
unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach)
void cPlayer::AwardAchievement(const Statistic a_Ach)
{
eStatistic Prerequisite = cStatInfo::GetPrerequisite(a_Ach);
// Check if the prerequisites are met
if (Prerequisite != statInvalid)
// Check if the prerequisites are met:
if (!m_Stats.SatisfiesPrerequisite(a_Ach))
{
if (m_Stats.GetValue(Prerequisite) == 0)
{
return 0;
}
return;
}
StatValue Old = m_Stats.GetValue(a_Ach);
if (Old > 0)
// Increment the statistic and check if we already have it:
if (m_Stats.AddValue(a_Ach) != 1)
{
return static_cast<unsigned int>(m_Stats.AddValue(a_Ach));
return;
}
else
{
if (m_World->ShouldBroadcastAchievementMessages())
{
cCompositeChat Msg;
Msg.SetMessageType(mtSuccess);
Msg.AddShowAchievementPart(GetName(), cStatInfo::GetName(a_Ach));
// TODO: cCompositeChat should not use protocol-specific strings
// Msg.AddShowAchievementPart(GetName(), nameNew);
Msg.AddTextPart("Achivement get!");
m_World->BroadcastChat(Msg);
}
// Increment the statistic
StatValue New = m_Stats.AddValue(a_Ach);
// Achievement Get!
m_ClientHandle->SendStatistics(m_Stats);
return static_cast<unsigned int>(New);
}
}
@ -2334,10 +2323,17 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
m_SpawnWorld = cRoot::Get()->GetDefaultWorld();
}
try
{
// Load the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetName(), GetUUID().ToLongString(), &m_Stats);
cStatSerializer StatSerializer(m_Stats, cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetUUID().ToLongString());
StatSerializer.Load();
}
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(), a_World->GetName()
@ -2476,10 +2472,14 @@ bool cPlayer::SaveToDisk()
return false;
}
try
{
// Save the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetName(), GetUUID().ToLongString(), &m_Stats);
if (!StatSerializer.Save())
cStatSerializer StatSerializer(m_Stats, cRoot::Get()->GetDefaultWorld()->GetDataPath(), GetUUID().ToLongString());
StatSerializer.Save();
}
catch (...)
{
LOGWARNING("Could not save stats for player %s", GetName().c_str());
return false;
@ -2659,12 +2659,12 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIs
return;
}
StatValue Value = FloorC<StatValue>(a_DeltaPos.Length() * 100 + 0.5);
const auto Value = FloorC<cStatManager::StatValue>(a_DeltaPos.Length() * 100 + 0.5);
if (m_AttachedTo == nullptr)
{
if (IsFlying())
{
m_Stats.AddValue(statDistFlown, Value);
m_Stats.AddValue(Statistic::FlyOneCm, Value);
// May be flying and doing any of the following:
}
@ -2672,17 +2672,18 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIs
{
if (a_DeltaPos.y > 0.0) // Going up
{
m_Stats.AddValue(statDistClimbed, FloorC<StatValue>(a_DeltaPos.y * 100 + 0.5));
m_Stats.AddValue(Statistic::ClimbOneCm, FloorC<cStatManager::StatValue>(a_DeltaPos.y * 100 + 0.5));
}
}
else if (IsInWater())
{
m_Stats.AddValue(statDistSwum, Value);
// TODO: implement differentiation between diving and swimming
m_Stats.AddValue(Statistic::WalkUnderWaterOneCm, Value);
AddFoodExhaustion(0.00015 * static_cast<double>(Value));
}
else if (IsOnGround())
{
m_Stats.AddValue(statDistWalked, Value);
m_Stats.AddValue(Statistic::WalkOneCm, Value);
AddFoodExhaustion((IsSprinting() ? 0.001 : 0.0001) * static_cast<double>(Value));
}
else
@ -2690,13 +2691,13 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIs
// If a jump just started, process food exhaustion:
if ((a_DeltaPos.y > 0.0) && a_PreviousIsOnGround)
{
m_Stats.AddValue(statJumps, 1);
m_Stats.AddValue(Statistic::Jump, 1);
AddFoodExhaustion((IsSprinting() ? 0.008 : 0.002) * static_cast<double>(Value));
}
else if (a_DeltaPos.y < 0.0)
{
// Increment statistic
m_Stats.AddValue(statDistFallen, static_cast<StatValue>(std::abs(a_DeltaPos.y) * 100 + 0.5));
m_Stats.AddValue(Statistic::FallOneCm, static_cast<cStatManager::StatValue>(std::abs(a_DeltaPos.y) * 100 + 0.5));
}
// TODO: good opportunity to detect illegal flight (check for falling tho)
}
@ -2705,15 +2706,15 @@ void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIs
{
switch (m_AttachedTo->GetEntityType())
{
case cEntity::etMinecart: m_Stats.AddValue(statDistMinecart, Value); break;
case cEntity::etBoat: m_Stats.AddValue(statDistBoat, Value); break;
case cEntity::etMinecart: m_Stats.AddValue(Statistic::MinecartOneCm, Value); break;
case cEntity::etBoat: m_Stats.AddValue(Statistic::BoatOneCm, Value); break;
case cEntity::etMonster:
{
cMonster * Monster = static_cast<cMonster *>(m_AttachedTo);
switch (Monster->GetMobType())
{
case mtPig: m_Stats.AddValue(statDistPig, Value); break;
case mtHorse: m_Stats.AddValue(statDistHorse, Value); break;
case mtPig: m_Stats.AddValue(Statistic::PigOneCm, Value); break;
case mtHorse: m_Stats.AddValue(Statistic::HorseOneCm, Value); break;
default: break;
}
break;

View File

@ -235,9 +235,8 @@ public:
/** Awards the player an achievement.
If all prerequisites are met, this method will award the achievement and will broadcast a chat message.
If the achievement has been already awarded to the player, this method will just increment the stat counter.
Returns the _new_ stat value. (0 = Could not award achievement) */
unsigned int AwardAchievement(const eStatistic a_Ach);
If the achievement has been already awarded to the player, this method will just increment the stat counter. */
void AwardAchievement(Statistic a_Ach);
void SetIP(const AString & a_IP);

View File

@ -210,7 +210,7 @@ public:
}
}
a_Player.GetStatManager().AddValue(statTreasureFished, 1);
a_Player.GetStatManager().AddValue(Statistic::TreasureFished, 1);
}
else if (ItemCategory < JunkChances[LotSLevel])
{
@ -262,7 +262,7 @@ public:
Drops.Add(cItem(E_BLOCK_TRIPWIRE_HOOK));
}
a_Player.GetStatManager().AddValue(statJunkFished, 1);
a_Player.GetStatManager().AddValue(Statistic::JunkFished, 1);
}
else
{
@ -284,7 +284,7 @@ public:
Drops.Add(cItem(E_ITEM_RAW_FISH, 1, E_META_RAW_FISH_FISH));
}
a_Player.GetStatManager().AddValue(statFishCaught, 1);
a_Player.GetStatManager().AddValue(Statistic::FishCaught, 1);
}
// Check with plugins if this loot is acceptable:

View File

@ -292,7 +292,7 @@ public:
double Dist = (a_Player.GetPosition() - Pos).Length();
if (Dist < 50.0)
{
a_Player.AwardAchievement(achSpawnWither);
a_Player.AwardAchievement(Statistic::AchSpawnWither);
}
return false;
}

View File

@ -109,7 +109,7 @@ void cWither::KilledBy(TakeDamageInfo & a_TDI)
if (Dist < 50.0)
{
// If player is close, award achievement
a_Player.AwardAchievement(achKillWither);
a_Player.AwardAchievement(Statistic::AchKillWither);
}
return false;
}

View File

@ -5,7 +5,7 @@
namespace Palette_1_13
{
UInt32 FromBlock(short ID)
UInt32 FromBlock(const short ID)
{
using namespace Block;
@ -7067,7 +7067,7 @@ namespace Palette_1_13
}
}
UInt32 FromItem(Item ID)
UInt32 FromItem(const Item ID)
{
switch (ID)
{
@ -7860,7 +7860,67 @@ namespace Palette_1_13
}
}
Item ToItem(UInt32 ID)
UInt32 From(const Statistic ID)
{
switch (ID)
{
case Statistic::AnimalsBred: return 25;
case Statistic::AviateOneCm: return 17;
case Statistic::BoatOneCm: return 14;
case Statistic::CleanArmor: return 33;
case Statistic::CleanBanner: return 34;
case Statistic::ClimbOneCm: return 10;
case Statistic::CrouchOneCm: return 6;
case Statistic::DamageDealt: return 21;
case Statistic::DamageTaken: return 22;
case Statistic::Deaths: return 23;
case Statistic::Drop: return 20;
case Statistic::EatCakeSlice: return 30;
case Statistic::EnchantItem: return 45;
case Statistic::FallOneCm: return 9;
case Statistic::FillCauldron: return 31;
case Statistic::FishCaught: return 27;
case Statistic::FlyOneCm: return 11;
case Statistic::HorseOneCm: return 16;
case Statistic::InspectDispenser: return 39;
case Statistic::InspectDropper: return 37;
case Statistic::InspectHopper: return 38;
case Statistic::InteractWithBeacon: return 36;
case Statistic::InteractWithBrewingstand: return 35;
case Statistic::InteractWithCraftingTable: return 48;
case Statistic::InteractWithFurnace: return 47;
case Statistic::Jump: return 19;
case Statistic::LeaveGame: return 0;
case Statistic::MinecartOneCm: return 13;
case Statistic::MobKills: return 24;
case Statistic::OpenChest: return 49;
case Statistic::OpenEnderchest: return 44;
case Statistic::OpenShulkerBox: return 51;
case Statistic::PigOneCm: return 15;
case Statistic::PlayerKills: return 26;
case Statistic::PlayNoteblock: return 40;
case Statistic::PlayOneMinute: return 1;
case Statistic::PlayRecord: return 46;
case Statistic::PotFlower: return 42;
case Statistic::SleepInBed: return 50;
case Statistic::SneakTime: return 4;
case Statistic::SprintOneCm: return 7;
case Statistic::SwimOneCm: return 8;
case Statistic::TalkedToVillager: return 28;
case Statistic::TimeSinceDeath: return 2;
case Statistic::TimeSinceRest: return 3;
case Statistic::TradedWithVillager: return 29;
case Statistic::TriggerTrappedChest: return 43;
case Statistic::TuneNoteblock: return 41;
case Statistic::UseCauldron: return 32;
case Statistic::WalkOneCm: return 5;
case Statistic::WalkOnWaterOneCm: return 18;
case Statistic::WalkUnderWaterOneCm: return 12;
default: return -1;
}
}
Item ToItem(const UInt32 ID)
{
switch (ID)
{

View File

@ -1,10 +1,12 @@
#pragma once
#include "../../Registries/Items.h"
#include "../../Registries/Statistics.h"
namespace Palette_1_13
{
UInt32 FromBlock(short ID);
UInt32 FromItem(Item ID);
UInt32 From(Statistic ID);
Item ToItem(UInt32 ID);
}

View File

@ -5,7 +5,7 @@
namespace Palette_1_13_1
{
UInt32 FromBlock(short ID)
UInt32 FromBlock(const short ID)
{
using namespace Block;
@ -7072,7 +7072,7 @@ namespace Palette_1_13_1
}
}
UInt32 FromItem(Item ID)
UInt32 FromItem(const Item ID)
{
switch (ID)
{
@ -7870,7 +7870,73 @@ namespace Palette_1_13_1
}
}
Item ToItem(UInt32 ID)
UInt32 From(const Statistic ID)
{
switch (ID)
{
case Statistic::AnimalsBred: return 30;
case Statistic::AviateOneCm: return 17;
case Statistic::BoatOneCm: return 14;
case Statistic::CleanArmor: return 38;
case Statistic::CleanBanner: return 39;
case Statistic::CleanShulkerBox: return 40;
case Statistic::ClimbOneCm: return 10;
case Statistic::CrouchOneCm: return 6;
case Statistic::DamageAbsorbed: return 26;
case Statistic::DamageBlockedByShield: return 25;
case Statistic::DamageDealt: return 21;
case Statistic::DamageDealtAbsorbed: return 22;
case Statistic::DamageDealtResisted: return 23;
case Statistic::DamageResisted: return 27;
case Statistic::DamageTaken: return 24;
case Statistic::Deaths: return 28;
case Statistic::Drop: return 20;
case Statistic::EatCakeSlice: return 35;
case Statistic::EnchantItem: return 51;
case Statistic::FallOneCm: return 9;
case Statistic::FillCauldron: return 36;
case Statistic::FishCaught: return 32;
case Statistic::FlyOneCm: return 11;
case Statistic::HorseOneCm: return 16;
case Statistic::InspectDispenser: return 45;
case Statistic::InspectDropper: return 43;
case Statistic::InspectHopper: return 44;
case Statistic::InteractWithBeacon: return 42;
case Statistic::InteractWithBrewingstand: return 41;
case Statistic::InteractWithCraftingTable: return 54;
case Statistic::InteractWithFurnace: return 53;
case Statistic::Jump: return 19;
case Statistic::LeaveGame: return 0;
case Statistic::MinecartOneCm: return 13;
case Statistic::MobKills: return 29;
case Statistic::OpenChest: return 55;
case Statistic::OpenEnderchest: return 50;
case Statistic::OpenShulkerBox: return 57;
case Statistic::PigOneCm: return 15;
case Statistic::PlayerKills: return 31;
case Statistic::PlayNoteblock: return 46;
case Statistic::PlayOneMinute: return 1;
case Statistic::PlayRecord: return 52;
case Statistic::PotFlower: return 48;
case Statistic::SleepInBed: return 56;
case Statistic::SneakTime: return 4;
case Statistic::SprintOneCm: return 7;
case Statistic::SwimOneCm: return 8;
case Statistic::TalkedToVillager: return 33;
case Statistic::TimeSinceDeath: return 2;
case Statistic::TimeSinceRest: return 3;
case Statistic::TradedWithVillager: return 34;
case Statistic::TriggerTrappedChest: return 49;
case Statistic::TuneNoteblock: return 47;
case Statistic::UseCauldron: return 37;
case Statistic::WalkOneCm: return 5;
case Statistic::WalkOnWaterOneCm: return 18;
case Statistic::WalkUnderWaterOneCm: return 12;
default: return -1;
}
}
Item ToItem(const UInt32 ID)
{
switch (ID)
{

View File

@ -1,10 +1,12 @@
#pragma once
#include "../../Registries/Items.h"
#include "../../Registries/Statistics.h"
namespace Palette_1_13_1
{
UInt32 FromBlock(short ID);
UInt32 FromItem(Item ID);
UInt32 From(Statistic ID);
Item ToItem(UInt32 ID);
}

View File

@ -5,7 +5,7 @@
namespace Palette_1_14
{
UInt32 FromBlock(short ID)
UInt32 FromBlock(const short ID)
{
using namespace Block;
@ -8620,7 +8620,7 @@ namespace Palette_1_14
}
}
UInt32 FromItem(Item ID)
UInt32 FromItem(const Item ID)
{
switch (ID)
{
@ -9505,7 +9505,84 @@ namespace Palette_1_14
}
}
Item ToItem(UInt32 ID)
UInt32 From(const Statistic ID)
{
switch (ID)
{
case Statistic::AnimalsBred: return 30;
case Statistic::AviateOneCm: return 17;
case Statistic::BellRing: return 66;
case Statistic::BoatOneCm: return 14;
case Statistic::CleanArmor: return 38;
case Statistic::CleanBanner: return 39;
case Statistic::CleanShulkerBox: return 40;
case Statistic::ClimbOneCm: return 10;
case Statistic::CrouchOneCm: return 6;
case Statistic::DamageAbsorbed: return 26;
case Statistic::DamageBlockedByShield: return 25;
case Statistic::DamageDealt: return 21;
case Statistic::DamageDealtAbsorbed: return 22;
case Statistic::DamageDealtResisted: return 23;
case Statistic::DamageResisted: return 27;
case Statistic::DamageTaken: return 24;
case Statistic::Deaths: return 28;
case Statistic::Drop: return 20;
case Statistic::EatCakeSlice: return 35;
case Statistic::EnchantItem: return 51;
case Statistic::FallOneCm: return 9;
case Statistic::FillCauldron: return 36;
case Statistic::FishCaught: return 32;
case Statistic::FlyOneCm: return 11;
case Statistic::HorseOneCm: return 16;
case Statistic::InspectDispenser: return 45;
case Statistic::InspectDropper: return 43;
case Statistic::InspectHopper: return 44;
case Statistic::InteractWithBeacon: return 42;
case Statistic::InteractWithBlastFurnace: return 59;
case Statistic::InteractWithBrewingstand: return 41;
case Statistic::InteractWithCampfire: return 62;
case Statistic::InteractWithCartographyTable: return 63;
case Statistic::InteractWithCraftingTable: return 54;
case Statistic::InteractWithFurnace: return 53;
case Statistic::InteractWithLectern: return 61;
case Statistic::InteractWithLoom: return 64;
case Statistic::InteractWithSmoker: return 60;
case Statistic::InteractWithStonecutter: return 65;
case Statistic::Jump: return 19;
case Statistic::LeaveGame: return 0;
case Statistic::MinecartOneCm: return 13;
case Statistic::MobKills: return 29;
case Statistic::OpenBarrel: return 58;
case Statistic::OpenChest: return 55;
case Statistic::OpenEnderchest: return 50;
case Statistic::OpenShulkerBox: return 57;
case Statistic::PigOneCm: return 15;
case Statistic::PlayerKills: return 31;
case Statistic::PlayNoteblock: return 46;
case Statistic::PlayOneMinute: return 1;
case Statistic::PlayRecord: return 52;
case Statistic::PotFlower: return 48;
case Statistic::RaidTrigger: return 67;
case Statistic::RaidWin: return 68;
case Statistic::SleepInBed: return 56;
case Statistic::SneakTime: return 4;
case Statistic::SprintOneCm: return 7;
case Statistic::SwimOneCm: return 18;
case Statistic::TalkedToVillager: return 33;
case Statistic::TimeSinceDeath: return 2;
case Statistic::TimeSinceRest: return 3;
case Statistic::TradedWithVillager: return 34;
case Statistic::TriggerTrappedChest: return 49;
case Statistic::TuneNoteblock: return 47;
case Statistic::UseCauldron: return 37;
case Statistic::WalkOneCm: return 5;
case Statistic::WalkOnWaterOneCm: return 8;
case Statistic::WalkUnderWaterOneCm: return 12;
default: return -1;
}
}
Item ToItem(const UInt32 ID)
{
switch (ID)
{

View File

@ -1,10 +1,12 @@
#pragma once
#include "../../Registries/Items.h"
#include "../../Registries/Statistics.h"
namespace Palette_1_14
{
UInt32 FromBlock(short ID);
UInt32 FromItem(Item ID);
UInt32 From(Statistic ID);
Item ToItem(UInt32 ID);
}

View File

@ -155,7 +155,45 @@ void cProtocol_1_13::SendScoreboardObjective(const AString & a_Name, const AStri
void cProtocol_1_13::SendStatistics(const cStatManager & a_Manager)
{
// TODO
ASSERT(m_State == 3); // In game mode?
UInt32 Size = 0;
a_Manager.ForEachStatisticType([this, &Size](const auto & Store)
{
for (const auto & Item : Store)
{
// Client balks at out-of-range values so there is no good default value
// We're forced to not send the statistics this protocol version doesn't support
if (GetProtocolStatisticType(Item.first) != static_cast<UInt32>(-1))
{
Size++;
}
}
});
// No need to check Size != 0
// Assume that the vast majority of the time there's at least one statistic to send
cPacketizer Pkt(*this, pktStatistics);
Pkt.WriteVarInt32(Size);
a_Manager.ForEachStatisticType([this, &Pkt](const cStatManager::CustomStore & Store)
{
for (const auto & Item : Store)
{
const auto ID = GetProtocolStatisticType(Item.first);
if (ID == static_cast<UInt32>(-1))
{
// Unsupported, don't send:
continue;
}
Pkt.WriteVarInt32(8); // "Custom" category
Pkt.WriteVarInt32(ID);
Pkt.WriteVarInt32(static_cast<UInt32>(Item.second));
}
});
}
@ -563,6 +601,15 @@ UInt32 cProtocol_1_13::GetProtocolIDFromItem(short a_ItemID, short a_ItemDamage)
UInt32 cProtocol_1_13::GetProtocolStatisticType(Statistic a_Statistic)
{
return Palette_1_13::From(a_Statistic);
}
bool cProtocol_1_13::ReadItem(cByteBuffer & a_ByteBuffer, cItem & a_Item, size_t a_KeepRemainingBytes)
{
HANDLE_PACKET_READ(a_ByteBuffer, ReadBEInt16, Int16, ItemID);
@ -1210,6 +1257,15 @@ UInt32 cProtocol_1_13_1::GetProtocolIDFromItem(short a_ItemID, short a_ItemDamag
UInt32 cProtocol_1_13_1::GetProtocolStatisticType(Statistic a_Statistic)
{
return Palette_1_13_1::From(a_Statistic);
}
////////////////////////////////////////////////////////////////////////////////
// cProtocol_1_13_2:

View File

@ -79,6 +79,7 @@ protected:
virtual UInt8 GetEntityMetadataID(eEntityMetadataType a_FieldType);
virtual std::pair<short, short> GetItemFromProtocolID(UInt32 a_ProtocolID);
virtual UInt32 GetProtocolIDFromItem(short a_ItemID, short a_ItemDamage);
virtual UInt32 GetProtocolStatisticType(Statistic a_Statistic);
virtual bool HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) override;
virtual void HandlePacketPluginMessage(cByteBuffer & a_ByteBuffer) override;
@ -111,6 +112,7 @@ protected:
virtual Version GetProtocolVersion() override;
virtual std::pair<short, short> GetItemFromProtocolID(UInt32 a_ProtocolID) override;
virtual UInt32 GetProtocolIDFromItem(short a_ItemID, short a_ItemDamage) override;
virtual UInt32 GetProtocolStatisticType(Statistic a_Statistic) override;
};

View File

@ -303,3 +303,12 @@ UInt32 cProtocol_1_14::GetProtocolIDFromItem(short a_ItemID, short a_ItemDamage)
{
return Palette_1_14::FromItem(PaletteUpgrade::FromItem(a_ItemID, a_ItemDamage));
}
UInt32 cProtocol_1_14::GetProtocolStatisticType(Statistic a_Statistic)
{
return Palette_1_14::From(a_Statistic);
}

View File

@ -53,6 +53,7 @@ protected:
virtual std::pair<short, short> GetItemFromProtocolID(UInt32 a_ProtocolID) override;
virtual UInt32 GetProtocolIDFromItem(short a_ItemID, short a_ItemDamage) override;
virtual UInt32 GetProtocolStatisticType(Statistic a_Statistic) override;
virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) override {}
virtual void WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) override {}

View File

@ -19,7 +19,6 @@ Implements the 1.8 protocol classes:
#include "../EffectID.h"
#include "../StringCompression.h"
#include "../CompositeChat.h"
#include "../Statistics.h"
#include "../UUID.h"
#include "../World.h"
#include "../JsonUtils.h"
@ -1421,18 +1420,26 @@ void cProtocol_1_8_0::SendStatistics(const cStatManager & a_Manager)
{
ASSERT(m_State == 3); // In game mode?
cPacketizer Pkt(*this, pktStatistics);
Pkt.WriteVarInt32(statCount); // TODO 2014-05-11 xdot: Optimization: Send "dirty" statistics only
size_t Count = static_cast<size_t>(statCount);
for (size_t i = 0; i < Count; ++i)
UInt32 Size = 0;
a_Manager.ForEachStatisticType([&Size](const auto & Store)
{
StatValue Value = a_Manager.GetValue(static_cast<eStatistic>(i));
const AString & StatName = cStatInfo::GetName(static_cast<eStatistic>(i));
Size += static_cast<UInt32>(Store.size());
});
Pkt.WriteString(StatName);
Pkt.WriteVarInt32(static_cast<UInt32>(Value));
// No need to check Size != 0
// Assume that the vast majority of the time there's at least one statistic to send
cPacketizer Pkt(*this, pktStatistics);
Pkt.WriteVarInt32(Size);
a_Manager.ForEachStatisticType([&Pkt](const cStatManager::CustomStore & Store)
{
for (const auto & Item : Store)
{
Pkt.WriteString(GetProtocolStatisticName(Item.first));
Pkt.WriteVarInt32(static_cast<UInt32>(Item.second));
}
});
}
@ -2565,7 +2572,7 @@ void cProtocol_1_8_0::HandlePacketClientStatus(cByteBuffer & a_ByteBuffer)
case 2:
{
// Open Inventory achievement
m_Client->GetPlayer()->AwardAchievement(achOpenInv);
m_Client->GetPlayer()->AwardAchievement(Statistic::AchOpenInventory);
break;
}
}
@ -3968,3 +3975,104 @@ UInt8 cProtocol_1_8_0::GetProtocolEntityType(const cEntity & a_Entity)
}
UNREACHABLE("Unhandled entity kind");
}
const char * cProtocol_1_8_0::GetProtocolStatisticName(Statistic a_Statistic)
{
switch (a_Statistic)
{
// V1.8 Achievements
case Statistic::AchOpenInventory: return "achievement.openInventory";
case Statistic::AchMineWood: return "achievement.mineWood";
case Statistic::AchBuildWorkBench: return "achievement.buildWorkBench";
case Statistic::AchBuildPickaxe: return "achievement.buildPickaxe";
case Statistic::AchBuildFurnace: return "achievement.buildFurnace";
case Statistic::AchAcquireIron: return "achievement.acquireIron";
case Statistic::AchBuildHoe: return "achievement.buildHoe";
case Statistic::AchMakeBread: return "achievement.makeBread";
case Statistic::AchBakeCake: return "achievement.bakeCake";
case Statistic::AchBuildBetterPickaxe: return "achievement.buildBetterPickaxe";
case Statistic::AchCookFish: return "achievement.cookFish";
case Statistic::AchOnARail: return "achievement.onARail";
case Statistic::AchBuildSword: return "achievement.buildSword";
case Statistic::AchKillEnemy: return "achievement.killEnemy";
case Statistic::AchKillCow: return "achievement.killCow";
case Statistic::AchFlyPig: return "achievement.flyPig";
case Statistic::AchSnipeSkeleton: return "achievement.snipeSkeleton";
case Statistic::AchDiamonds: return "achievement.diamonds";
case Statistic::AchPortal: return "achievement.portal";
case Statistic::AchGhast: return "achievement.ghast";
case Statistic::AchBlazeRod: return "achievement.blazeRod";
case Statistic::AchPotion: return "achievement.potion";
case Statistic::AchTheEnd: return "achievement.theEnd";
case Statistic::AchTheEnd2: return "achievement.theEnd2";
case Statistic::AchEnchantments: return "achievement.enchantments";
case Statistic::AchOverkill: return "achievement.overkill";
case Statistic::AchBookcase: return "achievement.bookcase";
case Statistic::AchExploreAllBiomes: return "achievement.exploreAllBiomes";
case Statistic::AchSpawnWither: return "achievement.spawnWither";
case Statistic::AchKillWither: return "achievement.killWither";
case Statistic::AchFullBeacon: return "achievement.fullBeacon";
case Statistic::AchBreedCow: return "achievement.breedCow";
case Statistic::AchDiamondsToYou: return "achievement.diamondsToYou";
// V1.8 stats
case Statistic::AnimalsBred: return "stat.animalsBred";
case Statistic::BoatOneCm: return "stat.boatOneCm";
case Statistic::ClimbOneCm: return "stat.climbOneCm";
case Statistic::CrouchOneCm: return "stat.crouchOneCm";
case Statistic::DamageDealt: return "stat.damageDealt";
case Statistic::DamageTaken: return "stat.damageTaken";
case Statistic::Deaths: return "stat.deaths";
case Statistic::Drop: return "stat.drop";
case Statistic::FallOneCm: return "stat.fallOneCm";
case Statistic::FishCaught: return "stat.fishCaught";
case Statistic::FlyOneCm: return "stat.flyOneCm";
case Statistic::HorseOneCm: return "stat.horseOneCm";
case Statistic::Jump: return "stat.jump";
case Statistic::LeaveGame: return "stat.leaveGame";
case Statistic::MinecartOneCm: return "stat.minecartOneCm";
case Statistic::MobKills: return "stat.mobKills";
case Statistic::PigOneCm: return "stat.pigOneCm";
case Statistic::PlayerKills: return "stat.playerKills";
case Statistic::PlayOneMinute: return "stat.playOneMinute";
case Statistic::SprintOneCm: return "stat.sprintOneCm";
case Statistic::SwimOneCm: return "stat.swimOneCm";
case Statistic::TalkedToVillager: return "stat.talkedToVillager";
case Statistic::TimeSinceDeath: return "stat.timeSinceDeath";
case Statistic::TradedWithVillager: return "stat.tradedWithVillager";
case Statistic::WalkOneCm: return "stat.walkOneCm";
case Statistic::WalkUnderWaterOneCm: return "stat.diveOneCm";
// V1.8.2 stats
case Statistic::CleanArmor: return "stat.armorCleaned";
case Statistic::CleanBanner: return "stat.bannerCleaned";
case Statistic::EatCakeSlice: return "stat.cakeSlicesEaten";
case Statistic::EnchantItem: return "stat.itemEnchanted";
case Statistic::FillCauldron: return "stat.cauldronFilled";
case Statistic::InspectDispenser: return "stat.dispenserInspected";
case Statistic::InspectDropper: return "stat.dropperInspected";
case Statistic::InspectHopper: return "stat.hopperInspected";
case Statistic::InteractWithBeacon: return "stat.beaconInteraction";
case Statistic::InteractWithBrewingstand: return "stat.brewingstandInteraction";
case Statistic::InteractWithCraftingTable: return "stat.craftingTableInteraction";
case Statistic::InteractWithFurnace: return "stat.furnaceInteraction";
case Statistic::OpenChest: return "stat.chestOpened";
case Statistic::OpenEnderchest: return "stat.enderchestOpened";
case Statistic::PlayNoteblock: return "stat.noteblockPlayed";
case Statistic::PlayRecord: return "stat.recordPlayed";
case Statistic::PotFlower: return "stat.flowerPotted";
case Statistic::TriggerTrappedChest: return "stat.trappedChestTriggered";
case Statistic::TuneNoteblock: return "stat.noteblockTuned";
case Statistic::UseCauldron: return "stat.cauldronUsed";
// V1.9 stats
case Statistic::AviateOneCm: return "stat.aviateOneCm";
case Statistic::SleepInBed: return "stat.sleepInBed";
case Statistic::SneakTime: return "stat.sneakTime";
default: return "";
}
}

View File

@ -15,6 +15,7 @@ Declares the 1.8 protocol classes:
#include "Protocol.h"
#include "../ByteBuffer.h"
#include "../Registries/Statistics.h"
#include "../mbedTLS++/AesCfb128Decryptor.h"
#include "../mbedTLS++/AesCfb128Encryptor.h"
@ -254,5 +255,10 @@ private:
/** Converts an entity to a protocol-specific entity type.
Only entities that the Send Spawn Entity packet supports are valid inputs to this method */
UInt8 GetProtocolEntityType(const cEntity & a_Entity);
static UInt8 GetProtocolEntityType(const cEntity & a_Entity);
/** Converts a statistic to a protocol-specific string.
Protocols <= 1.12 use strings, hence this is a static as the string-mapping was append-only for the versions that used it.
Returns an empty string, handled correctly by the client, for newer, unsupported statistics. */
static const char * GetProtocolStatisticName(Statistic a_Statistic);
} ;

View File

@ -24,7 +24,6 @@ Implements the 1.9 protocol classes:
#include "../World.h"
#include "../StringCompression.h"
#include "../CompositeChat.h"
#include "../Statistics.h"
#include "../JsonUtils.h"
#include "../WorldStorage/FastNBT.h"

View File

@ -5,4 +5,5 @@ target_sources(
Blocks.h
Items.h
Statistics.h
)

120
src/Registries/Statistics.h Normal file
View File

@ -0,0 +1,120 @@
#pragma once
enum class Statistic
{
/* Achievements */
AchOpenInventory, /* Taking Inventory */
AchMineWood, /* Getting Wood */
AchBuildWorkBench, /* Benchmarking */
AchBuildPickaxe, /* Time to Mine! */
AchBuildFurnace, /* Hot Topic */
AchAcquireIron, /* Acquire Hardware */
AchBuildHoe, /* Time to Farm! */
AchMakeBread, /* Bake Bread */
AchBakeCake, /* The Lie */
AchBuildBetterPickaxe, /* Getting an Upgrade */
AchCookFish, /* Delicious Fish */
AchOnARail, /* On A Rail */
AchBuildSword, /* Time to Strike! */
AchKillEnemy, /* Monster Hunter */
AchKillCow, /* Cow Tipper */
AchFlyPig, /* When Pigs Fly */
AchSnipeSkeleton, /* Sniper Duel */
AchDiamonds, /* DIAMONDS! */
AchPortal, /* We Need to Go Deeper */
AchGhast, /* Return to Sender */
AchBlazeRod, /* Into Fire */
AchPotion, /* Local Brewery */
AchTheEnd, /* The End? */
AchTheEnd2, /* The End. */
AchEnchantments, /* Enchanter */
AchOverkill, /* Overkill */
AchBookcase, /* Librarian */
AchExploreAllBiomes, /* Adventuring Time */
AchSpawnWither, /* The Beginning? */
AchKillWither, /* The Beginning. */
AchFullBeacon, /* Beaconator */
AchBreedCow, /* Repopulation */
AchDiamondsToYou, /* Diamonds to you! */
/* Statistics */
AnimalsBred,
AviateOneCm,
BellRing,
BoatOneCm,
CleanArmor,
CleanBanner,
CleanShulkerBox,
ClimbOneCm,
CrouchOneCm,
DamageAbsorbed,
DamageBlockedByShield,
DamageDealt,
DamageDealtAbsorbed,
DamageDealtResisted,
DamageResisted,
DamageTaken,
Deaths,
Drop,
EatCakeSlice,
EnchantItem,
FallOneCm,
FillCauldron,
FishCaught,
FlyOneCm,
HorseOneCm,
InspectDispenser,
InspectDropper,
InspectHopper,
InteractWithAnvil,
InteractWithBeacon,
InteractWithBlastFurnace,
InteractWithBrewingstand,
InteractWithCampfire,
InteractWithCartographyTable,
InteractWithCraftingTable,
InteractWithFurnace,
InteractWithGrindstone,
InteractWithLectern,
InteractWithLoom,
InteractWithSmithingTable,
InteractWithSmoker,
InteractWithStonecutter,
Jump,
LeaveGame,
MinecartOneCm,
MobKills,
OpenBarrel,
OpenChest,
OpenEnderchest,
OpenShulkerBox,
PigOneCm,
PlayNoteblock,
PlayOneMinute,
PlayRecord,
PlayerKills,
PotFlower,
RaidTrigger,
RaidWin,
SleepInBed,
SneakTime,
SprintOneCm,
StriderOneCm,
SwimOneCm,
TalkedToVillager,
TargetHit,
TimeSinceDeath,
TimeSinceRest,
TradedWithVillager,
TriggerTrappedChest,
TuneNoteblock,
UseCauldron,
WalkOnWaterOneCm,
WalkOneCm,
WalkUnderWaterOneCm,
// Old ones just for compatibility
JunkFished,
TreasureFished,
};

View File

@ -6,199 +6,78 @@
#include "Statistics.h"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#endif
cStatInfo cStatInfo::ms_Info[statCount] =
void cStatManager::SetValue(const Statistic a_Stat, const StatValue a_Value)
{
// The order must match the order of enum eStatistic
// https://minecraft.gamepedia.com/Achievements
/* Type | Name | Prerequisite */
cStatInfo(achOpenInv, "achievement.openInventory"),
cStatInfo(achMineWood, "achievement.mineWood", achOpenInv),
cStatInfo(achCraftWorkbench, "achievement.buildWorkBench", achMineWood),
cStatInfo(achCraftPickaxe, "achievement.buildPickaxe", achCraftWorkbench),
cStatInfo(achCraftFurnace, "achievement.buildFurnace", achCraftPickaxe),
cStatInfo(achAcquireIron, "achievement.acquireIron", achCraftFurnace),
cStatInfo(achCraftHoe, "achievement.buildHoe", achCraftWorkbench),
cStatInfo(achMakeBread, "achievement.makeBread", achCraftHoe),
cStatInfo(achBakeCake, "achievement.bakeCake", achCraftHoe),
cStatInfo(achCraftBetterPick, "achievement.buildBetterPickaxe", achCraftPickaxe),
cStatInfo(achCookFish, "achievement.cookFish", achAcquireIron),
cStatInfo(achOnARail, "achievement.onARail", achAcquireIron),
cStatInfo(achCraftSword, "achievement.buildSword", achCraftWorkbench),
cStatInfo(achKillMonster, "achievement.killEnemy", achCraftSword),
cStatInfo(achKillCow, "achievement.killCow", achCraftSword),
cStatInfo(achFlyPig, "achievement.flyPig", achKillCow),
cStatInfo(achSnipeSkeleton, "achievement.snipeSkeleton", achKillMonster),
cStatInfo(achDiamonds, "achievement.diamonds", achAcquireIron),
cStatInfo(achEnterPortal, "achievement.portal", achDiamonds),
cStatInfo(achReturnToSender, "achievement.ghast", achEnterPortal),
cStatInfo(achBlazeRod, "achievement.blazeRod", achEnterPortal),
cStatInfo(achBrewPotion, "achievement.potion", achBlazeRod),
cStatInfo(achEnterTheEnd, "achievement.theEnd", achBlazeRod),
cStatInfo(achDefeatDragon, "achievement.theEnd2", achEnterTheEnd),
cStatInfo(achCraftEnchantTable, "achievement.enchantments", achDiamonds),
cStatInfo(achOverkill, "achievement.overkill", achCraftEnchantTable),
cStatInfo(achBookshelf, "achievement.bookcase", achCraftEnchantTable),
cStatInfo(achExploreAllBiomes, "achievement.exploreAllBiomes", achEnterTheEnd),
cStatInfo(achSpawnWither, "achievement.spawnWither", achDefeatDragon),
cStatInfo(achKillWither, "achievement.killWither", achSpawnWither),
cStatInfo(achFullBeacon, "achievement.fullBeacon", achKillWither),
cStatInfo(achBreedCow, "achievement.breedCow", achKillCow),
cStatInfo(achThrowDiamonds, "achievement.diamondsToYou", achDiamonds),
// https://minecraft.gamepedia.com/Statistics
/* Type | Name */
cStatInfo(statGamesQuit, "stat.leaveGame"),
cStatInfo(statMinutesPlayed, "stat.playOneMinute"),
cStatInfo(statDistWalked, "stat.walkOneCm"),
cStatInfo(statDistSwum, "stat.swimOneCm"),
cStatInfo(statDistFallen, "stat.fallOneCm"),
cStatInfo(statDistClimbed, "stat.climbOneCm"),
cStatInfo(statDistFlown, "stat.flyOneCm"),
cStatInfo(statDistDove, "stat.diveOneCm"),
cStatInfo(statDistMinecart, "stat.minecartOneCm"),
cStatInfo(statDistBoat, "stat.boatOneCm"),
cStatInfo(statDistPig, "stat.pigOneCm"),
cStatInfo(statDistHorse, "stat.horseOneCm"),
cStatInfo(statJumps, "stat.jump"),
cStatInfo(statItemsDropped, "stat.drop"),
cStatInfo(statDamageDealt, "stat.damageDealt"),
cStatInfo(statDamageTaken, "stat.damageTaken"),
cStatInfo(statDeaths, "stat.deaths"),
cStatInfo(statMobKills, "stat.mobKills"),
cStatInfo(statAnimalsBred, "stat.animalsBred"),
cStatInfo(statPlayerKills, "stat.playerKills"),
cStatInfo(statFishCaught, "stat.fishCaught"),
cStatInfo(statJunkFished, "stat.junkFished"),
cStatInfo(statTreasureFished, "stat.treasureFished")
};
#ifdef __clang__
#pragma clang diagnostic pop
#endif
cStatInfo::cStatInfo()
: m_Type(statInvalid)
, m_Depends(statInvalid)
{}
cStatInfo::cStatInfo(const eStatistic a_Type, const AString & a_Name, const eStatistic a_Depends)
: m_Type(a_Type)
, m_Name(a_Name)
, m_Depends(a_Depends)
{}
const AString & cStatInfo::GetName(const eStatistic a_Type)
{
ASSERT((a_Type > statInvalid) && (a_Type < statCount));
return ms_Info[a_Type].m_Name;
m_CustomStatistics[a_Stat] = a_Value;
}
eStatistic cStatInfo::GetType(const AString & a_Name)
cStatManager::StatValue cStatManager::AddValue(const Statistic a_Stat, const StatValue a_Delta)
{
for (unsigned int i = 0; i < ARRAYCOUNT(ms_Info); ++i)
{
if (NoCaseCompare(ms_Info[i].m_Name, a_Name) == 0)
{
return ms_Info[i].m_Type;
}
}
return statInvalid;
return m_CustomStatistics[a_Stat] += a_Delta;
}
eStatistic cStatInfo::GetPrerequisite(const eStatistic a_Type)
bool cStatManager::SatisfiesPrerequisite(const Statistic a_Stat)
{
ASSERT((a_Type > statInvalid) && (a_Type < statCount));
switch (a_Stat)
{
case Statistic::AchMineWood: return IsStatisticPresent(Statistic::AchOpenInventory);
case Statistic::AchBuildWorkBench: return IsStatisticPresent(Statistic::AchMineWood);
case Statistic::AchBuildHoe: return IsStatisticPresent(Statistic::AchBuildWorkBench);
case Statistic::AchBakeCake: return IsStatisticPresent(Statistic::AchBuildHoe);
case Statistic::AchMakeBread: return IsStatisticPresent(Statistic::AchBuildHoe);
case Statistic::AchBuildSword: return IsStatisticPresent(Statistic::AchBuildWorkBench);
case Statistic::AchKillCow: return IsStatisticPresent(Statistic::AchBuildSword);
case Statistic::AchFlyPig: return IsStatisticPresent(Statistic::AchKillCow);
case Statistic::AchBreedCow: return IsStatisticPresent(Statistic::AchKillCow);
case Statistic::AchKillEnemy: return IsStatisticPresent(Statistic::AchBuildSword);
case Statistic::AchSnipeSkeleton: return IsStatisticPresent(Statistic::AchKillEnemy);
case Statistic::AchBuildPickaxe: return IsStatisticPresent(Statistic::AchBuildWorkBench);
case Statistic::AchBuildBetterPickaxe: return IsStatisticPresent(Statistic::AchBuildPickaxe);
case Statistic::AchBuildFurnace: return IsStatisticPresent(Statistic::AchBuildWorkBench);
case Statistic::AchCookFish: return IsStatisticPresent(Statistic::AchBuildFurnace);
case Statistic::AchAcquireIron: return IsStatisticPresent(Statistic::AchBuildFurnace);
case Statistic::AchOnARail: return IsStatisticPresent(Statistic::AchAcquireIron);
case Statistic::AchDiamonds: return IsStatisticPresent(Statistic::AchAcquireIron);
case Statistic::AchPortal: return IsStatisticPresent(Statistic::AchDiamonds);
case Statistic::AchGhast: return IsStatisticPresent(Statistic::AchPortal);
case Statistic::AchBlazeRod: return IsStatisticPresent(Statistic::AchPortal);
case Statistic::AchPotion: return IsStatisticPresent(Statistic::AchBlazeRod);
case Statistic::AchTheEnd: return IsStatisticPresent(Statistic::AchBlazeRod);
case Statistic::AchTheEnd2: return IsStatisticPresent(Statistic::AchTheEnd);
case Statistic::AchEnchantments: return IsStatisticPresent(Statistic::AchDiamonds);
case Statistic::AchOverkill: return IsStatisticPresent(Statistic::AchEnchantments);
case Statistic::AchBookcase: return IsStatisticPresent(Statistic::AchEnchantments);
case Statistic::AchExploreAllBiomes: return IsStatisticPresent(Statistic::AchTheEnd);
case Statistic::AchSpawnWither: return IsStatisticPresent(Statistic::AchTheEnd2);
case Statistic::AchKillWither: return IsStatisticPresent(Statistic::AchSpawnWither);
case Statistic::AchFullBeacon: return IsStatisticPresent(Statistic::AchKillWither);
case Statistic::AchDiamondsToYou: return IsStatisticPresent(Statistic::AchDiamonds);
}
return ms_Info[a_Type].m_Depends;
return true;
}
cStatManager::cStatManager()
bool cStatManager::IsStatisticPresent(const Statistic a_Stat) const
{
Reset();
}
StatValue cStatManager::GetValue(const eStatistic a_Stat) const
const auto Result = m_CustomStatistics.find(a_Stat);
if (Result != m_CustomStatistics.end())
{
ASSERT((a_Stat > statInvalid) && (a_Stat < statCount));
return m_MainStats[a_Stat];
return Result->second > 0;
}
void cStatManager::SetValue(const eStatistic a_Stat, const StatValue a_Value)
{
ASSERT((a_Stat > statInvalid) && (a_Stat < statCount));
m_MainStats[a_Stat] = a_Value;
return false;
}
StatValue cStatManager::AddValue(const eStatistic a_Stat, const StatValue a_Delta)
{
ASSERT((a_Stat > statInvalid) && (a_Stat < statCount));
m_MainStats[a_Stat] += a_Delta;
return m_MainStats[a_Stat];
}
void cStatManager::Reset(void)
{
for (unsigned int i = 0; i < static_cast<unsigned int>(statCount); ++i)
{
m_MainStats[i] = 0;
}
}

View File

@ -1,162 +1,59 @@
// Statistics.h
#pragma once
#include "Registries/Statistics.h"
/* Hello fellow developer !
In case you are trying to add new statistics to Cuberite you need to do a few things:
---------------------------------------------------------------------------
1. add a new entry to the enum class Statistic in Registries\Statistics.h file
2. add this to serialization functions in WorldStorage\NamespaceSerializer.cpp
The String in the above is used for saving on disk!
so use the same string!
In case you want to add a mapping of network IDs to the used stats
you will find a lua script in ../Tools/BlockTypePaletteGenerator/ExportStatMapping.lua
it will provide you with information how to use it. you need a registries.json
exported from the server https://wiki.vg/Data_Generators
// tolua_begin
enum eStatistic
{
// The order must match the order of cStatInfo::ms_Info
Greetings 12xx12 */
statInvalid = -1,
/* Achievements */
achOpenInv, /* Taking Inventory */
achMineWood, /* Getting Wood */
achCraftWorkbench, /* Benchmarking */
achCraftPickaxe, /* Time to Mine! */
achCraftFurnace, /* Hot Topic */
achAcquireIron, /* Acquire Hardware */
achCraftHoe, /* Time to Farm! */
achMakeBread, /* Bake Bread */
achBakeCake, /* The Lie */
achCraftBetterPick, /* Getting an Upgrade */
achCookFish, /* Delicious Fish */
achOnARail, /* On A Rail */
achCraftSword, /* Time to Strike! */
achKillMonster, /* Monster Hunter */
achKillCow, /* Cow Tipper */
achFlyPig, /* When Pigs Fly */
achSnipeSkeleton, /* Sniper Duel */
achDiamonds, /* DIAMONDS! */
achEnterPortal, /* We Need to Go Deeper */
achReturnToSender, /* Return to Sender */
achBlazeRod, /* Into Fire */
achBrewPotion, /* Local Brewery */
achEnterTheEnd, /* The End? */
achDefeatDragon, /* The End. */
achCraftEnchantTable, /* Enchanter */
achOverkill, /* Overkill */
achBookshelf, /* Librarian */
achExploreAllBiomes, /* Adventuring Time */
achSpawnWither, /* The Beginning? */
achKillWither, /* The Beginning. */
achFullBeacon, /* Beaconator */
achBreedCow, /* Repopulation */
achThrowDiamonds, /* Diamonds to you! */
/* Statistics */
statGamesQuit,
statMinutesPlayed,
statDistWalked,
statDistSwum,
statDistFallen,
statDistClimbed,
statDistFlown,
statDistDove,
statDistMinecart,
statDistBoat,
statDistPig,
statDistHorse,
statJumps,
statItemsDropped,
statDamageDealt,
statDamageTaken,
statDeaths,
statMobKills,
statAnimalsBred,
statPlayerKills,
statFishCaught,
statJunkFished,
statTreasureFished,
statCount
};
// tolua_end
/** Class used to store and query statistic-related information. */
class cStatInfo
{
public:
cStatInfo();
cStatInfo(const eStatistic a_Type, const AString & a_Name, const eStatistic a_Depends = statInvalid);
/** Type -> Name */
static const AString & GetName(const eStatistic a_Type);
/** Name -> Type */
static eStatistic GetType(const AString & a_Name);
/** Returns stat prerequisite. (Used for achievements) */
static eStatistic GetPrerequisite(const eStatistic a_Type);
private:
eStatistic m_Type;
AString m_Name;
eStatistic m_Depends;
static cStatInfo ms_Info[statCount];
};
/* Signed (?) integral value. */
typedef int StatValue; // tolua_export
/** Class that manages the statistics and achievements of a single player. */
// tolua_begin
class cStatManager
{
public:
// tolua_end
cStatManager();
typedef unsigned StatValue;
typedef std::unordered_map<Statistic, StatValue> CustomStore;
// tolua_begin
/** Set the value of the specified statistic. */
void SetValue(Statistic a_Stat, StatValue a_Value);
/** Return the value of the specified stat. */
StatValue GetValue(const eStatistic a_Stat) const;
/** Increments the specified statistic. Returns the new value. */
StatValue AddValue(Statistic a_Stat, StatValue a_Delta = 1);
/** Set the value of the specified stat. */
void SetValue(const eStatistic a_Stat, const StatValue a_Value);
/** Returns whether the prerequisite for awarding an achievement are satisfied. */
bool SatisfiesPrerequisite(Statistic a_Stat);
/** Reset everything. */
void Reset();
/** Increments the specified stat.
Returns the new value.
*/
StatValue AddValue(const eStatistic a_Stat, const StatValue a_Delta = 1);
// tolua_end
/** Invokes the given callbacks for each category of tracked statistics. */
template <class CustomCallback>
void ForEachStatisticType(CustomCallback a_Custom) const
{
a_Custom(m_CustomStatistics);
}
private:
StatValue m_MainStats[statCount];
// TODO 10-05-2014 xdot: Use, mine, craft statistics
}; // tolua_export
/** Returns if a statistic is both present and has nonzero value. */
bool IsStatisticPresent(Statistic a_Stat) const;
// TODO: Block tallies, entities killed, all the others
CustomStore m_CustomStatistics;
};

View File

@ -766,16 +766,16 @@ void cSlotAreaCrafting::HandleCraftItem(const cItem & a_Result, cPlayer & a_Play
{
switch (a_Result.m_ItemType)
{
case E_BLOCK_WORKBENCH: a_Player.AwardAchievement(achCraftWorkbench); break;
case E_BLOCK_FURNACE: a_Player.AwardAchievement(achCraftFurnace); break;
case E_BLOCK_CAKE: a_Player.AwardAchievement(achBakeCake); break;
case E_BLOCK_ENCHANTMENT_TABLE: a_Player.AwardAchievement(achCraftEnchantTable); break;
case E_BLOCK_BOOKCASE: a_Player.AwardAchievement(achBookshelf); break;
case E_ITEM_WOODEN_PICKAXE: a_Player.AwardAchievement(achCraftPickaxe); break;
case E_ITEM_WOODEN_SWORD: a_Player.AwardAchievement(achCraftSword); break;
case E_ITEM_STONE_PICKAXE: a_Player.AwardAchievement(achCraftBetterPick); break;
case E_ITEM_WOODEN_HOE: a_Player.AwardAchievement(achCraftHoe); break;
case E_ITEM_BREAD: a_Player.AwardAchievement(achMakeBread); break;
case E_BLOCK_WORKBENCH: a_Player.AwardAchievement(Statistic::AchBuildWorkBench); break;
case E_BLOCK_FURNACE: a_Player.AwardAchievement(Statistic::AchBuildFurnace); break;
case E_BLOCK_CAKE: a_Player.AwardAchievement(Statistic::AchBakeCake); break;
case E_BLOCK_ENCHANTMENT_TABLE: a_Player.AwardAchievement(Statistic::AchEnchantments); break;
case E_BLOCK_BOOKCASE: a_Player.AwardAchievement(Statistic::AchBookcase); break;
case E_ITEM_WOODEN_PICKAXE: a_Player.AwardAchievement(Statistic::AchBuildPickaxe); break;
case E_ITEM_WOODEN_SWORD: a_Player.AwardAchievement(Statistic::AchBuildSword); break;
case E_ITEM_STONE_PICKAXE: a_Player.AwardAchievement(Statistic::AchBuildBetterPickaxe); break;
case E_ITEM_WOODEN_HOE: a_Player.AwardAchievement(Statistic::AchBuildHoe); break;
case E_ITEM_BREAD: a_Player.AwardAchievement(Statistic::AchMakeBread); break;
default: break;
}
}
@ -2043,8 +2043,8 @@ void cSlotAreaFurnace::HandleSmeltItem(const cItem & a_Result, cPlayer & a_Playe
/** TODO 2014-05-12 xdot: Figure out when to call this method. */
switch (a_Result.m_ItemType)
{
case E_ITEM_IRON: a_Player.AwardAchievement(achAcquireIron); break;
case E_ITEM_COOKED_FISH: a_Player.AwardAchievement(achCookFish); break;
case E_ITEM_IRON: a_Player.AwardAchievement(Statistic::AchAcquireIron); break;
case E_ITEM_COOKED_FISH: a_Player.AwardAchievement(Statistic::AchCookFish); break;
default: break;
}
}
@ -2193,7 +2193,7 @@ void cSlotAreaBrewingstand::HandleBrewedItem(cPlayer & a_Player, const cItem & a
// Award an achievement if the item is not a water bottle (is a real brewed potion)
if (a_ClickedItem.m_ItemDamage > 0)
{
a_Player.AwardAchievement(achBrewPotion);
a_Player.AwardAchievement(Statistic::AchPotion);
}
}

View File

@ -5,6 +5,7 @@ target_sources(
FastNBT.cpp
FireworksSerializer.cpp
MapSerializer.cpp
NamespaceSerializer.cpp
NBTChunkSerializer.cpp
SchematicFileSerializer.cpp
ScoreboardSerializer.cpp
@ -16,6 +17,7 @@ target_sources(
FastNBT.h
FireworksSerializer.h
MapSerializer.h
NamespaceSerializer.h
NBTChunkSerializer.h
SchematicFileSerializer.h
ScoreboardSerializer.h

View File

@ -0,0 +1,281 @@
#include "Globals.h"
#include "NamespaceSerializer.h"
namespace NamespaceSerializer
{
unsigned DataVersion()
{
return 2566;
}
const char * From(const Statistic ID)
{
switch (ID)
{
case Statistic::AnimalsBred: return "animals_bred";
case Statistic::AviateOneCm: return "aviate_one_cm";
case Statistic::BellRing: return "bell_ring";
case Statistic::BoatOneCm: return "boat_one_cm";
case Statistic::CleanArmor: return "clean_armor";
case Statistic::CleanBanner: return "clean_banner";
case Statistic::CleanShulkerBox: return "clean_shulker_box";
case Statistic::ClimbOneCm: return "climb_one_cm";
case Statistic::CrouchOneCm: return "crouch_one_cm";
case Statistic::DamageAbsorbed: return "damage_absorbed";
case Statistic::DamageBlockedByShield: return "damage_blocked_by_shield";
case Statistic::DamageDealt: return "damage_dealt";
case Statistic::DamageDealtAbsorbed: return "damage_dealt_absorbed";
case Statistic::DamageDealtResisted: return "damage_dealt_resisted";
case Statistic::DamageResisted: return "damage_resisted";
case Statistic::DamageTaken: return "damage_taken";
case Statistic::Deaths: return "deaths";
case Statistic::Drop: return "drop";
case Statistic::EatCakeSlice: return "eat_cake_slice";
case Statistic::EnchantItem: return "enchant_item";
case Statistic::FallOneCm: return "fall_one_cm";
case Statistic::FillCauldron: return "fill_cauldron";
case Statistic::FishCaught: return "fish_caught";
case Statistic::FlyOneCm: return "fly_one_cm";
case Statistic::HorseOneCm: return "horse_one_cm";
case Statistic::InspectDispenser: return "inspect_dispenser";
case Statistic::InspectDropper: return "inspect_dropper";
case Statistic::InspectHopper: return "inspect_hopper";
case Statistic::InteractWithAnvil: return "interact_with_anvil";
case Statistic::InteractWithBeacon: return "interact_with_beacon";
case Statistic::InteractWithBlastFurnace: return "interact_with_blast_furnace";
case Statistic::InteractWithBrewingstand: return "interact_with_brewingstand";
case Statistic::InteractWithCampfire: return "interact_with_campfire";
case Statistic::InteractWithCartographyTable: return "interact_with_cartography_table";
case Statistic::InteractWithCraftingTable: return "interact_with_crafting_table";
case Statistic::InteractWithFurnace: return "interact_with_furnace";
case Statistic::InteractWithGrindstone: return "interact_with_grindstone";
case Statistic::InteractWithLectern: return "interact_with_lectern";
case Statistic::InteractWithLoom: return "interact_with_loom";
case Statistic::InteractWithSmithingTable: return "interact_with_smithing_table";
case Statistic::InteractWithSmoker: return "interact_with_smoker";
case Statistic::InteractWithStonecutter: return "interact_with_stonecutter";
case Statistic::Jump: return "jump";
case Statistic::LeaveGame: return "leave_game";
case Statistic::MinecartOneCm: return "minecart_one_cm";
case Statistic::MobKills: return "mob_kills";
case Statistic::OpenBarrel: return "open_barrel";
case Statistic::OpenChest: return "open_chest";
case Statistic::OpenEnderchest: return "open_enderchest";
case Statistic::OpenShulkerBox: return "open_shulker_box";
case Statistic::PigOneCm: return "pig_one_cm";
case Statistic::PlayNoteblock: return "play_noteblock";
case Statistic::PlayOneMinute: return "play_one_minute";
case Statistic::PlayRecord: return "play_record";
case Statistic::PlayerKills: return "player_kills";
case Statistic::PotFlower: return "pot_flower";
case Statistic::RaidTrigger: return "raid_trigger";
case Statistic::RaidWin: return "raid_win";
case Statistic::SleepInBed: return "sleep_in_bed";
case Statistic::SneakTime: return "sneak_time";
case Statistic::SprintOneCm: return "sprint_one_cm";
case Statistic::StriderOneCm: return "strider_one_cm";
case Statistic::SwimOneCm: return "swim_one_cm";
case Statistic::TalkedToVillager: return "talked_to_villager";
case Statistic::TargetHit: return "target_hit";
case Statistic::TimeSinceDeath: return "time_since_death";
case Statistic::TimeSinceRest: return "time_since_rest";
case Statistic::TradedWithVillager: return "traded_with_villager";
case Statistic::TriggerTrappedChest: return "trigger_trapped_chest";
case Statistic::TuneNoteblock: return "tune_noteblock";
case Statistic::UseCauldron: return "use_cauldron";
case Statistic::WalkOnWaterOneCm: return "walk_on_water_one_cm";
case Statistic::WalkOneCm: return "walk_one_cm";
case Statistic::WalkUnderWaterOneCm: return "walk_under_water_one_cm";
// Old ones just for compatibility
case Statistic::JunkFished: return "junk_fished";
case Statistic::TreasureFished: return "treasure_fished";
// The old advancements
case Statistic::AchOpenInventory: return "cuberite:achievement.openInventory";
case Statistic::AchMineWood: return "cuberite:achievement.mineWood";
case Statistic::AchBuildWorkBench: return "cuberite:achievement.buildWorkBench";
case Statistic::AchBuildPickaxe: return "cuberite:achievement.buildPickaxe";
case Statistic::AchBuildFurnace: return "cuberite:achievement.buildFurnace";
case Statistic::AchAcquireIron: return "cuberite:achievement.acquireIron";
case Statistic::AchBuildHoe: return "cuberite:achievement.buildHoe";
case Statistic::AchMakeBread: return "cuberite:achievement.makeBread";
case Statistic::AchBakeCake: return "cuberite:achievement.bakeCake";
case Statistic::AchBuildBetterPickaxe: return "cuberite:achievement.buildBetterPickaxe";
case Statistic::AchCookFish: return "cuberite:achievement.cookFish";
case Statistic::AchOnARail: return "cuberite:achievement.onARail";
case Statistic::AchBuildSword: return "cuberite:achievement.buildSword";
case Statistic::AchKillEnemy: return "cuberite:achievement.killEnemy";
case Statistic::AchKillCow: return "cuberite:achievement.killCow";
case Statistic::AchFlyPig: return "cuberite:achievement.flyPig";
case Statistic::AchSnipeSkeleton: return "cuberite:achievement.snipeSkeleton";
case Statistic::AchDiamonds: return "cuberite:achievement.diamonds";
case Statistic::AchPortal: return "cuberite:achievement.portal";
case Statistic::AchGhast: return "cuberite:achievement.ghast";
case Statistic::AchBlazeRod: return "cuberite:achievement.blazeRod";
case Statistic::AchPotion: return "cuberite:achievement.potion";
case Statistic::AchTheEnd: return "cuberite:achievement.theEnd";
case Statistic::AchTheEnd2: return "cuberite:achievement.theEnd2";
case Statistic::AchEnchantments: return "cuberite:achievement.enchantments";
case Statistic::AchOverkill: return "cuberite:achievement.overkill";
case Statistic::AchBookcase: return "cuberite:achievement.bookcase";
case Statistic::AchExploreAllBiomes: return "cuberite:achievement.exploreAllBiomes";
case Statistic::AchSpawnWither: return "cuberite:achievement.spawnWither";
case Statistic::AchKillWither: return "cuberite:achievement.killWither";
case Statistic::AchFullBeacon: return "cuberite:achievement.fullBeacon";
case Statistic::AchBreedCow: return "cuberite:achievement.breedCow";
case Statistic::AchDiamondsToYou: return "cuberite:achievement.diamondsToYou";
}
UNREACHABLE("Tried to save unhandled statistic");
}
static const std::unordered_map<std::string_view, Statistic> CustomStatistics
{
{ "animals_bred", Statistic::AnimalsBred },
{ "aviate_one_cm", Statistic::AviateOneCm },
{ "bell_ring", Statistic::BellRing },
{ "boat_one_cm", Statistic::BoatOneCm },
{ "clean_armor", Statistic::CleanArmor },
{ "clean_banner", Statistic::CleanBanner },
{ "clean_shulker_box", Statistic::CleanShulkerBox },
{ "climb_one_cm", Statistic::ClimbOneCm },
{ "crouch_one_cm", Statistic::CrouchOneCm },
{ "damage_absorbed", Statistic::DamageAbsorbed },
{ "damage_blocked_by_shield", Statistic::DamageBlockedByShield },
{ "damage_dealt", Statistic::DamageDealt },
{ "damage_dealt_absorbed", Statistic::DamageDealtAbsorbed },
{ "damage_dealt_resisted", Statistic::DamageDealtResisted },
{ "damage_resisted", Statistic::DamageResisted },
{ "damage_taken", Statistic::DamageTaken },
{ "deaths", Statistic::Deaths },
{ "drop", Statistic::Drop },
{ "eat_cake_slice", Statistic::EatCakeSlice },
{ "enchant_item", Statistic::EnchantItem },
{ "fall_one_cm", Statistic::FallOneCm },
{ "fill_cauldron", Statistic::FillCauldron },
{ "fish_caught", Statistic::FishCaught },
{ "fly_one_cm", Statistic::FlyOneCm },
{ "horse_one_cm", Statistic::HorseOneCm },
{ "inspect_dispenser", Statistic::InspectDispenser },
{ "inspect_dropper", Statistic::InspectDropper },
{ "inspect_hopper", Statistic::InspectHopper },
{ "interact_with_anvil", Statistic::InteractWithAnvil },
{ "interact_with_beacon", Statistic::InteractWithBeacon },
{ "interact_with_blast_furnace", Statistic::InteractWithBlastFurnace },
{ "interact_with_brewingstand", Statistic::InteractWithBrewingstand },
{ "interact_with_campfire", Statistic::InteractWithCampfire },
{ "interact_with_cartography_table", Statistic::InteractWithCartographyTable },
{ "interact_with_crafting_table", Statistic::InteractWithCraftingTable },
{ "interact_with_furnace", Statistic::InteractWithFurnace },
{ "interact_with_grindstone", Statistic::InteractWithGrindstone },
{ "interact_with_lectern", Statistic::InteractWithLectern },
{ "interact_with_loom", Statistic::InteractWithLoom },
{ "interact_with_smithing_table", Statistic::InteractWithSmithingTable },
{ "interact_with_smoker", Statistic::InteractWithSmoker },
{ "interact_with_stonecutter", Statistic::InteractWithStonecutter },
{ "jump", Statistic::Jump },
{ "leave_game", Statistic::LeaveGame },
{ "minecart_one_cm", Statistic::MinecartOneCm },
{ "mob_kills", Statistic::MobKills },
{ "open_barrel", Statistic::OpenBarrel },
{ "open_chest", Statistic::OpenChest },
{ "open_enderchest", Statistic::OpenEnderchest },
{ "open_shulker_box", Statistic::OpenShulkerBox },
{ "pig_one_cm", Statistic::PigOneCm },
{ "play_noteblock", Statistic::PlayNoteblock },
{ "play_one_minute", Statistic::PlayOneMinute },
{ "play_record", Statistic::PlayRecord },
{ "player_kills", Statistic::PlayerKills },
{ "pot_flower", Statistic::PotFlower },
{ "raid_trigger", Statistic::RaidTrigger },
{ "raid_win", Statistic::RaidWin },
{ "sleep_in_bed", Statistic::SleepInBed },
{ "sneak_time", Statistic::SneakTime },
{ "sprint_one_cm", Statistic::SprintOneCm },
{ "strider_one_cm", Statistic::StriderOneCm },
{ "swim_one_cm", Statistic::SwimOneCm },
{ "talked_to_villager", Statistic::TalkedToVillager },
{ "target_hit", Statistic::TargetHit },
{ "time_since_death", Statistic::TimeSinceDeath },
{ "time_since_rest", Statistic::TimeSinceRest },
{ "traded_with_villager", Statistic::TradedWithVillager },
{ "trigger_trapped_chest", Statistic::TriggerTrappedChest },
{ "tune_noteblock", Statistic::TuneNoteblock },
{ "use_cauldron", Statistic::UseCauldron },
{ "walk_on_water_one_cm", Statistic::WalkOnWaterOneCm },
{ "walk_one_cm", Statistic::WalkOneCm },
{ "walk_under_water_one_cm", Statistic::WalkUnderWaterOneCm },
// Old ones just for compatibility
{ "junk_fished", Statistic::JunkFished },
{ "treasure_fished", Statistic::TreasureFished },
// The old advancements
{ "cuberite:achievement.openInventory", Statistic::AchOpenInventory },
{ "cuberite:achievement.mineWood", Statistic::AchMineWood },
{ "cuberite:achievement.buildWorkBench", Statistic::AchBuildWorkBench },
{ "cuberite:achievement.buildPickaxe", Statistic::AchBuildPickaxe },
{ "cuberite:achievement.buildFurnace", Statistic::AchBuildFurnace },
{ "cuberite:achievement.acquireIron", Statistic::AchAcquireIron },
{ "cuberite:achievement.buildHoe", Statistic::AchBuildHoe },
{ "cuberite:achievement.makeBread", Statistic::AchMakeBread },
{ "cuberite:achievement.bakeCake", Statistic::AchBakeCake },
{ "cuberite:achievement.buildBetterPickaxe", Statistic::AchBuildBetterPickaxe },
{ "cuberite:achievement.cookFish", Statistic::AchCookFish },
{ "cuberite:achievement.onARail", Statistic::AchOnARail },
{ "cuberite:achievement.buildSword", Statistic::AchBuildSword },
{ "cuberite:achievement.killEnemy", Statistic::AchKillEnemy },
{ "cuberite:achievement.killCow", Statistic::AchKillCow },
{ "cuberite:achievement.flyPig", Statistic::AchFlyPig },
{ "cuberite:achievement.snipeSkeleton", Statistic::AchSnipeSkeleton },
{ "cuberite:achievement.diamonds", Statistic::AchDiamonds },
{ "cuberite:achievement.portal", Statistic::AchPortal },
{ "cuberite:achievement.ghast", Statistic::AchGhast },
{ "cuberite:achievement.blazeRod", Statistic::AchBlazeRod },
{ "cuberite:achievement.potion", Statistic::AchPotion },
{ "cuberite:achievement.theEnd", Statistic::AchTheEnd },
{ "cuberite:achievement.theEnd2", Statistic::AchTheEnd2 },
{ "cuberite:achievement.enchantments", Statistic::AchEnchantments },
{ "cuberite:achievement.overkill", Statistic::AchOverkill },
{ "cuberite:achievement.bookcase", Statistic::AchBookcase },
{ "cuberite:achievement.exploreAllBiomes", Statistic::AchExploreAllBiomes },
{ "cuberite:achievement.spawnWither", Statistic::AchSpawnWither },
{ "cuberite:achievement.killWither", Statistic::AchKillWither },
{ "cuberite:achievement.fullBeacon", Statistic::AchFullBeacon },
{ "cuberite:achievement.breedCow", Statistic::AchBreedCow },
{ "cuberite:achievement.diamondsToYou", Statistic::AchDiamondsToYou}
};
Statistic ToCustomStatistic(const std::string_view ID)
{
return CustomStatistics.at(ID);
}
std::pair<Namespace, std::string_view> SplitNamespacedID(const std::string_view ID)
{
const auto NamespaceIndex = ID.find(':');
if (NamespaceIndex == std::string_view::npos)
{
// No explicit namespace default to the Minecraft namespace:
return { Namespace::Minecraft, ID };
}
const auto Namespace = ID.substr(0, NamespaceIndex);
if (Namespace == "minecraft")
{
// An unprefixed ID in the vanilla Minecraft namespace
const auto Value = ID.substr(NamespaceIndex + 1);
return { Namespace::Minecraft, Value };
}
if (Namespace == "cuberite")
{
return { Namespace::Cuberite, ID };
}
return { Namespace::Unknown, ID };
}
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "../Registries/Statistics.h"
namespace NamespaceSerializer
{
enum class Namespace
{
Minecraft,
Cuberite,
Unknown
};
unsigned DataVersion();
const char * From(Statistic ID);
Statistic ToCustomStatistic(std::string_view ID);
std::pair<Namespace, std::string_view> SplitNamespacedID(std::string_view ID);
}

View File

@ -4,15 +4,17 @@
#include "Globals.h"
#include "StatSerializer.h"
#include "../Statistics.h"
#include "../JsonUtils.h"
#include "NamespaceSerializer.h"
#include <fstream>
#include <json/json.h>
cStatSerializer::cStatSerializer(const AString & a_WorldName, const AString & a_PlayerName, const AString & a_FileName, cStatManager * a_Manager)
cStatSerializer::cStatSerializer(cStatManager & a_Manager, const AString & a_WorldName, const AString & a_FileName)
: m_Manager(a_Manager)
{
// Even though stats are shared between worlds, they are (usually) saved
@ -21,8 +23,7 @@ cStatSerializer::cStatSerializer(const AString & a_WorldName, const AString & a_
AString StatsPath;
Printf(StatsPath, "%s%cstats", a_WorldName.c_str(), cFile::PathSeparator());
m_LegacyPath = StatsPath + "/" + a_PlayerName + ".json";
m_Path = StatsPath + "/" + a_FileName + ".json";
m_Path = StatsPath + cFile::PathSeparator() + a_FileName + ".json";
// Ensure that the directory exists.
cFile::CreateFolder(StatsPath);
@ -32,49 +33,26 @@ cStatSerializer::cStatSerializer(const AString & a_WorldName, const AString & a_
bool cStatSerializer::Load(void)
{
AString Data = cFile::ReadWholeFile(m_Path);
if (Data.empty())
{
Data = cFile::ReadWholeFile(m_LegacyPath);
if (Data.empty())
{
return false;
}
}
Json::Value Root;
if (JsonUtils::ParseString(Data, Root))
{
return LoadStatFromJSON(Root);
}
return false;
}
bool cStatSerializer::Save(void)
void cStatSerializer::Load(void)
{
Json::Value Root;
SaveStatToJSON(Root);
std::ifstream(m_Path) >> Root;
cFile File;
if (!File.Open(m_Path, cFile::fmWrite))
{
return false;
LoadCustomStatFromJSON(Root["stats"]["custom"]);
}
AString JsonData = JsonUtils::WriteStyledString(Root);
File.Write(JsonData.data(), JsonData.size());
File.Close();
return true;
void cStatSerializer::Save(void)
{
Json::Value Root;
SaveStatToJSON(Root["stats"]);
Root["DataVersion"] = NamespaceSerializer::DataVersion();
std::ofstream(m_Path) << Root;
}
@ -83,68 +61,50 @@ bool cStatSerializer::Save(void)
void cStatSerializer::SaveStatToJSON(Json::Value & a_Out)
{
for (unsigned int i = 0; i < static_cast<unsigned int>(statCount); ++i)
m_Manager.ForEachStatisticType([&a_Out](const cStatManager::CustomStore & Store)
{
StatValue Value = m_Manager->GetValue(static_cast<eStatistic>(i));
if (Value != 0)
if (Store.empty())
{
const AString & StatName = cStatInfo::GetName(static_cast<eStatistic>(i));
a_Out[StatName] = Value;
// Avoid saving "custom": null to disk:
return;
}
// TODO 2014-05-11 xdot: Save "progress"
auto & Custom = a_Out["custom"];
for (const auto & Item : Store)
{
Custom[NamespaceSerializer::From(Item.first)] = Item.second;
}
});
}
bool cStatSerializer::LoadStatFromJSON(const Json::Value & a_In)
void cStatSerializer::LoadCustomStatFromJSON(const Json::Value & a_In)
{
m_Manager->Reset();
for (Json::Value::const_iterator it = a_In.begin() ; it != a_In.end() ; ++it)
for (auto it = a_In.begin() ; it != a_In.end() ; ++it)
{
AString StatName = it.key().asString();
eStatistic StatType = cStatInfo::GetType(StatName);
if (StatType == statInvalid)
const auto & Key = it.key().asString();
const auto StatInfo = NamespaceSerializer::SplitNamespacedID(Key);
if (StatInfo.first == NamespaceSerializer::Namespace::Unknown)
{
LOGWARNING("Invalid statistic type \"%s\"", StatName.c_str());
// Ignore non-Vanilla, non-Cuberite namespaces for now:
continue;
}
const Json::Value & Node = *it;
if (Node.isInt())
const auto & StatName = StatInfo.second;
try
{
m_Manager->SetValue(StatType, Node.asInt());
m_Manager.SetValue(NamespaceSerializer::ToCustomStatistic(StatName), it->asInt());
}
else if (Node.isObject())
catch (const std::out_of_range & Oops)
{
StatValue Value = Node.get("value", 0).asInt();
// TODO 2014-05-11 xdot: Load "progress"
m_Manager->SetValue(StatType, Value);
FLOGWARNING("Invalid statistic type \"{}\"", StatName);
}
else
catch (const Json::LogicError & Oops)
{
LOGWARNING("Invalid statistic value for type \"%s\"", StatName.c_str());
FLOGWARNING("Invalid statistic value for type \"{}\"", StatName);
}
}
return true;
}

View File

@ -9,14 +9,14 @@
#pragma once
#include "json/json.h"
// fwd:
class cStatManager;
namespace Json { class Value; }
@ -25,32 +25,21 @@ class cStatSerializer
{
public:
cStatSerializer(const AString & a_WorldName, const AString & a_PlayerName, const AString & a_FileName, cStatManager * a_Manager);
cStatSerializer(cStatManager & a_Manager, const AString & a_WorldName, const AString & a_FileName);
/* Try to load the player statistics. Returns whether the operation was successful or not. */
bool Load(void);
/* Try to save the player statistics. Returns whether the operation was successful or not. */
bool Save(void);
protected:
void SaveStatToJSON(Json::Value & a_Out);
bool LoadStatFromJSON(const Json::Value & a_In);
/* Try to load the player statistics. */
void Load(void);
/* Try to save the player statistics. */
void Save(void);
private:
cStatManager * m_Manager;
void SaveStatToJSON(Json::Value & a_Out);
void LoadCustomStatFromJSON(const Json::Value & a_In);
cStatManager & m_Manager;
AString m_LegacyPath; // The old <username>.json path to try to read from if the uuid path doesn't exist on load
AString m_Path;
} ;