1
0

Merge pull request #998 from mc-server/StatManager

Statistic Manager
This commit is contained in:
Mattes D 2014-05-18 22:30:34 +02:00
commit ac00568183
24 changed files with 711 additions and 61 deletions

View File

@ -76,6 +76,7 @@ $cfile "../CompositeChat.h"
$cfile "../Map.h"
$cfile "../MapManager.h"
$cfile "../Scoreboard.h"
$cfile "../Statistics.h"

View File

@ -336,7 +336,7 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID)
// Send scoreboard data
World->GetScoreBoard().SendTo(*this);
// Delay the first ping until the client "settles down"
// This should fix #889, "BadCast exception, cannot convert bit to fm" error in client
cTimer t1;
@ -2500,6 +2500,15 @@ void cClientHandle::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleTy
void cClientHandle::SendStatistics(const cStatManager & a_Manager)
{
m_Protocol->SendStatistics(a_Manager);
}
void cClientHandle::SendTabCompletionResults(const AStringVector & a_Results)
{
m_Protocol->SendTabCompletionResults(a_Results);

View File

@ -39,6 +39,7 @@ class cFallingBlock;
class cItemHandler;
class cWorld;
class cCompositeChat;
class cStatManager;
@ -160,6 +161,7 @@ public:
void SendSpawnMob (const cMonster & a_Mob);
void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch);
void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType = 0);
void SendStatistics (const cStatManager & a_Manager);
void SendTabCompletionResults(const AStringVector & a_Results);
void SendTeleportEntity (const cEntity & a_Entity);
void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ);

View File

@ -10,3 +10,5 @@ file(GLOB SOURCE
)
add_library(Entities ${SOURCE})
target_link_libraries(Entities WorldStorage)

View File

@ -312,12 +312,16 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI)
if ((a_TDI.Attacker != NULL) && (a_TDI.Attacker->IsPlayer()))
{
cPlayer * Player = (cPlayer *)a_TDI.Attacker;
// IsOnGround() only is false if the player is moving downwards
if (!((cPlayer *)a_TDI.Attacker)->IsOnGround()) // TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain)
if (!Player->IsOnGround()) // TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain)
{
a_TDI.FinalDamage += 2;
m_World->BroadcastEntityAnimation(*this, 4); // Critical hit
}
Player->GetStatManager().AddValue(statDamageDealt, round(a_TDI.FinalDamage * 10));
}
m_Health -= (short)a_TDI.FinalDamage;
@ -370,6 +374,11 @@ bool cEntity::DoTakeDamage(TakeDamageInfo & a_TDI)
if (m_Health <= 0)
{
KilledBy(a_TDI.Attacker);
if (a_TDI.Attacker != NULL)
{
a_TDI.Attacker->Killed(this);
}
}
return true;
}
@ -575,9 +584,16 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk)
if (m_AttachedTo != NULL)
{
if ((m_Pos - m_AttachedTo->GetPosition()).Length() > 0.5)
Vector3d DeltaPos = m_Pos - m_AttachedTo->GetPosition();
if (DeltaPos.Length() > 0.5)
{
SetPosition(m_AttachedTo->GetPosition());
if (IsPlayer())
{
cPlayer * Player = (cPlayer *)this;
Player->UpdateMovementStats(DeltaPos);
}
}
}
else

View File

@ -299,6 +299,9 @@ public:
/// Called when the health drops below zero. a_Killer may be NULL (environmental damage)
virtual void KilledBy(cEntity * a_Killer);
/// Called when the entity kills another entity
virtual void Killed(cEntity * a_Victim) {}
/// Heals the specified amount of HPs
void Heal(int a_HitPoints);

View File

@ -192,6 +192,16 @@ bool cPickup::CollectedBy(cPlayer * a_Dest)
int NumAdded = a_Dest->GetInventory().AddItem(m_Item);
if (NumAdded > 0)
{
// 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;
default: break;
}
m_Item.m_ItemCount -= NumAdded;
m_World->BroadcastCollectPickup(*this, *a_Dest);
// Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)

View File

@ -16,6 +16,9 @@
#include "../Items/ItemHandler.h"
#include "../Vector3.h"
#include "../WorldStorage/StatSerializer.h"
#include "../CompositeChat.h"
#include "inifile/iniFile.h"
#include "json/json.h"
@ -191,6 +194,8 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk)
return;
}
}
m_Stats.AddValue(statMinutesPlayed, 1);
if (!a_Chunk.IsValid())
{
@ -815,7 +820,7 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
if ((a_TDI.Attacker != NULL) && (a_TDI.Attacker->IsPlayer()))
{
cPlayer* Attacker = (cPlayer*) a_TDI.Attacker;
cPlayer * Attacker = (cPlayer *)a_TDI.Attacker;
if ((m_Team != NULL) && (m_Team == Attacker->m_Team))
{
@ -832,6 +837,8 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
// Any kind of damage adds food exhaustion
AddFoodExhaustion(0.3f);
SendHealth();
m_Stats.AddValue(statDamageTaken, round(a_TDI.FinalDamage * 10));
return true;
}
return false;
@ -862,6 +869,8 @@ void cPlayer::KilledBy(cEntity * a_Killer)
Pickups.Add(cItem(E_ITEM_RED_APPLE));
}
m_Stats.AddValue(statItemsDropped, Pickups.Size());
m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10);
SaveToDisk(); // Save it, yeah the world is a tough place !
@ -871,9 +880,9 @@ void cPlayer::KilledBy(cEntity * a_Killer)
}
else if (a_Killer->IsPlayer())
{
GetWorld()->BroadcastChatDeath(Printf("%s was killed by %s", GetName().c_str(), ((cPlayer *)a_Killer)->GetName().c_str()));
cPlayer * Killer = (cPlayer *)a_Killer;
m_World->GetScoreBoard().AddPlayerScore(((cPlayer *)a_Killer)->GetName(), cObjective::otPlayerKillCount, 1);
GetWorld()->BroadcastChatDeath(Printf("%s was killed by %s", GetName().c_str(), Killer->GetName().c_str()));
}
else
{
@ -883,6 +892,8 @@ void cPlayer::KilledBy(cEntity * a_Killer)
GetWorld()->BroadcastChatDeath(Printf("%s was killed by a %s", GetName().c_str(), KillerClass.c_str()));
}
m_Stats.AddValue(statDeaths);
m_World->GetScoreBoard().AddPlayerScore(GetName(), cObjective::otDeathCount, 1);
}
@ -890,6 +901,33 @@ void cPlayer::KilledBy(cEntity * a_Killer)
void cPlayer::Killed(cEntity * a_Victim)
{
cScoreboard & ScoreBoard = m_World->GetScoreBoard();
if (a_Victim->IsPlayer())
{
m_Stats.AddValue(statPlayerKills);
ScoreBoard.AddPlayerScore(GetName(), cObjective::otPlayerKillCount, 1);
}
else if (a_Victim->IsMob())
{
if (((cMonster *)a_Victim)->GetMobFamily() == cMonster::mfHostile)
{
AwardAchievement(achKillMonster);
}
m_Stats.AddValue(statMobKills);
}
ScoreBoard.AddPlayerScore(GetName(), cObjective::otTotalKillCount, 1);
}
void cPlayer::Respawn(void)
{
m_Health = GetMaxHealth();
@ -1108,6 +1146,47 @@ void cPlayer::SetIP(const AString & a_IP)
unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach)
{
eStatistic Prerequisite = cStatInfo::GetPrerequisite(a_Ach);
// Check if the prerequisites are met
if (Prerequisite != statInvalid)
{
if (m_Stats.GetValue(Prerequisite) == 0)
{
return 0;
}
}
StatValue Old = m_Stats.GetValue(a_Ach);
if (Old > 0)
{
return m_Stats.AddValue(a_Ach);
}
else
{
// First time, announce it
cCompositeChat Msg;
Msg.AddTextPart(m_PlayerName + " has just earned the achievement ");
Msg.AddTextPart(cStatInfo::GetName(a_Ach)); // TODO 2014-05-12 xdot: Use the proper cCompositeChat part (cAchievement)
m_World->BroadcastChat(Msg);
// Increment the statistic
StatValue New = m_Stats.AddValue(a_Ach);
// Achievement Get!
m_ClientHandle->SendStatistics(m_Stats);
return New;
}
}
void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
{
SetPosition(a_PosX, a_PosY, a_PosZ);
@ -1192,6 +1271,9 @@ void cPlayer::MoveTo( const Vector3d & a_NewPos )
// TODO: should do some checks to see if player is not moving through terrain
// TODO: Official server refuses position packets too far away from each other, kicking "hacked" clients; we should, too
Vector3d DeltaPos = a_NewPos - GetPosition();
UpdateMovementStats(DeltaPos);
SetPosition( a_NewPos );
SetStance(a_NewPos.y + 1.62);
@ -1422,10 +1504,7 @@ void cPlayer::TossEquippedItem(char a_Amount)
Drops.push_back(DroppedItem);
}
double vX = 0, vY = 0, vZ = 0;
EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY);
vY = -vY * 2 + 1.f;
m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player
TossItems(Drops);
}
@ -1441,6 +1520,7 @@ void cPlayer::TossHeldItem(char a_Amount)
char OriginalItemAmount = Item.m_ItemCount;
Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount);
Drops.push_back(Item);
if (OriginalItemAmount > a_Amount)
{
Item.m_ItemCount = OriginalItemAmount - a_Amount;
@ -1451,10 +1531,7 @@ void cPlayer::TossHeldItem(char a_Amount)
}
}
double vX = 0, vY = 0, vZ = 0;
EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY);
vY = -vY * 2 + 1.f;
m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player
TossItems(Drops);
}
@ -1466,10 +1543,21 @@ void cPlayer::TossPickup(const cItem & a_Item)
cItems Drops;
Drops.push_back(a_Item);
TossItems(Drops);
}
void cPlayer::TossItems(const cItems & a_Items)
{
m_Stats.AddValue(statItemsDropped, a_Items.Size());
double vX = 0, vY = 0, vZ = 0;
EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY);
vY = -vY * 2 + 1.f;
m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player
m_World->SpawnItemPickups(a_Items, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player
}
@ -1621,6 +1709,11 @@ bool cPlayer::LoadFromDisk()
m_Inventory.LoadFromJson(root["inventory"]);
m_LoadedWorldName = root.get("world", "world").asString();
// Load the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions/worlds.
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), &m_Stats);
StatSerializer.Load();
LOGD("Player \"%s\" was read from file, spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
m_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str()
@ -1692,6 +1785,16 @@ bool cPlayer::SaveToDisk()
LOGERROR("ERROR WRITING PLAYER JSON TO FILE \"%s\"", SourceFile.c_str());
return false;
}
// Save the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions/worlds.
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), m_PlayerName, &m_Stats);
if (!StatSerializer.Save())
{
LOGERROR("Could not save stats for player %s", m_PlayerName.c_str());
return false;
}
return true;
}
@ -1706,7 +1809,10 @@ cPlayer::StringList cPlayer::GetResolvedPermissions()
const PermissionMap& ResolvedPermissions = m_ResolvedPermissions;
for( PermissionMap::const_iterator itr = ResolvedPermissions.begin(); itr != ResolvedPermissions.end(); ++itr )
{
if( itr->second ) Permissions.push_back( itr->first );
if (itr->second)
{
Permissions.push_back( itr->first );
}
}
return Permissions;
@ -1845,6 +1951,59 @@ void cPlayer::HandleFloater()
void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos)
{
StatValue Value = round(a_DeltaPos.Length() * 100);
if (m_AttachedTo == NULL)
{
int PosX = POSX_TOINT;
int PosY = POSY_TOINT;
int PosZ = POSZ_TOINT;
BLOCKTYPE Block;
NIBBLETYPE Meta;
if (!m_World->GetBlockTypeMeta(PosX, PosY, PosZ, Block, Meta))
{
return;
}
if ((Block == E_BLOCK_LADDER) && (a_DeltaPos.y > 0.0)) // Going up
{
m_Stats.AddValue(statDistClimbed, round(a_DeltaPos.y * 100));
}
else
{
// TODO 2014-05-12 xdot: Other types
m_Stats.AddValue(statDistWalked, Value);
}
}
else
{
switch (m_AttachedTo->GetEntityType())
{
case cEntity::etMinecart: m_Stats.AddValue(statDistMinecart, Value); break;
case cEntity::etBoat: m_Stats.AddValue(statDistBoat, Value); break;
case cEntity::etMonster:
{
cMonster * Monster = (cMonster *)m_AttachedTo;
switch (Monster->GetMobType())
{
case cMonster::mtPig: m_Stats.AddValue(statDistPig, Value); break;
case cMonster::mtHorse: m_Stats.AddValue(statDistHorse, Value); break;
default: break;
}
break;
}
default: break;
}
}
}
void cPlayer::ApplyFoodExhaustionFromMovement()
{
if (IsGameModeCreative())

View File

@ -7,6 +7,8 @@
#include "../World.h"
#include "../ClientHandle.h"
#include "../Statistics.h"
@ -174,6 +176,15 @@ public:
cTeam * UpdateTeam(void);
// tolua_end
/** Return the associated statistic and achievement manager. */
cStatManager & GetStatManager() { return m_Stats; }
/** 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);
void SetIP(const AString & a_IP);
@ -306,6 +317,8 @@ public:
void AbortEating(void);
virtual void KilledBy(cEntity * a_Killer) override;
virtual void Killed(cEntity * a_Victim) override;
void Respawn(void); // tolua_export
@ -375,6 +388,9 @@ public:
/** If true the player can fly even when he's not in creative. */
void SetCanFly(bool a_CanFly);
/** Update movement-related statistics. */
void UpdateMovementStats(const Vector3d & a_DeltaPos);
/** Returns wheter the player can fly or not. */
virtual bool CanFly(void) const { return m_CanFly; }
// tolua_end
@ -487,6 +503,8 @@ protected:
cTeam * m_Team;
cStatManager m_Stats;
void ResolvePermissions(void);
@ -506,6 +524,9 @@ protected:
/** Called in each tick if the player is fishing to make sure the floater dissapears when the player doesn't have a fishing rod as equipped item. */
void HandleFloater(void);
/** Tosses a list of items. */
void TossItems(const cItems & a_Items);
/** Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block) */
void ApplyFoodExhaustionFromMovement();

View File

@ -228,7 +228,7 @@ public:
void Add (const cItem & a_Item) {push_back(a_Item); }
void Delete(int a_Idx);
void Clear (void) {clear(); }
size_t Size (void) {return size(); }
size_t Size (void) const { return size(); }
void Set (int a_Idx, short a_ItemType, char a_ItemCount, short a_ItemDamage);
void Add (short a_ItemType, char a_ItemCount, short a_ItemDamage)

View File

@ -466,8 +466,10 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI)
return false;
}
if((m_SoundHurt != "") && (m_Health > 0))
if (!m_SoundHurt.empty() && (m_Health > 0))
{
m_World->BroadcastSoundEffect(m_SoundHurt, (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 1.0f, 0.8f);
}
if (a_TDI.Attacker != NULL)
{

View File

@ -31,6 +31,7 @@ class cMonster;
class cChunkDataSerializer;
class cFallingBlock;
class cCompositeChat;
class cStatManager;
@ -111,6 +112,7 @@ public:
virtual void SendSpawnMob (const cMonster & a_Mob) = 0;
virtual void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) = 0;
virtual void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) = 0;
virtual void SendStatistics (const cStatManager & a_Manager) = 0;
virtual void SendTabCompletionResults(const AStringVector & a_Results) = 0;
virtual void SendTeleportEntity (const cEntity & a_Entity) = 0;
virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) = 0;

View File

@ -99,6 +99,7 @@ enum
PACKET_ENCHANT_ITEM = 0x6C,
PACKET_UPDATE_SIGN = 0x82,
PACKET_ITEM_DATA = 0x83,
PACKET_INCREMENT_STATISTIC = 0xC8,
PACKET_PLAYER_LIST_ITEM = 0xC9,
PACKET_PLAYER_ABILITIES = 0xca,
PACKET_PLUGIN_MESSAGE = 0xfa,
@ -992,6 +993,33 @@ void cProtocol125::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleTyp
void cProtocol125::SendStatistics(const cStatManager & a_Manager)
{
/* NOTE:
* Versions prior to minecraft 1.7 use an incremental statistic sync
* method. The current setup does not allow us to implement that, because
* of performance considerations.
*/
#if 0
for (unsigned int i = 0; i < (unsigned int)statCount; ++i)
{
StatValue Value = m_Manager->GetValue((eStatistic) i);
unsigned int StatID = cStatInfo::GetID((eStatistic) i);
cCSLock Lock(m_CSPacket);
WriteByte(PACKET_INCREMENT_STATISTIC);
WriteInt(StatID);
WriteByte(Value); /* Can overflow! */
Flush();
}
#endif
}
void cProtocol125::SendTabCompletionResults(const AStringVector & a_Results)
{
// This protocol version doesn't support tab completion

View File

@ -84,6 +84,7 @@ public:
virtual void SendSpawnMob (const cMonster & a_Mob) override;
virtual void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) override;
virtual void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) override;
virtual void SendStatistics (const cStatManager & a_Manager) override;
virtual void SendTabCompletionResults(const AStringVector & a_Results) override;
virtual void SendTeleportEntity (const cEntity & a_Entity) override;
virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override;

View File

@ -11,13 +11,19 @@ Implements the 1.7.x protocol classes:
#include "json/json.h"
#include "Protocol17x.h"
#include "ChunkDataSerializer.h"
#include "PolarSSL++/Sha1Checksum.h"
#include "../ClientHandle.h"
#include "../Root.h"
#include "../Server.h"
#include "../World.h"
#include "../StringCompression.h"
#include "../CompositeChat.h"
#include "../Statistics.h"
#include "../WorldStorage/FastNBT.h"
#include "../WorldStorage/EnchantmentSerializer.h"
#include "../StringCompression.h"
#include "../Entities/ExpOrb.h"
#include "../Entities/Minecart.h"
#include "../Entities/FallingBlock.h"
@ -25,15 +31,15 @@ Implements the 1.7.x protocol classes:
#include "../Entities/Pickup.h"
#include "../Entities/Player.h"
#include "../Entities/ItemFrame.h"
#include "../Entities/ArrowEntity.h"
#include "../Entities/FireworkEntity.h"
#include "../Mobs/IncludeAllMonsters.h"
#include "../UI/Window.h"
#include "../BlockEntities/CommandBlockEntity.h"
#include "../BlockEntities/MobHeadEntity.h"
#include "../BlockEntities/FlowerPotEntity.h"
#include "../CompositeChat.h"
#include "../Entities/ArrowEntity.h"
#include "../Entities/FireworkEntity.h"
#include "PolarSSL++/Sha1Checksum.h"
@ -1169,6 +1175,28 @@ void cProtocol172::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleTyp
void cProtocol172::SendStatistics(const cStatManager & a_Manager)
{
ASSERT(m_State == 3); // In game mode?
cPacketizer Pkt(*this, 0x37);
Pkt.WriteVarInt(statCount); // TODO 2014-05-11 xdot: Optimization: Send "dirty" statistics only
for (unsigned int i = 0; i < (unsigned int)statCount; ++i)
{
StatValue Value = a_Manager.GetValue((eStatistic) i);
const AString & StatName = cStatInfo::GetName((eStatistic) i);
Pkt.WriteString(StatName);
Pkt.WriteVarInt(Value);
}
}
void cProtocol172::SendTabCompletionResults(const AStringVector & a_Results)
{
ASSERT(m_State == 3); // In game mode?
@ -1843,13 +1871,15 @@ void cProtocol172::HandlePacketClientStatus(cByteBuffer & a_ByteBuffer)
case 1:
{
// Request stats
// TODO
const cStatManager & Manager = m_Client->GetPlayer()->GetStatManager();
SendStatistics(Manager);
break;
}
case 2:
{
// Open Inventory achievement
// TODO
m_Client->GetPlayer()->AwardAchievement(achOpenInv);
break;
}
}

View File

@ -116,6 +116,7 @@ public:
virtual void SendSpawnMob (const cMonster & a_Mob) override;
virtual void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) override;
virtual void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) override;
virtual void SendStatistics (const cStatManager & a_Manager) override;
virtual void SendTabCompletionResults(const AStringVector & a_Results) override;
virtual void SendTeleportEntity (const cEntity & a_Entity) override;
virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override;

View File

@ -675,6 +675,16 @@ void cProtocolRecognizer::SendSpawnVehicle(const cEntity & a_Vehicle, char a_Veh
void cProtocolRecognizer::SendStatistics(const cStatManager & a_Manager)
{
ASSERT(m_Protocol != NULL);
m_Protocol->SendStatistics(a_Manager);
}
void cProtocolRecognizer::SendTabCompletionResults(const AStringVector & a_Results)
{
ASSERT(m_Protocol != NULL);

View File

@ -119,6 +119,7 @@ public:
virtual void SendSpawnMob (const cMonster & a_Mob) override;
virtual void SendSpawnObject (const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch) override;
virtual void SendSpawnVehicle (const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) override;
virtual void SendStatistics (const cStatManager & a_Manager) override;
virtual void SendTabCompletionResults(const AStringVector & a_Results) override;
virtual void SendTeleportEntity (const cEntity & a_Entity) override;
virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override;

View File

@ -13,39 +13,39 @@ cStatInfo cStatInfo::ms_Info[statCount] = {
// http://minecraft.gamepedia.com/Achievements
/* Type | Name | Prerequisite */
cStatInfo(achOpenInv, "openInventory"),
cStatInfo(achMineWood, "mineWood", achOpenInv),
cStatInfo(achCraftWorkbench, "buildWorkBench", achMineWood),
cStatInfo(achCraftPickaxe, "buildPickaxe", achCraftWorkbench),
cStatInfo(achCraftFurnace, "buildFurnace", achCraftPickaxe),
cStatInfo(achAcquireIron, "acquireIron", achCraftFurnace),
cStatInfo(achCraftHoe, "buildHoe", achCraftWorkbench),
cStatInfo(achMakeBread, "makeBread", achCraftHoe),
cStatInfo(achBakeCake, "bakeCake", achCraftHoe),
cStatInfo(achCraftBetterPick, "buildBetterPickaxe", achCraftPickaxe),
cStatInfo(achCookFish, "cookFish", achAcquireIron),
cStatInfo(achOnARail, "onARail", achAcquireIron),
cStatInfo(achCraftSword, "buildSword", achCraftWorkbench),
cStatInfo(achKillMonster, "killEnemy", achCraftSword),
cStatInfo(achKillCow, "killCow", achCraftSword),
cStatInfo(achFlyPig, "flyPig", achKillCow),
cStatInfo(achSnipeSkeleton, "snipeSkeleton", achKillMonster),
cStatInfo(achDiamonds, "diamonds", achAcquireIron),
cStatInfo(achEnterPortal, "portal", achDiamonds),
cStatInfo(achReturnToSender, "ghast", achEnterPortal),
cStatInfo(achBlazeRod, "blazeRod", achEnterPortal),
cStatInfo(achBrewPotion, "potion", achBlazeRod),
cStatInfo(achEnterTheEnd, "theEnd", achBlazeRod),
cStatInfo(achDefeatDragon, "theEnd2", achEnterTheEnd),
cStatInfo(achCraftEnchantTable, "enchantments", achDiamonds),
cStatInfo(achOverkill, "overkill", achCraftEnchantTable),
cStatInfo(achBookshelf, "bookcase", achCraftEnchantTable),
cStatInfo(achExploreAllBiomes, "exploreAllBiomes", achEnterTheEnd),
cStatInfo(achSpawnWither, "spawnWither", achDefeatDragon),
cStatInfo(achKillWither, "killWither", achSpawnWither),
cStatInfo(achFullBeacon, "fullBeacon", achKillWither),
cStatInfo(achBreedCow, "breedCow", achKillCow),
cStatInfo(achThrowDiamonds, "diamondsToYou", achDiamonds),
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),
// http://minecraft.gamepedia.com/Statistics
@ -57,13 +57,14 @@ cStatInfo cStatInfo::ms_Info[statCount] = {
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.damageDealth"),
cStatInfo(statDamageDealt, "stat.damageDealt"),
cStatInfo(statDamageTaken, "stat.damageTaken"),
cStatInfo(statDeaths, "stat.deaths"),
cStatInfo(statMobKills, "stat.mobKills"),
@ -113,7 +114,7 @@ eStatistic cStatInfo::GetType(const AString & a_Name)
{
for (unsigned int i = 0; i < ARRAYCOUNT(ms_Info); ++i)
{
if (NoCaseCompare(ms_Info[i].m_Name, a_Name))
if (NoCaseCompare(ms_Info[i].m_Name, a_Name) == 0)
{
return ms_Info[i].m_Type;
}
@ -137,3 +138,59 @@ eStatistic cStatInfo::GetPrerequisite(const eStatistic a_Type)
cStatManager::cStatManager()
{
Reset();
}
StatValue cStatManager::GetValue(const eStatistic a_Stat) const
{
ASSERT((a_Stat > statInvalid) && (a_Stat < statCount));
return m_MainStats[a_Stat];
}
void cStatManager::SetValue(const eStatistic a_Stat, const StatValue a_Value)
{
ASSERT((a_Stat > statInvalid) && (a_Stat < statCount));
m_MainStats[a_Stat] = a_Value;
}
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 < (unsigned int)statCount; ++i)
{
m_MainStats[i] = 0;
}
}

View File

@ -9,6 +9,7 @@
// tolua_begin
enum eStatistic
{
// The order must match the order of cStatInfo::ms_Info
@ -77,6 +78,7 @@ enum eStatistic
statCount
};
// tolua_end
@ -114,3 +116,49 @@ private:
/* 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();
// tolua_begin
/** Return the value of the specified stat. */
StatValue GetValue(const eStatistic a_Stat) const;
/** Set the value of the specified stat. */
void SetValue(const eStatistic a_Stat, const StatValue a_Value);
/** Reset everything. */
void Reset();
/** Increment the specified stat.
*
* Returns the new value.
*/
StatValue AddValue(const eStatistic a_Stat, const StatValue a_Delta = 1);
// tolua_end
private:
StatValue m_MainStats[statCount];
// TODO 10-05-2014 xdot: Use, mine, craft statistics
}; // tolua_export

View File

@ -496,6 +496,8 @@ void cSlotAreaCrafting::ClickedResult(cPlayer & a_Player)
DraggingItem = Result;
Recipe.ConsumeIngredients(Grid);
Grid.CopyToItems(PlayerSlots);
HandleCraftItem(Result, a_Player);
}
else if (DraggingItem.IsEqual(Result))
{
@ -505,6 +507,8 @@ void cSlotAreaCrafting::ClickedResult(cPlayer & a_Player)
DraggingItem.m_ItemCount += Result.m_ItemCount;
Recipe.ConsumeIngredients(Grid);
Grid.CopyToItems(PlayerSlots);
HandleCraftItem(Result, a_Player);
}
}
@ -594,6 +598,27 @@ cCraftingRecipe & cSlotAreaCrafting::GetRecipeForPlayer(cPlayer & a_Player)
void cSlotAreaCrafting::HandleCraftItem(const cItem & a_Result, cPlayer & a_Player)
{
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;
default: break;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cSlotAreaAnvil:
@ -1393,7 +1418,7 @@ void cSlotAreaFurnace::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
// Something has changed in the window, broadcast the entire window to all clients
ASSERT(a_ItemGrid == &(m_Furnace->GetContents()));
m_ParentWindow.BroadcastWholeWindow();
}
@ -1401,6 +1426,21 @@ void cSlotAreaFurnace::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
void cSlotAreaFurnace::HandleSmeltItem(const cItem & a_Result, cPlayer & a_Player)
{
/** 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;
default: break;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cSlotAreaInventoryBase:

View File

@ -254,6 +254,9 @@ protected:
/// Retrieves the recipe for the specified player from the map, or creates one if not found
cCraftingRecipe & GetRecipeForPlayer(cPlayer & a_Player);
/// Called after an item has been crafted to handle statistics e.t.c.
void HandleCraftItem(const cItem & a_Result, cPlayer & a_Player);
} ;
@ -397,6 +400,9 @@ protected:
// cItemGrid::cListener overrides:
virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) override;
/// Called after an item has been smelted to handle statistics e.t.c.
void HandleSmeltItem(const cItem & a_Result, cPlayer & a_Player);
} ;

View File

@ -0,0 +1,146 @@
// StatSerializer.cpp
#include "Globals.h"
#include "StatSerializer.h"
#include "../Statistics.h"
cStatSerializer::cStatSerializer(const AString & a_WorldName, const AString & a_PlayerName, cStatManager * a_Manager)
: m_Manager(a_Manager)
{
// Even though stats are shared between worlds, they are (usually) saved
// inside the folder of the default world.
AString StatsPath;
Printf(StatsPath, "%s/stats", a_WorldName.c_str());
m_Path = StatsPath + "/" + a_PlayerName + ".json";
// Ensure that the directory exists.
cFile::CreateFolder(FILE_IO_PREFIX + StatsPath);
}
bool cStatSerializer::Load(void)
{
AString Data = cFile::ReadWholeFile(FILE_IO_PREFIX + m_Path);
if (Data.empty())
{
return false;
}
Json::Value Root;
Json::Reader Reader;
if (Reader.parse(Data, Root, false))
{
return LoadStatFromJSON(Root);
}
return false;
}
bool cStatSerializer::Save(void)
{
Json::Value Root;
SaveStatToJSON(Root);
cFile File;
if (!File.Open(FILE_IO_PREFIX + m_Path, cFile::fmWrite))
{
return false;
}
Json::StyledWriter Writer;
AString JsonData = Writer.write(Root);
File.Write(JsonData.data(), JsonData.size());
File.Close();
return true;
}
void cStatSerializer::SaveStatToJSON(Json::Value & a_Out)
{
for (unsigned int i = 0; i < (unsigned int)statCount; ++i)
{
StatValue Value = m_Manager->GetValue((eStatistic) i);
if (Value != 0)
{
const AString & StatName = cStatInfo::GetName((eStatistic) i);
a_Out[StatName] = Value;
}
// TODO 2014-05-11 xdot: Save "progress"
}
}
bool cStatSerializer::LoadStatFromJSON(const Json::Value & a_In)
{
m_Manager->Reset();
for (Json::ValueIterator it = a_In.begin() ; it != a_In.end() ; ++it)
{
AString StatName = it.key().asString();
eStatistic StatType = cStatInfo::GetType(StatName);
if (StatType == statInvalid)
{
LOGWARNING("Invalid statistic type \"%s\"", StatName.c_str());
continue;
}
Json::Value & Node = *it;
if (Node.isInt())
{
m_Manager->SetValue(StatType, Node.asInt());
}
else if (Node.isObject())
{
StatValue Value = Node.get("value", 0).asInt();
// TODO 2014-05-11 xdot: Load "progress"
m_Manager->SetValue(StatType, Value);
}
else
{
LOGWARNING("Invalid statistic value for type \"%s\"", StatName.c_str());
}
}
return true;
}

View File

@ -0,0 +1,55 @@
// StatSerializer.h
// Declares the cStatSerializer class that is used for saving stats into JSON
#pragma once
#include "json/json.h"
// fwd:
class cStatManager;
class cStatSerializer
{
public:
cStatSerializer(const AString & a_WorldName, const AString & a_PlayerName, cStatManager * a_Manager);
/* 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);
private:
cStatManager* m_Manager;
AString m_Path;
} ;