2014-06-16 15:57:23 -04:00
2013-07-29 07:13:03 -04:00
# include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
# include "Player.h"
2018-08-28 20:51:25 -04:00
# include "../Mobs/Wolf.h"
# include "../Mobs/Horse.h"
2016-01-11 14:34:41 -05:00
# include "../BoundingBox.h"
2014-07-18 02:25:14 -04:00
# include "../ChatColor.h"
2013-08-19 05:39:13 -04:00
# include "../Server.h"
2014-12-13 09:06:55 -05:00
# include "../UI/InventoryWindow.h"
2013-08-19 05:39:13 -04:00
# include "../UI/WindowOwner.h"
2013-12-08 06:17:54 -05:00
# include "../Bindings/PluginManager.h"
2013-08-19 05:39:13 -04:00
# include "../BlockEntities/BlockEntity.h"
2014-06-29 06:36:38 -04:00
# include "../BlockEntities/EnderChestEntity.h"
2013-08-19 05:39:13 -04:00
# include "../Root.h"
# include "../Chunk.h"
# include "../Items/ItemHandler.h"
2014-08-19 10:08:17 -04:00
# include "../FastRandom.h"
2016-11-18 14:00:04 -05:00
# include "../ClientHandle.h"
2013-07-29 07:13:03 -04:00
2014-05-11 07:57:06 -04:00
# include "../WorldStorage/StatSerializer.h"
2014-05-12 10:05:09 -04:00
# include "../CompositeChat.h"
2014-05-11 07:57:06 -04:00
2014-12-24 01:20:17 -05:00
# include "../Blocks/BlockHandler.h"
# include "../Blocks/BlockSlab.h"
# include "../Blocks/ChunkInterface.h"
2014-10-23 09:15:10 -04:00
# include "../IniFile.h"
2020-05-09 10:51:15 -04:00
# include "../JsonUtils.h"
2013-11-27 03:17:25 -05:00
# include "json/json.h"
2013-07-29 07:13:03 -04:00
2020-07-14 12:56:42 -04:00
# include "../CraftingRecipes.h"
2014-06-16 10:12:50 -04:00
// 6000 ticks or 5 minutes
# define PLAYER_INVENTORY_SAVE_INTERVAL 6000
2018-07-19 19:30:09 -04:00
namespace
{
/** Returns the old Offline UUID generated before becoming vanilla compliant. */
cUUID GetOldStyleOfflineUUID ( const AString & a_PlayerName )
{
// Use lowercase username
auto BaseUUID = cUUID : : GenerateVersion3 ( StrToLower ( a_PlayerName ) ) . ToRaw ( ) ;
// Clobber a full nibble around the variant bits
BaseUUID [ 8 ] = ( BaseUUID [ 8 ] & 0x0f ) | 0x80 ;
cUUID Ret ;
Ret . FromRaw ( BaseUUID ) ;
return Ret ;
}
/** 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 15:14:00 -04:00
AString res ( " players/ " ) ;
2018-07-19 19:30:09 -04:00
res . append ( UUID , 0 , 2 ) ;
res . push_back ( ' / ' ) ;
return res ;
}
} // namespace (anonymous)
2013-07-29 07:13:03 -04:00
2014-08-10 20:13:14 -04:00
const int cPlayer : : MAX_HEALTH = 20 ;
const int cPlayer : : MAX_FOOD_LEVEL = 20 ;
/** Number of ticks it takes to eat an item */
const int cPlayer : : EATING_TICKS = 30 ;
2013-07-29 07:13:03 -04:00
2013-11-13 08:50:47 -05:00
2021-03-20 16:58:19 -04:00
cPlayer : : cPlayer ( const std : : shared_ptr < cClientHandle > & a_Client ) :
2020-04-13 12:38:06 -04:00
Super ( etPlayer , 0.6 , 1.8 ) ,
2014-07-11 07:13:10 -04:00
m_bVisible ( true ) ,
m_FoodLevel ( MAX_FOOD_LEVEL ) ,
m_FoodSaturationLevel ( 5.0 ) ,
m_FoodTickTimer ( 0 ) ,
m_FoodExhaustionLevel ( 0.0 ) ,
m_Stance ( 0.0 ) ,
m_Inventory ( * this ) ,
m_EnderChestContents ( 9 , 3 ) ,
2021-01-05 19:35:42 -05:00
m_DefaultWorldPath ( cRoot : : Get ( ) - > GetDefaultWorld ( ) - > GetDataPath ( ) ) ,
2014-07-11 07:13:10 -04:00
m_GameMode ( eGameMode_NotSet ) ,
m_ClientHandle ( a_Client ) ,
2016-04-13 08:15:12 -04:00
m_IsFrozen ( false ) ,
2014-07-11 07:13:10 -04:00
m_NormalMaxSpeed ( 1.0 ) ,
m_SprintingMaxSpeed ( 1.3 ) ,
m_FlyingMaxSpeed ( 1.0 ) ,
m_IsCrouched ( false ) ,
m_IsSprinting ( false ) ,
m_IsFlying ( false ) ,
m_IsFishing ( false ) ,
m_CanFly ( false ) ,
m_EatingFinishTick ( - 1 ) ,
m_LifetimeTotalXp ( 0 ) ,
m_CurrentXp ( 0 ) ,
m_IsChargingBow ( false ) ,
m_BowCharge ( 0 ) ,
2015-07-29 11:04:03 -04:00
m_FloaterID ( cEntity : : INVALID_ID ) ,
2014-10-20 16:55:07 -04:00
m_Team ( nullptr ) ,
2016-10-08 00:47:15 -04:00
m_bIsInBed ( false ) ,
2014-07-11 07:13:10 -04:00
m_TicksUntilNextSave ( PLAYER_INVENTORY_SAVE_INTERVAL ) ,
m_bIsTeleporting ( false ) ,
2017-08-25 08:43:18 -04:00
m_UUID ( ( a_Client ! = nullptr ) ? a_Client - > GetUUID ( ) : cUUID { } ) ,
2017-01-03 15:19:29 -05:00
m_SkinParts ( 0 ) ,
m_MainHand ( mhRight )
2013-07-29 07:13:03 -04:00
{
2021-01-05 19:35:42 -05:00
ASSERT ( GetName ( ) . length ( ) < = 16 ) ; // Otherwise this player could crash many clients...
2016-01-13 01:55:05 -05:00
2013-07-29 07:13:03 -04:00
m_InventoryWindow = new cInventoryWindow ( * this ) ;
m_CurrentWindow = m_InventoryWindow ;
m_InventoryWindow - > OpenedByPlayer ( * this ) ;
2013-08-01 03:51:25 -04:00
SetMaxHealth ( MAX_HEALTH ) ;
m_Health = MAX_HEALTH ;
2015-05-23 14:31:33 -04:00
2014-10-20 16:55:07 -04:00
cWorld * World = nullptr ;
2014-06-01 13:46:59 -04:00
if ( ! LoadFromDisk ( World ) )
2013-07-29 07:13:03 -04:00
{
m_Inventory . Clear ( ) ;
2014-06-01 13:46:59 -04:00
SetPosX ( World - > GetSpawnX ( ) ) ;
SetPosY ( World - > GetSpawnY ( ) ) ;
SetPosZ ( World - > GetSpawnZ ( ) ) ;
2016-03-31 11:47:29 -04:00
// This is a new player. Set the player spawn point to the spawn point of the default world
SetBedPos ( Vector3i ( static_cast < int > ( World - > GetSpawnX ( ) ) , static_cast < int > ( World - > GetSpawnY ( ) ) , static_cast < int > ( World - > GetSpawnZ ( ) ) ) , World ) ;
2015-05-23 14:31:33 -04:00
2020-10-01 17:33:32 -04:00
m_EnchantmentSeed = GetRandomProvider ( ) . RandInt < unsigned int > ( ) ; // Use a random number to seed the enchantment generator
2018-09-24 16:33:39 -04:00
FLOGD ( " Player \" {0} \" is connecting for the first time, spawning at default world spawn {1:.2f} " ,
2021-01-05 19:35:42 -05:00
GetName ( ) , GetPosition ( )
2013-07-29 07:13:03 -04:00
) ;
}
2014-01-24 18:58:51 -05:00
2015-02-28 12:15:06 -05:00
m_LastGroundHeight = static_cast < float > ( GetPosY ( ) ) ;
2013-07-29 07:13:03 -04:00
m_Stance = GetPosY ( ) + 1.62 ;
2016-04-05 04:45:09 -04:00
2014-01-24 18:58:51 -05:00
if ( m_GameMode = = gmNotSet )
{
if ( World - > IsGameModeCreative ( ) )
{
m_CanFly = true ;
}
2014-12-03 23:04:53 -05:00
if ( World - > IsGameModeSpectator ( ) ) // Otherwise Player will fall out of the world on join
2014-12-02 20:54:56 -05:00
{
m_CanFly = true ;
m_IsFlying = true ;
}
2014-01-24 18:58:51 -05:00
}
2015-05-22 19:30:23 -04:00
if ( m_GameMode = = gmSpectator ) // If player is reconnecting to the server in spectator mode
{
m_CanFly = true ;
m_IsFlying = true ;
m_bVisible = false ;
}
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
cPlayer : : ~ cPlayer ( void )
2020-07-14 12:56:42 -04:00
{
2021-02-06 13:37:03 -05:00
LOGD ( " Deleting cPlayer \" %s \" at %p, ID %d " , GetName ( ) . c_str ( ) , static_cast < void * > ( this ) , GetUniqueID ( ) ) ;
2020-07-14 12:56:42 -04:00
2021-02-06 13:37:03 -05: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 12:56:42 -04:00
{
2021-02-06 13:37:03 -05:00
return a_XpTotal / XP_PER_LEVEL_TO15 ;
2020-07-14 12:56:42 -04:00
}
2021-02-06 13:37:03 -05:00
// level 30+
if ( a_XpTotal > XP_TO_LEVEL30 )
2020-07-14 12:56:42 -04:00
{
2021-02-06 13:37:03 -05:00
return static_cast < int > ( ( 151.5 + sqrt ( 22952.25 - ( 14 * ( 2220 - a_XpTotal ) ) ) ) / 7 ) ;
2020-07-14 12:56:42 -04:00
}
2021-02-06 13:37:03 -05:00
// level 16 to 30
return static_cast < int > ( ( 29.5 + sqrt ( 870.25 - ( 6 * ( 360 - a_XpTotal ) ) ) ) / 3 ) ;
2020-07-14 12:56:42 -04:00
}
2021-02-06 13:37:03 -05:00
const std : : set < UInt32 > & cPlayer : : GetKnownRecipes ( ) const
2020-07-14 12:56:42 -04:00
{
2021-02-06 13:37:03 -05:00
return m_KnownRecipes ;
}
int cPlayer : : XpForLevel ( int a_Level )
{
// level 0 to 15
if ( a_Level < = 15 )
2020-07-14 12:56:42 -04:00
{
2021-02-06 13:37:03 -05:00
return a_Level * XP_PER_LEVEL_TO15 ;
2020-07-14 12:56:42 -04:00
}
2021-02-06 13:37:03 -05: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 12:56:42 -04:00
}
2021-02-06 13:37:03 -05:00
int cPlayer : : GetXpLevel ( ) const
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
return CalcLevelFromXp ( m_CurrentXp ) ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
2015-05-23 14:31:33 -04:00
2021-02-06 13:37:03 -05: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 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
bool cPlayer : : SetCurrentExperience ( int a_CurrentXp )
2021-01-05 19:35:42 -05:00
{
2021-02-06 13:37:03 -05: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-05 19:35:42 -05:00
2021-02-06 13:37:03 -05:00
m_CurrentXp = a_CurrentXp ;
2021-01-05 19:35:42 -05:00
2021-02-06 13:37:03 -05:00
// Update experience:
m_ClientHandle - > SendExperience ( ) ;
2021-01-05 19:35:42 -05:00
2021-02-06 13:37:03 -05:00
return true ;
}
2021-01-05 19:35:42 -05:00
2021-02-06 13:37:03 -05: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-05 19:35:42 -05:00
2021-02-06 13:37:03 -05:00
m_CurrentXp + = a_Xp_delta ;
2021-01-05 19:35:42 -05:00
2021-02-06 13:37:03 -05:00
// Make sure they didn't subtract too much
m_CurrentXp = std : : max ( m_CurrentXp , 0 ) ;
2021-01-05 19:35:42 -05:00
2021-02-06 13:37:03 -05:00
// Update total for score calculation
if ( a_Xp_delta > 0 )
{
m_LifetimeTotalXp + = a_Xp_delta ;
}
2021-01-05 19:35:42 -05:00
2021-02-06 13:37:03 -05: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-05 19:35:42 -05:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : StartChargingBow ( void )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05: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-05 19:35:42 -05:00
2021-02-06 13:37:03 -05: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-05 19:35:42 -05:00
2021-02-06 13:37:03 -05:00
return res ;
}
2021-01-05 19:35:42 -05:00
2021-02-06 13:37:03 -05: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 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetTouchGround ( bool a_bTouchGround )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
if ( IsGameModeSpectator ( ) ) // You can fly through the ground in Spectator
2013-07-29 07:13:03 -04:00
{
2013-08-13 16:45:29 -04:00
return ;
2013-07-29 07:13:03 -04:00
}
2020-03-05 05:52:34 -05:00
2021-02-06 13:37:03 -05: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 05:52:34 -05:00
2021-02-06 13:37:03 -05:00
void cPlayer : : Heal ( int a_Health )
{
Super : : Heal ( a_Health ) ;
m_ClientHandle - > SendHealth ( ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetFoodLevel ( int a_FoodLevel )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
int FoodLevel = Clamp ( a_FoodLevel , 0 , MAX_FOOD_LEVEL ) ;
2015-05-23 14:31:33 -04:00
2021-02-06 13:37:03 -05:00
if ( cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookPlayerFoodLevelChange ( * this , FoodLevel ) )
2015-06-30 10:50:15 -04:00
{
2021-02-06 13:37:03 -05:00
m_FoodSaturationLevel = 5.0 ;
2021-01-05 19:35:42 -05:00
return ;
2015-06-30 10:50:15 -04:00
}
2014-05-12 14:38:52 -04:00
2021-02-06 13:37:03 -05:00
m_FoodLevel = FoodLevel ;
m_ClientHandle - > SendHealth ( ) ;
}
2016-04-05 04:45:09 -04:00
2021-01-05 19:35:42 -05:00
2015-05-23 14:31:33 -04:00
2016-10-12 08:38:45 -04:00
2015-05-23 14:31:33 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SetFoodSaturationLevel ( double a_FoodSaturationLevel )
{
m_FoodSaturationLevel = Clamp ( a_FoodSaturationLevel , 0.0 , static_cast < double > ( m_FoodLevel ) ) ;
}
2016-03-27 13:43:30 -04:00
2021-01-05 19:35:42 -05:00
2015-05-23 14:31:33 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SetFoodTickTimer ( int a_FoodTickTimer )
{
m_FoodTickTimer = a_FoodTickTimer ;
}
2013-07-29 07:13:03 -04:00
2015-05-23 14:31:33 -04:00
2013-12-21 11:31:05 -05:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SetFoodExhaustionLevel ( double a_FoodExhaustionLevel )
{
m_FoodExhaustionLevel = Clamp ( a_FoodExhaustionLevel , 0.0 , 40.0 ) ;
}
2014-02-17 09:27:12 -05:00
2013-12-22 15:03:09 -05:00
2021-02-06 13:37:03 -05:00
bool cPlayer : : Feed ( int a_Food , double a_Saturation )
{
if ( IsSatiated ( ) )
2014-06-16 10:12:50 -04:00
{
2021-02-06 13:37:03 -05:00
return false ;
2014-06-16 10:12:50 -04:00
}
2021-02-06 13:37:03 -05:00
SetFoodSaturationLevel ( m_FoodSaturationLevel + a_Saturation ) ;
SetFoodLevel ( m_FoodLevel + a_Food ) ;
return true ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : AddFoodExhaustion ( double a_Exhaustion )
2016-03-27 13:43:30 -04:00
{
2021-02-06 13:37:03 -05:00
if ( ! ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) ) )
2016-03-27 13:43:30 -04:00
{
2021-02-06 13:37:03 -05:00
m_FoodExhaustionLevel = std : : min ( m_FoodExhaustionLevel + a_Exhaustion , 40.0 ) ;
2016-03-27 13:43:30 -04:00
}
}
2021-02-06 13:37:03 -05:00
void cPlayer : : TossItems ( const cItems & a_Items )
2013-11-13 12:25:47 -05:00
{
2021-02-06 13:37:03 -05:00
if ( IsGameModeSpectator ( ) ) // Players can't toss items in spectator
2013-11-13 12:25:47 -05:00
{
2021-02-06 13:37:03 -05:00
return ;
2013-11-13 12:25:47 -05:00
}
2021-02-06 13:37:03 -05:00
m_Stats . AddValue ( Statistic : : Drop , static_cast < cStatManager : : StatValue > ( a_Items . Size ( ) ) ) ;
2013-11-13 12:25:47 -05:00
2021-02-06 13:37:03 -05: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-13 12:25:47 -05:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : StartEating ( void )
2021-01-05 19:35:42 -05:00
{
2021-02-06 13:37:03 -05: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-05 19:35:42 -05:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : FinishEating ( void )
2013-11-13 12:25:47 -05:00
{
2021-02-06 13:37:03 -05:00
// Reset the timer:
m_EatingFinishTick = - 1 ;
2013-11-13 12:25:47 -05:00
2021-02-06 13:37:03 -05: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-13 12:25:47 -05:00
{
2021-02-06 13:37:03 -05:00
return ;
2013-11-13 12:25:47 -05:00
}
2021-02-06 13:37:03 -05:00
ItemHandler - > OnFoodEaten ( m_World , this , & Item ) ;
2013-11-13 12:25:47 -05:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : AbortEating ( void )
2013-11-13 12:25:47 -05:00
{
2021-02-06 13:37:03 -05:00
m_EatingFinishTick = - 1 ;
m_World - > BroadcastEntityMetadata ( * this ) ;
2013-11-13 12:25:47 -05:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : ClearInventoryPaintSlots ( void )
2013-11-13 12:25:47 -05:00
{
2021-02-06 13:37:03 -05:00
// Clear the list of slots that are being inventory-painted. Used by cWindow only
m_InventoryPaintSlots . clear ( ) ;
2013-11-13 12:25:47 -05:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : AddInventoryPaintSlot ( int a_SlotNum )
2013-11-13 08:50:47 -05:00
{
2021-02-06 13:37:03 -05:00
// Add a slot to the list for inventory painting. Used by cWindow only
m_InventoryPaintSlots . push_back ( a_SlotNum ) ;
}
2013-11-13 08:50:47 -05:00
2013-11-15 06:42:09 -05:00
2021-02-06 13:37:03 -05: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-13 08:50:47 -05:00
}
2021-02-06 13:37:03 -05:00
double cPlayer : : GetMaxSpeed ( void ) const
2013-11-13 08:50:47 -05:00
{
2021-02-06 13:37:03 -05:00
if ( m_IsFlying )
2013-11-13 08:50:47 -05:00
{
2021-02-06 13:37:03 -05:00
return m_FlyingMaxSpeed ;
2013-11-13 08:50:47 -05:00
}
2021-02-06 13:37:03 -05:00
else if ( m_IsSprinting )
2013-11-16 05:38:57 -05:00
{
2021-02-06 13:37:03 -05:00
return m_SprintingMaxSpeed ;
}
else
{
return m_NormalMaxSpeed ;
2013-11-16 05:38:57 -05:00
}
2021-02-06 13:37:03 -05:00
}
2013-11-16 05:38:57 -05:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SetNormalMaxSpeed ( double a_Speed )
2013-08-30 08:24:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_NormalMaxSpeed = a_Speed ;
if ( ! m_IsSprinting & & ! m_IsFlying & & ! m_IsFrozen )
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
m_ClientHandle - > SendPlayerMaxSpeed ( ) ;
}
2013-08-30 08:24:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetSprintingMaxSpeed ( double a_Speed )
2013-08-30 08:24:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_SprintingMaxSpeed = a_Speed ;
if ( m_IsSprinting & & ! m_IsFlying & & ! m_IsFrozen )
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
m_ClientHandle - > SendPlayerMaxSpeed ( ) ;
}
2013-08-30 08:24:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetFlyingMaxSpeed ( double a_Speed )
2013-08-30 08:24:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_FlyingMaxSpeed = a_Speed ;
// Update the flying speed, always:
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 08:24:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetCrouch ( bool a_IsCrouched )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
// Set the crouch status, broadcast to all visible players
if ( a_IsCrouched = = m_IsCrouched )
2014-09-17 11:15:47 -04:00
{
2021-02-06 13:37:03 -05:00
// No change
2014-09-17 11:15:47 -04:00
return ;
}
2015-05-23 14:31:33 -04:00
2021-02-06 13:37:03 -05:00
if ( a_IsCrouched )
{
cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookPlayerCrouched ( * this ) ;
}
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
m_IsCrouched = a_IsCrouched ;
m_World - > BroadcastEntityMetadata ( * this ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetSprint ( bool a_IsSprinting )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
if ( a_IsSprinting = = m_IsSprinting )
2014-06-30 09:12:56 -04:00
{
2021-02-06 13:37:03 -05:00
// No change
2014-06-30 09:12:56 -04:00
return ;
}
2015-05-23 14:31:33 -04:00
2021-02-06 13:37:03 -05:00
m_IsSprinting = a_IsSprinting ;
m_ClientHandle - > SendPlayerMaxSpeed ( ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetCanFly ( bool a_CanFly )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
if ( a_CanFly = = m_CanFly )
{
return ;
}
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
m_CanFly = a_CanFly ;
m_ClientHandle - > SendPlayerAbilities ( ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetCustomName ( const AString & a_CustomName )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
if ( m_CustomName = = a_CustomName )
{
return ;
}
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
m_World - > BroadcastPlayerListRemovePlayer ( * this ) ;
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
m_CustomName = a_CustomName ;
if ( m_CustomName . length ( ) > 16 )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_CustomName = m_CustomName . substr ( 0 , 16 ) ;
2013-07-29 07:13:03 -04:00
}
2014-06-30 09:12:56 -04:00
2021-02-06 13:37:03 -05:00
m_World - > BroadcastPlayerListAddPlayer ( * this ) ;
m_World - > BroadcastSpawnEntity ( * this , m_ClientHandle . get ( ) ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetBedPos ( const Vector3i & a_Pos )
2014-07-31 17:04:00 -04:00
{
2021-02-06 13:37:03 -05:00
m_LastBedPos = a_Pos ;
m_SpawnWorldName = m_World - > GetName ( ) ;
2014-07-31 17:04:00 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetBedPos ( const Vector3i & a_Pos , cWorld * a_World )
2020-05-04 04:10:47 -04:00
{
2021-02-06 13:37:03 -05:00
m_LastBedPos = a_Pos ;
ASSERT ( a_World ! = nullptr ) ;
m_SpawnWorldName = a_World - > GetName ( ) ;
2020-05-04 04:10:47 -04:00
}
2021-02-06 13:37:03 -05:00
cWorld * cPlayer : : GetBedWorld ( )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
if ( const auto World = cRoot : : Get ( ) - > GetWorld ( m_SpawnWorldName ) ; World ! = nullptr )
{
return World ;
}
2015-05-23 14:31:33 -04:00
2021-02-06 13:37:03 -05:00
return cRoot : : Get ( ) - > GetDefaultWorld ( ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetFlying ( bool a_IsFlying )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
if ( a_IsFlying = = m_IsFlying )
2013-07-29 07:13:03 -04:00
{
return ;
}
2021-02-06 13:37:03 -05:00
m_IsFlying = a_IsFlying ;
if ( ! m_IsFrozen )
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
m_ClientHandle - > SendPlayerAbilities ( ) ;
}
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : NotifyNearbyWolves ( cPawn * a_Opponent , bool a_IsPlayerInvolved )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
ASSERT ( a_Opponent ! = nullptr ) ;
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05: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 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : KilledBy ( TakeDamageInfo & a_TDI )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
Super : : KilledBy ( a_TDI ) ;
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
if ( m_Health > 0 )
{
return ; // not dead yet =]
}
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
m_bVisible = false ; // So new clients don't see the player
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05: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 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
// Puke out all the items
cItems Pickups ;
m_Inventory . CopyToItems ( Pickups ) ;
m_Inventory . Clear ( ) ;
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
if ( GetName ( ) = = " Notch " )
2014-03-20 11:14:40 -04:00
{
2021-02-06 13:37:03 -05:00
Pickups . Add ( cItem ( E_ITEM_RED_APPLE ) ) ;
2014-03-20 11:14:40 -04:00
}
2021-02-06 13:37:03 -05:00
m_Stats . AddValue ( Statistic : : Drop , static_cast < cStatManager : : StatValue > ( Pickups . Size ( ) ) ) ;
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05: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 07:13:03 -04:00
2021-03-11 16:26:35 -05:00
if ( a_TDI . Attacker = = nullptr )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05: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 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
m_Stats . AddValue ( Statistic : : Deaths ) ;
m_Stats . SetValue ( Statistic : : TimeSinceDeath , 0 ) ;
m_World - > GetScoreBoard ( ) . AddPlayerScore ( GetName ( ) , cObjective : : otDeathCount , 1 ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : Killed ( cEntity * a_Victim )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
cScoreboard & ScoreBoard = m_World - > GetScoreBoard ( ) ;
if ( a_Victim - > IsPlayer ( ) )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05: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 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
ScoreBoard . AddPlayerScore ( GetName ( ) , cObjective : : otTotalKillCount , 1 ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : Respawn ( void )
2014-03-20 11:14:40 -04:00
{
2021-02-06 13:37:03 -05:00
ASSERT ( m_World ! = nullptr ) ;
2014-03-20 11:14:40 -04:00
2021-02-06 13:37:03 -05:00
m_Health = GetMaxHealth ( ) ;
SetInvulnerableTicks ( 20 ) ;
2014-03-20 11:14:40 -04:00
2021-02-06 13:37:03 -05:00
// Reset food level:
m_FoodLevel = MAX_FOOD_LEVEL ;
m_FoodSaturationLevel = 5.0 ;
m_FoodExhaustionLevel = 0.0 ;
2014-03-20 11:14:40 -04:00
2021-02-06 13:37:03 -05:00
// Reset Experience
m_CurrentXp = 0 ;
m_LifetimeTotalXp = 0 ;
// ToDo: send score to client? How?
2014-03-20 11:14:40 -04:00
2021-02-06 13:37:03 -05:00
// Extinguish the fire:
StopBurning ( ) ;
2014-03-20 11:14:40 -04:00
2021-02-06 13:37:03 -05:00
if ( const auto BedWorld = GetBedWorld ( ) ; m_World ! = BedWorld )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
MoveToWorld ( * BedWorld , GetLastBedPos ( ) , false , false ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
else
2020-03-23 08:18:12 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendRespawn ( m_World - > GetDimension ( ) , true ) ;
TeleportToCoords ( GetLastBedPos ( ) . x , GetLastBedPos ( ) . y , GetLastBedPos ( ) . z ) ;
2020-03-23 08:18:12 -04:00
}
2021-02-06 13:37:03 -05:00
SetVisible ( true ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
double cPlayer : : GetEyeHeight ( void ) const
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
return m_Stance ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
Vector3d cPlayer : : GetEyePosition ( void ) const
2013-12-15 08:48:17 -05:00
{
2021-02-06 13:37:03 -05:00
return Vector3d ( GetPosX ( ) , m_Stance , GetPosZ ( ) ) ;
2013-12-15 08:48:17 -05:00
}
2021-02-06 13:37:03 -05:00
bool cPlayer : : IsGameModeCreative ( void ) const
2014-09-02 13:12:35 -04:00
{
2021-02-06 13:37:03 -05:00
return ( GetEffectiveGameMode ( ) = = gmCreative ) ;
}
2014-09-26 11:37:19 -04:00
2014-09-02 13:12:35 -04:00
2021-02-06 13:37:03 -05:00
bool cPlayer : : IsGameModeSurvival ( void ) const
{
return ( GetEffectiveGameMode ( ) = = gmSurvival ) ;
2014-09-02 13:12:35 -04:00
}
2021-02-06 13:37:03 -05:00
bool cPlayer : : IsGameModeAdventure ( void ) const
2016-03-24 13:13:23 -04:00
{
2021-02-06 13:37:03 -05:00
return ( GetEffectiveGameMode ( ) = = gmAdventure ) ;
2016-03-24 13:13:23 -04:00
}
2021-02-06 13:37:03 -05:00
bool cPlayer : : IsGameModeSpectator ( void ) const
2016-03-24 13:13:23 -04:00
{
2021-02-06 13:37:03 -05:00
return ( GetEffectiveGameMode ( ) = = gmSpectator ) ;
2016-03-24 13:13:23 -04:00
}
2021-02-06 13:37:03 -05:00
bool cPlayer : : CanMobsTarget ( void ) const
2016-08-27 02:37:54 -04:00
{
2021-02-06 13:37:03 -05:00
return ( IsGameModeSurvival ( ) | | IsGameModeAdventure ( ) ) & & ( m_Health > 0 ) ;
}
2021-01-05 19:35:42 -05:00
2021-02-06 13:37:03 -05:00
AString cPlayer : : GetIP ( void ) const
{
return m_ClientHandle - > GetIPString ( ) ;
2016-08-27 02:37:54 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetTeam ( cTeam * a_Team )
2013-12-15 08:48:17 -05:00
{
2021-02-06 13:37:03 -05:00
if ( m_Team = = a_Team )
2013-12-15 08:48:17 -05:00
{
return ;
}
2021-02-06 13:37:03 -05:00
if ( m_Team )
2016-04-05 04:45:09 -04:00
{
2021-02-06 13:37:03 -05:00
m_Team - > RemovePlayer ( GetName ( ) ) ;
}
m_Team = a_Team ;
if ( m_Team )
{
m_Team - > AddPlayer ( GetName ( ) ) ;
2016-04-05 04:45:09 -04:00
}
2013-12-15 08:48:17 -05:00
}
2018-07-26 17:24:36 -04:00
2021-02-06 13:37:03 -05:00
cTeam * cPlayer : : UpdateTeam ( void )
2017-07-23 05:46:38 -04:00
{
2021-02-06 13:37:03 -05:00
if ( m_World = = nullptr )
2017-07-23 05:46:38 -04:00
{
2021-02-06 13:37:03 -05:00
SetTeam ( nullptr ) ;
}
else
{
cScoreboard & Scoreboard = m_World - > GetScoreBoard ( ) ;
SetTeam ( Scoreboard . QueryPlayerTeam ( GetName ( ) ) ) ;
2017-07-23 05:46:38 -04:00
}
2021-02-06 13:37:03 -05:00
return m_Team ;
2017-07-23 05:46:38 -04:00
}
2013-12-15 08:48:17 -05:00
2021-02-06 13:37:03 -05:00
void cPlayer : : OpenWindow ( cWindow & a_Window )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
if ( cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookPlayerOpeningWindow ( * this , a_Window ) )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
return ;
2013-07-29 07:13:03 -04:00
}
2014-01-19 07:20:57 -05:00
2021-02-06 13:37:03 -05:00
if ( & a_Window ! = m_CurrentWindow )
2014-01-19 07:20:57 -05:00
{
2021-02-06 13:37:03 -05:00
CloseWindow ( false ) ;
}
2014-01-19 07:20:57 -05:00
2021-02-06 13:37:03 -05:00
a_Window . OpenedByPlayer ( * this ) ;
m_CurrentWindow = & a_Window ;
a_Window . SendWholeWindow ( * GetClientHandle ( ) ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : CloseWindow ( bool a_CanRefuse )
2016-01-11 14:34:41 -05:00
{
2021-02-06 13:37:03 -05:00
if ( m_CurrentWindow = = nullptr )
{
m_CurrentWindow = m_InventoryWindow ;
return ;
}
2016-01-11 14:34:41 -05:00
2021-02-06 13:37:03 -05: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 14:34:41 -05:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : CloseWindowIfID ( char a_WindowID , bool a_CanRefuse )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
if ( ( m_CurrentWindow = = nullptr ) | | ( m_CurrentWindow - > GetWindowID ( ) ! = a_WindowID ) )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
return ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
CloseWindow ( ) ;
}
2013-07-29 07:13:03 -04:00
2017-03-19 04:48:15 -04:00
2014-02-15 13:51:05 -05:00
2014-05-12 14:38:52 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessage ( const AString & a_Message )
{
m_ClientHandle - > SendChat ( a_Message , mtCustom ) ;
}
2013-12-26 09:55:19 -05:00
2014-01-19 07:20:57 -05:00
2014-05-11 07:57:06 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessageInfo ( const AString & a_Message )
{
m_ClientHandle - > SendChat ( a_Message , mtInformation ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessageFailure ( const AString & a_Message )
2014-05-12 10:05:09 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChat ( a_Message , mtFailure ) ;
}
2014-05-12 10:05:09 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessageSuccess ( const AString & a_Message )
{
m_ClientHandle - > SendChat ( a_Message , mtSuccess ) ;
2014-05-12 10:05:09 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessageWarning ( const AString & a_Message )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChat ( a_Message , mtWarning ) ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
2013-11-16 05:38:57 -05:00
2013-07-29 07:13:03 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessageFatal ( const AString & a_Message )
{
m_ClientHandle - > SendChat ( a_Message , mtFailure ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessagePrivateMsg ( const AString & a_Message , const AString & a_Sender )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChat ( a_Message , mtPrivateMessage , a_Sender ) ;
2013-07-29 07:13:03 -04:00
}
2018-07-26 17:24:36 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessage ( const cCompositeChat & a_Message )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChat ( a_Message ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SendMessageRaw ( const AString & a_MessageRaw , eChatType a_Type )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChatRaw ( a_MessageRaw , a_Type ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SendSystemMessage ( const AString & a_Message )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChatSystem ( a_Message , mtCustom ) ;
2013-07-29 07:13:03 -04:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SendAboveActionBarMessage ( const AString & a_Message )
2013-07-29 07:13:03 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChatAboveActionBar ( a_Message , mtCustom ) ;
2013-07-29 07:13:03 -04:00
}
2018-07-26 17:24:36 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SendSystemMessage ( const cCompositeChat & a_Message )
2014-09-16 14:04:17 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChatSystem ( a_Message ) ;
2014-09-16 14:04:17 -04:00
}
2013-07-29 07:13:03 -04:00
2016-10-12 08:38:45 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : SendAboveActionBarMessage ( const cCompositeChat & a_Message )
2016-10-12 08:38:45 -04:00
{
2021-02-06 13:37:03 -05:00
m_ClientHandle - > SendChatAboveActionBar ( a_Message ) ;
2016-10-12 08:38:45 -04:00
}
2021-02-06 13:37:03 -05:00
const AString & cPlayer : : GetName ( void ) const
2021-01-05 19:35:42 -05:00
{
2021-02-06 13:37:03 -05:00
return m_ClientHandle - > GetUsername ( ) ;
2021-01-05 19:35:42 -05:00
}
2021-02-06 13:37:03 -05:00
void cPlayer : : SetGameMode ( eGameMode a_GameMode )
2014-01-19 07:20:57 -05:00
{
2021-02-06 13:37:03 -05:00
if ( ( a_GameMode < gmMin ) | | ( a_GameMode > = gmMax ) )
2014-01-20 09:10:39 -05:00
{
2021-02-06 13:37:03 -05:00
LOGWARNING ( " %s: Setting invalid gamemode: %d " , GetName ( ) . c_str ( ) , a_GameMode ) ;
2014-01-20 09:10:39 -05:00
return ;
}
2021-02-06 13:37:03 -05:00
if ( m_GameMode = = a_GameMode )
2014-01-19 07:20:57 -05:00
{
2021-02-06 13:37:03 -05:00
// Gamemode already set
return ;
2013-07-29 07:13:03 -04:00
}
2015-05-23 14:31:33 -04:00
2016-10-12 08:38:45 -04:00
// Detach, if the player is switching from or to the spectator mode
if ( ( m_GameMode = = gmSpectator ) | | ( a_GameMode = = gmSpectator ) )
{
Detach ( ) ;
}
2013-07-29 07:13:03 -04:00
m_GameMode = a_GameMode ;
m_ClientHandle - > SendGameMode ( a_GameMode ) ;
2013-12-22 15:03:09 -05:00
2015-05-23 14:31:33 -04:00
SetCapabilities ( ) ;
2015-05-22 15:25:16 -04:00
2014-09-18 12:50:17 -04:00
m_World - > BroadcastPlayerListUpdateGameMode ( * this ) ;
2013-07-29 07:13:03 -04:00
}
2015-05-23 14:31:33 -04:00
void cPlayer : : SetCapabilities ( )
{
2018-07-22 19:35:32 -04:00
// Fly ability
if ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) )
{
SetCanFly ( true ) ;
}
else
2015-05-23 14:31:33 -04:00
{
SetFlying ( false ) ;
SetCanFly ( false ) ;
}
2018-07-22 19:35:32 -04:00
// Visible
2015-05-23 14:31:33 -04:00
if ( IsGameModeSpectator ( ) )
{
SetVisible ( false ) ;
2018-07-22 19:35:32 -04:00
}
else
{
SetVisible ( true ) ;
}
2016-10-12 08:38:45 -04:00
2018-07-22 19:35:32 -04:00
// Set for spectator
if ( IsGameModeSpectator ( ) )
{
2016-10-12 08:38:45 -04:00
// Clear the current dragging item of the player
if ( GetWindow ( ) ! = nullptr )
{
m_DraggingItem . Empty ( ) ;
GetClientHandle ( ) - > SendInventorySlot ( - 1 , - 1 , m_DraggingItem ) ;
}
2015-05-23 14:31:33 -04:00
}
2013-07-29 07:13:03 -04:00
}
2020-08-12 04:54:36 -04:00
void cPlayer : : AwardAchievement ( const Statistic a_Ach )
2014-05-12 10:05:09 -04:00
{
2020-08-12 04:54:36 -04:00
// Check if the prerequisites are met:
if ( ! m_Stats . SatisfiesPrerequisite ( a_Ach ) )
2014-05-12 10:05:09 -04:00
{
2020-08-12 04:54:36 -04:00
return ;
2014-05-12 10:05:09 -04:00
}
2020-08-12 04:54:36 -04:00
// Increment the statistic and check if we already have it:
if ( m_Stats . AddValue ( a_Ach ) ! = 1 )
2014-05-12 10:05:09 -04:00
{
2020-08-12 04:54:36 -04:00
return ;
2014-05-12 10:05:09 -04:00
}
2020-08-12 04:54:36 -04: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 10:05:09 -04:00
}
2020-08-12 04:54:36 -04:00
// Achievement Get!
m_ClientHandle - > SendStatistics ( m_Stats ) ;
2014-05-12 10:05:09 -04:00
}
2013-07-29 07:13:03 -04:00
void cPlayer : : TeleportToCoords ( double a_PosX , double a_PosY , double a_PosZ )
{
2015-03-05 05:52:42 -05:00
// ask plugins to allow teleport to the new position.
2015-07-21 16:25:37 -04:00
if ( ! cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookEntityTeleport ( * this , m_LastPosition , Vector3d ( a_PosX , a_PosY , a_PosZ ) ) )
2015-03-05 05:52:42 -05:00
{
2020-05-04 04:10:47 -04:00
SetPosition ( { a_PosX , a_PosY , a_PosZ } ) ;
2016-03-27 13:43:30 -04:00
FreezeInternal ( GetPosition ( ) , false ) ;
2015-03-05 05:52:42 -05:00
m_bIsTeleporting = true ;
2013-07-29 07:13:03 -04:00
2015-03-05 05:52:42 -05:00
m_ClientHandle - > SendPlayerMoveLook ( ) ;
}
2013-07-29 07:13:03 -04:00
}
2015-12-24 05:37:32 -05:00
void cPlayer : : Freeze ( const Vector3d & a_Location )
{
FreezeInternal ( a_Location , true ) ;
}
bool cPlayer : : IsFrozen ( )
{
return m_IsFrozen ;
}
void cPlayer : : Unfreeze ( )
{
2016-04-05 04:45:09 -04:00
GetClientHandle ( ) - > SendPlayerAbilities ( ) ;
GetClientHandle ( ) - > SendPlayerMaxSpeed ( ) ;
2015-12-24 05:37:32 -05:00
m_IsFrozen = false ;
2021-01-05 19:35:42 -05:00
BroadcastMovementUpdate ( m_ClientHandle . get ( ) ) ;
2016-04-05 04:45:09 -04:00
GetClientHandle ( ) - > SendPlayerPosition ( ) ;
2015-12-24 05:37:32 -05:00
}
2014-04-04 17:06:47 -04:00
void cPlayer : : SendRotation ( double a_YawDegrees , double a_PitchDegrees )
{
SetYaw ( a_YawDegrees ) ;
SetPitch ( a_PitchDegrees ) ;
m_ClientHandle - > SendPlayerMoveLook ( ) ;
}
2020-03-30 15:35:37 -04: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 11:29:46 -04:00
Vector3d cPlayer : : GetThrowStartPos ( void ) const
{
Vector3d res = GetEyePosition ( ) ;
2015-05-23 14:31:33 -04:00
2013-08-30 11:29:46 -04:00
// Adjust the position to be just outside the player's bounding box:
res . x + = 0.16 * cos ( GetPitch ( ) ) ;
res . y + = - 0.1 ;
res . z + = 0.16 * sin ( GetPitch ( ) ) ;
2015-05-23 14:31:33 -04:00
2013-08-30 11:29:46 -04:00
return res ;
}
Vector3d cPlayer : : GetThrowSpeed ( double a_SpeedCoeff ) const
{
Vector3d res = GetLookVector ( ) ;
res . Normalize ( ) ;
2015-05-23 14:31:33 -04:00
2013-08-30 11:29:46 -04:00
// TODO: Add a slight random change (+-0.0075 in each direction)
2015-05-23 14:31:33 -04:00
2013-08-30 11:29:46 -04:00
return res * a_SpeedCoeff ;
2014-07-17 16:50:58 -04:00
}
2013-08-30 11:29:46 -04:00
2021-01-05 19:35:42 -05:00
eGameMode cPlayer : : GetEffectiveGameMode ( void ) const
{
// Since entities' m_World aren't set until Initialize, but cClientHandle sends the player's gamemode early
// the below block deals with m_World being nullptr when called.
auto World = m_World ;
if ( World = = nullptr )
{
World = cRoot : : Get ( ) - > GetDefaultWorld ( ) ;
}
else if ( IsWorldChangeScheduled ( ) )
{
World = m_WorldChangeInfo . m_NewWorld ;
}
return ( m_GameMode = = gmNotSet ) ? World - > GetGameMode ( ) : m_GameMode ;
}
2014-04-19 14:51:52 -04:00
void cPlayer : : ForceSetSpeed ( const Vector3d & a_Speed )
2013-12-15 12:54:54 -05:00
{
2014-04-19 14:51:52 -04:00
SetSpeed ( a_Speed ) ;
2014-06-16 10:12:50 -04:00
}
2013-07-29 07:13:03 -04:00
void cPlayer : : SetVisible ( bool a_bVisible )
{
2014-09-17 11:15:47 -04:00
// Need to Check if the player or other players are in gamemode spectator, but will break compatibility
2014-07-17 16:15:34 -04:00
if ( a_bVisible & & ! m_bVisible ) // Make visible
2013-07-29 07:13:03 -04:00
{
m_bVisible = true ;
m_World - > BroadcastSpawnEntity ( * this ) ;
}
if ( ! a_bVisible & & m_bVisible )
{
m_bVisible = false ;
2015-01-24 14:17:00 -05:00
m_World - > BroadcastDestroyEntity ( * this , m_ClientHandle . get ( ) ) ; // Destroy on all clients
2013-07-29 07:13:03 -04:00
}
}
2020-10-01 17:33:32 -04: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 07:13:03 -04:00
bool cPlayer : : HasPermission ( const AString & a_Permission )
{
if ( a_Permission . empty ( ) )
{
// Empty permission request is always granted
return true ;
}
2015-05-23 14:31:33 -04:00
2014-08-19 11:34:11 -04:00
AStringVector Split = StringSplit ( a_Permission , " . " ) ;
2015-04-25 13:40:44 -04:00
// Iterate over all restrictions; if any matches, then return failure:
for ( auto & Restriction : m_SplitRestrictions )
{
if ( PermissionMatches ( Split , Restriction ) )
{
return false ;
}
} // for Restriction - m_SplitRestrictions[]
2014-08-19 11:34:11 -04:00
// Iterate over all granted permissions; if any matches, then return success:
2015-04-25 13:40:44 -04:00
for ( auto & Permission : m_SplitPermissions )
2013-07-29 07:13:03 -04:00
{
2015-04-25 13:40:44 -04:00
if ( PermissionMatches ( Split , Permission ) )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
return true ;
2013-07-29 07:13:03 -04:00
}
2015-04-25 13:40:44 -04:00
} // for Permission - m_SplitPermissions[]
2013-07-29 07:13:03 -04:00
2014-08-19 11:34:11 -04:00
// No granted permission matches
2013-07-29 07:13:03 -04:00
return false ;
}
2014-08-19 11:34:11 -04:00
bool cPlayer : : PermissionMatches ( const AStringVector & a_Permission , const AStringVector & a_Template )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
// Check the sub-items if they are the same or there's a wildcard:
size_t lenP = a_Permission . size ( ) ;
size_t lenT = a_Template . size ( ) ;
size_t minLen = std : : min ( lenP , lenT ) ;
for ( size_t i = 0 ; i < minLen ; i + + )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
if ( a_Template [ i ] = = " * " )
{
// Has matched so far and now there's a wildcard in the template, so the permission matches:
2013-07-29 07:13:03 -04:00
return true ;
2014-08-19 11:34:11 -04:00
}
if ( a_Permission [ i ] ! = a_Template [ i ] )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
// Found a mismatch
return false ;
2013-07-29 07:13:03 -04:00
}
}
2014-08-19 11:34:11 -04:00
// So far all the sub-items have matched
2020-05-14 18:15:35 -04:00
// If the sub-item count is the same, then the permission matches
return ( lenP = = lenT ) ;
2013-07-29 07:13:03 -04:00
}
AString cPlayer : : GetColor ( void ) const
{
2014-08-19 11:34:11 -04:00
if ( m_MsgNameColorCode . empty ( ) | | ( m_MsgNameColorCode = = " - " ) )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
// Color has not been assigned, return an empty string:
return AString ( ) ;
2013-07-29 07:13:03 -04:00
}
2014-08-19 11:34:11 -04:00
// Return the color, including the delimiter:
return cChatColor : : Delimiter + m_MsgNameColorCode ;
2013-07-29 07:13:03 -04:00
}
2017-05-26 21:15:56 -04:00
AString cPlayer : : GetPrefix ( void ) const
{
return m_MsgPrefix ;
}
AString cPlayer : : GetSuffix ( void ) const
{
return m_MsgSuffix ;
}
2014-09-23 08:39:49 -04:00
AString cPlayer : : GetPlayerListName ( void ) const
2014-09-02 13:12:35 -04:00
{
const AString & Color = GetColor ( ) ;
if ( HasCustomName ( ) )
{
return m_CustomName ;
}
else if ( ( GetName ( ) . length ( ) < = 14 ) & & ! Color . empty ( ) )
{
return Printf ( " %s%s " , Color . c_str ( ) , GetName ( ) . c_str ( ) ) ;
}
else
{
return GetName ( ) ;
}
}
2017-07-06 23:27:04 -04:00
void cPlayer : : SetDraggingItem ( const cItem & a_Item )
{
if ( GetWindow ( ) ! = nullptr )
{
m_DraggingItem = a_Item ;
GetClientHandle ( ) - > SendInventorySlot ( - 1 , - 1 , m_DraggingItem ) ;
}
}
2014-01-23 02:27:39 -05:00
void cPlayer : : TossEquippedItem ( char a_Amount )
2013-07-29 07:13:03 -04:00
{
cItems Drops ;
2014-01-23 22:11:10 -05:00
cItem DroppedItem ( GetInventory ( ) . GetEquippedItem ( ) ) ;
if ( ! DroppedItem . IsEmpty ( ) )
2013-07-29 07:13:03 -04:00
{
2014-01-23 22:11:10 -05:00
char NewAmount = a_Amount ;
if ( NewAmount > GetInventory ( ) . GetEquippedItem ( ) . m_ItemCount )
{
2014-07-17 16:15:34 -04:00
NewAmount = GetInventory ( ) . GetEquippedItem ( ) . m_ItemCount ; // Drop only what's there
2014-01-23 22:11:10 -05:00
}
2014-01-15 17:36:19 -05:00
2014-01-23 22:11:10 -05:00
GetInventory ( ) . GetHotbarGrid ( ) . ChangeSlotCount ( GetInventory ( ) . GetEquippedSlotNum ( ) /* Returns hotbar subslot, which HotbarGrid takes */ , - a_Amount ) ;
2014-01-23 02:27:39 -05:00
2014-01-23 22:11:10 -05:00
DroppedItem . m_ItemCount = NewAmount ;
Drops . push_back ( DroppedItem ) ;
2013-07-29 07:13:03 -04:00
}
2014-01-23 02:27:39 -05:00
2014-05-12 14:38:52 -04:00
TossItems ( Drops ) ;
2014-01-23 02:27:39 -05:00
}
2020-04-02 08:42:15 -04: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 02:27:39 -05:00
void cPlayer : : TossHeldItem ( char a_Amount )
{
cItems Drops ;
2014-01-23 22:11:10 -05:00
cItem & Item = GetDraggingItem ( ) ;
if ( ! Item . IsEmpty ( ) )
2013-07-29 07:13:03 -04:00
{
2014-01-23 22:11:10 -05:00
char OriginalItemAmount = Item . m_ItemCount ;
Item . m_ItemCount = std : : min ( OriginalItemAmount , a_Amount ) ;
Drops . push_back ( Item ) ;
2014-05-12 14:38:52 -04:00
2014-01-23 22:11:10 -05:00
if ( OriginalItemAmount > a_Amount )
2013-07-29 07:13:03 -04:00
{
2014-01-23 22:11:10 -05:00
Item . m_ItemCount = OriginalItemAmount - a_Amount ;
2013-07-29 07:13:03 -04:00
}
else
{
2014-01-23 22:11:10 -05:00
Item . Empty ( ) ;
2013-07-29 07:13:03 -04:00
}
}
2014-01-23 22:11:10 -05:00
2014-05-12 14:38:52 -04:00
TossItems ( Drops ) ;
2014-01-23 02:27:39 -05:00
}
void cPlayer : : TossPickup ( const cItem & a_Item )
{
cItems Drops ;
2014-01-23 22:11:10 -05:00
Drops . push_back ( a_Item ) ;
2014-01-15 17:36:19 -05:00
2014-05-12 14:38:52 -04:00
TossItems ( Drops ) ;
}
2014-06-15 23:27:27 -04:00
bool cPlayer : : LoadFromDisk ( cWorldPtr & a_World )
2013-07-29 07:13:03 -04:00
{
2014-08-19 11:34:11 -04:00
LoadRank ( ) ;
2013-07-29 07:13:03 -04:00
2014-07-11 07:13:10 -04:00
// Load from the UUID file:
2014-07-18 15:12:27 -04:00
if ( LoadFromFile ( GetUUIDFileName ( m_UUID ) , a_World ) )
2014-07-10 18:06:05 -04:00
{
return true ;
}
2015-05-23 14:31:33 -04:00
2018-07-19 19:30:09 -04:00
// Check for old offline UUID filename, if it exists migrate to new filename
2017-08-25 08:43:18 -04:00
cUUID OfflineUUID = cClientHandle : : GenerateOfflineUUID ( GetName ( ) ) ;
2018-07-19 19:30:09 -04:00
auto OldFilename = GetUUIDFileName ( GetOldStyleOfflineUUID ( GetName ( ) ) ) ;
auto NewFilename = GetUUIDFileName ( m_UUID ) ;
// Only move if there isn't already a new file
if ( ! cFile : : IsFile ( NewFilename ) & & cFile : : IsFile ( OldFilename ) )
{
cFile : : CreateFolderRecursive ( GetUUIDFolderName ( m_UUID ) ) ; // Ensure folder exists to move to
if (
cFile : : Rename ( OldFilename , NewFilename ) & &
( m_UUID = = OfflineUUID ) & &
LoadFromFile ( NewFilename , a_World )
)
{
return true ;
}
}
// Load from the offline UUID file, if allowed:
2014-07-31 16:52:06 -04:00
const char * OfflineUsage = " (unused) " ;
2014-07-11 07:13:10 -04:00
if ( cRoot : : Get ( ) - > GetServer ( ) - > ShouldLoadOfflinePlayerData ( ) )
{
2014-07-31 16:52:06 -04:00
OfflineUsage = " " ;
2014-07-18 15:12:27 -04:00
if ( LoadFromFile ( GetUUIDFileName ( OfflineUUID ) , a_World ) )
2014-07-11 07:13:10 -04:00
{
return true ;
}
}
2015-05-23 14:31:33 -04:00
2014-07-11 07:13:10 -04:00
// Load from the old-style name-based file, if allowed:
if ( cRoot : : Get ( ) - > GetServer ( ) - > ShouldLoadNamedPlayerData ( ) )
{
AString OldStyleFileName = Printf ( " players/%s.json " , GetName ( ) . c_str ( ) ) ;
2014-07-18 15:12:27 -04:00
if ( LoadFromFile ( OldStyleFileName , a_World ) )
2014-07-11 07:13:10 -04:00
{
// Save in new format and remove the old file
2014-07-11 17:12:57 -04:00
if ( SaveToDisk ( ) )
{
cFile : : Delete ( OldStyleFileName ) ;
}
2014-07-11 07:13:10 -04:00
return true ;
}
}
2015-05-23 14:31:33 -04:00
2014-07-11 07:13:10 -04:00
// None of the files loaded successfully
2014-07-31 16:52:06 -04:00
LOG ( " Player data file not found for %s (%s, offline %s%s), will be reset to defaults. " ,
2017-08-25 08:43:18 -04:00
GetName ( ) . c_str ( ) , m_UUID . ToShortString ( ) . c_str ( ) , OfflineUUID . ToShortString ( ) . c_str ( ) , OfflineUsage
2014-07-11 07:13:10 -04:00
) ;
2014-07-20 05:46:45 -04:00
2014-10-20 16:55:07 -04:00
if ( a_World = = nullptr )
2014-07-20 05:46:45 -04:00
{
a_World = cRoot : : Get ( ) - > GetDefaultWorld ( ) ;
}
2014-07-10 18:06:58 -04:00
return false ;
2014-07-10 18:06:05 -04:00
}
2013-07-29 07:13:03 -04:00
2014-07-10 18:06:05 -04:00
2013-07-29 07:13:03 -04:00
2014-07-10 18:06:05 -04:00
2013-07-29 07:13:03 -04:00
2014-07-20 05:46:45 -04:00
bool cPlayer : : LoadFromFile ( const AString & a_FileName , cWorldPtr & a_World )
2014-07-10 18:06:05 -04:00
{
// Load the data from the file:
2013-07-29 07:13:03 -04:00
cFile f ;
2014-07-10 18:06:05 -04:00
if ( ! f . Open ( a_FileName , cFile : : fmRead ) )
2013-07-29 07:13:03 -04:00
{
2013-11-13 15:12:16 -05:00
// This is a new player whom we haven't seen yet, bail out, let them have the defaults
2013-07-29 07:13:03 -04:00
return false ;
}
AString buffer ;
if ( f . ReadRestOfFile ( buffer ) ! = f . GetSize ( ) )
{
2014-07-10 18:06:05 -04:00
LOGWARNING ( " Cannot read player data from file \" %s \" " , a_FileName . c_str ( ) ) ;
2013-07-29 07:13:03 -04:00
return false ;
}
2014-07-10 18:06:05 -04:00
f . Close ( ) ;
2013-07-29 07:13:03 -04:00
2014-07-10 18:06:05 -04:00
// Parse the JSON format:
2013-07-29 07:13:03 -04:00
Json : : Value root ;
2020-05-09 10:51:15 -04:00
AString ParseError ;
if ( ! JsonUtils : : ParseString ( buffer , root , & ParseError ) )
2013-07-29 07:13:03 -04:00
{
2020-05-09 10:51:15 -04:00
FLOGWARNING (
" Cannot parse player data in file \" {0} \" : \n {1} " ,
a_FileName , ParseError
) ;
2014-07-10 18:06:05 -04:00
return false ;
2013-07-29 07:13:03 -04:00
}
2014-07-10 18:06:05 -04:00
// Load the player data:
2013-07-29 07:13:03 -04:00
Json : : Value & JSON_PlayerPosition = root [ " position " ] ;
if ( JSON_PlayerPosition . size ( ) = = 3 )
{
2015-07-29 11:04:03 -04:00
SetPosX ( JSON_PlayerPosition [ 0 ] . asDouble ( ) ) ;
SetPosY ( JSON_PlayerPosition [ 1 ] . asDouble ( ) ) ;
SetPosZ ( JSON_PlayerPosition [ 2 ] . asDouble ( ) ) ;
2015-07-21 16:25:37 -04:00
m_LastPosition = GetPosition ( ) ;
2013-07-29 07:13:03 -04:00
}
Json : : Value & JSON_PlayerRotation = root [ " rotation " ] ;
if ( JSON_PlayerRotation . size ( ) = = 3 )
{
2015-07-29 11:04:03 -04: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 07:13:03 -04:00
}
2019-09-27 11:51:44 -04:00
m_Health = root . get ( " health " , 0 ) . asFloat ( ) ;
2013-08-08 05:32:34 -04:00
m_AirLevel = root . get ( " air " , MAX_AIR_LEVEL ) . asInt ( ) ;
2013-07-29 07:13:03 -04:00
m_FoodLevel = root . get ( " food " , MAX_FOOD_LEVEL ) . asInt ( ) ;
m_FoodSaturationLevel = root . get ( " foodSaturation " , MAX_FOOD_LEVEL ) . asDouble ( ) ;
m_FoodTickTimer = root . get ( " foodTickTimer " , 0 ) . asInt ( ) ;
m_FoodExhaustionLevel = root . get ( " foodExhaustion " , 0 ) . asDouble ( ) ;
2015-03-05 05:52:42 -05:00
m_LifetimeTotalXp = root . get ( " xpTotal " , 0 ) . asInt ( ) ;
m_CurrentXp = root . get ( " xpCurrent " , 0 ) . asInt ( ) ;
m_IsFlying = root . get ( " isflying " , 0 ) . asBool ( ) ;
2020-10-01 17:33:32 -04:00
m_EnchantmentSeed = root . get ( " enchantmentSeed " , GetRandomProvider ( ) . RandInt < unsigned int > ( ) ) . asUInt ( ) ;
2013-07-29 07:13:03 -04:00
2020-07-14 12:56:42 -04:00
Json : : Value & JSON_KnownItems = root [ " knownItems " ] ;
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 ( ) ;
Json : : Value & JSON_KnownRecipes = root [ " knownRecipes " ] ;
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 ) ;
}
}
2015-07-29 11:04:03 -04:00
m_GameMode = static_cast < eGameMode > ( root . get ( " gamemode " , eGameMode_NotSet ) . asInt ( ) ) ;
2013-12-19 15:53:47 -05:00
if ( m_GameMode = = eGameMode_Creative )
{
m_CanFly = true ;
}
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
m_Inventory . LoadFromJson ( root [ " inventory " ] ) ;
2018-07-22 18:23:33 -04:00
int equippedSlotNum = root . get ( " equippedItemSlot " , 0 ) . asInt ( ) ;
m_Inventory . SetEquippedSlotNum ( equippedSlotNum ) ;
2014-06-29 06:36:38 -04:00
cEnderChestEntity : : LoadFromJson ( root [ " enderchestinventory " ] , m_EnderChestContents ) ;
2013-07-29 07:13:03 -04:00
2021-01-05 19:35:42 -05:00
m_CurrentWorldName = root . get ( " world " , " world " ) . asString ( ) ;
2016-02-08 05:06:14 -05:00
a_World = cRoot : : Get ( ) - > GetWorld ( GetLoadedWorldName ( ) ) ;
2014-10-20 16:55:07 -04:00
if ( a_World = = nullptr )
2014-09-06 09:26:20 -04:00
{
a_World = cRoot : : Get ( ) - > GetDefaultWorld ( ) ;
}
2014-07-20 05:46:45 -04:00
2016-03-24 13:13:23 -04:00
2014-07-20 05:46:45 -04:00
m_LastBedPos . x = root . get ( " SpawnX " , a_World - > GetSpawnX ( ) ) . asInt ( ) ;
m_LastBedPos . y = root . get ( " SpawnY " , a_World - > GetSpawnY ( ) ) . asInt ( ) ;
m_LastBedPos . z = root . get ( " SpawnZ " , a_World - > GetSpawnZ ( ) ) . asInt ( ) ;
2021-01-05 19:35:42 -05:00
m_SpawnWorldName = root . get ( " SpawnWorld " , cRoot : : Get ( ) - > GetDefaultWorld ( ) - > GetName ( ) ) . asString ( ) ;
2014-05-11 13:30:54 -04:00
2020-08-12 04:54:36 -04:00
try
{
// Load the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
2021-01-05 19:35:42 -05:00
StatSerializer : : Load ( m_Stats , m_DefaultWorldPath , GetUUID ( ) . ToLongString ( ) ) ;
2020-08-12 04:54:36 -04:00
}
catch ( . . . )
{
LOGWARNING ( " Failed loading player statistics " ) ;
}
2015-05-23 14:31:33 -04:00
2018-09-24 16:33:39 -04:00
FLOGD ( " Player {0} was read from file \" {1} \" , spawning at {2:.2f} in world \" {3} \" " ,
GetName ( ) , a_FileName , GetPosition ( ) , a_World - > GetName ( )
2013-07-29 07:13:03 -04:00
) ;
2015-05-23 14:31:33 -04:00
2013-07-29 07:13:03 -04:00
return true ;
}
2017-10-21 12:56:09 -04: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 ) ;
}
}
2013-07-29 07:13:03 -04:00
bool cPlayer : : SaveToDisk ( )
{
2018-07-19 19:30:09 -04:00
cFile : : CreateFolderRecursive ( GetUUIDFolderName ( m_UUID ) ) ;
2013-07-29 07:13:03 -04:00
// create the JSON data
Json : : Value JSON_PlayerPosition ;
JSON_PlayerPosition . append ( Json : : Value ( GetPosX ( ) ) ) ;
JSON_PlayerPosition . append ( Json : : Value ( GetPosY ( ) ) ) ;
JSON_PlayerPosition . append ( Json : : Value ( GetPosZ ( ) ) ) ;
Json : : Value JSON_PlayerRotation ;
2014-01-17 05:11:17 -05:00
JSON_PlayerRotation . append ( Json : : Value ( GetYaw ( ) ) ) ;
2013-07-29 07:13:03 -04:00
JSON_PlayerRotation . append ( Json : : Value ( GetPitch ( ) ) ) ;
JSON_PlayerRotation . append ( Json : : Value ( GetRoll ( ) ) ) ;
Json : : Value JSON_Inventory ;
m_Inventory . SaveToJson ( JSON_Inventory ) ;
2014-06-29 06:36:38 -04:00
Json : : Value JSON_EnderChestInventory ;
cEnderChestEntity : : SaveToJson ( JSON_EnderChestInventory , m_EnderChestContents ) ;
2020-07-14 12:56:42 -04: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 07:13:03 -04:00
Json : : Value root ;
2014-07-02 13:51:37 -04:00
root [ " position " ] = JSON_PlayerPosition ;
root [ " rotation " ] = JSON_PlayerRotation ;
root [ " inventory " ] = JSON_Inventory ;
2020-07-14 12:56:42 -04:00
root [ " knownItems " ] = JSON_KnownItems ;
root [ " knownRecipes " ] = JSON_KnownRecipes ;
2018-07-22 18:23:33 -04:00
root [ " equippedItemSlot " ] = m_Inventory . GetEquippedSlotNum ( ) ;
2014-06-29 06:36:38 -04:00
root [ " enderchestinventory " ] = JSON_EnderChestInventory ;
2014-07-02 13:51:37 -04:00
root [ " health " ] = m_Health ;
root [ " xpTotal " ] = m_LifetimeTotalXp ;
root [ " xpCurrent " ] = m_CurrentXp ;
root [ " air " ] = m_AirLevel ;
root [ " food " ] = m_FoodLevel ;
root [ " foodSaturation " ] = m_FoodSaturationLevel ;
root [ " foodTickTimer " ] = m_FoodTickTimer ;
root [ " foodExhaustion " ] = m_FoodExhaustionLevel ;
root [ " isflying " ] = IsFlying ( ) ;
2014-07-11 07:13:10 -04:00
root [ " lastknownname " ] = GetName ( ) ;
2014-07-18 15:12:27 -04:00
root [ " SpawnX " ] = GetLastBedPos ( ) . x ;
root [ " SpawnY " ] = GetLastBedPos ( ) . y ;
root [ " SpawnZ " ] = GetLastBedPos ( ) . z ;
2021-01-05 19:35:42 -05:00
root [ " SpawnWorld " ] = m_SpawnWorldName ;
2020-10-01 17:33:32 -04:00
root [ " enchantmentSeed " ] = m_EnchantmentSeed ;
2021-01-05 19:35:42 -05:00
root [ " world " ] = m_CurrentWorldName ;
root [ " gamemode " ] = static_cast < int > ( m_GameMode ) ;
2013-07-29 07:13:03 -04:00
2020-05-09 10:51:15 -04:00
auto JsonData = JsonUtils : : WriteStyledString ( root ) ;
2014-07-11 07:13:10 -04:00
AString SourceFile = GetUUIDFileName ( m_UUID ) ;
2013-07-29 07:13:03 -04:00
cFile f ;
if ( ! f . Open ( SourceFile , cFile : : fmWrite ) )
{
2014-07-11 07:13:10 -04:00
LOGWARNING ( " Error writing player \" %s \" to file \" %s \" - cannot open file. Player will lose their progress. " ,
GetName ( ) . c_str ( ) , SourceFile . c_str ( )
) ;
2013-07-29 07:13:03 -04:00
return false ;
}
2015-02-28 12:15:06 -05:00
if ( f . Write ( JsonData . c_str ( ) , JsonData . size ( ) ) ! = static_cast < int > ( JsonData . size ( ) ) )
2013-07-29 07:13:03 -04:00
{
2014-07-11 07:13:10 -04:00
LOGWARNING ( " Error writing player \" %s \" to file \" %s \" - cannot save data. Player will lose their progress. " ,
GetName ( ) . c_str ( ) , SourceFile . c_str ( )
2014-07-17 16:50:58 -04:00
) ;
2013-07-29 07:13:03 -04:00
return false ;
}
2014-05-11 13:30:54 -04:00
2020-08-12 04:54:36 -04:00
try
{
// Save the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
2021-01-05 19:35:42 -05:00
// TODO: save together with player.dat, not in some other place.
StatSerializer : : Save ( m_Stats , m_DefaultWorldPath , GetUUID ( ) . ToLongString ( ) ) ;
2020-08-12 04:54:36 -04:00
}
catch ( . . . )
2014-05-11 13:30:54 -04:00
{
2014-07-11 07:13:10 -04:00
LOGWARNING ( " Could not save stats for player %s " , GetName ( ) . c_str ( ) ) ;
2014-05-11 13:30:54 -04:00
return false ;
}
2013-07-29 07:13:03 -04:00
return true ;
}
2018-01-05 06:28:06 -05:00
void cPlayer : : UseEquippedItem ( short a_Damage )
2013-07-29 07:13:03 -04:00
{
2018-01-05 06:28:06 -05:00
// No durability loss in creative or spectator modes:
if ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) )
2013-07-29 07:13:03 -04:00
{
return ;
}
2013-11-02 10:08:00 -04:00
2018-07-12 18:10:15 -04:00
UseItem ( cInventory : : invHotbarOffset + m_Inventory . GetEquippedSlotNum ( ) , a_Damage ) ;
2013-07-29 07:13:03 -04:00
}
2013-08-14 04:33:26 -04:00
2018-01-05 06:28:06 -05: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-12 18:10:15 -04: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 07:13:03 -04:00
void cPlayer : : HandleFood ( void )
{
2017-08-24 05:19:40 -04:00
// Ref.: https://minecraft.gamepedia.com/Hunger
2014-06-30 09:12:56 -04:00
2014-12-02 20:25:41 -05:00
if ( IsGameModeCreative ( ) | | IsGameModeSpectator ( ) )
2014-02-11 09:34:18 -05:00
{
2014-12-02 20:25:41 -05:00
// Hunger is disabled for Creative and Spectator
2014-02-11 09:34:18 -05:00
return ;
}
2014-06-30 09:12:56 -04:00
2014-07-31 17:04:00 -04:00
// Apply food exhaustion that has accumulated:
if ( m_FoodExhaustionLevel > 4.0 )
{
m_FoodExhaustionLevel - = 4.0 ;
if ( m_FoodSaturationLevel > 0.0 )
{
m_FoodSaturationLevel = std : : max ( m_FoodSaturationLevel - 1.0 , 0.0 ) ;
}
else
{
SetFoodLevel ( m_FoodLevel - 1 ) ;
}
}
2013-07-29 07:13:03 -04:00
// Heal or damage, based on the food level, using the m_FoodTickTimer:
2014-07-31 17:04:00 -04:00
if ( ( m_FoodLevel > = 18 ) | | ( m_FoodLevel < = 0 ) )
2013-07-29 07:13:03 -04:00
{
m_FoodTickTimer + + ;
if ( m_FoodTickTimer > = 80 )
{
m_FoodTickTimer = 0 ;
2014-07-31 17:04:00 -04:00
if ( ( m_FoodLevel > = 18 ) & & ( GetHealth ( ) < GetMaxHealth ( ) ) )
2013-07-29 07:13:03 -04:00
{
// Regenerate health from food, incur 3 pts of food exhaustion:
Heal ( 1 ) ;
2014-07-31 17:04:00 -04:00
AddFoodExhaustion ( 3.0 ) ;
2013-07-29 07:13:03 -04:00
}
2014-02-16 08:37:36 -05:00
else if ( ( m_FoodLevel < = 0 ) & & ( m_Health > 1 ) )
2013-07-29 07:13:03 -04:00
{
// Damage from starving
2014-10-20 16:55:07 -04:00
TakeDamage ( dtStarving , nullptr , 1 , 1 , 0 ) ;
2013-07-29 07:13:03 -04:00
}
}
}
2014-07-31 17:04:00 -04:00
else
2013-07-29 07:13:03 -04:00
{
2014-07-31 17:04:00 -04:00
m_FoodTickTimer = 0 ;
2013-07-29 07:13:03 -04:00
}
}
2013-12-21 11:31:05 -05:00
void cPlayer : : HandleFloater ( )
{
if ( GetEquippedItem ( ) . m_ItemType = = E_ITEM_FISHING_ROD )
{
return ;
}
2017-09-11 17:20:49 -04:00
m_World - > DoWithEntityByID ( m_FloaterID , [ ] ( cEntity & a_Entity )
2013-12-21 11:31:05 -05:00
{
2020-03-05 05:52:34 -05:00
a_Entity . Destroy ( ) ;
2013-12-21 11:31:05 -05:00
return true ;
}
2017-09-11 17:20:49 -04:00
) ;
2013-12-21 11:31:05 -05:00
SetIsFishing ( false ) ;
}
2014-05-20 08:52:59 -04:00
bool cPlayer : : IsClimbing ( void ) const
{
2021-03-14 22:28:18 -04:00
const auto Position = GetPosition ( ) . Floor ( ) ;
2014-05-20 08:52:59 -04:00
2021-03-14 22:28:18 -04:00
if ( ! cChunkDef : : IsValidHeight ( Position . y ) )
2014-05-20 08:52:59 -04:00
{
return false ;
}
2021-03-14 22:28:18 -04:00
BLOCKTYPE Block = m_World - > GetBlock ( Position ) ;
2014-05-20 08:52:59 -04:00
switch ( Block )
{
case E_BLOCK_LADDER :
case E_BLOCK_VINES :
{
return true ;
}
default : return false ;
}
}
2015-07-21 16:25:37 -04:00
void cPlayer : : UpdateMovementStats ( const Vector3d & a_DeltaPos , bool a_PreviousIsOnGround )
2014-05-12 14:38:52 -04:00
{
2015-07-21 16:25:37 -04:00
if ( m_bIsTeleporting )
{
m_bIsTeleporting = false ;
return ;
}
2014-05-12 14:38:52 -04:00
2020-08-12 04:54:36 -04:00
const auto Value = FloorC < cStatManager : : StatValue > ( a_DeltaPos . Length ( ) * 100 + 0.5 ) ;
2014-10-20 16:55:07 -04:00
if ( m_AttachedTo = = nullptr )
2014-05-12 14:38:52 -04:00
{
2015-07-21 16:25:37 -04:00
if ( IsFlying ( ) )
{
2020-08-12 04:54:36 -04:00
m_Stats . AddValue ( Statistic : : FlyOneCm , Value ) ;
2015-07-21 16:25:37 -04:00
// May be flying and doing any of the following:
}
2014-05-20 08:52:59 -04:00
if ( IsClimbing ( ) )
2014-05-12 14:38:52 -04:00
{
2014-07-17 16:15:34 -04:00
if ( a_DeltaPos . y > 0.0 ) // Going up
2014-05-20 08:52:59 -04:00
{
2020-08-12 04:54:36 -04:00
m_Stats . AddValue ( Statistic : : ClimbOneCm , FloorC < cStatManager : : StatValue > ( a_DeltaPos . y * 100 + 0.5 ) ) ;
2014-05-20 08:52:59 -04:00
}
2014-05-12 14:38:52 -04:00
}
2018-01-14 13:44:45 -05:00
else if ( IsInWater ( ) )
2014-05-20 08:52:59 -04:00
{
2020-09-05 11:13:44 -04:00
if ( m_IsHeadInWater )
{
m_Stats . AddValue ( Statistic : : WalkUnderWaterOneCm , Value ) ;
}
else
{
m_Stats . AddValue ( Statistic : : WalkOnWaterOneCm , Value ) ;
}
2015-02-28 12:15:06 -05:00
AddFoodExhaustion ( 0.00015 * static_cast < double > ( Value ) ) ;
2014-05-20 08:52:59 -04:00
}
else if ( IsOnGround ( ) )
2014-05-12 14:38:52 -04:00
{
2020-09-05 11:13:44 -04: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 14:38:52 -04:00
}
2014-05-20 08:52:59 -04:00
else
{
2015-07-21 16:25:37 -04:00
// If a jump just started, process food exhaustion:
if ( ( a_DeltaPos . y > 0.0 ) & & a_PreviousIsOnGround )
{
2020-08-12 04:54:36 -04:00
m_Stats . AddValue ( Statistic : : Jump , 1 ) ;
2015-07-21 16:25:37 -04:00
AddFoodExhaustion ( ( IsSprinting ( ) ? 0.008 : 0.002 ) * static_cast < double > ( Value ) ) ;
}
else if ( a_DeltaPos . y < 0.0 )
2014-05-20 08:52:59 -04:00
{
2015-07-21 16:25:37 -04:00
// Increment statistic
2020-08-12 04:54:36 -04:00
m_Stats . AddValue ( Statistic : : FallOneCm , static_cast < cStatManager : : StatValue > ( std : : abs ( a_DeltaPos . y ) * 100 + 0.5 ) ) ;
2014-05-20 08:52:59 -04:00
}
2015-07-21 16:25:37 -04:00
// TODO: good opportunity to detect illegal flight (check for falling tho)
2014-05-20 08:52:59 -04:00
}
2014-05-12 14:38:52 -04:00
}
else
{
switch ( m_AttachedTo - > GetEntityType ( ) )
{
2020-08-12 04:54:36 -04:00
case cEntity : : etMinecart : m_Stats . AddValue ( Statistic : : MinecartOneCm , Value ) ; break ;
case cEntity : : etBoat : m_Stats . AddValue ( Statistic : : BoatOneCm , Value ) ; break ;
2014-05-12 14:38:52 -04:00
case cEntity : : etMonster :
{
2018-05-02 03:50:36 -04:00
cMonster * Monster = static_cast < cMonster * > ( m_AttachedTo ) ;
2014-05-12 14:38:52 -04:00
switch ( Monster - > GetMobType ( ) )
{
2020-08-12 04:54:36 -04:00
case mtPig : m_Stats . AddValue ( Statistic : : PigOneCm , Value ) ; break ;
case mtHorse : m_Stats . AddValue ( Statistic : : HorseOneCm , Value ) ; break ;
2014-05-12 14:38:52 -04:00
default : break ;
}
break ;
}
default : break ;
}
}
}
2014-08-19 11:34:11 -04:00
void cPlayer : : LoadRank ( void )
{
// Load the values from cRankManager:
2014-10-18 14:55:01 -04:00
cRankManager * RankMgr = cRoot : : Get ( ) - > GetRankManager ( ) ;
m_Rank = RankMgr - > GetPlayerRankName ( m_UUID ) ;
2014-08-24 14:00:45 -04:00
if ( m_Rank . empty ( ) )
{
2014-10-18 14:55:01 -04:00
m_Rank = RankMgr - > GetDefaultRank ( ) ;
2014-08-24 14:00:45 -04:00
}
2014-09-27 20:17:32 -04:00
else
{
// Update the name:
2021-01-05 19:35:42 -05:00
RankMgr - > UpdatePlayerName ( m_UUID , GetName ( ) ) ;
2014-09-27 20:17:32 -04:00
}
2014-10-18 14:55:01 -04:00
m_Permissions = RankMgr - > GetPlayerPermissions ( m_UUID ) ;
2015-04-25 13:40:44 -04:00
m_Restrictions = RankMgr - > GetPlayerRestrictions ( m_UUID ) ;
2014-10-18 14:55:01 -04:00
RankMgr - > GetRankVisuals ( m_Rank , m_MsgPrefix , m_MsgSuffix , m_MsgNameColorCode ) ;
2014-08-19 11:34:11 -04:00
// Break up the individual permissions on each dot, into m_SplitPermissions:
m_SplitPermissions . clear ( ) ;
m_SplitPermissions . reserve ( m_Permissions . size ( ) ) ;
2015-04-25 13:40:44 -04:00
for ( auto & Permission : m_Permissions )
{
m_SplitPermissions . push_back ( StringSplit ( Permission , " . " ) ) ;
} // for Permission - m_Permissions[]
// Break up the individual restrictions on each dot, into m_SplitRestrictions:
m_SplitRestrictions . clear ( ) ;
m_SplitRestrictions . reserve ( m_Restrictions . size ( ) ) ;
for ( auto & Restriction : m_Restrictions )
2014-08-19 11:34:11 -04:00
{
2015-04-25 13:40:44 -04:00
m_SplitRestrictions . push_back ( StringSplit ( Restriction , " . " ) ) ;
} // for itr - m_Restrictions[]
2014-08-19 11:34:11 -04:00
}
2014-12-24 01:20:17 -05:00
bool cPlayer : : PlaceBlock ( int a_BlockX , int a_BlockY , int a_BlockZ , BLOCKTYPE a_BlockType , NIBBLETYPE a_BlockMeta )
{
sSetBlockVector blk { { a_BlockX , a_BlockY , a_BlockZ , a_BlockType , a_BlockMeta } } ;
return PlaceBlocks ( blk ) ;
}
void cPlayer : : SendBlocksAround ( int a_BlockX , int a_BlockY , int a_BlockZ , int a_Range )
{
// Collect the coords of all the blocks to send:
sSetBlockVector blks ;
for ( int y = a_BlockY - a_Range + 1 ; y < a_BlockY + a_Range ; y + + )
{
for ( int z = a_BlockZ - a_Range + 1 ; z < a_BlockZ + a_Range ; z + + )
{
for ( int x = a_BlockX - a_Range + 1 ; x < a_BlockX + a_Range ; x + + )
{
blks . emplace_back ( x , y , z , E_BLOCK_AIR , 0 ) ; // Use fake blocktype, it will get set later on.
2019-08-11 05:39:43 -04:00
}
}
2014-12-24 01:20:17 -05: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 02:38:37 -05:00
std : : unordered_map < cChunkCoords , sSetBlockVector , cChunkCoordsHash > Changes ;
2014-12-24 01:20:17 -05:00
for ( const auto & blk : blks )
{
Changes [ cChunkCoords ( blk . m_ChunkX , blk . m_ChunkZ ) ] . push_back ( blk ) ;
} // for blk - blks[]
blks . clear ( ) ;
// Send the blocks for each affected chunk:
for ( auto itr = Changes . cbegin ( ) , end = Changes . cend ( ) ; itr ! = end ; + + itr )
{
m_ClientHandle - > SendBlockChanges ( itr - > first . m_ChunkX , itr - > first . m_ChunkZ , itr - > second ) ;
}
}
2017-07-28 12:59:21 -04: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 09:50:52 -04:00
cBoundingBox BlockBox = cBlockHandler : : For ( blk . m_BlockType ) . GetPlacementCollisionBox (
2021-03-14 22:28:18 -04: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 12:59:21 -04: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 17:20:49 -04:00
return ! World - > ForEachEntityInBox ( PlacingBounds , [ & ] ( cEntity & a_Entity )
2017-07-28 12:59:21 -04:00
{
2017-09-11 17:20:49 -04:00
// The distance inside the block the entity can still be.
const double EPSILON = 0.0005 ;
2017-07-28 12:59:21 -04:00
2017-09-11 17:20:49 -04:00
if ( ! a_Entity . DoesPreventBlockPlacement ( ) )
2017-07-28 12:59:21 -04:00
{
return false ;
}
2020-05-03 16:04:33 -04:00
auto EntBox = a_Entity . GetBoundingBox ( ) ;
2017-09-11 17:20:49 -04:00
for ( auto BlockBox : PlacementBoxes )
2017-07-28 12:59:21 -04: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 17:20:49 -04:00
) ;
2017-07-28 12:59:21 -04:00
}
2014-12-24 01:20:17 -05:00
bool cPlayer : : PlaceBlocks ( const sSetBlockVector & a_Blocks )
{
2017-07-28 12:59:21 -04: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 15:50:40 -04:00
m_World - > SendBlockTo ( blk2 . GetX ( ) , blk2 . GetY ( ) , blk2 . GetZ ( ) , * this ) ;
2017-07-28 12:59:21 -04:00
}
return false ;
}
2014-12-24 01:20:17 -05: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 15:50:40 -04:00
m_World - > SendBlockTo ( blk2 . GetX ( ) , blk2 . GetY ( ) , blk2 . GetZ ( ) , * this ) ;
2014-12-24 01:20:17 -05:00
}
return false ;
}
} // for blk - a_Blocks[]
cChunkInterface ChunkInterface ( m_World - > GetChunkMap ( ) ) ;
for ( auto blk : a_Blocks )
{
2020-07-29 14:30:38 -04:00
// Set the blocks:
m_World - > PlaceBlock ( blk . GetAbsolutePos ( ) , blk . m_BlockType , blk . m_BlockMeta ) ;
// Notify the blockhandlers:
2020-09-20 09:50:52 -04:00
cBlockHandler : : For ( blk . m_BlockType ) . OnPlacedByPlayer ( ChunkInterface , * m_World , * this , blk ) ;
2014-12-24 01:20:17 -05:00
2020-07-29 14:30:38 -04:00
// Call the "placed" hooks:
2014-12-24 01:20:17 -05:00
pm - > CallHookPlayerPlacedBlock ( * this , blk ) ;
}
2020-07-29 14:30:38 -04:00
2014-12-24 01:20:17 -05:00
return true ;
}
2017-01-03 15:19:29 -05: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 08:38:45 -04:00
void cPlayer : : AttachTo ( cEntity * a_AttachTo )
{
// Different attach, if this is a spectator
if ( IsGameModeSpectator ( ) )
{
2020-03-30 15:35:37 -04:00
SpectateEntity ( a_AttachTo ) ;
2016-10-12 08:38:45 -04:00
return ;
}
2020-04-13 12:38:06 -04:00
Super : : AttachTo ( a_AttachTo ) ;
2016-10-12 08:38:45 -04:00
}
2014-01-12 18:23:36 -05:00
void cPlayer : : Detach ( )
{
2016-10-12 08:38:45 -04: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 12:38:06 -04:00
Super : : Detach ( ) ;
2014-04-17 13:50:25 -04:00
int PosX = POSX_TOINT ;
int PosY = POSY_TOINT ;
int PosZ = POSZ_TOINT ;
2014-01-12 18:23:36 -05:00
// Search for a position within an area to teleport player after detachment
2016-05-03 03:48:39 -04:00
// Position must be solid land with two air blocks above.
2014-01-12 18:23:36 -05:00
// If nothing found, player remains where they are
2016-05-03 03:48:39 -04:00
for ( int x = PosX - 1 ; x < = ( PosX + 1 ) ; + + x )
2014-01-12 18:23:36 -05:00
{
for ( int y = PosY ; y < = ( PosY + 3 ) ; + + y )
{
2016-05-03 03:48:39 -04:00
for ( int z = PosZ - 1 ; z < = ( PosZ + 1 ) ; + + z )
2014-01-12 18:23:36 -05:00
{
2016-05-03 03:48:39 -04:00
if (
2021-03-14 22:28:18 -04: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 03:48:39 -04:00
)
2014-01-12 18:23:36 -05:00
{
2016-05-03 03:48:39 -04:00
TeleportToCoords ( x + 0.5 , y , z + 0.5 ) ;
2014-01-12 18:23:36 -05:00
return ;
}
}
}
}
2014-01-14 13:16:13 -05:00
}
2014-07-11 07:13:10 -04:00
2017-08-25 08:43:18 -04:00
AString cPlayer : : GetUUIDFileName ( const cUUID & a_UUID )
2014-07-11 07:13:10 -04:00
{
2017-08-25 08:43:18 -04:00
AString UUID = a_UUID . ToLongString ( ) ;
2015-05-23 14:31:33 -04:00
2020-05-07 15:14:00 -04:00
AString res ( " players/ " ) ;
2014-07-31 16:52:06 -04:00
res . append ( UUID , 0 , 2 ) ;
2014-07-11 07:13:10 -04:00
res . push_back ( ' / ' ) ;
2014-07-31 16:52:06 -04:00
res . append ( UUID , 2 , AString : : npos ) ;
2014-07-11 07:13:10 -04:00
res . append ( " .json " ) ;
return res ;
}
2016-04-13 08:15:12 -04: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
GetClientHandle ( ) - > SendPlayerMoveLook ( ) ;
GetClientHandle ( ) - > SendPlayerAbilities ( ) ;
GetClientHandle ( ) - > SendPlayerMaxSpeed ( ) ;
GetClientHandle ( ) - > SendEntityVelocity ( * this ) ;
// 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 13:30:19 -05:00
2018-07-26 17:24:36 -04:00
2016-11-06 13:30:19 -05: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-03-14 22:28:18 -04:00
BLOCKTYPE Block = m_World - > GetBlock ( Vector3d ( GetPosX ( ) , m_Stance , GetPosZ ( ) ) . Floor ( ) ) ;
2016-11-06 13:30:19 -05:00
if ( ( Block ! = E_BLOCK_WATER ) & & ( Block ! = E_BLOCK_STATIONARY_WATER ) )
{
return false ;
}
2021-03-14 22:28:18 -04:00
NIBBLETYPE Meta = GetWorld ( ) - > GetBlockMeta ( Vector3d ( GetPosX ( ) , m_Stance , GetPosZ ( ) ) . Floor ( ) ) ;
2016-11-06 13:30:19 -05:00
float f = GetLiquidHeightPercent ( Meta ) - 0.11111111f ;
float f1 = static_cast < float > ( m_Stance + 1 ) - f ;
bool flag = ( m_Stance < f1 ) ;
return flag ;
}
float cPlayer : : GetDigSpeed ( BLOCKTYPE a_Block )
{
2020-10-02 16:57:17 -04: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 13:30:19 -05:00
{
2020-10-02 16:57:17 -04:00
if ( MiningSpeed > 1.0f ) // If the base multiplier for this block is greater than 1, now we can check enchantments
2016-11-06 13:30:19 -05:00
{
2020-10-02 16:57:17 -04: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 13:30:19 -05:00
}
}
2020-10-02 16:57:17 -04:00
else // If we can't harvest the block then no bonuses:
{
MiningSpeed = 1 ;
}
2016-11-06 13:30:19 -05:00
2020-10-02 16:57:17 -04:00
// Haste increases speed by 20% per level
2017-08-01 13:51:43 -04:00
auto Haste = GetEntityEffect ( cEntityEffect : : effHaste ) ;
if ( Haste ! = nullptr )
2016-11-06 13:30:19 -05:00
{
2017-08-01 13:51:43 -04:00
int intensity = Haste - > GetIntensity ( ) + 1 ;
2020-10-02 16:57:17 -04:00
MiningSpeed * = 1.0f + ( intensity * 0.2f ) ;
2016-11-06 13:30:19 -05:00
}
2020-10-02 16:57:17 -04:00
// Mining fatigue decreases speed a lot
2017-08-01 13:51:43 -04:00
auto MiningFatigue = GetEntityEffect ( cEntityEffect : : effMiningFatigue ) ;
if ( MiningFatigue ! = nullptr )
2016-11-06 13:30:19 -05:00
{
2017-08-01 13:51:43 -04:00
int intensity = MiningFatigue - > GetIntensity ( ) ;
2016-11-06 13:30:19 -05:00
switch ( intensity )
{
2020-10-02 16:57:17 -04: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 13:30:19 -05:00
}
}
2020-10-02 16:57:17 -04:00
// 5x speed loss for being in water
2016-11-06 13:30:19 -05:00
if ( IsInsideWater ( ) & & ! ( GetEquippedItem ( ) . m_Enchantments . GetLevel ( cEnchantments : : eEnchantment : : enchAquaAffinity ) > 0 ) )
{
2020-10-02 16:57:17 -04:00
MiningSpeed / = 5.0f ;
2016-11-06 13:30:19 -05:00
}
2020-10-02 16:57:17 -04:00
// 5x speed loss for not touching ground
2016-11-06 13:30:19 -05:00
if ( ! IsOnGround ( ) )
{
2020-10-02 16:57:17 -04:00
MiningSpeed / = 5.0f ;
2016-11-06 13:30:19 -05:00
}
2020-10-02 16:57:17 -04:00
return MiningSpeed ;
2016-11-06 13:30:19 -05:00
}
2020-10-02 16:57:17 -04:00
float cPlayer : : GetMiningProgressPerTick ( BLOCKTYPE a_Block )
2016-11-06 13:30:19 -05:00
{
2020-10-02 16:57:17 -04: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 09:58:47 -05:00
const bool CanHarvest = GetEquippedItem ( ) . GetHandler ( ) - > CanHarvestBlock ( a_Block ) ;
const float BlockHardness = cBlockInfo : : GetHardness ( a_Block ) * ( CanHarvest ? 1.5f : 5.0f ) ;
2020-10-02 16:57:17 -04:00
ASSERT ( BlockHardness > 0 ) ; // Can't divide by 0 or less, IsOneHitDig should have returned true
2020-11-06 09:58:47 -05:00
2020-10-02 16:57:17 -04: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 09:58:47 -05:00
return GetDigSpeed ( a_Block ) / ( 20.0f * BlockHardness ) ;
2020-10-02 16:57:17 -04:00
}
bool cPlayer : : CanInstantlyMine ( BLOCKTYPE a_Block )
{
// Based on: https://minecraft.gamepedia.com/Breaking#Calculation
2020-11-06 09:58:47 -05: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 13:30:19 -05:00
}
2018-07-23 05:24:00 -04:00
2021-02-06 13:37:03 -05:00
void cPlayer : : AddKnownItem ( const cItem & a_Item )
2018-07-23 05:24:00 -04:00
{
2021-02-06 13:37:03 -05:00
if ( a_Item . m_ItemType < 0 )
{
return ;
}
auto Response = m_KnownItems . insert ( a_Item . CopyOne ( ) ) ;
if ( ! Response . second )
2018-07-23 05:24:00 -04:00
{
2021-02-06 13:37:03 -05:00
// The item was already known, bail out:
return ;
2018-07-23 05:24:00 -04:00
}
2021-02-06 13:37:03 -05: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 ) ;
}
}
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 ;
}
}
}
else if ( GetWorld ( ) - > GetWorldAge ( ) % 100 = = 0 )
{
// 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 ( ) ;
}
void cPlayer : : OnAddToWorld ( cWorld & a_World )
{
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 ) ;
// Set capabilities based on new world:
SetCapabilities ( ) ;
// 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:
m_ClientHandle - > SendTimeUpdate ( a_World . GetWorldAge ( ) , a_World . GetTimeOfDay ( ) , a_World . IsDaylightCycleEnabled ( ) ) ;
// 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 ) ;
}
// 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 ( ) ;
// Clientside warp start:
m_ClientHandle - > SendRespawn ( DestinationDimension , false ) ;
}
void cPlayer : : SpawnOn ( cClientHandle & a_Client )
{
if ( ! m_bVisible | | ( m_ClientHandle . get ( ) = = ( & a_Client ) ) )
{
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 ;
}
BroadcastMovementUpdate ( m_ClientHandle . get ( ) ) ;
if ( m_Health > 0 ) // make sure player is alive
{
m_World - > CollectPickupsByPlayer ( * this ) ;
if ( ( m_EatingFinishTick > = 0 ) & & ( m_EatingFinishTick < = m_World - > GetWorldAge ( ) ) )
{
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 05:24:00 -04:00
}