2014-06-16 21:57:23 +02:00
2013-07-29 12:13:03 +01:00
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Player.h"
2014-12-24 07:20:17 +01:00
#include <unordered_map>
2014-07-17 23:25:14 -07:00
#include "../ChatColor.h"
2013-08-19 11:39:13 +02:00
#include "../Server.h"
2014-12-13 15:06:55 +01:00
#include "../UI/InventoryWindow.h"
2013-08-19 11:39:13 +02:00
#include "../UI/WindowOwner.h"
#include "../World.h"
2013-12-08 12:17:54 +01:00
#include "../Bindings/PluginManager.h"
2013-08-19 11:39:13 +02:00
#include "../BlockEntities/BlockEntity.h"
2014-06-29 11:36:38 +01:00
#include "../BlockEntities/EnderChestEntity.h"
2013-08-19 11:39:13 +02:00
#include "../Root.h"
#include "../Chunk.h"
#include "../Items/ItemHandler.h"
2014-03-11 16:01:17 +02:00
#include "../Vector3.h"
2014-08-19 16:08:17 +02:00
#include "../FastRandom.h"
2013-07-29 12:13:03 +01:00
2014-05-11 14:57:06 +03:00
#include "../WorldStorage/StatSerializer.h"
2014-05-12 17:05:09 +03:00
#include "../CompositeChat.h"
2014-05-11 14:57:06 +03:00
2014-12-24 07:20:17 +01:00
#include "../Blocks/BlockHandler.h"
#include "../Blocks/BlockSlab.h"
#include "../Blocks/ChunkInterface.h"
2014-10-23 15:15:10 +02:00
#include "../IniFile.h"
2013-11-27 09:17:25 +01:00
#include "json/json.h"
2013-07-29 12:13:03 +01:00
2014-06-16 15:12:50 +01:00
// 6000 ticks or 5 minutes
#define PLAYER_INVENTORY_SAVE_INTERVAL 6000
// 1000 = once per second
2014-10-20 18:59:40 +01:00
#define PLAYER_LIST_TIME_MS std::chrono::milliseconds(1000)
2014-06-16 15:12:50 +01:00
2013-07-29 12:13:03 +01:00
2014-08-10 17:13:14 -07:00
const int cPlayer :: MAX_HEALTH = 20 ;
const int cPlayer :: MAX_FOOD_LEVEL = 20 ;
/** Number of ticks it takes to eat an item */
const int cPlayer :: EATING_TICKS = 30 ;
2013-07-29 12:13:03 +01:00
2013-11-14 00:50:47 +11:00
2015-01-24 20:17:00 +01:00
cPlayer :: cPlayer ( cClientHandlePtr a_Client , const AString & a_PlayerName ) :
2014-07-11 13:13:10 +02:00
super ( etPlayer , 0.6 , 1.8 ),
m_bVisible ( true ),
m_FoodLevel ( MAX_FOOD_LEVEL ),
m_FoodSaturationLevel ( 5.0 ),
m_FoodTickTimer ( 0 ),
m_FoodExhaustionLevel ( 0.0 ),
m_LastJumpHeight ( 0 ),
m_LastGroundHeight ( 0 ),
m_bTouchGround ( false ),
m_Stance ( 0.0 ),
m_Inventory ( * this ),
m_EnderChestContents ( 9 , 3 ),
2014-10-20 21:55:07 +01:00
m_CurrentWindow ( nullptr ),
m_InventoryWindow ( nullptr ),
2014-07-11 13:13:10 +02:00
m_GameMode ( eGameMode_NotSet ),
m_IP ( "" ),
m_ClientHandle ( a_Client ),
m_NormalMaxSpeed ( 1.0 ),
m_SprintingMaxSpeed ( 1.3 ),
m_FlyingMaxSpeed ( 1.0 ),
m_IsCrouched ( false ),
m_IsSprinting ( false ),
m_IsFlying ( false ),
m_IsSwimming ( false ),
m_IsSubmerged ( false ),
m_IsFishing ( false ),
m_CanFly ( false ),
m_EatingFinishTick ( - 1 ),
m_LifetimeTotalXp ( 0 ),
m_CurrentXp ( 0 ),
m_bDirtyExperience ( false ),
m_IsChargingBow ( false ),
m_BowCharge ( 0 ),
m_FloaterID ( - 1 ),
2014-10-20 21:55:07 +01:00
m_Team ( nullptr ),
2014-07-11 13:13:10 +02:00
m_TicksUntilNextSave ( PLAYER_INVENTORY_SAVE_INTERVAL ),
m_bIsTeleporting ( false ),
2014-10-20 21:55:07 +01:00
m_UUID (( a_Client != nullptr ) ? a_Client -> GetUUID () : "" ),
2014-09-02 19:12:35 +02:00
m_CustomName ( "" )
2013-07-29 12:13:03 +01:00
{
m_InventoryWindow = new cInventoryWindow ( * this );
m_CurrentWindow = m_InventoryWindow ;
m_InventoryWindow -> OpenedByPlayer ( * this );
2013-08-01 09:51:25 +02:00
SetMaxHealth ( MAX_HEALTH );
m_Health = MAX_HEALTH ;
2015-05-23 19:31:33 +01:00
2014-10-20 18:59:40 +01:00
m_LastPlayerListTime = std :: chrono :: steady_clock :: now ();
2013-07-29 12:13:03 +01:00
m_PlayerName = a_PlayerName ;
2014-10-20 21:55:07 +01:00
cWorld * World = nullptr ;
2014-06-01 18:46:59 +01:00
if ( ! LoadFromDisk ( World ))
2013-07-29 12:13:03 +01:00
{
m_Inventory . Clear ();
2014-06-01 18:46:59 +01:00
SetPosX ( World -> GetSpawnX ());
SetPosY ( World -> GetSpawnY ());
SetPosZ ( World -> GetSpawnZ ());
2015-03-01 03:45:06 +10:30
SetBedPos ( Vector3i ( static_cast < int > ( World -> GetSpawnX ()), static_cast < int > ( World -> GetSpawnY ()), static_cast < int > ( World -> GetSpawnZ ())));
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
LOGD ( "Player \" %s \" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}" ,
a_PlayerName . c_str (), GetPosX (), GetPosY (), GetPosZ ()
);
}
2014-01-24 23:58:51 +00:00
2015-03-01 03:45:06 +10:30
m_LastJumpHeight = static_cast < float > ( GetPosY ());
m_LastGroundHeight = static_cast < float > ( GetPosY ());
2013-07-29 12:13:03 +01:00
m_Stance = GetPosY () + 1.62 ;
2014-01-24 23:58:51 +00:00
if ( m_GameMode == gmNotSet )
{
if ( World -> IsGameModeCreative ())
{
m_CanFly = true ;
}
2014-12-03 23:04:53 -05:00
if ( World -> IsGameModeSpectator ()) // Otherwise Player will fall out of the world on join
2014-12-02 20:54:56 -05:00
{
m_CanFly = true ;
m_IsFlying = true ;
}
2014-01-24 23:58:51 +00:00
}
2015-05-22 16:30:23 -07:00
if ( m_GameMode == gmSpectator ) // If player is reconnecting to the server in spectator mode
{
m_CanFly = true ;
m_IsFlying = true ;
m_bVisible = false ;
}
2015-05-23 19:31:33 +01:00
2013-08-14 10:24:34 +02:00
cRoot :: Get () -> GetServer () -> PlayerCreated ( this );
2013-07-29 12:13:03 +01:00
}
cPlayer ::~ cPlayer ( void )
{
2014-02-03 21:12:44 +00:00
if ( ! cRoot :: Get () -> GetPluginManager () -> CallHookPlayerDestroyed ( * this ))
{
2014-02-05 23:24:16 +00:00
cRoot :: Get () -> BroadcastChatLeave ( Printf ( "%s has left the game" , GetName (). c_str ()));
2014-07-14 19:49:31 +01:00
LOGINFO ( "Player %s has left the game" , GetName (). c_str ());
2014-02-03 21:12:44 +00:00
}
2014-01-25 11:25:22 +01:00
2014-02-03 22:24:22 +00:00
LOGD ( "Deleting cPlayer \" %s \" at %p, ID %d" , GetName (). c_str (), this , GetUniqueID ());
2015-05-23 19:31:33 +01:00
2013-08-14 19:11:54 +02:00
// Notify the server that the player is being destroyed
cRoot :: Get () -> GetServer () -> PlayerDestroying ( this );
2013-07-29 12:13:03 +01:00
SaveToDisk ();
2014-10-20 21:55:07 +01:00
m_ClientHandle = nullptr ;
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
delete m_InventoryWindow ;
2014-10-20 21:55:07 +01:00
m_InventoryWindow = nullptr ;
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
LOGD ( "Player %p deleted" , this );
}
void cPlayer :: Destroyed ()
{
CloseWindow ( false );
}
void cPlayer :: SpawnOn ( cClientHandle & a_Client )
{
2015-01-24 20:17:00 +01:00
if ( ! m_bVisible || ( m_ClientHandle . get () == ( & a_Client )))
2013-07-29 12:13:03 +01:00
{
2013-08-13 22:45:29 +02:00
return ;
2013-07-29 12:13:03 +01:00
}
2013-08-13 22:45:29 +02:00
a_Client . SendPlayerSpawn ( * this );
a_Client . SendEntityHeadLook ( * this );
2014-07-21 15:19:48 +02:00
a_Client . SendEntityEquipment ( * this , 0 , m_Inventory . GetEquippedItem ());
a_Client . SendEntityEquipment ( * this , 1 , m_Inventory . GetEquippedBoots ());
a_Client . SendEntityEquipment ( * this , 2 , m_Inventory . GetEquippedLeggings ());
a_Client . SendEntityEquipment ( * this , 3 , m_Inventory . GetEquippedChestplate ());
a_Client . SendEntityEquipment ( * this , 4 , m_Inventory . GetEquippedHelmet ());
2013-07-29 12:13:03 +01:00
}
2015-01-11 21:12:26 +00:00
void cPlayer :: Tick ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
2013-07-29 12:13:03 +01:00
{
2014-10-20 21:55:07 +01:00
if ( m_ClientHandle != nullptr )
2013-07-29 12:13:03 +01:00
{
2013-08-13 22:45:29 +02:00
if ( m_ClientHandle -> IsDestroyed ())
{
// This should not happen, because destroying a client will remove it from the world, but just in case
2014-10-20 21:55:07 +01:00
m_ClientHandle = nullptr ;
2013-08-13 22:45:29 +02:00
return ;
}
2015-05-23 19:31:33 +01:00
2013-08-12 08:35:13 +02:00
if ( ! m_ClientHandle -> IsPlaying ())
{
// We're not yet in the game, ignore everything
return ;
}
2013-07-29 12:13:03 +01:00
}
2014-05-12 21:38:52 +03:00
m_Stats . AddValue ( statMinutesPlayed , 1 );
2015-05-23 19:31:33 +01:00
2013-08-20 21:17:33 +02:00
if ( ! a_Chunk . IsValid ())
{
// This may happen if the cPlayer is created before the chunks have the chance of being loaded / generated (#83)
return ;
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
super :: Tick ( a_Dt , a_Chunk );
2015-05-23 19:31:33 +01:00
2013-08-30 14:24:03 +02:00
// Handle charging the bow:
if ( m_IsChargingBow )
{
m_BowCharge += 1 ;
}
2015-05-23 19:31:33 +01:00
2014-04-23 21:06:46 +01:00
// Handle updating experience
2013-11-16 20:29:57 +11:00
if ( m_bDirtyExperience )
{
SendExperience ();
}
2013-08-08 10:32:34 +01:00
2014-08-16 18:44:14 +02:00
bool CanMove = true ;
2015-03-05 11:52:42 +01:00
if ( ! GetPosition (). EqualsEps ( m_LastPos , 0.02 )) // Non negligible change in position from last tick? 0.02 tp prevent continous calling while floating sometimes.
2013-07-29 12:13:03 +01:00
{
// Apply food exhaustion from movement:
2013-08-09 08:50:33 +01:00
ApplyFoodExhaustionFromMovement ();
2015-05-23 19:31:33 +01:00
2014-08-16 18:44:14 +02:00
if ( cRoot :: Get () -> GetPluginManager () -> CallHookPlayerMoving ( * this , m_LastPos , GetPosition ()))
{
CanMove = false ;
TeleportToCoords ( m_LastPos . x , m_LastPos . y , m_LastPos . z );
}
2013-07-29 12:13:03 +01:00
}
2014-04-23 21:06:46 +01:00
2014-08-16 18:44:14 +02:00
if ( CanMove )
{
2015-01-24 20:17:00 +01:00
BroadcastMovementUpdate ( m_ClientHandle . get ());
2014-08-16 18:44:14 +02:00
}
2013-07-29 12:13:03 +01:00
if ( m_Health > 0 ) // make sure player is alive
{
2014-10-15 19:01:55 +02:00
m_World -> CollectPickupsByPlayer ( * this );
2013-07-29 12:13:03 +01:00
if (( m_EatingFinishTick >= 0 ) && ( m_EatingFinishTick <= m_World -> GetWorldAge ()))
{
FinishEating ();
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
HandleFood ();
}
2015-05-23 19:31:33 +01:00
2013-12-21 17:31:05 +01:00
if ( m_IsFishing )
{
HandleFloater ();
}
2014-02-17 16:27:12 +02:00
// Update items (e.g. Maps)
m_Inventory . UpdateItems ();
2013-07-29 12:13:03 +01:00
// Send Player List (Once per m_LastPlayerListTime/1000 ms)
2014-10-20 18:59:40 +01:00
if ( m_LastPlayerListTime + PLAYER_LIST_TIME_MS <= std :: chrono :: steady_clock :: now ())
2013-07-29 12:13:03 +01:00
{
2014-09-18 18:51:36 +02:00
m_World -> BroadcastPlayerListUpdatePing ( * this );
2014-10-20 18:59:40 +01:00
m_LastPlayerListTime = std :: chrono :: steady_clock :: now ();
2013-07-29 12:13:03 +01:00
}
2013-12-22 20:03:09 +00:00
if ( IsFlying ())
2013-12-25 14:05:37 +00:00
{
2015-03-01 03:45:06 +10:30
m_LastGroundHeight = static_cast < float > ( GetPosY ());
2013-12-25 14:05:37 +00:00
}
2014-06-16 15:12:50 +01:00
if ( m_TicksUntilNextSave == 0 )
{
SaveToDisk ();
m_TicksUntilNextSave = PLAYER_INVENTORY_SAVE_INTERVAL ;
}
else
{
m_TicksUntilNextSave -- ;
}
2013-07-29 12:13:03 +01:00
}
2015-03-01 03:03:41 +10:30
int cPlayer :: CalcLevelFromXp ( int a_XpTotal )
2013-11-14 04:25:47 +11:00
{
2014-07-17 22:15:34 +02:00
// level 0 to 15
2014-07-20 23:10:31 +02:00
if ( a_XpTotal <= XP_TO_LEVEL15 )
2013-11-14 04:25:47 +11:00
{
2013-11-14 07:02:53 +11:00
return a_XpTotal / XP_PER_LEVEL_TO15 ;
2013-11-14 04:25:47 +11:00
}
2014-07-17 22:15:34 +02:00
// level 30+
2014-07-20 23:10:31 +02:00
if ( a_XpTotal > XP_TO_LEVEL30 )
2013-11-14 04:25:47 +11:00
{
2015-03-01 03:45:06 +10:30
return static_cast < int > (( 151.5 + sqrt ( 22952.25 - ( 14 * ( 2220 - a_XpTotal )))) / 7 );
2013-11-14 04:25:47 +11:00
}
2014-07-17 22:15:34 +02:00
// level 16 to 30
2015-03-01 03:45:06 +10:30
return static_cast < int > (( 29.5 + sqrt ( 870.25 - ( 6 * ( 360 - a_XpTotal )))) / 3 );
2013-11-14 04:25:47 +11:00
}
2015-03-01 03:03:41 +10:30
int cPlayer :: XpForLevel ( int a_Level )
2013-11-14 04:25:47 +11:00
{
2014-07-17 22:15:34 +02:00
// level 0 to 15
2014-07-20 23:10:31 +02:00
if ( a_Level <= 15 )
2013-11-14 04:25:47 +11:00
{
return a_Level * XP_PER_LEVEL_TO15 ;
}
2014-07-17 22:15:34 +02:00
// level 30+
2014-07-20 23:10:31 +02:00
if ( a_Level >= 31 )
2013-11-14 04:25:47 +11:00
{
2015-03-01 03:45:06 +10:30
return static_cast < int > (( 3.5 * a_Level * a_Level ) - ( 151.5 * a_Level ) + 2220 );
2013-11-14 04:25:47 +11:00
}
2014-07-17 22:15:34 +02:00
// level 16 to 30
2015-03-01 03:45:06 +10:30
return static_cast < int > (( 1.5 * a_Level * a_Level ) - ( 29.5 * a_Level ) + 360 );
2013-11-14 04:25:47 +11:00
}
2015-03-01 03:03:41 +10:30
int cPlayer :: GetXpLevel ()
2013-11-14 04:25:47 +11:00
{
2013-11-16 20:29:57 +11:00
return CalcLevelFromXp ( m_CurrentXp );
2013-11-14 04:25:47 +11:00
}
2013-11-16 20:29:57 +11:00
float cPlayer :: GetXpPercentage ()
2013-11-14 04:25:47 +11:00
{
2015-03-01 03:03:41 +10:30
int currentLevel = CalcLevelFromXp ( m_CurrentXp );
int currentLevel_XpBase = XpForLevel ( currentLevel );
2013-11-14 04:25:47 +11:00
2015-03-01 03:45:06 +10:30
return static_cast < float > ( m_CurrentXp - currentLevel_XpBase ) /
2015-05-09 09:25:09 +02:00
static_cast < float > ( XpForLevel ( 1 + currentLevel ) - currentLevel_XpBase );
2013-11-14 04:25:47 +11:00
}
2015-03-01 03:03:41 +10:30
bool cPlayer :: SetCurrentExperience ( int a_CurrentXp )
2013-11-14 00:50:47 +11:00
{
2015-05-09 09:25:09 +02:00
if ( ! ( a_CurrentXp >= 0 ) || ( a_CurrentXp > ( std :: numeric_limits < int >:: max () - m_LifetimeTotalXp )))
2013-11-14 00:50:47 +11:00
{
2013-11-16 22:17:46 +11:00
LOGWARNING ( "Tried to update experiece with an invalid Xp value: %d" , a_CurrentXp );
2014-07-17 22:15:34 +02:00
return false ; // oops, they gave us a dodgey number
2013-11-14 00:50:47 +11:00
}
2013-11-16 22:17:46 +11:00
m_CurrentXp = a_CurrentXp ;
2013-11-14 00:50:47 +11:00
2013-11-16 20:29:57 +11:00
// Set experience to be updated
m_bDirtyExperience = true ;
2013-11-15 22:42:09 +11:00
2013-11-14 04:25:47 +11:00
return true ;
2013-11-14 00:50:47 +11:00
}
2015-03-01 03:03:41 +10:30
int cPlayer :: DeltaExperience ( int a_Xp_delta )
2013-11-14 00:50:47 +11:00
{
2015-03-01 03:03:41 +10:30
if ( a_Xp_delta > ( std :: numeric_limits < int > (). max () - m_CurrentXp ))
2013-11-14 00:50:47 +11:00
{
2013-11-16 22:43:42 +11:00
// Value was bad, abort and report
2015-03-01 03:03:41 +10:30
LOGWARNING ( "Attempt was made to increment Xp by %d, which overflowed the int datatype. Ignoring." , a_Xp_delta );
2014-07-17 22:15:34 +02:00
return - 1 ; // Should we instead just return the current Xp?
2013-11-14 00:50:47 +11:00
}
2013-11-16 20:29:57 +11:00
m_CurrentXp += a_Xp_delta ;
2013-11-16 22:43:42 +11:00
// Make sure they didn't subtract too much
2015-03-01 03:45:06 +10:30
m_CurrentXp = std :: max ( m_CurrentXp , 0 );
2013-11-16 22:43:42 +11:00
2013-11-16 20:29:57 +11:00
// Update total for score calculation
2013-11-21 22:09:11 +01:00
if ( a_Xp_delta > 0 )
2013-11-16 21:38:57 +11:00
{
2013-11-16 22:00:45 +11:00
m_LifetimeTotalXp += a_Xp_delta ;
2013-11-16 21:38:57 +11:00
}
2015-05-09 09:25:09 +02:00
LOGD ( "Player \" %s \" gained / lost %d experience, total is now: %d" , GetName (). c_str (), a_Xp_delta , m_CurrentXp );
2013-11-16 21:38:57 +11:00
// Set experience to be updated
m_bDirtyExperience = true ;
return m_CurrentXp ;
}
2013-08-30 14:24:03 +02:00
void cPlayer :: StartChargingBow ( void )
{
2014-05-20 13:35:39 +01:00
LOGD ( "Player \" %s \" started charging their bow" , GetName (). c_str ());
2013-08-30 14:24:03 +02:00
m_IsChargingBow = true ;
m_BowCharge = 0 ;
2015-01-24 20:17:00 +01:00
m_World -> BroadcastEntityMetadata ( * this , m_ClientHandle . get ());
2013-08-30 14:24:03 +02:00
}
int cPlayer :: FinishChargingBow ( void )
{
2014-05-20 13:35:39 +01:00
LOGD ( "Player \" %s \" finished charging their bow at a charge of %d" , GetName (). c_str (), m_BowCharge );
2013-08-30 14:24:03 +02:00
int res = m_BowCharge ;
m_IsChargingBow = false ;
m_BowCharge = 0 ;
2015-01-24 20:17:00 +01:00
m_World -> BroadcastEntityMetadata ( * this , m_ClientHandle . get ());
2014-06-16 21:57:23 +02:00
2013-08-30 14:24:03 +02:00
return res ;
}
void cPlayer :: CancelChargingBow ( void )
{
2014-05-20 13:35:39 +01:00
LOGD ( "Player \" %s \" cancelled charging their bow at a charge of %d" , GetName (). c_str (), m_BowCharge );
2013-08-30 14:24:03 +02:00
m_IsChargingBow = false ;
m_BowCharge = 0 ;
2015-01-24 20:17:00 +01:00
m_World -> BroadcastEntityMetadata ( * this , m_ClientHandle . get ());
2013-08-30 14:24:03 +02:00
}
2013-07-29 12:13:03 +01:00
void cPlayer :: SetTouchGround ( bool a_bTouchGround )
{
2014-09-17 23:32:14 +02:00
if ( IsGameModeSpectator ()) // You can fly through the ground in Spectator
2014-09-17 17:15:47 +02:00
{
return ;
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
m_bTouchGround = a_bTouchGround ;
if ( ! m_bTouchGround )
{
if ( GetPosY () > m_LastJumpHeight )
{
2015-03-01 03:45:06 +10:30
m_LastJumpHeight = static_cast < float > ( GetPosY ());
2013-07-29 12:13:03 +01:00
}
cWorld * World = GetWorld ();
2013-11-02 14:08:00 +00:00
if (( GetPosY () >= 0 ) && ( GetPosY () < cChunkDef :: Height ))
2013-07-29 12:13:03 +01:00
{
2014-04-17 18:50:25 +01:00
BLOCKTYPE BlockType = World -> GetBlock ( POSX_TOINT , POSY_TOINT , POSZ_TOINT );
2013-07-29 12:13:03 +01:00
if ( BlockType != E_BLOCK_AIR )
{
m_bTouchGround = true ;
}
if (
( BlockType == E_BLOCK_WATER ) ||
( BlockType == E_BLOCK_STATIONARY_WATER ) ||
( BlockType == E_BLOCK_LADDER ) ||
( BlockType == E_BLOCK_VINES )
2013-11-02 16:01:40 +00:00
)
2013-07-29 12:13:03 +01:00
{
2015-03-01 03:45:06 +10:30
m_LastGroundHeight = static_cast < float > ( GetPosY ());
2013-07-29 12:13:03 +01:00
}
}
}
2013-11-02 14:08:00 +00:00
else
2013-07-29 12:13:03 +01:00
{
2015-03-01 03:45:06 +10:30
float Dist = static_cast < float > ( m_LastGroundHeight - floor ( GetPosY ()));
2014-05-20 15:52:59 +03:00
2014-07-17 22:15:34 +02:00
if ( Dist >= 2.0 ) // At least two blocks - TODO: Use m_LastJumpHeight instead of m_LastGroundHeight above
2014-05-20 15:52:59 +03:00
{
// Increment statistic
m_Stats . AddValue ( statDistFallen , ( StatValue ) floor ( Dist * 100 + 0.5 ));
}
2015-03-01 03:45:06 +10:30
int Damage = static_cast < int > ( Dist - 3.f );
2014-05-20 15:52:59 +03:00
if ( m_LastJumpHeight > m_LastGroundHeight )
{
Damage ++ ;
}
2015-03-01 03:45:06 +10:30
m_LastJumpHeight = static_cast < float > ( GetPosY ());
2013-11-02 14:08:00 +00:00
2013-12-22 20:03:09 +00:00
if ( Damage > 0 )
2013-07-29 12:13:03 +01:00
{
2014-01-25 19:05:44 +00:00
// cPlayer makes sure damage isn't applied in creative, no need to check here
2014-10-20 21:55:07 +01:00
TakeDamage ( dtFalling , nullptr , Damage , Damage , 0 );
2015-05-23 19:31:33 +01:00
2014-01-25 19:02:13 +00:00
// Fall particles
2015-03-01 03:45:06 +10:30
GetWorld () -> BroadcastSoundParticleEffect ( 2006 , POSX_TOINT , static_cast < int > ( GetPosY ()) - 1 , POSZ_TOINT , Damage /* Used as particle effect speed modifier */ );
2014-07-17 22:59:02 +02:00
}
2013-07-29 12:13:03 +01:00
2015-03-01 03:45:06 +10:30
m_LastGroundHeight = static_cast < float > ( GetPosY ());
2013-07-29 12:13:03 +01:00
}
}
void cPlayer :: Heal ( int a_Health )
{
2013-10-24 11:05:43 +02:00
super :: Heal ( a_Health );
SendHealth ();
2013-07-29 12:13:03 +01:00
}
void cPlayer :: SetFoodLevel ( int a_FoodLevel )
{
2014-08-02 22:42:53 -07:00
int FoodLevel = Clamp ( a_FoodLevel , 0 , MAX_FOOD_LEVEL );
2014-06-30 15:12:56 +02:00
2014-06-30 21:50:40 +02:00
if ( cRoot :: Get () -> GetPluginManager () -> CallHookPlayerFoodLevelChange ( * this , FoodLevel ))
2014-06-30 15:12:56 +02:00
{
m_FoodSaturationLevel = 5.0 ;
return ;
}
2015-05-23 19:31:33 +01:00
2014-06-30 21:50:40 +02:00
m_FoodLevel = FoodLevel ;
2013-07-29 12:13:03 +01:00
SendHealth ();
}
void cPlayer :: SetFoodSaturationLevel ( double a_FoodSaturationLevel )
{
2015-03-01 03:45:06 +10:30
m_FoodSaturationLevel = Clamp ( a_FoodSaturationLevel , 0.0 , static_cast < double > ( m_FoodLevel ));
2013-07-29 12:13:03 +01:00
}
void cPlayer :: SetFoodTickTimer ( int a_FoodTickTimer )
{
m_FoodTickTimer = a_FoodTickTimer ;
}
2013-08-19 22:48:13 +02:00
void cPlayer :: SetFoodExhaustionLevel ( double a_FoodExhaustionLevel )
2013-07-29 12:13:03 +01:00
{
2014-08-02 22:35:29 -07:00
m_FoodExhaustionLevel = Clamp ( a_FoodExhaustionLevel , 0.0 , 40.0 );
2013-07-29 12:13:03 +01:00
}
bool cPlayer :: Feed ( int a_Food , double a_Saturation )
{
2014-06-07 13:45:00 -07:00
if ( IsSatiated ())
2013-07-29 12:13:03 +01:00
{
return false ;
}
2014-06-30 15:12:56 +02:00
SetFoodSaturationLevel ( m_FoodSaturationLevel + a_Saturation );
SetFoodLevel ( m_FoodLevel + a_Food );
2013-07-29 12:13:03 +01:00
return true ;
}
2014-07-31 23:04:00 +02:00
void cPlayer :: AddFoodExhaustion ( double a_Exhaustion )
{
2014-09-17 17:15:47 +02:00
if ( ! ( IsGameModeCreative () || IsGameModeSpectator ()))
2014-07-31 23:04:00 +02:00
{
m_FoodExhaustionLevel = std :: min ( m_FoodExhaustionLevel + a_Exhaustion , 40.0 );
}
}
2013-07-29 12:13:03 +01:00
void cPlayer :: StartEating ( void )
{
// Set the timer:
m_EatingFinishTick = m_World -> GetWorldAge () + EATING_TICKS ;
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
// Send the packets:
2013-12-06 23:47:07 +00:00
m_World -> BroadcastEntityAnimation ( * this , 3 );
2013-07-29 12:13:03 +01:00
m_World -> BroadcastEntityMetadata ( * this );
}
void cPlayer :: FinishEating ( void )
{
// Reset the timer:
m_EatingFinishTick = - 1 ;
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
// Send the packets:
2014-04-12 13:16:48 +01:00
m_ClientHandle -> SendEntityStatus ( * this , esPlayerEatingAccepted );
2013-07-29 12:13:03 +01:00
m_World -> BroadcastEntityMetadata ( * this );
// consume the item:
cItem Item ( GetEquippedItem ());
Item . m_ItemCount = 1 ;
cItemHandler * ItemHandler = cItemHandler :: GetItemHandler ( Item . m_ItemType );
if ( ! ItemHandler -> EatItem ( this , & Item ))
{
return ;
}
ItemHandler -> OnFoodEaten ( m_World , this , & Item );
}
void cPlayer :: AbortEating ( void )
{
m_EatingFinishTick = - 1 ;
m_World -> BroadcastEntityMetadata ( * this );
}
void cPlayer :: SendHealth ( void )
{
2014-10-20 21:55:07 +01:00
if ( m_ClientHandle != nullptr )
2013-07-29 12:13:03 +01:00
{
m_ClientHandle -> SendHealth ();
}
}
2013-11-16 02:23:50 +11:00
void cPlayer :: SendExperience ( void )
{
2014-10-20 21:55:07 +01:00
if ( m_ClientHandle != nullptr )
2013-11-16 02:23:50 +11:00
{
m_ClientHandle -> SendExperience ();
2013-11-16 20:29:57 +11:00
m_bDirtyExperience = false ;
2013-11-16 02:23:50 +11:00
}
}
2013-07-29 12:13:03 +01:00
void cPlayer :: ClearInventoryPaintSlots ( void )
{
// Clear the list of slots that are being inventory-painted. Used by cWindow only
m_InventoryPaintSlots . clear ();
}
void cPlayer :: AddInventoryPaintSlot ( int a_SlotNum )
{
// Add a slot to the list for inventory painting. Used by cWindow only
m_InventoryPaintSlots . push_back ( a_SlotNum );
}
const cSlotNums & cPlayer :: GetInventoryPaintSlots ( void ) const
{
// Return the list of slots currently stored for inventory painting. Used by cWindow only
return m_InventoryPaintSlots ;
}
double cPlayer :: GetMaxSpeed ( void ) const
{
2014-03-20 16:14:40 +01:00
if ( m_IsFlying )
{
return m_FlyingMaxSpeed ;
}
2014-08-02 22:35:29 -07:00
else if ( m_IsSprinting )
{
return m_SprintingMaxSpeed ;
}
2014-03-20 16:14:40 +01:00
else
{
2014-08-02 22:35:29 -07:00
return m_NormalMaxSpeed ;
2014-03-20 16:14:40 +01:00
}
2013-07-29 12:13:03 +01:00
}
void cPlayer :: SetNormalMaxSpeed ( double a_Speed )
{
m_NormalMaxSpeed = a_Speed ;
2014-03-20 16:14:40 +01:00
if ( ! m_IsSprinting && ! m_IsFlying )
2013-07-29 12:13:03 +01:00
{
m_ClientHandle -> SendPlayerMaxSpeed ();
}
}
void cPlayer :: SetSprintingMaxSpeed ( double a_Speed )
{
m_SprintingMaxSpeed = a_Speed ;
2014-03-20 16:14:40 +01:00
if ( m_IsSprinting && ! m_IsFlying )
2013-07-29 12:13:03 +01:00
{
m_ClientHandle -> SendPlayerMaxSpeed ();
}
}
2014-03-20 16:14:40 +01:00
void cPlayer :: SetFlyingMaxSpeed ( double a_Speed )
{
m_FlyingMaxSpeed = a_Speed ;
2015-05-23 19:31:33 +01:00
2014-03-20 16:14:40 +01:00
// Update the flying speed, always:
m_ClientHandle -> SendPlayerAbilities ();
}
2013-07-29 12:13:03 +01:00
void cPlayer :: SetCrouch ( bool a_IsCrouched )
{
// Set the crouch status, broadcast to all visible players
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
if ( a_IsCrouched == m_IsCrouched )
{
// No change
return ;
}
m_IsCrouched = a_IsCrouched ;
m_World -> BroadcastEntityMetadata ( * this );
}
void cPlayer :: SetSprint ( bool a_IsSprinting )
{
if ( a_IsSprinting == m_IsSprinting )
{
// No change
return ;
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
m_IsSprinting = a_IsSprinting ;
m_ClientHandle -> SendPlayerMaxSpeed ();
}
2013-12-15 14:48:17 +01:00
void cPlayer :: SetCanFly ( bool a_CanFly )
{
if ( a_CanFly == m_CanFly )
{
return ;
}
m_CanFly = a_CanFly ;
m_ClientHandle -> SendPlayerAbilities ();
}
2014-09-02 19:12:35 +02:00
void cPlayer :: SetCustomName ( const AString & a_CustomName )
{
if ( m_CustomName == a_CustomName )
{
return ;
}
2014-09-26 17:37:19 +02:00
m_World -> BroadcastPlayerListRemovePlayer ( * this );
2014-09-02 19:12:35 +02:00
m_CustomName = a_CustomName ;
if ( m_CustomName . length () > 16 )
{
m_CustomName = m_CustomName . substr ( 0 , 16 );
}
2014-09-26 17:37:19 +02:00
m_World -> BroadcastPlayerListAddPlayer ( * this );
m_World -> BroadcastSpawnEntity ( * this , GetClientHandle ());
2014-09-02 19:12:35 +02:00
}
2013-12-15 14:48:17 +01:00
void cPlayer :: SetFlying ( bool a_IsFlying )
{
if ( a_IsFlying == m_IsFlying )
{
return ;
}
m_IsFlying = a_IsFlying ;
m_ClientHandle -> SendPlayerAbilities ();
}
2014-04-26 00:32:30 +02:00
bool cPlayer :: DoTakeDamage ( TakeDamageInfo & a_TDI )
2013-07-29 12:13:03 +01:00
{
2014-02-02 12:47:17 +00:00
if (( a_TDI . DamageType != dtInVoid ) && ( a_TDI . DamageType != dtPlugin ))
2013-07-29 12:13:03 +01:00
{
2014-09-17 17:15:47 +02:00
if ( IsGameModeCreative () || IsGameModeSpectator ())
2013-09-10 23:02:35 +01:00
{
2014-09-17 17:15:47 +02:00
// No damage / health in creative or spectator mode if not void or plugin damage
2014-04-26 00:32:30 +02:00
return false ;
2013-09-10 23:02:35 +01:00
}
2013-07-29 12:13:03 +01:00
}
2014-01-19 14:20:57 +02:00
2014-10-20 21:55:07 +01:00
if (( a_TDI . Attacker != nullptr ) && ( a_TDI . Attacker -> IsPlayer ()))
2014-01-19 14:20:57 +02:00
{
2014-05-13 14:53:15 +03:00
cPlayer * Attacker = ( cPlayer * ) a_TDI . Attacker ;
2014-01-19 14:20:57 +02:00
2014-10-20 21:55:07 +01:00
if (( m_Team != nullptr ) && ( m_Team == Attacker -> m_Team ))
2014-01-19 14:20:57 +02:00
{
2014-01-19 16:02:37 +02:00
if ( ! m_Team -> AllowsFriendlyFire ())
2014-01-19 14:20:57 +02:00
{
// Friendly fire is disabled
2014-04-26 00:32:30 +02:00
return false ;
2014-01-19 14:20:57 +02:00
}
}
}
2015-05-23 19:31:33 +01:00
2014-04-26 00:32:30 +02:00
if ( super :: DoTakeDamage ( a_TDI ))
{
// Any kind of damage adds food exhaustion
AddFoodExhaustion ( 0.3f );
SendHealth ();
2014-05-12 21:38:52 +03:00
2014-05-18 22:49:27 +02:00
m_Stats . AddValue ( statDamageTaken , ( StatValue ) floor ( a_TDI . FinalDamage * 10 + 0.5 ));
2014-04-26 00:32:30 +02:00
return true ;
}
return false ;
2013-07-29 12:13:03 +01:00
}
2014-07-04 10:55:09 +01:00
void cPlayer :: KilledBy ( TakeDamageInfo & a_TDI )
2013-07-29 12:13:03 +01:00
{
2014-07-04 10:55:09 +01:00
super :: KilledBy ( a_TDI );
2013-07-29 12:13:03 +01:00
if ( m_Health > 0 )
{
2014-07-17 22:15:34 +02:00
return ; // not dead yet =]
2013-07-29 12:13:03 +01:00
}
2014-07-17 22:15:34 +02:00
m_bVisible = false ; // So new clients don't see the player
2013-07-29 12:13:03 +01:00
// Puke out all the items
cItems Pickups ;
m_Inventory . CopyToItems ( Pickups );
m_Inventory . Clear ();
2014-02-15 19:51:05 +01:00
if ( GetName () == "Notch" )
{
Pickups . Add ( cItem ( E_ITEM_RED_APPLE ));
}
2014-08-18 22:48:15 +02:00
m_Stats . AddValue ( statItemsDropped , ( StatValue ) Pickups . Size ());
2014-05-12 21:38:52 +03:00
2013-07-29 12:13:03 +01:00
m_World -> SpawnItemPickups ( Pickups , GetPosX (), GetPosY (), GetPosZ (), 10 );
SaveToDisk (); // Save it, yeah the world is a tough place !
2015-06-02 17:06:18 +02:00
cPluginManager * PluginManager = cRoot :: Get () -> GetPluginManager ();
2013-12-26 14:55:19 +00:00
2014-10-20 21:55:07 +01:00
if (( a_TDI . Attacker == nullptr ) && m_World -> ShouldBroadcastDeathMessages ())
2013-12-26 14:55:19 +00:00
{
2014-07-04 10:55:09 +01:00
AString DamageText ;
switch ( a_TDI . DamageType )
{
case dtRangedAttack : DamageText = "was shot" ; break ;
case dtLightning : DamageText = "was plasmified by lightining" ; break ;
case dtFalling : DamageText = ( GetWorld () -> GetTickRandomNumber ( 10 ) % 2 == 0 ) ? "fell to death" : "hit the ground too hard" ; break ;
case dtDrowning : DamageText = "drowned" ; break ;
case dtSuffocating : DamageText = ( GetWorld () -> GetTickRandomNumber ( 10 ) % 2 == 0 ) ? "git merge'd into a block" : "fused with a block" ; break ;
case dtStarving : DamageText = "forgot the importance of food" ; break ;
case dtCactusContact : DamageText = "was impaled on a cactus" ; break ;
case dtLavaContact : DamageText = "was melted by lava" ; break ;
case dtPoisoning : DamageText = "died from septicaemia" ; break ;
2014-07-17 14:08:54 -07:00
case dtWithering : DamageText = "is a husk of their former selves" ; break ;
2014-07-04 10:55:09 +01:00
case dtOnFire : DamageText = "forgot to stop, drop, and roll" ; break ;
case dtFireContact : DamageText = "burnt themselves to death" ; break ;
case dtInVoid : DamageText = "somehow fell out of the world" ; break ;
case dtPotionOfHarming : DamageText = "was magicked to death" ; break ;
case dtEnderPearl : DamageText = "misused an ender pearl" ; break ;
case dtAdmin : DamageText = "was administrator'd" ; break ;
case dtExplosion : DamageText = "blew up" ; break ;
default : DamageText = "died, somehow; we've no idea how though" ; break ;
}
2015-06-02 17:06:18 +02:00
AString DeathMessage = Printf ( "%s %s" , GetName (). c_str (), DamageText . c_str ());
PluginManager -> CallHookKilled ( * this , a_TDI , DeathMessage );
if ( DeathMessage != AString ( "" ))
{
GetWorld () -> BroadcastChatDeath ( DeathMessage );
}
2013-12-26 14:55:19 +00:00
}
2014-10-20 21:55:07 +01:00
else if ( a_TDI . Attacker == nullptr ) // && !m_World->ShouldBroadcastDeathMessages() by fallthrough
2014-08-10 20:47:16 +01:00
{
// no-op
}
2014-07-04 10:55:09 +01:00
else if ( a_TDI . Attacker -> IsPlayer ())
2013-12-26 14:55:19 +00:00
{
2014-07-04 10:55:09 +01:00
cPlayer * Killer = ( cPlayer * ) a_TDI . Attacker ;
2015-06-02 17:06:18 +02:00
AString DeathMessage = Printf ( "%s was killed by %s" , GetName (). c_str (), Killer -> GetName (). c_str ());
PluginManager -> CallHookKilled ( * this , a_TDI , DeathMessage );
if ( DeathMessage != AString ( "" ))
{
GetWorld () -> BroadcastChatDeath ( DeathMessage );
}
2013-12-26 14:55:19 +00:00
}
else
{
2014-07-04 10:55:09 +01:00
AString KillerClass = a_TDI . Attacker -> GetClass ();
2014-07-17 22:15:34 +02:00
KillerClass . erase ( KillerClass . begin ()); // Erase the 'c' of the class (e.g. "cWitch" -> "Witch")
2015-06-02 17:06:18 +02:00
AString DeathMessage = Printf ( "%s was killed by a %s" , GetName (). c_str (), KillerClass . c_str ());
PluginManager -> CallHookKilled ( * this , a_TDI , DeathMessage );
if ( DeathMessage != AString ( "" ))
{
GetWorld () -> BroadcastChatDeath ( DeathMessage );
}
2013-12-26 14:55:19 +00:00
}
2014-01-19 14:20:57 +02:00
2014-05-11 14:57:06 +03:00
m_Stats . AddValue ( statDeaths );
2014-03-01 14:20:29 +02:00
m_World -> GetScoreBoard (). AddPlayerScore ( GetName (), cObjective :: otDeathCount , 1 );
2013-07-29 12:13:03 +01:00
}
2014-05-12 17:05:09 +03:00
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 );
}
2013-07-29 12:13:03 +01:00
void cPlayer :: Respawn ( void )
{
2014-10-20 21:55:07 +01:00
ASSERT ( m_World != nullptr );
2014-06-08 21:58:08 +02:00
2013-07-29 12:13:03 +01:00
m_Health = GetMaxHealth ();
2014-04-26 00:32:30 +02:00
SetInvulnerableTicks ( 20 );
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
// Reset food level:
2013-08-01 09:51:25 +02:00
m_FoodLevel = MAX_FOOD_LEVEL ;
2014-06-30 15:12:56 +02:00
m_FoodSaturationLevel = 5.0 ;
2014-07-02 18:48:05 +01:00
m_FoodExhaustionLevel = 0.0 ;
2013-07-29 12:13:03 +01:00
2013-11-16 21:38:57 +11:00
// Reset Experience
2013-11-21 22:09:11 +01:00
m_CurrentXp = 0 ;
m_LifetimeTotalXp = 0 ;
2013-11-16 21:38:57 +11:00
// ToDo: send score to client? How?
2014-07-20 10:46:45 +01:00
m_ClientHandle -> SendRespawn ( GetWorld () -> GetDimension (), true );
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
// Extinguish the fire:
StopBurning ();
2014-06-01 18:46:59 +01:00
TeleportToCoords ( GetLastBedPos (). x , GetLastBedPos (). y , GetLastBedPos (). z );
2013-07-29 12:13:03 +01:00
SetVisible ( true );
}
double cPlayer :: GetEyeHeight ( void ) const
{
return m_Stance ;
}
Vector3d cPlayer :: GetEyePosition ( void ) const
{
2014-07-21 15:19:48 +02:00
return Vector3d ( GetPosX (), m_Stance , GetPosZ ());
2013-07-29 12:13:03 +01:00
}
bool cPlayer :: IsGameModeCreative ( void ) const
{
return ( m_GameMode == gmCreative ) || // Either the player is explicitly in Creative
(( m_GameMode == gmNotSet ) && m_World -> IsGameModeCreative ()); // or they inherit from the world and the world is Creative
}
bool cPlayer :: IsGameModeSurvival ( void ) const
{
return ( m_GameMode == gmSurvival ) || // Either the player is explicitly in Survival
(( m_GameMode == gmNotSet ) && m_World -> IsGameModeSurvival ()); // or they inherit from the world and the world is Survival
}
bool cPlayer :: IsGameModeAdventure ( void ) const
{
2013-12-30 21:30:20 -07:00
return ( m_GameMode == gmAdventure ) || // Either the player is explicitly in Adventure
(( m_GameMode == gmNotSet ) && m_World -> IsGameModeAdventure ()); // or they inherit from the world and the world is Adventure
2013-07-29 12:13:03 +01:00
}
2014-09-16 20:04:17 +02:00
bool cPlayer :: IsGameModeSpectator ( void ) const
{
return ( m_GameMode == gmSpectator ) || // Either the player is explicitly in Spectator
2014-12-02 20:25:41 -05:00
(( m_GameMode == gmNotSet ) && m_World -> IsGameModeSpectator ()); // or they inherit from the world and the world is Spectator
2014-09-16 20:04:17 +02:00
}
2013-07-29 12:13:03 +01:00
2014-01-20 16:10:39 +02:00
void cPlayer :: SetTeam ( cTeam * a_Team )
2014-01-19 14:20:57 +02:00
{
2014-01-20 16:10:39 +02:00
if ( m_Team == a_Team )
{
return ;
}
2014-01-19 14:20:57 +02:00
if ( m_Team )
{
2014-01-19 16:02:37 +02:00
m_Team -> RemovePlayer ( GetName ());
2014-01-19 14:20:57 +02:00
}
m_Team = a_Team ;
if ( m_Team )
{
2014-01-19 16:02:37 +02:00
m_Team -> AddPlayer ( GetName ());
2014-01-19 14:20:57 +02:00
}
}
2014-01-20 16:10:39 +02:00
cTeam * cPlayer :: UpdateTeam ( void )
{
2014-10-20 21:55:07 +01:00
if ( m_World == nullptr )
2014-01-21 15:58:17 +02:00
{
2014-10-20 21:55:07 +01:00
SetTeam ( nullptr );
2014-01-21 15:58:17 +02:00
}
else
{
cScoreboard & Scoreboard = m_World -> GetScoreBoard ();
2014-01-20 16:10:39 +02:00
2014-01-21 15:58:17 +02:00
SetTeam ( Scoreboard . QueryPlayerTeam ( GetName ()));
}
2014-01-20 16:10:39 +02:00
return m_Team ;
}
2013-07-29 12:13:03 +01:00
void cPlayer :: OpenWindow ( cWindow * a_Window )
{
if ( a_Window != m_CurrentWindow )
{
CloseWindow ( false );
}
a_Window -> OpenedByPlayer ( * this );
m_CurrentWindow = a_Window ;
a_Window -> SendWholeWindow ( * GetClientHandle ());
}
void cPlayer :: CloseWindow ( bool a_CanRefuse )
{
2014-10-20 21:55:07 +01:00
if ( m_CurrentWindow == nullptr )
2013-07-29 12:13:03 +01:00
{
m_CurrentWindow = m_InventoryWindow ;
return ;
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
if ( m_CurrentWindow -> ClosedByPlayer ( * this , a_CanRefuse ) || ! a_CanRefuse )
{
// Close accepted, go back to inventory window (the default):
m_CurrentWindow = m_InventoryWindow ;
}
else
{
// Re-open the window
m_CurrentWindow -> OpenedByPlayer ( * this );
m_CurrentWindow -> SendWholeWindow ( * GetClientHandle ());
}
}
void cPlayer :: CloseWindowIfID ( char a_WindowID , bool a_CanRefuse )
{
2014-10-20 21:55:07 +01:00
if (( m_CurrentWindow == nullptr ) || ( m_CurrentWindow -> GetWindowID () != a_WindowID ))
2013-07-29 12:13:03 +01:00
{
return ;
}
CloseWindow ();
}
void cPlayer :: SetGameMode ( eGameMode a_GameMode )
{
2013-07-30 14:48:18 +02:00
if (( a_GameMode < gmMin ) || ( a_GameMode >= gmMax ))
2013-07-29 12:13:03 +01:00
{
LOGWARNING ( "%s: Setting invalid gamemode: %d" , GetName (). c_str (), a_GameMode );
return ;
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
if ( m_GameMode == a_GameMode )
{
// Gamemode already set
return ;
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
m_GameMode = a_GameMode ;
m_ClientHandle -> SendGameMode ( a_GameMode );
2013-12-22 20:03:09 +00:00
2015-05-23 19:31:33 +01:00
SetCapabilities ();
2015-05-22 12:25:16 -07:00
2014-09-18 18:50:17 +02:00
m_World -> BroadcastPlayerListUpdateGameMode ( * this );
2013-07-29 12:13:03 +01:00
}
2014-07-21 15:19:48 +02:00
void cPlayer :: LoginSetGameMode ( eGameMode a_GameMode )
2013-07-29 12:13:03 +01:00
{
m_GameMode = a_GameMode ;
2015-05-23 19:31:33 +01:00
SetCapabilities ();
}
void cPlayer :: SetCapabilities ()
{
if ( ! IsGameModeCreative () || IsGameModeSpectator ())
{
SetFlying ( false );
SetCanFly ( false );
}
if ( IsGameModeSpectator ())
{
SetVisible ( false );
}
else
{
SetVisible ( true );
}
2013-07-29 12:13:03 +01:00
}
void cPlayer :: SetIP ( const AString & a_IP )
{
m_IP = a_IP ;
}
2014-05-12 17:05:09 +03:00
unsigned int cPlayer :: AwardAchievement ( const eStatistic a_Ach )
{
eStatistic Prerequisite = cStatInfo :: GetPrerequisite ( a_Ach );
2014-05-13 14:53:15 +03:00
// Check if the prerequisites are met
2014-05-12 17:05:09 +03:00
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
{
2014-07-27 00:39:39 +02:00
if ( m_World -> ShouldBroadcastAchievementMessages ())
{
cCompositeChat Msg ;
Msg . SetMessageType ( mtSuccess );
Msg . AddShowAchievementPart ( GetName (), cStatInfo :: GetName ( a_Ach ));
m_World -> BroadcastChat ( Msg );
}
2014-05-12 17:05:09 +03:00
2014-05-13 14:53:15 +03:00
// Increment the statistic
2014-05-12 17:05:09 +03:00
StatValue New = m_Stats . AddValue ( a_Ach );
2014-05-13 14:53:15 +03:00
// Achievement Get!
2014-05-12 17:05:09 +03:00
m_ClientHandle -> SendStatistics ( m_Stats );
return New ;
}
}
2013-07-29 12:13:03 +01:00
void cPlayer :: TeleportToCoords ( double a_PosX , double a_PosY , double a_PosZ )
{
2015-03-05 11:52:42 +01:00
// ask plugins to allow teleport to the new position.
if ( ! cRoot :: Get () -> GetPluginManager () -> CallHookEntityTeleport ( * this , m_LastPos , Vector3d ( a_PosX , a_PosY , a_PosZ )))
{
SetPosition ( a_PosX , a_PosY , a_PosZ );
m_LastGroundHeight = static_cast < float > ( a_PosY );
m_LastJumpHeight = static_cast < float > ( a_PosY );
m_bIsTeleporting = true ;
2013-07-29 12:13:03 +01:00
2015-03-05 11:52:42 +01:00
m_World -> BroadcastTeleportEntity ( * this , GetClientHandle ());
m_ClientHandle -> SendPlayerMoveLook ();
}
2013-07-29 12:13:03 +01:00
}
2014-04-04 23:06:47 +02:00
void cPlayer :: SendRotation ( double a_YawDegrees , double a_PitchDegrees )
{
SetYaw ( a_YawDegrees );
SetPitch ( a_PitchDegrees );
m_ClientHandle -> SendPlayerMoveLook ();
}
2013-08-30 17:29:46 +02:00
Vector3d cPlayer :: GetThrowStartPos ( void ) const
{
Vector3d res = GetEyePosition ();
2015-05-23 19:31:33 +01:00
2013-08-30 17:29:46 +02:00
// Adjust the position to be just outside the player's bounding box:
res . x += 0.16 * cos ( GetPitch ());
res . y += - 0.1 ;
res . z += 0.16 * sin ( GetPitch ());
2015-05-23 19:31:33 +01:00
2013-08-30 17:29:46 +02:00
return res ;
}
Vector3d cPlayer :: GetThrowSpeed ( double a_SpeedCoeff ) const
{
Vector3d res = GetLookVector ();
res . Normalize ();
2015-05-23 19:31:33 +01:00
2013-08-30 17:29:46 +02:00
// TODO: Add a slight random change (+-0.0075 in each direction)
2015-05-23 19:31:33 +01:00
2013-08-30 17:29:46 +02:00
return res * a_SpeedCoeff ;
2014-07-17 22:50:58 +02:00
}
2013-08-30 17:29:46 +02:00
2014-04-19 20:51:52 +02:00
void cPlayer :: ForceSetSpeed ( const Vector3d & a_Speed )
2013-12-15 18:54:54 +01:00
{
2014-04-19 20:51:52 +02:00
SetSpeed ( a_Speed );
2014-06-16 15:12:50 +01:00
}
void cPlayer :: DoSetSpeed ( double a_SpeedX , double a_SpeedY , double a_SpeedZ )
{
super :: DoSetSpeed ( a_SpeedX , a_SpeedY , a_SpeedZ );
// Send the speed to the client so he actualy moves
2013-12-15 18:54:54 +01:00
m_ClientHandle -> SendEntityVelocity ( * this );
}
2014-07-21 15:19:48 +02:00
void cPlayer :: MoveTo ( const Vector3d & a_NewPos )
2013-07-29 12:13:03 +01:00
{
if (( a_NewPos . y < - 990 ) && ( GetPosY () > - 100 ))
{
// When attached to an entity, the client sends position packets with weird coords:
// Y = -999 and X, Z = attempting to create speed, usually up to 0.03
2014-07-17 22:50:58 +02:00
// We cannot test m_AttachedTo, because when deattaching, the server thinks the client is already deattached while
2013-07-29 12:13:03 +01:00
// the client may still send more of these nonsensical packets.
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo != nullptr )
2013-07-29 12:13:03 +01:00
{
Vector3d AddSpeed ( a_NewPos );
AddSpeed . y = 0 ;
m_AttachedTo -> AddSpeed ( AddSpeed );
}
return ;
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
// 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
2014-05-12 21:38:52 +03:00
Vector3d DeltaPos = a_NewPos - GetPosition ();
UpdateMovementStats ( DeltaPos );
2015-05-23 19:31:33 +01:00
2014-07-21 15:19:48 +02:00
SetPosition ( a_NewPos );
2013-07-29 12:13:03 +01:00
SetStance ( a_NewPos . y + 1.62 );
}
void cPlayer :: SetVisible ( bool a_bVisible )
{
2014-09-17 17:15:47 +02:00
// Need to Check if the player or other players are in gamemode spectator, but will break compatibility
2014-07-17 22:15:34 +02:00
if ( a_bVisible && ! m_bVisible ) // Make visible
2013-07-29 12:13:03 +01:00
{
m_bVisible = true ;
m_World -> BroadcastSpawnEntity ( * this );
}
if ( ! a_bVisible && m_bVisible )
{
m_bVisible = false ;
2015-01-24 20:17:00 +01:00
m_World -> BroadcastDestroyEntity ( * this , m_ClientHandle . get ()); // Destroy on all clients
2013-07-29 12:13:03 +01:00
}
}
bool cPlayer :: HasPermission ( const AString & a_Permission )
{
if ( a_Permission . empty ())
{
// Empty permission request is always granted
return true ;
}
2015-05-23 19:31:33 +01:00
2014-08-19 17:34:11 +02:00
AStringVector Split = StringSplit ( a_Permission , "." );
2015-04-25 19:40:44 +02:00
// Iterate over all restrictions; if any matches, then return failure:
for ( auto & Restriction : m_SplitRestrictions )
{
if ( PermissionMatches ( Split , Restriction ))
{
return false ;
}
} // for Restriction - m_SplitRestrictions[]
2014-08-19 17:34:11 +02:00
// Iterate over all granted permissions; if any matches, then return success:
2015-04-25 19:40:44 +02:00
for ( auto & Permission : m_SplitPermissions )
2013-07-29 12:13:03 +01:00
{
2015-04-25 19:40:44 +02:00
if ( PermissionMatches ( Split , Permission ))
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
return true ;
2013-07-29 12:13:03 +01:00
}
2015-04-25 19:40:44 +02:00
} // for Permission - m_SplitPermissions[]
2013-07-29 12:13:03 +01:00
2014-08-19 17:34:11 +02:00
// No granted permission matches
2013-07-29 12:13:03 +01:00
return false ;
}
2014-08-19 17:34:11 +02:00
bool cPlayer :: PermissionMatches ( const AStringVector & a_Permission , const AStringVector & a_Template )
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
// Check the sub-items if they are the same or there's a wildcard:
size_t lenP = a_Permission . size ();
size_t lenT = a_Template . size ();
size_t minLen = std :: min ( lenP , lenT );
for ( size_t i = 0 ; i < minLen ; i ++ )
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
if ( a_Template [ i ] == "*" )
{
// Has matched so far and now there's a wildcard in the template, so the permission matches:
2013-07-29 12:13:03 +01:00
return true ;
2014-08-19 17:34:11 +02:00
}
if ( a_Permission [ i ] != a_Template [ i ])
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
// Found a mismatch
return false ;
2013-07-29 12:13:03 +01:00
}
}
2014-08-19 17:34:11 +02:00
// So far all the sub-items have matched
// If the sub-item count is the same, then the permission matches:
if ( lenP == lenT )
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
return true ;
2013-07-29 12:13:03 +01:00
}
2014-08-19 17:34:11 +02:00
// There are more sub-items in either the permission or the template, not a match:
return false ;
2013-07-29 12:13:03 +01:00
}
AString cPlayer :: GetColor ( void ) const
{
2014-08-19 17:34:11 +02:00
if ( m_MsgNameColorCode . empty () || ( m_MsgNameColorCode == "-" ))
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
// Color has not been assigned, return an empty string:
return AString ();
2013-07-29 12:13:03 +01:00
}
2014-08-19 17:34:11 +02:00
// Return the color, including the delimiter:
return cChatColor :: Delimiter + m_MsgNameColorCode ;
2013-07-29 12:13:03 +01:00
}
2014-09-23 14:39:49 +02:00
AString cPlayer :: GetPlayerListName ( void ) const
2014-09-02 19:12:35 +02:00
{
const AString & Color = GetColor ();
if ( HasCustomName ())
{
return m_CustomName ;
}
else if (( GetName (). length () <= 14 ) && ! Color . empty ())
{
return Printf ( "%s%s" , Color . c_str (), GetName (). c_str ());
}
else
{
return GetName ();
}
}
2014-01-23 00:27:39 -07:00
void cPlayer :: TossEquippedItem ( char a_Amount )
2013-07-29 12:13:03 +01:00
{
cItems Drops ;
2014-01-23 20:11:10 -07:00
cItem DroppedItem ( GetInventory (). GetEquippedItem ());
if ( ! DroppedItem . IsEmpty ())
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
char NewAmount = a_Amount ;
if ( NewAmount > GetInventory (). GetEquippedItem (). m_ItemCount )
{
2014-07-17 22:15:34 +02:00
NewAmount = GetInventory (). GetEquippedItem (). m_ItemCount ; // Drop only what's there
2014-01-23 20:11:10 -07:00
}
2014-01-15 22:36:19 +00:00
2014-01-23 20:11:10 -07:00
GetInventory (). GetHotbarGrid (). ChangeSlotCount ( GetInventory (). GetEquippedSlotNum () /* Returns hotbar subslot, which HotbarGrid takes */ , - a_Amount );
2014-01-23 00:27:39 -07:00
2014-01-23 20:11:10 -07:00
DroppedItem . m_ItemCount = NewAmount ;
Drops . push_back ( DroppedItem );
2013-07-29 12:13:03 +01:00
}
2014-01-23 00:27:39 -07:00
2014-05-12 21:38:52 +03:00
TossItems ( Drops );
2014-01-23 00:27:39 -07:00
}
void cPlayer :: TossHeldItem ( char a_Amount )
{
cItems Drops ;
2014-01-23 20:11:10 -07:00
cItem & Item = GetDraggingItem ();
if ( ! Item . IsEmpty ())
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
char OriginalItemAmount = Item . m_ItemCount ;
Item . m_ItemCount = std :: min ( OriginalItemAmount , a_Amount );
Drops . push_back ( Item );
2014-05-12 21:38:52 +03:00
2014-01-23 20:11:10 -07:00
if ( OriginalItemAmount > a_Amount )
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
Item . m_ItemCount = OriginalItemAmount - a_Amount ;
2013-07-29 12:13:03 +01:00
}
else
{
2014-01-23 20:11:10 -07:00
Item . Empty ();
2013-07-29 12:13:03 +01:00
}
}
2014-01-23 20:11:10 -07:00
2014-05-12 21:38:52 +03:00
TossItems ( Drops );
2014-01-23 00:27:39 -07:00
}
void cPlayer :: TossPickup ( const cItem & a_Item )
{
cItems Drops ;
2014-01-23 20:11:10 -07:00
Drops . push_back ( a_Item );
2014-01-15 22:36:19 +00:00
2014-05-12 21:38:52 +03:00
TossItems ( Drops );
}
void cPlayer :: TossItems ( const cItems & a_Items )
{
2014-09-17 23:32:14 +02:00
if ( IsGameModeSpectator ()) // Players can't toss items in spectator
2014-09-17 17:15:47 +02:00
{
return ;
}
2015-05-23 19:31:33 +01:00
2014-08-18 22:48:15 +02:00
m_Stats . AddValue ( statItemsDropped , ( StatValue ) a_Items . Size ());
2014-05-12 21:38:52 +03:00
2013-07-29 12:13:03 +01:00
double vX = 0 , vY = 0 , vZ = 0 ;
2014-01-17 11:11:17 +01:00
EulerToVector ( - GetYaw (), GetPitch (), vZ , vX , vY );
2013-07-29 12:13:03 +01:00
vY = - vY * 2 + 1.f ;
2014-07-17 22:15:34 +02:00
m_World -> SpawnItemPickups ( a_Items , GetPosX (), GetEyeHeight (), GetPosZ (), vX * 3 , vY * 3 , vZ * 3 , true ); // 'true' because created by player
2013-07-29 12:13:03 +01:00
}
2015-05-26 20:35:28 -05:00
bool cPlayer :: DoMoveToWorld ( cWorld * a_World , bool a_ShouldSendRespawn , Vector3d a_NewPosition )
2013-07-29 12:13:03 +01:00
{
2014-10-20 21:55:07 +01:00
ASSERT ( a_World != nullptr );
2014-07-22 17:26:48 +01:00
2014-07-21 22:49:06 +01:00
if ( GetWorld () == a_World )
2013-07-29 12:13:03 +01:00
{
2014-06-12 15:21:07 +01:00
// Don't move to same world
2013-07-29 12:13:03 +01:00
return false ;
}
2015-05-23 19:31:33 +01:00
2015-05-21 12:27:54 +02:00
// Ask the plugins if the player is allowed to changing the world
if ( cRoot :: Get () -> GetPluginManager () -> CallHookEntityChangingWorld ( * this , * a_World ))
2015-05-18 22:29:39 +02:00
{
2015-05-21 12:27:54 +02:00
// A Plugin doesn't allow the player to changing the world
2015-05-18 22:29:39 +02:00
return false ;
}
2014-06-08 21:58:08 +02:00
// Send the respawn packet:
2014-10-20 21:55:07 +01:00
if ( a_ShouldSendRespawn && ( m_ClientHandle != nullptr ))
2014-06-08 21:58:08 +02:00
{
2014-07-21 22:49:06 +01:00
m_ClientHandle -> SendRespawn ( a_World -> GetDimension ());
2014-06-08 21:58:08 +02:00
}
2015-04-03 18:40:20 +02:00
// Broadcast for other people that the player is gone.
GetWorld () -> BroadcastDestroyEntity ( * this );
2014-06-21 20:42:10 +01:00
// Remove player from the old world
2014-07-22 17:26:48 +01:00
SetWorldTravellingFrom ( GetWorld ()); // cChunk handles entity removal
2014-07-29 20:50:30 +01:00
GetWorld () -> RemovePlayer ( this , false );
2013-07-29 12:13:03 +01:00
2015-05-26 20:35:28 -05:00
SetPosition ( a_NewPosition );
2014-06-08 21:58:08 +02:00
// Queue adding player to the new world, including all the necessary adjustments to the object
2014-07-21 22:49:06 +01:00
a_World -> AddPlayer ( this );
2015-05-18 22:29:39 +02:00
cWorld * OldWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetName ()); // Required for the hook HOOK_ENTITY_CHANGED_WORLD
2014-07-21 22:49:06 +01:00
SetWorld ( a_World ); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
2013-07-29 12:13:03 +01:00
2014-11-14 22:53:12 +01:00
// Update the view distance.
2014-11-15 14:27:50 +01:00
m_ClientHandle -> SetViewDistance ( m_ClientHandle -> GetRequestedViewDistance ());
2014-11-14 22:53:12 +01:00
2014-12-15 17:45:42 +01:00
// Send current weather of target world to player
if ( a_World -> GetDimension () == dimOverworld )
2014-12-15 18:41:47 +01:00
{
2014-12-15 17:45:42 +01:00
m_ClientHandle -> SendWeather ( a_World -> GetWeather ());
2014-12-15 18:41:47 +01:00
}
2015-04-03 18:40:20 +02:00
// Broadcast the player into the new world.
a_World -> BroadcastSpawnEntity ( * this );
2015-05-23 19:31:33 +01:00
2015-05-18 22:29:39 +02:00
// Player changed the world, call the hook
cRoot :: Get () -> GetPluginManager () -> CallHookEntityChangedWorld ( * this , * OldWorld );
2013-07-29 12:13:03 +01:00
return true ;
}
2014-06-15 20:27:27 -07:00
bool cPlayer :: LoadFromDisk ( cWorldPtr & a_World )
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
LoadRank ();
2013-07-29 12:13:03 +01:00
2014-07-11 13:13:10 +02:00
// Load from the UUID file:
2014-07-18 20:12:27 +01:00
if ( LoadFromFile ( GetUUIDFileName ( m_UUID ), a_World ))
2014-07-11 00:06:05 +02:00
{
return true ;
}
2015-05-23 19:31:33 +01:00
2014-07-11 13:13:10 +02:00
// Load from the offline UUID file, if allowed:
AString OfflineUUID = cClientHandle :: GenerateOfflineUUID ( GetName ());
2014-07-31 22:52:06 +02:00
const char * OfflineUsage = " (unused)" ;
2014-07-11 13:13:10 +02:00
if ( cRoot :: Get () -> GetServer () -> ShouldLoadOfflinePlayerData ())
{
2014-07-31 22:52:06 +02:00
OfflineUsage = "" ;
2014-07-18 20:12:27 +01:00
if ( LoadFromFile ( GetUUIDFileName ( OfflineUUID ), a_World ))
2014-07-11 13:13:10 +02:00
{
return true ;
}
}
2015-05-23 19:31:33 +01:00
2014-07-11 13:13:10 +02:00
// Load from the old-style name-based file, if allowed:
if ( cRoot :: Get () -> GetServer () -> ShouldLoadNamedPlayerData ())
{
AString OldStyleFileName = Printf ( "players/%s.json" , GetName (). c_str ());
2014-07-18 20:12:27 +01:00
if ( LoadFromFile ( OldStyleFileName , a_World ))
2014-07-11 13:13:10 +02:00
{
// Save in new format and remove the old file
2014-07-11 23:12:57 +02:00
if ( SaveToDisk ())
{
cFile :: Delete ( OldStyleFileName );
}
2014-07-11 13:13:10 +02:00
return true ;
}
}
2015-05-23 19:31:33 +01:00
2014-07-11 13:13:10 +02:00
// None of the files loaded successfully
2014-07-31 22:52:06 +02:00
LOG ( "Player data file not found for %s (%s, offline %s%s), will be reset to defaults." ,
GetName (). c_str (), m_UUID . c_str (), OfflineUUID . c_str (), OfflineUsage
2014-07-11 13:13:10 +02:00
);
2014-07-20 10:46:45 +01:00
2014-10-20 21:55:07 +01:00
if ( a_World == nullptr )
2014-07-20 10:46:45 +01:00
{
a_World = cRoot :: Get () -> GetDefaultWorld ();
}
2014-07-11 00:06:58 +02:00
return false ;
2014-07-11 00:06:05 +02:00
}
2013-07-29 12:13:03 +01:00
2014-07-11 00:06:05 +02:00
2013-07-29 12:13:03 +01:00
2014-07-11 00:06:05 +02:00
2013-07-29 12:13:03 +01:00
2014-07-20 10:46:45 +01:00
bool cPlayer :: LoadFromFile ( const AString & a_FileName , cWorldPtr & a_World )
2014-07-11 00:06:05 +02:00
{
// Load the data from the file:
2013-07-29 12:13:03 +01:00
cFile f ;
2014-07-11 00:06:05 +02:00
if ( ! f . Open ( a_FileName , cFile :: fmRead ))
2013-07-29 12:13:03 +01:00
{
2013-11-14 07:12:16 +11:00
// This is a new player whom we haven't seen yet, bail out, let them have the defaults
2013-07-29 12:13:03 +01:00
return false ;
}
AString buffer ;
if ( f . ReadRestOfFile ( buffer ) != f . GetSize ())
{
2014-07-11 00:06:05 +02:00
LOGWARNING ( "Cannot read player data from file \" %s \" " , a_FileName . c_str ());
2013-07-29 12:13:03 +01:00
return false ;
}
2014-07-11 00:06:05 +02:00
f . Close ();
2013-07-29 12:13:03 +01:00
2014-07-11 00:06:05 +02:00
// Parse the JSON format:
2013-07-29 12:13:03 +01:00
Json :: Value root ;
Json :: Reader reader ;
if ( ! reader . parse ( buffer , root , false ))
{
2014-07-11 00:06:05 +02:00
LOGWARNING ( "Cannot parse player data in file \" %s \" " , a_FileName . c_str ());
return false ;
2013-07-29 12:13:03 +01:00
}
2014-07-11 00:06:05 +02:00
// Load the player data:
2013-07-29 12:13:03 +01:00
Json :: Value & JSON_PlayerPosition = root [ "position" ];
if ( JSON_PlayerPosition . size () == 3 )
{
2014-07-11 00:06:05 +02:00
SetPosX ( JSON_PlayerPosition [( unsigned ) 0 ]. asDouble ());
SetPosY ( JSON_PlayerPosition [( unsigned ) 1 ]. asDouble ());
SetPosZ ( JSON_PlayerPosition [( unsigned ) 2 ]. asDouble ());
2014-04-23 21:06:46 +01:00
m_LastPos = GetPosition ();
2013-07-29 12:13:03 +01:00
}
Json :: Value & JSON_PlayerRotation = root [ "rotation" ];
if ( JSON_PlayerRotation . size () == 3 )
{
2015-03-01 03:45:06 +10:30
SetYaw ( static_cast < float > ( JSON_PlayerRotation [( unsigned ) 0 ]. asDouble ()));
SetPitch ( static_cast < float > ( JSON_PlayerRotation [( unsigned ) 1 ]. asDouble ()));
SetRoll ( static_cast < float > ( JSON_PlayerRotation [( unsigned ) 2 ]. asDouble ()));
2013-07-29 12:13:03 +01:00
}
2014-07-11 00:06:05 +02:00
m_Health = root . get ( "health" , 0 ). asInt ();
2013-08-08 10:32:34 +01:00
m_AirLevel = root . get ( "air" , MAX_AIR_LEVEL ). asInt ();
2013-07-29 12:13:03 +01:00
m_FoodLevel = root . get ( "food" , MAX_FOOD_LEVEL ). asInt ();
m_FoodSaturationLevel = root . get ( "foodSaturation" , MAX_FOOD_LEVEL ). asDouble ();
m_FoodTickTimer = root . get ( "foodTickTimer" , 0 ). asInt ();
m_FoodExhaustionLevel = root . get ( "foodExhaustion" , 0 ). asDouble ();
2015-03-05 11:52:42 +01:00
m_LifetimeTotalXp = root . get ( "xpTotal" , 0 ). asInt ();
m_CurrentXp = root . get ( "xpCurrent" , 0 ). asInt ();
m_IsFlying = root . get ( "isflying" , 0 ). asBool ();
2013-07-29 12:13:03 +01:00
m_GameMode = ( eGameMode ) root . get ( "gamemode" , eGameMode_NotSet ). asInt ();
2013-12-19 20:53:47 +00:00
if ( m_GameMode == eGameMode_Creative )
{
m_CanFly = true ;
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
m_Inventory . LoadFromJson ( root [ "inventory" ]);
2014-06-29 11:36:38 +01:00
cEnderChestEntity :: LoadFromJson ( root [ "enderchestinventory" ], m_EnderChestContents );
2013-07-29 12:13:03 +01:00
m_LoadedWorldName = root . get ( "world" , "world" ). asString ();
2014-09-05 16:55:16 +02:00
a_World = cRoot :: Get () -> GetWorld ( GetLoadedWorldName (), false );
2014-10-20 21:55:07 +01:00
if ( a_World == nullptr )
2014-09-06 15:26:20 +02:00
{
a_World = cRoot :: Get () -> GetDefaultWorld ();
}
2014-07-20 10:46:45 +01:00
m_LastBedPos . x = root . get ( "SpawnX" , a_World -> GetSpawnX ()). asInt ();
m_LastBedPos . y = root . get ( "SpawnY" , a_World -> GetSpawnY ()). asInt ();
m_LastBedPos . z = root . get ( "SpawnZ" , a_World -> GetSpawnZ ()). asInt ();
2014-05-11 20:30:54 +03:00
2014-05-13 14:53:15 +03:00
// Load the player stats.
2015-05-09 09:25:09 +02:00
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
2014-05-11 20:30:54 +03:00
cStatSerializer StatSerializer ( cRoot :: Get () -> GetDefaultWorld () -> GetName (), GetName (), & m_Stats );
StatSerializer . Load ();
2015-05-23 19:31:33 +01:00
2014-07-14 19:49:31 +01:00
LOGD ( "Player %s was read from file \" %s \" , spawning at {%.2f, %.2f, %.2f} in world \" %s \" " ,
2014-07-20 10:46:45 +01:00
GetName (). c_str (), a_FileName . c_str (), GetPosX (), GetPosY (), GetPosZ (), a_World -> GetName (). c_str ()
2013-07-29 12:13:03 +01:00
);
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
return true ;
}
bool cPlayer :: SaveToDisk ()
{
2014-08-17 22:47:00 +02:00
cFile :: CreateFolder ( FILE_IO_PREFIX + AString ( "players/" )); // Create the "players" folder, if it doesn't exist yet (#1268)
2014-07-11 13:13:10 +02:00
cFile :: CreateFolder ( FILE_IO_PREFIX + AString ( "players/" ) + m_UUID . substr ( 0 , 2 ));
2013-07-29 12:13:03 +01:00
// create the JSON data
Json :: Value JSON_PlayerPosition ;
JSON_PlayerPosition . append ( Json :: Value ( GetPosX ()));
JSON_PlayerPosition . append ( Json :: Value ( GetPosY ()));
JSON_PlayerPosition . append ( Json :: Value ( GetPosZ ()));
Json :: Value JSON_PlayerRotation ;
2014-01-17 11:11:17 +01:00
JSON_PlayerRotation . append ( Json :: Value ( GetYaw ()));
2013-07-29 12:13:03 +01:00
JSON_PlayerRotation . append ( Json :: Value ( GetPitch ()));
JSON_PlayerRotation . append ( Json :: Value ( GetRoll ()));
Json :: Value JSON_Inventory ;
m_Inventory . SaveToJson ( JSON_Inventory );
2014-06-29 11:36:38 +01:00
Json :: Value JSON_EnderChestInventory ;
cEnderChestEntity :: SaveToJson ( JSON_EnderChestInventory , m_EnderChestContents );
2013-07-29 12:13:03 +01:00
Json :: Value root ;
2014-07-02 18:51:37 +01:00
root [ "position" ] = JSON_PlayerPosition ;
root [ "rotation" ] = JSON_PlayerRotation ;
root [ "inventory" ] = JSON_Inventory ;
2014-06-29 11:36:38 +01:00
root [ "enderchestinventory" ] = JSON_EnderChestInventory ;
2014-07-02 18:51:37 +01:00
root [ "health" ] = m_Health ;
root [ "xpTotal" ] = m_LifetimeTotalXp ;
root [ "xpCurrent" ] = m_CurrentXp ;
root [ "air" ] = m_AirLevel ;
root [ "food" ] = m_FoodLevel ;
root [ "foodSaturation" ] = m_FoodSaturationLevel ;
root [ "foodTickTimer" ] = m_FoodTickTimer ;
root [ "foodExhaustion" ] = m_FoodExhaustionLevel ;
root [ "isflying" ] = IsFlying ();
2014-07-11 13:13:10 +02:00
root [ "lastknownname" ] = GetName ();
2014-07-18 20:12:27 +01:00
root [ "SpawnX" ] = GetLastBedPos (). x ;
root [ "SpawnY" ] = GetLastBedPos (). y ;
root [ "SpawnZ" ] = GetLastBedPos (). z ;
2014-10-20 21:55:07 +01:00
if ( m_World != nullptr )
2013-07-29 12:13:03 +01:00
{
2014-07-11 13:13:10 +02:00
root [ "world" ] = m_World -> GetName ();
if ( m_GameMode == m_World -> GetGameMode ())
{
2015-03-01 03:45:06 +10:30
root [ "gamemode" ] = static_cast < int > ( eGameMode_NotSet );
2014-07-11 13:13:10 +02:00
}
else
{
2015-03-01 03:45:06 +10:30
root [ "gamemode" ] = static_cast < int > ( m_GameMode );
2014-07-11 13:13:10 +02:00
}
2013-07-29 12:13:03 +01:00
}
else
{
2014-07-11 13:13:10 +02:00
// This happens if the player is saved to new format after loading from the old format
root [ "world" ] = m_LoadedWorldName ;
2015-03-01 03:45:06 +10:30
root [ "gamemode" ] = static_cast < int > ( eGameMode_NotSet );
2013-07-29 12:13:03 +01:00
}
Json :: StyledWriter writer ;
std :: string JsonData = writer . write ( root );
2014-07-11 13:13:10 +02:00
AString SourceFile = GetUUIDFileName ( m_UUID );
2013-07-29 12:13:03 +01:00
cFile f ;
if ( ! f . Open ( SourceFile , cFile :: fmWrite ))
{
2014-07-11 13:13:10 +02:00
LOGWARNING ( "Error writing player \" %s \" to file \" %s \" - cannot open file. Player will lose their progress." ,
GetName (). c_str (), SourceFile . c_str ()
);
2013-07-29 12:13:03 +01:00
return false ;
}
2015-03-01 03:45:06 +10:30
if ( f . Write ( JsonData . c_str (), JsonData . size ()) != static_cast < int > ( JsonData . size ()))
2013-07-29 12:13:03 +01:00
{
2014-07-11 13:13:10 +02:00
LOGWARNING ( "Error writing player \" %s \" to file \" %s \" - cannot save data. Player will lose their progress. " ,
GetName (). c_str (), SourceFile . c_str ()
2014-07-17 22:50:58 +02:00
);
2013-07-29 12:13:03 +01:00
return false ;
}
2014-05-11 20:30:54 +03:00
2014-05-13 14:53:15 +03:00
// Save the player stats.
2015-05-09 09:25:09 +02:00
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
2014-05-19 20:40:56 +01:00
cStatSerializer StatSerializer ( cRoot :: Get () -> GetDefaultWorld () -> GetName (), GetName (), & m_Stats );
2014-05-11 20:30:54 +03:00
if ( ! StatSerializer . Save ())
{
2014-07-11 13:13:10 +02:00
LOGWARNING ( "Could not save stats for player %s" , GetName (). c_str ());
2014-05-11 20:30:54 +03:00
return false ;
}
2013-07-29 12:13:03 +01:00
return true ;
}
2014-07-23 16:32:09 +02:00
void cPlayer :: UseEquippedItem ( int a_Amount )
2013-07-29 12:13:03 +01:00
{
2014-09-17 17:15:47 +02:00
if ( IsGameModeCreative () || IsGameModeSpectator ()) // No damage in creative or spectator
2013-07-29 12:13:03 +01:00
{
return ;
}
2013-11-02 14:08:00 +00:00
2014-08-31 11:28:42 +02:00
// If the item has an unbreaking enchantment, give it a random chance of not breaking:
2014-08-19 16:08:17 +02:00
cItem Item = GetEquippedItem ();
int UnbreakingLevel = Item . m_Enchantments . GetLevel ( cEnchantments :: enchUnbreaking );
if ( UnbreakingLevel > 0 )
{
int chance ;
if ( ItemCategory :: IsArmor ( Item . m_ItemType ))
{
chance = 60 + ( 40 / ( UnbreakingLevel + 1 ));
}
else
{
chance = 100 / ( UnbreakingLevel + 1 );
}
2013-11-02 14:08:00 +00:00
2014-08-19 16:08:17 +02:00
cFastRandom Random ;
2014-08-21 12:08:38 +02:00
if ( Random . NextInt ( 101 ) <= chance )
2014-08-19 16:08:17 +02:00
{
return ;
}
}
2014-08-31 11:28:42 +02:00
2014-07-23 16:32:09 +02:00
if ( GetInventory (). DamageEquippedItem ( a_Amount ))
2013-12-06 19:59:14 +00:00
{
2015-03-01 03:45:06 +10:30
m_World -> BroadcastSoundEffect ( "random.break" , GetPosX (), GetPosY (), GetPosZ (), 0.5f , static_cast < float > ( 0.75 + ( static_cast < float > (( GetUniqueID () * 23 ) % 32 )) / 64 ));
2013-12-06 19:59:14 +00:00
}
2013-07-29 12:13:03 +01:00
}
2013-08-14 10:33:26 +02:00
2014-02-04 23:27:13 +00:00
void cPlayer :: TickBurning ( cChunk & a_Chunk )
{
2014-12-02 20:25:41 -05:00
// Don't burn in creative or spectator and stop burning in creative if necessary
2014-12-03 23:04:53 -05:00
if ( ! IsGameModeCreative () && ! IsGameModeSpectator ())
2014-02-04 23:27:13 +00:00
{
super :: TickBurning ( a_Chunk );
}
else if ( IsOnFire ())
{
m_TicksLeftBurning = 0 ;
OnFinishedBurning ();
}
}
2013-08-14 10:33:26 +02:00
2013-07-29 12:13:03 +01:00
void cPlayer :: HandleFood ( void )
{
// Ref.: http://www.minecraftwiki.net/wiki/Hunger
2014-06-30 15:12:56 +02:00
2014-12-02 20:25:41 -05:00
if ( IsGameModeCreative () || IsGameModeSpectator ())
2014-02-11 15:34:18 +01:00
{
2014-12-02 20:25:41 -05:00
// Hunger is disabled for Creative and Spectator
2014-02-11 15:34:18 +01:00
return ;
}
2014-06-30 15:12:56 +02:00
2014-07-31 23:04:00 +02:00
// Apply food exhaustion that has accumulated:
if ( m_FoodExhaustionLevel > 4.0 )
{
m_FoodExhaustionLevel -= 4.0 ;
if ( m_FoodSaturationLevel > 0.0 )
{
m_FoodSaturationLevel = std :: max ( m_FoodSaturationLevel - 1.0 , 0.0 );
}
else
{
SetFoodLevel ( m_FoodLevel - 1 );
}
}
2013-07-29 12:13:03 +01:00
// Heal or damage, based on the food level, using the m_FoodTickTimer:
2014-07-31 23:04:00 +02:00
if (( m_FoodLevel >= 18 ) || ( m_FoodLevel <= 0 ))
2013-07-29 12:13:03 +01:00
{
m_FoodTickTimer ++ ;
if ( m_FoodTickTimer >= 80 )
{
m_FoodTickTimer = 0 ;
2014-07-31 23:04:00 +02:00
if (( m_FoodLevel >= 18 ) && ( GetHealth () < GetMaxHealth ()))
2013-07-29 12:13:03 +01:00
{
// Regenerate health from food, incur 3 pts of food exhaustion:
Heal ( 1 );
2014-07-31 23:04:00 +02:00
AddFoodExhaustion ( 3.0 );
2013-07-29 12:13:03 +01:00
}
2014-02-16 13:37:36 +00:00
else if (( m_FoodLevel <= 0 ) && ( m_Health > 1 ))
2013-07-29 12:13:03 +01:00
{
// Damage from starving
2014-10-20 21:55:07 +01:00
TakeDamage ( dtStarving , nullptr , 1 , 1 , 0 );
2013-07-29 12:13:03 +01:00
}
}
}
2014-07-31 23:04:00 +02:00
else
2013-07-29 12:13:03 +01:00
{
2014-07-31 23:04:00 +02:00
m_FoodTickTimer = 0 ;
2013-07-29 12:13:03 +01:00
}
}
2013-12-21 17:31:05 +01:00
void cPlayer :: HandleFloater ()
{
if ( GetEquippedItem (). m_ItemType == E_ITEM_FISHING_ROD )
{
return ;
}
class cFloaterCallback :
public cEntityCallback
{
public :
virtual bool Item ( cEntity * a_Entity ) override
{
a_Entity -> Destroy ( true );
return true ;
}
} Callback ;
m_World -> DoWithEntityByID ( m_FloaterID , Callback );
SetIsFishing ( false );
}
2014-05-20 15:52:59 +03:00
bool cPlayer :: IsClimbing ( void ) const
{
int PosX = POSX_TOINT ;
int PosY = POSY_TOINT ;
int PosZ = POSZ_TOINT ;
if (( PosY < 0 ) || ( PosY >= cChunkDef :: Height ))
{
return false ;
}
BLOCKTYPE Block = m_World -> GetBlock ( PosX , PosY , PosZ );
switch ( Block )
{
case E_BLOCK_LADDER :
case E_BLOCK_VINES :
{
return true ;
}
default : return false ;
}
}
2014-05-12 21:38:52 +03:00
void cPlayer :: UpdateMovementStats ( const Vector3d & a_DeltaPos )
{
2014-05-18 22:49:27 +02:00
StatValue Value = ( StatValue ) floor ( a_DeltaPos . Length () * 100 + 0.5 );
2014-05-12 21:38:52 +03:00
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo == nullptr )
2014-05-12 21:38:52 +03:00
{
2014-05-20 15:52:59 +03:00
if ( IsClimbing ())
2014-05-12 21:38:52 +03:00
{
2014-07-17 22:15:34 +02:00
if ( a_DeltaPos . y > 0.0 ) // Going up
2014-05-20 15:52:59 +03:00
{
m_Stats . AddValue ( statDistClimbed , ( StatValue ) floor ( a_DeltaPos . y * 100 + 0.5 ));
}
2014-05-12 21:38:52 +03:00
}
2014-05-20 15:52:59 +03:00
else if ( IsSubmerged ())
2014-05-12 21:38:52 +03:00
{
2014-05-20 15:52:59 +03:00
m_Stats . AddValue ( statDistDove , Value );
2015-03-01 03:45:06 +10:30
AddFoodExhaustion ( 0.00015 * static_cast < double > ( Value ));
2014-05-12 21:38:52 +03:00
}
2014-05-20 15:52:59 +03:00
else if ( IsSwimming ())
{
m_Stats . AddValue ( statDistSwum , Value );
2015-03-01 03:45:06 +10:30
AddFoodExhaustion ( 0.00015 * static_cast < double > ( Value ));
2014-05-20 15:52:59 +03:00
}
else if ( IsOnGround ())
2014-05-12 21:38:52 +03:00
{
m_Stats . AddValue ( statDistWalked , Value );
2015-03-01 03:45:06 +10:30
AddFoodExhaustion (( m_IsSprinting ? 0.001 : 0.0001 ) * static_cast < double > ( Value ));
2014-05-12 21:38:52 +03:00
}
2014-05-20 15:52:59 +03:00
else
{
2015-05-09 09:25:09 +02:00
if ( Value >= 25 ) // Ignore small / slow movement
2014-05-20 15:52:59 +03:00
{
m_Stats . AddValue ( statDistFlown , Value );
}
}
2014-05-12 21:38:52 +03:00
}
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 ())
{
2014-09-17 18:40:10 +01:00
case mtPig : m_Stats . AddValue ( statDistPig , Value ); break ;
case mtHorse : m_Stats . AddValue ( statDistHorse , Value ); break ;
2014-05-12 21:38:52 +03:00
default : break ;
}
break ;
}
default : break ;
}
}
}
2013-08-09 08:50:33 +01:00
void cPlayer :: ApplyFoodExhaustionFromMovement ()
2013-07-29 12:13:03 +01:00
{
2014-12-02 20:25:41 -05:00
if ( IsGameModeCreative () || IsGameModeSpectator ())
2013-07-29 12:13:03 +01:00
{
return ;
}
2014-04-26 23:52:18 +01:00
2014-07-16 22:22:45 +01:00
// If we have just teleported, apply no exhaustion
2014-07-02 18:46:13 +01:00
if ( m_bIsTeleporting )
{
m_bIsTeleporting = false ;
return ;
}
2014-04-26 23:52:18 +01:00
2013-07-29 12:13:03 +01:00
// If riding anything, apply no food exhaustion
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo != nullptr )
2013-07-29 12:13:03 +01:00
{
return ;
}
2014-07-16 22:22:45 +01:00
// Process exhaustion every two ticks as that is how frequently m_LastPos is updated
// Otherwise, we apply exhaustion for a 'movement' every tick, one of which is an already processed value
if ( GetWorld () -> GetWorldAge () % 2 != 0 )
{
return ;
}
2015-05-23 19:31:33 +01:00
2014-04-26 23:52:18 +01:00
// Calculate the distance travelled, update the last pos:
Vector3d Movement ( GetPosition () - m_LastPos );
Movement . y = 0 ; // Only take XZ movement into account
2013-07-29 12:13:03 +01:00
// Apply the exhaustion based on distance travelled:
double BaseExhaustion = Movement . Length ();
if ( IsSprinting ())
{
// 0.1 pt per meter sprinted
BaseExhaustion = BaseExhaustion * 0.1 ;
}
2013-08-09 08:50:33 +01:00
else if ( IsSwimming ())
2013-07-29 12:13:03 +01:00
{
// 0.015 pt per meter swum
BaseExhaustion = BaseExhaustion * 0.015 ;
}
else
{
// 0.01 pt per meter walked / sneaked
BaseExhaustion = BaseExhaustion * 0.01 ;
}
m_FoodExhaustionLevel += BaseExhaustion ;
}
2014-01-12 23:23:36 +00:00
2014-08-19 17:34:11 +02:00
void cPlayer :: LoadRank ( void )
{
// Load the values from cRankManager:
2014-10-18 19:55:01 +01:00
cRankManager * RankMgr = cRoot :: Get () -> GetRankManager ();
m_Rank = RankMgr -> GetPlayerRankName ( m_UUID );
2014-08-24 20:00:45 +02:00
if ( m_Rank . empty ())
{
2014-10-18 19:55:01 +01:00
m_Rank = RankMgr -> GetDefaultRank ();
2014-08-24 20:00:45 +02:00
}
2014-09-28 02:17:32 +02:00
else
{
// Update the name:
2014-10-18 19:55:01 +01:00
RankMgr -> UpdatePlayerName ( m_UUID , m_PlayerName );
2014-09-28 02:17:32 +02:00
}
2014-10-18 19:55:01 +01:00
m_Permissions = RankMgr -> GetPlayerPermissions ( m_UUID );
2015-04-25 19:40:44 +02:00
m_Restrictions = RankMgr -> GetPlayerRestrictions ( m_UUID );
2014-10-18 19:55:01 +01:00
RankMgr -> GetRankVisuals ( m_Rank , m_MsgPrefix , m_MsgSuffix , m_MsgNameColorCode );
2014-08-19 17:34:11 +02:00
// Break up the individual permissions on each dot, into m_SplitPermissions:
m_SplitPermissions . clear ();
m_SplitPermissions . reserve ( m_Permissions . size ());
2015-04-25 19:40:44 +02:00
for ( auto & Permission : m_Permissions )
{
m_SplitPermissions . push_back ( StringSplit ( Permission , "." ));
} // for Permission - m_Permissions[]
// Break up the individual restrictions on each dot, into m_SplitRestrictions:
m_SplitRestrictions . clear ();
m_SplitRestrictions . reserve ( m_Restrictions . size ());
for ( auto & Restriction : m_Restrictions )
2014-08-19 17:34:11 +02:00
{
2015-04-25 19:40:44 +02:00
m_SplitRestrictions . push_back ( StringSplit ( Restriction , "." ));
} // for itr - m_Restrictions[]
2014-08-19 17:34:11 +02:00
}
2014-12-24 07:20:17 +01:00
bool cPlayer :: PlaceBlock ( int a_BlockX , int a_BlockY , int a_BlockZ , BLOCKTYPE a_BlockType , NIBBLETYPE a_BlockMeta )
{
sSetBlockVector blk {{ a_BlockX , a_BlockY , a_BlockZ , a_BlockType , a_BlockMeta }};
return PlaceBlocks ( blk );
}
void cPlayer :: SendBlocksAround ( int a_BlockX , int a_BlockY , int a_BlockZ , int a_Range )
{
// Collect the coords of all the blocks to send:
sSetBlockVector blks ;
for ( int y = a_BlockY - a_Range + 1 ; y < a_BlockY + a_Range ; y ++ )
{
for ( int z = a_BlockZ - a_Range + 1 ; z < a_BlockZ + a_Range ; z ++ )
{
for ( int x = a_BlockX - a_Range + 1 ; x < a_BlockX + a_Range ; x ++ )
{
blks . emplace_back ( x , y , z , E_BLOCK_AIR , 0 ); // Use fake blocktype, it will get set later on.
};
};
} // for y
// Get the values of all the blocks:
if ( ! m_World -> GetBlocks ( blks , false ))
{
LOGD ( "%s: Cannot query all blocks, not sending an update" , __FUNCTION__ );
return ;
}
// Divide the block changes by their respective chunks:
2014-12-24 08:38:37 +01:00
std :: unordered_map < cChunkCoords , sSetBlockVector , cChunkCoordsHash > Changes ;
2014-12-24 07:20:17 +01:00
for ( const auto & blk : blks )
{
Changes [ cChunkCoords ( blk . m_ChunkX , blk . m_ChunkZ )]. push_back ( blk );
} // for blk - blks[]
blks . clear ();
// Send the blocks for each affected chunk:
for ( auto itr = Changes . cbegin (), end = Changes . cend (); itr != end ; ++ itr )
{
m_ClientHandle -> SendBlockChanges ( itr -> first . m_ChunkX , itr -> first . m_ChunkZ , itr -> second );
}
}
bool cPlayer :: PlaceBlocks ( const sSetBlockVector & a_Blocks )
{
// Call the "placing" hooks; if any fail, abort:
cPluginManager * pm = cPluginManager :: Get ();
for ( auto blk : a_Blocks )
{
if ( pm -> CallHookPlayerPlacingBlock ( * this , blk ))
{
// Abort - re-send all the current blocks in the a_Blocks' coords to the client:
for ( auto blk2 : a_Blocks )
{
m_World -> SendBlockTo ( blk2 . GetX (), blk2 . GetY (), blk2 . GetZ (), this );
}
return false ;
}
} // for blk - a_Blocks[]
// Set the blocks:
m_World -> SetBlocks ( a_Blocks );
// Notify the blockhandlers:
cChunkInterface ChunkInterface ( m_World -> GetChunkMap ());
for ( auto blk : a_Blocks )
{
cBlockHandler * newBlock = BlockHandler ( blk . m_BlockType );
newBlock -> OnPlacedByPlayer ( ChunkInterface , * m_World , this , blk );
}
// Call the "placed" hooks:
for ( auto blk : a_Blocks )
{
pm -> CallHookPlayerPlacedBlock ( * this , blk );
}
return true ;
}
2014-01-12 23:23:36 +00:00
void cPlayer :: Detach ()
{
super :: Detach ();
2014-04-17 18:50:25 +01:00
int PosX = POSX_TOINT ;
int PosY = POSY_TOINT ;
int PosZ = POSZ_TOINT ;
2014-01-12 23:23:36 +00:00
// Search for a position within an area to teleport player after detachment
// Position must be solid land, and occupied by a nonsolid block
// If nothing found, player remains where they are
for ( int x = PosX - 2 ; x <= ( PosX + 2 ); ++ x )
{
for ( int y = PosY ; y <= ( PosY + 3 ); ++ y )
{
for ( int z = PosZ - 2 ; z <= ( PosZ + 2 ); ++ z )
{
2014-03-01 21:34:19 +02:00
if ( ! cBlockInfo :: IsSolid ( m_World -> GetBlock ( x , y , z )) && cBlockInfo :: IsSolid ( m_World -> GetBlock ( x , y - 1 , z )))
2014-01-12 23:23:36 +00:00
{
TeleportToCoords ( x , y , z );
return ;
}
}
}
}
2014-01-14 18:16:13 +00:00
}
2014-07-11 13:13:10 +02:00
2015-01-24 20:17:00 +01:00
void cPlayer :: RemoveClientHandle ( void )
{
ASSERT ( m_ClientHandle != nullptr );
m_ClientHandle . reset ();
}
2014-07-11 13:13:10 +02:00
AString cPlayer :: GetUUIDFileName ( const AString & a_UUID )
{
2014-07-31 22:52:06 +02:00
AString UUID = cMojangAPI :: MakeUUIDDashed ( a_UUID );
ASSERT ( UUID . length () == 36 );
2015-05-23 19:31:33 +01:00
2014-07-11 13:13:10 +02:00
AString res ( "players/" );
2014-07-31 22:52:06 +02:00
res . append ( UUID , 0 , 2 );
2014-07-11 13:13:10 +02:00
res . push_back ( '/' );
2014-07-31 22:52:06 +02:00
res . append ( UUID , 2 , AString :: npos );
2014-07-11 13:13:10 +02:00
res . append ( ".json" );
return res ;
}