commit
ac00568183
@ -76,6 +76,7 @@ $cfile "../CompositeChat.h"
|
||||
$cfile "../Map.h"
|
||||
$cfile "../MapManager.h"
|
||||
$cfile "../Scoreboard.h"
|
||||
$cfile "../Statistics.h"
|
||||
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -10,3 +10,5 @@ file(GLOB SOURCE
|
||||
)
|
||||
|
||||
add_library(Entities ${SOURCE})
|
||||
|
||||
target_link_libraries(Entities WorldStorage)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 ;)
|
||||
|
@ -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())
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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);
|
||||
} ;
|
||||
|
||||
|
||||
|
146
src/WorldStorage/StatSerializer.cpp
Normal file
146
src/WorldStorage/StatSerializer.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
55
src/WorldStorage/StatSerializer.h
Normal file
55
src/WorldStorage/StatSerializer.h
Normal 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;
|
||||
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user