2014-04-10 15:52:00 +01:00
2012-06-14 13:06:06 +00:00
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
2013-10-20 13:25:56 +02:00
#include "IncludeAllMonsters.h"
2020-12-18 20:48:32 +00:00
#include "LineBlockTracer.h"
2020-04-03 08:57:01 +02:00
#include "../BlockInfo.h"
2012-09-23 22:09:57 +00:00
#include "../Root.h"
#include "../Server.h"
#include "../ClientHandle.h"
2017-08-18 05:23:08 -05:00
#include "../Items/ItemHandler.h"
2012-09-23 22:09:57 +00:00
#include "../World.h"
2015-11-23 23:39:19 +00:00
#include "../EffectID.h"
2013-08-19 11:39:13 +02:00
#include "../Entities/Player.h"
2013-11-25 20:04:39 +01:00
#include "../Entities/ExpOrb.h"
2012-09-23 22:09:57 +00:00
#include "../MonsterConfig.h"
2018-08-29 01:51:25 +01:00
#include "../BoundingBox.h"
2012-09-23 20:53:08 +00:00
2020-10-09 22:49:25 +02:00
#include "Items/ItemSpawnEgg.h"
2013-09-05 22:40:08 +02:00
#include "../Chunk.h"
2013-10-20 13:25:56 +02:00
#include "../FastRandom.h"
2013-04-22 07:18:03 +00:00
2015-11-20 10:03:20 +02:00
#include "PathFinder.h"
2017-08-21 10:46:41 +02:00
#include "../Entities/LeashKnot.h"
2013-10-20 13:25:56 +02: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 14:58:13 +01:00
m_VanillaName is the name that vanilla use for this mob.
2013-10-20 13:25:56 +02:00
*/
static const struct
{
2014-09-17 18:40:10 +01:00
eMonsterType m_Type ;
2013-10-20 13:25:56 +02:00
const char * m_lcName ;
2014-11-29 15:20:44 +01:00
const char * m_VanillaName ;
2017-05-22 17:41:41 +02:00
const char * m_VanillaNameNBT ;
2013-10-20 13:25:56 +02:00
} g_MobTypeNames [] =
{
2020-04-04 14:44:17 +03: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 12:24:19 +01:00
{ mtZombieVillager , "zombievillager" , "ZombieVillager" , "zombie_villager" },
2013-10-20 13:25:56 +02:00
} ;
2014-07-17 22:15:34 +02:00
////////////////////////////////////////////////////////////////////////////////
2013-10-20 13:25:56 +02:00
// cMonster:
2012-06-14 13:06:06 +00:00
2020-03-22 17:50:34 +02:00
cMonster :: cMonster ( const AString & a_ConfigName , eMonsterType a_MobType , const AString & a_SoundHurt , const AString & a_SoundDeath , const AString & a_SoundAmbient , double a_Width , double a_Height )
2020-04-13 18:38:06 +02:00
: Super ( etMonster , a_Width , a_Height )
2013-12-20 16:39:20 +01:00
, m_EMState ( IDLE )
, m_EMPersonality ( AGGRESSIVE )
2015-11-20 10:03:20 +02:00
, m_PathFinder ( a_Width , a_Height )
2015-07-11 20:40:03 +01:00
, m_PathfinderActivated ( false )
2015-05-02 21:22:28 +03:00
, m_JumpCoolDown ( 0 )
2014-02-05 09:43:49 -08:00
, m_IdleInterval ( 0 )
2014-01-24 21:55:04 +00:00
, m_DestroyTimer ( 0 )
2013-10-20 10:23:30 +02:00
, m_MobType ( a_MobType )
2014-09-01 20:12:56 +02:00
, m_CustomName ( "" )
, m_CustomNameAlwaysVisible ( false )
2012-12-22 09:39:13 +00:00
, m_SoundHurt ( a_SoundHurt )
, m_SoundDeath ( a_SoundDeath )
2020-03-22 17:50:34 +02:00
, m_SoundAmbient ( a_SoundAmbient )
2014-02-05 09:43:49 -08:00
, m_AttackRate ( 3 )
2014-01-24 21:55:04 +00:00
, m_AttackDamage ( 1 )
2015-05-16 14:05:33 +03:00
, m_AttackRange ( 1 )
2016-01-12 12:11:10 +02:00
, m_AttackCoolDownTicksLeft ( 0 )
2014-02-24 15:38:38 +01:00
, m_SightDistance ( 25 )
2014-03-16 22:00:28 +01:00
, m_DropChanceWeapon ( 0.085f )
, m_DropChanceHelmet ( 0.085f )
, m_DropChanceChestplate ( 0.085f )
, m_DropChanceLeggings ( 0.085f )
, m_DropChanceBoots ( 0.085f )
2014-02-23 19:44:58 +01:00
, m_CanPickUpLoot ( true )
2015-04-29 19:24:14 +03:00
, m_TicksSinceLastDamaged ( 100 )
2013-09-07 16:42:34 +02:00
, m_BurnsInDaylight ( false )
2015-04-29 19:24:14 +03:00
, m_RelativeWalkSpeed ( 1 )
2015-07-12 18:00:56 +02:00
, m_Age ( 1 )
2020-10-09 22:49:25 +02:00
, m_AgingTimer ( TPS * 60 * 20 ) // about 20 minutes
2017-05-21 10:29:06 +02:00
, m_WasLastTargetAPlayer ( false )
2017-08-21 10:46:41 +02:00
, m_LeashedTo ( nullptr )
, m_LeashToPos ( nullptr )
, m_IsLeashActionJustDone ( false )
, m_CanBeLeashed ( GetMobFamily () == eFamily :: mfPassive )
2020-10-09 22:49:25 +02:00
, m_LovePartner ( nullptr )
, m_LoveTimer ( 0 )
, m_LoveCooldown ( 0 )
, m_MatingTimer ( 0 )
2016-02-01 22:49:34 +02:00
, m_Target ( nullptr )
2012-06-14 13:06:06 +00:00
{
2012-12-22 09:39:13 +00:00
if ( ! a_ConfigName . empty ())
{
GetMonsterConfig ( a_ConfigName );
}
2020-03-22 17:50:34 +02:00
// Prevent mobs spawning at the same time from making sounds simultaneously
m_AmbientSoundTimer = GetRandomProvider (). RandInt ( 0 , 100 );
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
2016-02-01 22:49:34 +02:00
cMonster ::~ cMonster ()
{
ASSERT ( GetTarget () == nullptr );
}
2020-03-05 12:52:34 +02:00
void cMonster :: OnRemoveFromWorld ( cWorld & a_World )
2017-08-21 10:46:41 +02:00
{
if ( IsLeashed ())
{
cEntity * LeashedTo = GetLeashedTo ();
2020-03-05 12:52:34 +02:00
Unleash ( false , true );
2017-08-21 10:46:41 +02:00
// Remove leash knot if there are no more mobs leashed to
if ( ! LeashedTo -> HasAnyMobLeashed () && LeashedTo -> IsLeashKnot ())
{
LeashedTo -> Destroy ();
}
}
2020-04-13 18:38:06 +02:00
Super :: OnRemoveFromWorld ( a_World );
2017-08-21 10:46:41 +02:00
}
2016-02-01 22:49:34 +02:00
void cMonster :: Destroyed ()
{
SetTarget ( nullptr ); // Tell them we're no longer targeting them.
2020-10-09 22:49:25 +02:00
if ( m_LovePartner != nullptr )
{
m_LovePartner -> ResetLoveMode ();
}
2020-04-13 18:38:06 +02:00
Super :: Destroyed ();
2016-02-01 22:49:34 +02:00
}
2012-08-24 07:58:26 +00:00
void cMonster :: SpawnOn ( cClientHandle & a_Client )
2012-06-14 13:06:06 +00:00
{
2012-08-24 07:58:26 +00:00
a_Client . SendSpawnMob ( * this );
2017-08-21 10:46:41 +02:00
if ( IsLeashed ())
{
a_Client . SendLeashEntity ( * this , * this -> GetLeashedTo ());
}
2012-06-14 13:06:06 +00:00
}
2015-11-20 10:03:20 +02:00
void cMonster :: MoveToWayPoint ( cChunk & a_Chunk )
2012-06-14 13:06:06 +00:00
{
2015-11-20 10:03:20 +02:00
if (( m_NextWayPointPosition - GetPosition ()). SqrLength () < WAYPOINT_RADIUS * WAYPOINT_RADIUS )
2015-05-03 18:56:37 +01:00
{
2015-11-20 10:03:20 +02:00
return ;
2014-01-24 19:57:32 +00:00
}
2017-05-28 20:56:17 +02:00
if ( m_JumpCoolDown <= 0 )
2015-05-03 18:56:37 +01:00
{
2015-05-06 17:23:07 +03:00
if ( DoesPosYRequireJump ( FloorC ( m_NextWayPointPosition . y )))
2015-05-03 18:56:37 +01:00
{
2017-05-28 20:56:17 +02: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 18:44:45 +00:00
IsInWater ()
2017-05-28 20:56:17 +02:00
)
2015-05-03 18:56:37 +01:00
{
m_bOnGround = false ;
m_JumpCoolDown = 20 ;
AddPosY ( 1.6 ); // Jump!!
2017-05-28 20:56:17 +02:00
SetSpeedY ( 1 );
2015-05-05 10:04:41 +03:00
SetSpeedX ( 3.2 * ( m_NextWayPointPosition . x - GetPosition (). x )); // Move forward in a preset speed.
2017-05-28 20:56:17 +02:00
SetSpeedZ ( 3.2 * ( m_NextWayPointPosition . z - GetPosition (). z )); // The numbers were picked based on trial and error
2015-05-03 18:56:37 +01:00
}
}
}
else
{
-- m_JumpCoolDown ;
}
2015-04-29 19:24:14 +03:00
2015-05-05 10:04:41 +03:00
Vector3d Distance = m_NextWayPointPosition - GetPosition ();
2017-05-28 20:56:17 +02:00
if (( std :: abs ( Distance . x ) > 0.05 ) || ( std :: abs ( Distance . z ) > 0.05 ))
2015-05-03 18:56:37 +01:00
{
Distance . y = 0 ;
Distance . Normalize ();
2015-04-29 19:24:14 +03:00
2015-05-03 18:56:37 +01:00
if ( m_bOnGround )
{
Distance *= 2.5f ;
}
2018-01-14 18:44:45 +00:00
else if ( IsInWater ())
2015-05-03 18:56:37 +01: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 10:04:41 +03:00
Close to Vanilla, easier for mobs to follow m_NextWayPointPositions, hence
2015-05-03 18:56:37 +01:00
better pathfinding. */
Distance *= 0.5 ;
AddSpeedX ( Distance . x );
AddSpeedZ ( Distance . z );
}
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
2015-05-03 18:56:37 +01:00
void cMonster :: MoveToPosition ( const Vector3d & a_Position )
2015-04-29 19:24:14 +03:00
{
2015-07-11 20:40:03 +01:00
m_FinalDestination = a_Position ;
m_PathfinderActivated = true ;
2015-04-29 19:24:14 +03:00
}
2015-05-03 18:56:37 +01:00
void cMonster :: StopMovingToPosition ()
2012-06-14 13:06:06 +00:00
{
2015-07-11 20:40:03 +01:00
m_PathfinderActivated = false ;
2012-06-14 13:06:06 +00:00
}
2015-01-11 21:12:26 +00:00
void cMonster :: Tick ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
2012-06-14 13:06:06 +00:00
{
2020-04-13 18:38:06 +02:00
Super :: Tick ( a_Dt , a_Chunk );
2016-09-03 14:31:27 +03:00
if ( ! IsTicking ())
{
// The base class tick destroyed us
return ;
}
2015-05-01 20:34:24 +03:00
GET_AND_VERIFY_CURRENT_CHUNK ( Chunk , POSX_TOINT , POSZ_TOINT );
2012-06-14 13:06:06 +00:00
2016-02-01 22:49:34 +02:00
ASSERT (( GetTarget () == nullptr ) || ( GetTarget () -> IsPawn () && ( GetTarget () -> GetWorld () == GetWorld ())));
2016-01-12 12:11:10 +02:00
if ( m_AttackCoolDownTicksLeft > 0 )
{
m_AttackCoolDownTicksLeft -= 1 ;
}
2012-12-22 10:15:53 +00:00
if ( m_Health <= 0 )
2012-06-14 13:06:06 +00:00
{
2015-11-20 10:03:20 +02:00
// The mob is dead, but we're still animating the "puff" they leave when they die
2015-01-16 14:38:21 +00:00
m_DestroyTimer += a_Dt ;
if ( m_DestroyTimer > std :: chrono :: seconds ( 1 ))
2012-06-14 13:06:06 +00:00
{
2020-03-05 12:52:34 +02:00
Destroy ();
2012-06-14 13:06:06 +00:00
}
return ;
}
2015-04-29 19:24:14 +03:00
if ( m_TicksSinceLastDamaged < 100 )
{
++ m_TicksSinceLastDamaged ;
}
2014-01-24 23:56:19 +00:00
2015-11-20 10:03:20 +02:00
// Process the undead burning in daylight.
HandleDaylightBurning ( * Chunk , WouldBurnAt ( GetPosition (), * Chunk ));
bool a_IsFollowingPath = false ;
2020-03-26 19:54:40 +02:00
if ( m_PathfinderActivated && ( GetMobType () != mtGhast )) // Pathfinder is currently disabled for ghasts, which have their own flying mechanism
2012-06-14 13:06:06 +00:00
{
2017-08-21 10:46:41 +02:00
if ( ReachedFinalDestination () || ( m_LeashToPos != nullptr ))
2012-06-14 13:06:06 +00:00
{
2015-11-20 10:03:20 +02:00
StopMovingToPosition (); // Simply sets m_PathfinderActivated to false.
}
else
{
// Note that m_NextWayPointPosition is actually returned by GetNextWayPoint)
2020-05-14 23:15:35 +01:00
switch ( m_PathFinder . GetNextWayPoint ( * Chunk , GetPosition (), & m_FinalDestination , & m_NextWayPointPosition , m_EMState == IDLE ))
2015-09-05 22:02:15 +02:00
{
2015-11-20 10:03:20 +02: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 22:02:15 +02:00
}
2012-06-14 13:06:06 +00:00
}
}
2013-03-23 04:33:47 +00:00
2015-11-20 10:03:20 +02:00
SetPitchAndYawFromDestination ( a_IsFollowingPath );
2013-03-22 06:33:10 +00:00
2012-12-22 10:15:53 +00:00
switch ( m_EMState )
2012-08-19 19:42:32 +00:00
{
2012-12-22 10:15:53 +00:00
case IDLE :
{
2015-04-29 19:24:14 +03:00
// If enemy passive we ignore checks for player visibility.
2015-12-22 07:43:50 +02:00
InStateIdle ( a_Dt , a_Chunk );
2012-12-22 10:15:53 +00:00
break ;
2014-07-17 22:50:58 +02:00
}
2012-12-22 10:15:53 +00:00
case CHASING :
{
2015-04-29 19:24:14 +03:00
// If we do not see a player anymore skip chasing action.
2015-12-22 07:43:50 +02:00
InStateChasing ( a_Dt , a_Chunk );
2012-12-22 10:15:53 +00:00
break ;
2014-07-17 22:50:58 +02:00
}
2012-12-22 10:15:53 +00:00
case ESCAPING :
{
2015-12-22 07:43:50 +02:00
InStateEscaping ( a_Dt , a_Chunk );
2012-12-22 10:15:53 +00:00
break ;
}
2014-05-11 16:25:21 -07:00
case ATTACKING : break ;
2012-12-22 10:15:53 +00:00
} // switch (m_EMState)
2014-01-24 19:57:32 +00:00
2017-08-21 10:46:41 +02:00
// Leash calculations
2018-01-17 21:40:59 +00:00
CalcLeashActions ( a_Dt );
2017-08-21 10:46:41 +02:00
2014-01-24 19:57:32 +00:00
BroadcastMovementUpdate ();
2015-11-29 19:13:31 +01:00
2020-03-22 17:50:34 +02: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 19:13:31 +01:00
if ( m_AgingTimer > 0 )
{
m_AgingTimer -- ;
if (( m_AgingTimer <= 0 ) && IsBaby ())
{
SetAge ( 1 );
m_World -> BroadcastEntityMetadata ( * this );
}
}
2015-05-03 18:56:37 +01:00
}
2015-04-29 19:24:14 +03:00
2014-01-24 19:57:32 +00:00
2018-01-17 21:40:59 +00:00
void cMonster :: CalcLeashActions ( std :: chrono :: milliseconds a_Dt )
2017-08-21 10:46:41 +02: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 13:26:23 +02:00
LeashTo ( * LeashKnot );
2017-08-21 10:46:41 +02:00
SetLeashToPos ( nullptr );
}
}
2018-01-17 21:40:59 +00: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 10:46:41 +02:00
{
2018-01-17 21:40:59 +00:00
// Accelerate monster towards the leashed to entity:
const auto Extension = Distance - LeashNaturalLength ;
auto Acceleration = Direction * ( Extension * LeashSpringConstant );
2017-08-21 10:46:41 +02:00
2018-01-17 21:40:59 +00:00
// Stop mobs from floating up when on the ground
if ( IsOnGround () && ( Acceleration . y < std :: abs ( GetGravity ())))
2017-08-21 10:46:41 +02:00
{
2018-01-17 21:40:59 +00:00
Acceleration . y = 0.0 ;
2017-08-21 10:46:41 +02:00
}
2018-01-17 21:40:59 +00: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 10:46:41 +02:00
}
}
2015-11-20 10:03:20 +02:00
void cMonster :: SetPitchAndYawFromDestination ( bool a_IsFollowingPath )
2014-01-24 19:57:32 +00:00
{
2015-05-28 17:45:47 +03:00
Vector3d BodyDistance ;
2020-04-21 23:16:28 +01:00
if ( ! a_IsFollowingPath )
2015-05-28 17:45:47 +03:00
{
2020-04-21 23:16:28 +01:00
if ( GetTarget () == nullptr )
{
// Avoid dirtying head position when nothing will change
// Thus avoids creating unnecessary network traffic
return ;
}
2016-02-01 22:49:34 +02:00
BodyDistance = GetTarget () -> GetPosition () - GetPosition ();
2015-05-28 17:45:47 +03:00
}
else
{
BodyDistance = m_NextWayPointPosition - GetPosition ();
}
2020-04-21 23:16:28 +01:00
2015-05-16 14:05:33 +03:00
double BodyRotation , BodyPitch ;
BodyDistance . Normalize ();
VectorToEuler ( BodyDistance . x , BodyDistance . y , BodyDistance . z , BodyRotation , BodyPitch );
SetYaw ( BodyRotation );
2015-11-20 10:03:20 +02:00
Vector3d HeadDistance ;
2016-02-01 22:49:34 +02:00
if ( GetTarget () != nullptr )
2014-01-24 19:57:32 +00:00
{
2016-02-01 22:49:34 +02:00
if ( GetTarget () -> IsPlayer ()) // Look at a player
2015-05-16 14:05:33 +03:00
{
2016-02-01 22:49:34 +02:00
HeadDistance = GetTarget () -> GetPosition () - GetPosition ();
2015-05-16 14:05:33 +03:00
}
2015-11-20 10:03:20 +02:00
else // Look at some other entity
2015-05-16 14:05:33 +03:00
{
2016-02-01 22:49:34 +02:00
HeadDistance = GetTarget () -> GetPosition () - GetPosition ();
// HeadDistance.y = GetTarget()->GetPosY() + GetHeight();
2015-05-16 14:05:33 +03:00
}
2014-01-24 19:57:32 +00:00
}
2015-11-20 10:03:20 +02: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 07:43:50 +02:00
if (( std :: abs ( BodyRotation - HeadRotation ) < 70 ) && ( std :: abs ( HeadPitch ) < 60 ))
2015-11-20 10:03:20 +02:00
{
SetHeadYaw ( HeadRotation );
SetPitch ( - HeadPitch );
}
else
{
SetHeadYaw ( BodyRotation );
2015-12-22 07:43:50 +02:00
SetPitch ( 0 );
2015-11-20 10:03:20 +02:00
}
2014-01-24 19:57:32 +00:00
}
2015-04-29 19:24:14 +03:00
2014-01-25 19:02:13 +00:00
void cMonster :: HandleFalling ()
{
2015-11-10 14:02:07 +01:00
m_bTouchGround = IsOnGround ();
2020-04-13 18:38:06 +02:00
Super :: HandleFalling ();
2014-01-25 19:02:13 +00:00
}
2014-06-08 18:44:20 -07:00
2014-01-24 19:57:32 +00:00
int cMonster :: FindFirstNonAirBlockPosition ( double a_PosX , double a_PosZ )
{
2014-04-17 18:50:25 +01:00
int PosY = POSY_TOINT ;
2014-07-19 01:40:29 -07:00
PosY = Clamp ( PosY , 0 , cChunkDef :: Height );
2014-01-25 14:42:26 +00:00
2015-07-29 09:04:03 -06:00
if ( ! cBlockInfo :: IsSolid ( m_World -> GetBlock ( FloorC ( a_PosX ), PosY , FloorC ( a_PosZ ))))
2014-01-24 19:57:32 +00:00
{
2015-07-29 09:04:03 -06:00
while ( ! cBlockInfo :: IsSolid ( m_World -> GetBlock ( FloorC ( a_PosX ), PosY , FloorC ( a_PosZ ))) && ( PosY > 0 ))
2014-01-24 19:57:32 +00:00
{
PosY -- ;
}
return PosY + 1 ;
}
else
{
2015-07-29 09:04:03 -06:00
while (( PosY < cChunkDef :: Height ) && cBlockInfo :: IsSolid ( m_World -> GetBlock ( static_cast < int > ( floor ( a_PosX )), PosY , static_cast < int > ( floor ( a_PosZ )))))
2014-01-24 19:57:32 +00:00
{
PosY ++ ;
}
return PosY ;
}
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
2014-04-26 00:32:30 +02:00
bool cMonster :: DoTakeDamage ( TakeDamageInfo & a_TDI )
2012-06-14 13:06:06 +00:00
{
2020-04-13 18:38:06 +02:00
if ( ! Super :: DoTakeDamage ( a_TDI ))
2014-04-26 00:32:30 +02:00
{
return false ;
}
2014-01-24 19:57:32 +00:00
2014-05-13 14:53:15 +03:00
if ( ! m_SoundHurt . empty () && ( m_Health > 0 ))
2014-05-12 21:38:52 +03:00
{
2017-09-19 09:12:54 -05:00
m_World -> BroadcastSoundEffect ( m_SoundHurt , GetPosition (), 1.0f , 0.8f );
2014-05-12 21:38:52 +03:00
}
2014-01-24 19:57:32 +00:00
2016-02-01 22:49:34 +02:00
if (( a_TDI . Attacker != nullptr ) && a_TDI . Attacker -> IsPawn ())
2012-12-21 11:04:08 +00:00
{
2016-10-12 14:38:45 +02:00
if (
( ! a_TDI . Attacker -> IsPlayer ()) ||
( static_cast < cPlayer *> ( a_TDI . Attacker ) -> CanMobsTarget ())
)
{
SetTarget ( static_cast < cPawn *> ( a_TDI . Attacker ));
}
2015-04-29 19:24:14 +03:00
m_TicksSinceLastDamaged = 0 ;
2012-12-21 11:04:08 +00:00
}
2014-04-26 00:32:30 +02:00
return true ;
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
2020-03-05 12:52:34 +02:00
void cMonster :: DoMoveToWorld ( const cEntity :: sWorldChangeInfo & a_WorldChangeInfo )
{
// Stop all mobs from targeting this entity
// Stop this entity from targeting other mobs
SetTarget ( nullptr );
StopEveryoneFromTargetingMe ();
2020-04-13 18:38:06 +02:00
Super :: DoMoveToWorld ( a_WorldChangeInfo );
2020-03-05 12:52:34 +02:00
}
2014-07-04 10:55:09 +01:00
void cMonster :: KilledBy ( TakeDamageInfo & a_TDI )
2012-06-14 13:06:06 +00:00
{
2020-04-13 18:38:06 +02:00
Super :: KilledBy ( a_TDI );
2013-07-01 10:39:56 +00:00
if ( m_SoundHurt != "" )
{
2017-09-19 09:12:54 -05:00
m_World -> BroadcastSoundEffect ( m_SoundDeath , GetPosition (), 1.0f , 0.8f );
2013-07-01 10:39:56 +00:00
}
2013-11-25 21:43:43 +01:00
int Reward ;
2013-11-25 21:03:26 +01:00
switch ( m_MobType )
{
// Animals
2014-09-17 18:40:10 +01:00
case mtChicken :
case mtCow :
case mtHorse :
case mtPig :
2014-12-20 10:31:34 +01:00
case mtRabbit :
2014-09-17 18:40:10 +01:00
case mtSheep :
case mtSquid :
case mtMooshroom :
case mtOcelot :
case mtWolf :
2013-11-25 21:03:26 +01:00
{
2017-06-13 20:35:30 +01:00
Reward = GetRandomProvider (). RandInt ( 1 , 3 );
2013-12-14 12:50:08 +01:00
break ;
2013-11-25 21:03:26 +01:00
}
// Monsters
2014-09-17 18:40:10 +01:00
case mtCaveSpider :
case mtCreeper :
case mtEnderman :
case mtGhast :
2014-12-18 19:30:32 +01:00
case mtGuardian :
2014-09-17 18:40:10 +01:00
case mtSilverfish :
case mtSkeleton :
case mtSpider :
case mtWitch :
2020-04-04 14:44:17 +03:00
case mtWitherSkeleton :
2014-09-17 18:40:10 +01:00
case mtZombie :
case mtZombiePigman :
2018-02-03 12:24:19 +01:00
case mtZombieVillager :
2014-09-17 18:40:10 +01:00
case mtSlime :
case mtMagmaCube :
2013-11-25 21:03:26 +01:00
{
2017-06-13 20:35:30 +01:00
Reward = GetRandomProvider (). RandInt ( 6 , 8 );
2013-12-14 12:50:08 +01:00
break ;
2013-11-25 21:43:43 +01:00
}
2014-09-17 18:40:10 +01:00
case mtBlaze :
2013-11-25 21:43:43 +01:00
{
Reward = 10 ;
2013-12-14 12:50:08 +01:00
break ;
2013-11-25 21:03:26 +01:00
}
// Bosses
2014-09-17 18:40:10 +01:00
case mtEnderDragon :
2013-11-25 21:03:26 +01:00
{
2013-11-25 21:43:43 +01:00
Reward = 12000 ;
2013-12-14 12:50:08 +01:00
break ;
2013-11-25 21:03:26 +01:00
}
2014-09-17 18:40:10 +01:00
case mtWither :
2013-11-25 21:03:26 +01:00
{
2013-11-25 21:43:43 +01:00
Reward = 50 ;
2013-12-14 12:50:08 +01:00
break ;
2013-11-25 21:03:26 +01:00
}
default :
{
2013-11-25 21:43:43 +01:00
Reward = 0 ;
2013-12-14 12:50:08 +01:00
break ;
2013-11-25 21:03:26 +01:00
}
}
2014-10-20 21:55:07 +01:00
if (( a_TDI . Attacker != nullptr ) && ( ! IsBaby ()))
2014-04-10 15:50:43 +01:00
{
2018-08-02 22:59:10 +08:00
m_World -> SpawnSplitExperienceOrbs ( GetPosX (), GetPosY (), GetPosZ (), Reward );
2014-04-10 15:50:43 +01:00
}
2015-01-16 14:38:21 +00:00
m_DestroyTimer = std :: chrono :: milliseconds ( 0 );
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
2014-09-01 21:05:45 +02:00
void cMonster :: OnRightClicked ( cPlayer & a_Player )
{
2020-04-13 18:38:06 +02:00
Super :: OnRightClicked ( a_Player );
2014-09-01 21:05:45 +02: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 10:46:41 +02: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 13:26:23 +02:00
LeashTo ( a_Player );
2017-08-21 10:46:41 +02:00
}
2014-09-01 21:05:45 +02:00
}
2014-07-17 22:15:34 +02:00
// Checks to see if EventSeePlayer should be fired
// monster sez: Do I see the player
2016-01-17 16:09:25 +02:00
void cMonster :: CheckEventSeePlayer ( cChunk & a_Chunk )
2012-06-14 13:06:06 +00:00
{
2020-12-18 20:48:32 +00: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:
m_World -> ForEachEntityInBox ({ GetPosition (), m_SightDistance * 2.0 }, [ this , & TargetPlayer , & ClosestDistance , MyHeadPosition ]( cEntity & a_Entity )
{
if ( ! a_Entity . IsPlayer ())
{
// Continue iteration:
return false ;
}
const auto Player = static_cast < cPlayer *> ( & a_Entity );
if ( ! Player -> CanMobsTarget ())
{
return false ;
}
const auto TargetHeadPosition = a_Entity . GetPosition (). addedY ( a_Entity . GetHeight ());
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 )
)
{
TargetPlayer = Player ;
ClosestDistance = TargetDistance ;
}
return false ;
});
// Target him if suitable player found:
if ( TargetPlayer != nullptr )
2012-06-14 13:06:06 +00:00
{
2020-12-18 20:48:32 +00:00
EventSeePlayer ( TargetPlayer , a_Chunk );
}
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
2020-12-18 20:48:32 +00:00
void cMonster :: CheckEventLostPlayer ( const std :: chrono :: milliseconds a_Dt )
2014-07-17 22:50:58 +02:00
{
2020-12-18 20:48:32 +00: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 19:42:32 +00:00
{
2020-12-18 20:48:32 +00: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 13:06:06 +00:00
{
EventLosePlayer ();
}
2012-08-19 19:42:32 +00:00
}
else
{
2020-12-18 20:48:32 +00: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 13:06:06 +00:00
}
}
2012-08-19 19:42:32 +00:00
// What to do if player is seen
// default to change state to chasing
2016-10-12 14:38:45 +02:00
void cMonster :: EventSeePlayer ( cPlayer * a_SeenPlayer , cChunk & a_Chunk )
2012-06-14 13:06:06 +00:00
{
2016-02-01 22:49:34 +02:00
UNUSED ( a_Chunk );
2016-10-12 14:38:45 +02:00
SetTarget ( a_SeenPlayer );
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
2012-12-22 10:15:53 +00:00
void cMonster :: EventLosePlayer ( void )
2012-08-19 19:42:32 +00:00
{
2016-02-01 22:49:34 +02:00
SetTarget ( nullptr );
2020-12-18 20:48:32 +00:00
2012-06-14 13:06:06 +00:00
m_EMState = IDLE ;
2020-12-18 20:48:32 +00:00
m_LoseSightAbandonTargetTimer = std :: chrono :: seconds :: zero ();
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
2015-12-22 07:43:50 +02:00
void cMonster :: InStateIdle ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
2012-08-19 19:42:32 +00:00
{
2015-11-20 10:03:20 +02:00
if ( m_PathfinderActivated )
2014-01-25 14:42:26 +00:00
{
2014-07-17 22:15:34 +02:00
return ; // Still getting there
2014-01-25 14:42:26 +00:00
}
2013-12-20 16:39:20 +01:00
m_IdleInterval += a_Dt ;
2014-01-24 19:57:32 +00:00
2015-01-16 14:38:21 +00:00
if ( m_IdleInterval > std :: chrono :: seconds ( 1 ))
2012-08-19 19:42:32 +00:00
{
2017-06-13 20:35:30 +01:00
auto & Random = GetRandomProvider ();
2014-01-24 19:57:32 +00:00
// At this interval the results are predictable
2017-06-13 20:35:30 +01:00
int rem = Random . RandInt ( 1 , 7 );
2015-01-16 14:38:21 +00:00
m_IdleInterval -= std :: chrono :: seconds ( 1 ); // So nothing gets dropped when the server hangs for a few seconds
2014-01-24 19:57:32 +00:00
Vector3d Dist ;
2017-06-13 20:35:30 +01:00
Dist . x = static_cast < double > ( Random . RandInt ( - 5 , 5 ));
Dist . z = static_cast < double > ( Random . RandInt ( - 5 , 5 ));
2014-01-24 19:57:32 +00:00
2012-12-22 10:15:53 +00:00
if (( Dist . SqrLength () > 2 ) && ( rem >= 3 ))
2012-06-14 13:06:06 +00:00
{
2015-12-22 07:43:50 +02: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 21:58:57 +03:00
int YBelowUs = static_cast < int > ( Destination . y ) - 1 ;
if ( YBelowUs >= 0 )
2015-12-22 07:43:50 +02:00
{
2016-04-18 21:58:57 +03: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 07:43:50 +02:00
}
2012-06-14 13:06:06 +00:00
}
}
}
2012-08-19 19:42:32 +00:00
// What to do if in Chasing State
// This state should always be defined in each child class
2015-12-22 07:43:50 +02:00
void cMonster :: InStateChasing ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
2012-08-19 19:42:32 +00:00
{
UNUSED ( a_Dt );
2012-06-14 13:06:06 +00:00
}
2012-08-19 19:42:32 +00:00
// What to do if in Escaping State
2015-12-22 07:43:50 +02:00
void cMonster :: InStateEscaping ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
2012-08-19 19:42:32 +00:00
{
2012-12-22 10:15:53 +00:00
UNUSED ( a_Dt );
2015-04-29 19:24:14 +03:00
2016-02-01 22:49:34 +02:00
if ( GetTarget () != nullptr )
2012-08-19 19:42:32 +00:00
{
2013-03-22 06:33:10 +00:00
Vector3d newloc = GetPosition ();
2016-02-01 22:49:34 +02: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 13:06:06 +00:00
MoveToPosition ( newloc );
}
2012-08-19 19:42:32 +00:00
else
{
2012-12-22 10:15:53 +00:00
m_EMState = IDLE ; // This shouldnt be required but just to be safe
2012-06-14 13:06:06 +00:00
}
}
2016-01-12 12:11:10 +02:00
void cMonster :: ResetAttackCooldown ()
{
2020-10-09 22:49:25 +02: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 12:11:10 +02:00
}
2014-09-01 20:12:56 +02: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 21:55:07 +01:00
if ( m_World != nullptr )
2014-09-02 19:34:58 +02:00
{
m_World -> BroadcastEntityMetadata ( * this );
}
2014-09-01 20:12:56 +02:00
}
void cMonster :: SetCustomNameAlwaysVisible ( bool a_CustomNameAlwaysVisible )
{
m_CustomNameAlwaysVisible = a_CustomNameAlwaysVisible ;
2014-10-20 21:55:07 +01:00
if ( m_World != nullptr )
2014-09-02 19:34:58 +02:00
{
m_World -> BroadcastEntityMetadata ( * this );
}
2014-09-01 20:12:56 +02:00
}
2012-12-22 09:39:13 +00:00
void cMonster :: GetMonsterConfig ( const AString & a_Name )
2012-06-14 13:06:06 +00:00
{
2012-12-22 09:39:13 +00:00
cRoot :: Get () -> GetMonsterConfig () -> AssignAttributes ( this , a_Name );
2012-06-14 13:06:06 +00:00
}
2014-06-08 18:44:20 -07:00
bool cMonster :: IsUndead ( void )
{
return false ;
}
2014-09-17 18:40:10 +01:00
AString cMonster :: MobTypeToString ( eMonsterType a_MobType )
2013-10-20 13:25:56 +02:00
{
// Mob types aren't sorted, so we need to search linearly:
2013-12-20 16:01:34 +01:00
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ); i ++ )
2013-10-20 13:25:56 +02:00
{
if ( g_MobTypeNames [ i ]. m_Type == a_MobType )
{
return g_MobTypeNames [ i ]. m_lcName ;
}
}
2015-04-29 19:24:14 +03:00
2013-10-20 13:25:56 +02:00
// Not found:
return "" ;
}
2014-11-29 15:20:44 +01:00
AString cMonster :: MobTypeToVanillaName ( eMonsterType a_MobType )
2013-10-20 13:25:56 +02:00
{
2014-11-29 15:20:44 +01: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 13:25:56 +02:00
{
2014-11-29 15:20:44 +01:00
if ( g_MobTypeNames [ i ]. m_Type == a_MobType )
2013-10-20 13:25:56 +02:00
{
2014-11-29 15:20:44 +01:00
return g_MobTypeNames [ i ]. m_VanillaName ;
2013-10-20 13:25:56 +02:00
}
}
2014-11-29 15:20:44 +01:00
// Not found:
return "" ;
}
2017-05-22 17:41:41 +02: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 15:20:44 +01:00
eMonsterType cMonster :: StringToMobType ( const AString & a_Name )
{
AString lcName = StrToLower ( a_Name );
2015-09-25 10:14:17 +02:00
// Search Cuberite name:
2014-11-29 15:20:44 +01:00
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ); i ++ )
2013-10-20 13:25:56 +02:00
{
2014-11-29 15:20:44 +01:00
if ( strcmp ( g_MobTypeNames [ i ]. m_lcName , lcName . c_str ()) == 0 )
{
return g_MobTypeNames [ i ]. m_Type ;
}
2013-10-20 13:25:56 +02:00
}
2014-11-29 15:20:44 +01:00
// Not found. Search Vanilla name:
for ( size_t i = 0 ; i < ARRAYCOUNT ( g_MobTypeNames ); i ++ )
2013-10-20 13:25:56 +02:00
{
2014-11-29 15:20:44 +01:00
if ( strcmp ( StrToLower ( g_MobTypeNames [ i ]. m_VanillaName ). c_str (), lcName . c_str ()) == 0 )
{
return g_MobTypeNames [ i ]. m_Type ;
}
2013-10-20 13:25:56 +02:00
}
2014-11-29 15:20:44 +01:00
2017-05-22 17:41:41 +02: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 13:25:56 +02:00
// Not found:
return mtInvalidType ;
}
2014-09-17 18:40:10 +01:00
cMonster :: eFamily cMonster :: FamilyFromType ( eMonsterType a_Type )
2013-10-20 13:25:56 +02:00
{
2014-01-24 19:57:32 +00:00
// Passive-agressive mobs are counted in mob spawning code as passive
2013-10-20 14:15:55 +02:00
switch ( a_Type )
2013-10-20 13:25:56 +02:00
{
2020-11-23 00:41:13 +01: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 ;
2015-04-29 19:24:14 +03:00
2017-09-14 09:48:57 +01:00
default :
{
ASSERT ( ! "Unhandled mob type" );
return mfUnhandled ;
}
2014-04-24 18:11:11 -07:00
}
2013-10-20 13:25:56 +02:00
}
2013-10-24 16:45:13 +02:00
int cMonster :: GetSpawnDelay ( cMonster :: eFamily a_MobFamily )
2013-10-20 14:00:45 +02:00
{
switch ( a_MobFamily )
{
2014-04-24 18:11:11 -07:00
case mfHostile : return 40 ;
case mfPassive : return 40 ;
case mfAmbient : return 40 ;
case mfWater : return 400 ;
2014-04-25 20:49:55 -07:00
case mfNoSpawn : return - 1 ;
2017-09-14 09:48:57 +01:00
default :
{
ASSERT ( ! "Unhandled mob family" );
return - 1 ;
}
2013-10-20 14:00:45 +02:00
}
}
2016-02-01 22:49:34 +02:00
/** Sets the target. */
void cMonster :: SetTarget ( cPawn * a_NewTarget )
{
2016-02-07 19:07:14 +02:00
ASSERT (( a_NewTarget == nullptr ) || ( IsTicking ()));
2016-02-01 22:49:34 +02: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 19:07:14 +02:00
ASSERT ( a_NewTarget -> IsTicking ());
2016-02-01 22:49:34 +02:00
// Notify the new target that we are now targeting it.
m_Target -> TargetingMe ( this );
2017-05-21 10:29:06 +02:00
m_WasLastTargetAPlayer = m_Target -> IsPlayer ();
2016-02-01 22:49:34 +02:00
}
}
void cMonster :: UnsafeUnsetTarget ()
{
m_Target = nullptr ;
}
2016-12-19 20:12:23 +00:00
cPawn * cMonster :: GetTarget ()
2016-02-01 22:49:34 +02:00
{
return m_Target ;
}
2016-12-19 20:12:23 +00:00
std :: unique_ptr < cMonster > cMonster :: NewMonsterFromType ( eMonsterType a_MobType )
2013-10-20 13:25:56 +02:00
{
2017-06-13 20:35:30 +01:00
auto & Random = GetRandomProvider ();
2013-10-20 13:25:56 +02:00
2013-11-10 20:48:12 +00:00
// Create the mob entity
2013-10-20 13:25:56 +02:00
switch ( a_MobType )
{
case mtMagmaCube :
2014-05-02 19:17:22 +02:00
{
2020-08-01 20:18:03 +02:00
return std :: make_unique < cMagmaCube > ( 1 << Random . RandInt ( 2 )); // Size 1, 2 or 4
2014-05-02 19:17:22 +02:00
}
2013-10-20 13:25:56 +02:00
case mtSlime :
{
2020-08-01 20:18:03 +02:00
return std :: make_unique < cSlime > ( 1 << Random . RandInt ( 2 )); // Size 1, 2 or 4
2013-11-13 11:08:51 +01:00
}
2020-08-01 20:18:03 +02:00
case mtVillager : return std :: make_unique < cVillager > ( cVillager :: GetRandomProfession ());
2013-11-10 20:48:12 +00:00
case mtHorse :
{
// Horses take a type (species), a colour, and a style (dots, stripes, etc.)
2017-06-13 20:35:30 +01: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 20:48:12 +00:00
2013-11-13 11:08:51 +01:00
if (( HorseType == 5 ) || ( HorseType == 6 ) || ( HorseType == 7 ))
2013-10-20 13:25:56 +02:00
{
2013-11-13 11:08:51 +01:00
// Increase chances of normal horse (zero)
HorseType = 0 ;
2013-10-20 13:25:56 +02:00
}
2013-11-10 20:48:12 +00:00
2020-08-01 20:18:03 +02:00
return std :: make_unique < cHorse > ( HorseType , HorseColor , HorseStyle , HorseTameTimes );
2013-10-20 13:25:56 +02:00
}
2018-02-03 12:24:19 +01:00
case mtZombieVillager :
{
2020-08-01 20:18:03 +02:00
return std :: make_unique < cZombieVillager > ( cVillager :: GetRandomProfession ());
2018-02-03 12:24:19 +01:00
}
2020-08-01 20:18:03 +02: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 13:25:56 +02:00
default :
{
2013-11-10 20:48:12 +00:00
ASSERT ( ! "Unhandled mob type whilst trying to spawn mob!" );
2016-12-19 20:12:23 +00:00
return nullptr ;
2013-10-20 13:25:56 +02:00
}
}
}
2020-10-09 22:49:25 +02: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 )
{
a_Player . GetStatManager (). AddValue ( Statistic :: AnimalsBred );
if ( GetMobType () == eMonsterType :: mtCow )
{
a_Player . AwardAchievement ( Statistic :: AchBreedCow );
}
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
m_World -> BroadcastEntityStatus ( * this , esMobInLove );
}
}
// 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 19:42:32 +00:00
void cMonster :: AddRandomDropItem ( cItems & a_Drops , unsigned int a_Min , unsigned int a_Max , short a_Item , short a_ItemHealth )
2012-06-14 13:06:06 +00:00
{
2017-08-18 05:23:08 -05: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 13:06:06 +00:00
if ( Count > 0 )
{
2017-06-13 20:35:30 +01:00
a_Drops . emplace_back ( a_Item , Count , a_ItemHealth );
2012-06-14 13:06:06 +00:00
}
}
2013-09-05 22:40:08 +02:00
2014-02-23 19:44:58 +01:00
void cMonster :: AddRandomUncommonDropItem ( cItems & a_Drops , float a_Chance , short a_Item , short a_ItemHealth )
{
2017-06-13 20:35:30 +01:00
if ( GetRandomProvider (). RandBool ( a_Chance / 100.0 ))
2014-02-23 19:44:58 +01:00
{
a_Drops . push_back ( cItem ( a_Item , 1 , a_ItemHealth ));
}
}
2015-05-19 19:32:10 +01:00
void cMonster :: AddRandomRareDropItem ( cItems & a_Drops , cItems & a_Items , unsigned int a_LootingLevel )
2014-02-23 19:44:58 +01:00
{
2017-06-13 20:35:30 +01:00
auto & r1 = GetRandomProvider ();
if ( r1 . RandBool (( 5 + a_LootingLevel ) / 200.0 ))
2014-02-23 19:44:58 +01:00
{
2017-06-13 20:35:30 +01:00
size_t Rare = r1 . RandInt < size_t > ( a_Items . Size () - 1 );
2014-02-23 19:44:58 +01:00
a_Drops . push_back ( a_Items . at ( Rare ));
}
}
2015-05-19 19:32:10 +01:00
void cMonster :: AddRandomArmorDropItem ( cItems & a_Drops , unsigned int a_LootingLevel )
2014-02-23 19:44:58 +01:00
{
2017-06-13 20:35:30 +01:00
auto & r1 = GetRandomProvider ();
double LootingBonus = a_LootingLevel / 100.0 ;
if ( r1 . RandBool ( m_DropChanceHelmet + LootingBonus ))
2014-02-23 19:44:58 +01:00
{
2014-12-05 16:59:11 +01:00
if ( ! GetEquippedHelmet (). IsEmpty ())
{
a_Drops . push_back ( GetEquippedHelmet ());
}
2014-02-23 19:44:58 +01:00
}
2015-04-29 19:24:14 +03:00
2017-06-13 20:35:30 +01:00
if ( r1 . RandBool ( m_DropChanceChestplate + LootingBonus ))
2014-02-23 19:44:58 +01:00
{
2014-12-05 16:59:11 +01:00
if ( ! GetEquippedChestplate (). IsEmpty ())
{
a_Drops . push_back ( GetEquippedChestplate ());
}
2014-02-23 19:44:58 +01:00
}
2015-04-29 19:24:14 +03:00
2017-06-13 20:35:30 +01:00
if ( r1 . RandBool ( m_DropChanceLeggings + LootingBonus ))
2014-02-23 19:44:58 +01:00
{
2014-12-05 16:59:11 +01:00
if ( ! GetEquippedLeggings (). IsEmpty ())
{
a_Drops . push_back ( GetEquippedLeggings ());
}
2014-02-23 19:44:58 +01:00
}
2015-04-29 19:24:14 +03:00
2017-06-13 20:35:30 +01:00
if ( r1 . RandBool ( m_DropChanceBoots + LootingBonus ))
2014-02-23 19:44:58 +01:00
{
2014-12-05 16:59:11 +01:00
if ( ! GetEquippedBoots (). IsEmpty ())
{
a_Drops . push_back ( GetEquippedBoots ());
}
2014-02-23 19:44:58 +01:00
}
}
2015-05-19 19:32:10 +01:00
void cMonster :: AddRandomWeaponDropItem ( cItems & a_Drops , unsigned int a_LootingLevel )
2014-02-23 19:44:58 +01:00
{
2017-06-13 20:35:30 +01:00
if ( GetRandomProvider (). RandBool ( m_DropChanceWeapon + ( a_LootingLevel / 100.0 )))
2014-02-23 19:44:58 +01:00
{
2014-12-05 16:59:11 +01:00
if ( ! GetEquippedWeapon (). IsEmpty ())
{
a_Drops . push_back ( GetEquippedWeapon ());
}
2014-02-23 19:44:58 +01:00
}
}
2015-05-01 20:34:24 +03:00
void cMonster :: HandleDaylightBurning ( cChunk & a_Chunk , bool WouldBurn )
2013-09-05 22:40:08 +02:00
{
if ( ! m_BurnsInDaylight )
{
return ;
}
2015-04-29 19:24:14 +03:00
2014-04-17 18:50:25 +01:00
int RelY = POSY_TOINT ;
2013-09-05 22:40:08 +02:00
if (( RelY < 0 ) || ( RelY >= cChunkDef :: Height ))
{
// Outside the world
return ;
}
2014-01-24 19:57:32 +00:00
if ( ! a_Chunk . IsLightValid ())
{
m_World -> QueueLightChunk ( GetChunkX (), GetChunkZ ());
return ;
}
2015-05-01 20:34:24 +03:00
if ( ! IsOnFire () && WouldBurn )
2015-04-29 19:24:14 +03:00
{
// Burn for 100 ticks, then decide again
StartBurning ( 100 );
}
}
2018-07-26 22:24:36 +01:00
2015-04-29 19:24:14 +03:00
bool cMonster :: WouldBurnAt ( Vector3d a_Location , cChunk & a_Chunk )
{
2015-12-30 01:15:13 +01: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 16:34:41 +03:00
if ( RelY <= 0 )
2015-05-06 17:02:50 +03:00
{
2016-03-29 16:34:41 +03:00
// The mob is about to die, no point in burning
2015-05-06 17:02:50 +03:00
return false ;
}
2015-05-09 01:51:25 +01:00
2016-03-29 16:34:41 +03:00
PREPARE_REL_AND_CHUNK ( a_Location , a_Chunk );
if ( ! RelSuccess )
{
return false ;
}
2015-05-09 01:51:25 +01:00
2013-09-05 22:40:08 +02:00
if (
2016-03-29 16:34:41 +03:00
( Chunk -> GetBlock ( Rel . x , Rel . y , Rel . z ) != E_BLOCK_SOULSAND ) && // Not on soulsand
2015-12-30 01:15:13 +01:00
( GetWorld () -> GetTimeOfDay () < 12000 + 1000 ) && // Daytime
2018-01-15 23:44:48 +01:00
GetWorld () -> IsWeatherSunnyAt ( POSX_TOINT , POSZ_TOINT ) && // Not raining
! IsInWater () // Isn't swimming
2013-09-05 22:40:08 +02:00
)
{
2017-08-25 16:26:45 +01:00
int MobHeight = CeilC ( a_Location . y + GetHeight ()) - 1 ; // The block Y coord of the mob's head
2016-03-29 16:34:41 +03:00
if ( MobHeight >= cChunkDef :: Height )
{
return true ;
}
2017-08-25 16:26:45 +01:00
// Start with the highest block and scan down to just above the mob's head.
2016-03-29 16:34:41 +03: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 16:26:45 +01:00
while ( CurrentBlock > MobHeight )
2016-03-29 16:34:41 +03: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 19:24:14 +03:00
return true ;
2016-03-29 16:34:41 +03:00
2013-09-05 22:40:08 +02:00
}
2015-04-29 19:24:14 +03:00
return false ;
2013-09-05 22:40:08 +02:00
}
2015-04-29 19:24:14 +03:00
2013-09-10 15:09:45 +02:00
cMonster :: eFamily cMonster :: GetMobFamily ( void ) const
{
2013-10-20 13:25:56 +02:00
return FamilyFromType ( m_MobType );
2013-09-10 15:09:45 +02:00
}
2017-08-21 10:46:41 +02:00
2017-08-24 13:26:23 +02:00
void cMonster :: LeashTo ( cEntity & a_Entity , bool a_ShouldBroadcast )
2017-08-21 10:46:41 +02:00
{
// Do nothing if already leashed
if ( m_LeashedTo != nullptr )
{
return ;
}
2017-08-24 13:26:23 +02:00
m_LeashedTo = & a_Entity ;
2017-08-21 10:46:41 +02:00
2017-08-24 13:26:23 +02:00
a_Entity . AddLeashedMob ( this );
2017-08-21 10:46:41 +02:00
if ( a_ShouldBroadcast )
{
2017-08-24 13:26:23 +02:00
m_World -> BroadcastLeashEntity ( * this , a_Entity );
2017-08-21 10:46:41 +02: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 );
}