#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "cPlayer.h"
#include "cServer.h"
#include "cCreativeInventory.h"
#include "cSurvivalInventory.h"
#include "cClientHandle.h"
#include "cWorld.h"
#include "cPickup.h"
#include "cPluginManager.h"
#include "cWindow.h"
#include "cBlockEntity.h"
#include "cGroupManager.h"
#include "cGroup.h"
#include "cChatColor.h"
#include "cItem.h"
#include "cTracer.h"
#include "cRoot.h"
#include "cMakeDir.h"
#include "cTimer.h"
#include "MersenneTwister.h"
#include "packets/cPacket_NamedEntitySpawn.h"
#include "packets/cPacket_EntityLook.h"
#include "packets/cPacket_TeleportEntity.h"
#include "packets/cPacket_RelativeEntityMove.h"
#include "packets/cPacket_RelativeEntityMoveLook.h"
#include "packets/cPacket_UpdateHealth.h"
#include "packets/cPacket_Respawn.h"
#include "packets/cPacket_DestroyEntity.h"
#include "packets/cPacket_Metadata.h"
#include "packets/cPacket_Chat.h"
#include "packets/cPacket_NewInvalidState.h"
#include "packets/cPacket_BlockAction.h"
#include "Vector3d.h"
#include "Vector3f.h"
#include "../iniFile/iniFile.h"
#include <json/json.h>
#define float2int(x) ((x)<0 ? ((int)(x))-1 : (int)(x))
CLASS_DEFINITION( cPlayer, cPawn );
cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
: m_GameMode( eGameMode_Survival )
, m_IP("")
, m_LastBlockActionTime( 0 )
, m_LastBlockActionCnt( 0 )
, m_bVisible( true )
, m_LastGroundHeight( 0 )
, m_bTouchGround( false )
, m_Stance( 0.0 )
, m_Inventory( 0 )
, m_CurrentWindow( 0 )
, m_TimeLastPickupCheck( 0.f )
, m_Color('-')
, m_ClientHandle( a_Client )
{
LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d",
a_PlayerName.c_str(), a_Client->GetSocket().GetIPString().c_str(),
this, GetUniqueID()
);
m_EntityType = eEntityType_Player;
SetMaxHealth(20);
SetMaxFoodLevel(125);
m_Inventory = new cSurvivalInventory( this );
m_CreativeInventory = new cCreativeInventory(this);
cTimer t1;
m_LastPlayerListTime = t1.GetNowTime();
m_TimeLastTeleportPacket = cWorld::GetTime();
m_TimeLastPickupCheck = cWorld::GetTime();
m_PlayerName = a_PlayerName;
m_bDirtyPosition = true; // So chunks are streamed to player at spawn
if( !LoadFromDisk() )
{
m_Inventory->Clear();
m_CreativeInventory->Clear();
m_Pos.x = cRoot::Get()->GetDefaultWorld()->GetSpawnX();
m_Pos.y = cRoot::Get()->GetDefaultWorld()->GetSpawnY();
m_Pos.z = cRoot::Get()->GetDefaultWorld()->GetSpawnZ();
LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}",
a_PlayerName.c_str(), m_Pos.x, m_Pos.y, m_Pos.z
);
}
}
void cPlayer::Initialize( cWorld* a_World )
{
cPawn::Initialize( a_World );
GetWorld()->AddPlayer( this );
}
cPlayer::~cPlayer(void)
{
LOG("Deleting cPlayer \"%s\" at %p, ID %d", m_PlayerName.c_str(), this, GetUniqueID());
SaveToDisk();
m_World->RemovePlayer( this );
m_ClientHandle = NULL;
delete m_Inventory;
m_Inventory = NULL;
delete m_CreativeInventory;
LOG("Player %p deleted", this);
}
void cPlayer::Destroyed()
{
CloseWindow(-1);
m_ClientHandle = NULL;
}
cPacket * cPlayer::GetSpawnPacket(void) const
{
LOGD("cPlayer::GetSpawnPacket for \"%s\" at pos {%.2f, %.2f, %.2f}",
m_PlayerName.c_str(), m_Pos.x, m_Pos.y, m_Pos.z
);
if (!m_bVisible )
{
return NULL;
}
cPacket_NamedEntitySpawn * SpawnPacket = new cPacket_NamedEntitySpawn;
SpawnPacket->m_UniqueID = m_UniqueID;
SpawnPacket->m_PlayerName = m_PlayerName;
SpawnPacket->m_PosX = (int)(m_Pos.x * 32);
SpawnPacket->m_PosY = (int)(m_Pos.y * 32);
SpawnPacket->m_PosZ = (int)(m_Pos.z * 32);
SpawnPacket->m_Rotation = (char)((m_Rot.x / 360.f) * 256);
SpawnPacket->m_Pitch = (char)((m_Rot.y / 360.f) * 256);
short ItemID = (short)m_Inventory->GetEquippedItem().m_ItemID;
SpawnPacket->m_CurrentItem = (ItemID > 0) ? ItemID : 0; // Unlike -1 in inventory, the named entity packet uses 0 for "none"
return SpawnPacket;
}
void cPlayer::Tick(float a_Dt)
{
if (!m_ClientHandle->IsPlaying())
{
// We're not yet in the game, ignore everything
return;
}
cPawn::Tick(a_Dt);
if (m_bDirtyOrientation && !m_bDirtyPosition)
{
cPacket_EntityLook EntityLook(*this);
m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, EntityLook, m_ClientHandle );
cPacket_EntityHeadLook EntityHeadLook(*this);
m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, EntityHeadLook, m_ClientHandle);
m_bDirtyOrientation = false;
}
else if (m_bDirtyPosition )
{
cRoot::Get()->GetPluginManager()->CallHook( cPluginManager::E_PLUGIN_PLAYER_MOVE, 1, this );
float DiffX = (float)(GetPosX() - m_LastPosX );
float DiffY = (float)(GetPosY() - m_LastPosY );
float DiffZ = (float)(GetPosZ() - m_LastPosZ );
float SqrDist = DiffX * DiffX + DiffY * DiffY + DiffZ * DiffZ;
if (
(SqrDist > 4 * 4) || // 4 blocks is max Relative Move
(cWorld::GetTime() - m_TimeLastTeleportPacket > 2 ) // Send an absolute position every 2 seconds
)
{
//LOG("Teleported %f", sqrtf(SqrDist) );
cPacket_TeleportEntity TeleportEntity( this );
m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, TeleportEntity, m_ClientHandle);
m_TimeLastTeleportPacket = cWorld::GetTime();
}
else
{ // Relative move sucks balls! It's always wrong wtf!
if( m_bDirtyOrientation )
{
cPacket_RelativeEntityMoveLook RelativeEntityMoveLook;
RelativeEntityMoveLook.m_UniqueID = GetUniqueID();
RelativeEntityMoveLook.m_MoveX = (char)(DiffX*32);
RelativeEntityMoveLook.m_MoveY = (char)(DiffY*32);
RelativeEntityMoveLook.m_MoveZ = (char)(DiffZ*32);
RelativeEntityMoveLook.m_Yaw = (char)((GetRotation()/360.f)*256);
RelativeEntityMoveLook.m_Pitch = (char)((GetPitch()/360.f)*256);
m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, RelativeEntityMoveLook, m_ClientHandle );
}
else
{
cPacket_RelativeEntityMove RelativeEntityMove;
RelativeEntityMove.m_UniqueID = GetUniqueID();
RelativeEntityMove.m_MoveX = (char)(DiffX*32);
RelativeEntityMove.m_MoveY = (char)(DiffY*32);
RelativeEntityMove.m_MoveZ = (char)(DiffZ*32);
m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, RelativeEntityMove, m_ClientHandle );
}
}
m_LastPosX = GetPosX();
m_LastPosY = GetPosY();
m_LastPosZ = GetPosZ();
m_bDirtyPosition = false;
m_ClientHandle->StreamChunks();
}
if (m_Health > 0) // make sure player is alive
{
m_World->CollectPickupsByPlayer(this);
}
cTimer t1;
// Send Player List (Once per m_LastPlayerListTime/1000 ms)
if (m_LastPlayerListTime + cPlayer::PLAYER_LIST_TIME_MS <= t1.GetNowTime())
{
m_World->SendPlayerList(this);
m_LastPlayerListTime = t1.GetNowTime();
}
}
void cPlayer::SetTouchGround( bool a_bTouchGround )
{
m_bTouchGround = a_bTouchGround;
if( !m_bTouchGround )
{
cWorld* World = GetWorld();
char BlockID = World->GetBlock( float2int(m_Pos.x), float2int(m_Pos.y), float2int(m_Pos.z) );
if( BlockID != E_BLOCK_AIR )
{
m_bTouchGround = true;
}
if( BlockID == E_BLOCK_WATER || BlockID == E_BLOCK_STATIONARY_WATER || BlockID == E_BLOCK_LADDER || BlockID == E_BLOCK_TORCH )
{
m_LastGroundHeight = (float)m_Pos.y;
}
}
if( m_bTouchGround )
{
float Dist = (float)(m_LastGroundHeight - m_Pos.y);
if( Dist > 4.f ) // Player dropped
{
int Damage = (int)(Dist - 4.f);
if( Damage > 0 )
{
TakeDamage( Damage, 0 );
}
}
m_LastGroundHeight = (float)m_Pos.y;
}
}
void cPlayer::Heal( int a_Health )
{
if( m_Health < GetMaxHealth() )
{
m_Health = (short) MIN(a_Health + m_Health, GetMaxHealth());
cPacket_UpdateHealth Health;
Health.m_Health = m_Health;
Health.m_Food = GetFood();
Health.m_Saturation = GetFoodSaturation();
m_ClientHandle->Send( Health );
}
}
bool cPlayer::Feed(short a_Food)
{
if (m_FoodLevel >= GetMaxFoodLevel())
{
return false;
}
m_FoodLevel = MIN(a_Food + m_FoodLevel, GetMaxFoodLevel());
cPacket_UpdateHealth Health;
Health.m_Health = m_Health;
Health.m_Food = GetFood();
Health.m_Saturation = GetFoodSaturation();
m_ClientHandle->Send( Health );
return true;
}
void cPlayer::TakeDamage( int a_Damage, cEntity* a_Instigator )
{
if ( !(m_GameMode == 1) ) {
cPawn::TakeDamage( a_Damage, a_Instigator );
cPacket_UpdateHealth Health;
Health.m_Health = m_Health;
Health.m_Food = GetFood();
Health.m_Saturation = GetFoodSaturation();
//TODO: Causes problems sometimes O.o (E.G. Disconnecting when attacked)
if(m_ClientHandle != 0)
m_ClientHandle->Send( Health );
}
}
void cPlayer::KilledBy(cEntity * a_Killer)
{
cPawn::KilledBy(a_Killer);
if (m_Health > 0)
{
return; // not dead yet =]
}
m_bVisible = false; // So new clients don't see the player
// Puke out all the items
cItem* Items = m_Inventory->GetSlots();
cItems Pickups;
for (unsigned int i = 1; i < m_Inventory->c_NumSlots; ++i)
{
if( !Items[i].IsEmpty() )
{
Pickups.push_back(Items[i]);
}
Items[i].Empty();
}
m_World->SpawnItemPickups(Pickups, m_Pos.x, m_Pos.y, m_Pos.z, 10);
SaveToDisk(); // Save it, yeah the world is a tough place !
}
void cPlayer::Respawn()
{
m_Health = GetMaxHealth();
// Create Respawn player packet
cPacket_Respawn Packet;
//Set Gamemode for packet by looking at world's gamemode (Need to check players gamemode.)
//Packet.m_CreativeMode = (char)GetWorld()->GetGameMode();
Packet.m_CreativeMode = (char)m_GameMode; //Set GameMode packet based on Player's GameMode;
//Send Packet
m_ClientHandle->Send( Packet );
//Set non Burning
SetMetaData(NORMAL);
TeleportTo( GetWorld()->GetSpawnX(), GetWorld()->GetSpawnY(), GetWorld()->GetSpawnZ() );
SetVisible( true );
}
double cPlayer::GetEyeHeight()
{
return m_Stance;
}
Vector3d cPlayer::GetEyePosition()
{
return Vector3d( m_Pos.x, m_Stance, m_Pos.z );
}
void cPlayer::OpenWindow( cWindow* a_Window )
{
CloseWindow(m_CurrentWindow ? (char)m_CurrentWindow->GetWindowType() : 0);
a_Window->Open( *this );
m_CurrentWindow = a_Window;
}
void cPlayer::CloseWindow(char a_WindowType)
{
if (a_WindowType == 0)
{
// Inventory
if (
(m_Inventory->GetWindow()->GetDraggingItem() != NULL) &&
(m_Inventory->GetWindow()->GetDraggingItem()->m_ItemCount > 0)
)
{
LOG("Player holds item! Dropping it...");
TossItem( true, m_Inventory->GetWindow()->GetDraggingItem()->m_ItemCount );
}
//Drop whats in the crafting slots (1, 2, 3, 4)
cItems Drops;
for (int i = 1; i <= 4; i++)
{
cItem* Item = m_Inventory->GetSlot( i );
if (!Item->IsEmpty())
{
Drops.push_back(*Item);
}
Item->Empty();
}
float vX = 0, vY = 0, vZ = 0;
EulerToVector(-GetRotation(), GetPitch(), vZ, vX, vY);
vY = -vY*2 + 1.f;
m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY() + 1.6f, GetPosZ(), vX * 2, vY * 2, vZ * 2);
}
if (m_CurrentWindow)
{
// FIXME: If the player entity is destroyed while having a chest window open, the chest will not close
if (a_WindowType == 1 && strcmp(m_CurrentWindow->GetWindowTitle().c_str(), "UberChest") == 0) { // Chest
cBlockEntity *block = m_CurrentWindow->GetOwner()->GetEntity();
cPacket_BlockAction ChestClose;
ChestClose.m_PosX = block->GetPosX();
ChestClose.m_PosY = (short)block->GetPosY();
ChestClose.m_PosZ = block->GetPosZ();
ChestClose.m_Byte1 = 1;
ChestClose.m_Byte2 = 0;
m_World->Broadcast(ChestClose);
}
m_CurrentWindow->Close( *this );
}
m_CurrentWindow = NULL;
}
void cPlayer::SetLastBlockActionTime()
{
if (m_World != NULL)
{
m_LastBlockActionTime = m_World->GetTime();
}
}
void cPlayer::SetLastBlockActionCnt( int a_LastBlockActionCnt )
{
m_LastBlockActionCnt = a_LastBlockActionCnt;
}
void cPlayer::SetGameMode( eGameMode a_GameMode )
{
if ( (a_GameMode < 2) && (a_GameMode >= 0) )
{
if (m_GameMode != a_GameMode)
{
cInventory *OldInventory = 0;
if(m_GameMode == eGameMode_Survival)
OldInventory = m_Inventory;
else
OldInventory = m_CreativeInventory;
m_GameMode = a_GameMode;
cPacket_NewInvalidState GameModePacket;
GameModePacket.m_Reason = 3; //GameModeChange
GameModePacket.m_GameMode = (char)a_GameMode; //GameModeChange
m_ClientHandle->Send ( GameModePacket );
GetInventory().SendWholeInventory(m_ClientHandle);
OldInventory->SetEquippedSlot(GetInventory().GetEquippedSlot());
}
}
}
void cPlayer::LoginSetGameMode( eGameMode a_GameMode )
{
m_GameMode = a_GameMode;
}
void cPlayer::SetIP( std::string a_IP )
{
m_IP = a_IP;
}
void cPlayer::SendMessage( const char* a_Message )
{
m_ClientHandle->Send( cPacket_Chat( a_Message ) );
}
void cPlayer::TeleportTo( const double & a_PosX, const double & a_PosY, const double & a_PosZ )
{
SetPosition( a_PosX, a_PosY, a_PosZ );
cPacket_TeleportEntity TeleportEntity( this );
cRoot::Get()->GetServer()->Broadcast( TeleportEntity, GetClientHandle() );
cPacket_PlayerPosition PlayerPosition( this );
m_ClientHandle->Send( PlayerPosition );
}
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
SetPosition( a_NewPos );
}
void cPlayer::SetVisible( bool a_bVisible )
{
if (a_bVisible && !m_bVisible) // Make visible
{
m_bVisible = true;
SpawnOn( NULL ); // Spawn on everybody
}
if (!a_bVisible && m_bVisible)
{
m_bVisible = false;
cPacket_DestroyEntity DestroyEntity( this );
m_World->BroadcastToChunk(m_ChunkX, m_ChunkY, m_ChunkZ, DestroyEntity ); // Destroy on all clients
}
}
void cPlayer::AddToGroup( const char* a_GroupName )
{
cGroup* Group = cRoot::Get()->GetGroupManager()->GetGroup( a_GroupName );
m_Groups.push_back( Group );
LOG("Added %s to group %s", m_PlayerName.c_str(), a_GroupName );
ResolveGroups();
ResolvePermissions();
}
bool cPlayer::CanUseCommand( const char* a_Command )
{
for( GroupList::iterator itr = m_Groups.begin(); itr != m_Groups.end(); ++itr )
{
if( (*itr)->HasCommand( a_Command ) ) return true;
}
return false;
}
bool cPlayer::HasPermission( const char* a_Permission )
{
AStringVector Split = StringSplit( a_Permission, "." );
PermissionMap Possibilities = m_ResolvedPermissions;
// Now search the namespaces
while( Possibilities.begin() != Possibilities.end() )
{
PermissionMap::iterator itr = Possibilities.begin();
if( itr->second )
{
AStringVector OtherSplit = StringSplit( itr->first, "." );
if( OtherSplit.size() <= Split.size() )
{
unsigned int i;
for( i = 0; i < OtherSplit.size(); ++i )
{
if( OtherSplit[i].compare( Split[i] ) != 0 )
{
if( OtherSplit[i].compare("*") == 0 ) return true; // WildCard man!! WildCard!
break;
}
}
if( i == Split.size() ) return true;
}
}
Possibilities.erase( itr );
}
// Nothing that matched :(
return false;
}
bool cPlayer::IsInGroup( const char* a_Group )
{
for( GroupList::iterator itr = m_ResolvedGroups.begin(); itr != m_ResolvedGroups.end(); ++itr )
{
if( strcmp( a_Group, (*itr)->GetName().c_str() ) == 0 )
return true;
}
return false;
}
void cPlayer::ResolvePermissions()
{
m_ResolvedPermissions.clear(); // Start with an empty map yo~
// Copy all player specific permissions into the resolved permissions map
for( PermissionMap::iterator itr = m_Permissions.begin(); itr != m_Permissions.end(); ++itr )
{
m_ResolvedPermissions[ itr->first ] = itr->second;
}
for( GroupList::iterator GroupItr = m_ResolvedGroups.begin(); GroupItr != m_ResolvedGroups.end(); ++GroupItr )
{
const cGroup::PermissionMap & Permissions = (*GroupItr)->GetPermissions();
for( cGroup::PermissionMap::const_iterator itr = Permissions.begin(); itr != Permissions.end(); ++itr )
{
m_ResolvedPermissions[ itr->first ] = itr->second;
}
}
}
void cPlayer::ResolveGroups()
{
// Clear resolved groups first
m_ResolvedGroups.clear();
// Get a complete resolved list of all groups the player is in
std::map< cGroup*, bool > AllGroups; // Use a map, because it's faster than iterating through a list to find duplicates
GroupList ToIterate;
for( GroupList::iterator GroupItr = m_Groups.begin(); GroupItr != m_Groups.end(); ++GroupItr )
{
ToIterate.push_back( *GroupItr );
}
while( ToIterate.begin() != ToIterate.end() )
{
cGroup* CurrentGroup = *ToIterate.begin();
if( AllGroups.find( CurrentGroup ) != AllGroups.end() )
{
LOGWARNING("ERROR: Player \"%s\" is in the group multiple times (\"%s\"). Please fix your settings in users.ini!",
m_PlayerName.c_str(), CurrentGroup->GetName().c_str()
);
}
else
{
AllGroups[ CurrentGroup ] = true;
m_ResolvedGroups.push_back( CurrentGroup ); // Add group to resolved list
const cGroup::GroupList & Inherits = CurrentGroup->GetInherits();
for( cGroup::GroupList::const_iterator itr = Inherits.begin(); itr != Inherits.end(); ++itr )
{
if( AllGroups.find( *itr ) != AllGroups.end() )
{
LOGERROR("ERROR: Player %s is in the same group multiple times due to inheritance (%s). FIX IT!", m_PlayerName.c_str(), (*itr)->GetName().c_str() );
continue;
}
ToIterate.push_back( *itr );
}
}
ToIterate.erase( ToIterate.begin() );
}
}
AString cPlayer::GetColor(void) const
{
if ( m_Color != '-' )
{
return cChatColor::MakeColor( m_Color );
}
if ( m_Groups.size() < 1 )
{
return cChatColor::White;
}
return (*m_Groups.begin())->GetColor();
}
void cPlayer::TossItem( bool a_bDraggingItem, int a_Amount /* = 1 */ )
{
cItems Drops;
if (a_bDraggingItem)
{
cItem * Item = GetInventory().GetWindow()->GetDraggingItem();
if (!Item->IsEmpty())
{
Drops.push_back(*Item);
if( Item->m_ItemCount > a_Amount )
Item->m_ItemCount -= (char)a_Amount;
else
Item->Empty();
}
}
else
{
// Else drop equipped item
cItem DroppedItem = GetInventory().GetEquippedItem();
if (!DroppedItem.IsEmpty())
{
DroppedItem.m_ItemCount = 1;
if (GetInventory().RemoveItem(DroppedItem))
{
DroppedItem.m_ItemCount = 1; // RemoveItem decreases the count, so set it to 1 again
Drops.push_back(DroppedItem);
}
}
}
float vX = 0, vY = 0, vZ = 0;
EulerToVector( -GetRotation(), GetPitch(), vZ, vX, vY );
vY = -vY*2 + 1.f;
m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY() + 1.6f, GetPosZ(), vX * 2, vY * 2, vZ * 2);
}
bool cPlayer::MoveToWorld( const char* a_WorldName )
{
cWorld * World = cRoot::Get()->GetWorld( a_WorldName );
if ( World )
{
/* Remove all links to the old world */
m_World->RemovePlayer( this );
m_ClientHandle->RemoveFromAllChunks();
m_World->RemoveEntityFromChunk(this, m_ChunkX, m_ChunkY, m_ChunkZ);
/* Add player to all the necessary parts of the new world */
SetWorld( World );
GetWorld()->AddPlayer( this );
MoveToCorrectChunk(true);
GetClientHandle()->StreamChunks();
return true;
}
return false;
}
void cPlayer::LoadPermissionsFromDisk()
{
m_Groups.clear();
m_Permissions.clear();
cIniFile IniFile("users.ini");
if( IniFile.ReadFile() )
{
std::string Groups = IniFile.GetValue(m_PlayerName, "Groups", "");
if( Groups.size() > 0 )
{
AStringVector Split = StringSplit( Groups, "," );
for( unsigned int i = 0; i < Split.size(); i++ )
{
AddToGroup( Split[i].c_str() );
}
}
else
{
AddToGroup("Default");
}
m_Color = IniFile.GetValue(m_PlayerName, "Color", "-")[0];
}
else
{
LOGWARN("WARNING: Failed to read ini file users.ini");
AddToGroup("Default");
}
ResolvePermissions();
}
bool cPlayer::LoadFromDisk()
{
LoadPermissionsFromDisk();
// Log player permissions, cause it's what the cool kids do
LOGINFO("Player %s has permissions:", m_PlayerName.c_str() );
for( PermissionMap::iterator itr = m_ResolvedPermissions.begin(); itr != m_ResolvedPermissions.end(); ++itr )
{
if( itr->second ) LOGINFO("%s", itr->first.c_str() );
}
AString SourceFile;
Printf(SourceFile, "players/%s.json", m_PlayerName.c_str() );
cFile f;
if (!f.Open(SourceFile, cFile::fmRead))
{
return false;
}
AString buffer;
if (f.ReadRestOfFile(buffer) != f.GetSize())
{
LOGERROR("ERROR READING FROM FILE \"%s\"", SourceFile.c_str());
return false;
}
f.Close();
Json::Value root;
Json::Reader reader;
if (!reader.parse(buffer, root, false))
{
LOGERROR("ERROR WHILE PARSING JSON FROM FILE %s", SourceFile.c_str());
}
Json::Value & JSON_PlayerPosition = root["position"];
if( JSON_PlayerPosition.size() == 3 )
{
m_Pos.x = JSON_PlayerPosition[(unsigned int)0].asDouble();
m_Pos.y = JSON_PlayerPosition[(unsigned int)1].asDouble();
m_Pos.z = JSON_PlayerPosition[(unsigned int)2].asDouble();
}
Json::Value & JSON_PlayerRotation = root["rotation"];
if( JSON_PlayerRotation.size() == 3 )
{
m_Rot.x = (float)JSON_PlayerRotation[(unsigned int)0].asDouble();
m_Rot.y = (float)JSON_PlayerRotation[(unsigned int)1].asDouble();
m_Rot.z = (float)JSON_PlayerRotation[(unsigned int)2].asDouble();
}
m_Health = (short)root.get("health", 0 ).asInt();
m_FoodLevel = (short)root.get("food", 0 ).asInt();
m_Inventory->LoadFromJson(root["inventory"]);
m_CreativeInventory->LoadFromJson(root["creativeinventory"]);
m_LoadedWorldName = root.get("world", "world").asString();
LOGD("Player \"%s\" was read from file, spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
m_PlayerName.c_str(), m_Pos.x, m_Pos.y, m_Pos.z, m_LoadedWorldName.c_str()
);
return true;
}
bool cPlayer::SaveToDisk()
{
cMakeDir::MakeDir("players");
// create the JSON data
Json::Value JSON_PlayerPosition;
JSON_PlayerPosition.append( Json::Value( m_Pos.x ) );
JSON_PlayerPosition.append( Json::Value( m_Pos.y ) );
JSON_PlayerPosition.append( Json::Value( m_Pos.z ) );
Json::Value JSON_PlayerRotation;
JSON_PlayerRotation.append( Json::Value( m_Rot.x ) );
JSON_PlayerRotation.append( Json::Value( m_Rot.y ) );
JSON_PlayerRotation.append( Json::Value( m_Rot.z ) );
Json::Value JSON_Inventory;
m_Inventory->SaveToJson( JSON_Inventory );
Json::Value JSON_CreativeInventory;
m_CreativeInventory->SaveToJson( JSON_CreativeInventory );
Json::Value root;
root["position"] = JSON_PlayerPosition;
root["rotation"] = JSON_PlayerRotation;
root["inventory"] = JSON_Inventory;
root["creativeinventory"] = JSON_CreativeInventory;
root["health"] = m_Health;
root["food"] = m_FoodLevel;
root["world"] = GetWorld()->GetName();
Json::StyledWriter writer;
std::string JsonData = writer.write( root );
AString SourceFile;
Printf(SourceFile, "players/%s.json", m_PlayerName.c_str() );
cFile f;
if (!f.Open(SourceFile, cFile::fmWrite))
{
LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", m_PlayerName.c_str(), SourceFile.c_str());
return false;
}
if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size())
{
LOGERROR("ERROR WRITING PLAYER JSON TO FILE \"%s\"", SourceFile.c_str());
return false;
}
return true;
}
cPlayer::StringList cPlayer::GetResolvedPermissions()
{
StringList Permissions;
const PermissionMap& ResolvedPermissions = m_ResolvedPermissions;
for( PermissionMap::const_iterator itr = ResolvedPermissions.begin(); itr != ResolvedPermissions.end(); ++itr )
{
if( itr->second ) Permissions.push_back( itr->first );
}
return Permissions;
}
void cPlayer::UseEquippedItem()
{
if(GetGameMode() != 1) //No damage in creative
{
if (GetInventory().GetEquippedItem().DamageItem())
{
LOG("Player %s Broke ID: %i", GetClientHandle()->GetUsername().c_str(), GetInventory().GetEquippedItem().m_ItemID);
GetInventory().RemoveItem( GetInventory().GetEquippedItem());
}
}
}
bool cPlayer::EatItem(int a_ItemType)
{
// TODO: Handle hunger
switch (a_ItemType)
{
case E_ITEM_APPLE: return Feed(24); // 2 food bars
case E_ITEM_GOLDEN_APPLE: return Feed(60); // 5 food
case E_ITEM_MUSHROOM_SOUP: return Feed(48); // 4 food
case E_ITEM_BREAD: return Feed(30); // 2.5 food
case E_ITEM_RAW_MEAT: return Feed(18); // 1.5 food
case E_ITEM_COOKED_MEAT: return Feed(48); // 4 food
case E_ITEM_RAW_FISH: return Feed(12); // 1 food
case E_ITEM_COOKED_FISH: return Feed(30); // 2.5 food
case E_ITEM_COOKED_CHICKEN: return Feed(36); // 3 food
case E_ITEM_RAW_BEEF: return Feed(18); // 1.5 food
case E_ITEM_STEAK: return Feed(48); // 4 food
case E_ITEM_RAW_CHICKEN:
{
if (!Feed(12)) // 1 food
{
return false;
}
// TODO: A random chance to get food-poisoned
return true;
}
case E_ITEM_ROTTEN_FLESH:
{
if (!Feed(24))
{
return false;
}
// TODO: Food-poisoning
return true;
}
}
return false;
}