2014-06-16 15:57:23 -04:00
2013-07-29 07:13:03 -04:00
# include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
# include "Player.h"
2014-12-24 01:20:17 -05:00
# include <unordered_map>
2014-07-18 02:25:14 -04:00
# include "../ChatColor.h"
2013-08-19 05:39:13 -04:00
# include "../Server.h"
2014-12-13 09:06:55 -05:00
# include "../UI/InventoryWindow.h"
2013-08-19 05:39:13 -04:00
# include "../UI/WindowOwner.h"
# include "../World.h"
2013-12-08 06:17:54 -05:00
# include "../Bindings/PluginManager.h"
2013-08-19 05:39:13 -04:00
# include "../BlockEntities/BlockEntity.h"
2014-06-29 06:36:38 -04:00
# include "../BlockEntities/EnderChestEntity.h"
2013-08-19 05:39:13 -04:00
# include "../Root.h"
# include "../Chunk.h"
# include "../Items/ItemHandler.h"
2014-03-11 10:01:17 -04:00
# include "../Vector3.h"
2014-08-19 10:08:17 -04:00
# include "../FastRandom.h"
2013-07-29 07:13:03 -04:00
2014-05-11 07:57:06 -04:00
# include "../WorldStorage/StatSerializer.h"
2014-05-12 10:05:09 -04:00
# include "../CompositeChat.h"
2014-05-11 07:57:06 -04:00
2014-12-24 01:20:17 -05:00
# include "../Blocks/BlockHandler.h"
# include "../Blocks/BlockSlab.h"
# include "../Blocks/ChunkInterface.h"
2014-10-23 09:15:10 -04:00
# include "../IniFile.h"
2013-11-27 03:17:25 -05:00
# include "json/json.h"
2013-07-29 07:13:03 -04:00
2014-06-16 10:12:50 -04:00
// 6000 ticks or 5 minutes
# define PLAYER_INVENTORY_SAVE_INTERVAL 6000
// 1000 = once per second
2014-10-20 13:59:40 -04:00
# define PLAYER_LIST_TIME_MS std::chrono::milliseconds(1000)
2014-06-16 10:12:50 -04:00
2013-07-29 07:13:03 -04:00
2014-08-10 20:13:14 -04: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 07:13:03 -04:00
2013-11-13 08:50:47 -05:00
2015-01-24 14:17:00 -05:00
cPlayer : : cPlayer ( cClientHandlePtr a_Client , const AString & a_PlayerName ) :
2014-07-11 07:13:10 -04: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 16:55:07 -04:00
m_CurrentWindow ( nullptr ) ,
m_InventoryWindow ( nullptr ) ,
2014-07-11 07:13:10 -04: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 16:55:07 -04:00
m_Team ( nullptr ) ,
2014-07-11 07:13:10 -04:00
m_TicksUntilNextSave ( PLAYER_INVENTORY_SAVE_INTERVAL ) ,
m_bIsTeleporting ( false ) ,
2014-10-20 16:55:07 -04:00
m_UUID ( ( a_Client ! = nullptr ) ? a_Client - > GetUUID ( ) : " " ) ,
2014-09-02 13:12:35 -04:00
m_CustomName ( " " )
2013-07-29 07:13:03 -04:00
{
m_InventoryWindow = new cInventoryWindow ( * this ) ;
m_CurrentWindow = m_InventoryWindow ;
m_InventoryWindow - > OpenedByPlayer ( * this ) ;
2013-08-01 03:51:25 -04:00
SetMaxHealth ( MAX_HEALTH ) ;
m_Health = MAX_HEALTH ;
2015-05-23 14:31:33 -04:00
2014-10-20 13:59:40 -04:00
m_LastPlayerListTime = std : : chrono : : steady_clock : : now ( ) ;
2013-07-29 07:13:03 -04:00
m_PlayerName = a_PlayerName ;
2014-10-20 16:55:07 -04:00
cWorld * World = nullptr ;
2014-06-01 13:46:59 -04:00
if ( ! LoadFromDisk ( World ) )
2013-07-29 07:13:03 -04:00
{
m_Inventory . Clear ( ) ;
2014-06-01 13:46:59 -04:00
SetPosX ( World - > GetSpawnX ( ) ) ;
SetPosY ( World - > GetSpawnY ( ) ) ;
SetPosZ ( World - > GetSpawnZ ( ) ) ;
2015-02-28 12:15:06 -05:00
SetBedPos ( Vector3i ( static_cast < int > ( World - > GetSpawnX ( ) ) , static_cast < int > ( World - > GetSpawnY ( ) ) , static_cast < int > ( World - > GetSpawnZ ( ) ) ) ) ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04: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 18:58:51 -05:00
2015-02-28 12:15:06 -05:00
m_LastJumpHeight = static_cast < float > ( GetPosY ( ) ) ;
m_LastGroundHeight = static_cast < float > ( GetPosY ( ) ) ;
2013-07-29 07:13:03 -04:00
m_Stance = GetPosY ( ) + 1.62 ;
2014-01-24 18:58:51 -05: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 18:58:51 -05:00
}
2015-05-22 19:30:23 -04: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 14:31:33 -04:00
2013-08-14 04:24:34 -04:00
cRoot : : Get ( ) - > GetServer ( ) - > PlayerCreated ( this ) ;
2013-07-29 07:13:03 -04:00
}
cPlayer : : ~ cPlayer ( void )
{
2014-02-03 16:12:44 -05:00
if ( ! cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookPlayerDestroyed ( * this ) )
{
2014-02-05 18:24:16 -05:00
cRoot : : Get ( ) - > BroadcastChatLeave ( Printf ( " %s has left the game " , GetName ( ) . c_str ( ) ) ) ;
2014-07-14 14:49:31 -04:00
LOGINFO ( " Player %s has left the game " , GetName ( ) . c_str ( ) ) ;
2014-02-03 16:12:44 -05:00
}
2014-01-25 05:25:22 -05:00
2014-02-03 17:24:22 -05:00
LOGD ( " Deleting cPlayer \" %s \" at %p, ID %d " , GetName ( ) . c_str ( ) , this , GetUniqueID ( ) ) ;
2015-05-23 14:31:33 -04:00
2013-08-14 13:11:54 -04:00
// Notify the server that the player is being destroyed
cRoot : : Get ( ) - > GetServer ( ) - > PlayerDestroying ( this ) ;
2013-07-29 07:13:03 -04:00
SaveToDisk ( ) ;
2014-10-20 16:55:07 -04:00
m_ClientHandle = nullptr ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
delete m_InventoryWindow ;
2014-10-20 16:55:07 -04:00
m_InventoryWindow = nullptr ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
LOGD ( " Player %p deleted " , this ) ;
}
void cPlayer : : Destroyed ( )
{
CloseWindow ( false ) ;
}
void cPlayer : : SpawnOn ( cClientHandle & a_Client )
{
2015-01-24 14:17:00 -05:00
if ( ! m_bVisible | | ( m_ClientHandle . get ( ) = = ( & a_Client ) ) )
2013-07-29 07:13:03 -04:00
{
2013-08-13 16:45:29 -04:00
return ;
2013-07-29 07:13:03 -04:00
}
2013-08-13 16:45:29 -04:00
a_Client . SendPlayerSpawn ( * this ) ;
a_Client . SendEntityHeadLook ( * this ) ;
2014-07-21 09:19:48 -04: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 07:13:03 -04:00
}
2015-01-11 16:12:26 -05:00
void cPlayer : : Tick ( std : : chrono : : milliseconds a_Dt , cChunk & a_Chunk )
2013-07-29 07:13:03 -04:00
{
2014-10-20 16:55:07 -04:00
if ( m_ClientHandle ! = nullptr )
2013-07-29 07:13:03 -04:00
{
2013-08-13 16:45:29 -04: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 16:55:07 -04:00
m_ClientHandle = nullptr ;
2013-08-13 16:45:29 -04:00
return ;
}
2015-05-23 14:31:33 -04:00
2013-08-12 02:35:13 -04:00
if ( ! m_ClientHandle - > IsPlaying ( ) )
{
// We're not yet in the game, ignore everything
return ;
}
2013-07-29 07:13:03 -04:00
}
2014-05-12 14:38:52 -04:00
m_Stats . AddValue ( statMinutesPlayed , 1 ) ;
2015-05-23 14:31:33 -04:00
2013-08-20 15:17:33 -04: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 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
super : : Tick ( a_Dt , a_Chunk ) ;
2015-05-23 14:31:33 -04:00
2013-08-30 08:24:03 -04:00
// Handle charging the bow:
if ( m_IsChargingBow )
{
m_BowCharge + = 1 ;
}
2015-05-23 14:31:33 -04:00
2014-04-23 16:06:46 -04:00
// Handle updating experience
2013-11-16 04:29:57 -05:00
if ( m_bDirtyExperience )
{
SendExperience ( ) ;
}
2013-08-08 05:32:34 -04:00
2014-08-16 12:44:14 -04:00
bool CanMove = true ;
2015-03-05 05:52:42 -05: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 07:13:03 -04:00
{
// Apply food exhaustion from movement:
2013-08-09 03:50:33 -04:00
ApplyFoodExhaustionFromMovement ( ) ;
2015-05-23 14:31:33 -04:00
2014-08-16 12:44:14 -04: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 07:13:03 -04:00
}
2014-04-23 16:06:46 -04:00
2014-08-16 12:44:14 -04:00
if ( CanMove )
{
2015-01-24 14:17:00 -05:00
BroadcastMovementUpdate ( m_ClientHandle . get ( ) ) ;
2014-08-16 12:44:14 -04:00
}
2013-07-29 07:13:03 -04:00
if ( m_Health > 0 ) // make sure player is alive
{
2014-10-15 13:01:55 -04:00
m_World - > CollectPickupsByPlayer ( * this ) ;
2013-07-29 07:13:03 -04:00
if ( ( m_EatingFinishTick > = 0 ) & & ( m_EatingFinishTick < = m_World - > GetWorldAge ( ) ) )
{
FinishEating ( ) ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
HandleFood ( ) ;
}
2015-05-23 14:31:33 -04:00
2013-12-21 11:31:05 -05:00
if ( m_IsFishing )
{
HandleFloater ( ) ;
}
2014-02-17 09:27:12 -05:00
// Update items (e.g. Maps)
m_Inventory . UpdateItems ( ) ;
2013-07-29 07:13:03 -04:00
// Send Player List (Once per m_LastPlayerListTime/1000 ms)
2014-10-20 13:59:40 -04:00
if ( m_LastPlayerListTime + PLAYER_LIST_TIME_MS < = std : : chrono : : steady_clock : : now ( ) )
2013-07-29 07:13:03 -04:00
{
2014-09-18 12:51:36 -04:00
m_World - > BroadcastPlayerListUpdatePing ( * this ) ;
2014-10-20 13:59:40 -04:00
m_LastPlayerListTime = std : : chrono : : steady_clock : : now ( ) ;
2013-07-29 07:13:03 -04:00
}
2013-12-22 15:03:09 -05:00
if ( IsFlying ( ) )
2013-12-25 09:05:37 -05:00
{
2015-02-28 12:15:06 -05:00
m_LastGroundHeight = static_cast < float > ( GetPosY ( ) ) ;
2013-12-25 09:05:37 -05:00
}
2014-06-16 10:12:50 -04:00
if ( m_TicksUntilNextSave = = 0 )
{
SaveToDisk ( ) ;
m_TicksUntilNextSave = PLAYER_INVENTORY_SAVE_INTERVAL ;
}
else
{
m_TicksUntilNextSave - - ;
}
2013-07-29 07:13:03 -04:00
}
2015-02-28 11:33:41 -05:00
int cPlayer : : CalcLevelFromXp ( int a_XpTotal )
2013-11-13 12:25:47 -05:00
{
2014-07-17 16:15:34 -04:00
// level 0 to 15
2014-07-20 17:10:31 -04:00
if ( a_XpTotal < = XP_TO_LEVEL15 )
2013-11-13 12:25:47 -05:00
{
2013-11-13 15:02:53 -05:00
return a_XpTotal / XP_PER_LEVEL_TO15 ;
2013-11-13 12:25:47 -05:00
}
2014-07-17 16:15:34 -04:00
// level 30+
2014-07-20 17:10:31 -04:00
if ( a_XpTotal > XP_TO_LEVEL30 )
2013-11-13 12:25:47 -05:00
{
2015-02-28 12:15:06 -05:00
return static_cast < int > ( ( 151.5 + sqrt ( 22952.25 - ( 14 * ( 2220 - a_XpTotal ) ) ) ) / 7 ) ;
2013-11-13 12:25:47 -05:00
}
2014-07-17 16:15:34 -04:00
// level 16 to 30
2015-02-28 12:15:06 -05:00
return static_cast < int > ( ( 29.5 + sqrt ( 870.25 - ( 6 * ( 360 - a_XpTotal ) ) ) ) / 3 ) ;
2013-11-13 12:25:47 -05:00
}
2015-02-28 11:33:41 -05:00
int cPlayer : : XpForLevel ( int a_Level )
2013-11-13 12:25:47 -05:00
{
2014-07-17 16:15:34 -04:00
// level 0 to 15
2014-07-20 17:10:31 -04:00
if ( a_Level < = 15 )
2013-11-13 12:25:47 -05:00
{
return a_Level * XP_PER_LEVEL_TO15 ;
}
2014-07-17 16:15:34 -04:00
// level 30+
2014-07-20 17:10:31 -04:00
if ( a_Level > = 31 )
2013-11-13 12:25:47 -05:00
{
2015-02-28 12:15:06 -05:00
return static_cast < int > ( ( 3.5 * a_Level * a_Level ) - ( 151.5 * a_Level ) + 2220 ) ;
2013-11-13 12:25:47 -05:00
}
2014-07-17 16:15:34 -04:00
// level 16 to 30
2015-02-28 12:15:06 -05:00
return static_cast < int > ( ( 1.5 * a_Level * a_Level ) - ( 29.5 * a_Level ) + 360 ) ;
2013-11-13 12:25:47 -05:00
}
2015-02-28 11:33:41 -05:00
int cPlayer : : GetXpLevel ( )
2013-11-13 12:25:47 -05:00
{
2013-11-16 04:29:57 -05:00
return CalcLevelFromXp ( m_CurrentXp ) ;
2013-11-13 12:25:47 -05:00
}
2013-11-16 04:29:57 -05:00
float cPlayer : : GetXpPercentage ( )
2013-11-13 12:25:47 -05:00
{
2015-02-28 11:33:41 -05:00
int currentLevel = CalcLevelFromXp ( m_CurrentXp ) ;
int currentLevel_XpBase = XpForLevel ( currentLevel ) ;
2013-11-13 12:25:47 -05:00
2015-02-28 12:15:06 -05:00
return static_cast < float > ( m_CurrentXp - currentLevel_XpBase ) /
2015-05-09 03:25:09 -04:00
static_cast < float > ( XpForLevel ( 1 + currentLevel ) - currentLevel_XpBase ) ;
2013-11-13 12:25:47 -05:00
}
2015-02-28 11:33:41 -05:00
bool cPlayer : : SetCurrentExperience ( int a_CurrentXp )
2013-11-13 08:50:47 -05:00
{
2015-05-09 03:25:09 -04:00
if ( ! ( a_CurrentXp > = 0 ) | | ( a_CurrentXp > ( std : : numeric_limits < int > : : max ( ) - m_LifetimeTotalXp ) ) )
2013-11-13 08:50:47 -05:00
{
2013-11-16 06:17:46 -05:00
LOGWARNING ( " Tried to update experiece with an invalid Xp value: %d " , a_CurrentXp ) ;
2014-07-17 16:15:34 -04:00
return false ; // oops, they gave us a dodgey number
2013-11-13 08:50:47 -05:00
}
2013-11-16 06:17:46 -05:00
m_CurrentXp = a_CurrentXp ;
2013-11-13 08:50:47 -05:00
2013-11-16 04:29:57 -05:00
// Set experience to be updated
m_bDirtyExperience = true ;
2013-11-15 06:42:09 -05:00
2013-11-13 12:25:47 -05:00
return true ;
2013-11-13 08:50:47 -05:00
}
2015-02-28 11:33:41 -05:00
int cPlayer : : DeltaExperience ( int a_Xp_delta )
2013-11-13 08:50:47 -05:00
{
2015-02-28 11:33:41 -05:00
if ( a_Xp_delta > ( std : : numeric_limits < int > ( ) . max ( ) - m_CurrentXp ) )
2013-11-13 08:50:47 -05:00
{
2013-11-16 06:43:42 -05:00
// Value was bad, abort and report
2015-02-28 11:33:41 -05:00
LOGWARNING ( " Attempt was made to increment Xp by %d, which overflowed the int datatype. Ignoring. " , a_Xp_delta ) ;
2014-07-17 16:15:34 -04:00
return - 1 ; // Should we instead just return the current Xp?
2013-11-13 08:50:47 -05:00
}
2013-11-16 04:29:57 -05:00
m_CurrentXp + = a_Xp_delta ;
2013-11-16 06:43:42 -05:00
// Make sure they didn't subtract too much
2015-02-28 12:15:06 -05:00
m_CurrentXp = std : : max ( m_CurrentXp , 0 ) ;
2013-11-16 06:43:42 -05:00
2013-11-16 04:29:57 -05:00
// Update total for score calculation
2013-11-21 16:09:11 -05:00
if ( a_Xp_delta > 0 )
2013-11-16 05:38:57 -05:00
{
2013-11-16 06:00:45 -05:00
m_LifetimeTotalXp + = a_Xp_delta ;
2013-11-16 05:38:57 -05:00
}
2015-05-09 03:25:09 -04:00
LOGD ( " Player \" %s \" gained / lost %d experience, total is now: %d " , GetName ( ) . c_str ( ) , a_Xp_delta , m_CurrentXp ) ;
2013-11-16 05:38:57 -05:00
// Set experience to be updated
m_bDirtyExperience = true ;
return m_CurrentXp ;
}
2013-08-30 08:24:03 -04:00
void cPlayer : : StartChargingBow ( void )
{
2014-05-20 08:35:39 -04:00
LOGD ( " Player \" %s \" started charging their bow " , GetName ( ) . c_str ( ) ) ;
2013-08-30 08:24:03 -04:00
m_IsChargingBow = true ;
m_BowCharge = 0 ;
2015-01-24 14:17:00 -05:00
m_World - > BroadcastEntityMetadata ( * this , m_ClientHandle . get ( ) ) ;
2013-08-30 08:24:03 -04:00
}
int cPlayer : : FinishChargingBow ( void )
{
2014-05-20 08:35:39 -04:00
LOGD ( " Player \" %s \" finished charging their bow at a charge of %d " , GetName ( ) . c_str ( ) , m_BowCharge ) ;
2013-08-30 08:24:03 -04:00
int res = m_BowCharge ;
m_IsChargingBow = false ;
m_BowCharge = 0 ;
2015-01-24 14:17:00 -05:00
m_World - > BroadcastEntityMetadata ( * this , m_ClientHandle . get ( ) ) ;
2014-06-16 15:57:23 -04:00
2013-08-30 08:24:03 -04:00
return res ;
}
void cPlayer : : CancelChargingBow ( void )
{
2014-05-20 08:35:39 -04:00
LOGD ( " Player \" %s \" cancelled charging their bow at a charge of %d " , GetName ( ) . c_str ( ) , m_BowCharge ) ;
2013-08-30 08:24:03 -04:00
m_IsChargingBow = false ;
m_BowCharge = 0 ;
2015-01-24 14:17:00 -05:00
m_World - > BroadcastEntityMetadata ( * this , m_ClientHandle . get ( ) ) ;
2013-08-30 08:24:03 -04:00
}
2013-07-29 07:13:03 -04:00
void cPlayer : : SetTouchGround ( bool a_bTouchGround )
{
2014-09-17 17:32:14 -04:00
if ( IsGameModeSpectator ( ) ) // You can fly through the ground in Spectator
2014-09-17 11:15:47 -04:00
{
return ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
m_bTouchGround = a_bTouchGround ;
if ( ! m_bTouchGround )
{
if ( GetPosY ( ) > m_LastJumpHeight )
{
2015-02-28 12:15:06 -05:00
m_LastJumpHeight = static_cast < float > ( GetPosY ( ) ) ;
2013-07-29 07:13:03 -04:00
}
cWorld * World = GetWorld ( ) ;
2013-11-02 10:08:00 -04:00
if ( ( GetPosY ( ) > = 0 ) & & ( GetPosY ( ) < cChunkDef : : Height ) )
2013-07-29 07:13:03 -04:00
{
2014-04-17 13:50:25 -04:00
BLOCKTYPE BlockType = World - > GetBlock ( POSX_TOINT , POSY_TOINT , POSZ_TOINT ) ;
2013-07-29 07:13:03 -04: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 12:01:40 -04:00
)
2013-07-29 07:13:03 -04:00
{
2015-02-28 12:15:06 -05:00
m_LastGroundHeight = static_cast < float > ( GetPosY ( ) ) ;
2013-07-29 07:13:03 -04:00
}
}
}
2013-11-02 10:08:00 -04:00
else
2013-07-29 07:13:03 -04:00
{
2015-02-28 12:15:06 -05:00
float Dist = static_cast < float > ( m_LastGroundHeight - floor ( GetPosY ( ) ) ) ;
2014-05-20 08:52:59 -04:00
2014-07-17 16:15:34 -04:00
if ( Dist > = 2.0 ) // At least two blocks - TODO: Use m_LastJumpHeight instead of m_LastGroundHeight above
2014-05-20 08:52:59 -04:00
{
// Increment statistic
m_Stats . AddValue ( statDistFallen , ( StatValue ) floor ( Dist * 100 + 0.5 ) ) ;
}
2015-02-28 12:15:06 -05:00
int Damage = static_cast < int > ( Dist - 3.f ) ;
2014-05-20 08:52:59 -04:00
if ( m_LastJumpHeight > m_LastGroundHeight )
{
Damage + + ;
}
2015-02-28 12:15:06 -05:00
m_LastJumpHeight = static_cast < float > ( GetPosY ( ) ) ;
2013-11-02 10:08:00 -04:00
2013-12-22 15:03:09 -05:00
if ( Damage > 0 )
2013-07-29 07:13:03 -04:00
{
2014-01-25 14:05:44 -05:00
// cPlayer makes sure damage isn't applied in creative, no need to check here
2014-10-20 16:55:07 -04:00
TakeDamage ( dtFalling , nullptr , Damage , Damage , 0 ) ;
2015-05-23 14:31:33 -04:00
2014-01-25 14:02:13 -05:00
// Fall particles
2015-02-28 12:15:06 -05:00
GetWorld ( ) - > BroadcastSoundParticleEffect ( 2006 , POSX_TOINT , static_cast < int > ( GetPosY ( ) ) - 1 , POSZ_TOINT , Damage /* Used as particle effect speed modifier */ ) ;
2014-07-17 16:59:02 -04:00
}
2013-07-29 07:13:03 -04:00
2015-02-28 12:15:06 -05:00
m_LastGroundHeight = static_cast < float > ( GetPosY ( ) ) ;
2013-07-29 07:13:03 -04:00
}
}
void cPlayer : : Heal ( int a_Health )
{
2013-10-24 05:05:43 -04:00
super : : Heal ( a_Health ) ;
SendHealth ( ) ;
2013-07-29 07:13:03 -04:00
}
void cPlayer : : SetFoodLevel ( int a_FoodLevel )
{
2014-08-03 01:42:53 -04:00
int FoodLevel = Clamp ( a_FoodLevel , 0 , MAX_FOOD_LEVEL ) ;
2014-06-30 09:12:56 -04:00
2014-06-30 15:50:40 -04:00
if ( cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookPlayerFoodLevelChange ( * this , FoodLevel ) )
2014-06-30 09:12:56 -04:00
{
m_FoodSaturationLevel = 5.0 ;
return ;
}
2015-05-23 14:31:33 -04:00
2014-06-30 15:50:40 -04:00
m_FoodLevel = FoodLevel ;
2013-07-29 07:13:03 -04:00
SendHealth ( ) ;
}
void cPlayer : : SetFoodSaturationLevel ( double a_FoodSaturationLevel )
{
2015-02-28 12:15:06 -05:00
m_FoodSaturationLevel = Clamp ( a_FoodSaturationLevel , 0.0 , static_cast < double > ( m_FoodLevel ) ) ;
2013-07-29 07:13:03 -04:00
}
void cPlayer : : SetFoodTickTimer ( int a_FoodTickTimer )
{
m_FoodTickTimer = a_FoodTickTimer ;
}
2013-08-19 16:48:13 -04:00
void cPlayer : : SetFoodExhaustionLevel ( double a_FoodExhaustionLevel )
2013-07-29 07:13:03 -04:00
{
2014-08-03 01:35:29 -04:00
m_FoodExhaustionLevel = Clamp ( a_FoodExhaustionLevel , 0.0 , 40.0 ) ;
2013-07-29 07:13:03 -04:00
}
bool cPlayer : : Feed ( int a_Food , double a_Saturation )
{
2014-06-07 16:45:00 -04:00
if ( IsSatiated ( ) )
2013-07-29 07:13:03 -04:00
{
return false ;
}
2014-06-30 09:12:56 -04:00
SetFoodSaturationLevel ( m_FoodSaturationLevel + a_Saturation ) ;
SetFoodLevel ( m_FoodLevel + a_Food ) ;
2013-07-29 07:13:03 -04:00
return true ;
}
2014-07-31 17:04:00 -04:00
void cPlayer : : AddFoodExhaustion ( double a_Exhaustion )
{
2014-09-17 11:15:47 -04:00
if ( ! ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) ) )
2014-07-31 17:04:00 -04:00
{
m_FoodExhaustionLevel = std : : min ( m_FoodExhaustionLevel + a_Exhaustion , 40.0 ) ;
}
}
2013-07-29 07:13:03 -04:00
void cPlayer : : StartEating ( void )
{
// Set the timer:
m_EatingFinishTick = m_World - > GetWorldAge ( ) + EATING_TICKS ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
// Send the packets:
2013-12-06 18:47:07 -05:00
m_World - > BroadcastEntityAnimation ( * this , 3 ) ;
2013-07-29 07:13:03 -04:00
m_World - > BroadcastEntityMetadata ( * this ) ;
}
void cPlayer : : FinishEating ( void )
{
// Reset the timer:
m_EatingFinishTick = - 1 ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
// Send the packets:
2014-04-12 08:16:48 -04:00
m_ClientHandle - > SendEntityStatus ( * this , esPlayerEatingAccepted ) ;
2013-07-29 07:13:03 -04: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 16:55:07 -04:00
if ( m_ClientHandle ! = nullptr )
2013-07-29 07:13:03 -04:00
{
m_ClientHandle - > SendHealth ( ) ;
}
}
2013-11-15 10:23:50 -05:00
void cPlayer : : SendExperience ( void )
{
2014-10-20 16:55:07 -04:00
if ( m_ClientHandle ! = nullptr )
2013-11-15 10:23:50 -05:00
{
m_ClientHandle - > SendExperience ( ) ;
2013-11-16 04:29:57 -05:00
m_bDirtyExperience = false ;
2013-11-15 10:23:50 -05:00
}
}
2013-07-29 07:13:03 -04: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 11:14:40 -04:00
if ( m_IsFlying )
{
return m_FlyingMaxSpeed ;
}
2014-08-03 01:35:29 -04:00
else if ( m_IsSprinting )
{
return m_SprintingMaxSpeed ;
}
2014-03-20 11:14:40 -04:00
else
{
2014-08-03 01:35:29 -04:00
return m_NormalMaxSpeed ;
2014-03-20 11:14:40 -04:00
}
2013-07-29 07:13:03 -04:00
}
void cPlayer : : SetNormalMaxSpeed ( double a_Speed )
{
m_NormalMaxSpeed = a_Speed ;
2014-03-20 11:14:40 -04:00
if ( ! m_IsSprinting & & ! m_IsFlying )
2013-07-29 07:13:03 -04:00
{
m_ClientHandle - > SendPlayerMaxSpeed ( ) ;
}
}
void cPlayer : : SetSprintingMaxSpeed ( double a_Speed )
{
m_SprintingMaxSpeed = a_Speed ;
2014-03-20 11:14:40 -04:00
if ( m_IsSprinting & & ! m_IsFlying )
2013-07-29 07:13:03 -04:00
{
m_ClientHandle - > SendPlayerMaxSpeed ( ) ;
}
}
2014-03-20 11:14:40 -04:00
void cPlayer : : SetFlyingMaxSpeed ( double a_Speed )
{
m_FlyingMaxSpeed = a_Speed ;
2015-05-23 14:31:33 -04:00
2014-03-20 11:14:40 -04:00
// Update the flying speed, always:
m_ClientHandle - > SendPlayerAbilities ( ) ;
}
2013-07-29 07:13:03 -04:00
void cPlayer : : SetCrouch ( bool a_IsCrouched )
{
// Set the crouch status, broadcast to all visible players
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04: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 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
m_IsSprinting = a_IsSprinting ;
m_ClientHandle - > SendPlayerMaxSpeed ( ) ;
}
2013-12-15 08:48:17 -05:00
void cPlayer : : SetCanFly ( bool a_CanFly )
{
if ( a_CanFly = = m_CanFly )
{
return ;
}
m_CanFly = a_CanFly ;
m_ClientHandle - > SendPlayerAbilities ( ) ;
}
2014-09-02 13:12:35 -04:00
void cPlayer : : SetCustomName ( const AString & a_CustomName )
{
if ( m_CustomName = = a_CustomName )
{
return ;
}
2014-09-26 11:37:19 -04:00
m_World - > BroadcastPlayerListRemovePlayer ( * this ) ;
2014-09-02 13:12:35 -04:00
m_CustomName = a_CustomName ;
if ( m_CustomName . length ( ) > 16 )
{
m_CustomName = m_CustomName . substr ( 0 , 16 ) ;
}
2014-09-26 11:37:19 -04:00
m_World - > BroadcastPlayerListAddPlayer ( * this ) ;
m_World - > BroadcastSpawnEntity ( * this , GetClientHandle ( ) ) ;
2014-09-02 13:12:35 -04:00
}
2013-12-15 08:48:17 -05:00
void cPlayer : : SetFlying ( bool a_IsFlying )
{
if ( a_IsFlying = = m_IsFlying )
{
return ;
}
m_IsFlying = a_IsFlying ;
m_ClientHandle - > SendPlayerAbilities ( ) ;
}
2014-04-25 18:32:30 -04:00
bool cPlayer : : DoTakeDamage ( TakeDamageInfo & a_TDI )
2013-07-29 07:13:03 -04:00
{
2014-02-02 07:47:17 -05:00
if ( ( a_TDI . DamageType ! = dtInVoid ) & & ( a_TDI . DamageType ! = dtPlugin ) )
2013-07-29 07:13:03 -04:00
{
2014-09-17 11:15:47 -04:00
if ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) )
2013-09-10 18:02:35 -04:00
{
2014-09-17 11:15:47 -04:00
// No damage / health in creative or spectator mode if not void or plugin damage
2014-04-25 18:32:30 -04:00
return false ;
2013-09-10 18:02:35 -04:00
}
2013-07-29 07:13:03 -04:00
}
2014-01-19 07:20:57 -05:00
2014-10-20 16:55:07 -04:00
if ( ( a_TDI . Attacker ! = nullptr ) & & ( a_TDI . Attacker - > IsPlayer ( ) ) )
2014-01-19 07:20:57 -05:00
{
2014-05-13 07:53:15 -04:00
cPlayer * Attacker = ( cPlayer * ) a_TDI . Attacker ;
2014-01-19 07:20:57 -05:00
2014-10-20 16:55:07 -04:00
if ( ( m_Team ! = nullptr ) & & ( m_Team = = Attacker - > m_Team ) )
2014-01-19 07:20:57 -05:00
{
2014-01-19 09:02:37 -05:00
if ( ! m_Team - > AllowsFriendlyFire ( ) )
2014-01-19 07:20:57 -05:00
{
// Friendly fire is disabled
2014-04-25 18:32:30 -04:00
return false ;
2014-01-19 07:20:57 -05:00
}
}
}
2015-05-23 14:31:33 -04:00
2014-04-25 18:32:30 -04:00
if ( super : : DoTakeDamage ( a_TDI ) )
{
// Any kind of damage adds food exhaustion
AddFoodExhaustion ( 0.3f ) ;
SendHealth ( ) ;
2014-05-12 14:38:52 -04:00
2014-05-18 16:49:27 -04:00
m_Stats . AddValue ( statDamageTaken , ( StatValue ) floor ( a_TDI . FinalDamage * 10 + 0.5 ) ) ;
2014-04-25 18:32:30 -04:00
return true ;
}
return false ;
2013-07-29 07:13:03 -04:00
}
2014-07-04 05:55:09 -04:00
void cPlayer : : KilledBy ( TakeDamageInfo & a_TDI )
2013-07-29 07:13:03 -04:00
{
2014-07-04 05:55:09 -04:00
super : : KilledBy ( a_TDI ) ;
2013-07-29 07:13:03 -04:00
if ( m_Health > 0 )
{
2014-07-17 16:15:34 -04:00
return ; // not dead yet =]
2013-07-29 07:13:03 -04:00
}
2014-07-17 16:15:34 -04:00
m_bVisible = false ; // So new clients don't see the player
2013-07-29 07:13:03 -04:00
// Puke out all the items
cItems Pickups ;
m_Inventory . CopyToItems ( Pickups ) ;
m_Inventory . Clear ( ) ;
2014-02-15 13:51:05 -05:00
if ( GetName ( ) = = " Notch " )
{
Pickups . Add ( cItem ( E_ITEM_RED_APPLE ) ) ;
}
2014-08-18 16:48:15 -04:00
m_Stats . AddValue ( statItemsDropped , ( StatValue ) Pickups . Size ( ) ) ;
2014-05-12 14:38:52 -04:00
2013-07-29 07:13:03 -04:00
m_World - > SpawnItemPickups ( Pickups , GetPosX ( ) , GetPosY ( ) , GetPosZ ( ) , 10 ) ;
SaveToDisk ( ) ; // Save it, yeah the world is a tough place !
2013-12-26 09:55:19 -05:00
2014-10-20 16:55:07 -04:00
if ( ( a_TDI . Attacker = = nullptr ) & & m_World - > ShouldBroadcastDeathMessages ( ) )
2013-12-26 09:55:19 -05:00
{
2014-07-04 05:55:09 -04: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 17:08:54 -04:00
case dtWithering : DamageText = " is a husk of their former selves " ; break ;
2014-07-04 05:55:09 -04: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 ;
}
GetWorld ( ) - > BroadcastChatDeath ( Printf ( " %s %s " , GetName ( ) . c_str ( ) , DamageText . c_str ( ) ) ) ;
2013-12-26 09:55:19 -05:00
}
2014-10-20 16:55:07 -04:00
else if ( a_TDI . Attacker = = nullptr ) // && !m_World->ShouldBroadcastDeathMessages() by fallthrough
2014-08-10 15:47:16 -04:00
{
// no-op
}
2014-07-04 05:55:09 -04:00
else if ( a_TDI . Attacker - > IsPlayer ( ) )
2013-12-26 09:55:19 -05:00
{
2014-07-04 05:55:09 -04:00
cPlayer * Killer = ( cPlayer * ) a_TDI . Attacker ;
2014-03-01 05:06:19 -05:00
2014-05-11 07:57:06 -04:00
GetWorld ( ) - > BroadcastChatDeath ( Printf ( " %s was killed by %s " , GetName ( ) . c_str ( ) , Killer - > GetName ( ) . c_str ( ) ) ) ;
2013-12-26 09:55:19 -05:00
}
else
{
2014-07-04 05:55:09 -04:00
AString KillerClass = a_TDI . Attacker - > GetClass ( ) ;
2014-07-17 16:15:34 -04:00
KillerClass . erase ( KillerClass . begin ( ) ) ; // Erase the 'c' of the class (e.g. "cWitch" -> "Witch")
2013-12-26 09:55:19 -05:00
2014-02-05 18:24:16 -05:00
GetWorld ( ) - > BroadcastChatDeath ( Printf ( " %s was killed by a %s " , GetName ( ) . c_str ( ) , KillerClass . c_str ( ) ) ) ;
2013-12-26 09:55:19 -05:00
}
2014-01-19 07:20:57 -05:00
2014-05-11 07:57:06 -04:00
m_Stats . AddValue ( statDeaths ) ;
2014-03-01 07:20:29 -05:00
m_World - > GetScoreBoard ( ) . AddPlayerScore ( GetName ( ) , cObjective : : otDeathCount , 1 ) ;
2013-07-29 07:13:03 -04:00
}
2014-05-12 10:05:09 -04: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 07:13:03 -04:00
void cPlayer : : Respawn ( void )
{
2014-10-20 16:55:07 -04:00
ASSERT ( m_World ! = nullptr ) ;
2014-06-08 15:58:08 -04:00
2013-07-29 07:13:03 -04:00
m_Health = GetMaxHealth ( ) ;
2014-04-25 18:32:30 -04:00
SetInvulnerableTicks ( 20 ) ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
// Reset food level:
2013-08-01 03:51:25 -04:00
m_FoodLevel = MAX_FOOD_LEVEL ;
2014-06-30 09:12:56 -04:00
m_FoodSaturationLevel = 5.0 ;
2014-07-02 13:48:05 -04:00
m_FoodExhaustionLevel = 0.0 ;
2013-07-29 07:13:03 -04:00
2013-11-16 05:38:57 -05:00
// Reset Experience
2013-11-21 16:09:11 -05:00
m_CurrentXp = 0 ;
m_LifetimeTotalXp = 0 ;
2013-11-16 05:38:57 -05:00
// ToDo: send score to client? How?
2014-07-20 05:46:45 -04:00
m_ClientHandle - > SendRespawn ( GetWorld ( ) - > GetDimension ( ) , true ) ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
// Extinguish the fire:
StopBurning ( ) ;
2014-06-01 13:46:59 -04:00
TeleportToCoords ( GetLastBedPos ( ) . x , GetLastBedPos ( ) . y , GetLastBedPos ( ) . z ) ;
2013-07-29 07:13:03 -04:00
SetVisible ( true ) ;
}
double cPlayer : : GetEyeHeight ( void ) const
{
return m_Stance ;
}
Vector3d cPlayer : : GetEyePosition ( void ) const
{
2014-07-21 09:19:48 -04:00
return Vector3d ( GetPosX ( ) , m_Stance , GetPosZ ( ) ) ;
2013-07-29 07:13:03 -04: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 23:30:20 -05: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 07:13:03 -04:00
}
2014-09-16 14:04:17 -04: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 14:04:17 -04:00
}
2013-07-29 07:13:03 -04:00
2014-01-20 09:10:39 -05:00
void cPlayer : : SetTeam ( cTeam * a_Team )
2014-01-19 07:20:57 -05:00
{
2014-01-20 09:10:39 -05:00
if ( m_Team = = a_Team )
{
return ;
}
2014-01-19 07:20:57 -05:00
if ( m_Team )
{
2014-01-19 09:02:37 -05:00
m_Team - > RemovePlayer ( GetName ( ) ) ;
2014-01-19 07:20:57 -05:00
}
m_Team = a_Team ;
if ( m_Team )
{
2014-01-19 09:02:37 -05:00
m_Team - > AddPlayer ( GetName ( ) ) ;
2014-01-19 07:20:57 -05:00
}
}
2014-01-20 09:10:39 -05:00
cTeam * cPlayer : : UpdateTeam ( void )
{
2014-10-20 16:55:07 -04:00
if ( m_World = = nullptr )
2014-01-21 08:58:17 -05:00
{
2014-10-20 16:55:07 -04:00
SetTeam ( nullptr ) ;
2014-01-21 08:58:17 -05:00
}
else
{
cScoreboard & Scoreboard = m_World - > GetScoreBoard ( ) ;
2014-01-20 09:10:39 -05:00
2014-01-21 08:58:17 -05:00
SetTeam ( Scoreboard . QueryPlayerTeam ( GetName ( ) ) ) ;
}
2014-01-20 09:10:39 -05:00
return m_Team ;
}
2013-07-29 07:13:03 -04: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 16:55:07 -04:00
if ( m_CurrentWindow = = nullptr )
2013-07-29 07:13:03 -04:00
{
m_CurrentWindow = m_InventoryWindow ;
return ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04: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 16:55:07 -04:00
if ( ( m_CurrentWindow = = nullptr ) | | ( m_CurrentWindow - > GetWindowID ( ) ! = a_WindowID ) )
2013-07-29 07:13:03 -04:00
{
return ;
}
CloseWindow ( ) ;
}
void cPlayer : : SetGameMode ( eGameMode a_GameMode )
{
2013-07-30 08:48:18 -04:00
if ( ( a_GameMode < gmMin ) | | ( a_GameMode > = gmMax ) )
2013-07-29 07:13:03 -04:00
{
LOGWARNING ( " %s: Setting invalid gamemode: %d " , GetName ( ) . c_str ( ) , a_GameMode ) ;
return ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
if ( m_GameMode = = a_GameMode )
{
// Gamemode already set
return ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
m_GameMode = a_GameMode ;
m_ClientHandle - > SendGameMode ( a_GameMode ) ;
2013-12-22 15:03:09 -05:00
2015-05-23 14:31:33 -04:00
SetCapabilities ( ) ;
2015-05-22 15:25:16 -04:00
2014-09-18 12:50:17 -04:00
m_World - > BroadcastPlayerListUpdateGameMode ( * this ) ;
2013-07-29 07:13:03 -04:00
}
2014-07-21 09:19:48 -04:00
void cPlayer : : LoginSetGameMode ( eGameMode a_GameMode )
2013-07-29 07:13:03 -04:00
{
m_GameMode = a_GameMode ;
2015-05-23 14:31:33 -04:00
SetCapabilities ( ) ;
}
void cPlayer : : SetCapabilities ( )
{
if ( ! IsGameModeCreative ( ) | | IsGameModeSpectator ( ) )
{
SetFlying ( false ) ;
SetCanFly ( false ) ;
}
if ( IsGameModeSpectator ( ) )
{
SetVisible ( false ) ;
}
else
{
SetVisible ( true ) ;
}
2013-07-29 07:13:03 -04:00
}
void cPlayer : : SetIP ( const AString & a_IP )
{
m_IP = a_IP ;
}
2014-05-12 10:05:09 -04:00
unsigned int cPlayer : : AwardAchievement ( const eStatistic a_Ach )
{
eStatistic Prerequisite = cStatInfo : : GetPrerequisite ( a_Ach ) ;
2014-05-13 07:53:15 -04:00
// Check if the prerequisites are met
2014-05-12 10:05:09 -04: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-26 18:39:39 -04:00
if ( m_World - > ShouldBroadcastAchievementMessages ( ) )
{
cCompositeChat Msg ;
Msg . SetMessageType ( mtSuccess ) ;
Msg . AddShowAchievementPart ( GetName ( ) , cStatInfo : : GetName ( a_Ach ) ) ;
m_World - > BroadcastChat ( Msg ) ;
}
2014-05-12 10:05:09 -04:00
2014-05-13 07:53:15 -04:00
// Increment the statistic
2014-05-12 10:05:09 -04:00
StatValue New = m_Stats . AddValue ( a_Ach ) ;
2014-05-13 07:53:15 -04:00
// Achievement Get!
2014-05-12 10:05:09 -04:00
m_ClientHandle - > SendStatistics ( m_Stats ) ;
return New ;
}
}
2013-07-29 07:13:03 -04:00
void cPlayer : : TeleportToCoords ( double a_PosX , double a_PosY , double a_PosZ )
{
2015-03-05 05:52:42 -05: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 07:13:03 -04:00
2015-03-05 05:52:42 -05:00
m_World - > BroadcastTeleportEntity ( * this , GetClientHandle ( ) ) ;
m_ClientHandle - > SendPlayerMoveLook ( ) ;
}
2013-07-29 07:13:03 -04:00
}
2014-04-04 17:06:47 -04:00
void cPlayer : : SendRotation ( double a_YawDegrees , double a_PitchDegrees )
{
SetYaw ( a_YawDegrees ) ;
SetPitch ( a_PitchDegrees ) ;
m_ClientHandle - > SendPlayerMoveLook ( ) ;
}
2013-08-30 11:29:46 -04:00
Vector3d cPlayer : : GetThrowStartPos ( void ) const
{
Vector3d res = GetEyePosition ( ) ;
2015-05-23 14:31:33 -04:00
2013-08-30 11:29:46 -04: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 14:31:33 -04:00
2013-08-30 11:29:46 -04:00
return res ;
}
Vector3d cPlayer : : GetThrowSpeed ( double a_SpeedCoeff ) const
{
Vector3d res = GetLookVector ( ) ;
res . Normalize ( ) ;
2015-05-23 14:31:33 -04:00
2013-08-30 11:29:46 -04:00
// TODO: Add a slight random change (+-0.0075 in each direction)
2015-05-23 14:31:33 -04:00
2013-08-30 11:29:46 -04:00
return res * a_SpeedCoeff ;
2014-07-17 16:50:58 -04:00
}
2013-08-30 11:29:46 -04:00
2014-04-19 14:51:52 -04:00
void cPlayer : : ForceSetSpeed ( const Vector3d & a_Speed )
2013-12-15 12:54:54 -05:00
{
2014-04-19 14:51:52 -04:00
SetSpeed ( a_Speed ) ;
2014-06-16 10:12:50 -04: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 12:54:54 -05:00
m_ClientHandle - > SendEntityVelocity ( * this ) ;
}
2014-07-21 09:19:48 -04:00
void cPlayer : : MoveTo ( const Vector3d & a_NewPos )
2013-07-29 07:13:03 -04: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 16:50:58 -04:00
// We cannot test m_AttachedTo, because when deattaching, the server thinks the client is already deattached while
2013-07-29 07:13:03 -04:00
// the client may still send more of these nonsensical packets.
2014-10-20 16:55:07 -04:00
if ( m_AttachedTo ! = nullptr )
2013-07-29 07:13:03 -04:00
{
Vector3d AddSpeed ( a_NewPos ) ;
AddSpeed . y = 0 ;
m_AttachedTo - > AddSpeed ( AddSpeed ) ;
}
return ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04: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 14:38:52 -04:00
Vector3d DeltaPos = a_NewPos - GetPosition ( ) ;
UpdateMovementStats ( DeltaPos ) ;
2015-05-23 14:31:33 -04:00
2014-07-21 09:19:48 -04:00
SetPosition ( a_NewPos ) ;
2013-07-29 07:13:03 -04:00
SetStance ( a_NewPos . y + 1.62 ) ;
}
void cPlayer : : SetVisible ( bool a_bVisible )
{
2014-09-17 11:15:47 -04:00
// Need to Check if the player or other players are in gamemode spectator, but will break compatibility
2014-07-17 16:15:34 -04:00
if ( a_bVisible & & ! m_bVisible ) // Make visible
2013-07-29 07:13:03 -04:00
{
m_bVisible = true ;
m_World - > BroadcastSpawnEntity ( * this ) ;
}
if ( ! a_bVisible & & m_bVisible )
{
m_bVisible = false ;
2015-01-24 14:17:00 -05:00
m_World - > BroadcastDestroyEntity ( * this , m_ClientHandle . get ( ) ) ; // Destroy on all clients
2013-07-29 07:13:03 -04:00
}
}
bool cPlayer : : HasPermission ( const AString & a_Permission )
{
if ( a_Permission . empty ( ) )
{
// Empty permission request is always granted
return true ;
}
2015-05-23 14:31:33 -04:00
2014-08-19 11:34:11 -04:00
AStringVector Split = StringSplit ( a_Permission , " . " ) ;
2015-04-25 13:40:44 -04: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 11:34:11 -04:00
// Iterate over all granted permissions; if any matches, then return success:
2015-04-25 13:40:44 -04:00
for ( auto & Permission : m_SplitPermissions )
2013-07-29 07:13:03 -04:00
{
2015-04-25 13:40:44 -04:00
if ( PermissionMatches ( Split , Permission ) )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
return true ;
2013-07-29 07:13:03 -04:00
}
2015-04-25 13:40:44 -04:00
} // for Permission - m_SplitPermissions[]
2013-07-29 07:13:03 -04:00
2014-08-19 11:34:11 -04:00
// No granted permission matches
2013-07-29 07:13:03 -04:00
return false ;
}
2014-08-19 11:34:11 -04:00
bool cPlayer : : PermissionMatches ( const AStringVector & a_Permission , const AStringVector & a_Template )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04: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 07:13:03 -04:00
{
2014-08-19 11:34:11 -04: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 07:13:03 -04:00
return true ;
2014-08-19 11:34:11 -04:00
}
if ( a_Permission [ i ] ! = a_Template [ i ] )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
// Found a mismatch
return false ;
2013-07-29 07:13:03 -04:00
}
}
2014-08-19 11:34:11 -04: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 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
return true ;
2013-07-29 07:13:03 -04:00
}
2014-08-19 11:34:11 -04:00
// There are more sub-items in either the permission or the template, not a match:
return false ;
2013-07-29 07:13:03 -04:00
}
AString cPlayer : : GetColor ( void ) const
{
2014-08-19 11:34:11 -04:00
if ( m_MsgNameColorCode . empty ( ) | | ( m_MsgNameColorCode = = " - " ) )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
// Color has not been assigned, return an empty string:
return AString ( ) ;
2013-07-29 07:13:03 -04:00
}
2014-08-19 11:34:11 -04:00
// Return the color, including the delimiter:
return cChatColor : : Delimiter + m_MsgNameColorCode ;
2013-07-29 07:13:03 -04:00
}
2014-09-23 08:39:49 -04:00
AString cPlayer : : GetPlayerListName ( void ) const
2014-09-02 13:12:35 -04: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 02:27:39 -05:00
void cPlayer : : TossEquippedItem ( char a_Amount )
2013-07-29 07:13:03 -04:00
{
cItems Drops ;
2014-01-23 22:11:10 -05:00
cItem DroppedItem ( GetInventory ( ) . GetEquippedItem ( ) ) ;
if ( ! DroppedItem . IsEmpty ( ) )
2013-07-29 07:13:03 -04:00
{
2014-01-23 22:11:10 -05:00
char NewAmount = a_Amount ;
if ( NewAmount > GetInventory ( ) . GetEquippedItem ( ) . m_ItemCount )
{
2014-07-17 16:15:34 -04:00
NewAmount = GetInventory ( ) . GetEquippedItem ( ) . m_ItemCount ; // Drop only what's there
2014-01-23 22:11:10 -05:00
}
2014-01-15 17:36:19 -05:00
2014-01-23 22:11:10 -05:00
GetInventory ( ) . GetHotbarGrid ( ) . ChangeSlotCount ( GetInventory ( ) . GetEquippedSlotNum ( ) /* Returns hotbar subslot, which HotbarGrid takes */ , - a_Amount ) ;
2014-01-23 02:27:39 -05:00
2014-01-23 22:11:10 -05:00
DroppedItem . m_ItemCount = NewAmount ;
Drops . push_back ( DroppedItem ) ;
2013-07-29 07:13:03 -04:00
}
2014-01-23 02:27:39 -05:00
2014-05-12 14:38:52 -04:00
TossItems ( Drops ) ;
2014-01-23 02:27:39 -05:00
}
void cPlayer : : TossHeldItem ( char a_Amount )
{
cItems Drops ;
2014-01-23 22:11:10 -05:00
cItem & Item = GetDraggingItem ( ) ;
if ( ! Item . IsEmpty ( ) )
2013-07-29 07:13:03 -04:00
{
2014-01-23 22:11:10 -05:00
char OriginalItemAmount = Item . m_ItemCount ;
Item . m_ItemCount = std : : min ( OriginalItemAmount , a_Amount ) ;
Drops . push_back ( Item ) ;
2014-05-12 14:38:52 -04:00
2014-01-23 22:11:10 -05:00
if ( OriginalItemAmount > a_Amount )
2013-07-29 07:13:03 -04:00
{
2014-01-23 22:11:10 -05:00
Item . m_ItemCount = OriginalItemAmount - a_Amount ;
2013-07-29 07:13:03 -04:00
}
else
{
2014-01-23 22:11:10 -05:00
Item . Empty ( ) ;
2013-07-29 07:13:03 -04:00
}
}
2014-01-23 22:11:10 -05:00
2014-05-12 14:38:52 -04:00
TossItems ( Drops ) ;
2014-01-23 02:27:39 -05:00
}
void cPlayer : : TossPickup ( const cItem & a_Item )
{
cItems Drops ;
2014-01-23 22:11:10 -05:00
Drops . push_back ( a_Item ) ;
2014-01-15 17:36:19 -05:00
2014-05-12 14:38:52 -04:00
TossItems ( Drops ) ;
}
void cPlayer : : TossItems ( const cItems & a_Items )
{
2014-09-17 17:32:14 -04:00
if ( IsGameModeSpectator ( ) ) // Players can't toss items in spectator
2014-09-17 11:15:47 -04:00
{
return ;
}
2015-05-23 14:31:33 -04:00
2014-08-18 16:48:15 -04:00
m_Stats . AddValue ( statItemsDropped , ( StatValue ) a_Items . Size ( ) ) ;
2014-05-12 14:38:52 -04:00
2013-07-29 07:13:03 -04:00
double vX = 0 , vY = 0 , vZ = 0 ;
2014-01-17 05:11:17 -05:00
EulerToVector ( - GetYaw ( ) , GetPitch ( ) , vZ , vX , vY ) ;
2013-07-29 07:13:03 -04:00
vY = - vY * 2 + 1.f ;
2014-07-17 16:15:34 -04:00
m_World - > SpawnItemPickups ( a_Items , GetPosX ( ) , GetEyeHeight ( ) , GetPosZ ( ) , vX * 3 , vY * 3 , vZ * 3 , true ) ; // 'true' because created by player
2013-07-29 07:13:03 -04:00
}
2015-05-26 21:35:28 -04:00
bool cPlayer : : DoMoveToWorld ( cWorld * a_World , bool a_ShouldSendRespawn , Vector3d a_NewPosition )
2013-07-29 07:13:03 -04:00
{
2014-10-20 16:55:07 -04:00
ASSERT ( a_World ! = nullptr ) ;
2014-07-22 12:26:48 -04:00
2014-07-21 17:49:06 -04:00
if ( GetWorld ( ) = = a_World )
2013-07-29 07:13:03 -04:00
{
2014-06-12 10:21:07 -04:00
// Don't move to same world
2013-07-29 07:13:03 -04:00
return false ;
}
2015-05-23 14:31:33 -04:00
2015-05-21 06:27:54 -04:00
// Ask the plugins if the player is allowed to changing the world
if ( cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookEntityChangingWorld ( * this , * a_World ) )
2015-05-18 16:29:39 -04:00
{
2015-05-21 06:27:54 -04:00
// A Plugin doesn't allow the player to changing the world
2015-05-18 16:29:39 -04:00
return false ;
}
2014-06-08 15:58:08 -04:00
// Send the respawn packet:
2014-10-20 16:55:07 -04:00
if ( a_ShouldSendRespawn & & ( m_ClientHandle ! = nullptr ) )
2014-06-08 15:58:08 -04:00
{
2014-07-21 17:49:06 -04:00
m_ClientHandle - > SendRespawn ( a_World - > GetDimension ( ) ) ;
2014-06-08 15:58:08 -04:00
}
2015-04-03 12:40:20 -04:00
// Broadcast for other people that the player is gone.
GetWorld ( ) - > BroadcastDestroyEntity ( * this ) ;
2014-06-21 15:42:10 -04:00
// Remove player from the old world
2014-07-22 12:26:48 -04:00
SetWorldTravellingFrom ( GetWorld ( ) ) ; // cChunk handles entity removal
2014-07-29 15:50:30 -04:00
GetWorld ( ) - > RemovePlayer ( this , false ) ;
2013-07-29 07:13:03 -04:00
2015-05-26 21:35:28 -04:00
SetPosition ( a_NewPosition ) ;
2014-06-08 15:58:08 -04:00
// Queue adding player to the new world, including all the necessary adjustments to the object
2014-07-21 17:49:06 -04:00
a_World - > AddPlayer ( this ) ;
2015-05-18 16:29:39 -04:00
cWorld * OldWorld = cRoot : : Get ( ) - > GetWorld ( GetWorld ( ) - > GetName ( ) ) ; // Required for the hook HOOK_ENTITY_CHANGED_WORLD
2014-07-21 17:49:06 -04:00
SetWorld ( a_World ) ; // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
2013-07-29 07:13:03 -04:00
2014-11-14 16:53:12 -05:00
// Update the view distance.
2014-11-15 08:27:50 -05:00
m_ClientHandle - > SetViewDistance ( m_ClientHandle - > GetRequestedViewDistance ( ) ) ;
2014-11-14 16:53:12 -05:00
2014-12-15 11:45:42 -05:00
// Send current weather of target world to player
if ( a_World - > GetDimension ( ) = = dimOverworld )
2014-12-15 12:41:47 -05:00
{
2014-12-15 11:45:42 -05:00
m_ClientHandle - > SendWeather ( a_World - > GetWeather ( ) ) ;
2014-12-15 12:41:47 -05:00
}
2015-04-03 12:40:20 -04:00
// Broadcast the player into the new world.
a_World - > BroadcastSpawnEntity ( * this ) ;
2015-05-23 14:31:33 -04:00
2015-05-18 16:29:39 -04:00
// Player changed the world, call the hook
cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookEntityChangedWorld ( * this , * OldWorld ) ;
2013-07-29 07:13:03 -04:00
return true ;
}
2014-06-15 23:27:27 -04:00
bool cPlayer : : LoadFromDisk ( cWorldPtr & a_World )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
LoadRank ( ) ;
2013-07-29 07:13:03 -04:00
2014-07-11 07:13:10 -04:00
// Load from the UUID file:
2014-07-18 15:12:27 -04:00
if ( LoadFromFile ( GetUUIDFileName ( m_UUID ) , a_World ) )
2014-07-10 18:06:05 -04:00
{
return true ;
}
2015-05-23 14:31:33 -04:00
2014-07-11 07:13:10 -04:00
// Load from the offline UUID file, if allowed:
AString OfflineUUID = cClientHandle : : GenerateOfflineUUID ( GetName ( ) ) ;
2014-07-31 16:52:06 -04:00
const char * OfflineUsage = " (unused) " ;
2014-07-11 07:13:10 -04:00
if ( cRoot : : Get ( ) - > GetServer ( ) - > ShouldLoadOfflinePlayerData ( ) )
{
2014-07-31 16:52:06 -04:00
OfflineUsage = " " ;
2014-07-18 15:12:27 -04:00
if ( LoadFromFile ( GetUUIDFileName ( OfflineUUID ) , a_World ) )
2014-07-11 07:13:10 -04:00
{
return true ;
}
}
2015-05-23 14:31:33 -04:00
2014-07-11 07:13:10 -04: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 15:12:27 -04:00
if ( LoadFromFile ( OldStyleFileName , a_World ) )
2014-07-11 07:13:10 -04:00
{
// Save in new format and remove the old file
2014-07-11 17:12:57 -04:00
if ( SaveToDisk ( ) )
{
cFile : : Delete ( OldStyleFileName ) ;
}
2014-07-11 07:13:10 -04:00
return true ;
}
}
2015-05-23 14:31:33 -04:00
2014-07-11 07:13:10 -04:00
// None of the files loaded successfully
2014-07-31 16:52:06 -04: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 07:13:10 -04:00
) ;
2014-07-20 05:46:45 -04:00
2014-10-20 16:55:07 -04:00
if ( a_World = = nullptr )
2014-07-20 05:46:45 -04:00
{
a_World = cRoot : : Get ( ) - > GetDefaultWorld ( ) ;
}
2014-07-10 18:06:58 -04:00
return false ;
2014-07-10 18:06:05 -04:00
}
2013-07-29 07:13:03 -04:00
2014-07-10 18:06:05 -04:00
2013-07-29 07:13:03 -04:00
2014-07-10 18:06:05 -04:00
2013-07-29 07:13:03 -04:00
2014-07-20 05:46:45 -04:00
bool cPlayer : : LoadFromFile ( const AString & a_FileName , cWorldPtr & a_World )
2014-07-10 18:06:05 -04:00
{
// Load the data from the file:
2013-07-29 07:13:03 -04:00
cFile f ;
2014-07-10 18:06:05 -04:00
if ( ! f . Open ( a_FileName , cFile : : fmRead ) )
2013-07-29 07:13:03 -04:00
{
2013-11-13 15:12:16 -05:00
// This is a new player whom we haven't seen yet, bail out, let them have the defaults
2013-07-29 07:13:03 -04:00
return false ;
}
AString buffer ;
if ( f . ReadRestOfFile ( buffer ) ! = f . GetSize ( ) )
{
2014-07-10 18:06:05 -04:00
LOGWARNING ( " Cannot read player data from file \" %s \" " , a_FileName . c_str ( ) ) ;
2013-07-29 07:13:03 -04:00
return false ;
}
2014-07-10 18:06:05 -04:00
f . Close ( ) ;
2013-07-29 07:13:03 -04:00
2014-07-10 18:06:05 -04:00
// Parse the JSON format:
2013-07-29 07:13:03 -04:00
Json : : Value root ;
Json : : Reader reader ;
if ( ! reader . parse ( buffer , root , false ) )
{
2014-07-10 18:06:05 -04:00
LOGWARNING ( " Cannot parse player data in file \" %s \" " , a_FileName . c_str ( ) ) ;
return false ;
2013-07-29 07:13:03 -04:00
}
2014-07-10 18:06:05 -04:00
// Load the player data:
2013-07-29 07:13:03 -04:00
Json : : Value & JSON_PlayerPosition = root [ " position " ] ;
if ( JSON_PlayerPosition . size ( ) = = 3 )
{
2014-07-10 18:06:05 -04:00
SetPosX ( JSON_PlayerPosition [ ( unsigned ) 0 ] . asDouble ( ) ) ;
SetPosY ( JSON_PlayerPosition [ ( unsigned ) 1 ] . asDouble ( ) ) ;
SetPosZ ( JSON_PlayerPosition [ ( unsigned ) 2 ] . asDouble ( ) ) ;
2014-04-23 16:06:46 -04:00
m_LastPos = GetPosition ( ) ;
2013-07-29 07:13:03 -04:00
}
Json : : Value & JSON_PlayerRotation = root [ " rotation " ] ;
if ( JSON_PlayerRotation . size ( ) = = 3 )
{
2015-02-28 12:15:06 -05:00
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 07:13:03 -04:00
}
2014-07-10 18:06:05 -04:00
m_Health = root . get ( " health " , 0 ) . asInt ( ) ;
2013-08-08 05:32:34 -04:00
m_AirLevel = root . get ( " air " , MAX_AIR_LEVEL ) . asInt ( ) ;
2013-07-29 07:13:03 -04: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 05:52:42 -05: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 07:13:03 -04:00
m_GameMode = ( eGameMode ) root . get ( " gamemode " , eGameMode_NotSet ) . asInt ( ) ;
2013-12-19 15:53:47 -05:00
if ( m_GameMode = = eGameMode_Creative )
{
m_CanFly = true ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
m_Inventory . LoadFromJson ( root [ " inventory " ] ) ;
2014-06-29 06:36:38 -04:00
cEnderChestEntity : : LoadFromJson ( root [ " enderchestinventory " ] , m_EnderChestContents ) ;
2013-07-29 07:13:03 -04:00
m_LoadedWorldName = root . get ( " world " , " world " ) . asString ( ) ;
2014-09-05 10:55:16 -04:00
a_World = cRoot : : Get ( ) - > GetWorld ( GetLoadedWorldName ( ) , false ) ;
2014-10-20 16:55:07 -04:00
if ( a_World = = nullptr )
2014-09-06 09:26:20 -04:00
{
a_World = cRoot : : Get ( ) - > GetDefaultWorld ( ) ;
}
2014-07-20 05:46:45 -04: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 13:30:54 -04:00
2014-05-13 07:53:15 -04:00
// Load the player stats.
2015-05-09 03:25:09 -04:00
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
2014-05-11 13:30:54 -04:00
cStatSerializer StatSerializer ( cRoot : : Get ( ) - > GetDefaultWorld ( ) - > GetName ( ) , GetName ( ) , & m_Stats ) ;
StatSerializer . Load ( ) ;
2015-05-23 14:31:33 -04:00
2014-07-14 14:49:31 -04:00
LOGD ( " Player %s was read from file \" %s \" , spawning at {%.2f, %.2f, %.2f} in world \" %s \" " ,
2014-07-20 05:46:45 -04:00
GetName ( ) . c_str ( ) , a_FileName . c_str ( ) , GetPosX ( ) , GetPosY ( ) , GetPosZ ( ) , a_World - > GetName ( ) . c_str ( )
2013-07-29 07:13:03 -04:00
) ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
return true ;
}
bool cPlayer : : SaveToDisk ( )
{
2014-08-17 16:47:00 -04:00
cFile : : CreateFolder ( FILE_IO_PREFIX + AString ( " players/ " ) ) ; // Create the "players" folder, if it doesn't exist yet (#1268)
2014-07-11 07:13:10 -04:00
cFile : : CreateFolder ( FILE_IO_PREFIX + AString ( " players/ " ) + m_UUID . substr ( 0 , 2 ) ) ;
2013-07-29 07:13:03 -04: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 05:11:17 -05:00
JSON_PlayerRotation . append ( Json : : Value ( GetYaw ( ) ) ) ;
2013-07-29 07:13:03 -04: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 06:36:38 -04:00
Json : : Value JSON_EnderChestInventory ;
cEnderChestEntity : : SaveToJson ( JSON_EnderChestInventory , m_EnderChestContents ) ;
2013-07-29 07:13:03 -04:00
Json : : Value root ;
2014-07-02 13:51:37 -04:00
root [ " position " ] = JSON_PlayerPosition ;
root [ " rotation " ] = JSON_PlayerRotation ;
root [ " inventory " ] = JSON_Inventory ;
2014-06-29 06:36:38 -04:00
root [ " enderchestinventory " ] = JSON_EnderChestInventory ;
2014-07-02 13:51:37 -04: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 07:13:10 -04:00
root [ " lastknownname " ] = GetName ( ) ;
2014-07-18 15:12:27 -04:00
root [ " SpawnX " ] = GetLastBedPos ( ) . x ;
root [ " SpawnY " ] = GetLastBedPos ( ) . y ;
root [ " SpawnZ " ] = GetLastBedPos ( ) . z ;
2014-10-20 16:55:07 -04:00
if ( m_World ! = nullptr )
2013-07-29 07:13:03 -04:00
{
2014-07-11 07:13:10 -04:00
root [ " world " ] = m_World - > GetName ( ) ;
if ( m_GameMode = = m_World - > GetGameMode ( ) )
{
2015-02-28 12:15:06 -05:00
root [ " gamemode " ] = static_cast < int > ( eGameMode_NotSet ) ;
2014-07-11 07:13:10 -04:00
}
else
{
2015-02-28 12:15:06 -05:00
root [ " gamemode " ] = static_cast < int > ( m_GameMode ) ;
2014-07-11 07:13:10 -04:00
}
2013-07-29 07:13:03 -04:00
}
else
{
2014-07-11 07:13:10 -04:00
// This happens if the player is saved to new format after loading from the old format
root [ " world " ] = m_LoadedWorldName ;
2015-02-28 12:15:06 -05:00
root [ " gamemode " ] = static_cast < int > ( eGameMode_NotSet ) ;
2013-07-29 07:13:03 -04:00
}
Json : : StyledWriter writer ;
std : : string JsonData = writer . write ( root ) ;
2014-07-11 07:13:10 -04:00
AString SourceFile = GetUUIDFileName ( m_UUID ) ;
2013-07-29 07:13:03 -04:00
cFile f ;
if ( ! f . Open ( SourceFile , cFile : : fmWrite ) )
{
2014-07-11 07:13:10 -04: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 07:13:03 -04:00
return false ;
}
2015-02-28 12:15:06 -05:00
if ( f . Write ( JsonData . c_str ( ) , JsonData . size ( ) ) ! = static_cast < int > ( JsonData . size ( ) ) )
2013-07-29 07:13:03 -04:00
{
2014-07-11 07:13:10 -04: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 16:50:58 -04:00
) ;
2013-07-29 07:13:03 -04:00
return false ;
}
2014-05-11 13:30:54 -04:00
2014-05-13 07:53:15 -04:00
// Save the player stats.
2015-05-09 03:25:09 -04:00
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
2014-05-19 15:40:56 -04:00
cStatSerializer StatSerializer ( cRoot : : Get ( ) - > GetDefaultWorld ( ) - > GetName ( ) , GetName ( ) , & m_Stats ) ;
2014-05-11 13:30:54 -04:00
if ( ! StatSerializer . Save ( ) )
{
2014-07-11 07:13:10 -04:00
LOGWARNING ( " Could not save stats for player %s " , GetName ( ) . c_str ( ) ) ;
2014-05-11 13:30:54 -04:00
return false ;
}
2013-07-29 07:13:03 -04:00
return true ;
}
2014-07-23 10:32:09 -04:00
void cPlayer : : UseEquippedItem ( int a_Amount )
2013-07-29 07:13:03 -04:00
{
2014-09-17 11:15:47 -04:00
if ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) ) // No damage in creative or spectator
2013-07-29 07:13:03 -04:00
{
return ;
}
2013-11-02 10:08:00 -04:00
2014-08-31 05:28:42 -04:00
// If the item has an unbreaking enchantment, give it a random chance of not breaking:
2014-08-19 10:08:17 -04: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 10:08:00 -04:00
2014-08-19 10:08:17 -04:00
cFastRandom Random ;
2014-08-21 06:08:38 -04:00
if ( Random . NextInt ( 101 ) < = chance )
2014-08-19 10:08:17 -04:00
{
return ;
}
}
2014-08-31 05:28:42 -04:00
2014-07-23 10:32:09 -04:00
if ( GetInventory ( ) . DamageEquippedItem ( a_Amount ) )
2013-12-06 14:59:14 -05:00
{
2015-02-28 12:15:06 -05:00
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 14:59:14 -05:00
}
2013-07-29 07:13:03 -04:00
}
2013-08-14 04:33:26 -04:00
2014-02-04 18:27:13 -05: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 18:27:13 -05:00
{
super : : TickBurning ( a_Chunk ) ;
}
else if ( IsOnFire ( ) )
{
m_TicksLeftBurning = 0 ;
OnFinishedBurning ( ) ;
}
}
2013-08-14 04:33:26 -04:00
2013-07-29 07:13:03 -04:00
void cPlayer : : HandleFood ( void )
{
// Ref.: http://www.minecraftwiki.net/wiki/Hunger
2014-06-30 09:12:56 -04:00
2014-12-02 20:25:41 -05:00
if ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) )
2014-02-11 09:34:18 -05:00
{
2014-12-02 20:25:41 -05:00
// Hunger is disabled for Creative and Spectator
2014-02-11 09:34:18 -05:00
return ;
}
2014-06-30 09:12:56 -04:00
2014-07-31 17:04:00 -04: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 07:13:03 -04:00
// Heal or damage, based on the food level, using the m_FoodTickTimer:
2014-07-31 17:04:00 -04:00
if ( ( m_FoodLevel > = 18 ) | | ( m_FoodLevel < = 0 ) )
2013-07-29 07:13:03 -04:00
{
m_FoodTickTimer + + ;
if ( m_FoodTickTimer > = 80 )
{
m_FoodTickTimer = 0 ;
2014-07-31 17:04:00 -04:00
if ( ( m_FoodLevel > = 18 ) & & ( GetHealth ( ) < GetMaxHealth ( ) ) )
2013-07-29 07:13:03 -04:00
{
// Regenerate health from food, incur 3 pts of food exhaustion:
Heal ( 1 ) ;
2014-07-31 17:04:00 -04:00
AddFoodExhaustion ( 3.0 ) ;
2013-07-29 07:13:03 -04:00
}
2014-02-16 08:37:36 -05:00
else if ( ( m_FoodLevel < = 0 ) & & ( m_Health > 1 ) )
2013-07-29 07:13:03 -04:00
{
// Damage from starving
2014-10-20 16:55:07 -04:00
TakeDamage ( dtStarving , nullptr , 1 , 1 , 0 ) ;
2013-07-29 07:13:03 -04:00
}
}
}
2014-07-31 17:04:00 -04:00
else
2013-07-29 07:13:03 -04:00
{
2014-07-31 17:04:00 -04:00
m_FoodTickTimer = 0 ;
2013-07-29 07:13:03 -04:00
}
}
2013-12-21 11:31:05 -05: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 08:52:59 -04: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 14:38:52 -04:00
void cPlayer : : UpdateMovementStats ( const Vector3d & a_DeltaPos )
{
2014-05-18 16:49:27 -04:00
StatValue Value = ( StatValue ) floor ( a_DeltaPos . Length ( ) * 100 + 0.5 ) ;
2014-05-12 14:38:52 -04:00
2014-10-20 16:55:07 -04:00
if ( m_AttachedTo = = nullptr )
2014-05-12 14:38:52 -04:00
{
2014-05-20 08:52:59 -04:00
if ( IsClimbing ( ) )
2014-05-12 14:38:52 -04:00
{
2014-07-17 16:15:34 -04:00
if ( a_DeltaPos . y > 0.0 ) // Going up
2014-05-20 08:52:59 -04:00
{
m_Stats . AddValue ( statDistClimbed , ( StatValue ) floor ( a_DeltaPos . y * 100 + 0.5 ) ) ;
}
2014-05-12 14:38:52 -04:00
}
2014-05-20 08:52:59 -04:00
else if ( IsSubmerged ( ) )
2014-05-12 14:38:52 -04:00
{
2014-05-20 08:52:59 -04:00
m_Stats . AddValue ( statDistDove , Value ) ;
2015-02-28 12:15:06 -05:00
AddFoodExhaustion ( 0.00015 * static_cast < double > ( Value ) ) ;
2014-05-12 14:38:52 -04:00
}
2014-05-20 08:52:59 -04:00
else if ( IsSwimming ( ) )
{
m_Stats . AddValue ( statDistSwum , Value ) ;
2015-02-28 12:15:06 -05:00
AddFoodExhaustion ( 0.00015 * static_cast < double > ( Value ) ) ;
2014-05-20 08:52:59 -04:00
}
else if ( IsOnGround ( ) )
2014-05-12 14:38:52 -04:00
{
m_Stats . AddValue ( statDistWalked , Value ) ;
2015-02-28 12:15:06 -05:00
AddFoodExhaustion ( ( m_IsSprinting ? 0.001 : 0.0001 ) * static_cast < double > ( Value ) ) ;
2014-05-12 14:38:52 -04:00
}
2014-05-20 08:52:59 -04:00
else
{
2015-05-09 03:25:09 -04:00
if ( Value > = 25 ) // Ignore small / slow movement
2014-05-20 08:52:59 -04:00
{
m_Stats . AddValue ( statDistFlown , Value ) ;
}
}
2014-05-12 14:38:52 -04: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 13:40:10 -04:00
case mtPig : m_Stats . AddValue ( statDistPig , Value ) ; break ;
case mtHorse : m_Stats . AddValue ( statDistHorse , Value ) ; break ;
2014-05-12 14:38:52 -04:00
default : break ;
}
break ;
}
default : break ;
}
}
}
2013-08-09 03:50:33 -04:00
void cPlayer : : ApplyFoodExhaustionFromMovement ( )
2013-07-29 07:13:03 -04:00
{
2014-12-02 20:25:41 -05:00
if ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) )
2013-07-29 07:13:03 -04:00
{
return ;
}
2014-04-26 18:52:18 -04:00
2014-07-16 17:22:45 -04:00
// If we have just teleported, apply no exhaustion
2014-07-02 13:46:13 -04:00
if ( m_bIsTeleporting )
{
m_bIsTeleporting = false ;
return ;
}
2014-04-26 18:52:18 -04:00
2013-07-29 07:13:03 -04:00
// If riding anything, apply no food exhaustion
2014-10-20 16:55:07 -04:00
if ( m_AttachedTo ! = nullptr )
2013-07-29 07:13:03 -04:00
{
return ;
}
2014-07-16 17:22:45 -04: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 14:31:33 -04:00
2014-04-26 18:52:18 -04: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 07:13:03 -04: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 03:50:33 -04:00
else if ( IsSwimming ( ) )
2013-07-29 07:13:03 -04: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 18:23:36 -05:00
2014-08-19 11:34:11 -04:00
void cPlayer : : LoadRank ( void )
{
// Load the values from cRankManager:
2014-10-18 14:55:01 -04:00
cRankManager * RankMgr = cRoot : : Get ( ) - > GetRankManager ( ) ;
m_Rank = RankMgr - > GetPlayerRankName ( m_UUID ) ;
2014-08-24 14:00:45 -04:00
if ( m_Rank . empty ( ) )
{
2014-10-18 14:55:01 -04:00
m_Rank = RankMgr - > GetDefaultRank ( ) ;
2014-08-24 14:00:45 -04:00
}
2014-09-27 20:17:32 -04:00
else
{
// Update the name:
2014-10-18 14:55:01 -04:00
RankMgr - > UpdatePlayerName ( m_UUID , m_PlayerName ) ;
2014-09-27 20:17:32 -04:00
}
2014-10-18 14:55:01 -04:00
m_Permissions = RankMgr - > GetPlayerPermissions ( m_UUID ) ;
2015-04-25 13:40:44 -04:00
m_Restrictions = RankMgr - > GetPlayerRestrictions ( m_UUID ) ;
2014-10-18 14:55:01 -04:00
RankMgr - > GetRankVisuals ( m_Rank , m_MsgPrefix , m_MsgSuffix , m_MsgNameColorCode ) ;
2014-08-19 11:34:11 -04:00
// Break up the individual permissions on each dot, into m_SplitPermissions:
m_SplitPermissions . clear ( ) ;
m_SplitPermissions . reserve ( m_Permissions . size ( ) ) ;
2015-04-25 13:40:44 -04: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 11:34:11 -04:00
{
2015-04-25 13:40:44 -04:00
m_SplitRestrictions . push_back ( StringSplit ( Restriction , " . " ) ) ;
} // for itr - m_Restrictions[]
2014-08-19 11:34:11 -04:00
}
2014-12-24 01:20:17 -05: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 02:38:37 -05:00
std : : unordered_map < cChunkCoords , sSetBlockVector , cChunkCoordsHash > Changes ;
2014-12-24 01:20:17 -05: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 18:23:36 -05:00
void cPlayer : : Detach ( )
{
super : : Detach ( ) ;
2014-04-17 13:50:25 -04:00
int PosX = POSX_TOINT ;
int PosY = POSY_TOINT ;
int PosZ = POSZ_TOINT ;
2014-01-12 18:23:36 -05: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 14:34:19 -05:00
if ( ! cBlockInfo : : IsSolid ( m_World - > GetBlock ( x , y , z ) ) & & cBlockInfo : : IsSolid ( m_World - > GetBlock ( x , y - 1 , z ) ) )
2014-01-12 18:23:36 -05:00
{
TeleportToCoords ( x , y , z ) ;
return ;
}
}
}
}
2014-01-14 13:16:13 -05:00
}
2014-07-11 07:13:10 -04:00
2015-01-24 14:17:00 -05:00
void cPlayer : : RemoveClientHandle ( void )
{
ASSERT ( m_ClientHandle ! = nullptr ) ;
m_ClientHandle . reset ( ) ;
}
2014-07-11 07:13:10 -04:00
AString cPlayer : : GetUUIDFileName ( const AString & a_UUID )
{
2014-07-31 16:52:06 -04:00
AString UUID = cMojangAPI : : MakeUUIDDashed ( a_UUID ) ;
ASSERT ( UUID . length ( ) = = 36 ) ;
2015-05-23 14:31:33 -04:00
2014-07-11 07:13:10 -04:00
AString res ( " players/ " ) ;
2014-07-31 16:52:06 -04:00
res . append ( UUID , 0 , 2 ) ;
2014-07-11 07:13:10 -04:00
res . push_back ( ' / ' ) ;
2014-07-31 16:52:06 -04:00
res . append ( UUID , 2 , AString : : npos ) ;
2014-07-11 07:13:10 -04:00
res . append ( " .json " ) ;
return res ;
}