2013-11-13 08:50:47 -05: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"
2013-08-19 05:39:13 -04:00
# include "../Server.h"
# include "../UI/Window.h"
# 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"
# include "../GroupManager.h"
# include "../Group.h"
# include "../ChatColor.h"
# include "../Item.h"
# include "../Tracer.h"
# include "../Root.h"
# include "../OSSupport/Timer.h"
# include "../MersenneTwister.h"
# include "../Chunk.h"
# include "../Items/ItemHandler.h"
2013-07-29 07:13:03 -04:00
2013-08-19 05:39:13 -04:00
# include "../Vector3d.h"
# include "../Vector3f.h"
2013-07-29 07:13:03 -04:00
2013-11-27 02:27:19 -05:00
# include "inifile/iniFile.h"
2013-11-27 03:17:25 -05:00
# include "json/json.h"
2013-07-29 07:13:03 -04:00
2013-11-13 08:50:47 -05:00
2013-07-29 07:13:03 -04:00
cPlayer : : cPlayer ( cClientHandle * a_Client , const AString & a_PlayerName )
: super ( etPlayer , 0.6 , 1.8 )
2013-12-20 10:39:20 -05:00
, m_bVisible ( true )
2013-08-01 03:51:25 -04:00
, m_FoodLevel ( MAX_FOOD_LEVEL )
2013-07-29 07:13:03 -04:00
, m_FoodSaturationLevel ( 5 )
, m_FoodTickTimer ( 0 )
, m_FoodExhaustionLevel ( 0 )
, m_FoodPoisonedTicksRemaining ( 0 )
2013-12-20 10:39:20 -05:00
, m_LastJumpHeight ( 0 )
, m_LastGroundHeight ( 0 )
, m_bTouchGround ( false )
, m_Stance ( 0.0 )
, m_Inventory ( * this )
, m_CurrentWindow ( NULL )
, m_InventoryWindow ( NULL )
, m_TimeLastPickupCheck ( 0.f )
, m_Color ( ' - ' )
, m_LastBlockActionTime ( 0 )
, m_LastBlockActionCnt ( 0 )
, m_GameMode ( eGameMode_NotSet )
, m_IP ( " " )
, m_ClientHandle ( a_Client )
2013-07-29 07:13:03 -04:00
, m_NormalMaxSpeed ( 0.1 )
, m_SprintingMaxSpeed ( 0.13 )
, m_IsCrouched ( false )
, m_IsSprinting ( false )
2013-12-20 10:39:20 -05:00
, m_IsFlying ( false )
2013-08-09 03:57:24 -04:00
, m_IsSwimming ( false )
, m_IsSubmerged ( false )
2013-12-19 11:33:21 -05:00
, m_IsFishing ( false )
2013-12-20 10:39:20 -05:00
, m_CanFly ( false )
2013-07-29 07:13:03 -04:00
, m_EatingFinishTick ( - 1 )
2013-11-16 05:38:57 -05:00
, m_LifetimeTotalXp ( 0 )
2013-12-20 10:39:20 -05:00
, m_CurrentXp ( 0 )
2013-11-16 05:38:57 -05:00
, m_bDirtyExperience ( false )
2013-12-20 10:39:20 -05:00
, m_IsChargingBow ( false )
, m_BowCharge ( 0 )
, m_FloaterID ( - 1 )
2014-01-19 07:20:57 -05:00
, m_Team ( NULL )
2013-07-29 07:13:03 -04:00
{
LOGD ( " Created a player object for \" %s \" @ \" %s \" at %p, ID %d " ,
a_PlayerName . c_str ( ) , a_Client - > GetIPString ( ) . c_str ( ) ,
this , GetUniqueID ( )
) ;
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 ;
2013-07-29 07:13:03 -04:00
cTimer t1 ;
m_LastPlayerListTime = t1 . GetNowTime ( ) ;
m_TimeLastTeleportPacket = 0 ;
m_TimeLastPickupCheck = 0 ;
m_PlayerName = a_PlayerName ;
m_bDirtyPosition = true ; // So chunks are streamed to player at spawn
if ( ! LoadFromDisk ( ) )
{
m_Inventory . Clear ( ) ;
SetPosX ( cRoot : : Get ( ) - > GetDefaultWorld ( ) - > GetSpawnX ( ) ) ;
SetPosY ( cRoot : : Get ( ) - > GetDefaultWorld ( ) - > GetSpawnY ( ) ) ;
SetPosZ ( cRoot : : Get ( ) - > GetDefaultWorld ( ) - > GetSpawnZ ( ) ) ;
LOGD ( " Player \" %s \" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f} " ,
a_PlayerName . c_str ( ) , GetPosX ( ) , GetPosY ( ) , GetPosZ ( )
) ;
}
2014-01-24 18:58:51 -05:00
2013-07-29 07:13:03 -04:00
m_LastJumpHeight = ( float ) ( GetPosY ( ) ) ;
m_LastGroundHeight = ( float ) ( GetPosY ( ) ) ;
m_Stance = GetPosY ( ) + 1.62 ;
2014-01-24 18:58:51 -05:00
if ( m_GameMode = = gmNotSet )
{
cWorld * World = cRoot : : Get ( ) - > GetWorld ( GetLoadedWorldName ( ) ) ;
if ( World = = NULL )
{
World = cRoot : : Get ( ) - > GetDefaultWorld ( ) ;
}
if ( World - > IsGameModeCreative ( ) )
{
m_CanFly = true ;
}
}
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-02-03 17:24:22 -05: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 ( ) ) ;
2013-07-29 07:13:03 -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 ( ) ;
m_World - > RemovePlayer ( this ) ;
m_ClientHandle = NULL ;
delete m_InventoryWindow ;
LOGD ( " Player %p deleted " , this ) ;
}
void cPlayer : : Destroyed ( )
{
CloseWindow ( false ) ;
2013-08-13 16:45:29 -04:00
2013-07-29 07:13:03 -04:00
m_ClientHandle = NULL ;
}
void cPlayer : : SpawnOn ( cClientHandle & a_Client )
{
2013-08-13 16:45:29 -04:00
if ( ! m_bVisible | | ( m_ClientHandle = = ( & 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 ) ;
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
}
void cPlayer : : Tick ( float a_Dt , cChunk & a_Chunk )
{
2013-08-12 02:35:13 -04:00
if ( m_ClientHandle ! = NULL )
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
m_ClientHandle = NULL ;
return ;
}
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
}
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 ;
}
2013-07-29 07:13:03 -04:00
super : : Tick ( a_Dt , a_Chunk ) ;
2013-08-09 03:57:24 -04:00
2013-08-30 08:24:03 -04:00
// Handle charging the bow:
if ( m_IsChargingBow )
{
m_BowCharge + = 1 ;
}
2013-11-16 04:29:57 -05:00
//handle updating experience
if ( m_bDirtyExperience )
{
SendExperience ( ) ;
}
2013-08-08 05:32:34 -04:00
2013-07-29 07:13:03 -04:00
if ( m_bDirtyPosition )
{
// Apply food exhaustion from movement:
2013-08-09 03:50:33 -04:00
ApplyFoodExhaustionFromMovement ( ) ;
2013-07-29 07:13:03 -04:00
cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookPlayerMoving ( * this ) ;
BroadcastMovementUpdate ( m_ClientHandle ) ;
m_ClientHandle - > StreamChunks ( ) ;
}
else
{
BroadcastMovementUpdate ( m_ClientHandle ) ;
}
if ( m_Health > 0 ) // make sure player is alive
{
m_World - > CollectPickupsByPlayer ( this ) ;
if ( ( m_EatingFinishTick > = 0 ) & & ( m_EatingFinishTick < = m_World - > GetWorldAge ( ) ) )
{
FinishEating ( ) ;
}
HandleFood ( ) ;
}
2013-12-21 11:31:05 -05:00
if ( m_IsFishing )
{
HandleFloater ( ) ;
}
2013-07-29 07:13:03 -04:00
// Send Player List (Once per m_LastPlayerListTime/1000 ms)
cTimer t1 ;
if ( m_LastPlayerListTime + cPlayer : : PLAYER_LIST_TIME_MS < = t1 . GetNowTime ( ) )
{
m_World - > SendPlayerList ( this ) ;
m_LastPlayerListTime = t1 . GetNowTime ( ) ;
}
2013-12-22 15:03:09 -05:00
if ( IsFlying ( ) )
2013-12-25 09:05:37 -05:00
{
2013-12-22 15:03:09 -05:00
m_LastGroundHeight = ( float ) GetPosY ( ) ;
2013-12-25 09:05:37 -05:00
}
2013-07-29 07:13:03 -04:00
}
2013-11-15 06:42:09 -05:00
short cPlayer : : CalcLevelFromXp ( short a_XpTotal )
2013-11-13 12:25:47 -05:00
{
//level 0 to 15
if ( a_XpTotal < = XP_TO_LEVEL15 )
{
2013-11-13 15:02:53 -05:00
return a_XpTotal / XP_PER_LEVEL_TO15 ;
2013-11-13 12:25:47 -05:00
}
//level 30+
if ( a_XpTotal > XP_TO_LEVEL30 )
{
2013-11-15 06:42:09 -05:00
return ( short ) ( 151.5 + sqrt ( 22952.25 - ( 14 * ( 2220 - a_XpTotal ) ) ) ) / 7 ;
2013-11-13 12:25:47 -05:00
}
//level 16 to 30
2013-11-15 06:42:09 -05:00
return ( short ) ( 29.5 + sqrt ( 870.25 - ( 6 * ( 360 - a_XpTotal ) ) ) ) / 3 ;
2013-11-13 12:25:47 -05:00
}
2013-11-15 06:42:09 -05:00
short cPlayer : : XpForLevel ( short a_Level )
2013-11-13 12:25:47 -05:00
{
//level 0 to 15
if ( a_Level < = 15 )
{
return a_Level * XP_PER_LEVEL_TO15 ;
}
//level 30+
if ( a_Level > = 31 )
{
2013-11-15 06:42:09 -05:00
return ( short ) ( ( 3.5 * a_Level * a_Level ) - ( 151.5 * a_Level ) + 2220 ) ;
2013-11-13 12:25:47 -05:00
}
//level 16 to 30
2013-11-15 06:42:09 -05:00
return ( short ) ( ( 1.5 * a_Level * a_Level ) - ( 29.5 * a_Level ) + 360 ) ;
2013-11-13 12:25:47 -05:00
}
2013-11-16 04:29:57 -05:00
short 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
{
2013-11-16 04:29:57 -05:00
short int currentLevel = CalcLevelFromXp ( m_CurrentXp ) ;
2013-11-15 06:42:09 -05:00
short int currentLevel_XpBase = XpForLevel ( currentLevel ) ;
2013-11-13 12:25:47 -05:00
2013-11-16 04:29:57 -05:00
return ( float ) ( m_CurrentXp - currentLevel_XpBase ) /
2013-11-14 09:46:41 -05:00
( float ) ( XpForLevel ( 1 + currentLevel ) - currentLevel_XpBase ) ;
2013-11-13 12:25:47 -05:00
}
2013-11-16 06:17:46 -05:00
bool cPlayer : : SetCurrentExperience ( short int a_CurrentXp )
2013-11-13 08:50:47 -05:00
{
2013-11-16 06:17:46 -05:00
if ( ! ( a_CurrentXp > = 0 ) | | ( a_CurrentXp > ( SHRT_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 ) ;
2013-11-13 08:50:47 -05:00
return false ; //oops, they gave us a dodgey number
}
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
}
2013-11-16 06:00:45 -05:00
short cPlayer : : DeltaExperience ( short a_Xp_delta )
2013-11-13 08:50:47 -05:00
{
2013-11-21 16:09:11 -05:00
if ( a_Xp_delta > ( SHRT_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
2013-11-21 16:09:11 -05:00
LOGWARNING ( " Attempt was made to increment Xp by %d, which overflowed the short datatype. Ignoring. " ,
2013-11-13 15:02:53 -05:00
a_Xp_delta ) ;
2013-11-16 04:29:57 -05: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
2013-11-21 16:09:11 -05:00
if ( m_CurrentXp < 0 )
2013-11-16 06:43:42 -05:00
{
2013-11-21 16:09:11 -05:00
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
}
2013-11-16 08:13:53 -05:00
LOGD ( " Player \" %s \" gained/lost %d experience, total is now: %d " ,
m_PlayerName . 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 )
{
LOGD ( " Player \" %s \" started charging their bow " , m_PlayerName . c_str ( ) ) ;
m_IsChargingBow = true ;
m_BowCharge = 0 ;
}
int cPlayer : : FinishChargingBow ( void )
{
LOGD ( " Player \" %s \" finished charging their bow at a charge of %d " , m_PlayerName . c_str ( ) , m_BowCharge ) ;
int res = m_BowCharge ;
m_IsChargingBow = false ;
m_BowCharge = 0 ;
return res ;
}
void cPlayer : : CancelChargingBow ( void )
{
LOGD ( " Player \" %s \" cancelled charging their bow at a charge of %d " , m_PlayerName . c_str ( ) , m_BowCharge ) ;
m_IsChargingBow = false ;
m_BowCharge = 0 ;
}
2013-07-29 07:13:03 -04:00
void cPlayer : : SetTouchGround ( bool a_bTouchGround )
{
m_bTouchGround = a_bTouchGround ;
if ( ! m_bTouchGround )
{
if ( GetPosY ( ) > m_LastJumpHeight )
{
m_LastJumpHeight = ( float ) GetPosY ( ) ;
}
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-01-25 14:02:13 -05:00
BLOCKTYPE BlockType = World - > GetBlock ( ( int ) floor ( GetPosX ( ) ) , ( int ) floor ( GetPosY ( ) ) , ( int ) floor ( GetPosZ ( ) ) ) ;
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
{
m_LastGroundHeight = ( float ) GetPosY ( ) ;
}
}
}
2013-11-02 10:08:00 -04:00
else
2013-07-29 07:13:03 -04:00
{
float Dist = ( float ) ( m_LastGroundHeight - floor ( GetPosY ( ) ) ) ;
int Damage = ( int ) ( Dist - 3.f ) ;
2013-11-02 10:08:00 -04:00
if ( m_LastJumpHeight > m_LastGroundHeight ) Damage + + ;
2013-07-29 07:13:03 -04:00
m_LastJumpHeight = ( 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
TakeDamage ( dtFalling , NULL , Damage , Damage , 0 ) ;
2013-12-22 15:03:09 -05:00
2014-01-25 14:02:13 -05:00
// Fall particles
2013-12-23 16:18:01 -05:00
GetWorld ( ) - > BroadcastSoundParticleEffect ( 2006 , ( int ) floor ( GetPosX ( ) ) , ( int ) GetPosY ( ) - 1 , ( int ) floor ( GetPosZ ( ) ) , Damage /* Used as particle effect speed modifier */ ) ;
2013-12-22 15:03:09 -05:00
}
2013-07-29 07:13:03 -04:00
m_LastGroundHeight = ( float ) GetPosY ( ) ;
}
}
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 )
{
m_FoodLevel = std : : max ( 0 , std : : min ( a_FoodLevel , ( int ) MAX_FOOD_LEVEL ) ) ;
SendHealth ( ) ;
}
void cPlayer : : SetFoodSaturationLevel ( double a_FoodSaturationLevel )
{
m_FoodSaturationLevel = std : : max ( 0.0 , std : : min ( a_FoodSaturationLevel , ( double ) m_FoodLevel ) ) ;
}
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
{
2013-08-19 16:48:13 -04:00
m_FoodExhaustionLevel = std : : max ( 0.0 , std : : min ( a_FoodExhaustionLevel , 4.0 ) ) ;
2013-07-29 07:13:03 -04:00
}
void cPlayer : : SetFoodPoisonedTicksRemaining ( int a_FoodPoisonedTicksRemaining )
{
m_FoodPoisonedTicksRemaining = a_FoodPoisonedTicksRemaining ;
}
bool cPlayer : : Feed ( int a_Food , double a_Saturation )
{
if ( m_FoodLevel > = MAX_FOOD_LEVEL )
{
return false ;
}
m_FoodLevel = std : : min ( a_Food + m_FoodLevel , ( int ) MAX_FOOD_LEVEL ) ;
m_FoodSaturationLevel = std : : min ( m_FoodSaturationLevel + a_Saturation , ( double ) m_FoodLevel ) ;
SendHealth ( ) ;
return true ;
}
void cPlayer : : FoodPoison ( int a_NumTicks )
{
bool HasBeenFoodPoisoned = ( m_FoodPoisonedTicksRemaining > 0 ) ;
m_FoodPoisonedTicksRemaining = std : : max ( m_FoodPoisonedTicksRemaining , a_NumTicks ) ;
if ( ! HasBeenFoodPoisoned )
{
2013-12-15 04:52:54 -05:00
m_World - > BroadcastRemoveEntityEffect ( * this , E_EFFECT_HUNGER ) ;
2013-07-29 07:13:03 -04:00
SendHealth ( ) ;
}
2013-12-14 12:03:00 -05:00
else
{
2013-12-15 04:52:54 -05:00
m_World - > BroadcastEntityEffect ( * this , E_EFFECT_HUNGER , 0 , 400 ) ; // Give the player the "Hunger" effect for 20 seconds.
2013-12-14 12:03:00 -05:00
}
2013-07-29 07:13:03 -04:00
}
void cPlayer : : StartEating ( void )
{
// Set the timer:
m_EatingFinishTick = m_World - > GetWorldAge ( ) + EATING_TICKS ;
// 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 ;
// Send the packets:
m_ClientHandle - > SendEntityStatus ( * this , ENTITY_STATUS_EATING_ACCEPTED ) ;
2013-12-06 18:47:07 -05:00
m_World - > BroadcastEntityAnimation ( * this , 0 ) ;
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 ) ;
2013-08-08 11:32:14 -04:00
2013-07-29 07:13:03 -04:00
GetInventory ( ) . RemoveOneEquippedItem ( ) ;
2013-08-08 11:32:14 -04:00
//if the food is mushroom soup, return a bowl to the inventory
if ( Item . m_ItemType = = E_ITEM_MUSHROOM_SOUP ) {
cItem emptyBowl ( E_ITEM_BOWL , 1 , 0 , " " ) ;
GetInventory ( ) . AddItem ( emptyBowl , true , true ) ;
}
2013-07-29 07:13:03 -04:00
}
void cPlayer : : AbortEating ( void )
{
m_EatingFinishTick = - 1 ;
2013-12-06 18:47:07 -05:00
m_World - > BroadcastEntityAnimation ( * this , 0 ) ;
2013-07-29 07:13:03 -04:00
m_World - > BroadcastEntityMetadata ( * this ) ;
}
void cPlayer : : SendHealth ( void )
{
if ( m_ClientHandle ! = NULL )
{
m_ClientHandle - > SendHealth ( ) ;
}
}
2013-11-15 10:23:50 -05:00
void cPlayer : : SendExperience ( void )
{
if ( m_ClientHandle ! = NULL )
{
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
{
return m_IsSprinting ? m_SprintingMaxSpeed : m_NormalMaxSpeed ;
}
void cPlayer : : SetNormalMaxSpeed ( double a_Speed )
{
m_NormalMaxSpeed = a_Speed ;
if ( ! m_IsSprinting )
{
m_ClientHandle - > SendPlayerMaxSpeed ( ) ;
}
}
void cPlayer : : SetSprintingMaxSpeed ( double a_Speed )
{
m_SprintingMaxSpeed = a_Speed ;
if ( m_IsSprinting )
{
m_ClientHandle - > SendPlayerMaxSpeed ( ) ;
}
}
void cPlayer : : SetCrouch ( bool a_IsCrouched )
{
// Set the crouch status, broadcast to all visible players
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 ;
}
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 ( ) ;
}
void cPlayer : : SetFlying ( bool a_IsFlying )
{
if ( a_IsFlying = = m_IsFlying )
{
return ;
}
m_IsFlying = a_IsFlying ;
m_ClientHandle - > SendPlayerAbilities ( ) ;
}
2013-07-29 07:13:03 -04:00
void cPlayer : : DoTakeDamage ( TakeDamageInfo & a_TDI )
{
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
{
2013-09-11 13:50:14 -04:00
if ( IsGameModeCreative ( ) )
2013-09-10 18:02:35 -04:00
{
2014-02-02 07:47:17 -05:00
// No damage / health in creative mode if not void or plugin damage
2013-09-10 18:02:35 -04:00
return ;
}
2013-07-29 07:13:03 -04:00
}
2014-01-19 07:20:57 -05:00
if ( ( a_TDI . Attacker ! = NULL ) & & ( a_TDI . Attacker - > IsPlayer ( ) ) )
{
cPlayer * Attacker = ( cPlayer * ) a_TDI . Attacker ;
if ( ( m_Team ! = NULL ) & & ( m_Team = = Attacker - > m_Team ) )
{
2014-01-19 09:02:37 -05:00
if ( ! m_Team - > AllowsFriendlyFire ( ) )
2014-01-19 07:20:57 -05:00
{
// Friendly fire is disabled
return ;
}
}
}
2013-07-29 07:13:03 -04:00
super : : DoTakeDamage ( a_TDI ) ;
// Any kind of damage adds food exhaustion
AddFoodExhaustion ( 0.3f ) ;
SendHealth ( ) ;
}
void cPlayer : : KilledBy ( cEntity * a_Killer )
{
super : : KilledBy ( a_Killer ) ;
if ( m_Health > 0 )
{
return ; // not dead yet =]
}
m_bVisible = false ; // So new clients don't see the player
// Puke out all the items
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 ) ) ;
}
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
if ( a_Killer = = NULL )
{
2014-02-05 18:24:16 -05:00
GetWorld ( ) - > BroadcastChatDeath ( Printf ( " %s was killed by environmental damage " , GetName ( ) . c_str ( ) ) ) ;
2013-12-26 09:55:19 -05:00
}
else if ( a_Killer - > IsPlayer ( ) )
{
2014-02-05 18:24:16 -05:00
GetWorld ( ) - > BroadcastChatDeath ( Printf ( " %s was killed by %s " , GetName ( ) . c_str ( ) , ( ( cPlayer * ) a_Killer ) - > GetName ( ) . c_str ( ) ) ) ;
2013-12-26 09:55:19 -05:00
}
else
{
AString KillerClass = a_Killer - > GetClass ( ) ;
KillerClass . erase ( KillerClass . begin ( ) ) ; // Erase the 'c' of the class (e.g. "cWitch" -> "Witch")
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
class cIncrementCounterCB
: public cObjectiveCallback
{
AString m_Name ;
public :
cIncrementCounterCB ( const AString & a_Name ) : m_Name ( a_Name ) { }
virtual bool Item ( cObjective * a_Objective ) override
{
a_Objective - > AddScore ( m_Name , 1 ) ;
2014-01-22 10:58:25 -05:00
return true ;
2014-01-19 07:20:57 -05:00
}
} IncrementCounter ( GetName ( ) ) ;
2014-01-21 08:58:17 -05:00
cScoreboard & Scoreboard = m_World - > GetScoreBoard ( ) ;
2014-01-19 07:20:57 -05:00
// Update scoreboard objectives
2014-01-21 08:58:17 -05:00
Scoreboard . ForEachObjectiveWith ( cObjective : : E_TYPE_DEATH_COUNT , IncrementCounter ) ;
2013-07-29 07:13:03 -04:00
}
void cPlayer : : Respawn ( void )
{
m_Health = GetMaxHealth ( ) ;
// Reset food level:
2013-08-01 03:51:25 -04:00
m_FoodLevel = MAX_FOOD_LEVEL ;
2013-07-29 07:13:03 -04:00
m_FoodSaturationLevel = 5 ;
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?
2013-07-29 07:13:03 -04:00
m_ClientHandle - > SendRespawn ( ) ;
// Extinguish the fire:
StopBurning ( ) ;
TeleportToCoords ( GetWorld ( ) - > GetSpawnX ( ) , GetWorld ( ) - > GetSpawnY ( ) , GetWorld ( ) - > GetSpawnZ ( ) ) ;
SetVisible ( true ) ;
}
double cPlayer : : GetEyeHeight ( void ) const
{
return m_Stance ;
}
Vector3d cPlayer : : GetEyePosition ( void ) const
{
return Vector3d ( GetPosX ( ) , m_Stance , GetPosZ ( ) ) ;
}
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-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-01-21 08:58:17 -05:00
if ( m_World = = NULL )
{
SetTeam ( NULL ) ;
}
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 )
{
if ( m_CurrentWindow = = NULL )
{
m_CurrentWindow = m_InventoryWindow ;
return ;
}
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 )
{
if ( ( m_CurrentWindow = = NULL ) | | ( m_CurrentWindow - > GetWindowID ( ) ! = a_WindowID ) )
{
return ;
}
CloseWindow ( ) ;
}
void cPlayer : : SetLastBlockActionTime ( )
{
if ( m_World ! = NULL )
{
m_LastBlockActionTime = m_World - > GetWorldAge ( ) / 20.0f ;
}
}
void cPlayer : : SetLastBlockActionCnt ( int a_LastBlockActionCnt )
{
m_LastBlockActionCnt = a_LastBlockActionCnt ;
}
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 ;
}
if ( m_GameMode = = a_GameMode )
{
// Gamemode already set
return ;
}
m_GameMode = a_GameMode ;
m_ClientHandle - > SendGameMode ( a_GameMode ) ;
2013-12-22 15:03:09 -05:00
2013-12-24 18:47:04 -05:00
if ( ! IsGameModeCreative ( ) )
2013-12-23 04:51:41 -05:00
{
SetFlying ( false ) ;
SetCanFly ( false ) ;
}
2013-07-29 07:13:03 -04:00
}
void cPlayer : : LoginSetGameMode ( eGameMode a_GameMode )
{
m_GameMode = a_GameMode ;
}
void cPlayer : : SetIP ( const AString & a_IP )
{
m_IP = a_IP ;
}
void cPlayer : : TeleportToCoords ( double a_PosX , double a_PosY , double a_PosZ )
{
2014-02-11 17:09:56 -05:00
SetPosition ( a_PosX , a_PosY , a_PosZ ) ;
2014-02-12 16:53:46 -05:00
m_LastGroundHeight = ( float ) a_PosY ;
m_LastJumpHeight = ( float ) a_PosY ;
2013-07-29 07:13:03 -04:00
m_World - > BroadcastTeleportEntity ( * this , GetClientHandle ( ) ) ;
m_ClientHandle - > SendPlayerMoveLook ( ) ;
}
2013-08-30 11:29:46 -04:00
Vector3d cPlayer : : GetThrowStartPos ( void ) const
{
Vector3d res = GetEyePosition ( ) ;
// 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 ( ) ) ;
return res ;
}
Vector3d cPlayer : : GetThrowSpeed ( double a_SpeedCoeff ) const
{
Vector3d res = GetLookVector ( ) ;
res . Normalize ( ) ;
// TODO: Add a slight random change (+-0.0075 in each direction)
return res * a_SpeedCoeff ;
}
2013-12-15 14:19:58 -05:00
void cPlayer : : ForceSetSpeed ( Vector3d a_Direction )
2013-12-15 12:54:54 -05:00
{
SetSpeed ( a_Direction ) ;
m_ClientHandle - > SendEntityVelocity ( * this ) ;
}
2013-07-29 07:13:03 -04:00
void cPlayer : : MoveTo ( const Vector3d & a_NewPos )
{
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
// We cannot test m_AttachedTo, because when deattaching, the server thinks the client is already deattached while
// the client may still send more of these nonsensical packets.
if ( m_AttachedTo ! = NULL )
{
Vector3d AddSpeed ( a_NewPos ) ;
AddSpeed . y = 0 ;
m_AttachedTo - > AddSpeed ( AddSpeed ) ;
}
return ;
}
// TODO: should do some checks to see if player is not moving through terrain
// TODO: Official server refuses position packets too far away from each other, kicking "hacked" clients; we should, too
SetPosition ( a_NewPos ) ;
SetStance ( a_NewPos . y + 1.62 ) ;
}
void cPlayer : : SetVisible ( bool a_bVisible )
{
if ( a_bVisible & & ! m_bVisible ) // Make visible
{
m_bVisible = true ;
m_World - > BroadcastSpawnEntity ( * this ) ;
}
if ( ! a_bVisible & & m_bVisible )
{
m_bVisible = false ;
m_World - > BroadcastDestroyEntity ( * this , m_ClientHandle ) ; // Destroy on all clients
}
}
void cPlayer : : AddToGroup ( const AString & a_GroupName )
{
cGroup * Group = cRoot : : Get ( ) - > GetGroupManager ( ) - > GetGroup ( a_GroupName ) ;
m_Groups . push_back ( Group ) ;
LOGD ( " Added %s to group %s " , m_PlayerName . c_str ( ) , a_GroupName . c_str ( ) ) ;
ResolveGroups ( ) ;
ResolvePermissions ( ) ;
}
void cPlayer : : RemoveFromGroup ( const AString & a_GroupName )
{
bool bRemoved = false ;
for ( GroupList : : iterator itr = m_Groups . begin ( ) ; itr ! = m_Groups . end ( ) ; + + itr )
{
if ( ( * itr ) - > GetName ( ) . compare ( a_GroupName ) = = 0 )
{
m_Groups . erase ( itr ) ;
bRemoved = true ;
break ;
}
}
if ( bRemoved )
{
LOGD ( " Removed %s from group %s " , m_PlayerName . c_str ( ) , a_GroupName . c_str ( ) ) ;
ResolveGroups ( ) ;
ResolvePermissions ( ) ;
}
else
{
LOGWARN ( " Tried to remove %s from group %s but was not in that group " , m_PlayerName . c_str ( ) , a_GroupName . c_str ( ) ) ;
}
}
bool cPlayer : : HasPermission ( const AString & a_Permission )
{
if ( a_Permission . empty ( ) )
{
// Empty permission request is always granted
return true ;
}
AStringVector Split = StringSplit ( a_Permission , " . " ) ;
PermissionMap Possibilities = m_ResolvedPermissions ;
// Now search the namespaces
while ( Possibilities . begin ( ) ! = Possibilities . end ( ) )
{
PermissionMap : : iterator itr = Possibilities . begin ( ) ;
if ( itr - > second )
{
AStringVector OtherSplit = StringSplit ( itr - > first , " . " ) ;
if ( OtherSplit . size ( ) < = Split . size ( ) )
{
unsigned int i ;
for ( i = 0 ; i < OtherSplit . size ( ) ; + + i )
{
if ( OtherSplit [ i ] . compare ( Split [ i ] ) ! = 0 )
{
if ( OtherSplit [ i ] . compare ( " * " ) = = 0 ) return true ; // WildCard man!! WildCard!
break ;
}
}
if ( i = = Split . size ( ) ) return true ;
}
}
Possibilities . erase ( itr ) ;
}
// Nothing that matched :(
return false ;
}
bool cPlayer : : IsInGroup ( const AString & a_Group )
{
for ( GroupList : : iterator itr = m_ResolvedGroups . begin ( ) ; itr ! = m_ResolvedGroups . end ( ) ; + + itr )
{
if ( a_Group . compare ( ( * itr ) - > GetName ( ) . c_str ( ) ) = = 0 )
return true ;
}
return false ;
}
void cPlayer : : ResolvePermissions ( )
{
m_ResolvedPermissions . clear ( ) ; // Start with an empty map yo~
// Copy all player specific permissions into the resolved permissions map
for ( PermissionMap : : iterator itr = m_Permissions . begin ( ) ; itr ! = m_Permissions . end ( ) ; + + itr )
{
m_ResolvedPermissions [ itr - > first ] = itr - > second ;
}
for ( GroupList : : iterator GroupItr = m_ResolvedGroups . begin ( ) ; GroupItr ! = m_ResolvedGroups . end ( ) ; + + GroupItr )
{
const cGroup : : PermissionMap & Permissions = ( * GroupItr ) - > GetPermissions ( ) ;
for ( cGroup : : PermissionMap : : const_iterator itr = Permissions . begin ( ) ; itr ! = Permissions . end ( ) ; + + itr )
{
m_ResolvedPermissions [ itr - > first ] = itr - > second ;
}
}
}
void cPlayer : : ResolveGroups ( )
{
// Clear resolved groups first
m_ResolvedGroups . clear ( ) ;
// Get a complete resolved list of all groups the player is in
std : : map < cGroup * , bool > AllGroups ; // Use a map, because it's faster than iterating through a list to find duplicates
GroupList ToIterate ;
for ( GroupList : : iterator GroupItr = m_Groups . begin ( ) ; GroupItr ! = m_Groups . end ( ) ; + + GroupItr )
{
ToIterate . push_back ( * GroupItr ) ;
}
while ( ToIterate . begin ( ) ! = ToIterate . end ( ) )
{
cGroup * CurrentGroup = * ToIterate . begin ( ) ;
if ( AllGroups . find ( CurrentGroup ) ! = AllGroups . end ( ) )
{
LOGWARNING ( " ERROR: Player \" %s \" is in the group multiple times ( \" %s \" ). Please fix your settings in users.ini! " ,
m_PlayerName . c_str ( ) , CurrentGroup - > GetName ( ) . c_str ( )
) ;
}
else
{
AllGroups [ CurrentGroup ] = true ;
m_ResolvedGroups . push_back ( CurrentGroup ) ; // Add group to resolved list
const cGroup : : GroupList & Inherits = CurrentGroup - > GetInherits ( ) ;
for ( cGroup : : GroupList : : const_iterator itr = Inherits . begin ( ) ; itr ! = Inherits . end ( ) ; + + itr )
{
if ( AllGroups . find ( * itr ) ! = AllGroups . end ( ) )
{
LOGERROR ( " ERROR: Player %s is in the same group multiple times due to inheritance (%s). FIX IT! " , m_PlayerName . c_str ( ) , ( * itr ) - > GetName ( ) . c_str ( ) ) ;
continue ;
}
ToIterate . push_back ( * itr ) ;
}
}
ToIterate . erase ( ToIterate . begin ( ) ) ;
}
}
AString cPlayer : : GetColor ( void ) const
{
if ( m_Color ! = ' - ' )
{
2013-12-24 05:30:36 -05:00
return cChatColor : : Color + m_Color ;
2013-07-29 07:13:03 -04:00
}
if ( m_Groups . size ( ) < 1 )
{
return cChatColor : : White ;
}
return ( * m_Groups . begin ( ) ) - > GetColor ( ) ;
}
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 )
{
NewAmount = GetInventory ( ) . GetEquippedItem ( ) . m_ItemCount ; // Drop only what's there
}
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
double vX = 0 , vY = 0 , vZ = 0 ;
EulerToVector ( - GetYaw ( ) , GetPitch ( ) , vZ , vX , vY ) ;
vY = - vY * 2 + 1.f ;
m_World - > SpawnItemPickups ( Drops , GetPosX ( ) , GetEyeHeight ( ) , GetPosZ ( ) , vX * 3 , vY * 3 , vZ * 3 , true ) ; // 'true' because created by player
}
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 ) ;
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
double vX = 0 , vY = 0 , vZ = 0 ;
EulerToVector ( - GetYaw ( ) , GetPitch ( ) , vZ , vX , vY ) ;
2014-01-23 02:27:39 -05:00
vY = - vY * 2 + 1.f ;
m_World - > SpawnItemPickups ( Drops , GetPosX ( ) , GetEyeHeight ( ) , GetPosZ ( ) , vX * 3 , vY * 3 , vZ * 3 , true ) ; // 'true' because created by player
}
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
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 ;
2013-12-06 15:41:58 -05:00
m_World - > SpawnItemPickups ( Drops , GetPosX ( ) , GetEyeHeight ( ) , GetPosZ ( ) , vX * 3 , vY * 3 , vZ * 3 , true ) ; // 'true' because created by player
2013-07-29 07:13:03 -04:00
}
bool cPlayer : : MoveToWorld ( const char * a_WorldName )
{
cWorld * World = cRoot : : Get ( ) - > GetWorld ( a_WorldName ) ;
if ( World = = NULL )
{
LOG ( " %s: Couldn't find world \" %s \" . " , __FUNCTION__ , a_WorldName ) ;
return false ;
}
eDimension OldDimension = m_World - > GetDimension ( ) ;
// Remove all links to the old world
m_World - > RemovePlayer ( this ) ;
m_ClientHandle - > RemoveFromAllChunks ( ) ;
m_World - > RemoveEntity ( this ) ;
2013-08-14 04:24:34 -04:00
// If the dimension is different, we can send the respawn packet
// http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02
m_ClientHandle - > MoveToWorld ( * World , ( OldDimension ! = World - > GetDimension ( ) ) ) ;
2013-07-29 07:13:03 -04:00
// Add player to all the necessary parts of the new world
SetWorld ( World ) ;
World - > AddEntity ( this ) ;
World - > AddPlayer ( this ) ;
return true ;
}
void cPlayer : : LoadPermissionsFromDisk ( )
{
m_Groups . clear ( ) ;
m_Permissions . clear ( ) ;
2013-10-25 05:15:44 -04:00
cIniFile IniFile ;
if ( IniFile . ReadFile ( " users.ini " ) )
2013-07-29 07:13:03 -04:00
{
std : : string Groups = IniFile . GetValue ( m_PlayerName , " Groups " , " " ) ;
2013-10-25 05:15:44 -04:00
if ( ! Groups . empty ( ) )
2013-07-29 07:13:03 -04:00
{
AStringVector Split = StringSplit ( Groups , " , " ) ;
for ( unsigned int i = 0 ; i < Split . size ( ) ; i + + )
{
AddToGroup ( Split [ i ] . c_str ( ) ) ;
}
}
else
{
AddToGroup ( " Default " ) ;
}
m_Color = IniFile . GetValue ( m_PlayerName , " Color " , " - " ) [ 0 ] ;
}
else
{
2013-12-13 21:02:26 -05:00
LOGWARN ( " Regenerating users.ini, player %s will be added to the \" Default \" group " , m_PlayerName . c_str ( ) ) ;
IniFile . AddHeaderComment ( " This is the file in which the group the player belongs to is stored " ) ;
IniFile . AddHeaderComment ( " The format is: [PlayerName] | Groups=GroupName " ) ;
IniFile . SetValue ( m_PlayerName , " Groups " , " Default " ) ;
IniFile . WriteFile ( " users.ini " ) ;
2013-07-29 07:13:03 -04:00
AddToGroup ( " Default " ) ;
}
ResolvePermissions ( ) ;
}
bool cPlayer : : LoadFromDisk ( )
{
LoadPermissionsFromDisk ( ) ;
// Log player permissions, cause it's what the cool kids do
LOGINFO ( " Player %s has permissions: " , m_PlayerName . c_str ( ) ) ;
for ( PermissionMap : : iterator itr = m_ResolvedPermissions . begin ( ) ; itr ! = m_ResolvedPermissions . end ( ) ; + + itr )
{
2013-12-13 21:54:08 -05:00
if ( itr - > second ) LOG ( " - %s " , itr - > first . c_str ( ) ) ;
2013-07-29 07:13:03 -04:00
}
AString SourceFile ;
Printf ( SourceFile , " players/%s.json " , m_PlayerName . c_str ( ) ) ;
cFile f ;
if ( ! f . Open ( SourceFile , cFile : : fmRead ) )
{
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 ( ) )
{
LOGWARNING ( " Cannot read player data from file \" %s \" " , SourceFile . c_str ( ) ) ;
return false ;
}
2013-11-13 08:50:47 -05:00
f . Close ( ) ; //cool kids play nice
2013-07-29 07:13:03 -04:00
Json : : Value root ;
Json : : Reader reader ;
if ( ! reader . parse ( buffer , root , false ) )
{
LOGWARNING ( " Cannot parse player data in file \" %s \" , player will be reset " , SourceFile . c_str ( ) ) ;
}
Json : : Value & JSON_PlayerPosition = root [ " position " ] ;
if ( JSON_PlayerPosition . size ( ) = = 3 )
{
SetPosX ( JSON_PlayerPosition [ ( unsigned int ) 0 ] . asDouble ( ) ) ;
SetPosY ( JSON_PlayerPosition [ ( unsigned int ) 1 ] . asDouble ( ) ) ;
SetPosZ ( JSON_PlayerPosition [ ( unsigned int ) 2 ] . asDouble ( ) ) ;
m_LastPosX = GetPosX ( ) ;
m_LastPosY = GetPosY ( ) ;
m_LastPosZ = GetPosZ ( ) ;
m_LastFoodPos = GetPosition ( ) ;
}
Json : : Value & JSON_PlayerRotation = root [ " rotation " ] ;
if ( JSON_PlayerRotation . size ( ) = = 3 )
{
2014-01-16 14:00:49 -05:00
SetYaw ( ( float ) JSON_PlayerRotation [ ( unsigned int ) 0 ] . asDouble ( ) ) ;
2013-07-29 07:13:03 -04:00
SetPitch ( ( float ) JSON_PlayerRotation [ ( unsigned int ) 1 ] . asDouble ( ) ) ;
SetRoll ( ( float ) JSON_PlayerRotation [ ( unsigned int ) 2 ] . asDouble ( ) ) ;
}
2013-11-15 10:23:50 -05: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 ( ) ;
2013-11-16 05:38:57 -05:00
m_LifetimeTotalXp = ( short ) root . get ( " xpTotal " , 0 ) . asInt ( ) ;
m_CurrentXp = ( short ) root . get ( " xpCurrent " , 0 ) . asInt ( ) ;
2013-12-15 15:25:13 -05:00
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 ;
}
2013-07-29 07:13:03 -04:00
m_Inventory . LoadFromJson ( root [ " inventory " ] ) ;
m_LoadedWorldName = root . get ( " world " , " world " ) . asString ( ) ;
LOGD ( " Player \" %s \" was read from file, spawning at {%.2f, %.2f, %.2f} in world \" %s \" " ,
m_PlayerName . c_str ( ) , GetPosX ( ) , GetPosY ( ) , GetPosZ ( ) , m_LoadedWorldName . c_str ( )
) ;
return true ;
}
bool cPlayer : : SaveToDisk ( )
{
2013-10-09 03:57:48 -04:00
cFile : : CreateFolder ( FILE_IO_PREFIX + AString ( " players " ) ) ;
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 ) ;
Json : : Value root ;
root [ " position " ] = JSON_PlayerPosition ;
root [ " rotation " ] = JSON_PlayerRotation ;
root [ " inventory " ] = JSON_Inventory ;
root [ " health " ] = m_Health ;
2013-11-16 05:38:57 -05:00
root [ " xpTotal " ] = m_LifetimeTotalXp ;
root [ " xpCurrent " ] = m_CurrentXp ;
2013-08-08 05:57:36 -04:00
root [ " air " ] = m_AirLevel ;
2013-07-29 07:13:03 -04:00
root [ " food " ] = m_FoodLevel ;
root [ " foodSaturation " ] = m_FoodSaturationLevel ;
root [ " foodTickTimer " ] = m_FoodTickTimer ;
root [ " foodExhaustion " ] = m_FoodExhaustionLevel ;
2013-12-15 15:25:13 -05:00
root [ " world " ] = GetWorld ( ) - > GetName ( ) ;
root [ " isflying " ] = IsFlying ( ) ;
2013-07-29 07:13:03 -04:00
if ( m_GameMode = = GetWorld ( ) - > GetGameMode ( ) )
{
root [ " gamemode " ] = ( int ) eGameMode_NotSet ;
}
else
{
root [ " gamemode " ] = ( int ) m_GameMode ;
}
Json : : StyledWriter writer ;
std : : string JsonData = writer . write ( root ) ;
AString SourceFile ;
Printf ( SourceFile , " players/%s.json " , m_PlayerName . c_str ( ) ) ;
cFile f ;
if ( ! f . Open ( SourceFile , cFile : : fmWrite ) )
{
LOGERROR ( " ERROR WRITING PLAYER \" %s \" TO FILE \" %s \" - cannot open file " , m_PlayerName . c_str ( ) , SourceFile . c_str ( ) ) ;
return false ;
}
if ( f . Write ( JsonData . c_str ( ) , JsonData . size ( ) ) ! = ( int ) JsonData . size ( ) )
{
LOGERROR ( " ERROR WRITING PLAYER JSON TO FILE \" %s \" " , SourceFile . c_str ( ) ) ;
return false ;
}
return true ;
}
cPlayer : : StringList cPlayer : : GetResolvedPermissions ( )
{
StringList Permissions ;
const PermissionMap & ResolvedPermissions = m_ResolvedPermissions ;
for ( PermissionMap : : const_iterator itr = ResolvedPermissions . begin ( ) ; itr ! = ResolvedPermissions . end ( ) ; + + itr )
{
if ( itr - > second ) Permissions . push_back ( itr - > first ) ;
}
return Permissions ;
}
2013-08-14 04:33:26 -04:00
void cPlayer : : UseEquippedItem ( void )
2013-07-29 07:13:03 -04:00
{
2013-11-02 10:08:00 -04:00
if ( IsGameModeCreative ( ) ) // No damage in creative
2013-07-29 07:13:03 -04:00
{
return ;
}
2013-11-02 10:08:00 -04:00
2013-12-06 14:59:14 -05:00
if ( GetInventory ( ) . DamageEquippedItem ( ) )
{
m_World - > BroadcastSoundEffect ( " random.break " , ( int ) GetPosX ( ) * 8 , ( int ) GetPosY ( ) * 8 , ( int ) GetPosZ ( ) * 8 , 0.5f , ( float ) ( 0.75 + ( ( float ) ( ( GetUniqueID ( ) * 23 ) % 32 ) ) / 64 ) ) ;
}
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 )
{
// Don't burn in creative and stop burning in creative if necessary
if ( ! IsGameModeCreative ( ) )
{
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-02-11 09:34:18 -05:00
if ( IsGameModeCreative ( ) )
{
// Hunger is disabled for Creative
return ;
}
2013-07-29 07:13:03 -04:00
// Remember the food level before processing, for later comparison
int LastFoodLevel = m_FoodLevel ;
// Heal or damage, based on the food level, using the m_FoodTickTimer:
if ( ( m_FoodLevel > 17 ) | | ( m_FoodLevel < = 0 ) )
{
m_FoodTickTimer + + ;
if ( m_FoodTickTimer > = 80 )
{
m_FoodTickTimer = 0 ;
if ( m_FoodLevel > = 17 )
{
// Regenerate health from food, incur 3 pts of food exhaustion:
Heal ( 1 ) ;
m_FoodExhaustionLevel + = 3 ;
}
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
TakeDamage ( dtStarving , NULL , 1 , 1 , 0 ) ;
}
}
}
// Apply food poisoning food exhaustion:
if ( m_FoodPoisonedTicksRemaining > 0 )
{
m_FoodPoisonedTicksRemaining - - ;
m_FoodExhaustionLevel + = 0.025 ; // 0.5 per second = 0.025 per tick
}
2013-12-14 12:03:00 -05:00
else
{
2013-12-15 04:52:54 -05:00
m_World - > BroadcastRemoveEntityEffect ( * this , E_EFFECT_HUNGER ) ; // Remove the "Hunger" effect.
2013-12-14 12:03:00 -05:00
}
2013-07-29 07:13:03 -04:00
// Apply food exhaustion that has accumulated:
if ( m_FoodExhaustionLevel > = 4 )
{
m_FoodExhaustionLevel - = 4 ;
if ( m_FoodSaturationLevel > = 1 )
{
m_FoodSaturationLevel - = 1 ;
}
else
{
m_FoodLevel = std : : max ( m_FoodLevel - 1 , 0 ) ;
}
}
if ( m_FoodLevel ! = LastFoodLevel )
{
SendHealth ( ) ;
}
}
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 ) ;
}
2013-08-09 03:50:33 -04:00
void cPlayer : : ApplyFoodExhaustionFromMovement ( )
2013-07-29 07:13:03 -04:00
{
if ( IsGameModeCreative ( ) )
{
return ;
}
// Calculate the distance travelled, update the last pos:
Vector3d Movement ( GetPosition ( ) - m_LastFoodPos ) ;
Movement . y = 0 ; // Only take XZ movement into account
m_LastFoodPos = GetPosition ( ) ;
// If riding anything, apply no food exhaustion
if ( m_AttachedTo ! = NULL )
{
return ;
}
// 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
void cPlayer : : Detach ( )
{
super : : Detach ( ) ;
int PosX = ( int ) floor ( GetPosX ( ) ) ;
int PosY = ( int ) floor ( GetPosY ( ) ) ;
int PosZ = ( int ) floor ( GetPosZ ( ) ) ;
// 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 )
{
if ( ! g_BlockIsSolid [ m_World - > GetBlock ( x , y , z ) ] & & g_BlockIsSolid [ m_World - > GetBlock ( x , y - 1 , z ) ] )
{
TeleportToCoords ( x , y , z ) ;
return ;
}
}
}
}
2014-01-14 13:16:13 -05:00
}