2014-06-16 21:57:23 +02:00
2013-07-29 12:13:03 +01:00
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Player.h"
2018-08-29 01:51:25 +01:00
#include "../Mobs/Wolf.h"
#include "../Mobs/Horse.h"
2016-01-11 21:34:41 +02:00
#include "../BoundingBox.h"
2014-07-17 23:25:14 -07:00
#include "../ChatColor.h"
2013-08-19 11:39:13 +02:00
#include "../Server.h"
2014-12-13 15:06:55 +01:00
#include "../UI/InventoryWindow.h"
2013-08-19 11:39:13 +02:00
#include "../UI/WindowOwner.h"
2013-12-08 12:17:54 +01:00
#include "../Bindings/PluginManager.h"
2013-08-19 11:39:13 +02:00
#include "../BlockEntities/BlockEntity.h"
2014-06-29 11:36:38 +01:00
#include "../BlockEntities/EnderChestEntity.h"
2013-08-19 11:39:13 +02:00
#include "../Root.h"
#include "../Chunk.h"
#include "../Items/ItemHandler.h"
2014-08-19 16:08:17 +02:00
#include "../FastRandom.h"
2016-11-18 20:00:04 +01:00
#include "../ClientHandle.h"
2013-07-29 12:13:03 +01:00
2014-05-11 14:57:06 +03:00
#include "../WorldStorage/StatSerializer.h"
2014-05-12 17:05:09 +03:00
#include "../CompositeChat.h"
2014-05-11 14:57:06 +03:00
2014-12-24 07:20:17 +01:00
#include "../Blocks/BlockHandler.h"
#include "../Blocks/BlockSlab.h"
#include "../Blocks/ChunkInterface.h"
2014-10-23 15:15:10 +02:00
#include "../IniFile.h"
2020-05-09 15:51:15 +01:00
#include "../JsonUtils.h"
2013-11-27 09:17:25 +01:00
#include "json/json.h"
2013-07-29 12:13:03 +01:00
2020-07-14 18:56:42 +02:00
#include "../CraftingRecipes.h"
2014-06-16 15:12:50 +01:00
// 6000 ticks or 5 minutes
#define PLAYER_INVENTORY_SAVE_INTERVAL 6000
2018-07-20 00:30:09 +01:00
namespace
{
/** Returns the folder for the player data based on the UUID given.
This can be used both for online and offline UUIDs. */
AString GetUUIDFolderName ( const cUUID & a_Uuid )
{
AString UUID = a_Uuid . ToShortString ();
2020-05-07 22:14:00 +03:00
AString res ( "players/" );
2018-07-20 00:30:09 +01:00
res . append ( UUID , 0 , 2 );
res . push_back ( '/' );
return res ;
}
} // namespace (anonymous)
2013-07-29 12:13:03 +01:00
2014-08-10 17:13:14 -07:00
const int cPlayer :: MAX_HEALTH = 20 ;
const int cPlayer :: MAX_FOOD_LEVEL = 20 ;
2021-04-05 01:38:43 +01:00
// Number of ticks it takes to eat an item.
#define EATING_TICKS 30_tick
2014-08-10 17:13:14 -07:00
2013-07-29 12:13:03 +01:00
2013-11-14 00:50:47 +11:00
2021-04-03 19:45:20 +02:00
cPlayer :: BodyStanceCrouching :: BodyStanceCrouching ( cPlayer & a_Player )
{
a_Player . SetSize ( 0.6f , 1.65f );
}
cPlayer :: BodyStanceSleeping :: BodyStanceSleeping ( cPlayer & a_Player )
{
a_Player . SetSize ( 0.2f , 0.2f );
}
cPlayer :: BodyStanceStanding :: BodyStanceStanding ( cPlayer & a_Player )
{
a_Player . SetSize ( 0.6f , 1.8f );
}
cPlayer :: BodyStanceGliding :: BodyStanceGliding ( cPlayer & a_Player ) :
TicksElytraFlying ( 0 )
{
a_Player . SetSize ( 0.6f , 0.6f );
}
2021-03-20 20:58:19 +00:00
cPlayer :: cPlayer ( const std :: shared_ptr < cClientHandle > & a_Client ) :
2020-04-13 18:38:06 +02:00
Super ( etPlayer , 0.6 , 1.8 ),
2021-04-03 19:45:20 +02:00
m_BodyStance ( BodyStanceStanding ( * this )),
2014-07-11 13:13:10 +02:00
m_FoodLevel ( MAX_FOOD_LEVEL ),
m_FoodSaturationLevel ( 5.0 ),
m_FoodTickTimer ( 0 ),
m_FoodExhaustionLevel ( 0.0 ),
m_Inventory ( * this ),
m_EnderChestContents ( 9 , 3 ),
2021-01-06 00:35:42 +00:00
m_DefaultWorldPath ( cRoot :: Get () -> GetDefaultWorld () -> GetDataPath ()),
2014-07-11 13:13:10 +02:00
m_GameMode ( eGameMode_NotSet ),
m_ClientHandle ( a_Client ),
m_NormalMaxSpeed ( 1.0 ),
m_SprintingMaxSpeed ( 1.3 ),
m_FlyingMaxSpeed ( 1.0 ),
2021-04-03 19:45:20 +02:00
m_IsChargingBow ( false ),
2014-07-11 13:13:10 +02:00
m_IsFishing ( false ),
2021-04-03 19:45:20 +02:00
m_IsFlightCapable ( false ),
m_IsFlying ( false ),
m_IsFrozen ( false ),
m_IsTeleporting ( false ),
m_IsVisible ( true ),
2014-07-11 13:13:10 +02:00
m_EatingFinishTick ( - 1 ),
m_LifetimeTotalXp ( 0 ),
m_CurrentXp ( 0 ),
m_BowCharge ( 0 ),
2015-07-29 09:04:03 -06:00
m_FloaterID ( cEntity :: INVALID_ID ),
2014-10-20 21:55:07 +01:00
m_Team ( nullptr ),
2014-07-11 13:13:10 +02:00
m_TicksUntilNextSave ( PLAYER_INVENTORY_SAVE_INTERVAL ),
2017-01-03 12:19:29 -08:00
m_SkinParts ( 0 ),
m_MainHand ( mhRight )
2013-07-29 12:13:03 +01:00
{
2021-01-06 00:35:42 +00:00
ASSERT ( GetName (). length () <= 16 ); // Otherwise this player could crash many clients...
2016-01-13 07:55:05 +01:00
2013-07-29 12:13:03 +01:00
m_InventoryWindow = new cInventoryWindow ( * this );
m_CurrentWindow = m_InventoryWindow ;
m_InventoryWindow -> OpenedByPlayer ( * this );
2013-08-01 09:51:25 +02:00
SetMaxHealth ( MAX_HEALTH );
m_Health = MAX_HEALTH ;
2015-05-23 19:31:33 +01:00
2021-04-06 12:26:43 +01:00
LoadFromDisk ();
UpdateCapabilities ();
2014-01-24 23:58:51 +00:00
2015-03-01 03:45:06 +10:30
m_LastGroundHeight = static_cast < float > ( GetPosY ());
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
cPlayer ::~ cPlayer ( void )
2020-07-14 18:56:42 +02:00
{
2021-02-06 18:37:03 +00:00
LOGD ( "Deleting cPlayer \" %s \" at %p, ID %d" , GetName (). c_str (), static_cast < void *> ( this ), GetUniqueID ());
2020-07-14 18:56:42 +02:00
2021-02-06 18:37:03 +00:00
// "Times ragequit":
m_Stats . AddValue ( Statistic :: LeaveGame );
SaveToDisk ();
delete m_InventoryWindow ;
LOGD ( "Player %p deleted" , static_cast < void *> ( this ));
}
int cPlayer :: CalcLevelFromXp ( int a_XpTotal )
{
// level 0 to 15
if ( a_XpTotal <= XP_TO_LEVEL15 )
2020-07-14 18:56:42 +02:00
{
2021-02-06 18:37:03 +00:00
return a_XpTotal / XP_PER_LEVEL_TO15 ;
2020-07-14 18:56:42 +02:00
}
2021-02-06 18:37:03 +00:00
// level 30+
if ( a_XpTotal > XP_TO_LEVEL30 )
2020-07-14 18:56:42 +02:00
{
2021-02-06 18:37:03 +00:00
return static_cast < int > (( 151.5 + sqrt ( 22952.25 - ( 14 * ( 2220 - a_XpTotal )))) / 7 );
2020-07-14 18:56:42 +02:00
}
2021-02-06 18:37:03 +00:00
// level 16 to 30
return static_cast < int > (( 29.5 + sqrt ( 870.25 - ( 6 * ( 360 - a_XpTotal )))) / 3 );
2020-07-14 18:56:42 +02:00
}
2021-02-06 18:37:03 +00:00
const std :: set < UInt32 > & cPlayer :: GetKnownRecipes () const
2020-07-14 18:56:42 +02:00
{
2021-02-06 18:37:03 +00:00
return m_KnownRecipes ;
}
int cPlayer :: XpForLevel ( int a_Level )
{
// level 0 to 15
if ( a_Level <= 15 )
2020-07-14 18:56:42 +02:00
{
2021-02-06 18:37:03 +00:00
return a_Level * XP_PER_LEVEL_TO15 ;
2020-07-14 18:56:42 +02:00
}
2021-02-06 18:37:03 +00:00
// level 30+
if ( a_Level >= 31 )
{
return static_cast < int > (( 3.5 * a_Level * a_Level ) - ( 151.5 * a_Level ) + 2220 );
}
// level 16 to 30
return static_cast < int > (( 1.5 * a_Level * a_Level ) - ( 29.5 * a_Level ) + 360 );
2020-07-14 18:56:42 +02:00
}
2021-02-06 18:37:03 +00:00
int cPlayer :: GetXpLevel () const
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
return CalcLevelFromXp ( m_CurrentXp );
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
2015-05-23 19:31:33 +01:00
2021-02-06 18:37:03 +00:00
float cPlayer :: GetXpPercentage () const
{
int currentLevel = CalcLevelFromXp ( m_CurrentXp );
int currentLevel_XpBase = XpForLevel ( currentLevel );
return static_cast < float > ( m_CurrentXp - currentLevel_XpBase ) /
static_cast < float > ( XpForLevel ( 1 + currentLevel ) - currentLevel_XpBase );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
bool cPlayer :: SetCurrentExperience ( int a_CurrentXp )
2021-01-06 00:35:42 +00:00
{
2021-02-06 18:37:03 +00:00
if ( ! ( a_CurrentXp >= 0 ) || ( a_CurrentXp > ( std :: numeric_limits < int >:: max () - m_LifetimeTotalXp )))
{
LOGWARNING ( "Tried to update experiece with an invalid Xp value: %d" , a_CurrentXp );
return false ; // oops, they gave us a dodgey number
}
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
m_CurrentXp = a_CurrentXp ;
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
// Update experience:
m_ClientHandle -> SendExperience ();
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
return true ;
}
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
int cPlayer :: DeltaExperience ( int a_Xp_delta )
{
if ( a_Xp_delta > ( std :: numeric_limits < int > (). max () - m_CurrentXp ))
{
// Value was bad, abort and report
LOGWARNING ( "Attempt was made to increment Xp by %d, which overflowed the int datatype. Ignoring." , a_Xp_delta );
return - 1 ; // Should we instead just return the current Xp?
}
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
m_CurrentXp += a_Xp_delta ;
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
// Make sure they didn't subtract too much
m_CurrentXp = std :: max ( m_CurrentXp , 0 );
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
// Update total for score calculation
if ( a_Xp_delta > 0 )
{
m_LifetimeTotalXp += a_Xp_delta ;
}
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
LOGD ( "Player \" %s \" gained / lost %d experience, total is now: %d" , GetName (). c_str (), a_Xp_delta , m_CurrentXp );
// Set experience to be updated:
m_ClientHandle -> SendExperience ();
return m_CurrentXp ;
2021-01-06 00:35:42 +00:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: StartChargingBow ( void )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
LOGD ( "Player \" %s \" started charging their bow" , GetName (). c_str ());
m_IsChargingBow = true ;
m_BowCharge = 0 ;
m_World -> BroadcastEntityMetadata ( * this , m_ClientHandle . get ());
}
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
int cPlayer :: FinishChargingBow ( void )
{
LOGD ( "Player \" %s \" finished charging their bow at a charge of %d" , GetName (). c_str (), m_BowCharge );
int res = m_BowCharge ;
m_IsChargingBow = false ;
m_BowCharge = 0 ;
m_World -> BroadcastEntityMetadata ( * this , m_ClientHandle . get ());
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
return res ;
}
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
void cPlayer :: CancelChargingBow ( void )
{
LOGD ( "Player \" %s \" cancelled charging their bow at a charge of %d" , GetName (). c_str (), m_BowCharge );
m_IsChargingBow = false ;
m_BowCharge = 0 ;
m_World -> BroadcastEntityMetadata ( * this , m_ClientHandle . get ());
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetTouchGround ( bool a_bTouchGround )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
if ( IsGameModeSpectator ()) // You can fly through the ground in Spectator
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
}
2020-03-05 12:52:34 +02:00
2021-02-06 18:37:03 +00:00
UNUSED ( a_bTouchGround );
/* Unfortunately whatever the reason, there are still desyncs in on-ground status between the client and server. For example:
1. Walking off a ledge (whatever height)
2. Initial login
Thus, it is too risky to compare their value against ours and kick them for hacking */
}
2020-03-05 12:52:34 +02:00
2021-02-06 18:37:03 +00:00
void cPlayer :: Heal ( int a_Health )
{
Super :: Heal ( a_Health );
m_ClientHandle -> SendHealth ();
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetFoodLevel ( int a_FoodLevel )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
int FoodLevel = Clamp ( a_FoodLevel , 0 , MAX_FOOD_LEVEL );
2015-05-23 19:31:33 +01:00
2021-02-06 18:37:03 +00:00
if ( cRoot :: Get () -> GetPluginManager () -> CallHookPlayerFoodLevelChange ( * this , FoodLevel ))
2015-06-30 15:50:15 +01:00
{
2021-02-06 18:37:03 +00:00
m_FoodSaturationLevel = 5.0 ;
2021-01-06 00:35:42 +00:00
return ;
2015-06-30 15:50:15 +01:00
}
2014-05-12 21:38:52 +03:00
2021-02-06 18:37:03 +00:00
m_FoodLevel = FoodLevel ;
m_ClientHandle -> SendHealth ();
}
2016-04-05 11:45:09 +03:00
2021-01-06 00:35:42 +00:00
2015-05-23 19:31:33 +01:00
2016-10-12 14:38:45 +02:00
2015-05-23 19:31:33 +01:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SetFoodSaturationLevel ( double a_FoodSaturationLevel )
{
m_FoodSaturationLevel = Clamp ( a_FoodSaturationLevel , 0.0 , static_cast < double > ( m_FoodLevel ));
}
2016-03-27 20:43:30 +03:00
2021-01-06 00:35:42 +00:00
2015-05-23 19:31:33 +01:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SetFoodTickTimer ( int a_FoodTickTimer )
{
m_FoodTickTimer = a_FoodTickTimer ;
}
2013-07-29 12:13:03 +01:00
2015-05-23 19:31:33 +01:00
2013-12-21 17:31:05 +01:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SetFoodExhaustionLevel ( double a_FoodExhaustionLevel )
{
m_FoodExhaustionLevel = Clamp ( a_FoodExhaustionLevel , 0.0 , 40.0 );
}
2014-02-17 16:27:12 +02:00
2013-12-22 20:03:09 +00:00
2021-02-06 18:37:03 +00:00
bool cPlayer :: Feed ( int a_Food , double a_Saturation )
{
if ( IsSatiated ())
2014-06-16 15:12:50 +01:00
{
2021-02-06 18:37:03 +00:00
return false ;
2014-06-16 15:12:50 +01:00
}
2021-02-06 18:37:03 +00:00
SetFoodSaturationLevel ( m_FoodSaturationLevel + a_Saturation );
SetFoodLevel ( m_FoodLevel + a_Food );
return true ;
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: AddFoodExhaustion ( double a_Exhaustion )
2016-03-27 20:43:30 +03:00
{
2021-02-06 18:37:03 +00:00
if ( ! ( IsGameModeCreative () || IsGameModeSpectator ()))
2016-03-27 20:43:30 +03:00
{
2021-02-06 18:37:03 +00:00
m_FoodExhaustionLevel = std :: min ( m_FoodExhaustionLevel + a_Exhaustion , 40.0 );
2016-03-27 20:43:30 +03:00
}
}
2021-04-03 19:45:20 +02:00
bool cPlayer :: IsInBed ( void ) const
{
return std :: holds_alternative < BodyStanceSleeping > ( m_BodyStance );
}
bool cPlayer :: IsStanding () const
{
return std :: holds_alternative < BodyStanceStanding > ( m_BodyStance );
}
2021-02-06 18:37:03 +00:00
void cPlayer :: TossItems ( const cItems & a_Items )
2013-11-14 04:25:47 +11:00
{
2021-02-06 18:37:03 +00:00
if ( IsGameModeSpectator ()) // Players can't toss items in spectator
2013-11-14 04:25:47 +11:00
{
2021-02-06 18:37:03 +00:00
return ;
2013-11-14 04:25:47 +11:00
}
2021-02-06 18:37:03 +00:00
m_Stats . AddValue ( Statistic :: Drop , static_cast < cStatManager :: StatValue > ( a_Items . Size ()));
2013-11-14 04:25:47 +11:00
2021-02-06 18:37:03 +00:00
const auto Speed = ( GetLookVector () + Vector3d ( 0 , 0.2 , 0 )) * 6 ; // A dash of height and a dollop of speed
const auto Position = GetEyePosition () - Vector3d ( 0 , 0.2 , 0 ); // Correct for eye-height weirdness
m_World -> SpawnItemPickups ( a_Items , Position , Speed , true ); // 'true' because created by player
2013-11-14 04:25:47 +11:00
}
2021-04-03 19:45:20 +02:00
void cPlayer :: SetIsInBed ( const bool a_GoToBed )
{
if ( a_GoToBed && IsStanding ())
{
m_BodyStance = BodyStanceSleeping ( * this );
}
else if ( ! a_GoToBed && IsInBed ())
{
m_BodyStance = BodyStanceStanding ( * this );
GetWorld () -> BroadcastEntityAnimation ( * this , 2 );
}
}
2021-02-06 18:37:03 +00:00
void cPlayer :: StartEating ( void )
2021-01-06 00:35:42 +00:00
{
2021-02-06 18:37:03 +00:00
// Set the timer:
m_EatingFinishTick = m_World -> GetWorldAge () + EATING_TICKS ;
// Send the packets:
m_World -> BroadcastEntityAnimation ( * this , 3 );
m_World -> BroadcastEntityMetadata ( * this );
2021-01-06 00:35:42 +00:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: FinishEating ( void )
2013-11-14 04:25:47 +11:00
{
2021-02-06 18:37:03 +00:00
// Reset the timer:
2021-04-05 01:38:43 +01:00
m_EatingFinishTick = - 1 _tick ;
2013-11-14 04:25:47 +11:00
2021-02-06 18:37:03 +00:00
// Send the packets:
m_ClientHandle -> SendEntityStatus ( * this , esPlayerEatingAccepted );
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 ))
2013-11-14 04:25:47 +11:00
{
2021-02-06 18:37:03 +00:00
return ;
2013-11-14 04:25:47 +11:00
}
2021-02-06 18:37:03 +00:00
ItemHandler -> OnFoodEaten ( m_World , this , & Item );
2013-11-14 04:25:47 +11:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: AbortEating ( void )
2013-11-14 04:25:47 +11:00
{
2021-04-05 01:38:43 +01:00
m_EatingFinishTick = - 1 _tick ;
2021-02-06 18:37:03 +00:00
m_World -> BroadcastEntityMetadata ( * this );
2013-11-14 04:25:47 +11:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: ClearInventoryPaintSlots ( void )
2013-11-14 04:25:47 +11:00
{
2021-02-06 18:37:03 +00:00
// Clear the list of slots that are being inventory-painted. Used by cWindow only
m_InventoryPaintSlots . clear ();
2013-11-14 04:25:47 +11:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: AddInventoryPaintSlot ( int a_SlotNum )
2013-11-14 00:50:47 +11:00
{
2021-02-06 18:37:03 +00:00
// Add a slot to the list for inventory painting. Used by cWindow only
m_InventoryPaintSlots . push_back ( a_SlotNum );
}
2013-11-14 00:50:47 +11:00
2013-11-15 22:42:09 +11:00
2021-02-06 18:37:03 +00:00
const cSlotNums & cPlayer :: GetInventoryPaintSlots ( void ) const
{
// Return the list of slots currently stored for inventory painting. Used by cWindow only
return m_InventoryPaintSlots ;
2013-11-14 00:50:47 +11:00
}
2021-02-06 18:37:03 +00:00
double cPlayer :: GetMaxSpeed ( void ) const
2013-11-14 00:50:47 +11:00
{
2021-04-03 19:45:20 +02:00
if ( IsFlying ())
2013-11-14 00:50:47 +11:00
{
2021-02-06 18:37:03 +00:00
return m_FlyingMaxSpeed ;
2013-11-14 00:50:47 +11:00
}
2021-04-03 19:45:20 +02:00
else if ( IsSprinting ())
2013-11-16 21:38:57 +11:00
{
2021-02-06 18:37:03 +00:00
return m_SprintingMaxSpeed ;
}
else
{
return m_NormalMaxSpeed ;
2013-11-16 21:38:57 +11:00
}
2021-02-06 18:37:03 +00:00
}
2013-11-16 21:38:57 +11:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SetNormalMaxSpeed ( double a_Speed )
2013-08-30 14:24:03 +02:00
{
2021-02-06 18:37:03 +00:00
m_NormalMaxSpeed = a_Speed ;
2021-04-06 12:26:43 +01:00
if ( ! m_IsFrozen )
2021-02-06 18:37:03 +00:00
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
2021-04-06 12:26:43 +01:00
m_World -> BroadcastEntityProperties ( * this );
2021-02-06 18:37:03 +00:00
}
2013-08-30 14:24:03 +02:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetSprintingMaxSpeed ( double a_Speed )
2013-08-30 14:24:03 +02:00
{
2021-02-06 18:37:03 +00:00
m_SprintingMaxSpeed = a_Speed ;
2021-04-06 12:26:43 +01:00
if ( ! m_IsFrozen )
2021-02-06 18:37:03 +00:00
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
2021-04-06 12:26:43 +01:00
m_World -> BroadcastEntityProperties ( * this );
2021-02-06 18:37:03 +00:00
}
2013-08-30 14:24:03 +02:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetFlyingMaxSpeed ( double a_Speed )
2013-08-30 14:24:03 +02:00
{
2021-02-06 18:37:03 +00:00
m_FlyingMaxSpeed = a_Speed ;
if ( ! m_IsFrozen )
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
m_ClientHandle -> SendPlayerAbilities ();
}
2013-08-30 14:24:03 +02:00
}
2021-04-03 19:45:20 +02:00
void cPlayer :: SetCrouch ( const bool a_ShouldCrouch )
2013-07-29 12:13:03 +01:00
{
2021-04-03 19:45:20 +02:00
if ( a_ShouldCrouch && IsStanding ())
2014-09-17 17:15:47 +02:00
{
2021-04-03 19:45:20 +02:00
m_BodyStance = BodyStanceCrouching ( * this );
cRoot :: Get () -> GetPluginManager () -> CallHookPlayerCrouched ( * this );
2014-09-17 17:15:47 +02:00
}
2021-04-03 19:45:20 +02:00
else if ( ! a_ShouldCrouch && IsCrouched ())
{
m_BodyStance = BodyStanceStanding ( * this );
}
m_World -> BroadcastEntityMetadata ( * this );
}
2015-05-23 19:31:33 +01:00
2021-04-03 19:45:20 +02:00
void cPlayer :: SetElytraFlight ( const bool a_ShouldElytraFly )
{
if ( a_ShouldElytraFly && IsStanding () && ! IsOnGround () && ! IsInWater () && ! IsRiding () && ( GetEquippedChestplate (). m_ItemType == E_ITEM_ELYTRA ))
2021-02-06 18:37:03 +00:00
{
2021-04-03 19:45:20 +02:00
m_BodyStance = BodyStanceGliding ( * this );
}
else if ( ! a_ShouldElytraFly && IsElytraFlying ())
{
m_BodyStance = BodyStanceStanding ( * this );
2021-02-06 18:37:03 +00:00
}
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
m_World -> BroadcastEntityMetadata ( * this );
2013-07-29 12:13:03 +01:00
}
2021-04-03 19:45:20 +02:00
void cPlayer :: SetFlying ( const bool a_ShouldFly )
2013-07-29 12:13:03 +01:00
{
2021-04-03 19:45:20 +02:00
if ( a_ShouldFly == m_IsFlying )
2014-06-30 15:12:56 +02:00
{
return ;
}
2015-05-23 19:31:33 +01:00
2021-04-03 19:45:20 +02:00
m_IsFlying = a_ShouldFly ;
if ( ! m_IsFrozen )
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
m_ClientHandle -> SendPlayerAbilities ();
}
}
void cPlayer :: SetSprint ( const bool a_ShouldSprint )
{
if ( a_ShouldSprint && IsStanding ())
{
m_BodyStance = BodyStanceSprinting ();
}
else if ( ! a_ShouldSprint && IsSprinting ())
{
m_BodyStance = BodyStanceStanding ( * this );
}
m_World -> BroadcastEntityMetadata ( * this );
2021-04-06 12:26:43 +01:00
m_World -> BroadcastEntityProperties ( * this );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetCanFly ( bool a_CanFly )
2013-07-29 12:13:03 +01:00
{
2021-04-03 19:45:20 +02:00
if ( a_CanFly == m_IsFlightCapable )
2021-02-06 18:37:03 +00:00
{
return ;
}
2013-07-29 12:13:03 +01:00
2021-04-03 19:45:20 +02:00
m_IsFlightCapable = a_CanFly ;
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendPlayerAbilities ();
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetCustomName ( const AString & a_CustomName )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
if ( m_CustomName == a_CustomName )
{
return ;
}
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
m_World -> BroadcastPlayerListRemovePlayer ( * this );
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
m_CustomName = a_CustomName ;
if ( m_CustomName . length () > 16 )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
m_CustomName = m_CustomName . substr ( 0 , 16 );
2013-07-29 12:13:03 +01:00
}
2014-06-30 15:12:56 +02:00
2021-02-06 18:37:03 +00:00
m_World -> BroadcastPlayerListAddPlayer ( * this );
m_World -> BroadcastSpawnEntity ( * this , m_ClientHandle . get ());
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetBedPos ( const Vector3i & a_Pos )
2014-07-31 23:04:00 +02:00
{
2021-02-06 18:37:03 +00:00
m_LastBedPos = a_Pos ;
m_SpawnWorldName = m_World -> GetName ();
2014-07-31 23:04:00 +02:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetBedPos ( const Vector3i & a_Pos , cWorld * a_World )
2020-05-04 09:10:47 +01:00
{
2021-02-06 18:37:03 +00:00
m_LastBedPos = a_Pos ;
ASSERT ( a_World != nullptr );
m_SpawnWorldName = a_World -> GetName ();
2020-05-04 09:10:47 +01:00
}
2021-02-06 18:37:03 +00:00
cWorld * cPlayer :: GetBedWorld ()
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
if ( const auto World = cRoot :: Get () -> GetWorld ( m_SpawnWorldName ); World != nullptr )
{
return World ;
}
2015-05-23 19:31:33 +01:00
2021-02-06 18:37:03 +00:00
return cRoot :: Get () -> GetDefaultWorld ();
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: NotifyNearbyWolves ( cPawn * a_Opponent , bool a_IsPlayerInvolved )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
ASSERT ( a_Opponent != nullptr );
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
m_World -> ForEachEntityInBox ( cBoundingBox ( GetPosition (), 16 ), [ & ] ( cEntity & a_Entity )
{
if ( a_Entity . IsMob ())
{
auto & Mob = static_cast < cMonster &> ( a_Entity );
if ( Mob . GetMobType () == mtWolf )
{
auto & Wolf = static_cast < cWolf &> ( Mob );
Wolf . ReceiveNearbyFightInfo ( GetUUID (), a_Opponent , a_IsPlayerInvolved );
}
}
return false ;
}
);
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: KilledBy ( TakeDamageInfo & a_TDI )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
Super :: KilledBy ( a_TDI );
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
if ( m_Health > 0 )
{
return ; // not dead yet =]
}
2013-07-29 12:13:03 +01:00
2021-04-03 19:45:20 +02:00
m_IsVisible = false ; // So new clients don't see the player
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
// Detach player from object / entity. If the player dies, the server still says
// that the player is attached to the entity / object
Detach ();
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
// Puke out all the items
cItems Pickups ;
m_Inventory . CopyToItems ( Pickups );
m_Inventory . Clear ();
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
if ( GetName () == "Notch" )
2014-03-20 16:14:40 +01:00
{
2021-02-06 18:37:03 +00:00
Pickups . Add ( cItem ( E_ITEM_RED_APPLE ));
2014-03-20 16:14:40 +01:00
}
2021-02-06 18:37:03 +00:00
m_Stats . AddValue ( Statistic :: Drop , static_cast < cStatManager :: StatValue > ( Pickups . Size ()));
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
m_World -> SpawnItemPickups ( Pickups , GetPosX (), GetPosY (), GetPosZ (), 10 );
SaveToDisk (); // Save it, yeah the world is a tough place !
cPluginManager * PluginManager = cRoot :: Get () -> GetPluginManager ();
2013-07-29 12:13:03 +01:00
2021-03-11 22:26:35 +01:00
if ( a_TDI . Attacker == nullptr )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
const AString DamageText = [ & ]
{
switch ( a_TDI . DamageType )
{
case dtRangedAttack : return "was shot" ;
case dtLightning : return "was plasmified by lightining" ;
case dtFalling : return GetRandomProvider (). RandBool () ? "fell to death" : "hit the ground too hard" ;
case dtDrowning : return "drowned" ;
case dtSuffocating : return GetRandomProvider (). RandBool () ? "git merge'd into a block" : "fused with a block" ;
case dtStarving : return "forgot the importance of food" ;
case dtCactusContact : return "was impaled on a cactus" ;
case dtMagmaContact : return "discovered the floor was lava" ;
case dtLavaContact : return "was melted by lava" ;
case dtPoisoning : return "died from septicaemia" ;
case dtWithering : return "is a husk of their former selves" ;
case dtOnFire : return "forgot to stop, drop, and roll" ;
case dtFireContact : return "burnt themselves to death" ;
case dtInVoid : return "somehow fell out of the world" ;
case dtPotionOfHarming : return "was magicked to death" ;
case dtEnderPearl : return "misused an ender pearl" ;
case dtAdmin : return "was administrator'd" ;
case dtExplosion : return "blew up" ;
case dtAttack : return "was attacked by thin air" ;
case dtEnvironment : return "played too much dress up" ; // This is not vanilla - added a own pun
}
UNREACHABLE ( "Unsupported damage type" );
}();
AString DeathMessage = Printf ( "%s %s" , GetName (). c_str (), DamageText . c_str ());
PluginManager -> CallHookKilled ( * this , a_TDI , DeathMessage );
if ( DeathMessage != AString ( "" ))
{
GetWorld () -> BroadcastChatDeath ( DeathMessage );
}
}
else if ( a_TDI . Attacker -> IsPlayer ())
{
cPlayer * Killer = static_cast < cPlayer *> ( a_TDI . Attacker );
AString DeathMessage = Printf ( "%s was killed by %s" , GetName (). c_str (), Killer -> GetName (). c_str ());
PluginManager -> CallHookKilled ( * this , a_TDI , DeathMessage );
if ( DeathMessage != AString ( "" ))
{
GetWorld () -> BroadcastChatDeath ( DeathMessage );
}
}
else
{
AString KillerClass = a_TDI . Attacker -> GetClass ();
KillerClass . erase ( KillerClass . begin ()); // Erase the 'c' of the class (e.g. "cWitch" -> "Witch")
AString DeathMessage = Printf ( "%s was killed by a %s" , GetName (). c_str (), KillerClass . c_str ());
PluginManager -> CallHookKilled ( * this , a_TDI , DeathMessage );
if ( DeathMessage != AString ( "" ))
{
GetWorld () -> BroadcastChatDeath ( DeathMessage );
}
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
m_Stats . AddValue ( Statistic :: Deaths );
m_Stats . SetValue ( Statistic :: TimeSinceDeath , 0 );
m_World -> GetScoreBoard (). AddPlayerScore ( GetName (), cObjective :: otDeathCount , 1 );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: Killed ( cEntity * a_Victim )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
cScoreboard & ScoreBoard = m_World -> GetScoreBoard ();
if ( a_Victim -> IsPlayer ())
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
m_Stats . AddValue ( Statistic :: PlayerKills );
ScoreBoard . AddPlayerScore ( GetName (), cObjective :: otPlayerKillCount , 1 );
}
else if ( a_Victim -> IsMob ())
{
if ( static_cast < cMonster *> ( a_Victim ) -> GetMobFamily () == cMonster :: mfHostile )
{
AwardAchievement ( Statistic :: AchKillEnemy );
}
m_Stats . AddValue ( Statistic :: MobKills );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
ScoreBoard . AddPlayerScore ( GetName (), cObjective :: otTotalKillCount , 1 );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: Respawn ( void )
2014-03-20 16:14:40 +01:00
{
2021-02-06 18:37:03 +00:00
ASSERT ( m_World != nullptr );
2014-03-20 16:14:40 +01:00
2021-02-06 18:37:03 +00:00
m_Health = GetMaxHealth ();
SetInvulnerableTicks ( 20 );
2014-03-20 16:14:40 +01:00
2021-02-06 18:37:03 +00:00
// Reset food level:
m_FoodLevel = MAX_FOOD_LEVEL ;
m_FoodSaturationLevel = 5.0 ;
m_FoodExhaustionLevel = 0.0 ;
2014-03-20 16:14:40 +01:00
2021-02-06 18:37:03 +00:00
// Reset Experience
m_CurrentXp = 0 ;
m_LifetimeTotalXp = 0 ;
// ToDo: send score to client? How?
2014-03-20 16:14:40 +01:00
2021-02-06 18:37:03 +00:00
// Extinguish the fire:
StopBurning ();
2014-03-20 16:14:40 +01:00
2021-02-06 18:37:03 +00:00
if ( const auto BedWorld = GetBedWorld (); m_World != BedWorld )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
MoveToWorld ( * BedWorld , GetLastBedPos (), false , false );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
else
2020-03-23 12:18:12 +00:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendRespawn ( m_World -> GetDimension (), true );
TeleportToCoords ( GetLastBedPos (). x , GetLastBedPos (). y , GetLastBedPos (). z );
2020-03-23 12:18:12 +00:00
}
2021-02-06 18:37:03 +00:00
SetVisible ( true );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
double cPlayer :: GetEyeHeight ( void ) const
2013-07-29 12:13:03 +01:00
{
2021-04-03 19:45:20 +02:00
return GetEyePosition (). y - GetPosY ();
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
Vector3d cPlayer :: GetEyePosition ( void ) const
2013-12-15 14:48:17 +01:00
{
2021-04-03 19:45:20 +02:00
if ( IsCrouched ())
{
return GetPosition (). addedY ( 1.54 );
}
if ( IsElytraFlying ())
{
return GetPosition (). addedY ( 0.4 );
}
if ( IsInBed ())
{
return GetPosition (). addedY ( 0.2 );
}
return GetPosition (). addedY ( 1.6 );
2013-12-15 14:48:17 +01:00
}
2021-02-06 18:37:03 +00:00
bool cPlayer :: IsGameModeCreative ( void ) const
2014-09-02 19:12:35 +02:00
{
2021-02-06 18:37:03 +00:00
return ( GetEffectiveGameMode () == gmCreative );
}
2014-09-26 17:37:19 +02:00
2014-09-02 19:12:35 +02:00
2021-02-06 18:37:03 +00:00
bool cPlayer :: IsGameModeSurvival ( void ) const
{
return ( GetEffectiveGameMode () == gmSurvival );
2014-09-02 19:12:35 +02:00
}
2021-02-06 18:37:03 +00:00
bool cPlayer :: IsGameModeAdventure ( void ) const
2016-03-24 19:13:23 +02:00
{
2021-02-06 18:37:03 +00:00
return ( GetEffectiveGameMode () == gmAdventure );
2016-03-24 19:13:23 +02:00
}
2021-02-06 18:37:03 +00:00
bool cPlayer :: IsGameModeSpectator ( void ) const
2016-03-24 19:13:23 +02:00
{
2021-02-06 18:37:03 +00:00
return ( GetEffectiveGameMode () == gmSpectator );
2016-03-24 19:13:23 +02:00
}
2021-02-06 18:37:03 +00:00
bool cPlayer :: CanMobsTarget ( void ) const
2016-08-27 09:37:54 +03:00
{
2021-02-06 18:37:03 +00:00
return ( IsGameModeSurvival () || IsGameModeAdventure ()) && ( m_Health > 0 );
}
2021-01-06 00:35:42 +00:00
2021-02-06 18:37:03 +00:00
AString cPlayer :: GetIP ( void ) const
{
return m_ClientHandle -> GetIPString ();
2016-08-27 09:37:54 +03:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetTeam ( cTeam * a_Team )
2013-12-15 14:48:17 +01:00
{
2021-02-06 18:37:03 +00:00
if ( m_Team == a_Team )
2013-12-15 14:48:17 +01:00
{
return ;
}
2021-02-06 18:37:03 +00:00
if ( m_Team )
2016-04-05 11:45:09 +03:00
{
2021-02-06 18:37:03 +00:00
m_Team -> RemovePlayer ( GetName ());
}
m_Team = a_Team ;
if ( m_Team )
{
m_Team -> AddPlayer ( GetName ());
2016-04-05 11:45:09 +03:00
}
2013-12-15 14:48:17 +01:00
}
2018-07-26 22:24:36 +01:00
2021-02-06 18:37:03 +00:00
cTeam * cPlayer :: UpdateTeam ( void )
2017-07-23 04:46:38 -05:00
{
2021-02-06 18:37:03 +00:00
if ( m_World == nullptr )
2017-07-23 04:46:38 -05:00
{
2021-02-06 18:37:03 +00:00
SetTeam ( nullptr );
}
else
{
cScoreboard & Scoreboard = m_World -> GetScoreBoard ();
SetTeam ( Scoreboard . QueryPlayerTeam ( GetName ()));
2017-07-23 04:46:38 -05:00
}
2021-02-06 18:37:03 +00:00
return m_Team ;
2017-07-23 04:46:38 -05:00
}
2013-12-15 14:48:17 +01:00
2021-02-06 18:37:03 +00:00
void cPlayer :: OpenWindow ( cWindow & a_Window )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
if ( cRoot :: Get () -> GetPluginManager () -> CallHookPlayerOpeningWindow ( * this , a_Window ))
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
return ;
2013-07-29 12:13:03 +01:00
}
2014-01-19 14:20:57 +02:00
2021-02-06 18:37:03 +00:00
if ( & a_Window != m_CurrentWindow )
2014-01-19 14:20:57 +02:00
{
2021-02-06 18:37:03 +00:00
CloseWindow ( false );
}
2014-01-19 14:20:57 +02:00
2021-02-06 18:37:03 +00:00
a_Window . OpenedByPlayer ( * this );
m_CurrentWindow = & a_Window ;
a_Window . SendWholeWindow ( * GetClientHandle ());
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: CloseWindow ( bool a_CanRefuse )
2016-01-11 21:34:41 +02:00
{
2021-02-06 18:37:03 +00:00
if ( m_CurrentWindow == nullptr )
{
m_CurrentWindow = m_InventoryWindow ;
return ;
}
2016-01-11 21:34:41 +02:00
2021-02-06 18:37:03 +00:00
if ( m_CurrentWindow -> ClosedByPlayer ( * this , a_CanRefuse ) || ! a_CanRefuse )
{
// Close accepted, go back to inventory window (the default):
m_CurrentWindow = m_InventoryWindow ;
}
else
{
// Re-open the window
m_CurrentWindow -> OpenedByPlayer ( * this );
m_CurrentWindow -> SendWholeWindow ( * GetClientHandle ());
}
2016-01-11 21:34:41 +02:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: CloseWindowIfID ( char a_WindowID , bool a_CanRefuse )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
if (( m_CurrentWindow == nullptr ) || ( m_CurrentWindow -> GetWindowID () != a_WindowID ))
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
return ;
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
CloseWindow ();
}
2013-07-29 12:13:03 +01:00
2017-03-19 09:48:15 +01:00
2014-02-15 19:51:05 +01:00
2014-05-12 21:38:52 +03:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessage ( const AString & a_Message )
{
m_ClientHandle -> SendChat ( a_Message , mtCustom );
}
2013-12-26 14:55:19 +00:00
2014-01-19 14:20:57 +02:00
2014-05-11 14:57:06 +03:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessageInfo ( const AString & a_Message )
{
m_ClientHandle -> SendChat ( a_Message , mtInformation );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessageFailure ( const AString & a_Message )
2014-05-12 17:05:09 +03:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChat ( a_Message , mtFailure );
}
2014-05-12 17:05:09 +03:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessageSuccess ( const AString & a_Message )
{
m_ClientHandle -> SendChat ( a_Message , mtSuccess );
2014-05-12 17:05:09 +03:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessageWarning ( const AString & a_Message )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChat ( a_Message , mtWarning );
}
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
2013-11-16 21:38:57 +11:00
2013-07-29 12:13:03 +01:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessageFatal ( const AString & a_Message )
{
m_ClientHandle -> SendChat ( a_Message , mtFailure );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessagePrivateMsg ( const AString & a_Message , const AString & a_Sender )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChat ( a_Message , mtPrivateMessage , a_Sender );
2013-07-29 12:13:03 +01:00
}
2018-07-26 22:24:36 +01:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessage ( const cCompositeChat & a_Message )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChat ( a_Message );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SendMessageRaw ( const AString & a_MessageRaw , eChatType a_Type )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChatRaw ( a_MessageRaw , a_Type );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SendSystemMessage ( const AString & a_Message )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChatSystem ( a_Message , mtCustom );
2013-07-29 12:13:03 +01:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SendAboveActionBarMessage ( const AString & a_Message )
2013-07-29 12:13:03 +01:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChatAboveActionBar ( a_Message , mtCustom );
2013-07-29 12:13:03 +01:00
}
2018-07-26 22:24:36 +01:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SendSystemMessage ( const cCompositeChat & a_Message )
2014-09-16 20:04:17 +02:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChatSystem ( a_Message );
2014-09-16 20:04:17 +02:00
}
2013-07-29 12:13:03 +01:00
2016-10-12 14:38:45 +02:00
2021-02-06 18:37:03 +00:00
void cPlayer :: SendAboveActionBarMessage ( const cCompositeChat & a_Message )
2016-10-12 14:38:45 +02:00
{
2021-02-06 18:37:03 +00:00
m_ClientHandle -> SendChatAboveActionBar ( a_Message );
2016-10-12 14:38:45 +02:00
}
2021-02-06 18:37:03 +00:00
const AString & cPlayer :: GetName ( void ) const
2021-01-06 00:35:42 +00:00
{
2021-02-06 18:37:03 +00:00
return m_ClientHandle -> GetUsername ();
2021-01-06 00:35:42 +00:00
}
2021-02-06 18:37:03 +00:00
void cPlayer :: SetGameMode ( eGameMode a_GameMode )
2014-01-19 14:20:57 +02:00
{
2021-02-06 18:37:03 +00:00
if (( a_GameMode < gmMin ) || ( a_GameMode >= gmMax ))
2014-01-20 16:10:39 +02:00
{
2021-02-06 18:37:03 +00:00
LOGWARNING ( "%s: Setting invalid gamemode: %d" , GetName (). c_str (), a_GameMode );
2014-01-20 16:10:39 +02:00
return ;
}
2021-02-06 18:37:03 +00:00
if ( m_GameMode == a_GameMode )
2014-01-19 14:20:57 +02:00
{
2021-02-06 18:37:03 +00:00
// Gamemode already set
return ;
2013-07-29 12:13:03 +01:00
}
2015-05-23 19:31:33 +01:00
2016-10-12 14:38:45 +02:00
// Detach, if the player is switching from or to the spectator mode
if (( m_GameMode == gmSpectator ) || ( a_GameMode == gmSpectator ))
{
Detach ();
}
2013-07-29 12:13:03 +01:00
m_GameMode = a_GameMode ;
2021-04-06 12:26:43 +01:00
UpdateCapabilities ();
2015-05-22 12:25:16 -07:00
2021-04-06 12:26:43 +01:00
m_ClientHandle -> SendGameMode ( a_GameMode );
m_ClientHandle -> SendInventorySlot ( - 1 , - 1 , m_DraggingItem );
2014-09-18 18:50:17 +02:00
m_World -> BroadcastPlayerListUpdateGameMode ( * this );
2021-04-06 12:26:43 +01:00
m_World -> BroadcastEntityMetadata ( * this );
2013-07-29 12:13:03 +01:00
}
2021-04-06 12:26:43 +01:00
void cPlayer :: UpdateCapabilities ()
2015-05-23 19:31:33 +01:00
{
2021-04-06 12:26:43 +01:00
// Fly ability:
2018-07-23 07:35:32 +08:00
if ( IsGameModeCreative () || IsGameModeSpectator ())
{
2021-04-06 12:26:43 +01:00
m_IsFlightCapable = true ;
2018-07-23 07:35:32 +08:00
}
else
2015-05-23 19:31:33 +01:00
{
2021-04-06 12:26:43 +01:00
m_IsFlying = false ;
m_IsFlightCapable = false ;
2015-05-23 19:31:33 +01:00
}
2021-04-06 12:26:43 +01:00
// Visible:
m_IsVisible = ! IsGameModeSpectator ();
2016-10-12 14:38:45 +02:00
2021-04-06 12:26:43 +01:00
// Clear the current dragging item of spectators:
2018-07-23 07:35:32 +08:00
if ( IsGameModeSpectator ())
{
2021-04-06 12:26:43 +01:00
m_DraggingItem . Empty ();
2015-05-23 19:31:33 +01:00
}
2013-07-29 12:13:03 +01:00
}
2020-08-12 09:54:36 +01:00
void cPlayer :: AwardAchievement ( const Statistic a_Ach )
2014-05-12 17:05:09 +03:00
{
2020-08-12 09:54:36 +01:00
// Check if the prerequisites are met:
if ( ! m_Stats . SatisfiesPrerequisite ( a_Ach ))
2014-05-12 17:05:09 +03:00
{
2020-08-12 09:54:36 +01:00
return ;
2014-05-12 17:05:09 +03:00
}
2020-08-12 09:54:36 +01:00
// Increment the statistic and check if we already have it:
if ( m_Stats . AddValue ( a_Ach ) != 1 )
2014-05-12 17:05:09 +03:00
{
2020-08-12 09:54:36 +01:00
return ;
2014-05-12 17:05:09 +03:00
}
2020-08-12 09:54:36 +01:00
if ( m_World -> ShouldBroadcastAchievementMessages ())
{
cCompositeChat Msg ;
Msg . SetMessageType ( mtSuccess );
// TODO: cCompositeChat should not use protocol-specific strings
// Msg.AddShowAchievementPart(GetName(), nameNew);
Msg . AddTextPart ( "Achivement get!" );
m_World -> BroadcastChat ( Msg );
2014-05-12 17:05:09 +03:00
}
2020-08-12 09:54:36 +01:00
// Achievement Get!
m_ClientHandle -> SendStatistics ( m_Stats );
2014-05-12 17:05:09 +03:00
}
2013-07-29 12:13:03 +01:00
void cPlayer :: TeleportToCoords ( double a_PosX , double a_PosY , double a_PosZ )
{
2015-03-05 11:52:42 +01:00
// ask plugins to allow teleport to the new position.
2015-07-21 21:25:37 +01:00
if ( ! cRoot :: Get () -> GetPluginManager () -> CallHookEntityTeleport ( * this , m_LastPosition , Vector3d ( a_PosX , a_PosY , a_PosZ )))
2015-03-05 11:52:42 +01:00
{
2020-05-04 09:10:47 +01:00
SetPosition ({ a_PosX , a_PosY , a_PosZ });
2016-03-27 20:43:30 +03:00
FreezeInternal ( GetPosition (), false );
2021-04-03 19:45:20 +02:00
m_IsTeleporting = true ;
2013-07-29 12:13:03 +01:00
2015-03-05 11:52:42 +01:00
m_ClientHandle -> SendPlayerMoveLook ();
}
2013-07-29 12:13:03 +01:00
}
2015-12-24 12:37:32 +02:00
void cPlayer :: Freeze ( const Vector3d & a_Location )
{
FreezeInternal ( a_Location , true );
}
bool cPlayer :: IsFrozen ()
{
return m_IsFrozen ;
}
void cPlayer :: Unfreeze ()
{
2021-04-03 19:45:20 +02:00
if ( IsElytraFlying ())
{
m_World -> BroadcastEntityMetadata ( * this );
}
2021-04-06 12:26:43 +01:00
m_ClientHandle -> SendPlayerAbilities ();
m_World -> BroadcastEntityProperties ( * this );
2016-04-05 11:45:09 +03:00
2015-12-24 12:37:32 +02:00
m_IsFrozen = false ;
2021-01-06 00:35:42 +00:00
BroadcastMovementUpdate ( m_ClientHandle . get ());
2016-04-05 11:45:09 +03:00
GetClientHandle () -> SendPlayerPosition ();
2015-12-24 12:37:32 +02:00
}
2014-04-04 23:06:47 +02:00
void cPlayer :: SendRotation ( double a_YawDegrees , double a_PitchDegrees )
{
SetYaw ( a_YawDegrees );
SetPitch ( a_PitchDegrees );
m_ClientHandle -> SendPlayerMoveLook ();
}
2020-03-30 22:35:37 +03:00
void cPlayer :: SpectateEntity ( cEntity * a_Target )
{
if (( a_Target == nullptr ) || ( static_cast < cEntity *> ( this ) == a_Target ))
{
GetClientHandle () -> SendCameraSetTo ( * this );
m_AttachedTo = nullptr ;
return ;
}
m_AttachedTo = a_Target ;
GetClientHandle () -> SendCameraSetTo ( * m_AttachedTo );
}
2013-08-30 17:29:46 +02:00
Vector3d cPlayer :: GetThrowStartPos ( void ) const
{
Vector3d res = GetEyePosition ();
2015-05-23 19:31:33 +01:00
2013-08-30 17:29:46 +02:00
// Adjust the position to be just outside the player's bounding box:
res . x += 0.16 * cos ( GetPitch ());
res . y += - 0.1 ;
res . z += 0.16 * sin ( GetPitch ());
2015-05-23 19:31:33 +01:00
2013-08-30 17:29:46 +02:00
return res ;
}
Vector3d cPlayer :: GetThrowSpeed ( double a_SpeedCoeff ) const
{
Vector3d res = GetLookVector ();
res . Normalize ();
2015-05-23 19:31:33 +01:00
2013-08-30 17:29:46 +02:00
// TODO: Add a slight random change (+-0.0075 in each direction)
2015-05-23 19:31:33 +01:00
2013-08-30 17:29:46 +02:00
return res * a_SpeedCoeff ;
2014-07-17 22:50:58 +02:00
}
2013-08-30 17:29:46 +02:00
2021-01-06 00:35:42 +00:00
eGameMode cPlayer :: GetEffectiveGameMode ( void ) const
{
2021-04-06 12:26:43 +01:00
return ( m_GameMode == gmNotSet ) ? m_World -> GetGameMode () : m_GameMode ;
2021-01-06 00:35:42 +00:00
}
2014-04-19 20:51:52 +02:00
void cPlayer :: ForceSetSpeed ( const Vector3d & a_Speed )
2013-12-15 18:54:54 +01:00
{
2014-04-19 20:51:52 +02:00
SetSpeed ( a_Speed );
2014-06-16 15:12:50 +01:00
}
2013-07-29 12:13:03 +01:00
void cPlayer :: SetVisible ( bool a_bVisible )
{
2021-04-06 12:26:43 +01:00
if ( a_bVisible && ! m_IsVisible )
2013-07-29 12:13:03 +01:00
{
2021-04-03 19:45:20 +02:00
m_IsVisible = true ;
2013-07-29 12:13:03 +01:00
}
2021-04-03 19:45:20 +02:00
if ( ! a_bVisible && m_IsVisible )
2013-07-29 12:13:03 +01:00
{
2021-04-03 19:45:20 +02:00
m_IsVisible = false ;
2013-07-29 12:13:03 +01:00
}
2021-04-06 12:26:43 +01:00
m_World -> BroadcastEntityMetadata ( * this );
2013-07-29 12:13:03 +01:00
}
2020-10-02 00:33:32 +03:00
MTRand cPlayer :: GetEnchantmentRandomProvider ()
{
return m_EnchantmentSeed ;
}
void cPlayer :: PermuteEnchantmentSeed ()
{
// Get a new random integer and save that as the seed:
m_EnchantmentSeed = GetRandomProvider (). RandInt < unsigned int > ();
}
2013-07-29 12:13:03 +01:00
bool cPlayer :: HasPermission ( const AString & a_Permission )
{
if ( a_Permission . empty ())
{
// Empty permission request is always granted
return true ;
}
2015-05-23 19:31:33 +01:00
2014-08-19 17:34:11 +02:00
AStringVector Split = StringSplit ( a_Permission , "." );
2015-04-25 19:40:44 +02:00
// Iterate over all restrictions; if any matches, then return failure:
for ( auto & Restriction : m_SplitRestrictions )
{
if ( PermissionMatches ( Split , Restriction ))
{
return false ;
}
} // for Restriction - m_SplitRestrictions[]
2014-08-19 17:34:11 +02:00
// Iterate over all granted permissions; if any matches, then return success:
2015-04-25 19:40:44 +02:00
for ( auto & Permission : m_SplitPermissions )
2013-07-29 12:13:03 +01:00
{
2015-04-25 19:40:44 +02:00
if ( PermissionMatches ( Split , Permission ))
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
return true ;
2013-07-29 12:13:03 +01:00
}
2015-04-25 19:40:44 +02:00
} // for Permission - m_SplitPermissions[]
2013-07-29 12:13:03 +01:00
2014-08-19 17:34:11 +02:00
// No granted permission matches
2013-07-29 12:13:03 +01:00
return false ;
}
2014-08-19 17:34:11 +02:00
bool cPlayer :: PermissionMatches ( const AStringVector & a_Permission , const AStringVector & a_Template )
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
// Check the sub-items if they are the same or there's a wildcard:
size_t lenP = a_Permission . size ();
size_t lenT = a_Template . size ();
size_t minLen = std :: min ( lenP , lenT );
for ( size_t i = 0 ; i < minLen ; i ++ )
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
if ( a_Template [ i ] == "*" )
{
// Has matched so far and now there's a wildcard in the template, so the permission matches:
2013-07-29 12:13:03 +01:00
return true ;
2014-08-19 17:34:11 +02:00
}
if ( a_Permission [ i ] != a_Template [ i ])
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
// Found a mismatch
return false ;
2013-07-29 12:13:03 +01:00
}
}
2014-08-19 17:34:11 +02:00
// So far all the sub-items have matched
2020-05-14 23:15:35 +01:00
// If the sub-item count is the same, then the permission matches
return ( lenP == lenT );
2013-07-29 12:13:03 +01:00
}
AString cPlayer :: GetColor ( void ) const
{
2014-08-19 17:34:11 +02:00
if ( m_MsgNameColorCode . empty () || ( m_MsgNameColorCode == "-" ))
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
// Color has not been assigned, return an empty string:
return AString ();
2013-07-29 12:13:03 +01:00
}
2014-08-19 17:34:11 +02:00
// Return the color, including the delimiter:
return cChatColor :: Delimiter + m_MsgNameColorCode ;
2013-07-29 12:13:03 +01:00
}
2017-05-27 03:15:56 +02:00
AString cPlayer :: GetPrefix ( void ) const
{
return m_MsgPrefix ;
}
AString cPlayer :: GetSuffix ( void ) const
{
return m_MsgSuffix ;
}
2014-09-23 14:39:49 +02:00
AString cPlayer :: GetPlayerListName ( void ) const
2014-09-02 19:12:35 +02:00
{
const AString & Color = GetColor ();
if ( HasCustomName ())
{
return m_CustomName ;
}
else if (( GetName (). length () <= 14 ) && ! Color . empty ())
{
return Printf ( "%s%s" , Color . c_str (), GetName (). c_str ());
}
else
{
return GetName ();
}
}
2017-07-06 22:27:04 -05:00
void cPlayer :: SetDraggingItem ( const cItem & a_Item )
{
if ( GetWindow () != nullptr )
{
m_DraggingItem = a_Item ;
GetClientHandle () -> SendInventorySlot ( - 1 , - 1 , m_DraggingItem );
}
}
2014-01-23 00:27:39 -07:00
void cPlayer :: TossEquippedItem ( char a_Amount )
2013-07-29 12:13:03 +01:00
{
cItems Drops ;
2014-01-23 20:11:10 -07:00
cItem DroppedItem ( GetInventory (). GetEquippedItem ());
if ( ! DroppedItem . IsEmpty ())
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
char NewAmount = a_Amount ;
if ( NewAmount > GetInventory (). GetEquippedItem (). m_ItemCount )
{
2014-07-17 22:15:34 +02:00
NewAmount = GetInventory (). GetEquippedItem (). m_ItemCount ; // Drop only what's there
2014-01-23 20:11:10 -07:00
}
2014-01-15 22:36:19 +00:00
2014-01-23 20:11:10 -07:00
GetInventory (). GetHotbarGrid (). ChangeSlotCount ( GetInventory (). GetEquippedSlotNum () /* Returns hotbar subslot, which HotbarGrid takes */ , - a_Amount );
2014-01-23 00:27:39 -07:00
2014-01-23 20:11:10 -07:00
DroppedItem . m_ItemCount = NewAmount ;
Drops . push_back ( DroppedItem );
2013-07-29 12:13:03 +01:00
}
2014-01-23 00:27:39 -07:00
2014-05-12 21:38:52 +03:00
TossItems ( Drops );
2014-01-23 00:27:39 -07:00
}
2020-04-02 12:42:15 +00:00
void cPlayer :: ReplaceOneEquippedItemTossRest ( const cItem & a_Item )
{
auto PlacedCount = GetInventory (). ReplaceOneEquippedItem ( a_Item );
char ItemCountToToss = a_Item . m_ItemCount - static_cast < char > ( PlacedCount );
if ( ItemCountToToss == 0 )
{
return ;
}
cItem Pickup = a_Item ;
Pickup . m_ItemCount = ItemCountToToss ;
TossPickup ( Pickup );
}
2014-01-23 00:27:39 -07:00
void cPlayer :: TossHeldItem ( char a_Amount )
{
cItems Drops ;
2014-01-23 20:11:10 -07:00
cItem & Item = GetDraggingItem ();
if ( ! Item . IsEmpty ())
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
char OriginalItemAmount = Item . m_ItemCount ;
Item . m_ItemCount = std :: min ( OriginalItemAmount , a_Amount );
Drops . push_back ( Item );
2014-05-12 21:38:52 +03:00
2014-01-23 20:11:10 -07:00
if ( OriginalItemAmount > a_Amount )
2013-07-29 12:13:03 +01:00
{
2014-01-23 20:11:10 -07:00
Item . m_ItemCount = OriginalItemAmount - a_Amount ;
2013-07-29 12:13:03 +01:00
}
else
{
2014-01-23 20:11:10 -07:00
Item . Empty ();
2013-07-29 12:13:03 +01:00
}
}
2014-01-23 20:11:10 -07:00
2014-05-12 21:38:52 +03:00
TossItems ( Drops );
2014-01-23 00:27:39 -07:00
}
void cPlayer :: TossPickup ( const cItem & a_Item )
{
cItems Drops ;
2014-01-23 20:11:10 -07:00
Drops . push_back ( a_Item );
2014-01-15 22:36:19 +00:00
2014-05-12 21:38:52 +03:00
TossItems ( Drops );
}
2021-04-06 12:26:43 +01:00
void cPlayer :: LoadFromDisk ()
2013-07-29 12:13:03 +01:00
{
2014-08-19 17:34:11 +02:00
LoadRank ();
2013-07-29 12:13:03 +01:00
2021-03-28 22:34:54 +01:00
const auto & UUID = GetUUID ();
2015-05-23 19:31:33 +01:00
2021-03-28 22:34:54 +01:00
// Load from the UUID file:
2021-04-06 12:26:43 +01:00
if ( LoadFromFile ( GetUUIDFileName ( UUID )))
2018-07-20 00:30:09 +01:00
{
2021-03-28 22:34:54 +01:00
return ;
2018-07-20 00:30:09 +01:00
}
2021-03-28 22:34:54 +01:00
// Player not found:
2021-04-06 12:26:43 +01:00
m_World = cRoot :: Get () -> GetDefaultWorld ();
2021-03-28 22:34:54 +01:00
LOG ( "Player \" %s \" (%s) data not found, resetting to defaults" , GetName (). c_str (), UUID . ToShortString (). c_str ());
2015-05-23 19:31:33 +01:00
2021-04-06 12:26:43 +01:00
const Vector3i WorldSpawn ( static_cast < int > ( m_World -> GetSpawnX ()), static_cast < int > ( m_World -> GetSpawnY ()), static_cast < int > ( m_World -> GetSpawnZ ()));
2021-03-28 22:34:54 +01:00
SetPosition ( WorldSpawn );
2021-04-06 12:26:43 +01:00
SetBedPos ( WorldSpawn , m_World );
2015-05-23 19:31:33 +01:00
2021-03-28 22:34:54 +01:00
m_Inventory . Clear ();
m_EnchantmentSeed = GetRandomProvider (). RandInt < unsigned int > (); // Use a random number to seed the enchantment generator
2014-07-20 10:46:45 +01:00
2021-03-28 22:34:54 +01:00
FLOGD ( "Player \" {0} \" is connecting for the first time, spawning at default world spawn {1:.2f}" , GetName (), GetPosition ());
2014-07-11 00:06:05 +02:00
}
2013-07-29 12:13:03 +01:00
2014-07-11 00:06:05 +02:00
2013-07-29 12:13:03 +01:00
2014-07-11 00:06:05 +02:00
2013-07-29 12:13:03 +01:00
2021-04-06 12:26:43 +01:00
bool cPlayer :: LoadFromFile ( const AString & a_FileName )
2014-07-11 00:06:05 +02:00
{
2021-03-28 22:34:54 +01:00
Json :: Value Root ;
try
2013-07-29 12:13:03 +01:00
{
2021-03-28 22:34:54 +01:00
// Load the data from the file and parse:
InputFileStream ( a_FileName ) >> Root ;
2013-07-29 12:13:03 +01:00
}
2021-03-28 22:34:54 +01:00
catch ( const Json :: Exception & Oops )
2013-07-29 12:13:03 +01:00
{
2021-03-28 22:34:54 +01:00
// Parse failure:
throw std :: runtime_error ( Oops . what ());
2013-07-29 12:13:03 +01:00
}
2021-03-28 22:34:54 +01:00
catch ( const InputFileStream :: failure & )
2013-07-29 12:13:03 +01:00
{
2021-03-28 22:34:54 +01:00
if ( errno == ENOENT )
{
// This is a new player whom we haven't seen yet, bail out, let them have the defaults:
return false ;
}
throw ;
2013-07-29 12:13:03 +01:00
}
2014-07-11 00:06:05 +02:00
// Load the player data:
2021-03-28 22:34:54 +01:00
Json :: Value & JSON_PlayerPosition = Root [ "position" ];
2013-07-29 12:13:03 +01:00
if ( JSON_PlayerPosition . size () == 3 )
{
2015-07-29 09:04:03 -06:00
SetPosX ( JSON_PlayerPosition [ 0 ]. asDouble ());
SetPosY ( JSON_PlayerPosition [ 1 ]. asDouble ());
SetPosZ ( JSON_PlayerPosition [ 2 ]. asDouble ());
2015-07-21 21:25:37 +01:00
m_LastPosition = GetPosition ();
2013-07-29 12:13:03 +01:00
}
2021-03-28 22:34:54 +01:00
Json :: Value & JSON_PlayerRotation = Root [ "rotation" ];
2013-07-29 12:13:03 +01:00
if ( JSON_PlayerRotation . size () == 3 )
{
2015-07-29 09:04:03 -06:00
SetYaw ( static_cast < float > ( JSON_PlayerRotation [ 0 ]. asDouble ()));
SetPitch ( static_cast < float > ( JSON_PlayerRotation [ 1 ]. asDouble ()));
SetRoll ( static_cast < float > ( JSON_PlayerRotation [ 2 ]. asDouble ()));
2013-07-29 12:13:03 +01:00
}
2021-03-28 22:34:54 +01:00
m_Health = Root . get ( "health" , 0 ). asFloat ();
m_AirLevel = Root . get ( "air" , MAX_AIR_LEVEL ). asInt ();
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 ();
m_LifetimeTotalXp = Root . get ( "xpTotal" , 0 ). asInt ();
m_CurrentXp = Root . get ( "xpCurrent" , 0 ). asInt ();
m_IsFlying = Root . get ( "isflying" , 0 ). asBool ();
m_EnchantmentSeed = Root . get ( "enchantmentSeed" , GetRandomProvider (). RandInt < unsigned int > ()). asUInt ();
2013-07-29 12:13:03 +01:00
2021-03-28 22:34:54 +01:00
Json :: Value & JSON_KnownItems = Root [ "knownItems" ];
2020-07-14 18:56:42 +02:00
for ( UInt32 i = 0 ; i < JSON_KnownItems . size (); i ++ )
{
cItem Item ;
Item . FromJson ( JSON_KnownItems [ i ]);
m_KnownItems . insert ( Item );
}
const auto & RecipeNameMap = cRoot :: Get () -> GetCraftingRecipes () -> GetRecipeNameMap ();
2021-03-28 22:34:54 +01:00
Json :: Value & JSON_KnownRecipes = Root [ "knownRecipes" ];
2020-07-14 18:56:42 +02:00
for ( UInt32 i = 0 ; i < JSON_KnownRecipes . size (); i ++ )
{
auto RecipeId = RecipeNameMap . find ( JSON_KnownRecipes [ i ]. asString ());
if ( RecipeId != RecipeNameMap . end ())
{
m_KnownRecipes . insert ( RecipeId -> second );
}
}
2021-03-28 22:34:54 +01:00
m_GameMode = static_cast < eGameMode > ( Root . get ( "gamemode" , eGameMode_NotSet ). asInt ());
2013-12-19 20:53:47 +00:00
if ( m_GameMode == eGameMode_Creative )
{
2021-04-03 19:45:20 +02:00
m_IsFlightCapable = true ;
2013-12-19 20:53:47 +00:00
}
2015-05-23 19:31:33 +01:00
2021-03-28 22:34:54 +01:00
m_Inventory . LoadFromJson ( Root [ "inventory" ]);
2018-07-23 06:23:33 +08:00
2021-03-28 22:34:54 +01:00
int equippedSlotNum = Root . get ( "equippedItemSlot" , 0 ). asInt ();
2018-07-23 06:23:33 +08:00
m_Inventory . SetEquippedSlotNum ( equippedSlotNum );
2021-03-28 22:34:54 +01:00
cEnderChestEntity :: LoadFromJson ( Root [ "enderchestinventory" ], m_EnderChestContents );
2013-07-29 12:13:03 +01:00
2021-03-28 22:34:54 +01:00
m_CurrentWorldName = Root . get ( "world" , "world" ). asString ();
2021-04-06 12:26:43 +01:00
m_World = cRoot :: Get () -> GetWorld ( m_CurrentWorldName );
if ( m_World == nullptr )
2014-09-06 15:26:20 +02:00
{
2021-04-06 12:26:43 +01:00
m_World = cRoot :: Get () -> GetDefaultWorld ();
2014-09-06 15:26:20 +02:00
}
2014-07-20 10:46:45 +01:00
2021-04-06 12:26:43 +01:00
m_LastBedPos . x = Root . get ( "SpawnX" , m_World -> GetSpawnX ()). asInt ();
m_LastBedPos . y = Root . get ( "SpawnY" , m_World -> GetSpawnY ()). asInt ();
m_LastBedPos . z = Root . get ( "SpawnZ" , m_World -> GetSpawnZ ()). asInt ();
2021-03-28 22:34:54 +01:00
m_SpawnWorldName = Root . get ( "SpawnWorld" , cRoot :: Get () -> GetDefaultWorld () -> GetName ()). asString ();
2014-05-11 20:30:54 +03:00
2020-08-12 09:54:36 +01:00
try
{
// Load the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
2021-01-06 00:35:42 +00:00
StatSerializer :: Load ( m_Stats , m_DefaultWorldPath , GetUUID (). ToLongString ());
2020-08-12 09:54:36 +01:00
}
catch (...)
{
LOGWARNING ( "Failed loading player statistics" );
}
2015-05-23 19:31:33 +01:00
2018-09-24 21:33:39 +01:00
FLOGD ( "Player {0} was read from file \" {1} \" , spawning at {2:.2f} in world \" {3} \" " ,
2021-04-06 12:26:43 +01:00
GetName (), a_FileName , GetPosition (), m_World -> GetName ()
2013-07-29 12:13:03 +01:00
);
2015-05-23 19:31:33 +01:00
2013-07-29 12:13:03 +01:00
return true ;
}
2017-10-21 17:56:09 +01:00
void cPlayer :: OpenHorseInventory ()
{
if (
( m_AttachedTo == nullptr ) ||
! m_AttachedTo -> IsMob ()
)
{
return ;
}
auto & Mob = static_cast < cMonster &> ( * m_AttachedTo );
if ( Mob . GetMobType () != mtHorse )
{
return ;
}
auto & Horse = static_cast < cHorse &> ( Mob );
// The client sends requests for untame horses as well but shouldn't actually open
if ( Horse . IsTame ())
{
Horse . PlayerOpenWindow ( * this );
}
}
2021-03-28 22:34:54 +01:00
void cPlayer :: SaveToDisk ()
2013-07-29 12:13:03 +01:00
{
2021-03-28 22:34:54 +01:00
const auto & UUID = GetUUID ();
cFile :: CreateFolderRecursive ( GetUUIDFolderName ( UUID ));
2013-07-29 12:13:03 +01:00
// create the JSON data
Json :: Value JSON_PlayerPosition ;
JSON_PlayerPosition . append ( Json :: Value ( GetPosX ()));
JSON_PlayerPosition . append ( Json :: Value ( GetPosY ()));
JSON_PlayerPosition . append ( Json :: Value ( GetPosZ ()));
Json :: Value JSON_PlayerRotation ;
2014-01-17 11:11:17 +01:00
JSON_PlayerRotation . append ( Json :: Value ( GetYaw ()));
2013-07-29 12:13:03 +01:00
JSON_PlayerRotation . append ( Json :: Value ( GetPitch ()));
JSON_PlayerRotation . append ( Json :: Value ( GetRoll ()));
Json :: Value JSON_Inventory ;
m_Inventory . SaveToJson ( JSON_Inventory );
2014-06-29 11:36:38 +01:00
Json :: Value JSON_EnderChestInventory ;
cEnderChestEntity :: SaveToJson ( JSON_EnderChestInventory , m_EnderChestContents );
2020-07-14 18:56:42 +02:00
Json :: Value JSON_KnownItems ;
for ( const auto & KnownItem : m_KnownItems )
{
Json :: Value JSON_Item ;
KnownItem . GetJson ( JSON_Item );
JSON_KnownItems . append ( JSON_Item );
}
Json :: Value JSON_KnownRecipes ;
for ( auto KnownRecipe : m_KnownRecipes )
{
auto Recipe = cRoot :: Get () -> GetCraftingRecipes () -> GetRecipeById ( KnownRecipe );
JSON_KnownRecipes . append ( Recipe -> m_RecipeName );
}
2013-07-29 12:13:03 +01:00
Json :: Value root ;
2014-07-02 18:51:37 +01:00
root [ "position" ] = JSON_PlayerPosition ;
root [ "rotation" ] = JSON_PlayerRotation ;
root [ "inventory" ] = JSON_Inventory ;
2020-07-14 18:56:42 +02:00
root [ "knownItems" ] = JSON_KnownItems ;
root [ "knownRecipes" ] = JSON_KnownRecipes ;
2018-07-23 06:23:33 +08:00
root [ "equippedItemSlot" ] = m_Inventory . GetEquippedSlotNum ();
2014-06-29 11:36:38 +01:00
root [ "enderchestinventory" ] = JSON_EnderChestInventory ;
2014-07-02 18:51:37 +01:00
root [ "health" ] = m_Health ;
root [ "xpTotal" ] = m_LifetimeTotalXp ;
root [ "xpCurrent" ] = m_CurrentXp ;
root [ "air" ] = m_AirLevel ;
root [ "food" ] = m_FoodLevel ;
root [ "foodSaturation" ] = m_FoodSaturationLevel ;
root [ "foodTickTimer" ] = m_FoodTickTimer ;
root [ "foodExhaustion" ] = m_FoodExhaustionLevel ;
root [ "isflying" ] = IsFlying ();
2014-07-11 13:13:10 +02:00
root [ "lastknownname" ] = GetName ();
2014-07-18 20:12:27 +01:00
root [ "SpawnX" ] = GetLastBedPos (). x ;
root [ "SpawnY" ] = GetLastBedPos (). y ;
root [ "SpawnZ" ] = GetLastBedPos (). z ;
2021-01-06 00:35:42 +00:00
root [ "SpawnWorld" ] = m_SpawnWorldName ;
2020-10-02 00:33:32 +03:00
root [ "enchantmentSeed" ] = m_EnchantmentSeed ;
2021-01-06 00:35:42 +00:00
root [ "world" ] = m_CurrentWorldName ;
root [ "gamemode" ] = static_cast < int > ( m_GameMode );
2013-07-29 12:13:03 +01:00
2020-05-09 15:51:15 +01:00
auto JsonData = JsonUtils :: WriteStyledString ( root );
2021-03-28 22:34:54 +01:00
AString SourceFile = GetUUIDFileName ( UUID );
2013-07-29 12:13:03 +01:00
cFile f ;
if ( ! f . Open ( SourceFile , cFile :: fmWrite ))
{
2021-03-28 22:34:54 +01:00
LOGWARNING ( "Error writing player \" %s \" to file \" %s \" : cannot open file. Player will lose their progress" ,
2014-07-11 13:13:10 +02:00
GetName (). c_str (), SourceFile . c_str ()
);
2021-03-28 22:34:54 +01:00
return ;
2013-07-29 12:13:03 +01:00
}
2015-03-01 03:45:06 +10:30
if ( f . Write ( JsonData . c_str (), JsonData . size ()) != static_cast < int > ( JsonData . size ()))
2013-07-29 12:13:03 +01:00
{
2021-03-28 22:34:54 +01:00
LOGWARNING ( "Error writing player \" %s \" to file \" %s \" : cannot save data. Player will lose their progress" ,
2014-07-11 13:13:10 +02:00
GetName (). c_str (), SourceFile . c_str ()
2014-07-17 22:50:58 +02:00
);
2021-03-28 22:34:54 +01:00
return ;
2013-07-29 12:13:03 +01:00
}
2014-05-11 20:30:54 +03:00
2020-08-12 09:54:36 +01:00
try
{
// Save the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
2021-01-06 00:35:42 +00:00
// TODO: save together with player.dat, not in some other place.
StatSerializer :: Save ( m_Stats , m_DefaultWorldPath , GetUUID (). ToLongString ());
2020-08-12 09:54:36 +01:00
}
catch (...)
2014-05-11 20:30:54 +03:00
{
2021-03-28 22:34:54 +01:00
LOGWARNING ( "Error writing player \" %s \" statistics to file" , GetName (). c_str ());
2014-05-11 20:30:54 +03:00
}
2013-07-29 12:13:03 +01:00
}
2018-01-05 11:28:06 +00:00
void cPlayer :: UseEquippedItem ( short a_Damage )
2013-07-29 12:13:03 +01:00
{
2018-01-05 11:28:06 +00:00
// No durability loss in creative or spectator modes:
if ( IsGameModeCreative () || IsGameModeSpectator ())
2013-07-29 12:13:03 +01:00
{
return ;
}
2013-11-02 14:08:00 +00:00
2018-07-13 00:10:15 +02:00
UseItem ( cInventory :: invHotbarOffset + m_Inventory . GetEquippedSlotNum (), a_Damage );
2013-07-29 12:13:03 +01:00
}
2013-08-14 10:33:26 +02:00
2018-01-05 11:28:06 +00:00
void cPlayer :: UseEquippedItem ( cItemHandler :: eDurabilityLostAction a_Action )
{
// Get item being used:
cItem Item = GetEquippedItem ();
// Get base damage for action type:
short Dmg = cItemHandler :: GetItemHandler ( Item ) -> GetDurabilityLossByAction ( a_Action );
UseEquippedItem ( Dmg );
}
2018-07-13 00:10:15 +02:00
void cPlayer :: UseItem ( int a_SlotNumber , short a_Damage )
{
const cItem & Item = m_Inventory . GetSlot ( a_SlotNumber );
if ( Item . IsEmpty ())
{
return ;
}
// Ref: https://minecraft.gamepedia.com/Enchanting#Unbreaking
unsigned int UnbreakingLevel = Item . m_Enchantments . GetLevel ( cEnchantments :: enchUnbreaking );
double chance = ItemCategory :: IsArmor ( Item . m_ItemType )
? ( 0.6 + ( 0.4 / ( UnbreakingLevel + 1 ))) : ( 1.0 / ( UnbreakingLevel + 1 ));
// When durability is reduced by multiple points
// Unbreaking is applied for each point of reduction.
std :: binomial_distribution < short > Dist ( a_Damage , chance );
short ReducedDamage = Dist ( GetRandomProvider (). Engine ());
if ( m_Inventory . DamageItem ( a_SlotNumber , ReducedDamage ))
{
m_World -> BroadcastSoundEffect ( "entity.item.break" , GetPosition (), 0.5f , static_cast < float > ( 0.75 + ( static_cast < float > (( GetUniqueID () * 23 ) % 32 )) / 64 ));
}
}
2013-07-29 12:13:03 +01:00
void cPlayer :: HandleFood ( void )
{
2017-08-24 12:19:40 +03:00
// Ref.: https://minecraft.gamepedia.com/Hunger
2014-06-30 15:12:56 +02:00
2014-12-02 20:25:41 -05:00
if ( IsGameModeCreative () || IsGameModeSpectator ())
2014-02-11 15:34:18 +01:00
{
2014-12-02 20:25:41 -05:00
// Hunger is disabled for Creative and Spectator
2014-02-11 15:34:18 +01:00
return ;
}
2014-06-30 15:12:56 +02:00
2014-07-31 23:04:00 +02:00
// Apply food exhaustion that has accumulated:
if ( m_FoodExhaustionLevel > 4.0 )
{
m_FoodExhaustionLevel -= 4.0 ;
if ( m_FoodSaturationLevel > 0.0 )
{
m_FoodSaturationLevel = std :: max ( m_FoodSaturationLevel - 1.0 , 0.0 );
}
else
{
SetFoodLevel ( m_FoodLevel - 1 );
}
}
2013-07-29 12:13:03 +01:00
// Heal or damage, based on the food level, using the m_FoodTickTimer:
2014-07-31 23:04:00 +02:00
if (( m_FoodLevel >= 18 ) || ( m_FoodLevel <= 0 ))
2013-07-29 12:13:03 +01:00
{
m_FoodTickTimer ++ ;
if ( m_FoodTickTimer >= 80 )
{
m_FoodTickTimer = 0 ;
2014-07-31 23:04:00 +02:00
if (( m_FoodLevel >= 18 ) && ( GetHealth () < GetMaxHealth ()))
2013-07-29 12:13:03 +01:00
{
// Regenerate health from food, incur 3 pts of food exhaustion:
Heal ( 1 );
2014-07-31 23:04:00 +02:00
AddFoodExhaustion ( 3.0 );
2013-07-29 12:13:03 +01:00
}
2014-02-16 13:37:36 +00:00
else if (( m_FoodLevel <= 0 ) && ( m_Health > 1 ))
2013-07-29 12:13:03 +01:00
{
// Damage from starving
2014-10-20 21:55:07 +01:00
TakeDamage ( dtStarving , nullptr , 1 , 1 , 0 );
2013-07-29 12:13:03 +01:00
}
}
}
2014-07-31 23:04:00 +02:00
else
2013-07-29 12:13:03 +01:00
{
2014-07-31 23:04:00 +02:00
m_FoodTickTimer = 0 ;
2013-07-29 12:13:03 +01:00
}
}
2013-12-21 17:31:05 +01:00
void cPlayer :: HandleFloater ()
{
if ( GetEquippedItem (). m_ItemType == E_ITEM_FISHING_ROD )
{
return ;
}
2017-09-11 22:20:49 +01:00
m_World -> DoWithEntityByID ( m_FloaterID , []( cEntity & a_Entity )
2013-12-21 17:31:05 +01:00
{
2020-03-05 12:52:34 +02:00
a_Entity . Destroy ();
2013-12-21 17:31:05 +01:00
return true ;
}
2017-09-11 22:20:49 +01:00
);
2013-12-21 17:31:05 +01:00
SetIsFishing ( false );
}
2014-05-20 15:52:59 +03:00
bool cPlayer :: IsClimbing ( void ) const
{
2021-03-15 02:28:18 +00:00
const auto Position = GetPosition (). Floor ();
2014-05-20 15:52:59 +03:00
2021-03-15 02:28:18 +00:00
if ( ! cChunkDef :: IsValidHeight ( Position . y ))
2014-05-20 15:52:59 +03:00
{
return false ;
}
2021-03-15 02:28:18 +00:00
BLOCKTYPE Block = m_World -> GetBlock ( Position );
2014-05-20 15:52:59 +03:00
switch ( Block )
{
case E_BLOCK_LADDER :
case E_BLOCK_VINES :
{
return true ;
}
default : return false ;
}
}
2015-07-21 21:25:37 +01:00
void cPlayer :: UpdateMovementStats ( const Vector3d & a_DeltaPos , bool a_PreviousIsOnGround )
2014-05-12 21:38:52 +03:00
{
2021-04-03 19:45:20 +02:00
if ( m_IsTeleporting )
2015-07-21 21:25:37 +01:00
{
2021-04-03 19:45:20 +02:00
m_IsTeleporting = false ;
2015-07-21 21:25:37 +01:00
return ;
}
2014-05-12 21:38:52 +03:00
2020-08-12 09:54:36 +01:00
const auto Value = FloorC < cStatManager :: StatValue > ( a_DeltaPos . Length () * 100 + 0.5 );
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo == nullptr )
2014-05-12 21:38:52 +03:00
{
2015-07-21 21:25:37 +01:00
if ( IsFlying ())
{
2020-08-12 09:54:36 +01:00
m_Stats . AddValue ( Statistic :: FlyOneCm , Value );
2015-07-21 21:25:37 +01:00
// May be flying and doing any of the following:
}
2014-05-20 15:52:59 +03:00
if ( IsClimbing ())
2014-05-12 21:38:52 +03:00
{
2014-07-17 22:15:34 +02:00
if ( a_DeltaPos . y > 0.0 ) // Going up
2014-05-20 15:52:59 +03:00
{
2020-08-12 09:54:36 +01:00
m_Stats . AddValue ( Statistic :: ClimbOneCm , FloorC < cStatManager :: StatValue > ( a_DeltaPos . y * 100 + 0.5 ));
2014-05-20 15:52:59 +03:00
}
2014-05-12 21:38:52 +03:00
}
2018-01-14 18:44:45 +00:00
else if ( IsInWater ())
2014-05-20 15:52:59 +03:00
{
2020-09-05 17:13:44 +02:00
if ( m_IsHeadInWater )
{
m_Stats . AddValue ( Statistic :: WalkUnderWaterOneCm , Value );
}
else
{
m_Stats . AddValue ( Statistic :: WalkOnWaterOneCm , Value );
}
2015-03-01 03:45:06 +10:30
AddFoodExhaustion ( 0.00015 * static_cast < double > ( Value ));
2014-05-20 15:52:59 +03:00
}
else if ( IsOnGround ())
2014-05-12 21:38:52 +03:00
{
2020-09-05 17:13:44 +02:00
if ( IsCrouched ())
{
m_Stats . AddValue ( Statistic :: CrouchOneCm , Value );
AddFoodExhaustion ( 0.0001 * static_cast < double > ( Value ));
}
if ( IsSprinting ())
{
m_Stats . AddValue ( Statistic :: SprintOneCm , Value );
AddFoodExhaustion ( 0.001 * static_cast < double > ( Value ));
}
else
{
m_Stats . AddValue ( Statistic :: WalkOneCm , Value );
AddFoodExhaustion ( 0.0001 * static_cast < double > ( Value ));
}
2014-05-12 21:38:52 +03:00
}
2014-05-20 15:52:59 +03:00
else
{
2015-07-21 21:25:37 +01:00
// If a jump just started, process food exhaustion:
if (( a_DeltaPos . y > 0.0 ) && a_PreviousIsOnGround )
{
2020-08-12 09:54:36 +01:00
m_Stats . AddValue ( Statistic :: Jump , 1 );
2015-07-21 21:25:37 +01:00
AddFoodExhaustion (( IsSprinting () ? 0.008 : 0.002 ) * static_cast < double > ( Value ));
}
else if ( a_DeltaPos . y < 0.0 )
2014-05-20 15:52:59 +03:00
{
2015-07-21 21:25:37 +01:00
// Increment statistic
2020-08-12 09:54:36 +01:00
m_Stats . AddValue ( Statistic :: FallOneCm , static_cast < cStatManager :: StatValue > ( std :: abs ( a_DeltaPos . y ) * 100 + 0.5 ));
2014-05-20 15:52:59 +03:00
}
2015-07-21 21:25:37 +01:00
// TODO: good opportunity to detect illegal flight (check for falling tho)
2014-05-20 15:52:59 +03:00
}
2014-05-12 21:38:52 +03:00
}
else
{
switch ( m_AttachedTo -> GetEntityType ())
{
2020-08-12 09:54:36 +01:00
case cEntity :: etMinecart : m_Stats . AddValue ( Statistic :: MinecartOneCm , Value ); break ;
case cEntity :: etBoat : m_Stats . AddValue ( Statistic :: BoatOneCm , Value ); break ;
2014-05-12 21:38:52 +03:00
case cEntity :: etMonster :
{
2018-05-02 08:50:36 +01:00
cMonster * Monster = static_cast < cMonster *> ( m_AttachedTo );
2014-05-12 21:38:52 +03:00
switch ( Monster -> GetMobType ())
{
2020-08-12 09:54:36 +01:00
case mtPig : m_Stats . AddValue ( Statistic :: PigOneCm , Value ); break ;
case mtHorse : m_Stats . AddValue ( Statistic :: HorseOneCm , Value ); break ;
2014-05-12 21:38:52 +03:00
default : break ;
}
break ;
}
default : break ;
}
}
}
2014-08-19 17:34:11 +02:00
void cPlayer :: LoadRank ( void )
{
2021-03-28 22:34:54 +01:00
const auto & UUID = GetUUID ();
2014-10-18 19:55:01 +01:00
cRankManager * RankMgr = cRoot :: Get () -> GetRankManager ();
2021-03-28 22:34:54 +01:00
// Load the values from cRankManager:
m_Rank = RankMgr -> GetPlayerRankName ( UUID );
2014-08-24 20:00:45 +02:00
if ( m_Rank . empty ())
{
2014-10-18 19:55:01 +01:00
m_Rank = RankMgr -> GetDefaultRank ();
2014-08-24 20:00:45 +02:00
}
2014-09-28 02:17:32 +02:00
else
{
// Update the name:
2021-03-28 22:34:54 +01:00
RankMgr -> UpdatePlayerName ( UUID , GetName ());
2014-09-28 02:17:32 +02:00
}
2021-03-28 22:34:54 +01:00
m_Permissions = RankMgr -> GetPlayerPermissions ( UUID );
m_Restrictions = RankMgr -> GetPlayerRestrictions ( UUID );
2014-10-18 19:55:01 +01:00
RankMgr -> GetRankVisuals ( m_Rank , m_MsgPrefix , m_MsgSuffix , m_MsgNameColorCode );
2014-08-19 17:34:11 +02:00
// Break up the individual permissions on each dot, into m_SplitPermissions:
m_SplitPermissions . clear ();
m_SplitPermissions . reserve ( m_Permissions . size ());
2015-04-25 19:40:44 +02:00
for ( auto & Permission : m_Permissions )
{
m_SplitPermissions . push_back ( StringSplit ( Permission , "." ));
} // for Permission - m_Permissions[]
// Break up the individual restrictions on each dot, into m_SplitRestrictions:
m_SplitRestrictions . clear ();
m_SplitRestrictions . reserve ( m_Restrictions . size ());
for ( auto & Restriction : m_Restrictions )
2014-08-19 17:34:11 +02:00
{
2015-04-25 19:40:44 +02:00
m_SplitRestrictions . push_back ( StringSplit ( Restriction , "." ));
} // for itr - m_Restrictions[]
2014-08-19 17:34:11 +02:00
}
2014-12-24 07:20:17 +01:00
bool cPlayer :: PlaceBlock ( int a_BlockX , int a_BlockY , int a_BlockZ , BLOCKTYPE a_BlockType , NIBBLETYPE a_BlockMeta )
{
sSetBlockVector blk {{ a_BlockX , a_BlockY , a_BlockZ , a_BlockType , a_BlockMeta }};
return PlaceBlocks ( blk );
}
void cPlayer :: SendBlocksAround ( int a_BlockX , int a_BlockY , int a_BlockZ , int a_Range )
{
// Collect the coords of all the blocks to send:
sSetBlockVector blks ;
for ( int y = a_BlockY - a_Range + 1 ; y < a_BlockY + a_Range ; y ++ )
{
for ( int z = a_BlockZ - a_Range + 1 ; z < a_BlockZ + a_Range ; z ++ )
{
for ( int x = a_BlockX - a_Range + 1 ; x < a_BlockX + a_Range ; x ++ )
{
blks . emplace_back ( x , y , z , E_BLOCK_AIR , 0 ); // Use fake blocktype, it will get set later on.
2019-08-11 11:39:43 +02:00
}
}
2014-12-24 07:20:17 +01:00
} // for y
// Get the values of all the blocks:
if ( ! m_World -> GetBlocks ( blks , false ))
{
LOGD ( "%s: Cannot query all blocks, not sending an update" , __FUNCTION__ );
return ;
}
// Divide the block changes by their respective chunks:
2014-12-24 08:38:37 +01:00
std :: unordered_map < cChunkCoords , sSetBlockVector , cChunkCoordsHash > Changes ;
2014-12-24 07:20:17 +01:00
for ( const auto & blk : blks )
{
Changes [ cChunkCoords ( blk . m_ChunkX , blk . m_ChunkZ )]. push_back ( blk );
} // for blk - blks[]
blks . clear ();
// Send the blocks for each affected chunk:
for ( auto itr = Changes . cbegin (), end = Changes . cend (); itr != end ; ++ itr )
{
m_ClientHandle -> SendBlockChanges ( itr -> first . m_ChunkX , itr -> first . m_ChunkZ , itr -> second );
}
}
2017-07-28 11:59:21 -05:00
bool cPlayer :: DoesPlacingBlocksIntersectEntity ( const sSetBlockVector & a_Blocks )
{
// Compute the bounding box for each block to be placed
std :: vector < cBoundingBox > PlacementBoxes ;
cBoundingBox PlacingBounds ( 0 , 0 , 0 , 0 , 0 , 0 );
bool HasInitializedBounds = false ;
for ( auto blk : a_Blocks )
{
int x = blk . GetX ();
int y = blk . GetY ();
int z = blk . GetZ ();
2020-09-20 14:50:52 +01:00
cBoundingBox BlockBox = cBlockHandler :: For ( blk . m_BlockType ). GetPlacementCollisionBox (
2021-03-15 02:28:18 +00:00
m_World -> GetBlock ({ x - 1 , y , z }),
m_World -> GetBlock ({ x + 1 , y , z }),
( y == 0 ) ? E_BLOCK_AIR : m_World -> GetBlock ({ x , y - 1 , z }),
( y == cChunkDef :: Height - 1 ) ? E_BLOCK_AIR : m_World -> GetBlock ({ x , y + 1 , z }),
m_World -> GetBlock ({ x , y , z - 1 }),
m_World -> GetBlock ({ x , y , z + 1 })
2017-07-28 11:59:21 -05:00
);
BlockBox . Move ( x , y , z );
PlacementBoxes . push_back ( BlockBox );
if ( HasInitializedBounds )
{
PlacingBounds = PlacingBounds . Union ( BlockBox );
}
else
{
PlacingBounds = BlockBox ;
HasInitializedBounds = true ;
}
}
cWorld * World = GetWorld ();
// Check to see if any entity intersects any block being placed
2017-09-11 22:20:49 +01:00
return ! World -> ForEachEntityInBox ( PlacingBounds , [ & ]( cEntity & a_Entity )
2017-07-28 11:59:21 -05:00
{
2017-09-11 22:20:49 +01:00
// The distance inside the block the entity can still be.
const double EPSILON = 0.0005 ;
2017-07-28 11:59:21 -05:00
2017-09-11 22:20:49 +01:00
if ( ! a_Entity . DoesPreventBlockPlacement ())
2017-07-28 11:59:21 -05:00
{
return false ;
}
2020-05-03 20:04:33 +00:00
auto EntBox = a_Entity . GetBoundingBox ();
2017-09-11 22:20:49 +01:00
for ( auto BlockBox : PlacementBoxes )
2017-07-28 11:59:21 -05:00
{
// Put in a little bit of wiggle room
BlockBox . Expand ( - EPSILON , - EPSILON , - EPSILON );
if ( EntBox . DoesIntersect ( BlockBox ))
{
return true ;
}
}
return false ;
}
2017-09-11 22:20:49 +01:00
);
2017-07-28 11:59:21 -05:00
}
2021-03-28 22:34:54 +01:00
const cUUID & cPlayer :: GetUUID ( void ) const
{
return m_ClientHandle -> GetUUID ();
}
2014-12-24 07:20:17 +01:00
bool cPlayer :: PlaceBlocks ( const sSetBlockVector & a_Blocks )
{
2017-07-28 11:59:21 -05:00
if ( DoesPlacingBlocksIntersectEntity ( a_Blocks ))
{
// Abort - re-send all the current blocks in the a_Blocks' coords to the client:
for ( auto blk2 : a_Blocks )
{
2017-07-31 20:50:40 +01:00
m_World -> SendBlockTo ( blk2 . GetX (), blk2 . GetY (), blk2 . GetZ (), * this );
2017-07-28 11:59:21 -05:00
}
return false ;
}
2014-12-24 07:20:17 +01:00
// Call the "placing" hooks; if any fail, abort:
cPluginManager * pm = cPluginManager :: Get ();
for ( auto blk : a_Blocks )
{
if ( pm -> CallHookPlayerPlacingBlock ( * this , blk ))
{
// Abort - re-send all the current blocks in the a_Blocks' coords to the client:
for ( auto blk2 : a_Blocks )
{
2017-07-31 20:50:40 +01:00
m_World -> SendBlockTo ( blk2 . GetX (), blk2 . GetY (), blk2 . GetZ (), * this );
2014-12-24 07:20:17 +01:00
}
return false ;
}
} // for blk - a_Blocks[]
cChunkInterface ChunkInterface ( m_World -> GetChunkMap ());
for ( auto blk : a_Blocks )
{
2020-07-29 19:30:38 +01:00
// Set the blocks:
m_World -> PlaceBlock ( blk . GetAbsolutePos (), blk . m_BlockType , blk . m_BlockMeta );
// Notify the blockhandlers:
2020-09-20 14:50:52 +01:00
cBlockHandler :: For ( blk . m_BlockType ). OnPlacedByPlayer ( ChunkInterface , * m_World , * this , blk );
2014-12-24 07:20:17 +01:00
2020-07-29 19:30:38 +01:00
// Call the "placed" hooks:
2014-12-24 07:20:17 +01:00
pm -> CallHookPlayerPlacedBlock ( * this , blk );
}
2020-07-29 19:30:38 +01:00
2014-12-24 07:20:17 +01:00
return true ;
}
2017-01-03 12:19:29 -08:00
void cPlayer :: SetSkinParts ( int a_Parts )
{
m_SkinParts = a_Parts & spMask ;
m_World -> BroadcastEntityMetadata ( * this , m_ClientHandle . get ());
}
void cPlayer :: SetMainHand ( eMainHand a_Hand )
{
m_MainHand = a_Hand ;
m_World -> BroadcastEntityMetadata ( * this , m_ClientHandle . get ());
}
2016-10-12 14:38:45 +02:00
void cPlayer :: AttachTo ( cEntity * a_AttachTo )
{
// Different attach, if this is a spectator
if ( IsGameModeSpectator ())
{
2020-03-30 22:35:37 +03:00
SpectateEntity ( a_AttachTo );
2016-10-12 14:38:45 +02:00
return ;
}
2020-04-13 18:38:06 +02:00
Super :: AttachTo ( a_AttachTo );
2016-10-12 14:38:45 +02:00
}
2014-01-12 23:23:36 +00:00
void cPlayer :: Detach ()
2021-04-08 06:17:24 -04:00
{
Detach ( false );
}
void cPlayer :: Detach ( bool a_IsTeleporting )
2014-01-12 23:23:36 +00:00
{
2016-10-12 14:38:45 +02:00
if ( m_AttachedTo == nullptr )
{
// The player is not attached to anything. Bail out.
return ;
}
// Different detach, if this is a spectator
if ( IsGameModeSpectator ())
{
GetClientHandle () -> SendCameraSetTo ( * this );
TeleportToEntity ( * m_AttachedTo );
m_AttachedTo = nullptr ;
return ;
}
2020-04-13 18:38:06 +02:00
Super :: Detach ();
2021-04-08 06:17:24 -04:00
// If they are teleporting, no need to figure out position
if ( a_IsTeleporting )
{
return ;
}
2014-04-17 18:50:25 +01:00
int PosX = POSX_TOINT ;
int PosY = POSY_TOINT ;
int PosZ = POSZ_TOINT ;
2014-01-12 23:23:36 +00:00
// Search for a position within an area to teleport player after detachment
2016-05-03 10:48:39 +03:00
// Position must be solid land with two air blocks above.
2014-01-12 23:23:36 +00:00
// If nothing found, player remains where they are
2016-05-03 10:48:39 +03:00
for ( int x = PosX - 1 ; x <= ( PosX + 1 ); ++ x )
2014-01-12 23:23:36 +00:00
{
for ( int y = PosY ; y <= ( PosY + 3 ); ++ y )
{
2016-05-03 10:48:39 +03:00
for ( int z = PosZ - 1 ; z <= ( PosZ + 1 ); ++ z )
2014-01-12 23:23:36 +00:00
{
2016-05-03 10:48:39 +03:00
if (
2021-03-15 02:28:18 +00:00
( m_World -> GetBlock ({ x , y , z }) == E_BLOCK_AIR ) &&
( m_World -> GetBlock ({ x , y + 1 , z }) == E_BLOCK_AIR ) &&
cBlockInfo :: IsSolid ( m_World -> GetBlock ({ x , y - 1 , z }))
2016-05-03 10:48:39 +03:00
)
2014-01-12 23:23:36 +00:00
{
2016-05-03 10:48:39 +03:00
TeleportToCoords ( x + 0.5 , y , z + 0.5 );
2014-01-12 23:23:36 +00:00
return ;
}
}
}
}
2014-01-14 18:16:13 +00:00
}
2014-07-11 13:13:10 +02:00
2017-08-25 13:43:18 +01:00
AString cPlayer :: GetUUIDFileName ( const cUUID & a_UUID )
2014-07-11 13:13:10 +02:00
{
2017-08-25 13:43:18 +01:00
AString UUID = a_UUID . ToLongString ();
2015-05-23 19:31:33 +01:00
2020-05-07 22:14:00 +03:00
AString res ( "players/" );
2014-07-31 22:52:06 +02:00
res . append ( UUID , 0 , 2 );
2014-07-11 13:13:10 +02:00
res . push_back ( '/' );
2014-07-31 22:52:06 +02:00
res . append ( UUID , 2 , AString :: npos );
2014-07-11 13:13:10 +02:00
res . append ( ".json" );
return res ;
}
2016-04-13 15:15:12 +03:00
void cPlayer :: FreezeInternal ( const Vector3d & a_Location , bool a_ManuallyFrozen )
{
SetSpeed ( 0 , 0 , 0 );
SetPosition ( a_Location );
m_IsFrozen = true ;
m_IsManuallyFrozen = a_ManuallyFrozen ;
double NormalMaxSpeed = GetNormalMaxSpeed ();
double SprintMaxSpeed = GetSprintingMaxSpeed ();
double FlyingMaxpeed = GetFlyingMaxSpeed ();
bool IsFlying = m_IsFlying ;
// Set the client-side speed to 0
m_NormalMaxSpeed = 0 ;
m_SprintingMaxSpeed = 0 ;
m_FlyingMaxSpeed = 0 ;
m_IsFlying = true ;
// Send the client its fake speed and max speed of 0
2021-04-06 12:26:43 +01:00
m_ClientHandle -> SendPlayerMoveLook ();
m_ClientHandle -> SendPlayerAbilities ();
m_ClientHandle -> SendEntityVelocity ( * this );
m_World -> BroadcastEntityProperties ( * this );
2016-04-13 15:15:12 +03:00
// Keep the server side speed variables as they were in the first place
m_NormalMaxSpeed = NormalMaxSpeed ;
m_SprintingMaxSpeed = SprintMaxSpeed ;
m_FlyingMaxSpeed = FlyingMaxpeed ;
m_IsFlying = IsFlying ;
}
2016-11-06 19:30:19 +01:00
2018-07-26 22:24:36 +01:00
2016-11-06 19:30:19 +01:00
float cPlayer :: GetLiquidHeightPercent ( NIBBLETYPE a_Meta )
{
if ( a_Meta >= 8 )
{
a_Meta = 0 ;
}
return static_cast < float > ( a_Meta + 1 ) / 9.0f ;
}
bool cPlayer :: IsInsideWater ()
{
2021-04-03 19:45:20 +02:00
BLOCKTYPE Block ;
NIBBLETYPE Meta ;
m_World -> GetBlockTypeMeta ( GetEyePosition (). Floor (), Block , Meta );
2016-11-06 19:30:19 +01:00
if (( Block != E_BLOCK_WATER ) && ( Block != E_BLOCK_STATIONARY_WATER ))
{
return false ;
}
2021-04-03 19:45:20 +02:00
const auto EyeHeight = GetEyeHeight ();
2016-11-06 19:30:19 +01:00
float f = GetLiquidHeightPercent ( Meta ) - 0.11111111f ;
2021-04-03 19:45:20 +02:00
float f1 = static_cast < float > ( EyeHeight + 1 ) - f ;
return EyeHeight < f1 ;
2016-11-06 19:30:19 +01:00
}
float cPlayer :: GetDigSpeed ( BLOCKTYPE a_Block )
{
2020-10-02 23:57:17 +03:00
// Based on: https://minecraft.gamepedia.com/Breaking#Speed
// Get the base speed multiplier of the equipped tool for the mined block
float MiningSpeed = GetEquippedItem (). GetHandler () -> GetBlockBreakingStrength ( a_Block );
// If we can harvest the block then we can apply material and enchantment bonuses
if ( GetEquippedItem (). GetHandler () -> CanHarvestBlock ( a_Block ))
2016-11-06 19:30:19 +01:00
{
2020-10-02 23:57:17 +03:00
if ( MiningSpeed > 1.0f ) // If the base multiplier for this block is greater than 1, now we can check enchantments
2016-11-06 19:30:19 +01:00
{
2020-10-02 23:57:17 +03:00
unsigned int EfficiencyModifier = GetEquippedItem (). m_Enchantments . GetLevel ( cEnchantments :: eEnchantment :: enchEfficiency );
if ( EfficiencyModifier > 0 ) // If an efficiency enchantment is present, apply formula as on wiki
{
MiningSpeed += ( EfficiencyModifier * EfficiencyModifier ) + 1 ;
}
2016-11-06 19:30:19 +01:00
}
}
2020-10-02 23:57:17 +03:00
else // If we can't harvest the block then no bonuses:
{
MiningSpeed = 1 ;
}
2016-11-06 19:30:19 +01:00
2020-10-02 23:57:17 +03:00
// Haste increases speed by 20% per level
2017-08-01 18:51:43 +01:00
auto Haste = GetEntityEffect ( cEntityEffect :: effHaste );
if ( Haste != nullptr )
2016-11-06 19:30:19 +01:00
{
2017-08-01 18:51:43 +01:00
int intensity = Haste -> GetIntensity () + 1 ;
2020-10-02 23:57:17 +03:00
MiningSpeed *= 1.0f + ( intensity * 0.2f );
2016-11-06 19:30:19 +01:00
}
2020-10-02 23:57:17 +03:00
// Mining fatigue decreases speed a lot
2017-08-01 18:51:43 +01:00
auto MiningFatigue = GetEntityEffect ( cEntityEffect :: effMiningFatigue );
if ( MiningFatigue != nullptr )
2016-11-06 19:30:19 +01:00
{
2017-08-01 18:51:43 +01:00
int intensity = MiningFatigue -> GetIntensity ();
2016-11-06 19:30:19 +01:00
switch ( intensity )
{
2020-10-02 23:57:17 +03:00
case 0 : MiningSpeed *= 0.3f ; break ;
case 1 : MiningSpeed *= 0.09f ; break ;
case 2 : MiningSpeed *= 0.0027f ; break ;
default : MiningSpeed *= 0.00081f ; break ;
2016-11-06 19:30:19 +01:00
}
}
2020-10-02 23:57:17 +03:00
// 5x speed loss for being in water
2016-11-06 19:30:19 +01:00
if ( IsInsideWater () && ! ( GetEquippedItem (). m_Enchantments . GetLevel ( cEnchantments :: eEnchantment :: enchAquaAffinity ) > 0 ))
{
2020-10-02 23:57:17 +03:00
MiningSpeed /= 5.0f ;
2016-11-06 19:30:19 +01:00
}
2020-10-02 23:57:17 +03:00
// 5x speed loss for not touching ground
2016-11-06 19:30:19 +01:00
if ( ! IsOnGround ())
{
2020-10-02 23:57:17 +03:00
MiningSpeed /= 5.0f ;
2016-11-06 19:30:19 +01:00
}
2020-10-02 23:57:17 +03:00
return MiningSpeed ;
2016-11-06 19:30:19 +01:00
}
2020-10-02 23:57:17 +03:00
float cPlayer :: GetMiningProgressPerTick ( BLOCKTYPE a_Block )
2016-11-06 19:30:19 +01:00
{
2020-10-02 23:57:17 +03:00
// Based on https://minecraft.gamepedia.com/Breaking#Calculation
// If we know it's instantly breakable then quit here:
if ( cBlockInfo :: IsOneHitDig ( a_Block ))
{
return 1 ;
}
2020-11-06 14:58:47 +00:00
const bool CanHarvest = GetEquippedItem (). GetHandler () -> CanHarvestBlock ( a_Block );
const float BlockHardness = cBlockInfo :: GetHardness ( a_Block ) * ( CanHarvest ? 1.5f : 5.0f );
2020-10-02 23:57:17 +03:00
ASSERT ( BlockHardness > 0 ); // Can't divide by 0 or less, IsOneHitDig should have returned true
2020-11-06 14:58:47 +00:00
2020-10-02 23:57:17 +03:00
// LOGD("Time to mine block = %f", BlockHardness/DigSpeed);
// Number of ticks to mine = (20 * BlockHardness)/DigSpeed;
// Therefore take inverse to get fraction mined per tick:
2020-11-06 14:58:47 +00:00
return GetDigSpeed ( a_Block ) / ( 20.0f * BlockHardness );
2020-10-02 23:57:17 +03:00
}
bool cPlayer :: CanInstantlyMine ( BLOCKTYPE a_Block )
{
// Based on: https://minecraft.gamepedia.com/Breaking#Calculation
2020-11-06 14:58:47 +00:00
// If the dig speed is greater than 30 times the hardness, then the wiki says we can instantly mine:
return GetDigSpeed ( a_Block ) > ( 30 * cBlockInfo :: GetHardness ( a_Block ));
2016-11-06 19:30:19 +01:00
}
2018-07-23 17:24:00 +08:00
2021-02-06 18:37:03 +00:00
void cPlayer :: AddKnownItem ( const cItem & a_Item )
2018-07-23 17:24:00 +08:00
{
2021-02-06 18:37:03 +00:00
if ( a_Item . m_ItemType < 0 )
{
return ;
}
auto Response = m_KnownItems . insert ( a_Item . CopyOne ());
if ( ! Response . second )
2018-07-23 17:24:00 +08:00
{
2021-02-06 18:37:03 +00:00
// The item was already known, bail out:
return ;
2018-07-23 17:24:00 +08:00
}
2021-02-06 18:37:03 +00:00
// Process the recipes that got unlocked by this newly-known item:
auto Recipes = cRoot :: Get () -> GetCraftingRecipes () -> FindNewRecipesForItem ( a_Item , m_KnownItems );
for ( const auto & RecipeId : Recipes )
{
AddKnownRecipe ( RecipeId );
}
}
2021-04-03 19:45:20 +02:00
void cPlayer :: SetSize ( const float a_Width , const float a_Height )
{
m_Width = a_Width ;
m_Height = a_Height ;
}
2021-02-06 18:37:03 +00:00
void cPlayer :: AddKnownRecipe ( UInt32 a_RecipeId )
{
auto Response = m_KnownRecipes . insert ( a_RecipeId );
if ( ! Response . second )
{
// The recipe was already known, bail out:
return ;
}
m_ClientHandle -> SendUnlockRecipe ( a_RecipeId );
}
void cPlayer :: TickFreezeCode ()
{
if ( m_IsFrozen )
{
if (( ! m_IsManuallyFrozen ) && ( GetClientHandle () -> IsPlayerChunkSent ()))
{
// If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent
Unfreeze ();
// Pull the player out of any solids that might have loaded on them.
PREPARE_REL_AND_CHUNK ( GetPosition (), * ( GetParentChunk ()));
if ( RelSuccess )
{
int NewY = Rel . y ;
if ( NewY < 0 )
{
NewY = 0 ;
}
while ( NewY < cChunkDef :: Height - 2 )
{
// If we find a position with enough space for the player
if (
! cBlockInfo :: IsSolid ( Chunk -> GetBlock ( Rel . x , NewY , Rel . z )) &&
! cBlockInfo :: IsSolid ( Chunk -> GetBlock ( Rel . x , NewY + 1 , Rel . z ))
)
{
// If the found position is not the same as the original
if ( NewY != Rel . y )
{
SetPosition ( GetPosition (). x , NewY , GetPosition (). z );
GetClientHandle () -> SendPlayerPosition ();
}
break ;
}
++ NewY ;
}
}
}
2021-04-05 01:38:43 +01:00
else if (( GetWorld () -> GetWorldTickAge () % 100 _tick ) == 0 _tick )
2021-02-06 18:37:03 +00:00
{
// Despite the client side freeze, the player may be able to move a little by
// Jumping or canceling flight. Re-freeze every now and then
FreezeInternal ( GetPosition (), m_IsManuallyFrozen );
}
}
else
{
if ( ! GetClientHandle () -> IsPlayerChunkSent () || ( ! GetParentChunk () -> IsValid ()))
{
FreezeInternal ( GetPosition (), false );
}
}
}
void cPlayer :: ApplyArmorDamage ( int a_DamageBlocked )
{
short ArmorDamage = static_cast < short > ( std :: max ( a_DamageBlocked / 4 , 1 ));
for ( int i = 0 ; i < 4 ; i ++ )
{
UseItem ( cInventory :: invArmorOffset + i , ArmorDamage );
}
}
void cPlayer :: BroadcastMovementUpdate ( const cClientHandle * a_Exclude )
{
if ( ! m_IsFrozen && m_Speed . SqrLength () > 0.001 )
{
// If the player is not frozen, has a non-zero speed,
// send the speed to the client so he is forced to move so:
m_ClientHandle -> SendEntityVelocity ( * this );
}
// Since we do no physics processing for players, speed will otherwise never decrease:
m_Speed . Set ( 0 , 0 , 0 );
Super :: BroadcastMovementUpdate ( a_Exclude );
}
bool cPlayer :: DoTakeDamage ( TakeDamageInfo & a_TDI )
{
// Filters out damage for creative mode / friendly fire.
if (( a_TDI . DamageType != dtInVoid ) && ( a_TDI . DamageType != dtPlugin ))
{
if ( IsGameModeCreative () || IsGameModeSpectator ())
{
// No damage / health in creative or spectator mode if not void or plugin damage
return false ;
}
}
if (( a_TDI . Attacker != nullptr ) && ( a_TDI . Attacker -> IsPlayer ()))
{
cPlayer * Attacker = static_cast < cPlayer *> ( a_TDI . Attacker );
if (( m_Team != nullptr ) && ( m_Team == Attacker -> m_Team ))
{
if ( ! m_Team -> AllowsFriendlyFire ())
{
// Friendly fire is disabled
return false ;
}
}
}
if ( Super :: DoTakeDamage ( a_TDI ))
{
// Any kind of damage adds food exhaustion
AddFoodExhaustion ( 0.3f );
m_ClientHandle -> SendHealth ();
// Tell the wolves
if ( a_TDI . Attacker != nullptr )
{
if ( a_TDI . Attacker -> IsPawn ())
{
NotifyNearbyWolves ( static_cast < cPawn *> ( a_TDI . Attacker ), true );
}
}
m_Stats . AddValue ( Statistic :: DamageTaken , FloorC < cStatManager :: StatValue > ( a_TDI . FinalDamage * 10 + 0.5 ));
return true ;
}
return false ;
}
float cPlayer :: GetEnchantmentBlastKnockbackReduction ()
{
if (
IsGameModeSpectator () ||
( IsGameModeCreative () && ! IsOnGround ())
)
{
return 1 ; // No impact from explosion
}
return Super :: GetEnchantmentBlastKnockbackReduction ();
}
2021-04-06 12:26:43 +01:00
bool cPlayer :: IsInvisible () const
{
return ! m_IsVisible || Super :: IsInvisible ();
}
2021-04-03 19:45:20 +02:00
bool cPlayer :: IsCrouched ( void ) const
{
return std :: holds_alternative < BodyStanceCrouching > ( m_BodyStance );
}
bool cPlayer :: IsElytraFlying ( void ) const
{
return std :: holds_alternative < BodyStanceGliding > ( m_BodyStance );
}
bool cPlayer :: IsSprinting ( void ) const
{
return std :: holds_alternative < BodyStanceSprinting > ( m_BodyStance );
}
2021-02-06 18:37:03 +00:00
void cPlayer :: OnAddToWorld ( cWorld & a_World )
{
2021-04-06 12:26:43 +01:00
// Sends player spawn:
2021-02-06 18:37:03 +00:00
Super :: OnAddToWorld ( a_World );
// Update world name tracking:
m_CurrentWorldName = m_World -> GetName ();
// Fix to stop the player falling through the world, until we get serversided collision detection:
FreezeInternal ( GetPosition (), false );
2021-04-06 12:26:43 +01:00
// UpdateCapabilities was called in the constructor, and in OnRemoveFromWorld, possibly changing our visibility.
// If world is in spectator mode, invisibility will need updating. If we just connected, we might be on fire from a previous game.
// Hence, tell the client by sending metadata:
m_ClientHandle -> SendEntityMetadata ( * this );
2021-02-06 18:37:03 +00:00
// Send contents of the inventory window:
m_ClientHandle -> SendWholeInventory ( * m_CurrentWindow );
// Send health (the respawn packet, which understandably resets health, is also used for world travel...):
m_ClientHandle -> SendHealth ();
// Send experience, similar story with the respawn packet:
m_ClientHandle -> SendExperience ();
// Send hotbar active slot (also reset by respawn):
m_ClientHandle -> SendHeldItemChange ( m_Inventory . GetEquippedSlotNum ());
// Update player team:
UpdateTeam ();
// Send scoreboard data:
m_World -> GetScoreBoard (). SendTo ( * m_ClientHandle );
// Update the view distance:
m_ClientHandle -> SetViewDistance ( m_ClientHandle -> GetRequestedViewDistance ());
// Send current weather of target world:
m_ClientHandle -> SendWeather ( a_World . GetWeather ());
// Send time:
2021-04-05 01:38:43 +01:00
m_ClientHandle -> SendTimeUpdate ( a_World . GetWorldAge (), a_World . GetWorldDate (), a_World . IsDaylightCycleEnabled ());
2021-02-06 18:37:03 +00:00
// Finally, deliver the notification hook:
cRoot :: Get () -> GetPluginManager () -> CallHookPlayerSpawned ( * this );
}
void cPlayer :: OnRemoveFromWorld ( cWorld & a_World )
{
Super :: OnRemoveFromWorld ( a_World );
// Remove any references to this player pointer by windows in the old world:
CloseWindow ( false );
// Remove the client handle from the world:
m_World -> RemoveClientFromChunks ( m_ClientHandle . get ());
if ( m_ClientHandle -> IsDestroyed ()) // Note: checking IsWorldChangeScheduled not appropriate here since we can disconnecting while having a scheduled warp
{
// Disconnecting, do the necessary cleanup.
// This isn't in the destructor to avoid crashing accessing destroyed objects during shutdown.
if ( ! cRoot :: Get () -> GetPluginManager () -> CallHookPlayerDestroyed ( * this ))
{
cRoot :: Get () -> BroadcastChatLeave ( Printf ( "%s has left the game" , GetName (). c_str ()));
LOGINFO ( "Player %s has left the game" , GetName (). c_str ());
}
// Remove ourself from everyone's lists:
cRoot :: Get () -> BroadcastPlayerListsRemovePlayer ( * this );
// Atomically decrement player count (in world thread):
cRoot :: Get () -> GetServer () -> PlayerDestroyed ();
// We're just disconnecting. The remaining code deals with going through portals, so bail:
return ;
}
const auto DestinationDimension = m_WorldChangeInfo . m_NewWorld -> GetDimension ();
// Award relevant achievements:
if ( DestinationDimension == dimEnd )
{
AwardAchievement ( Statistic :: AchTheEnd );
}
else if ( DestinationDimension == dimNether )
{
AwardAchievement ( Statistic :: AchPortal );
}
2021-04-06 12:26:43 +01:00
// Set capabilities based on new world:
UpdateCapabilities ();
2021-03-28 21:20:13 +01:00
// Clientside warp start:
m_ClientHandle -> SendRespawn ( DestinationDimension , false );
2021-04-06 12:26:43 +01:00
m_ClientHandle -> SendPlayerListUpdateGameMode ( * this );
m_World -> BroadcastPlayerListUpdateGameMode ( * this );
2021-03-28 21:20:13 +01:00
2021-02-06 18:37:03 +00:00
// Clear sent chunk lists from the clienthandle:
m_ClientHandle -> RemoveFromWorld ();
// The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
m_ClientHandle -> InvalidateCachedSentChunk ();
}
void cPlayer :: SpawnOn ( cClientHandle & a_Client )
{
2021-04-06 12:26:43 +01:00
if ( m_ClientHandle . get () == & a_Client )
2021-02-06 18:37:03 +00:00
{
return ;
}
LOGD ( "Spawing %s on %s" , GetName (). c_str (), a_Client . GetUsername (). c_str ());
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 ());
}
void cPlayer :: Tick ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
{
m_ClientHandle -> Tick ( a_Dt . count ());
if ( m_ClientHandle -> IsDestroyed ())
{
Destroy ();
return ;
}
if ( ! m_ClientHandle -> IsPlaying ())
{
// We're not yet in the game, ignore everything:
return ;
}
m_Stats . AddValue ( Statistic :: PlayOneMinute );
m_Stats . AddValue ( Statistic :: TimeSinceDeath );
if ( IsCrouched ())
{
m_Stats . AddValue ( Statistic :: SneakTime );
}
// Handle the player detach, when the player is in spectator mode
if (
( IsGameModeSpectator ()) &&
( m_AttachedTo != nullptr ) &&
(
( m_AttachedTo -> IsDestroyed ()) || // Watching entity destruction
( m_AttachedTo -> GetHealth () <= 0 ) || // Watching entity dead
( IsCrouched ()) // Or the player wants to be detached
)
)
{
Detach ();
}
if ( ! a_Chunk . IsValid ())
{
// Players are ticked even if the parent chunk is invalid.
// We've processed as much as we can, bail:
return ;
}
ASSERT (( GetParentChunk () != nullptr ) && ( GetParentChunk () -> IsValid ()));
ASSERT ( a_Chunk . IsValid ());
// Handle a frozen player:
TickFreezeCode ();
if (
m_IsFrozen || // Don't do Tick updates if frozen
IsWorldChangeScheduled () // If we're about to change worlds (e.g. respawn), abort processing all world interactions (GH #3939)
)
{
return ;
}
Super :: Tick ( a_Dt , a_Chunk );
// Handle charging the bow:
if ( m_IsChargingBow )
{
m_BowCharge += 1 ;
}
2021-04-03 19:45:20 +02:00
if ( IsElytraFlying ())
{
// Damage elytra, once per second:
{
using namespace std :: chrono_literals ;
auto & TicksFlying = std :: get < BodyStanceGliding > ( m_BodyStance ). TicksElytraFlying ;
const auto TotalFlew = TicksFlying + a_Dt ;
const auto Periods = static_cast < short > ( TotalFlew / 1 s );
TicksFlying = std :: chrono :: duration_cast < cTickTime > ( TotalFlew - Periods * 1 s );
UseItem ( cInventory :: invArmorOffset + 1 , Periods );
}
// Check if flight is still possible:
if ( IsOnGround () || IsInWater () || IsRiding () || ( GetEquippedChestplate (). m_ItemType != E_ITEM_ELYTRA ))
{
SetElytraFlight ( false );
}
}
2021-02-06 18:37:03 +00:00
BroadcastMovementUpdate ( m_ClientHandle . get ());
if ( m_Health > 0 ) // make sure player is alive
{
m_World -> CollectPickupsByPlayer ( * this );
2021-04-05 01:38:43 +01:00
if (( m_EatingFinishTick >= 0 _tick ) && ( m_EatingFinishTick <= m_World -> GetWorldAge ()))
2021-02-06 18:37:03 +00:00
{
FinishEating ();
}
HandleFood ();
}
if ( m_IsFishing )
{
HandleFloater ();
}
// Update items (e.g. Maps)
m_Inventory . UpdateItems ();
if ( m_TicksUntilNextSave == 0 )
{
SaveToDisk ();
m_TicksUntilNextSave = PLAYER_INVENTORY_SAVE_INTERVAL ;
}
else
{
m_TicksUntilNextSave -- ;
}
2018-07-23 17:24:00 +08:00
}