#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 "Vector3d.h" #include "Vector3f.h" #include "../iniFile/iniFile.h" #include #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_NotSet) , 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 ) , m_FoodExhaustionLevel(0.f) , m_FoodTickTimer(0) { 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); m_MaxFoodLevel = 20; m_MaxFoodSaturationLevel = 20.f; m_FoodLevel = m_MaxFoodLevel; m_FoodSaturationLevel = 5.f; 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 ); } m_LastGroundHeight = (float)(m_Pos.y); m_Stance = m_Pos.y + 1.62; } 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) { m_World->BroadcastEntLook(*this, m_ClientHandle); m_World->BroadcastEntHeadLook(*this, 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) ); m_World->BroadcastTeleportEntity(*this, m_ClientHandle); m_TimeLastTeleportPacket = cWorld::GetTime(); } else { // Relative move sucks balls! It's always wrong wtf! if (m_bDirtyOrientation) { m_World->BroadcastRelEntMoveLook(*this, (char)(DiffX * 32), (char)(DiffY * 32), (char)(DiffZ * 32), m_ClientHandle); m_bDirtyOrientation = false; } else { m_World->BroadcastRelEntMove(*this, (char)(DiffX * 32), (char)(DiffY * 32), (char)(DiffZ * 32), 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); //Handle Health: m_FoodTickTimer++; if(m_FoodTickTimer >= 80) { m_FoodTickTimer = 0; if(m_FoodLevel >= 17) { Heal(1); }else if(m_FoodLevel == 0) { TakeDamage(1, NULL); } } // TODO: Increase Exhaustion level http://www.minecraftwiki.net/wiki/Hunger#Exhaustion_level_increase if (m_FoodExhaustionLevel >= 4.f) { m_FoodExhaustionLevel -= 4.f; if (m_FoodSaturationLevel >= 1.f) { m_FoodSaturationLevel--; } else { m_FoodLevel = MAX(m_FoodLevel -1, 0); } SendHealth(); } } 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 ) { // LOGD("TouchGround set to true by server"); m_bTouchGround = true; } if( BlockID == E_BLOCK_WATER || BlockID == E_BLOCK_STATIONARY_WATER || BlockID == E_BLOCK_LADDER || BlockID == E_BLOCK_TORCH ) { // LOGD("Water / Ladder / Torch"); m_LastGroundHeight = (float)m_Pos.y; } } if (m_bTouchGround) { float Dist = (float)(m_LastGroundHeight - m_Pos.y); 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()); SendHealth(); } } bool cPlayer::Feed(short a_Food, float a_Saturation) { if (m_FoodLevel >= GetMaxFoodLevel()) { return false; } m_FoodLevel = MIN(a_Food + m_FoodLevel, GetMaxFoodLevel()); m_FoodSaturationLevel = MIN(m_FoodSaturationLevel + a_Saturation, GetMaxFoodSaturationLevel()); SendHealth(); return true; } void cPlayer::SendHealth() { if (m_ClientHandle != NULL) { m_ClientHandle->SendHealth(); } } void cPlayer::TakeDamage( int a_Damage, cEntity* a_Instigator ) { if (m_GameMode != eGameMode_Creative) { cPawn::TakeDamage( a_Damage, a_Instigator ); AddFoodExhaustion(0.3f); SendHealth(); } } 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(); m_ClientHandle->SendRespawn(); // 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) ) { LOGD("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 != NULL) { // FIXME: If the player entity is destroyed while having a chest window open, the chest will not close if ((a_WindowType == 1) && (m_CurrentWindow->GetWindowType() == cWindow::Chest)) { int x, y, z; m_CurrentWindow->GetOwner()->GetBlockPos(x, y, z); m_World->BroadcastBlockAction(x, y, z, 1, 0); } 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)) { LOGWARNING("%s: Setting invalid gamemode: %d", GetName().c_str(), a_GameMode); return; } if (m_GameMode == a_GameMode) { // Gamemode already set return; } short OldSlotNum = 0; if (m_GameMode == eGameMode_Survival) { OldSlotNum = m_Inventory->GetEquippedSlot(); } else { OldSlotNum = m_CreativeInventory->GetEquippedSlot(); } m_GameMode = a_GameMode; m_ClientHandle->SendGameMode(a_GameMode); GetInventory().SendWholeInventory(m_ClientHandle); GetInventory().SetEquippedSlot(OldSlotNum); } void cPlayer::LoginSetGameMode( eGameMode a_GameMode ) { m_GameMode = a_GameMode; } void cPlayer::SetIP(const AString & a_IP) { m_IP = a_IP; } void cPlayer::SendMessage(const AString & a_Message) { m_ClientHandle->SendChat(a_Message); } void cPlayer::TeleportTo(const double & a_PosX, const double & a_PosY, const double & a_PosZ) { SetPosition( a_PosX, a_PosY, a_PosZ ); m_World->BroadcastTeleportEntity(*this, GetClientHandle()); m_ClientHandle->SendPlayerMoveLook(); } 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 ); SetStance(a_NewPos.y + 1.62); } void cPlayer::SetVisible(bool a_bVisible) { if (a_bVisible && !m_bVisible) // Make visible { m_bVisible = true; SpawnOn(NULL); // Spawn on all clients } if (!a_bVisible && m_bVisible) { m_bVisible = false; m_World->BroadcastDestroyEntity(*this, m_ClientHandle); // 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 */, int a_CreateType /* = 0 */, int a_CreateHealth /* = 0 */ ) { cItems Drops; if (a_CreateType) { // Just create item without touching the inventory (used in creative mode) Drops.push_back(cItem((ENUM_ITEM_ID)a_CreateType, (char)a_Amount, a_CreateHealth)); } else { // Drop an item from the inventory: 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", m_MaxFoodLevel ).asInt(); m_FoodSaturationLevel = (float)root.get("foodSaturation", m_MaxFoodSaturationLevel ).asDouble(); m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).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["foodSaturation"] = m_FoodSaturationLevel; root["world"] = GetWorld()->GetName(); if(m_GameMode == GetWorld()->GetGameMode()) { root["gamemode"] = (int) eGameMode_NotSet; }else{ root["gamemode"] = (int) m_GameMode; } 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()); } } }