2013-11-14 00:50:47 +11:00
2013-07-29 12:13:03 +01:00
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Player.h"
2013-08-19 11:39:13 +02:00
#include "../Server.h"
#include "../UI/Window.h"
#include "../UI/WindowOwner.h"
#include "../World.h"
2013-12-08 12:17:54 +01:00
#include "../Bindings/PluginManager.h"
2013-08-19 11:39:13 +02:00
#include "../BlockEntities/BlockEntity.h"
#include "../GroupManager.h"
#include "../Group.h"
#include "../Root.h"
#include "../OSSupport/Timer.h"
#include "../Chunk.h"
#include "../Items/ItemHandler.h"
2014-03-11 16:01:17 +02:00
#include "../Vector3.h"
2013-07-29 12:13:03 +01:00
2013-11-27 07:27:19 +00:00
#include "inifile/iniFile.h"
2013-11-27 09:17:25 +01:00
#include "json/json.h"
2013-07-29 12:13:03 +01:00
2013-11-14 00:50:47 +11:00
2013-07-29 12:13:03 +01:00
cPlayer :: cPlayer ( cClientHandle * a_Client , const AString & a_PlayerName )
: super ( etPlayer , 0.6 , 1.8 )
2013-12-20 16:39:20 +01:00
, m_bVisible ( true )
2013-08-01 09:51:25 +02:00
, m_FoodLevel ( MAX_FOOD_LEVEL )
2013-07-29 12:13:03 +01:00
, m_FoodSaturationLevel ( 5 )
, m_FoodTickTimer ( 0 )
, m_FoodExhaustionLevel ( 0 )
, m_FoodPoisonedTicksRemaining ( 0 )
2013-12-20 16:39:20 +01:00
, m_LastJumpHeight ( 0 )
, m_LastGroundHeight ( 0 )
, m_bTouchGround ( false )
, m_Stance ( 0.0 )
, m_Inventory ( * this )
, m_CurrentWindow ( NULL )
, m_InventoryWindow ( NULL )
, m_Color ( '-' )
, m_GameMode ( eGameMode_NotSet )
, m_IP ( "" )
, m_ClientHandle ( a_Client )
2014-03-20 15:45:42 +01:00
, m_NormalMaxSpeed ( 1.0 )
, m_SprintingMaxSpeed ( 1.3 )
2014-03-20 16:14:40 +01:00
, m_FlyingMaxSpeed ( 1.0 )
2013-07-29 12:13:03 +01:00
, m_IsCrouched ( false )
, m_IsSprinting ( false )
2013-12-20 16:39:20 +01:00
, m_IsFlying ( false )
2013-08-09 08:57:24 +01:00
, m_IsSwimming ( false )
, m_IsSubmerged ( false )
2013-12-19 17:33:21 +01:00
, m_IsFishing ( false )
2013-12-20 16:39:20 +01:00
, m_CanFly ( false )
2013-07-29 12:13:03 +01:00
, m_EatingFinishTick ( - 1 )
2013-11-16 21:38:57 +11:00
, m_LifetimeTotalXp ( 0 )
2013-12-20 16:39:20 +01:00
, m_CurrentXp ( 0 )
2013-11-16 21:38:57 +11:00
, m_bDirtyExperience ( false )
2013-12-20 16:39:20 +01:00
, m_IsChargingBow ( false )
, m_BowCharge ( 0 )
, m_FloaterID ( - 1 )
2014-01-19 14:20:57 +02:00
, m_Team ( NULL )
2013-07-29 12:13:03 +01: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 09:51:25 +02:00
SetMaxHealth ( MAX_HEALTH );
m_Health = MAX_HEALTH ;
2013-07-29 12:13:03 +01:00
cTimer t1 ;
m_LastPlayerListTime = t1 . GetNowTime ();
m_TimeLastTeleportPacket = 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 23:58:51 +00:00
2013-07-29 12:13:03 +01:00
m_LastJumpHeight = ( float )( GetPosY ());
m_LastGroundHeight = ( float )( GetPosY ());
m_Stance = GetPosY () + 1.62 ;
2014-01-24 23:58:51 +00: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 10:24:34 +02:00
cRoot :: Get () -> GetServer () -> PlayerCreated ( this );
2013-07-29 12:13:03 +01:00
}
cPlayer ::~ cPlayer ( void )
{
2014-02-03 21:12:44 +00:00
if ( ! cRoot :: Get () -> GetPluginManager () -> CallHookPlayerDestroyed ( * this ))
{
2014-02-05 23:24:16 +00:00
cRoot :: Get () -> BroadcastChatLeave ( Printf ( "%s has left the game" , GetName (). c_str ()));
2014-02-03 22:24:22 +00:00
LOGINFO ( "Player %s has left the game." , GetName (). c_str ());
2014-02-03 21:12:44 +00:00
}
2014-01-25 11:25:22 +01:00
2014-02-03 22:24:22 +00:00
LOGD ( "Deleting cPlayer \" %s \" at %p, ID %d" , GetName (). c_str (), this , GetUniqueID ());
2013-07-29 12:13:03 +01:00
2013-08-14 19:11:54 +02:00
// Notify the server that the player is being destroyed
cRoot :: Get () -> GetServer () -> PlayerDestroying ( this );
2013-07-29 12:13:03 +01:00
SaveToDisk ();
m_World -> RemovePlayer ( this );
m_ClientHandle = NULL ;
delete m_InventoryWindow ;
LOGD ( "Player %p deleted" , this );
}
void cPlayer :: Destroyed ()
{
CloseWindow ( false );
2013-08-13 22:45:29 +02:00
2013-07-29 12:13:03 +01:00
m_ClientHandle = NULL ;
}
void cPlayer :: SpawnOn ( cClientHandle & a_Client )
{
2013-08-13 22:45:29 +02:00
if ( ! m_bVisible || ( m_ClientHandle == ( & a_Client )))
2013-07-29 12:13:03 +01:00
{
2013-08-13 22:45:29 +02:00
return ;
2013-07-29 12:13:03 +01:00
}
2013-08-13 22:45:29 +02:00
a_Client . SendPlayerSpawn ( * this );
a_Client . SendEntityHeadLook ( * this );
a_Client . SendEntityEquipment ( * this , 0 , m_Inventory . GetEquippedItem () );
a_Client . SendEntityEquipment ( * this , 1 , m_Inventory . GetEquippedBoots () );
a_Client . SendEntityEquipment ( * this , 2 , m_Inventory . GetEquippedLeggings () );
a_Client . SendEntityEquipment ( * this , 3 , m_Inventory . GetEquippedChestplate () );
a_Client . SendEntityEquipment ( * this , 4 , m_Inventory . GetEquippedHelmet () );
2013-07-29 12:13:03 +01:00
}
void cPlayer :: Tick ( float a_Dt , cChunk & a_Chunk )
{
2013-08-12 08:35:13 +02:00
if ( m_ClientHandle != NULL )
2013-07-29 12:13:03 +01:00
{
2013-08-13 22:45:29 +02:00
if ( m_ClientHandle -> IsDestroyed ())
{
// This should not happen, because destroying a client will remove it from the world, but just in case
m_ClientHandle = NULL ;
return ;
}
2013-08-12 08:35:13 +02:00
if ( ! m_ClientHandle -> IsPlaying ())
{
// We're not yet in the game, ignore everything
return ;
}
2013-07-29 12:13:03 +01:00
}
2013-08-20 21:17:33 +02:00
if ( ! a_Chunk . IsValid ())
{
// This may happen if the cPlayer is created before the chunks have the chance of being loaded / generated (#83)
return ;
}
2013-07-29 12:13:03 +01:00
super :: Tick ( a_Dt , a_Chunk );
2013-08-09 08:57:24 +01:00
2013-08-30 14:24:03 +02:00
// Handle charging the bow:
if ( m_IsChargingBow )
{
m_BowCharge += 1 ;
}
2013-11-16 20:29:57 +11:00
//handle updating experience
if ( m_bDirtyExperience )
{
SendExperience ();
}
2013-08-08 10:32:34 +01:00
2013-07-29 12:13:03 +01:00
if ( m_bDirtyPosition )
{
// Apply food exhaustion from movement:
2013-08-09 08:50:33 +01:00
ApplyFoodExhaustionFromMovement ();
2013-07-29 12:13:03 +01: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 17:31:05 +01:00
if ( m_IsFishing )
{
HandleFloater ();
}
2014-02-17 16:27:12 +02:00
// Update items (e.g. Maps)
m_Inventory . UpdateItems ();
2013-07-29 12:13:03 +01:00
// Send Player List (Once per m_LastPlayerListTime/1000 ms)
cTimer t1 ;
if ( m_LastPlayerListTime + cPlayer :: PLAYER_LIST_TIME_MS <= t1 . GetNowTime ())
{
m_World -> SendPlayerList ( this );
m_LastPlayerListTime = t1 . GetNowTime ();
}
2013-12-22 20:03:09 +00:00
if ( IsFlying ())
2013-12-25 14:05:37 +00:00
{
2013-12-22 20:03:09 +00:00
m_LastGroundHeight = ( float ) GetPosY ();
2013-12-25 14:05:37 +00:00
}
2013-07-29 12:13:03 +01:00
}
2013-11-15 22:42:09 +11:00
short cPlayer :: CalcLevelFromXp ( short a_XpTotal )
2013-11-14 04:25:47 +11:00
{
//level 0 to 15
if ( a_XpTotal <= XP_TO_LEVEL15 )
{
2013-11-14 07:02:53 +11:00
return a_XpTotal / XP_PER_LEVEL_TO15 ;
2013-11-14 04:25:47 +11:00
}
//level 30+
if ( a_XpTotal > XP_TO_LEVEL30 )
{
2013-11-15 22:42:09 +11:00
return ( short ) ( 151.5 + sqrt ( 22952.25 - ( 14 * ( 2220 - a_XpTotal )))) / 7 ;
2013-11-14 04:25:47 +11:00
}
//level 16 to 30
2013-11-15 22:42:09 +11:00
return ( short ) ( 29.5 + sqrt ( 870.25 - ( 6 * ( 360 - a_XpTotal )))) / 3 ;
2013-11-14 04:25:47 +11:00
}
2013-11-15 22:42:09 +11:00
short cPlayer :: XpForLevel ( short a_Level )
2013-11-14 04:25:47 +11: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 22:42:09 +11:00
return ( short ) ( ( 3.5 * a_Level * a_Level ) - ( 151.5 * a_Level ) + 2220 );
2013-11-14 04:25:47 +11:00
}
//level 16 to 30
2013-11-15 22:42:09 +11:00
return ( short ) ( ( 1.5 * a_Level * a_Level ) - ( 29.5 * a_Level ) + 360 );
2013-11-14 04:25:47 +11:00
}
2013-11-16 20:29:57 +11:00
short cPlayer :: GetXpLevel ()
2013-11-14 04:25:47 +11:00
{
2013-11-16 20:29:57 +11:00
return CalcLevelFromXp ( m_CurrentXp );
2013-11-14 04:25:47 +11:00
}
2013-11-16 20:29:57 +11:00
float cPlayer :: GetXpPercentage ()
2013-11-14 04:25:47 +11:00
{
2013-11-16 20:29:57 +11:00
short int currentLevel = CalcLevelFromXp ( m_CurrentXp );
2013-11-15 22:42:09 +11:00
short int currentLevel_XpBase = XpForLevel ( currentLevel );
2013-11-14 04:25:47 +11:00
2013-11-16 20:29:57 +11:00
return ( float )( m_CurrentXp - currentLevel_XpBase ) /
2013-11-15 01:46:41 +11:00
( float )( XpForLevel ( 1 + currentLevel ) - currentLevel_XpBase );
2013-11-14 04:25:47 +11:00
}
2013-11-16 22:17:46 +11:00
bool cPlayer :: SetCurrentExperience ( short int a_CurrentXp )
2013-11-14 00:50:47 +11:00
{
2013-11-16 22:17:46 +11:00
if ( ! ( a_CurrentXp >= 0 ) || ( a_CurrentXp > ( SHRT_MAX - m_LifetimeTotalXp )))
2013-11-14 00:50:47 +11:00
{
2013-11-16 22:17:46 +11:00
LOGWARNING ( "Tried to update experiece with an invalid Xp value: %d" , a_CurrentXp );
2013-11-14 00:50:47 +11:00
return false ; //oops, they gave us a dodgey number
}
2013-11-16 22:17:46 +11:00
m_CurrentXp = a_CurrentXp ;
2013-11-14 00:50:47 +11:00
2013-11-16 20:29:57 +11:00
// Set experience to be updated
m_bDirtyExperience = true ;
2013-11-15 22:42:09 +11:00
2013-11-14 04:25:47 +11:00
return true ;
2013-11-14 00:50:47 +11:00
}
2013-11-16 22:00:45 +11:00
short cPlayer :: DeltaExperience ( short a_Xp_delta )
2013-11-14 00:50:47 +11:00
{
2013-11-21 22:09:11 +01:00
if ( a_Xp_delta > ( SHRT_MAX - m_CurrentXp ))
2013-11-14 00:50:47 +11:00
{
2013-11-16 22:43:42 +11:00
// Value was bad, abort and report
2013-11-21 22:09:11 +01:00
LOGWARNING ( "Attempt was made to increment Xp by %d, which overflowed the short datatype. Ignoring." ,
2013-11-14 07:02:53 +11:00
a_Xp_delta );
2013-11-16 20:29:57 +11:00
return - 1 ; // Should we instead just return the current Xp?
2013-11-14 00:50:47 +11:00
}
2013-11-16 20:29:57 +11:00
m_CurrentXp += a_Xp_delta ;
2013-11-16 22:43:42 +11:00
// Make sure they didn't subtract too much
2013-11-21 22:09:11 +01:00
if ( m_CurrentXp < 0 )
2013-11-16 22:43:42 +11:00
{
2013-11-21 22:09:11 +01:00
m_CurrentXp = 0 ;
2013-11-16 22:43:42 +11:00
}
2013-11-16 20:29:57 +11:00
// Update total for score calculation
2013-11-21 22:09:11 +01:00
if ( a_Xp_delta > 0 )
2013-11-16 21:38:57 +11:00
{
2013-11-16 22:00:45 +11:00
m_LifetimeTotalXp += a_Xp_delta ;
2013-11-16 21:38:57 +11:00
}
2013-11-17 00:13:53 +11:00
LOGD ( "Player \" %s \" gained/lost %d experience, total is now: %d" ,
m_PlayerName . c_str (), a_Xp_delta , m_CurrentXp );
2013-11-16 21:38:57 +11:00
// Set experience to be updated
m_bDirtyExperience = true ;
return m_CurrentXp ;
}
2013-08-30 14:24:03 +02:00
void cPlayer :: StartChargingBow ( void )
{
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 12:13:03 +01: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 14:08:00 +00:00
if (( GetPosY () >= 0 ) && ( GetPosY () < cChunkDef :: Height ))
2013-07-29 12:13:03 +01:00
{
2014-01-25 19:02:13 +00:00
BLOCKTYPE BlockType = World -> GetBlock (( int ) floor ( GetPosX ()), ( int ) floor ( GetPosY ()), ( int ) floor ( GetPosZ ()));
2013-07-29 12:13:03 +01:00
if ( BlockType != E_BLOCK_AIR )
{
m_bTouchGround = true ;
}
if (
( BlockType == E_BLOCK_WATER ) ||
( BlockType == E_BLOCK_STATIONARY_WATER ) ||
( BlockType == E_BLOCK_LADDER ) ||
( BlockType == E_BLOCK_VINES )
2013-11-02 16:01:40 +00:00
)
2013-07-29 12:13:03 +01:00
{
m_LastGroundHeight = ( float ) GetPosY ();
}
}
}
2013-11-02 14:08:00 +00:00
else
2013-07-29 12:13:03 +01:00
{
float Dist = ( float )( m_LastGroundHeight - floor ( GetPosY ()));
int Damage = ( int )( Dist - 3.f );
2013-11-02 14:08:00 +00:00
if ( m_LastJumpHeight > m_LastGroundHeight ) Damage ++ ;
2013-07-29 12:13:03 +01:00
m_LastJumpHeight = ( float ) GetPosY ();
2013-11-02 14:08:00 +00:00
2013-12-22 20:03:09 +00:00
if ( Damage > 0 )
2013-07-29 12:13:03 +01:00
{
2014-01-25 19:05:44 +00:00
// cPlayer makes sure damage isn't applied in creative, no need to check here
TakeDamage ( dtFalling , NULL , Damage , Damage , 0 );
2013-12-22 20:03:09 +00:00
2014-01-25 19:02:13 +00:00
// Fall particles
2013-12-23 21:18:01 +00:00
GetWorld () -> BroadcastSoundParticleEffect ( 2006 , ( int ) floor ( GetPosX ()), ( int ) GetPosY () - 1 , ( int ) floor ( GetPosZ ()), Damage /* Used as particle effect speed modifier */ );
2013-12-22 20:03:09 +00:00
}
2013-07-29 12:13:03 +01:00
m_LastGroundHeight = ( float ) GetPosY ();
}
}
void cPlayer :: Heal ( int a_Health )
{
2013-10-24 11:05:43 +02:00
super :: Heal ( a_Health );
SendHealth ();
2013-07-29 12:13:03 +01:00
}
void cPlayer :: SetFoodLevel ( int a_FoodLevel )
{
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 22:48:13 +02:00
void cPlayer :: SetFoodExhaustionLevel ( double a_FoodExhaustionLevel )
2013-07-29 12:13:03 +01:00
{
2013-08-19 22:48:13 +02:00
m_FoodExhaustionLevel = std :: max ( 0.0 , std :: min ( a_FoodExhaustionLevel , 4.0 ));
2013-07-29 12:13:03 +01: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 10:52:54 +01:00
m_World -> BroadcastRemoveEntityEffect ( * this , E_EFFECT_HUNGER );
2013-07-29 12:13:03 +01:00
SendHealth ();
}
2013-12-14 18:03:00 +01:00
else
{
2013-12-15 10:52:54 +01:00
m_World -> BroadcastEntityEffect ( * this , E_EFFECT_HUNGER , 0 , 400 ); // Give the player the "Hunger" effect for 20 seconds.
2013-12-14 18:03:00 +01:00
}
2013-07-29 12:13:03 +01:00
}
void cPlayer :: StartEating ( void )
{
// Set the timer:
m_EatingFinishTick = m_World -> GetWorldAge () + EATING_TICKS ;
// Send the packets:
2013-12-06 23:47:07 +00:00
m_World -> BroadcastEntityAnimation ( * this , 3 );
2013-07-29 12:13:03 +01:00
m_World -> BroadcastEntityMetadata ( * this );
}
void cPlayer :: FinishEating ( void )
{
// Reset the timer:
m_EatingFinishTick = - 1 ;
// Send the packets:
m_ClientHandle -> SendEntityStatus ( * this , ENTITY_STATUS_EATING_ACCEPTED );
2013-12-06 23:47:07 +00:00
m_World -> BroadcastEntityAnimation ( * this , 0 );
2013-07-29 12:13:03 +01:00
m_World -> BroadcastEntityMetadata ( * this );
// consume the item:
cItem Item ( GetEquippedItem ());
Item . m_ItemCount = 1 ;
cItemHandler * ItemHandler = cItemHandler :: GetItemHandler ( Item . m_ItemType );
if ( ! ItemHandler -> EatItem ( this , & Item ))
{
return ;
}
ItemHandler -> OnFoodEaten ( m_World , this , & Item );
2013-08-08 16:32:14 +01:00
2013-07-29 12:13:03 +01:00
GetInventory (). RemoveOneEquippedItem ();
2013-08-08 16:32:14 +01: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 12:13:03 +01:00
}
void cPlayer :: AbortEating ( void )
{
m_EatingFinishTick = - 1 ;
2013-12-06 23:47:07 +00:00
m_World -> BroadcastEntityAnimation ( * this , 0 );
2013-07-29 12:13:03 +01:00
m_World -> BroadcastEntityMetadata ( * this );
}
void cPlayer :: SendHealth ( void )
{
if ( m_ClientHandle != NULL )
{
m_ClientHandle -> SendHealth ();
}
}
2013-11-16 02:23:50 +11:00
void cPlayer :: SendExperience ( void )
{
if ( m_ClientHandle != NULL )
{
m_ClientHandle -> SendExperience ();
2013-11-16 20:29:57 +11:00
m_bDirtyExperience = false ;
2013-11-16 02:23:50 +11:00
}
}
2013-07-29 12:13:03 +01:00
void cPlayer :: ClearInventoryPaintSlots ( void )
{
// Clear the list of slots that are being inventory-painted. Used by cWindow only
m_InventoryPaintSlots . clear ();
}
void cPlayer :: AddInventoryPaintSlot ( int a_SlotNum )
{
// Add a slot to the list for inventory painting. Used by cWindow only
m_InventoryPaintSlots . push_back ( a_SlotNum );
}
const cSlotNums & cPlayer :: GetInventoryPaintSlots ( void ) const
{
// Return the list of slots currently stored for inventory painting. Used by cWindow only
return m_InventoryPaintSlots ;
}
double cPlayer :: GetMaxSpeed ( void ) const
{
2014-03-20 16:14:40 +01:00
if ( m_IsFlying )
{
return m_FlyingMaxSpeed ;
}
else
{
if ( m_IsSprinting )
{
return m_SprintingMaxSpeed ;
}
else
{
return m_NormalMaxSpeed ;
}
}
2013-07-29 12:13:03 +01:00
}
void cPlayer :: SetNormalMaxSpeed ( double a_Speed )
{
m_NormalMaxSpeed = a_Speed ;
2014-03-20 16:14:40 +01:00
if ( ! m_IsSprinting && ! m_IsFlying )
2013-07-29 12:13:03 +01:00
{
m_ClientHandle -> SendPlayerMaxSpeed ();
}
}
void cPlayer :: SetSprintingMaxSpeed ( double a_Speed )
{
m_SprintingMaxSpeed = a_Speed ;
2014-03-20 16:14:40 +01:00
if ( m_IsSprinting && ! m_IsFlying )
2013-07-29 12:13:03 +01:00
{
m_ClientHandle -> SendPlayerMaxSpeed ();
}
}
2014-03-20 16:14:40 +01:00
void cPlayer :: SetFlyingMaxSpeed ( double a_Speed )
{
m_FlyingMaxSpeed = a_Speed ;
// Update the flying speed, always:
m_ClientHandle -> SendPlayerAbilities ();
}
2013-07-29 12:13:03 +01:00
void cPlayer :: SetCrouch ( bool a_IsCrouched )
{
// Set the crouch status, broadcast to all visible players
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 14:48:17 +01: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 12:13:03 +01:00
void cPlayer :: DoTakeDamage ( TakeDamageInfo & a_TDI )
{
2014-02-02 12:47:17 +00:00
if (( a_TDI . DamageType != dtInVoid ) && ( a_TDI . DamageType != dtPlugin ))
2013-07-29 12:13:03 +01:00
{
2013-09-11 18:50:14 +01:00
if ( IsGameModeCreative ())
2013-09-10 23:02:35 +01:00
{
2014-02-02 12:47:17 +00:00
// No damage / health in creative mode if not void or plugin damage
2013-09-10 23:02:35 +01:00
return ;
}
2013-07-29 12:13:03 +01:00
}
2014-01-19 14:20:57 +02: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 16:02:37 +02:00
if ( ! m_Team -> AllowsFriendlyFire ())
2014-01-19 14:20:57 +02:00
{
// Friendly fire is disabled
return ;
}
}
}
2013-07-29 12:13:03 +01: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 19:51:05 +01:00
if ( GetName () == "Notch" )
{
Pickups . Add ( cItem ( E_ITEM_RED_APPLE ));
}
2013-07-29 12:13:03 +01:00
m_World -> SpawnItemPickups ( Pickups , GetPosX (), GetPosY (), GetPosZ (), 10 );
SaveToDisk (); // Save it, yeah the world is a tough place !
2013-12-26 14:55:19 +00:00
if ( a_Killer == NULL )
{
2014-02-05 23:24:16 +00:00
GetWorld () -> BroadcastChatDeath ( Printf ( "%s was killed by environmental damage" , GetName (). c_str ()));
2013-12-26 14:55:19 +00:00
}
else if ( a_Killer -> IsPlayer ())
{
2014-02-05 23:24:16 +00:00
GetWorld () -> BroadcastChatDeath ( Printf ( "%s was killed by %s" , GetName (). c_str (), (( cPlayer * ) a_Killer ) -> GetName (). c_str ()));
2014-03-01 12:06:19 +02:00
2014-03-01 14:20:29 +02:00
m_World -> GetScoreBoard (). AddPlayerScore ((( cPlayer * ) a_Killer ) -> GetName (), cObjective :: otPlayerKillCount , 1 );
2013-12-26 14:55:19 +00:00
}
else
{
AString KillerClass = a_Killer -> GetClass ();
KillerClass . erase ( KillerClass . begin ()); // Erase the 'c' of the class (e.g. "cWitch" -> "Witch")
2014-02-05 23:24:16 +00:00
GetWorld () -> BroadcastChatDeath ( Printf ( "%s was killed by a %s" , GetName (). c_str (), KillerClass . c_str ()));
2013-12-26 14:55:19 +00:00
}
2014-01-19 14:20:57 +02:00
2014-03-01 14:20:29 +02:00
m_World -> GetScoreBoard (). AddPlayerScore ( GetName (), cObjective :: otDeathCount , 1 );
2013-07-29 12:13:03 +01:00
}
void cPlayer :: Respawn ( void )
{
m_Health = GetMaxHealth ();
// Reset food level:
2013-08-01 09:51:25 +02:00
m_FoodLevel = MAX_FOOD_LEVEL ;
2013-07-29 12:13:03 +01:00
m_FoodSaturationLevel = 5 ;
2013-11-16 21:38:57 +11:00
// Reset Experience
2013-11-21 22:09:11 +01:00
m_CurrentXp = 0 ;
m_LifetimeTotalXp = 0 ;
2013-11-16 21:38:57 +11:00
// ToDo: send score to client? How?
2013-07-29 12:13:03 +01: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 21:30:20 -07:00
return ( m_GameMode == gmAdventure ) || // Either the player is explicitly in Adventure
(( m_GameMode == gmNotSet ) && m_World -> IsGameModeAdventure ()); // or they inherit from the world and the world is Adventure
2013-07-29 12:13:03 +01:00
}
2014-01-20 16:10:39 +02:00
void cPlayer :: SetTeam ( cTeam * a_Team )
2014-01-19 14:20:57 +02:00
{
2014-01-20 16:10:39 +02:00
if ( m_Team == a_Team )
{
return ;
}
2014-01-19 14:20:57 +02:00
if ( m_Team )
{
2014-01-19 16:02:37 +02:00
m_Team -> RemovePlayer ( GetName ());
2014-01-19 14:20:57 +02:00
}
m_Team = a_Team ;
if ( m_Team )
{
2014-01-19 16:02:37 +02:00
m_Team -> AddPlayer ( GetName ());
2014-01-19 14:20:57 +02:00
}
}
2014-01-20 16:10:39 +02:00
cTeam * cPlayer :: UpdateTeam ( void )
{
2014-01-21 15:58:17 +02:00
if ( m_World == NULL )
{
SetTeam ( NULL );
}
else
{
cScoreboard & Scoreboard = m_World -> GetScoreBoard ();
2014-01-20 16:10:39 +02:00
2014-01-21 15:58:17 +02:00
SetTeam ( Scoreboard . QueryPlayerTeam ( GetName ()));
}
2014-01-20 16:10:39 +02:00
return m_Team ;
}
2013-07-29 12:13:03 +01:00
void cPlayer :: OpenWindow ( cWindow * a_Window )
{
if ( a_Window != m_CurrentWindow )
{
CloseWindow ( false );
}
a_Window -> OpenedByPlayer ( * this );
m_CurrentWindow = a_Window ;
a_Window -> SendWholeWindow ( * GetClientHandle ());
}
void cPlayer :: CloseWindow ( bool a_CanRefuse )
{
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 :: SetGameMode ( eGameMode a_GameMode )
{
2013-07-30 14:48:18 +02:00
if (( a_GameMode < gmMin ) || ( a_GameMode >= gmMax ))
2013-07-29 12:13:03 +01:00
{
LOGWARNING ( "%s: Setting invalid gamemode: %d" , GetName (). c_str (), a_GameMode );
return ;
}
if ( m_GameMode == a_GameMode )
{
// Gamemode already set
return ;
}
m_GameMode = a_GameMode ;
m_ClientHandle -> SendGameMode ( a_GameMode );
2013-12-22 20:03:09 +00:00
2013-12-24 23:47:04 +00:00
if ( ! IsGameModeCreative ())
2013-12-23 09:51:41 +00:00
{
SetFlying ( false );
SetCanFly ( false );
}
2013-07-29 12:13:03 +01: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 22:09:56 +00:00
SetPosition ( a_PosX , a_PosY , a_PosZ );
2013-08-12 02:52:32 -07:00
m_LastGroundHeight = ( float ) a_PosY ;
2014-02-12 21:53:46 +00:00
m_LastJumpHeight = ( float ) a_PosY ;
2013-07-29 12:13:03 +01:00
m_World -> BroadcastTeleportEntity ( * this , GetClientHandle ());
m_ClientHandle -> SendPlayerMoveLook ();
}
2013-08-30 17:29:46 +02: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 20:19:58 +01:00
void cPlayer :: ForceSetSpeed ( Vector3d a_Direction )
2013-12-15 18:54:54 +01:00
{
SetSpeed ( a_Direction );
m_ClientHandle -> SendEntityVelocity ( * this );
}
2013-07-29 12:13:03 +01: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 10:30:36 +00:00
return cChatColor :: Color + m_Color ;
2013-07-29 12:13:03 +01:00
}
if ( m_Groups . size () < 1 )
{
return cChatColor :: White ;
}
return ( * m_Groups . begin ()) -> GetColor ();
}
2014-01-23 00:27:39 -07:00
void cPlayer :: TossEquippedItem ( char a_Amount )
2013-07-29 12:13:03 +01:00
{
cItems Drops ;
2014-01-23 20:11:10 -07:00
cItem DroppedItem ( GetInventory (). GetEquippedItem ());
if ( ! DroppedItem . IsEmpty ())
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
char NewAmount = a_Amount ;
if ( NewAmount > GetInventory (). GetEquippedItem (). m_ItemCount )
{
NewAmount = GetInventory (). GetEquippedItem (). m_ItemCount ; // Drop only what's there
}
2014-01-15 22:36:19 +00:00
2014-01-23 20:11:10 -07:00
GetInventory (). GetHotbarGrid (). ChangeSlotCount ( GetInventory (). GetEquippedSlotNum () /* Returns hotbar subslot, which HotbarGrid takes */ , - a_Amount );
2014-01-23 00:27:39 -07:00
2014-01-23 20:11:10 -07:00
DroppedItem . m_ItemCount = NewAmount ;
Drops . push_back ( DroppedItem );
2013-07-29 12:13:03 +01:00
}
2014-01-23 00:27:39 -07:00
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 20:11:10 -07:00
cItem & Item = GetDraggingItem ();
if ( ! Item . IsEmpty ())
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
char OriginalItemAmount = Item . m_ItemCount ;
Item . m_ItemCount = std :: min ( OriginalItemAmount , a_Amount );
Drops . push_back ( Item );
if ( OriginalItemAmount > a_Amount )
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
Item . m_ItemCount = OriginalItemAmount - a_Amount ;
2013-07-29 12:13:03 +01:00
}
else
{
2014-01-23 20:11:10 -07:00
Item . Empty ();
2013-07-29 12:13:03 +01:00
}
}
2014-01-23 20:11:10 -07:00
double vX = 0 , vY = 0 , vZ = 0 ;
EulerToVector ( - GetYaw (), GetPitch (), vZ , vX , vY );
2014-01-23 00:27:39 -07: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 20:11:10 -07:00
Drops . push_back ( a_Item );
2014-01-15 22:36:19 +00:00
2013-07-29 12:13:03 +01:00
double vX = 0 , vY = 0 , vZ = 0 ;
2014-01-17 11:11:17 +01:00
EulerToVector ( - GetYaw (), GetPitch (), vZ , vX , vY );
2013-07-29 12:13:03 +01:00
vY = - vY * 2 + 1.f ;
2013-12-06 20:41:58 +00:00
m_World -> SpawnItemPickups ( Drops , GetPosX (), GetEyeHeight (), GetPosZ (), vX * 3 , vY * 3 , vZ * 3 , true ); // 'true' because created by player
2013-07-29 12:13:03 +01: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 10:24:34 +02: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 12:13:03 +01: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 11:15:44 +02:00
cIniFile IniFile ;
if ( IniFile . ReadFile ( "users.ini" ))
2013-07-29 12:13:03 +01:00
{
std :: string Groups = IniFile . GetValue ( m_PlayerName , "Groups" , "" );
2013-10-25 11:15:44 +02:00
if ( ! Groups . empty ())
2013-07-29 12:13:03 +01:00
{
2014-02-28 15:31:20 +01:00
AStringVector Split = StringSplitAndTrim ( Groups , "," );
for ( AStringVector :: const_iterator itr = Split . begin (), end = Split . end (); itr != end ; ++ itr )
2013-07-29 12:13:03 +01:00
{
2014-02-28 15:31:20 +01:00
if ( ! cRoot :: Get () -> GetGroupManager () -> ExistsGroup ( * itr ))
2014-02-21 14:53:46 +01:00
{
2014-02-28 15:31:20 +01:00
LOGWARNING ( "The group %s for player %s was not found!" , itr -> c_str (), m_PlayerName . c_str ());
2014-02-21 14:53:46 +01:00
}
2014-02-28 15:31:20 +01:00
AddToGroup ( * itr );
2013-07-29 12:13:03 +01:00
}
}
else
{
AddToGroup ( "Default" );
}
2014-02-28 15:31:20 +01:00
AString Color = IniFile . GetValue ( m_PlayerName , "Color" , "-" );
if ( ! Color . empty ())
{
m_Color = Color [ 0 ];
}
2013-07-29 12:13:03 +01:00
}
else
{
2014-02-28 15:31:20 +01:00
cGroupManager :: GenerateDefaultUsersIni ( IniFile );
2013-07-29 12:13:03 +01: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-14 02:54:08 +00:00
if ( itr -> second ) LOG ( " - %s" , itr -> first . c_str () );
2013-07-29 12:13:03 +01:00
}
AString SourceFile ;
Printf ( SourceFile , "players/%s.json" , m_PlayerName . c_str () );
cFile f ;
if ( ! f . Open ( SourceFile , cFile :: fmRead ))
{
2013-11-14 07:12:16 +11:00
// This is a new player whom we haven't seen yet, bail out, let them have the defaults
2013-07-29 12:13:03 +01:00
return false ;
}
AString buffer ;
if ( f . ReadRestOfFile ( buffer ) != f . GetSize ())
{
LOGWARNING ( "Cannot read player data from file \" %s \" " , SourceFile . c_str ());
return false ;
}
2013-11-14 00:50:47 +11:00
f . Close (); //cool kids play nice
2013-07-29 12:13:03 +01: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 19:00:49 +00:00
SetYaw (( float ) JSON_PlayerRotation [( unsigned int ) 0 ]. asDouble ());
2013-07-29 12:13:03 +01:00
SetPitch (( float ) JSON_PlayerRotation [( unsigned int ) 1 ]. asDouble ());
SetRoll (( float ) JSON_PlayerRotation [( unsigned int ) 2 ]. asDouble ());
}
2013-11-16 02:23:50 +11:00
m_Health = root . get ( "health" , 0 ). asInt ();
2013-08-08 10:32:34 +01:00
m_AirLevel = root . get ( "air" , MAX_AIR_LEVEL ). asInt ();
2013-07-29 12:13:03 +01:00
m_FoodLevel = root . get ( "food" , MAX_FOOD_LEVEL ). asInt ();
m_FoodSaturationLevel = root . get ( "foodSaturation" , MAX_FOOD_LEVEL ). asDouble ();
m_FoodTickTimer = root . get ( "foodTickTimer" , 0 ). asInt ();
m_FoodExhaustionLevel = root . get ( "foodExhaustion" , 0 ). asDouble ();
2013-11-16 21:38:57 +11:00
m_LifetimeTotalXp = ( short ) root . get ( "xpTotal" , 0 ). asInt ();
m_CurrentXp = ( short ) root . get ( "xpCurrent" , 0 ). asInt ();
2013-12-15 21:25:13 +01:00
m_IsFlying = root . get ( "isflying" , 0 ). asBool ();
2013-07-29 12:13:03 +01:00
m_GameMode = ( eGameMode ) root . get ( "gamemode" , eGameMode_NotSet ). asInt ();
2013-12-19 20:53:47 +00:00
if ( m_GameMode == eGameMode_Creative )
{
m_CanFly = true ;
}
2013-07-29 12:13:03 +01: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 09:57:48 +02:00
cFile :: CreateFolder ( FILE_IO_PREFIX + AString ( "players" ));
2013-07-29 12:13:03 +01:00
// create the JSON data
Json :: Value JSON_PlayerPosition ;
JSON_PlayerPosition . append ( Json :: Value ( GetPosX ()));
JSON_PlayerPosition . append ( Json :: Value ( GetPosY ()));
JSON_PlayerPosition . append ( Json :: Value ( GetPosZ ()));
Json :: Value JSON_PlayerRotation ;
2014-01-17 11:11:17 +01:00
JSON_PlayerRotation . append ( Json :: Value ( GetYaw ()));
2013-07-29 12:13:03 +01:00
JSON_PlayerRotation . append ( Json :: Value ( GetPitch ()));
JSON_PlayerRotation . append ( Json :: Value ( GetRoll ()));
Json :: Value JSON_Inventory ;
m_Inventory . SaveToJson ( JSON_Inventory );
Json :: Value root ;
root [ "position" ] = JSON_PlayerPosition ;
root [ "rotation" ] = JSON_PlayerRotation ;
root [ "inventory" ] = JSON_Inventory ;
root [ "health" ] = m_Health ;
2013-11-16 21:38:57 +11:00
root [ "xpTotal" ] = m_LifetimeTotalXp ;
root [ "xpCurrent" ] = m_CurrentXp ;
2013-08-08 10:57:36 +01:00
root [ "air" ] = m_AirLevel ;
2013-07-29 12:13:03 +01:00
root [ "food" ] = m_FoodLevel ;
root [ "foodSaturation" ] = m_FoodSaturationLevel ;
root [ "foodTickTimer" ] = m_FoodTickTimer ;
root [ "foodExhaustion" ] = m_FoodExhaustionLevel ;
2013-12-15 21:25:13 +01:00
root [ "world" ] = GetWorld () -> GetName ();
root [ "isflying" ] = IsFlying ();
2013-07-29 12:13:03 +01: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 10:33:26 +02:00
void cPlayer :: UseEquippedItem ( void )
2013-07-29 12:13:03 +01:00
{
2013-11-02 14:08:00 +00:00
if ( IsGameModeCreative ()) // No damage in creative
2013-07-29 12:13:03 +01:00
{
return ;
}
2013-11-02 14:08:00 +00:00
2013-12-06 19:59:14 +00: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 12:13:03 +01:00
}
2013-08-14 10:33:26 +02:00
2014-02-04 23:27:13 +00:00
void cPlayer :: TickBurning ( cChunk & a_Chunk )
{
// 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 10:33:26 +02:00
2013-07-29 12:13:03 +01:00
void cPlayer :: HandleFood ( void )
{
// Ref.: http://www.minecraftwiki.net/wiki/Hunger
2014-02-11 15:34:18 +01:00
if ( IsGameModeCreative ())
{
// Hunger is disabled for Creative
return ;
}
2013-07-29 12:13:03 +01: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 13:37:36 +00:00
else if (( m_FoodLevel <= 0 ) && ( m_Health > 1 ))
2013-07-29 12:13:03 +01: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 18:03:00 +01:00
else
{
2013-12-15 10:52:54 +01:00
m_World -> BroadcastRemoveEntityEffect ( * this , E_EFFECT_HUNGER ); // Remove the "Hunger" effect.
2013-12-14 18:03:00 +01:00
}
2013-07-29 12:13:03 +01: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 17:31:05 +01:00
void cPlayer :: HandleFloater ()
{
if ( GetEquippedItem (). m_ItemType == E_ITEM_FISHING_ROD )
{
return ;
}
class cFloaterCallback :
public cEntityCallback
{
public :
virtual bool Item ( cEntity * a_Entity ) override
{
a_Entity -> Destroy ( true );
return true ;
}
} Callback ;
m_World -> DoWithEntityByID ( m_FloaterID , Callback );
SetIsFishing ( false );
}
2013-08-09 08:50:33 +01:00
void cPlayer :: ApplyFoodExhaustionFromMovement ()
2013-07-29 12:13:03 +01: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 08:50:33 +01:00
else if ( IsSwimming ())
2013-07-29 12:13:03 +01:00
{
// 0.015 pt per meter swum
BaseExhaustion = BaseExhaustion * 0.015 ;
}
else
{
// 0.01 pt per meter walked / sneaked
BaseExhaustion = BaseExhaustion * 0.01 ;
}
m_FoodExhaustionLevel += BaseExhaustion ;
}
2014-01-12 23:23:36 +00:00
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 )
{
2014-03-01 21:34:19 +02:00
if ( ! cBlockInfo :: IsSolid ( m_World -> GetBlock ( x , y , z )) && cBlockInfo :: IsSolid ( m_World -> GetBlock ( x , y - 1 , z )))
2014-01-12 23:23:36 +00:00
{
TeleportToCoords ( x , y , z );
return ;
}
}
}
}
2014-01-14 18:16:13 +00:00
}