2014-04-10 10:52:00 -04:00
2012-06-14 09:06:06 -04:00
# include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
2013-10-20 07:25:56 -04:00
# include "IncludeAllMonsters.h"
2020-12-18 15:48:32 -05:00
# include "LineBlockTracer.h"
2020-04-03 02:57:01 -04:00
# include "../BlockInfo.h"
2012-09-23 18:09:57 -04:00
# include "../Root.h"
# include "../Server.h"
# include "../ClientHandle.h"
2017-08-18 06:23:08 -04:00
# include "../Items/ItemHandler.h"
2012-09-23 18:09:57 -04:00
# include "../World.h"
2015-11-23 18:39:19 -05:00
# include "../EffectID.h"
2013-08-19 05:39:13 -04:00
# include "../Entities/Player.h"
2013-11-25 14:04:39 -05:00
# include "../Entities/ExpOrb.h"
2012-09-23 18:09:57 -04:00
# include "../MonsterConfig.h"
2018-08-28 20:51:25 -04:00
# include "../BoundingBox.h"
2012-09-23 16:53:08 -04:00
2020-10-09 16:49:25 -04:00
# include "Items/ItemSpawnEgg.h"
2013-09-05 16:40:08 -04:00
# include "../Chunk.h"
2013-10-20 07:25:56 -04:00
# include "../FastRandom.h"
2013-04-22 03:18:03 -04:00
2015-11-20 03:03:20 -05:00
# include "PathFinder.h"
2017-08-21 04:46:41 -04:00
# include "../Entities/LeashKnot.h"
2013-10-20 07:25:56 -04:00
/** Map for eType <-> string
Needs to be alpha - sorted by the strings , because binary search is used in StringToMobType ( )
The strings need to be lowercase ( for more efficient comparisons in StringToMobType ( ) )
2014-12-01 08:58:13 -05:00
m_VanillaName is the name that vanilla use for this mob .
2013-10-20 07:25:56 -04:00
*/
static const struct
{
2014-09-17 13:40:10 -04:00
eMonsterType m_Type ;
2013-10-20 07:25:56 -04:00
const char * m_lcName ;
2014-11-29 09:20:44 -05:00
const char * m_VanillaName ;
2017-05-22 11:41:41 -04:00
const char * m_VanillaNameNBT ;
2013-10-20 07:25:56 -04:00
} g_MobTypeNames [ ] =
{
2020-04-04 07:44:17 -04:00
{ mtBat , " bat " , " Bat " , " bat " } ,
{ mtBlaze , " blaze " , " Blaze " , " blaze " } ,
{ mtCaveSpider , " cavespider " , " CaveSpider " , " cave_spider " } ,
{ mtChicken , " chicken " , " Chicken " , " chicken " } ,
{ mtCow , " cow " , " Cow " , " cow " } ,
{ mtCreeper , " creeper " , " Creeper " , " creeper " } ,
{ mtEnderman , " enderman " , " Enderman " , " enderman " } ,
{ mtEnderDragon , " enderdragon " , " EnderDragon " , " ender_dragon " } ,
{ mtGhast , " ghast " , " Ghast " , " ghast " } ,
{ mtGiant , " giant " , " Giant " , " giant " } ,
{ mtGuardian , " guardian " , " Guardian " , " guardian " } ,
{ mtHorse , " horse " , " EntityHorse " , " horse " } ,
{ mtIronGolem , " irongolem " , " VillagerGolem " , " iron_golem " } ,
{ mtMagmaCube , " magmacube " , " LavaSlime " , " magma_cube " } ,
{ mtMooshroom , " mooshroom " , " MushroomCow " , " mooshroom " } ,
{ mtOcelot , " ocelot " , " Ozelot " , " ocelot " } ,
{ mtPig , " pig " , " Pig " , " pig " } ,
{ mtRabbit , " rabbit " , " Rabbit " , " rabbit " } ,
{ mtSheep , " sheep " , " Sheep " , " sheep " } ,
{ mtSilverfish , " silverfish " , " Silverfish " , " silverfish " } ,
{ mtSkeleton , " skeleton " , " Skeleton " , " skeleton " } ,
{ mtSlime , " slime " , " Slime " , " slime " } ,
{ mtSnowGolem , " snowgolem " , " SnowMan " , " snow_golem " } ,
{ mtSpider , " spider " , " Spider " , " spider " } ,
{ mtSquid , " squid " , " Squid " , " squid " } ,
{ mtVillager , " villager " , " Villager " , " villager " } ,
{ mtWitch , " witch " , " Witch " , " witch " } ,
{ mtWither , " wither " , " WitherBoss " , " wither " } ,
{ mtWitherSkeleton , " witherskeleton " , " WitherSkeleton " , " wither_skeleton " } ,
{ mtWolf , " wolf " , " Wolf " , " wolf " } ,
{ mtZombie , " zombie " , " Zombie " , " zombie " } ,
{ mtZombiePigman , " zombiepigman " , " PigZombie " , " zombie_pigman " } ,
2018-02-03 06:24:19 -05:00
{ mtZombieVillager , " zombievillager " , " ZombieVillager " , " zombie_villager " } ,
2013-10-20 07:25:56 -04:00
} ;
2014-07-17 16:15:34 -04:00
////////////////////////////////////////////////////////////////////////////////
2013-10-20 07:25:56 -04:00
// cMonster:
2012-06-14 09:06:06 -04:00
2021-04-06 11:09:16 -04:00
cMonster : : cMonster ( const AString & a_ConfigName , eMonsterType a_MobType , const AString & a_SoundHurt , const AString & a_SoundDeath , const AString & a_SoundAmbient , float a_Width , float a_Height )
2020-04-13 12:38:06 -04:00
: Super ( etMonster , a_Width , a_Height )
2013-12-20 10:39:20 -05:00
, m_EMState ( IDLE )
, m_EMPersonality ( AGGRESSIVE )
2015-11-20 03:03:20 -05:00
, m_PathFinder ( a_Width , a_Height )
2015-07-11 15:40:03 -04:00
, m_PathfinderActivated ( false )
2015-05-02 14:22:28 -04:00
, m_JumpCoolDown ( 0 )
2014-02-05 12:43:49 -05:00
, m_IdleInterval ( 0 )
2014-01-24 16:55:04 -05:00
, m_DestroyTimer ( 0 )
2013-10-20 04:23:30 -04:00
, m_MobType ( a_MobType )
2021-06-15 05:00:02 -04:00
, m_CustomName ( )
2014-09-01 14:12:56 -04:00
, m_CustomNameAlwaysVisible ( false )
2012-12-22 04:39:13 -05:00
, m_SoundHurt ( a_SoundHurt )
, m_SoundDeath ( a_SoundDeath )
2020-03-22 11:50:34 -04:00
, m_SoundAmbient ( a_SoundAmbient )
2014-02-05 12:43:49 -05:00
, m_AttackRate ( 3 )
2014-01-24 16:55:04 -05:00
, m_AttackDamage ( 1 )
2015-05-16 07:05:33 -04:00
, m_AttackRange ( 1 )
2016-01-12 05:11:10 -05:00
, m_AttackCoolDownTicksLeft ( 0 )
2014-02-24 09:38:38 -05:00
, m_SightDistance ( 25 )
2014-03-16 17:00:28 -04:00
, m_DropChanceWeapon ( 0.085f )
, m_DropChanceHelmet ( 0.085f )
, m_DropChanceChestplate ( 0.085f )
, m_DropChanceLeggings ( 0.085f )
, m_DropChanceBoots ( 0.085f )
2014-02-23 13:44:58 -05:00
, m_CanPickUpLoot ( true )
2015-04-29 12:24:14 -04:00
, m_TicksSinceLastDamaged ( 100 )
2013-09-07 10:42:34 -04:00
, m_BurnsInDaylight ( false )
2015-04-29 12:24:14 -04:00
, m_RelativeWalkSpeed ( 1 )
2015-07-12 12:00:56 -04:00
, m_Age ( 1 )
2020-10-09 16:49:25 -04:00
, m_AgingTimer ( TPS * 60 * 20 ) // about 20 minutes
2017-05-21 04:29:06 -04:00
, m_WasLastTargetAPlayer ( false )
2017-08-21 04:46:41 -04:00
, m_LeashedTo ( nullptr )
, m_LeashToPos ( nullptr )
, m_IsLeashActionJustDone ( false )
, m_CanBeLeashed ( GetMobFamily ( ) = = eFamily : : mfPassive )
2020-10-09 16:49:25 -04:00
, m_LovePartner ( nullptr )
, m_LoveTimer ( 0 )
, m_LoveCooldown ( 0 )
, m_MatingTimer ( 0 )
2016-02-01 15:49:34 -05:00
, m_Target ( nullptr )
2012-06-14 09:06:06 -04:00
{
2012-12-22 04:39:13 -05:00
if ( ! a_ConfigName . empty ( ) )
{
GetMonsterConfig ( a_ConfigName ) ;
}
2020-03-22 11:50:34 -04:00
// Prevent mobs spawning at the same time from making sounds simultaneously
m_AmbientSoundTimer = GetRandomProvider ( ) . RandInt ( 0 , 100 ) ;
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
2021-01-02 08:50:34 -05:00
void cMonster : : OnRemoveFromWorld ( cWorld & a_World )
2016-02-01 15:49:34 -05:00
{
2021-01-02 08:50:34 -05:00
SetTarget ( nullptr ) ; // Tell them we're no longer targeting them.
2016-02-01 15:49:34 -05:00
2021-01-02 08:50:34 -05:00
if ( m_LovePartner ! = nullptr )
{
m_LovePartner - > ResetLoveMode ( ) ;
}
2016-02-01 15:49:34 -05:00
2017-08-21 04:46:41 -04:00
if ( IsLeashed ( ) )
{
cEntity * LeashedTo = GetLeashedTo ( ) ;
2020-03-05 05:52:34 -05:00
Unleash ( false , true ) ;
2017-08-21 04:46:41 -04:00
// Remove leash knot if there are no more mobs leashed to
if ( ! LeashedTo - > HasAnyMobLeashed ( ) & & LeashedTo - > IsLeashKnot ( ) )
{
LeashedTo - > Destroy ( ) ;
}
}
2020-04-13 12:38:06 -04:00
Super : : OnRemoveFromWorld ( a_World ) ;
2017-08-21 04:46:41 -04:00
}
2012-08-24 03:58:26 -04:00
void cMonster : : SpawnOn ( cClientHandle & a_Client )
2012-06-14 09:06:06 -04:00
{
2012-08-24 03:58:26 -04:00
a_Client . SendSpawnMob ( * this ) ;
2017-08-21 04:46:41 -04:00
if ( IsLeashed ( ) )
{
a_Client . SendLeashEntity ( * this , * this - > GetLeashedTo ( ) ) ;
}
2012-06-14 09:06:06 -04:00
}
2015-11-20 03:03:20 -05:00
void cMonster : : MoveToWayPoint ( cChunk & a_Chunk )
2012-06-14 09:06:06 -04:00
{
2015-11-20 03:03:20 -05:00
if ( ( m_NextWayPointPosition - GetPosition ( ) ) . SqrLength ( ) < WAYPOINT_RADIUS * WAYPOINT_RADIUS )
2015-05-03 13:56:37 -04:00
{
2015-11-20 03:03:20 -05:00
return ;
2014-01-24 14:57:32 -05:00
}
2017-05-28 14:56:17 -04:00
if ( m_JumpCoolDown < = 0 )
2015-05-03 13:56:37 -04:00
{
2015-05-06 10:23:07 -04:00
if ( DoesPosYRequireJump ( FloorC ( m_NextWayPointPosition . y ) ) )
2015-05-03 13:56:37 -04:00
{
2017-05-28 14:56:17 -04:00
if (
( IsOnGround ( ) & & ( GetSpeed ( ) . SqrLength ( ) < = 0.5 ) ) | | // If walking on the ground, we need to slow down first, otherwise we miss the jump
2018-01-14 13:44:45 -05:00
IsInWater ( )
2017-05-28 14:56:17 -04:00
)
2015-05-03 13:56:37 -04:00
{
m_bOnGround = false ;
m_JumpCoolDown = 20 ;
AddPosY ( 1.6 ) ; // Jump!!
2017-05-28 14:56:17 -04:00
SetSpeedY ( 1 ) ;
2015-05-05 03:04:41 -04:00
SetSpeedX ( 3.2 * ( m_NextWayPointPosition . x - GetPosition ( ) . x ) ) ; // Move forward in a preset speed.
2017-05-28 14:56:17 -04:00
SetSpeedZ ( 3.2 * ( m_NextWayPointPosition . z - GetPosition ( ) . z ) ) ; // The numbers were picked based on trial and error
2015-05-03 13:56:37 -04:00
}
}
}
else
{
- - m_JumpCoolDown ;
}
2015-04-29 12:24:14 -04:00
2015-05-05 03:04:41 -04:00
Vector3d Distance = m_NextWayPointPosition - GetPosition ( ) ;
2017-05-28 14:56:17 -04:00
if ( ( std : : abs ( Distance . x ) > 0.05 ) | | ( std : : abs ( Distance . z ) > 0.05 ) )
2015-05-03 13:56:37 -04:00
{
Distance . y = 0 ;
Distance . Normalize ( ) ;
2015-04-29 12:24:14 -04:00
2015-05-03 13:56:37 -04:00
if ( m_bOnGround )
{
Distance * = 2.5f ;
}
2018-01-14 13:44:45 -05:00
else if ( IsInWater ( ) )
2015-05-03 13:56:37 -04:00
{
Distance * = 1.3f ;
}
else
{
// Don't let the mob move too much if he's falling.
Distance * = 0.25f ;
}
// Apply walk speed:
Distance * = m_RelativeWalkSpeed ;
/* Reduced default speed.
2015-05-05 03:04:41 -04:00
Close to Vanilla , easier for mobs to follow m_NextWayPointPositions , hence
2015-05-03 13:56:37 -04:00
better pathfinding . */
Distance * = 0.5 ;
AddSpeedX ( Distance . x ) ;
AddSpeedZ ( Distance . z ) ;
}
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
2015-05-03 13:56:37 -04:00
void cMonster : : MoveToPosition ( const Vector3d & a_Position )
2015-04-29 12:24:14 -04:00
{
2015-07-11 15:40:03 -04:00
m_FinalDestination = a_Position ;
m_PathfinderActivated = true ;
2015-04-29 12:24:14 -04:00
}
2015-05-03 13:56:37 -04:00
void cMonster : : StopMovingToPosition ( )
2012-06-14 09:06:06 -04:00
{
2015-07-11 15:40:03 -04:00
m_PathfinderActivated = false ;
2012-06-14 09:06:06 -04:00
}
2015-01-11 16:12:26 -05:00
void cMonster : : Tick ( std : : chrono : : milliseconds a_Dt , cChunk & a_Chunk )
2012-06-14 09:06:06 -04:00
{
2020-04-13 12:38:06 -04:00
Super : : Tick ( a_Dt , a_Chunk ) ;
2016-09-03 07:31:27 -04:00
if ( ! IsTicking ( ) )
{
// The base class tick destroyed us
return ;
}
2015-05-01 13:34:24 -04:00
GET_AND_VERIFY_CURRENT_CHUNK ( Chunk , POSX_TOINT , POSZ_TOINT ) ;
2012-06-14 09:06:06 -04:00
2016-02-01 15:49:34 -05:00
ASSERT ( ( GetTarget ( ) = = nullptr ) | | ( GetTarget ( ) - > IsPawn ( ) & & ( GetTarget ( ) - > GetWorld ( ) = = GetWorld ( ) ) ) ) ;
2016-01-12 05:11:10 -05:00
if ( m_AttackCoolDownTicksLeft > 0 )
{
m_AttackCoolDownTicksLeft - = 1 ;
}
2012-12-22 05:15:53 -05:00
if ( m_Health < = 0 )
2012-06-14 09:06:06 -04:00
{
2015-11-20 03:03:20 -05:00
// The mob is dead, but we're still animating the "puff" they leave when they die
2015-01-16 09:38:21 -05:00
m_DestroyTimer + = a_Dt ;
if ( m_DestroyTimer > std : : chrono : : seconds ( 1 ) )
2012-06-14 09:06:06 -04:00
{
2020-03-05 05:52:34 -05:00
Destroy ( ) ;
2012-06-14 09:06:06 -04:00
}
return ;
}
2015-04-29 12:24:14 -04:00
if ( m_TicksSinceLastDamaged < 100 )
{
+ + m_TicksSinceLastDamaged ;
}
2014-01-24 18:56:19 -05:00
2015-11-20 03:03:20 -05:00
// Process the undead burning in daylight.
HandleDaylightBurning ( * Chunk , WouldBurnAt ( GetPosition ( ) , * Chunk ) ) ;
bool a_IsFollowingPath = false ;
2020-03-26 13:54:40 -04:00
if ( m_PathfinderActivated & & ( GetMobType ( ) ! = mtGhast ) ) // Pathfinder is currently disabled for ghasts, which have their own flying mechanism
2012-06-14 09:06:06 -04:00
{
2017-08-21 04:46:41 -04:00
if ( ReachedFinalDestination ( ) | | ( m_LeashToPos ! = nullptr ) )
2012-06-14 09:06:06 -04:00
{
2015-11-20 03:03:20 -05:00
StopMovingToPosition ( ) ; // Simply sets m_PathfinderActivated to false.
}
else
{
// Note that m_NextWayPointPosition is actually returned by GetNextWayPoint)
2020-05-14 18:15:35 -04:00
switch ( m_PathFinder . GetNextWayPoint ( * Chunk , GetPosition ( ) , & m_FinalDestination , & m_NextWayPointPosition , m_EMState = = IDLE ) )
2015-09-05 16:02:15 -04:00
{
2015-11-20 03:03:20 -05:00
case ePathFinderStatus : : PATH_FOUND :
{
/* If I burn in daylight, and I won't burn where I'm standing, and I'll burn in my next position, and at least one of those is true:
1. I am idle
2. I was not hurt by a player recently .
Then STOP . */
if (
m_BurnsInDaylight & & ( ( m_TicksSinceLastDamaged > = 100 ) | | ( m_EMState = = IDLE ) ) & &
WouldBurnAt ( m_NextWayPointPosition , * Chunk ) & &
! WouldBurnAt ( GetPosition ( ) , * Chunk )
)
{
// If we burn in daylight, and we would burn at the next step, and we won't burn where we are right now, and we weren't provoked recently:
StopMovingToPosition ( ) ;
}
else
{
a_IsFollowingPath = true ; // Used for proper body / head orientation only.
MoveToWayPoint ( * Chunk ) ;
}
break ;
}
case ePathFinderStatus : : PATH_NOT_FOUND :
{
StopMovingToPosition ( ) ;
break ;
}
default :
{
}
2015-09-05 16:02:15 -04:00
}
2012-06-14 09:06:06 -04:00
}
}
2013-03-23 00:33:47 -04:00
2015-11-20 03:03:20 -05:00
SetPitchAndYawFromDestination ( a_IsFollowingPath ) ;
2013-03-22 02:33:10 -04:00
2012-12-22 05:15:53 -05:00
switch ( m_EMState )
2012-08-19 15:42:32 -04:00
{
2012-12-22 05:15:53 -05:00
case IDLE :
{
2015-04-29 12:24:14 -04:00
// If enemy passive we ignore checks for player visibility.
2015-12-22 00:43:50 -05:00
InStateIdle ( a_Dt , a_Chunk ) ;
2012-12-22 05:15:53 -05:00
break ;
2014-07-17 16:50:58 -04:00
}
2012-12-22 05:15:53 -05:00
case CHASING :
{
2015-04-29 12:24:14 -04:00
// If we do not see a player anymore skip chasing action.
2015-12-22 00:43:50 -05:00
InStateChasing ( a_Dt , a_Chunk ) ;
2012-12-22 05:15:53 -05:00
break ;
2014-07-17 16:50:58 -04:00
}
2012-12-22 05:15:53 -05:00
case ESCAPING :
{
2015-12-22 00:43:50 -05:00
InStateEscaping ( a_Dt , a_Chunk ) ;
2012-12-22 05:15:53 -05:00
break ;
}
2014-05-11 19:25:21 -04:00
case ATTACKING : break ;
2012-12-22 05:15:53 -05:00
} // switch (m_EMState)
2014-01-24 14:57:32 -05:00
2017-08-21 04:46:41 -04:00
// Leash calculations
2018-01-17 16:40:59 -05:00
CalcLeashActions ( a_Dt ) ;
2017-08-21 04:46:41 -04:00
2014-01-24 14:57:32 -05:00
BroadcastMovementUpdate ( ) ;
2015-11-29 13:13:31 -05:00
2020-03-22 11:50:34 -04:00
// Ambient mob sounds
if ( ! m_SoundAmbient . empty ( ) & & ( - - m_AmbientSoundTimer < = 0 ) )
{
auto & Random = GetRandomProvider ( ) ;
auto ShouldPlaySound = Random . RandBool ( ) ;
if ( ShouldPlaySound )
{
auto SoundPitchMultiplier = 1.0f + ( Random . RandReal ( 1.0f ) - Random . RandReal ( 1.0f ) ) * 0.2f ;
m_World - > BroadcastSoundEffect ( m_SoundAmbient , GetPosition ( ) , 1.0f , SoundPitchMultiplier * 1.0f ) ;
}
m_AmbientSoundTimer = 100 ;
}
2015-11-29 13:13:31 -05:00
if ( m_AgingTimer > 0 )
{
m_AgingTimer - - ;
if ( ( m_AgingTimer < = 0 ) & & IsBaby ( ) )
{
SetAge ( 1 ) ;
m_World - > BroadcastEntityMetadata ( * this ) ;
}
}
2015-05-03 13:56:37 -04:00
}
2015-04-29 12:24:14 -04:00
2014-01-24 14:57:32 -05:00
2018-01-17 16:40:59 -05:00
void cMonster : : CalcLeashActions ( std : : chrono : : milliseconds a_Dt )
2017-08-21 04:46:41 -04:00
{
// This mob just spotted in the world and [m_LeashToPos not null] shows that should be leashed to a leash knot at m_LeashToPos.
// This keeps trying until knot is found. Leash knot may be in a different chunk that needn't or can't be loaded yet.
if ( ! IsLeashed ( ) & & ( m_LeashToPos ! = nullptr ) )
{
auto LeashKnot = cLeashKnot : : FindKnotAtPos ( * m_World , { FloorC ( m_LeashToPos - > x ) , FloorC ( m_LeashToPos - > y ) , FloorC ( m_LeashToPos - > z ) } ) ;
if ( LeashKnot ! = nullptr )
{
2017-08-24 07:26:23 -04:00
LeashTo ( * LeashKnot ) ;
2017-08-21 04:46:41 -04:00
SetLeashToPos ( nullptr ) ;
}
}
2018-01-17 16:40:59 -05:00
if ( ! IsLeashed ( ) )
{
return ;
}
static const double CloseFollowDistance = 1.8 ; // The closest the mob will path towards the leashed to entity
static const double LeashNaturalLength = 5.0 ; // The closest the mob is actively pulled towards the entity
static const double LeashMaximumLength = 10.0 ; // Length where the leash breaks
static const double LeashSpringConstant = 20.0 ; // How stiff the leash is
const auto LeashedToPos = m_LeashedTo - > GetPosition ( ) ;
const auto Displacement = LeashedToPos - GetPosition ( ) ;
const auto Distance = Displacement . Length ( ) ;
const auto Direction = Displacement . NormalizeCopy ( ) ;
// If the leash is over-extended, break the leash:
if ( Distance > LeashMaximumLength )
{
LOGD ( " Leash broken (distance) " ) ;
Unleash ( false ) ;
return ;
}
// If the mob isn't following close enough, pull the mob towards the leashed to entity:
if ( Distance > LeashNaturalLength )
2017-08-21 04:46:41 -04:00
{
2018-01-17 16:40:59 -05:00
// Accelerate monster towards the leashed to entity:
const auto Extension = Distance - LeashNaturalLength ;
auto Acceleration = Direction * ( Extension * LeashSpringConstant ) ;
2017-08-21 04:46:41 -04:00
2018-01-17 16:40:59 -05:00
// Stop mobs from floating up when on the ground
if ( IsOnGround ( ) & & ( Acceleration . y < std : : abs ( GetGravity ( ) ) ) )
2017-08-21 04:46:41 -04:00
{
2018-01-17 16:40:59 -05:00
Acceleration . y = 0.0 ;
2017-08-21 04:46:41 -04:00
}
2018-01-17 16:40:59 -05:00
// Apply the acceleration
using namespace std : : chrono ;
AddSpeed ( Acceleration * duration_cast < duration < double > > ( a_Dt ) . count ( ) ) ;
}
// Passively follow the leashed to entity:
if ( Distance > CloseFollowDistance )
{
const Vector3d TargetBlock ( ( LeashedToPos - Direction * CloseFollowDistance ) . Floor ( ) ) ;
// Move to centre of target block face
MoveToPosition ( TargetBlock + Vector3d { 0.5 , 0.0 , 0.5 } ) ;
2017-08-21 04:46:41 -04:00
}
}
2015-11-20 03:03:20 -05:00
void cMonster : : SetPitchAndYawFromDestination ( bool a_IsFollowingPath )
2014-01-24 14:57:32 -05:00
{
2015-05-28 10:45:47 -04:00
Vector3d BodyDistance ;
2020-04-21 18:16:28 -04:00
if ( ! a_IsFollowingPath )
2015-05-28 10:45:47 -04:00
{
2020-04-21 18:16:28 -04:00
if ( GetTarget ( ) = = nullptr )
{
// Avoid dirtying head position when nothing will change
// Thus avoids creating unnecessary network traffic
return ;
}
2016-02-01 15:49:34 -05:00
BodyDistance = GetTarget ( ) - > GetPosition ( ) - GetPosition ( ) ;
2015-05-28 10:45:47 -04:00
}
else
{
BodyDistance = m_NextWayPointPosition - GetPosition ( ) ;
}
2020-04-21 18:16:28 -04:00
2015-05-16 07:05:33 -04:00
double BodyRotation , BodyPitch ;
BodyDistance . Normalize ( ) ;
VectorToEuler ( BodyDistance . x , BodyDistance . y , BodyDistance . z , BodyRotation , BodyPitch ) ;
SetYaw ( BodyRotation ) ;
2015-11-20 03:03:20 -05:00
Vector3d HeadDistance ;
2016-02-01 15:49:34 -05:00
if ( GetTarget ( ) ! = nullptr )
2014-01-24 14:57:32 -05:00
{
2016-02-01 15:49:34 -05:00
if ( GetTarget ( ) - > IsPlayer ( ) ) // Look at a player
2015-05-16 07:05:33 -04:00
{
2016-02-01 15:49:34 -05:00
HeadDistance = GetTarget ( ) - > GetPosition ( ) - GetPosition ( ) ;
2015-05-16 07:05:33 -04:00
}
2015-11-20 03:03:20 -05:00
else // Look at some other entity
2015-05-16 07:05:33 -04:00
{
2016-02-01 15:49:34 -05:00
HeadDistance = GetTarget ( ) - > GetPosition ( ) - GetPosition ( ) ;
// HeadDistance.y = GetTarget()->GetPosY() + GetHeight();
2015-05-16 07:05:33 -04:00
}
2014-01-24 14:57:32 -05:00
}
2015-11-20 03:03:20 -05:00
else // Look straight
{
HeadDistance = BodyDistance ;
HeadDistance . y = 0 ;
}
double HeadRotation , HeadPitch ;
HeadDistance . Normalize ( ) ;
VectorToEuler ( HeadDistance . x , HeadDistance . y , HeadDistance . z , HeadRotation , HeadPitch ) ;
2015-12-22 00:43:50 -05:00
if ( ( std : : abs ( BodyRotation - HeadRotation ) < 70 ) & & ( std : : abs ( HeadPitch ) < 60 ) )
2015-11-20 03:03:20 -05:00
{
SetHeadYaw ( HeadRotation ) ;
SetPitch ( - HeadPitch ) ;
}
else
{
SetHeadYaw ( BodyRotation ) ;
2015-12-22 00:43:50 -05:00
SetPitch ( 0 ) ;
2015-11-20 03:03:20 -05:00
}
2014-01-24 14:57:32 -05:00
}
2015-04-29 12:24:14 -04:00
2014-01-25 14:02:13 -05:00
void cMonster : : HandleFalling ( )
{
2015-11-10 08:02:07 -05:00
m_bTouchGround = IsOnGround ( ) ;
2020-04-13 12:38:06 -04:00
Super : : HandleFalling ( ) ;
2014-01-25 14:02:13 -05:00
}
2014-06-08 21:44:20 -04:00
2014-01-24 14:57:32 -05:00
int cMonster : : FindFirstNonAirBlockPosition ( double a_PosX , double a_PosZ )
{
2021-03-14 22:28:18 -04:00
auto Position = GetPosition ( ) . Floor ( ) ;
Position . y = Clamp ( Position . y , 0 , cChunkDef : : Height ) ;
2014-01-25 09:42:26 -05:00
2021-03-14 22:28:18 -04:00
if ( ! cBlockInfo : : IsSolid ( m_World - > GetBlock ( Position ) ) )
2014-01-24 14:57:32 -05:00
{
2021-03-14 22:28:18 -04:00
while ( ! cBlockInfo : : IsSolid ( m_World - > GetBlock ( Position ) ) & & ( Position . y > 0 ) )
2014-01-24 14:57:32 -05:00
{
2021-03-14 22:28:18 -04:00
Position . y - - ;
2014-01-24 14:57:32 -05:00
}
2021-03-14 22:28:18 -04:00
return Position . y + 1 ;
2014-01-24 14:57:32 -05:00
}
else
{
2021-03-14 22:28:18 -04:00
while ( ( Position . y < cChunkDef : : Height ) & & cBlockInfo : : IsSolid ( m_World - > GetBlock ( Position ) ) )
2014-01-24 14:57:32 -05:00
{
2021-03-14 22:28:18 -04:00
Position . y + + ;
2014-01-24 14:57:32 -05:00
}
2021-03-14 22:28:18 -04:00
return Position . y ;
2014-01-24 14:57:32 -05:00
}
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
2014-04-25 18:32:30 -04:00
bool cMonster : : DoTakeDamage ( TakeDamageInfo & a_TDI )
2012-06-14 09:06:06 -04:00
{
2020-04-13 12:38:06 -04:00
if ( ! Super : : DoTakeDamage ( a_TDI ) )
2014-04-25 18:32:30 -04:00
{
return false ;
}
2014-01-24 14:57:32 -05:00
2014-05-13 07:53:15 -04:00
if ( ! m_SoundHurt . empty ( ) & & ( m_Health > 0 ) )
2014-05-12 14:38:52 -04:00
{
2017-09-19 10:12:54 -04:00
m_World - > BroadcastSoundEffect ( m_SoundHurt , GetPosition ( ) , 1.0f , 0.8f ) ;
2014-05-12 14:38:52 -04:00
}
2014-01-24 14:57:32 -05:00
2016-02-01 15:49:34 -05:00
if ( ( a_TDI . Attacker ! = nullptr ) & & a_TDI . Attacker - > IsPawn ( ) )
2012-12-21 06:04:08 -05:00
{
2016-10-12 08:38:45 -04:00
if (
( ! a_TDI . Attacker - > IsPlayer ( ) ) | |
( static_cast < cPlayer * > ( a_TDI . Attacker ) - > CanMobsTarget ( ) )
)
{
SetTarget ( static_cast < cPawn * > ( a_TDI . Attacker ) ) ;
}
2015-04-29 12:24:14 -04:00
m_TicksSinceLastDamaged = 0 ;
2012-12-21 06:04:08 -05:00
}
2014-04-25 18:32:30 -04:00
return true ;
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
2014-07-04 05:55:09 -04:00
void cMonster : : KilledBy ( TakeDamageInfo & a_TDI )
2012-06-14 09:06:06 -04:00
{
2020-04-13 12:38:06 -04:00
Super : : KilledBy ( a_TDI ) ;
2013-07-01 06:39:56 -04:00
if ( m_SoundHurt ! = " " )
{
2017-09-19 10:12:54 -04:00
m_World - > BroadcastSoundEffect ( m_SoundDeath , GetPosition ( ) , 1.0f , 0.8f ) ;
2013-07-01 06:39:56 -04:00
}
2021-06-19 17:25:24 -04:00
if ( IsTame ( ) )
{
if ( ( m_MobType = = mtWolf ) | | ( m_MobType = = mtOcelot ) | | ( m_MobType = = mtCat ) | | ( m_MobType = = mtParrot ) )
{
BroadcastDeathMessage ( a_TDI ) ;
}
}
2013-11-25 15:43:43 -05:00
int Reward ;
2013-11-25 15:03:26 -05:00
switch ( m_MobType )
{
// Animals
2014-09-17 13:40:10 -04:00
case mtChicken :
case mtCow :
case mtHorse :
case mtPig :
2014-12-20 04:31:34 -05:00
case mtRabbit :
2014-09-17 13:40:10 -04:00
case mtSheep :
case mtSquid :
case mtMooshroom :
case mtOcelot :
case mtWolf :
2013-11-25 15:03:26 -05:00
{
2017-06-13 15:35:30 -04:00
Reward = GetRandomProvider ( ) . RandInt ( 1 , 3 ) ;
2013-12-14 06:50:08 -05:00
break ;
2013-11-25 15:03:26 -05:00
}
// Monsters
2014-09-17 13:40:10 -04:00
case mtCaveSpider :
case mtCreeper :
case mtEnderman :
case mtGhast :
2014-12-18 13:30:32 -05:00
case mtGuardian :
2014-09-17 13:40:10 -04:00
case mtSilverfish :
case mtSkeleton :
case mtSpider :
case mtWitch :
2020-04-04 07:44:17 -04:00
case mtWitherSkeleton :
2014-09-17 13:40:10 -04:00
case mtZombie :
case mtZombiePigman :
2018-02-03 06:24:19 -05:00
case mtZombieVillager :
2014-09-17 13:40:10 -04:00
case mtSlime :
case mtMagmaCube :
2013-11-25 15:03:26 -05:00
{
2017-06-13 15:35:30 -04:00
Reward = GetRandomProvider ( ) . RandInt ( 6 , 8 ) ;
2013-12-14 06:50:08 -05:00
break ;
2013-11-25 15:43:43 -05:00
}
2014-09-17 13:40:10 -04:00
case mtBlaze :
2013-11-25 15:43:43 -05:00
{
Reward = 10 ;
2013-12-14 06:50:08 -05:00
break ;
2013-11-25 15:03:26 -05:00
}
// Bosses
2014-09-17 13:40:10 -04:00
case mtEnderDragon :
2013-11-25 15:03:26 -05:00
{
2013-11-25 15:43:43 -05:00
Reward = 12000 ;
2013-12-14 06:50:08 -05:00
break ;
2013-11-25 15:03:26 -05:00
}
2014-09-17 13:40:10 -04:00
case mtWither :
2013-11-25 15:03:26 -05:00
{
2013-11-25 15:43:43 -05:00
Reward = 50 ;
2013-12-14 06:50:08 -05:00
break ;
2013-11-25 15:03:26 -05:00
}
default :
{
2013-11-25 15:43:43 -05:00
Reward = 0 ;
2013-12-14 06:50:08 -05:00
break ;
2013-11-25 15:03:26 -05:00
}
}
2014-10-20 16:55:07 -04:00
if ( ( a_TDI . Attacker ! = nullptr ) & & ( ! IsBaby ( ) ) )
2014-04-10 10:50:43 -04:00
{
2018-08-02 10:59:10 -04:00
m_World - > SpawnSplitExperienceOrbs ( GetPosX ( ) , GetPosY ( ) , GetPosZ ( ) , Reward ) ;
2014-04-10 10:50:43 -04:00
}
2015-01-16 09:38:21 -05:00
m_DestroyTimer = std : : chrono : : milliseconds ( 0 ) ;
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
2014-09-01 15:05:45 -04:00
void cMonster : : OnRightClicked ( cPlayer & a_Player )
{
2020-04-13 12:38:06 -04:00
Super : : OnRightClicked ( a_Player ) ;
2014-09-01 15:05:45 -04:00
const cItem & EquippedItem = a_Player . GetEquippedItem ( ) ;
if ( ( EquippedItem . m_ItemType = = E_ITEM_NAME_TAG ) & & ! EquippedItem . m_CustomName . empty ( ) )
{
SetCustomName ( EquippedItem . m_CustomName ) ;
if ( ! a_Player . IsGameModeCreative ( ) )
{
a_Player . GetInventory ( ) . RemoveOneEquippedItem ( ) ;
}
}
2017-08-21 04:46:41 -04:00
// Using leashes
m_IsLeashActionJustDone = false ;
if ( IsLeashed ( ) & & ( GetLeashedTo ( ) = = & a_Player ) ) // a player can only unleash a mob leashed to him
{
Unleash ( ! a_Player . IsGameModeCreative ( ) ) ;
}
else if ( IsLeashed ( ) )
{
// Mob is already leashed but client anticipates the server action and draws a leash link, so we need to send current leash to cancel it
m_World - > BroadcastLeashEntity ( * this , * this - > GetLeashedTo ( ) ) ;
}
else if ( CanBeLeashed ( ) & & ( EquippedItem . m_ItemType = = E_ITEM_LEASH ) )
{
if ( ! a_Player . IsGameModeCreative ( ) )
{
a_Player . GetInventory ( ) . RemoveOneEquippedItem ( ) ;
}
2017-08-24 07:26:23 -04:00
LeashTo ( a_Player ) ;
2017-08-21 04:46:41 -04:00
}
2014-09-01 15:05:45 -04:00
}
2014-07-17 16:15:34 -04:00
// Checks to see if EventSeePlayer should be fired
// monster sez: Do I see the player
2016-01-17 09:09:25 -05:00
void cMonster : : CheckEventSeePlayer ( cChunk & a_Chunk )
2012-06-14 09:06:06 -04:00
{
2020-12-18 15:48:32 -05:00
if ( GetTarget ( ) ! = nullptr )
{
return ;
}
cPlayer * TargetPlayer = nullptr ;
double ClosestDistance = m_SightDistance * m_SightDistance ;
const auto MyHeadPosition = GetPosition ( ) . addedY ( GetHeight ( ) ) ;
// Enumerate all players within sight:
2021-01-02 08:50:34 -05:00
m_World - > ForEachPlayer ( [ this , & TargetPlayer , & ClosestDistance , MyHeadPosition ] ( cPlayer & a_Player )
2020-12-18 15:48:32 -05:00
{
2021-01-02 08:50:34 -05:00
if ( ! a_Player . CanMobsTarget ( ) )
2020-12-18 15:48:32 -05:00
{
return false ;
}
2021-01-02 08:50:34 -05:00
const auto TargetHeadPosition = a_Player . GetPosition ( ) . addedY ( a_Player . GetHeight ( ) ) ;
2020-12-18 15:48:32 -05:00
const auto TargetDistance = ( TargetHeadPosition - MyHeadPosition ) . SqrLength ( ) ;
// TODO: Currently all mobs see through lava, but only Nether-native mobs should be able to.
if (
( TargetDistance < ClosestDistance ) & &
cLineBlockTracer : : LineOfSightTrace ( * GetWorld ( ) , MyHeadPosition , TargetHeadPosition , cLineBlockTracer : : losAirWaterLava )
)
{
2021-01-02 08:50:34 -05:00
TargetPlayer = & a_Player ;
2020-12-18 15:48:32 -05:00
ClosestDistance = TargetDistance ;
}
return false ;
} ) ;
// Target him if suitable player found:
if ( TargetPlayer ! = nullptr )
2012-06-14 09:06:06 -04:00
{
2020-12-18 15:48:32 -05:00
EventSeePlayer ( TargetPlayer , a_Chunk ) ;
}
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
2020-12-18 15:48:32 -05:00
void cMonster : : CheckEventLostPlayer ( const std : : chrono : : milliseconds a_Dt )
2014-07-17 16:50:58 -04:00
{
2020-12-18 15:48:32 -05:00
const auto Target = GetTarget ( ) ;
if ( Target = = nullptr )
{
return ;
}
// Check if the player died, is in creative mode, etc:
if ( Target - > IsPlayer ( ) & & ! static_cast < cPlayer * > ( Target ) - > CanMobsTarget ( ) )
2012-08-19 15:42:32 -04:00
{
2020-12-18 15:48:32 -05:00
EventLosePlayer ( ) ;
return ;
}
// Check if the target is too far away:
if ( ! Target - > GetBoundingBox ( ) . DoesIntersect ( { GetPosition ( ) , m_SightDistance * 2.0 } ) )
{
EventLosePlayer ( ) ;
return ;
}
const auto MyHeadPosition = GetPosition ( ) . addedY ( GetHeight ( ) ) ;
const auto TargetHeadPosition = Target - > GetPosition ( ) . addedY ( Target - > GetHeight ( ) ) ;
if ( ! cLineBlockTracer : : LineOfSightTrace ( * GetWorld ( ) , MyHeadPosition , TargetHeadPosition , cLineBlockTracer : : losAirWaterLava ) )
{
if ( ( m_LoseSightAbandonTargetTimer + = a_Dt ) > std : : chrono : : seconds ( 4 ) )
2012-06-14 09:06:06 -04:00
{
EventLosePlayer ( ) ;
}
2012-08-19 15:42:32 -04:00
}
else
{
2020-12-18 15:48:32 -05:00
// Subtract the amount of time we "handled" instead of setting to zero, so we don't ignore a large a_Dt of say, 8s:
m_LoseSightAbandonTargetTimer - = std : : min ( std : : chrono : : milliseconds ( 4000 ) , m_LoseSightAbandonTargetTimer ) ;
2012-06-14 09:06:06 -04:00
}
}
2012-08-19 15:42:32 -04:00
// What to do if player is seen
// default to change state to chasing
2016-10-12 08:38:45 -04:00
void cMonster : : EventSeePlayer ( cPlayer * a_SeenPlayer , cChunk & a_Chunk )
2012-06-14 09:06:06 -04:00
{
2016-02-01 15:49:34 -05:00
UNUSED ( a_Chunk ) ;
2016-10-12 08:38:45 -04:00
SetTarget ( a_SeenPlayer ) ;
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
2012-12-22 05:15:53 -05:00
void cMonster : : EventLosePlayer ( void )
2012-08-19 15:42:32 -04:00
{
2016-02-01 15:49:34 -05:00
SetTarget ( nullptr ) ;
2020-12-18 15:48:32 -05:00
2012-06-14 09:06:06 -04:00
m_EMState = IDLE ;
2020-12-18 15:48:32 -05:00
m_LoseSightAbandonTargetTimer = std : : chrono : : seconds : : zero ( ) ;
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
2015-12-22 00:43:50 -05:00
void cMonster : : InStateIdle ( std : : chrono : : milliseconds a_Dt , cChunk & a_Chunk )
2012-08-19 15:42:32 -04:00
{
2015-11-20 03:03:20 -05:00
if ( m_PathfinderActivated )
2014-01-25 09:42:26 -05:00
{
2014-07-17 16:15:34 -04:00
return ; // Still getting there
2014-01-25 09:42:26 -05:00
}
2013-12-20 10:39:20 -05:00
m_IdleInterval + = a_Dt ;
2014-01-24 14:57:32 -05:00
2015-01-16 09:38:21 -05:00
if ( m_IdleInterval > std : : chrono : : seconds ( 1 ) )
2012-08-19 15:42:32 -04:00
{
2017-06-13 15:35:30 -04:00
auto & Random = GetRandomProvider ( ) ;
2014-01-24 14:57:32 -05:00
// At this interval the results are predictable
2017-06-13 15:35:30 -04:00
int rem = Random . RandInt ( 1 , 7 ) ;
2015-01-16 09:38:21 -05:00
m_IdleInterval - = std : : chrono : : seconds ( 1 ) ; // So nothing gets dropped when the server hangs for a few seconds
2014-01-24 14:57:32 -05:00
Vector3d Dist ;
2017-06-13 15:35:30 -04:00
Dist . x = static_cast < double > ( Random . RandInt ( - 5 , 5 ) ) ;
Dist . z = static_cast < double > ( Random . RandInt ( - 5 , 5 ) ) ;
2014-01-24 14:57:32 -05:00
2012-12-22 05:15:53 -05:00
if ( ( Dist . SqrLength ( ) > 2 ) & & ( rem > = 3 ) )
2012-06-14 09:06:06 -04:00
{
2015-12-22 00:43:50 -05:00
Vector3d Destination ( GetPosX ( ) + Dist . x , GetPosition ( ) . y , GetPosZ ( ) + Dist . z ) ;
cChunk * Chunk = a_Chunk . GetNeighborChunk ( static_cast < int > ( Destination . x ) , static_cast < int > ( Destination . z ) ) ;
if ( ( Chunk = = nullptr ) | | ! Chunk - > IsValid ( ) )
{
return ;
}
BLOCKTYPE BlockType ;
NIBBLETYPE BlockMeta ;
int RelX = static_cast < int > ( Destination . x ) - Chunk - > GetPosX ( ) * cChunkDef : : Width ;
int RelZ = static_cast < int > ( Destination . z ) - Chunk - > GetPosZ ( ) * cChunkDef : : Width ;
2016-04-18 14:58:57 -04:00
int YBelowUs = static_cast < int > ( Destination . y ) - 1 ;
if ( YBelowUs > = 0 )
2015-12-22 00:43:50 -05:00
{
2016-04-18 14:58:57 -04:00
Chunk - > GetBlockTypeMeta ( RelX , YBelowUs , RelZ , BlockType , BlockMeta ) ;
if ( BlockType ! = E_BLOCK_STATIONARY_WATER ) // Idle mobs shouldn't enter water on purpose
{
MoveToPosition ( Destination ) ;
}
2015-12-22 00:43:50 -05:00
}
2012-06-14 09:06:06 -04:00
}
}
}
2012-08-19 15:42:32 -04:00
// What to do if in Chasing State
// This state should always be defined in each child class
2015-12-22 00:43:50 -05:00
void cMonster : : InStateChasing ( std : : chrono : : milliseconds a_Dt , cChunk & a_Chunk )
2012-08-19 15:42:32 -04:00
{
UNUSED ( a_Dt ) ;
2012-06-14 09:06:06 -04:00
}
2012-08-19 15:42:32 -04:00
// What to do if in Escaping State
2015-12-22 00:43:50 -05:00
void cMonster : : InStateEscaping ( std : : chrono : : milliseconds a_Dt , cChunk & a_Chunk )
2012-08-19 15:42:32 -04:00
{
2012-12-22 05:15:53 -05:00
UNUSED ( a_Dt ) ;
2015-04-29 12:24:14 -04:00
2016-02-01 15:49:34 -05:00
if ( GetTarget ( ) ! = nullptr )
2012-08-19 15:42:32 -04:00
{
2013-03-22 02:33:10 -04:00
Vector3d newloc = GetPosition ( ) ;
2016-02-01 15:49:34 -05:00
newloc . x = ( GetTarget ( ) - > GetPosition ( ) . x < newloc . x ) ? ( newloc . x + m_SightDistance ) : ( newloc . x - m_SightDistance ) ;
newloc . z = ( GetTarget ( ) - > GetPosition ( ) . z < newloc . z ) ? ( newloc . z + m_SightDistance ) : ( newloc . z - m_SightDistance ) ;
2012-06-14 09:06:06 -04:00
MoveToPosition ( newloc ) ;
}
2012-08-19 15:42:32 -04:00
else
{
2012-12-22 05:15:53 -05:00
m_EMState = IDLE ; // This shouldnt be required but just to be safe
2012-06-14 09:06:06 -04:00
}
}
2016-01-12 05:11:10 -05:00
void cMonster : : ResetAttackCooldown ( )
{
2020-10-09 16:49:25 -04:00
m_AttackCoolDownTicksLeft = static_cast < int > ( TPS * m_AttackRate ) ; // A second has 20 ticks, an attack rate of 1 means 1 hit every second
2016-01-12 05:11:10 -05:00
}
2014-09-01 14:12:56 -04:00
void cMonster : : SetCustomName ( const AString & a_CustomName )
{
m_CustomName = a_CustomName ;
// The maximal length is 64
if ( a_CustomName . length ( ) > 64 )
{
m_CustomName = a_CustomName . substr ( 0 , 64 ) ;
}
2014-10-20 16:55:07 -04:00
if ( m_World ! = nullptr )
2014-09-02 13:34:58 -04:00
{
m_World - > BroadcastEntityMetadata ( * this ) ;
}
2014-09-01 14:12:56 -04:00
}
void cMonster : : SetCustomNameAlwaysVisible ( bool a_CustomNameAlwaysVisible )
{
m_CustomNameAlwaysVisible = a_CustomNameAlwaysVisible ;
2014-10-20 16:55:07 -04:00
if ( m_World ! = nullptr )
2014-09-02 13:34:58 -04:00
{
m_World - > BroadcastEntityMetadata ( * this ) ;
}
2014-09-01 14:12:56 -04:00
}
2012-12-22 04:39:13 -05:00
void cMonster : : GetMonsterConfig ( const AString & a_Name )
2012-06-14 09:06:06 -04:00
{
2012-12-22 04:39:13 -05:00
cRoot : : Get ( ) - > GetMonsterConfig ( ) - > AssignAttributes ( this , a_Name ) ;
2012-06-14 09:06:06 -04:00
}
2014-06-08 21:44:20 -04:00
bool cMonster : : IsUndead ( void )
{
return false ;
}
2014-09-17 13:40:10 -04:00
AString cMonster : : MobTypeToString ( eMonsterType a_MobType )
2013-10-20 07:25:56 -04:00
{
// Mob types aren't sorted, so we need to search linearly:
2013-12-20 10:01:34 -05:00
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ) ; i + + )
2013-10-20 07:25:56 -04:00
{
if ( g_MobTypeNames [ i ] . m_Type = = a_MobType )
{
return g_MobTypeNames [ i ] . m_lcName ;
}
}
2015-04-29 12:24:14 -04:00
2013-10-20 07:25:56 -04:00
// Not found:
return " " ;
}
2014-11-29 09:20:44 -05:00
AString cMonster : : MobTypeToVanillaName ( eMonsterType a_MobType )
2013-10-20 07:25:56 -04:00
{
2014-11-29 09:20:44 -05:00
// Mob types aren't sorted, so we need to search linearly:
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ) ; i + + )
2013-10-20 07:25:56 -04:00
{
2014-11-29 09:20:44 -05:00
if ( g_MobTypeNames [ i ] . m_Type = = a_MobType )
2013-10-20 07:25:56 -04:00
{
2014-11-29 09:20:44 -05:00
return g_MobTypeNames [ i ] . m_VanillaName ;
2013-10-20 07:25:56 -04:00
}
}
2014-11-29 09:20:44 -05:00
// Not found:
return " " ;
}
2017-05-22 11:41:41 -04:00
AString cMonster : : MobTypeToVanillaNBT ( eMonsterType a_MobType )
{
// Mob types aren't sorted, so we need to search linearly:
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ) ; i + + )
{
if ( g_MobTypeNames [ i ] . m_Type = = a_MobType )
{
return g_MobTypeNames [ i ] . m_VanillaNameNBT ;
}
}
// Not found:
return " " ;
}
2014-11-29 09:20:44 -05:00
eMonsterType cMonster : : StringToMobType ( const AString & a_Name )
{
AString lcName = StrToLower ( a_Name ) ;
2015-09-25 04:14:17 -04:00
// Search Cuberite name:
2014-11-29 09:20:44 -05:00
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ) ; i + + )
2013-10-20 07:25:56 -04:00
{
2014-11-29 09:20:44 -05:00
if ( strcmp ( g_MobTypeNames [ i ] . m_lcName , lcName . c_str ( ) ) = = 0 )
{
return g_MobTypeNames [ i ] . m_Type ;
}
2013-10-20 07:25:56 -04:00
}
2014-11-29 09:20:44 -05:00
// Not found. Search Vanilla name:
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ) ; i + + )
2013-10-20 07:25:56 -04:00
{
2014-11-29 09:20:44 -05:00
if ( strcmp ( StrToLower ( g_MobTypeNames [ i ] . m_VanillaName ) . c_str ( ) , lcName . c_str ( ) ) = = 0 )
{
return g_MobTypeNames [ i ] . m_Type ;
}
2013-10-20 07:25:56 -04:00
}
2014-11-29 09:20:44 -05:00
2017-05-22 11:41:41 -04:00
// Search in NBT name
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ) ; i + + )
{
if ( strcmp ( StrToLower ( g_MobTypeNames [ i ] . m_VanillaNameNBT ) . c_str ( ) , lcName . c_str ( ) ) = = 0 )
{
return g_MobTypeNames [ i ] . m_Type ;
}
}
2013-10-20 07:25:56 -04:00
// Not found:
return mtInvalidType ;
}
2014-09-17 13:40:10 -04:00
cMonster : : eFamily cMonster : : FamilyFromType ( eMonsterType a_Type )
2013-10-20 07:25:56 -04:00
{
2014-01-24 14:57:32 -05:00
// Passive-agressive mobs are counted in mob spawning code as passive
2013-10-20 08:15:55 -04:00
switch ( a_Type )
2013-10-20 07:25:56 -04:00
{
2020-11-22 18:41:13 -05:00
case mtBat : return mfAmbient ;
case mtBlaze : return mfHostile ;
case mtCat : return mfPassive ;
case mtCaveSpider : return mfHostile ;
case mtChicken : return mfPassive ;
case mtCod : return mfWater ;
case mtCow : return mfPassive ;
case mtCreeper : return mfHostile ;
case mtDolphin : return mfWater ;
case mtDonkey : return mfPassive ;
case mtDrowned : return mfHostile ;
case mtElderGuardian : return mfHostile ;
case mtEnderDragon : return mfNoSpawn ;
case mtEnderman : return mfHostile ;
case mtEndermite : return mfHostile ;
case mtEvoker : return mfHostile ;
case mtFox : return mfPassive ;
case mtGhast : return mfHostile ;
case mtGiant : return mfNoSpawn ;
case mtGuardian : return mfWater ; // Just because they have special spawning conditions. TODO: If Watertemples have been added, this needs to be edited!
case mtHoglin : return mfHostile ;
case mtHorse : return mfPassive ;
case mtHusk : return mfHostile ;
case mtIllusioner : return mfHostile ;
case mtIronGolem : return mfPassive ;
case mtLlama : return mfPassive ;
case mtMagmaCube : return mfHostile ;
case mtMooshroom : return mfPassive ;
case mtMule : return mfPassive ;
case mtOcelot : return mfPassive ;
case mtPanda : return mfPassive ;
case mtParrot : return mfPassive ;
case mtPhantom : return mfHostile ;
case mtPig : return mfPassive ;
case mtPiglin : return mfHostile ;
case mtPiglinBrute : return mfHostile ;
case mtPillager : return mfHostile ;
case mtPolarBear : return mfPassive ;
case mtPufferfish : return mfWater ;
case mtRabbit : return mfPassive ;
case mtRavager : return mfHostile ;
case mtSalmon : return mfWater ;
case mtSheep : return mfPassive ;
case mtShulker : return mfHostile ;
case mtSilverfish : return mfHostile ;
case mtSkeleton : return mfHostile ;
case mtSkeletonHorse : return mfPassive ;
case mtSlime : return mfHostile ;
case mtSnowGolem : return mfNoSpawn ;
case mtSpider : return mfHostile ;
case mtSquid : return mfWater ;
case mtStray : return mfHostile ;
case mtStrider : return mfHostile ;
case mtTraderLlama : return mfPassive ;
case mtTropicalFish : return mfWater ;
case mtTurtle : return mfWater ; // I'm not quite sure
case mtVex : return mfHostile ;
case mtVindicator : return mfHostile ;
case mtVillager : return mfPassive ;
case mtWanderingTrader : return mfPassive ;
case mtWitch : return mfHostile ;
case mtWither : return mfNoSpawn ;
case mtWitherSkeleton : return mfHostile ;
case mtWolf : return mfPassive ;
case mtZoglin : return mfHostile ;
case mtZombie : return mfHostile ;
case mtZombieHorse : return mfPassive ;
case mtZombiePigman : return mfHostile ;
case mtZombieVillager : return mfHostile ;
2021-04-04 20:38:43 -04:00
case mtInvalidType : break ;
2014-04-24 21:11:11 -04:00
}
2021-04-04 20:38:43 -04:00
UNREACHABLE ( " Unhandled mob type " ) ;
2013-10-20 07:25:56 -04:00
}
2021-04-04 20:38:43 -04:00
cTickTime cMonster : : GetSpawnDelay ( cMonster : : eFamily a_MobFamily )
2013-10-20 08:00:45 -04:00
{
switch ( a_MobFamily )
{
2021-04-04 20:38:43 -04:00
case mfHostile : return 40 _tick ;
case mfPassive : return 40 _tick ;
case mfAmbient : return 40 _tick ;
case mfWater : return 400 _tick ;
case mfNoSpawn : return - 1 _tick ;
2013-10-20 08:00:45 -04:00
}
2021-04-04 20:38:43 -04:00
UNREACHABLE ( " Unhandled mob family " ) ;
2013-10-20 08:00:45 -04:00
}
2016-02-01 15:49:34 -05:00
/** Sets the target. */
void cMonster : : SetTarget ( cPawn * a_NewTarget )
{
2016-02-07 12:07:14 -05:00
ASSERT ( ( a_NewTarget = = nullptr ) | | ( IsTicking ( ) ) ) ;
2016-02-01 15:49:34 -05:00
if ( m_Target = = a_NewTarget )
{
return ;
}
cPawn * OldTarget = m_Target ;
m_Target = a_NewTarget ;
if ( OldTarget ! = nullptr )
{
// Notify the old target that we are no longer targeting it.
OldTarget - > NoLongerTargetingMe ( this ) ;
}
if ( a_NewTarget ! = nullptr )
{
2016-02-07 12:07:14 -05:00
ASSERT ( a_NewTarget - > IsTicking ( ) ) ;
2016-02-01 15:49:34 -05:00
// Notify the new target that we are now targeting it.
m_Target - > TargetingMe ( this ) ;
2017-05-21 04:29:06 -04:00
m_WasLastTargetAPlayer = m_Target - > IsPlayer ( ) ;
2016-02-01 15:49:34 -05:00
}
}
void cMonster : : UnsafeUnsetTarget ( )
{
m_Target = nullptr ;
}
2016-12-19 15:12:23 -05:00
cPawn * cMonster : : GetTarget ( )
2016-02-01 15:49:34 -05:00
{
return m_Target ;
}
2016-12-19 15:12:23 -05:00
std : : unique_ptr < cMonster > cMonster : : NewMonsterFromType ( eMonsterType a_MobType )
2013-10-20 07:25:56 -04:00
{
2017-06-13 15:35:30 -04:00
auto & Random = GetRandomProvider ( ) ;
2013-10-20 07:25:56 -04:00
2013-11-10 15:48:12 -05:00
// Create the mob entity
2013-10-20 07:25:56 -04:00
switch ( a_MobType )
{
case mtMagmaCube :
2014-05-02 13:17:22 -04:00
{
2020-08-01 14:18:03 -04:00
return std : : make_unique < cMagmaCube > ( 1 < < Random . RandInt ( 2 ) ) ; // Size 1, 2 or 4
2014-05-02 13:17:22 -04:00
}
2013-10-20 07:25:56 -04:00
case mtSlime :
{
2020-08-01 14:18:03 -04:00
return std : : make_unique < cSlime > ( 1 < < Random . RandInt ( 2 ) ) ; // Size 1, 2 or 4
2013-11-13 05:08:51 -05:00
}
2020-08-01 14:18:03 -04:00
case mtVillager : return std : : make_unique < cVillager > ( cVillager : : GetRandomProfession ( ) ) ;
2013-11-10 15:48:12 -05:00
case mtHorse :
{
// Horses take a type (species), a colour, and a style (dots, stripes, etc.)
2017-06-13 15:35:30 -04:00
int HorseType = Random . RandInt ( 7 ) ;
int HorseColor = Random . RandInt ( 6 ) ;
int HorseStyle = Random . RandInt ( 4 ) ;
int HorseTameTimes = Random . RandInt ( 1 , 6 ) ;
2013-11-10 15:48:12 -05:00
2013-11-13 05:08:51 -05:00
if ( ( HorseType = = 5 ) | | ( HorseType = = 6 ) | | ( HorseType = = 7 ) )
2013-10-20 07:25:56 -04:00
{
2013-11-13 05:08:51 -05:00
// Increase chances of normal horse (zero)
HorseType = 0 ;
2013-10-20 07:25:56 -04:00
}
2013-11-10 15:48:12 -05:00
2020-08-01 14:18:03 -04:00
return std : : make_unique < cHorse > ( HorseType , HorseColor , HorseStyle , HorseTameTimes ) ;
2013-10-20 07:25:56 -04:00
}
2018-02-03 06:24:19 -05:00
case mtZombieVillager :
{
2020-08-01 14:18:03 -04:00
return std : : make_unique < cZombieVillager > ( cVillager : : GetRandomProfession ( ) ) ;
2018-02-03 06:24:19 -05:00
}
2020-08-01 14:18:03 -04:00
case mtBat : return std : : make_unique < cBat > ( ) ;
case mtBlaze : return std : : make_unique < cBlaze > ( ) ;
case mtCaveSpider : return std : : make_unique < cCaveSpider > ( ) ;
case mtChicken : return std : : make_unique < cChicken > ( ) ;
case mtCow : return std : : make_unique < cCow > ( ) ;
case mtCreeper : return std : : make_unique < cCreeper > ( ) ;
case mtEnderDragon : return std : : make_unique < cEnderDragon > ( ) ;
case mtEnderman : return std : : make_unique < cEnderman > ( ) ;
case mtGhast : return std : : make_unique < cGhast > ( ) ;
case mtGiant : return std : : make_unique < cGiant > ( ) ;
case mtGuardian : return std : : make_unique < cGuardian > ( ) ;
case mtIronGolem : return std : : make_unique < cIronGolem > ( ) ;
case mtMooshroom : return std : : make_unique < cMooshroom > ( ) ;
case mtOcelot : return std : : make_unique < cOcelot > ( ) ;
case mtPig : return std : : make_unique < cPig > ( ) ;
case mtRabbit : return std : : make_unique < cRabbit > ( ) ;
case mtSheep : return std : : make_unique < cSheep > ( ) ;
case mtSilverfish : return std : : make_unique < cSilverfish > ( ) ;
case mtSkeleton : return std : : make_unique < cSkeleton > ( ) ;
case mtSnowGolem : return std : : make_unique < cSnowGolem > ( ) ;
case mtSpider : return std : : make_unique < cSpider > ( ) ;
case mtSquid : return std : : make_unique < cSquid > ( ) ;
case mtWitch : return std : : make_unique < cWitch > ( ) ;
case mtWither : return std : : make_unique < cWither > ( ) ;
case mtWitherSkeleton : return std : : make_unique < cWitherSkeleton > ( ) ;
case mtWolf : return std : : make_unique < cWolf > ( ) ;
case mtZombie : return std : : make_unique < cZombie > ( ) ;
case mtZombiePigman : return std : : make_unique < cZombiePigman > ( ) ;
2013-10-20 07:25:56 -04:00
default :
{
2013-11-10 15:48:12 -05:00
ASSERT ( ! " Unhandled mob type whilst trying to spawn mob! " ) ;
2016-12-19 15:12:23 -05:00
return nullptr ;
2013-10-20 07:25:56 -04:00
}
}
}
2020-10-09 16:49:25 -04:00
void cMonster : : EngageLoveMode ( cMonster * a_Partner )
{
m_LovePartner = a_Partner ;
m_MatingTimer = 50 ; // about 3 seconds of mating
}
void cMonster : : ResetLoveMode ( )
{
m_LovePartner = nullptr ;
m_LoveTimer = 0 ;
m_MatingTimer = 0 ;
m_LoveCooldown = TPS * 60 * 5 ; // 5 minutes
// when an animal is in love mode, the client only stops sending the hearts if we let them know it's in cooldown, which is done with the "age" metadata
m_World - > BroadcastEntityMetadata ( * this ) ;
}
void cMonster : : LoveTick ( void )
{
// if we have a partner, mate
if ( m_LovePartner ! = nullptr )
{
if ( m_MatingTimer > 0 )
{
// If we should still mate, keep bumping into them until baby is made
Vector3d Pos = m_LovePartner - > GetPosition ( ) ;
MoveToPosition ( Pos ) ;
}
else
{
// Mating finished. Spawn baby
Vector3f Pos = ( GetPosition ( ) + m_LovePartner - > GetPosition ( ) ) * 0.5 ;
UInt32 BabyID = m_World - > SpawnMob ( Pos . x , Pos . y , Pos . z , GetMobType ( ) , true ) ;
cMonster * Baby = nullptr ;
m_World - > DoWithEntityByID ( BabyID , [ & ] ( cEntity & a_Entity )
{
Baby = static_cast < cMonster * > ( & a_Entity ) ;
return true ;
} ) ;
if ( Baby ! = nullptr )
{
Baby - > InheritFromParents ( this , m_LovePartner ) ;
}
m_World - > SpawnExperienceOrb ( Pos . x , Pos . y , Pos . z , GetRandomProvider ( ) . RandInt ( 1 , 6 ) ) ;
m_World - > DoWithPlayerByUUID ( m_Feeder , [ & ] ( cPlayer & a_Player )
{
2021-05-03 16:07:09 -04:00
a_Player . GetStatistics ( ) . Custom [ CustomStatistic : : AnimalsBred ] + + ;
2020-10-09 16:49:25 -04:00
if ( GetMobType ( ) = = eMonsterType : : mtCow )
{
2021-05-03 16:07:09 -04:00
a_Player . AwardAchievement ( CustomStatistic : : AchBreedCow ) ;
2020-10-09 16:49:25 -04:00
}
return true ;
} ) ;
m_LovePartner - > ResetLoveMode ( ) ;
ResetLoveMode ( ) ;
}
}
else
{
// We have no partner, so we just chase the player if they have our breeding item
cItems FollowedItems ;
GetFollowedItems ( FollowedItems ) ;
if ( FollowedItems . Size ( ) > 0 )
{
m_World - > DoWithNearestPlayer ( GetPosition ( ) , static_cast < float > ( m_SightDistance ) , [ & ] ( cPlayer & a_Player ) - > bool
{
const cItem & EquippedItem = a_Player . GetEquippedItem ( ) ;
if ( FollowedItems . ContainsType ( EquippedItem ) )
{
Vector3d PlayerPos = a_Player . GetPosition ( ) ;
MoveToPosition ( PlayerPos ) ;
}
return true ;
} ) ;
}
}
// If we are in love mode but we have no partner, search for a partner neabry
if ( m_LoveTimer > 0 )
{
if ( m_LovePartner = = nullptr )
{
m_World - > ForEachEntityInBox ( cBoundingBox ( GetPosition ( ) , 8 , 8 ) , [ = ] ( cEntity & a_Entity )
{
// If the entity is not a monster, don't breed with it
// Also, do not self-breed
if ( ( a_Entity . GetEntityType ( ) ! = etMonster ) | | ( & a_Entity = = this ) )
{
return false ;
}
auto & Me = static_cast < cMonster & > ( * this ) ;
auto & PotentialPartner = static_cast < cMonster & > ( a_Entity ) ;
// If the potential partner is not of the same species, don't breed with it
if ( PotentialPartner . GetMobType ( ) ! = Me . GetMobType ( ) )
{
return false ;
}
// If the potential partner is not in love
// Or they already have a mate, do not breed with them
if ( ( ! PotentialPartner . IsInLove ( ) ) | | ( PotentialPartner . GetPartner ( ) ! = nullptr ) )
{
return false ;
}
// All conditions met, let's breed!
PotentialPartner . EngageLoveMode ( & Me ) ;
Me . EngageLoveMode ( & PotentialPartner ) ;
return true ;
} ) ;
}
m_LoveTimer - - ;
}
if ( m_MatingTimer > 0 )
{
m_MatingTimer - - ;
}
if ( m_LoveCooldown > 0 )
{
m_LoveCooldown - - ;
}
}
void cMonster : : RightClickFeed ( cPlayer & a_Player )
{
const cItem & EquippedItem = a_Player . GetEquippedItem ( ) ;
// If a player holding breeding items right-clicked me, go into love mode
if ( ( m_LoveCooldown = = 0 ) & & ! IsInLove ( ) & & ! IsBaby ( ) )
{
cItems Items ;
GetBreedingItems ( Items ) ;
if ( Items . ContainsType ( EquippedItem . m_ItemType ) )
{
if ( ! a_Player . IsGameModeCreative ( ) )
{
a_Player . GetInventory ( ) . RemoveOneEquippedItem ( ) ;
}
m_LoveTimer = TPS * 30 ; // half a minute
2021-04-09 18:17:01 -04:00
m_World - > BroadcastEntityAnimation ( * this , EntityAnimation : : AnimalFallsInLove ) ;
2020-10-09 16:49:25 -04:00
}
}
// If a player holding my spawn egg right-clicked me, spawn a new baby
if ( EquippedItem . m_ItemType = = E_ITEM_SPAWN_EGG )
{
eMonsterType MonsterType = cItemSpawnEggHandler : : ItemDamageToMonsterType ( EquippedItem . m_ItemDamage ) ;
if (
( MonsterType = = m_MobType ) & &
( m_World - > SpawnMob ( GetPosX ( ) , GetPosY ( ) , GetPosZ ( ) , m_MobType , true ) ! = cEntity : : INVALID_ID ) // Spawning succeeded
)
{
if ( ! a_Player . IsGameModeCreative ( ) )
{
// The mob was spawned, "use" the item:
a_Player . GetInventory ( ) . RemoveOneEquippedItem ( ) ;
}
}
}
// Stores feeder UUID for statistic tracking
m_Feeder = a_Player . GetUUID ( ) ;
}
2012-08-19 15:42:32 -04:00
void cMonster : : AddRandomDropItem ( cItems & a_Drops , unsigned int a_Min , unsigned int a_Max , short a_Item , short a_ItemHealth )
2012-06-14 09:06:06 -04:00
{
2017-08-18 06:23:08 -04:00
auto Count = GetRandomProvider ( ) . RandInt < unsigned int > ( a_Min , a_Max ) ;
auto MaxStackSize = static_cast < unsigned char > ( ItemHandler ( a_Item ) - > GetMaxStackSize ( ) ) ;
while ( Count > MaxStackSize )
{
a_Drops . emplace_back ( a_Item , MaxStackSize , a_ItemHealth ) ;
Count - = MaxStackSize ;
}
2012-06-14 09:06:06 -04:00
if ( Count > 0 )
{
2017-06-13 15:35:30 -04:00
a_Drops . emplace_back ( a_Item , Count , a_ItemHealth ) ;
2012-06-14 09:06:06 -04:00
}
}
2013-09-05 16:40:08 -04:00
2014-02-23 13:44:58 -05:00
void cMonster : : AddRandomUncommonDropItem ( cItems & a_Drops , float a_Chance , short a_Item , short a_ItemHealth )
{
2017-06-13 15:35:30 -04:00
if ( GetRandomProvider ( ) . RandBool ( a_Chance / 100.0 ) )
2014-02-23 13:44:58 -05:00
{
2021-03-07 11:31:43 -05:00
a_Drops . emplace_back ( a_Item , 1 , a_ItemHealth ) ;
2014-02-23 13:44:58 -05:00
}
}
2015-05-19 14:32:10 -04:00
void cMonster : : AddRandomRareDropItem ( cItems & a_Drops , cItems & a_Items , unsigned int a_LootingLevel )
2014-02-23 13:44:58 -05:00
{
2017-06-13 15:35:30 -04:00
auto & r1 = GetRandomProvider ( ) ;
if ( r1 . RandBool ( ( 5 + a_LootingLevel ) / 200.0 ) )
2014-02-23 13:44:58 -05:00
{
2017-06-13 15:35:30 -04:00
size_t Rare = r1 . RandInt < size_t > ( a_Items . Size ( ) - 1 ) ;
2014-02-23 13:44:58 -05:00
a_Drops . push_back ( a_Items . at ( Rare ) ) ;
}
}
2015-05-19 14:32:10 -04:00
void cMonster : : AddRandomArmorDropItem ( cItems & a_Drops , unsigned int a_LootingLevel )
2014-02-23 13:44:58 -05:00
{
2017-06-13 15:35:30 -04:00
auto & r1 = GetRandomProvider ( ) ;
double LootingBonus = a_LootingLevel / 100.0 ;
if ( r1 . RandBool ( m_DropChanceHelmet + LootingBonus ) )
2014-02-23 13:44:58 -05:00
{
2014-12-05 10:59:11 -05:00
if ( ! GetEquippedHelmet ( ) . IsEmpty ( ) )
{
a_Drops . push_back ( GetEquippedHelmet ( ) ) ;
}
2014-02-23 13:44:58 -05:00
}
2015-04-29 12:24:14 -04:00
2017-06-13 15:35:30 -04:00
if ( r1 . RandBool ( m_DropChanceChestplate + LootingBonus ) )
2014-02-23 13:44:58 -05:00
{
2014-12-05 10:59:11 -05:00
if ( ! GetEquippedChestplate ( ) . IsEmpty ( ) )
{
a_Drops . push_back ( GetEquippedChestplate ( ) ) ;
}
2014-02-23 13:44:58 -05:00
}
2015-04-29 12:24:14 -04:00
2017-06-13 15:35:30 -04:00
if ( r1 . RandBool ( m_DropChanceLeggings + LootingBonus ) )
2014-02-23 13:44:58 -05:00
{
2014-12-05 10:59:11 -05:00
if ( ! GetEquippedLeggings ( ) . IsEmpty ( ) )
{
a_Drops . push_back ( GetEquippedLeggings ( ) ) ;
}
2014-02-23 13:44:58 -05:00
}
2015-04-29 12:24:14 -04:00
2017-06-13 15:35:30 -04:00
if ( r1 . RandBool ( m_DropChanceBoots + LootingBonus ) )
2014-02-23 13:44:58 -05:00
{
2014-12-05 10:59:11 -05:00
if ( ! GetEquippedBoots ( ) . IsEmpty ( ) )
{
a_Drops . push_back ( GetEquippedBoots ( ) ) ;
}
2014-02-23 13:44:58 -05:00
}
}
2015-05-19 14:32:10 -04:00
void cMonster : : AddRandomWeaponDropItem ( cItems & a_Drops , unsigned int a_LootingLevel )
2014-02-23 13:44:58 -05:00
{
2017-06-13 15:35:30 -04:00
if ( GetRandomProvider ( ) . RandBool ( m_DropChanceWeapon + ( a_LootingLevel / 100.0 ) ) )
2014-02-23 13:44:58 -05:00
{
2014-12-05 10:59:11 -05:00
if ( ! GetEquippedWeapon ( ) . IsEmpty ( ) )
{
a_Drops . push_back ( GetEquippedWeapon ( ) ) ;
}
2014-02-23 13:44:58 -05:00
}
}
2015-05-01 13:34:24 -04:00
void cMonster : : HandleDaylightBurning ( cChunk & a_Chunk , bool WouldBurn )
2013-09-05 16:40:08 -04:00
{
if ( ! m_BurnsInDaylight )
{
return ;
}
2015-04-29 12:24:14 -04:00
2014-04-17 13:50:25 -04:00
int RelY = POSY_TOINT ;
2013-09-05 16:40:08 -04:00
if ( ( RelY < 0 ) | | ( RelY > = cChunkDef : : Height ) )
{
// Outside the world
return ;
}
2014-01-24 14:57:32 -05:00
if ( ! a_Chunk . IsLightValid ( ) )
{
m_World - > QueueLightChunk ( GetChunkX ( ) , GetChunkZ ( ) ) ;
return ;
}
2015-05-01 13:34:24 -04:00
if ( ! IsOnFire ( ) & & WouldBurn )
2015-04-29 12:24:14 -04:00
{
// Burn for 100 ticks, then decide again
StartBurning ( 100 ) ;
}
}
2018-07-26 17:24:36 -04:00
2015-04-29 12:24:14 -04:00
bool cMonster : : WouldBurnAt ( Vector3d a_Location , cChunk & a_Chunk )
{
2015-12-29 19:15:13 -05:00
// If the Y coord is out of range, return the most logical result without considering anything else:
int RelY = FloorC ( a_Location . y ) ;
if ( RelY > = cChunkDef : : Height )
{
// Always burn above the world
return true ;
}
2016-03-29 09:34:41 -04:00
if ( RelY < = 0 )
2015-05-06 10:02:50 -04:00
{
2016-03-29 09:34:41 -04:00
// The mob is about to die, no point in burning
2015-05-06 10:02:50 -04:00
return false ;
}
2015-05-08 20:51:25 -04:00
2016-03-29 09:34:41 -04:00
PREPARE_REL_AND_CHUNK ( a_Location , a_Chunk ) ;
if ( ! RelSuccess )
{
return false ;
}
2015-05-08 20:51:25 -04:00
2013-09-05 16:40:08 -04:00
if (
2021-01-04 21:13:02 -05:00
( Chunk - > GetBlock ( Rel ) ! = E_BLOCK_SOULSAND ) & & // Not on soulsand
2021-04-04 20:38:43 -04:00
( GetWorld ( ) - > GetTimeOfDay ( ) < 13000 _tick ) & & // Daytime
2021-01-04 21:13:02 -05:00
Chunk - > IsWeatherSunnyAt ( Rel . x , Rel . z ) & & // Not raining
! IsInWater ( ) // Isn't swimming
2013-09-05 16:40:08 -04:00
)
{
2017-08-25 11:26:45 -04:00
int MobHeight = CeilC ( a_Location . y + GetHeight ( ) ) - 1 ; // The block Y coord of the mob's head
2016-03-29 09:34:41 -04:00
if ( MobHeight > = cChunkDef : : Height )
{
return true ;
}
2017-08-25 11:26:45 -04:00
// Start with the highest block and scan down to just above the mob's head.
2016-03-29 09:34:41 -04:00
// If a non transparent is found, return false (do not burn). Otherwise return true.
// Note that this loop is not a performance concern as transparent blocks are rare and the loop almost always bailes out
// instantly.(An exception is e.g. standing under a long column of glass).
int CurrentBlock = Chunk - > GetHeight ( Rel . x , Rel . z ) ;
2017-08-25 11:26:45 -04:00
while ( CurrentBlock > MobHeight )
2016-03-29 09:34:41 -04:00
{
BLOCKTYPE Block = Chunk - > GetBlock ( Rel . x , CurrentBlock , Rel . z ) ;
if (
// Do not burn if a block above us meets one of the following conditions:
( ! cBlockInfo : : IsTransparent ( Block ) ) | |
( Block = = E_BLOCK_LEAVES ) | |
( Block = = E_BLOCK_NEW_LEAVES ) | |
( IsBlockWater ( Block ) )
)
{
return false ;
}
- - CurrentBlock ;
}
2015-04-29 12:24:14 -04:00
return true ;
2016-03-29 09:34:41 -04:00
2013-09-05 16:40:08 -04:00
}
2015-04-29 12:24:14 -04:00
return false ;
2013-09-05 16:40:08 -04:00
}
2015-04-29 12:24:14 -04:00
2013-09-10 09:09:45 -04:00
cMonster : : eFamily cMonster : : GetMobFamily ( void ) const
{
2013-10-20 07:25:56 -04:00
return FamilyFromType ( m_MobType ) ;
2013-09-10 09:09:45 -04:00
}
2017-08-21 04:46:41 -04:00
2017-08-24 07:26:23 -04:00
void cMonster : : LeashTo ( cEntity & a_Entity , bool a_ShouldBroadcast )
2017-08-21 04:46:41 -04:00
{
// Do nothing if already leashed
if ( m_LeashedTo ! = nullptr )
{
return ;
}
2017-08-24 07:26:23 -04:00
m_LeashedTo = & a_Entity ;
2017-08-21 04:46:41 -04:00
2017-08-24 07:26:23 -04:00
a_Entity . AddLeashedMob ( this ) ;
2017-08-21 04:46:41 -04:00
if ( a_ShouldBroadcast )
{
2017-08-24 07:26:23 -04:00
m_World - > BroadcastLeashEntity ( * this , a_Entity ) ;
2017-08-21 04:46:41 -04:00
}
m_IsLeashActionJustDone = true ;
}
void cMonster : : Unleash ( bool a_ShouldDropLeashPickup , bool a_ShouldBroadcast )
{
// Do nothing if not leashed
if ( m_LeashedTo = = nullptr )
{
return ;
}
m_LeashedTo - > RemoveLeashedMob ( this ) ;
m_LeashedTo = nullptr ;
if ( a_ShouldDropLeashPickup )
{
cItems Pickups ;
Pickups . Add ( cItem ( E_ITEM_LEASH , 1 , 0 ) ) ;
GetWorld ( ) - > SpawnItemPickups ( Pickups , GetPosX ( ) + 0.5 , GetPosY ( ) + 0.5 , GetPosZ ( ) + 0.5 ) ;
}
if ( a_ShouldBroadcast )
{
m_World - > BroadcastUnleashEntity ( * this ) ;
}
m_IsLeashActionJustDone = true ;
}
void cMonster : : Unleash ( bool a_ShouldDropLeashPickup )
{
Unleash ( a_ShouldDropLeashPickup , true ) ;
}