2014-04-24 22:03:47 +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
2012-09-23 22:09:57 +00:00
#include "Entity.h"
2013-08-19 11:39:13 +02:00
#include "../World.h"
#include "../Root.h"
2014-03-11 18:32:33 +02:00
#include "../Matrix4.h"
2013-08-19 11:39:13 +02:00
#include "../ClientHandle.h"
#include "../Chunk.h"
#include "../Simulator/FluidSimulator.h"
2013-12-08 12:17:54 +01:00
#include "../Bindings/PluginManager.h"
2017-05-11 14:34:36 +02:00
#include "../LineBlockTracer.h"
2013-12-22 20:04:17 +00:00
#include "Player.h"
2014-07-23 16:32:09 +02:00
#include "Items/ItemHandler.h"
2014-08-21 12:08:38 +02:00
#include "../FastRandom.h"
2015-05-26 20:35:28 -05:00
#include "../NetherPortalScanner.h"
2012-06-14 13:06:06 +00:00
2017-06-15 17:06:50 +02:00
static UInt32 GetNextUniqueID ( void )
{
static std :: atomic < UInt32 > counter ( 1 );
return counter . fetch_add ( 1 );
}
2012-06-14 13:06:06 +00:00
2017-06-15 17:06:50 +02:00
////////////////////////////////////////////////////////////////////////////////
// cEntity:
2012-06-14 13:06:06 +00:00
2015-03-21 15:18:17 +01:00
cEntity :: cEntity ( eEntityType a_EntityType , double a_X , double a_Y , double a_Z , double a_Width , double a_Height ) :
2017-06-15 17:06:50 +02:00
m_UniqueID ( GetNextUniqueID ()),
2015-03-21 15:18:17 +01:00
m_Health ( 1 ),
m_MaxHealth ( 1 ),
m_AttachedTo ( nullptr ),
m_Attachee ( nullptr ),
m_bDirtyHead ( true ),
m_bDirtyOrientation ( true ),
m_bHasSentNoSpeed ( true ),
m_bOnGround ( false ),
m_Gravity ( - 9.81f ),
2015-03-31 11:03:35 -04:00
m_AirDrag ( 0.02f ),
2015-07-21 21:25:37 +01:00
m_LastPosition ( a_X , a_Y , a_Z ),
2015-03-21 15:18:17 +01:00
m_EntityType ( a_EntityType ),
m_World ( nullptr ),
2015-05-26 20:35:28 -05:00
m_IsWorldChangeScheduled ( false ),
2015-03-21 15:18:17 +01:00
m_IsFireproof ( false ),
m_TicksSinceLastBurnDamage ( 0 ),
m_TicksSinceLastLavaDamage ( 0 ),
m_TicksSinceLastFireDamage ( 0 ),
m_TicksLeftBurning ( 0 ),
m_TicksSinceLastVoidDamage ( 0 ),
m_IsSwimming ( false ),
m_IsSubmerged ( false ),
m_AirLevel ( 0 ),
m_AirTickTimer ( 0 ),
m_TicksAlive ( 0 ),
2016-02-07 19:07:14 +02:00
m_IsTicking ( false ),
m_ParentChunk ( nullptr ),
2015-03-21 15:18:17 +01:00
m_HeadYaw ( 0.0 ),
m_Rot ( 0.0 , 0.0 , 0.0 ),
2015-07-21 21:25:37 +01:00
m_Position ( a_X , a_Y , a_Z ),
m_LastSentPosition ( a_X , a_Y , a_Z ),
2015-03-21 15:18:17 +01:00
m_WaterSpeed ( 0 , 0 , 0 ),
m_Mass ( 0.001 ), // Default 1g
m_Width ( a_Width ),
m_Height ( a_Height ),
m_InvulnerableTicks ( 0 )
2012-06-14 13:06:06 +00:00
{
}
cEntity ::~ cEntity ()
{
2016-02-07 19:07:14 +02:00
2014-01-24 09:57:12 +01:00
// Before deleting, the entity needs to have been removed from the world, if ever added
2014-10-20 21:55:07 +01:00
ASSERT (( m_World == nullptr ) || ! m_World -> HasEntity ( m_UniqueID ));
2016-02-07 19:07:14 +02:00
ASSERT ( ! IsTicking ());
2016-01-16 14:16:47 +02:00
2014-01-16 20:31:06 +01:00
/*
// DEBUG:
2014-07-17 22:50:58 +02:00
LOGD("Deleting entity %d at pos {%.2f, %.2f, %.2f} ~ [%d, %d]; ptr %p",
2012-06-14 13:06:06 +00:00
m_UniqueID,
2013-03-02 19:57:09 +00:00
m_Pos.x, m_Pos.y, m_Pos.z,
2012-06-14 13:06:06 +00:00
(int)(m_Pos.x / cChunkDef::Width), (int)(m_Pos.z / cChunkDef::Width),
this
2013-04-02 06:48:31 +00:00
);
2014-01-16 20:31:06 +01:00
*/
2016-01-16 14:16:47 +02:00
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo != nullptr )
2013-03-03 19:05:11 +00:00
{
Detach ();
}
2014-10-20 21:55:07 +01:00
if ( m_Attachee != nullptr )
2013-03-03 19:05:11 +00:00
{
m_Attachee -> Detach ();
}
2012-06-14 13:06:06 +00:00
}
2013-02-02 23:55:29 +00:00
const char * cEntity :: GetClass ( void ) const
{
return "cEntity" ;
}
const char * cEntity :: GetClassStatic ( void )
{
return "cEntity" ;
}
2012-08-24 07:58:26 +00:00
2012-12-21 12:21:20 +00:00
const char * cEntity :: GetParentClass ( void ) const
{
return "" ;
}
2016-12-19 20:12:23 +00:00
bool cEntity :: Initialize ( OwnedEntity a_Self , cWorld & a_EntityWorld )
2012-06-14 13:06:06 +00:00
{
2016-12-19 20:12:23 +00:00
if ( cPluginManager :: Get () -> CallHookSpawningEntity ( a_EntityWorld , * this ))
2013-08-08 09:13:13 +02:00
{
return false ;
}
2016-01-16 14:16:47 +02:00
2014-01-16 20:31:06 +01:00
/*
// DEBUG:
2013-04-13 21:02:10 +00:00
LOGD("Initializing entity #%d (%s) at {%.02f, %.02f, %.02f}",
m_UniqueID, GetClass(), m_Pos.x, m_Pos.y, m_Pos.z
);
2014-01-16 20:31:06 +01:00
*/
2016-01-16 14:16:47 +02:00
2016-02-07 19:07:14 +02:00
ASSERT ( m_World == nullptr );
ASSERT ( GetParentChunk () == nullptr );
2016-12-19 20:12:23 +00:00
a_EntityWorld . AddEntity ( std :: move ( a_Self ));
2016-02-07 19:07:14 +02:00
ASSERT ( m_World != nullptr );
2016-01-16 14:16:47 +02:00
2016-12-19 20:12:23 +00:00
cPluginManager :: Get () -> CallHookSpawnedEntity ( a_EntityWorld , * this );
2016-01-16 14:16:47 +02:00
2013-08-25 21:25:13 +02:00
// Spawn the entity on the clients:
2016-12-19 20:12:23 +00:00
a_EntityWorld . BroadcastSpawnEntity ( * this );
2016-01-16 14:16:47 +02:00
2013-08-08 09:13:13 +02:00
return true ;
2012-06-14 13:06:06 +00:00
}
2013-04-13 21:02:10 +00:00
void cEntity :: WrapHeadYaw ( void )
2013-04-02 06:48:31 +00:00
{
2013-12-08 13:08:56 +01:00
m_HeadYaw = NormalizeAngleDegrees ( m_HeadYaw );
2013-04-02 06:48:31 +00:00
}
2013-04-13 21:02:10 +00:00
void cEntity :: WrapRotation ( void )
2012-06-14 13:06:06 +00:00
{
2013-12-08 13:08:56 +01:00
m_Rot . x = NormalizeAngleDegrees ( m_Rot . x );
2013-12-08 18:54:04 +01:00
m_Rot . y = NormalizeAngleDegrees ( m_Rot . y );
2012-06-14 13:06:06 +00:00
}
2013-04-13 21:02:10 +00:00
void cEntity :: WrapSpeed ( void )
2013-03-22 06:33:10 +00:00
{
2014-06-16 15:12:50 +01:00
m_Speed . x = Clamp ( m_Speed . x , - 78.0 , 78.0 );
m_Speed . y = Clamp ( m_Speed . y , - 78.0 , 78.0 );
m_Speed . z = Clamp ( m_Speed . z , - 78.0 , 78.0 );
2013-03-22 06:33:10 +00:00
}
2012-06-14 13:06:06 +00:00
2016-02-07 19:07:14 +02:00
void cEntity :: SetParentChunk ( cChunk * a_Chunk )
{
m_ParentChunk = a_Chunk ;
}
cChunk * cEntity :: GetParentChunk ()
{
return m_ParentChunk ;
}
2013-06-25 06:36:59 +00:00
void cEntity :: Destroy ( bool a_ShouldBroadcast )
2012-06-14 13:06:06 +00:00
{
2016-02-07 19:07:14 +02:00
ASSERT ( IsTicking ());
ASSERT ( GetParentChunk () != nullptr );
SetIsTicking ( false );
if ( a_ShouldBroadcast )
2012-06-14 13:06:06 +00:00
{
2016-02-07 19:07:14 +02:00
m_World -> BroadcastDestroyEntity ( * this );
2012-06-14 13:06:06 +00:00
}
2016-01-16 14:16:47 +02:00
2016-02-07 19:07:14 +02:00
cChunk * ParentChunk = GetParentChunk ();
m_World -> QueueTask ([ this , ParentChunk ]( cWorld & a_World )
{
LOGD ( "Destroying entity #%i (%s) from chunk (%d, %d)" ,
this -> GetUniqueID (), this -> GetClass (),
ParentChunk -> GetPosX (), ParentChunk -> GetPosZ ()
);
2016-12-19 20:12:23 +00:00
// Make sure that RemoveEntity returned a valid smart pointer
// Also, not storing the returned pointer means automatic destruction
VERIFY ( ParentChunk -> RemoveEntity ( * this ));
2016-02-07 19:07:14 +02:00
});
Destroyed ();
}
void cEntity :: DestroyNoScheduling ( bool a_ShouldBroadcast )
{
SetIsTicking ( false );
2013-06-25 06:36:59 +00:00
if ( a_ShouldBroadcast )
{
m_World -> BroadcastDestroyEntity ( * this );
}
2013-04-02 06:48:31 +00:00
2012-06-14 13:06:06 +00:00
Destroyed ();
}
2016-02-07 19:07:14 +02:00
2013-07-01 10:39:56 +00:00
void cEntity :: TakeDamage ( cEntity & a_Attacker )
{
int RawDamage = a_Attacker . GetRawDamageAgainst ( * this );
TakeDamage ( dtAttack , & a_Attacker , RawDamage , a_Attacker . GetKnockbackAmountAgainst ( * this ));
}
void cEntity :: TakeDamage ( eDamageType a_DamageType , cEntity * a_Attacker , int a_RawDamage , double a_KnockbackAmount )
{
2017-07-23 04:46:38 -05:00
int ArmorCover = GetArmorCoverAgainst ( a_Attacker , a_DamageType , a_RawDamage );
int EnchantmentCover = GetEnchantmentCoverAgainst ( a_Attacker , a_DamageType , a_RawDamage );
int FinalDamage = a_RawDamage - ArmorCover - EnchantmentCover ;
if (( FinalDamage == 0 ) && ( a_RawDamage > 0 ))
{
// Nobody's invincible
FinalDamage = 1 ;
}
ApplyArmorDamage ( ArmorCover );
2013-07-01 10:39:56 +00:00
cEntity :: TakeDamage ( a_DamageType , a_Attacker , a_RawDamage , FinalDamage , a_KnockbackAmount );
}
2016-01-22 20:55:46 +02:00
void cEntity :: TakeDamage ( eDamageType a_DamageType , UInt32 a_AttackerID , int a_RawDamage , double a_KnockbackAmount )
{
2017-07-23 04:46:38 -05:00
class cFindEntity : public cEntityCallback
2016-01-22 20:55:46 +02:00
{
public :
cEntity * m_Entity ;
eDamageType m_DamageType ;
int m_RawDamage ;
double m_KnockbackAmount ;
virtual bool Item ( cEntity * a_Attacker ) override
{
cPawn * Attacker ;
if ( a_Attacker -> IsPawn ())
{
Attacker = static_cast < cPawn *> ( a_Attacker );
}
else
{
Attacker = nullptr ;
}
2017-07-23 04:46:38 -05:00
m_Entity -> TakeDamage ( m_DamageType , Attacker , m_RawDamage , m_KnockbackAmount );
2016-01-22 20:55:46 +02:00
return true ;
}
} Callback ;
Callback . m_Entity = this ;
Callback . m_DamageType = a_DamageType ;
Callback . m_RawDamage = a_RawDamage ;
Callback . m_KnockbackAmount = a_KnockbackAmount ;
m_World -> DoWithEntityByID ( a_AttackerID , Callback );
}
2013-07-01 10:39:56 +00:00
void cEntity :: TakeDamage ( eDamageType a_DamageType , cEntity * a_Attacker , int a_RawDamage , int a_FinalDamage , double a_KnockbackAmount )
{
TakeDamageInfo TDI ;
TDI . DamageType = a_DamageType ;
2016-01-16 14:16:47 +02:00
if (( a_Attacker != nullptr ) && a_Attacker -> IsPawn ())
{
TDI . Attacker = a_Attacker ;
}
else
{
TDI . Attacker = nullptr ;
}
2013-07-01 10:39:56 +00:00
TDI . RawDamage = a_RawDamage ;
TDI . FinalDamage = a_FinalDamage ;
2016-01-16 14:16:47 +02:00
2013-12-22 20:04:17 +00:00
Vector3d Heading ( 0 , 0 , 0 );
2014-10-20 21:55:07 +01:00
if ( a_Attacker != nullptr )
2013-12-22 20:04:17 +00:00
{
2017-08-13 06:40:23 -05:00
Heading = a_Attacker -> GetLookVector ();
2013-12-22 20:04:17 +00:00
}
2013-07-01 10:39:56 +00:00
TDI . Knockback = Heading * a_KnockbackAmount ;
DoTakeDamage ( TDI );
}
2014-01-16 19:00:49 +00:00
void cEntity :: SetYawFromSpeed ( void )
2013-09-07 17:14:37 +02:00
{
const double EPS = 0.0000001 ;
2014-09-22 03:22:36 -04:00
if (( std :: abs ( m_Speed . x ) < EPS ) && ( std :: abs ( m_Speed . z ) < EPS ))
2013-09-07 17:14:37 +02:00
{
// atan2() may overflow or is undefined, pick any number
2014-01-16 19:00:49 +00:00
SetYaw ( 0 );
2013-09-07 17:14:37 +02:00
return ;
}
2014-10-20 21:55:07 +01:00
SetYaw ( atan2 ( m_Speed . x , m_Speed . z ) * 180 / M_PI );
2013-09-07 17:14:37 +02:00
}
void cEntity :: SetPitchFromSpeed ( void )
{
const double EPS = 0.0000001 ;
2014-07-17 22:15:34 +02:00
double xz = sqrt ( m_Speed . x * m_Speed . x + m_Speed . z * m_Speed . z ); // Speed XZ-plane component
2014-09-22 03:22:36 -04:00
if (( std :: abs ( xz ) < EPS ) && ( std :: abs ( m_Speed . y ) < EPS ))
2013-09-07 17:14:37 +02:00
{
// atan2() may overflow or is undefined, pick any number
SetPitch ( 0 );
return ;
}
2014-10-20 21:55:07 +01:00
SetPitch ( atan2 ( m_Speed . y , xz ) * 180 / M_PI );
2013-09-07 17:14:37 +02:00
}
2014-04-26 00:32:30 +02:00
bool cEntity :: DoTakeDamage ( TakeDamageInfo & a_TDI )
2013-07-01 10:39:56 +00:00
{
if ( m_Health <= 0 )
{
// Can't take damage if already dead
2014-04-26 00:32:30 +02:00
return false ;
}
if ( m_InvulnerableTicks > 0 )
{
// Entity is invulnerable
return false ;
2013-07-01 10:39:56 +00:00
}
2014-07-23 16:32:09 +02:00
if ( cRoot :: Get () -> GetPluginManager () -> CallHookTakeDamage ( * this , a_TDI ))
{
return false ;
}
2014-10-20 21:55:07 +01:00
if (( a_TDI . Attacker != nullptr ) && ( a_TDI . Attacker -> IsPlayer ()))
2013-12-22 20:04:17 +00:00
{
2015-07-29 09:04:03 -06:00
cPlayer * Player = reinterpret_cast < cPlayer *> ( a_TDI . Attacker );
2014-05-12 21:38:52 +03:00
2014-07-23 16:32:09 +02:00
Player -> GetEquippedItem (). GetHandler () -> OnEntityAttack ( Player , this );
2013-12-23 09:41:45 +00:00
// IsOnGround() only is false if the player is moving downwards
2014-06-07 19:58:41 -07:00
// TODO: Better damage increase, and check for enchantments (and use magic critical instead of plain)
2014-09-01 14:31:05 +02:00
const cEnchantments & Enchantments = Player -> GetEquippedItem (). m_Enchantments ;
2016-01-16 14:16:47 +02:00
2015-07-29 09:04:03 -06:00
int SharpnessLevel = static_cast < int > ( Enchantments . GetLevel ( cEnchantments :: enchSharpness ));
int SmiteLevel = static_cast < int > ( Enchantments . GetLevel ( cEnchantments :: enchSmite ));
int BaneOfArthropodsLevel = static_cast < int > ( Enchantments . GetLevel ( cEnchantments :: enchBaneOfArthropods ));
2014-08-19 12:38:15 +02:00
if ( SharpnessLevel > 0 )
{
2015-07-29 09:04:03 -06:00
a_TDI . FinalDamage += static_cast < int > ( ceil ( 1.25 * SharpnessLevel ));
2014-08-19 12:38:15 +02:00
}
else if ( SmiteLevel > 0 )
{
if ( IsMob ())
{
2015-07-29 09:04:03 -06:00
cMonster * Monster = reinterpret_cast < cMonster *> ( this );
2014-08-19 12:38:15 +02:00
switch ( Monster -> GetMobType ())
{
2014-09-17 18:40:10 +01:00
case mtSkeleton :
case mtZombie :
case mtWither :
case mtZombiePigman :
2014-08-19 12:38:15 +02:00
{
2015-07-29 09:04:03 -06:00
a_TDI . FinalDamage += static_cast < int > ( ceil ( 2.5 * SmiteLevel ));
2014-08-19 12:38:15 +02:00
break ;
}
2014-09-22 18:23:56 -04:00
default : break ;
2014-08-19 12:38:15 +02:00
}
}
}
else if ( BaneOfArthropodsLevel > 0 )
2013-12-22 20:04:17 +00:00
{
2014-08-19 12:38:15 +02:00
if ( IsMob ())
{
2015-07-29 09:04:03 -06:00
cMonster * Monster = reinterpret_cast < cMonster *> ( this );
2014-08-19 12:38:15 +02:00
switch ( Monster -> GetMobType ())
{
2014-09-17 18:40:10 +01:00
case mtSpider :
case mtCaveSpider :
case mtSilverfish :
2014-08-19 12:38:15 +02:00
{
2015-07-29 09:04:03 -06:00
a_TDI . RawDamage += static_cast < int > ( ceil ( 2.5 * BaneOfArthropodsLevel ));
2014-08-22 11:49:49 +02:00
// TODO: Add slowness effect
2016-01-16 14:16:47 +02:00
2014-08-19 12:38:15 +02:00
break ;
2014-08-22 11:49:49 +02:00
};
default : break ;
2014-08-19 12:38:15 +02:00
}
}
}
2015-07-29 09:04:03 -06:00
int FireAspectLevel = static_cast < int > ( Enchantments . GetLevel ( cEnchantments :: enchFireAspect ));
2014-08-19 12:38:15 +02:00
if ( FireAspectLevel > 0 )
{
int BurnTicks = 3 ;
if ( FireAspectLevel > 1 )
{
BurnTicks += 4 * ( FireAspectLevel - 1 );
}
2014-08-19 16:08:17 +02:00
if ( ! IsMob () && ! IsSubmerged () && ! IsSwimming ())
{
StartBurning ( BurnTicks * 20 );
}
else if ( IsMob () && ! IsSubmerged () && ! IsSwimming ())
{
2015-07-29 09:04:03 -06:00
cMonster * Monster = reinterpret_cast < cMonster *> ( this );
2014-08-19 16:08:17 +02:00
switch ( Monster -> GetMobType ())
{
2014-09-17 18:40:10 +01:00
case mtGhast :
case mtZombiePigman :
case mtMagmaCube :
2014-09-03 10:29:10 +02:00
{
2014-08-19 16:08:17 +02:00
break ;
};
2014-08-19 16:47:33 +02:00
default : StartBurning ( BurnTicks * 20 );
2014-08-19 16:08:17 +02:00
}
}
2014-08-22 11:49:49 +02:00
}
2014-08-19 12:38:15 +02:00
2015-05-19 19:32:10 +01:00
unsigned int ThornsLevel = 0 ;
2014-09-01 14:35:52 +02:00
const cItem ArmorItems [] = { GetEquippedHelmet (), GetEquippedChestplate (), GetEquippedLeggings (), GetEquippedBoots () };
2014-08-22 11:49:49 +02:00
for ( size_t i = 0 ; i < ARRAYCOUNT ( ArmorItems ); i ++ )
{
2014-09-01 14:35:52 +02:00
const cItem & Item = ArmorItems [ i ];
ThornsLevel = std :: max ( ThornsLevel , Item . m_Enchantments . GetLevel ( cEnchantments :: enchThorns ));
2014-08-22 11:49:49 +02:00
}
2016-01-16 14:16:47 +02:00
2014-08-22 11:49:49 +02:00
if ( ThornsLevel > 0 )
{
2015-07-29 09:04:03 -06:00
int Chance = static_cast < int > ( ThornsLevel * 15 );
2014-08-22 11:49:49 +02:00
2017-06-13 20:35:30 +01:00
auto & Random = GetRandomProvider ();
2014-08-22 11:49:49 +02:00
2017-06-13 20:35:30 +01:00
if ( Random . RandBool ( Chance / 100.0 ))
2014-08-22 11:49:49 +02:00
{
2017-06-13 20:35:30 +01:00
a_TDI . Attacker -> TakeDamage ( dtAttack , this , 0 , Random . RandInt ( 1 , 4 ), 0 );
2014-08-22 11:49:49 +02:00
}
2014-08-19 12:38:15 +02:00
}
2016-01-16 14:16:47 +02:00
2014-08-19 12:38:15 +02:00
if ( ! Player -> IsOnGround ())
2014-09-03 10:29:10 +02:00
{
2014-07-14 13:46:15 -07:00
if (( a_TDI . DamageType == dtAttack ) || ( a_TDI . DamageType == dtArrowAttack ))
{
a_TDI . FinalDamage += 2 ;
2014-07-17 22:15:34 +02:00
m_World -> BroadcastEntityAnimation ( * this , 4 ); // Critical hit
2014-07-14 13:46:15 -07:00
}
2013-12-22 20:04:17 +00:00
}
2014-05-12 21:38:52 +03:00
2015-07-29 09:04:03 -06:00
Player -> GetStatManager (). AddValue ( statDamageDealt , static_cast < StatValue > ( floor ( a_TDI . FinalDamage * 10 + 0.5 )));
2013-12-22 20:04:17 +00:00
}
2014-08-22 11:49:49 +02:00
2015-07-29 09:04:03 -06:00
m_Health -= static_cast < short > ( a_TDI . FinalDamage );
2016-01-16 14:16:47 +02:00
2014-07-19 01:40:29 -07:00
m_Health = std :: max ( m_Health , 0 );
2013-07-01 10:39:56 +00:00
2014-09-01 14:31:05 +02:00
// Add knockback:
2014-10-20 21:55:07 +01:00
if (( IsMob () || IsPlayer ()) && ( a_TDI . Attacker != nullptr ))
2014-01-12 13:28:37 +00:00
{
2017-08-13 06:40:23 -05:00
AddSpeed ( a_TDI . Knockback );
2014-01-12 13:28:37 +00:00
}
2013-12-22 20:04:17 +00:00
2014-04-12 13:16:48 +01:00
m_World -> BroadcastEntityStatus ( * this , esGenericHurt );
2013-07-01 10:39:56 +00:00
2014-04-26 00:32:30 +02:00
m_InvulnerableTicks = 10 ;
2013-07-01 10:39:56 +00:00
if ( m_Health <= 0 )
{
2014-07-04 10:55:09 +01:00
KilledBy ( a_TDI );
2014-05-12 17:05:09 +03:00
2014-10-20 21:55:07 +01:00
if ( a_TDI . Attacker != nullptr )
2014-05-12 17:05:09 +03:00
{
a_TDI . Attacker -> Killed ( this );
}
2013-07-01 10:39:56 +00:00
}
2014-04-26 00:32:30 +02:00
return true ;
2013-07-01 10:39:56 +00:00
}
int cEntity :: GetRawDamageAgainst ( const cEntity & a_Receiver )
{
// Returns the hitpoints that this pawn can deal to a_Receiver using its equipped items
2016-12-19 18:08:19 +02:00
// Ref: http://minecraft.gamepedia.com/Damage#Dealing_damage as of 2012_12_20
2013-07-01 10:39:56 +00:00
switch ( this -> GetEquippedWeapon (). m_ItemType )
{
case E_ITEM_WOODEN_SWORD : return 4 ;
case E_ITEM_GOLD_SWORD : return 4 ;
case E_ITEM_STONE_SWORD : return 5 ;
case E_ITEM_IRON_SWORD : return 6 ;
case E_ITEM_DIAMOND_SWORD : return 7 ;
case E_ITEM_WOODEN_AXE : return 3 ;
case E_ITEM_GOLD_AXE : return 3 ;
case E_ITEM_STONE_AXE : return 4 ;
case E_ITEM_IRON_AXE : return 5 ;
case E_ITEM_DIAMOND_AXE : return 6 ;
case E_ITEM_WOODEN_PICKAXE : return 2 ;
case E_ITEM_GOLD_PICKAXE : return 2 ;
case E_ITEM_STONE_PICKAXE : return 3 ;
case E_ITEM_IRON_PICKAXE : return 4 ;
case E_ITEM_DIAMOND_PICKAXE : return 5 ;
case E_ITEM_WOODEN_SHOVEL : return 1 ;
case E_ITEM_GOLD_SHOVEL : return 1 ;
case E_ITEM_STONE_SHOVEL : return 2 ;
case E_ITEM_IRON_SHOVEL : return 3 ;
case E_ITEM_DIAMOND_SHOVEL : return 4 ;
}
// All other equipped items give a damage of 1:
return 1 ;
}
2017-07-23 04:46:38 -05:00
void cEntity :: ApplyArmorDamage ( int DamageBlocked )
{
// cEntities don't necessarily have armor to damage.
return ;
}
2013-07-01 10:39:56 +00:00
2014-04-25 19:49:08 -07:00
bool cEntity :: ArmorCoversAgainst ( eDamageType a_DamageType )
2013-07-01 10:39:56 +00:00
{
2016-12-19 18:08:19 +02:00
// Ref.: http://minecraft.gamepedia.com/Armor#Effects as of 2012_12_20
2013-07-01 10:39:56 +00:00
switch ( a_DamageType )
{
case dtOnFire :
case dtSuffocating :
case dtDrowning : // TODO: This one could be a special case - in various MC versions (PC vs XBox) it is and isn't armor-protected
2017-07-23 04:46:38 -05:00
case dtEnderPearl :
2013-07-01 10:39:56 +00:00
case dtStarving :
case dtInVoid :
case dtPoisoning :
2014-06-08 21:51:55 -07:00
case dtWithering :
2013-07-01 10:39:56 +00:00
case dtPotionOfHarming :
case dtFalling :
case dtLightning :
2014-02-02 12:47:17 +00:00
case dtPlugin :
2013-07-01 10:39:56 +00:00
{
2014-04-25 19:49:08 -07:00
return false ;
}
2016-01-16 14:16:47 +02:00
2014-04-25 19:49:08 -07:00
case dtAttack :
case dtArrowAttack :
case dtCactusContact :
case dtLavaContact :
case dtFireContact :
case dtExplosion :
{
return true ;
2013-07-01 10:39:56 +00:00
}
}
2014-04-26 09:21:49 -07:00
ASSERT ( ! "Invalid damage type!" );
2016-08-02 13:12:34 +02:00
#ifndef __clang__
return false ;
#endif
2014-04-25 19:49:08 -07:00
}
2017-07-23 04:46:38 -05:00
int cEntity :: GetEnchantmentCoverAgainst ( const cEntity * a_Attacker , eDamageType a_DamageType , int a_Damage )
{
int TotalEPF = 0.0 ;
const cItem ArmorItems [] = { GetEquippedHelmet (), GetEquippedChestplate (), GetEquippedLeggings (), GetEquippedBoots () };
for ( size_t i = 0 ; i < ARRAYCOUNT ( ArmorItems ); i ++ )
{
const cItem & Item = ArmorItems [ i ];
if (( a_DamageType != dtInVoid ) && ( a_DamageType != dtAdmin ) && ( a_DamageType != dtStarving ))
{
TotalEPF += static_cast < int > ( Item . m_Enchantments . GetLevel ( cEnchantments :: enchProtection )) * 1 ;
}
if (( a_DamageType == dtBurning ) || ( a_DamageType == dtFireContact ) || ( a_DamageType == dtLavaContact ))
{
TotalEPF += static_cast < int > ( Item . m_Enchantments . GetLevel ( cEnchantments :: enchFireProtection )) * 2 ;
}
if (( a_DamageType == dtFalling ) || ( a_DamageType == dtEnderPearl ))
{
TotalEPF += static_cast < int > ( Item . m_Enchantments . GetLevel ( cEnchantments :: enchFeatherFalling )) * 3 ;
}
if ( a_DamageType == dtExplosion )
{
TotalEPF += static_cast < int > ( Item . m_Enchantments . GetLevel ( cEnchantments :: enchBlastProtection )) * 2 ;
}
// Note: Also blocks against fire charges, etc.
if ( a_DamageType == dtProjectile )
{
TotalEPF += static_cast < int > ( Item . m_Enchantments . GetLevel ( cEnchantments :: enchProjectileProtection )) * 2 ;
}
}
int CappedEPF = std :: min ( 20 , TotalEPF );
return static_cast < int > ( a_Damage * CappedEPF / 25.0 );
}
2014-04-25 19:49:08 -07:00
int cEntity :: GetArmorCoverAgainst ( const cEntity * a_Attacker , eDamageType a_DamageType , int a_Damage )
{
// Returns the hitpoints out of a_RawDamage that the currently equipped armor would cover
2016-01-16 14:16:47 +02:00
2014-04-25 19:49:08 -07:00
// Filter out damage types that are not protected by armor:
2014-12-05 16:59:11 +01:00
if ( ! ArmorCoversAgainst ( a_DamageType ))
{
return 0 ;
}
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
// Add up all armor points:
2017-07-23 04:46:38 -05:00
// Ref.: http://minecraft.gamepedia.com/Armor#Defense_points
2013-07-01 10:39:56 +00:00
int ArmorValue = 0 ;
2017-07-23 04:46:38 -05:00
int Toughness = 0 ;
2013-07-01 10:39:56 +00:00
switch ( GetEquippedHelmet (). m_ItemType )
{
case E_ITEM_LEATHER_CAP : ArmorValue += 1 ; break ;
case E_ITEM_GOLD_HELMET : ArmorValue += 2 ; break ;
case E_ITEM_CHAIN_HELMET : ArmorValue += 2 ; break ;
case E_ITEM_IRON_HELMET : ArmorValue += 2 ; break ;
2017-07-23 04:46:38 -05:00
case E_ITEM_DIAMOND_HELMET : ArmorValue += 3 ; Toughness += 2 ; break ;
2013-07-01 10:39:56 +00:00
}
switch ( GetEquippedChestplate (). m_ItemType )
{
case E_ITEM_LEATHER_TUNIC : ArmorValue += 3 ; break ;
case E_ITEM_GOLD_CHESTPLATE : ArmorValue += 5 ; break ;
case E_ITEM_CHAIN_CHESTPLATE : ArmorValue += 5 ; break ;
case E_ITEM_IRON_CHESTPLATE : ArmorValue += 6 ; break ;
2017-07-23 04:46:38 -05:00
case E_ITEM_DIAMOND_CHESTPLATE : ArmorValue += 8 ; Toughness += 2 ; break ;
2013-07-01 10:39:56 +00:00
}
switch ( GetEquippedLeggings (). m_ItemType )
{
case E_ITEM_LEATHER_PANTS : ArmorValue += 2 ; break ;
case E_ITEM_GOLD_LEGGINGS : ArmorValue += 3 ; break ;
case E_ITEM_CHAIN_LEGGINGS : ArmorValue += 4 ; break ;
case E_ITEM_IRON_LEGGINGS : ArmorValue += 5 ; break ;
2017-07-23 04:46:38 -05:00
case E_ITEM_DIAMOND_LEGGINGS : ArmorValue += 6 ; Toughness += 2 ; break ;
2013-07-01 10:39:56 +00:00
}
switch ( GetEquippedBoots (). m_ItemType )
{
case E_ITEM_LEATHER_BOOTS : ArmorValue += 1 ; break ;
case E_ITEM_GOLD_BOOTS : ArmorValue += 1 ; break ;
case E_ITEM_CHAIN_BOOTS : ArmorValue += 1 ; break ;
case E_ITEM_IRON_BOOTS : ArmorValue += 2 ; break ;
2017-07-23 04:46:38 -05:00
case E_ITEM_DIAMOND_BOOTS : ArmorValue += 3 ; Toughness += 2 ; break ;
2013-07-01 10:39:56 +00:00
}
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
// TODO: Special armor cases, such as wool, saddles, dog's collar
2016-12-19 18:08:19 +02:00
// Ref.: http://minecraft.gamepedia.com/Armor#Mob_armor as of 2012_12_20
2016-01-16 14:16:47 +02:00
2017-07-23 04:46:38 -05:00
double Reduction = std :: max ( ArmorValue / 5.0 , ArmorValue - a_Damage / ( 2 + Toughness / 4.0 ));
return static_cast < int > ( a_Damage * std :: min ( 20.0 , Reduction ) / 25.0 );
2013-07-01 10:39:56 +00:00
}
double cEntity :: GetKnockbackAmountAgainst ( const cEntity & a_Receiver )
{
// Returns the knockback amount that the currently equipped items would cause to a_Receiver on a hit
2017-08-13 06:40:23 -05:00
double Knockback = 11 ;
2016-01-16 14:16:47 +02:00
2017-08-13 06:40:23 -05:00
// If we're sprinting, bump up the knockback
if ( IsSprinting ())
{
Knockback = 16 ;
}
// Check for knockback enchantments (punch only applies to shot arrows)
unsigned int KnockbackLevel = GetEquippedWeapon (). m_Enchantments . GetLevel ( cEnchantments :: enchKnockback );
Knockback += 10 * KnockbackLevel ;
return Knockback ;
2013-07-01 10:39:56 +00:00
}
2014-07-04 10:55:09 +01:00
void cEntity :: KilledBy ( TakeDamageInfo & a_TDI )
2013-07-01 10:39:56 +00:00
{
m_Health = 0 ;
2014-07-16 11:38:52 +01:00
cRoot :: Get () -> GetPluginManager () -> CallHookKilling ( * this , a_TDI . Attacker , a_TDI );
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
if ( m_Health > 0 )
{
// Plugin wants to 'unkill' the pawn. Abort
return ;
}
2015-06-02 17:06:18 +02:00
// If the victim is a player the hook is handled by the cPlayer class
if ( ! IsPlayer ())
{
AString emptystring = AString ( "" );
cRoot :: Get () -> GetPluginManager () -> CallHookKilled ( * this , a_TDI , emptystring );
}
2014-02-02 12:47:17 +00:00
// Drop loot:
2013-07-01 10:39:56 +00:00
cItems Drops ;
2014-07-04 10:55:09 +01:00
GetDrops ( Drops , a_TDI . Attacker );
2013-07-01 10:39:56 +00:00
m_World -> SpawnItemPickups ( Drops , GetPosX (), GetPosY (), GetPosZ ());
2014-04-12 13:16:48 +01:00
m_World -> BroadcastEntityStatus ( * this , esGenericDead );
2013-07-01 10:39:56 +00:00
}
void cEntity :: Heal ( int a_HitPoints )
{
m_Health += a_HitPoints ;
2014-08-02 22:35:29 -07:00
m_Health = std :: min ( m_Health , m_MaxHealth );
2013-07-01 10:39:56 +00:00
}
2013-07-07 15:09:05 +00:00
void cEntity :: SetHealth ( int a_Health )
{
2014-08-02 22:35:29 -07:00
m_Health = Clamp ( a_Health , 0 , m_MaxHealth );
2013-07-07 15:09:05 +00:00
}
2015-01-11 21:12:26 +00:00
void cEntity :: Tick ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
2012-06-14 13:06:06 +00:00
{
2016-02-07 19:07:14 +02:00
ASSERT ( IsTicking ());
ASSERT ( GetWorld () != nullptr );
2014-07-20 01:43:07 -07:00
m_TicksAlive ++ ;
2016-01-16 14:16:47 +02:00
2014-04-26 00:32:30 +02:00
if ( m_InvulnerableTicks > 0 )
{
m_InvulnerableTicks -- ;
}
2016-04-18 21:58:57 +03:00
if (( GetPosY () < 0 ) && ( ! IsPlayer ()))
{
Destroy ();
return ;
}
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo != nullptr )
2013-03-03 19:05:11 +00:00
{
2015-07-21 21:25:37 +01:00
SetPosition ( m_AttachedTo -> GetPosition ());
2013-03-03 19:05:11 +00:00
}
else
{
2014-04-12 13:16:48 +01:00
if ( ! a_Chunk . IsValid ())
2013-07-24 20:36:12 +00:00
{
2014-04-12 13:16:48 +01:00
return ;
}
2014-01-24 23:58:51 +00:00
2014-04-12 13:16:48 +01:00
// Position changed -> super::Tick() called
GET_AND_VERIFY_CURRENT_CHUNK ( NextChunk , POSX_TOINT , POSZ_TOINT )
2014-03-05 22:12:48 +00:00
2014-04-12 13:16:48 +01:00
TickBurning ( * NextChunk );
2014-01-24 23:58:51 +00:00
2014-04-12 13:16:48 +01:00
if ( GetPosY () < VOID_BOUNDARY )
{
TickInVoid ( * NextChunk );
}
else
{
m_TicksSinceLastVoidDamage = 0 ;
}
2014-03-05 22:12:48 +00:00
2017-07-02 07:39:19 +02:00
if (
IsMob () || IsPickup () || IsExpOrb () ||
( IsPlayer () && ! (( reinterpret_cast < cPlayer *> ( this )) -> IsGameModeCreative () || ( reinterpret_cast < cPlayer *> ( this )) -> IsGameModeSpectator ()))
)
2014-05-18 22:41:42 +01:00
{
DetectCacti ();
}
2014-04-12 13:16:48 +01:00
if ( IsMob () || IsPlayer ())
{
// Set swimming state
SetSwimState ( * NextChunk );
2014-03-05 22:12:48 +00:00
2014-04-12 13:16:48 +01:00
// Handle drowning
HandleAir ();
2014-07-29 16:36:24 +01:00
}
2016-01-16 14:16:47 +02:00
2014-07-22 17:26:48 +01:00
if ( ! DetectPortal ()) // Our chunk is invalid if we have moved to another world
{
// None of the above functions changed position, we remain in the chunk of NextChunk
HandlePhysics ( a_Dt , * NextChunk );
2014-03-05 22:12:48 +00:00
}
2014-01-24 23:58:51 +00:00
}
2013-03-03 19:05:11 +00:00
}
2015-01-11 21:12:26 +00:00
void cEntity :: HandlePhysics ( std :: chrono :: milliseconds a_Dt , cChunk & a_Chunk )
2013-04-22 07:18:03 +00:00
{
2014-04-12 13:16:48 +01:00
int BlockX = POSX_TOINT ;
int BlockY = POSY_TOINT ;
int BlockZ = POSZ_TOINT ;
// Position changed -> super::HandlePhysics() called
GET_AND_VERIFY_CURRENT_CHUNK ( NextChunk , BlockX , BlockZ )
2013-06-18 08:24:34 +00:00
// TODO Add collision detection with entities.
2015-01-11 21:12:26 +00:00
auto DtSec = std :: chrono :: duration_cast < std :: chrono :: duration < double >> ( a_Dt );
2014-04-12 13:16:48 +01:00
Vector3d NextPos = Vector3d ( GetPosX (), GetPosY (), GetPosZ ());
Vector3d NextSpeed = Vector3d ( GetSpeedX (), GetSpeedY (), GetSpeedZ ());
2016-01-16 14:16:47 +02:00
2013-06-18 08:24:34 +00:00
if (( BlockY >= cChunkDef :: Height ) || ( BlockY < 0 ))
{
2014-07-17 22:59:02 +02:00
// Outside of the world
2015-01-11 21:12:26 +00:00
AddSpeedY ( m_Gravity * DtSec . count ());
AddPosition ( GetSpeed () * DtSec . count ());
2013-06-18 08:24:34 +00:00
return ;
}
2016-01-16 14:16:47 +02:00
2014-04-12 13:16:48 +01:00
int RelBlockX = BlockX - ( NextChunk -> GetPosX () * cChunkDef :: Width );
int RelBlockZ = BlockZ - ( NextChunk -> GetPosZ () * cChunkDef :: Width );
2014-07-21 15:19:48 +02:00
BLOCKTYPE BlockIn = NextChunk -> GetBlock ( RelBlockX , BlockY , RelBlockZ );
2014-04-12 13:16:48 +01:00
BLOCKTYPE BlockBelow = ( BlockY > 0 ) ? NextChunk -> GetBlock ( RelBlockX , BlockY - 1 , RelBlockZ ) : E_BLOCK_AIR ;
2014-03-05 22:12:48 +00:00
if ( ! cBlockInfo :: IsSolid ( BlockIn )) // Making sure we are not inside a solid block
2013-04-22 07:18:03 +00:00
{
2014-03-05 22:12:48 +00:00
if ( m_bOnGround ) // check if it's still on the ground
2013-04-22 07:18:03 +00:00
{
2014-03-05 22:12:48 +00:00
if ( ! cBlockInfo :: IsSolid ( BlockBelow )) // Check if block below is air or water.
2013-04-22 07:18:03 +00:00
{
2014-03-05 22:12:48 +00:00
m_bOnGround = false ;
2013-04-22 07:18:03 +00:00
}
}
2014-03-05 22:12:48 +00:00
}
else
{
// Push out entity.
BLOCKTYPE GotBlock ;
2013-09-04 17:52:15 +01:00
2014-03-05 22:12:48 +00:00
static const struct
{
int x , y , z ;
} gCrossCoords [] =
{
{ 1 , 0 , 0 },
{ - 1 , 0 , 0 },
{ 0 , 0 , 1 },
{ 0 , 0 , - 1 },
} ;
bool IsNoAirSurrounding = true ;
for ( size_t i = 0 ; i < ARRAYCOUNT ( gCrossCoords ); i ++ )
{
2014-04-12 13:16:48 +01:00
if ( ! NextChunk -> UnboundedRelGetBlockType ( RelBlockX + gCrossCoords [ i ]. x , BlockY , RelBlockZ + gCrossCoords [ i ]. z , GotBlock ))
2013-09-11 22:10:29 +01:00
{
2014-03-05 22:12:48 +00:00
// The pickup is too close to an unloaded chunk, bail out of any physics handling
return ;
}
if ( ! cBlockInfo :: IsSolid ( GotBlock ))
2013-09-15 20:49:08 +02:00
{
2014-03-05 22:12:48 +00:00
NextPos . x += gCrossCoords [ i ]. x ;
NextPos . z += gCrossCoords [ i ]. z ;
IsNoAirSurrounding = false ;
break ;
2013-09-15 20:49:08 +02:00
}
2014-03-05 22:12:48 +00:00
} // for i - gCrossCoords[]
2016-01-16 14:16:47 +02:00
2014-03-05 22:12:48 +00:00
if ( IsNoAirSurrounding )
{
NextPos . y += 0.5 ;
}
2013-09-13 19:54:50 +01:00
2017-06-04 06:35:02 +02:00
m_bHasSentNoSpeed = false ; // this unlocks movement sending to client in BroadcastMovementUpdate function
2014-03-05 22:12:48 +00:00
m_bOnGround = true ;
2013-09-05 21:41:47 +01:00
2014-03-05 22:12:48 +00:00
/*
// DEBUG:
LOGD("Entity #%d (%s) is inside a block at {%d, %d, %d}",
m_UniqueID, GetClass(), BlockX, BlockY, BlockZ
);
*/
}
2013-04-22 07:18:03 +00:00
2014-03-05 22:12:48 +00:00
if ( ! m_bOnGround )
{
2015-01-18 11:02:17 +01:00
double fallspeed ;
2014-03-05 22:12:48 +00:00
if ( IsBlockWater ( BlockIn ))
2013-04-22 07:18:03 +00:00
{
2015-01-11 21:12:26 +00:00
fallspeed = m_Gravity * DtSec . count () / 3 ; // Fall 3x slower in water
2015-01-18 11:02:17 +01:00
ApplyFriction ( NextSpeed , 0.7 , static_cast < float > ( DtSec . count ()));
2014-03-05 22:12:48 +00:00
}
else if ( BlockIn == E_BLOCK_COBWEB )
{
NextSpeed . y *= 0.05 ; // Reduce overall falling speed
2014-09-13 22:49:05 +01:00
fallspeed = 0 ; // No falling
2013-04-22 07:18:03 +00:00
}
else
{
2014-03-05 22:12:48 +00:00
// Normal gravity
2015-01-11 21:12:26 +00:00
fallspeed = m_Gravity * DtSec . count ();
2015-03-31 11:03:35 -04:00
NextSpeed -= NextSpeed * ( m_AirDrag * 20.0f ) * DtSec . count ();
2014-03-05 22:12:48 +00:00
}
2015-01-18 11:02:17 +01:00
NextSpeed . y += static_cast < float > ( fallspeed );
2016-07-18 13:10:00 -07:00
// A real boat floats
if ( IsBoat ())
{
// Find top water block and sit there
int NextBlockY = BlockY ;
BLOCKTYPE NextBlock = NextChunk -> GetBlock ( RelBlockX , NextBlockY , RelBlockZ );
while ( IsBlockWater ( NextBlock ))
{
NextBlock = NextChunk -> GetBlock ( RelBlockX , ++ NextBlockY , RelBlockZ );
}
NextPos . y = NextBlockY - 0.5 ;
NextSpeed . y = 0 ;
}
2014-03-05 22:12:48 +00:00
}
2014-09-12 20:50:24 +01:00
else
2014-03-05 22:12:48 +00:00
{
2015-01-18 11:02:17 +01:00
ApplyFriction ( NextSpeed , 0.7 , static_cast < float > ( DtSec . count ()));
2014-03-05 22:12:48 +00:00
}
2013-05-21 05:51:38 +00:00
2014-03-05 22:12:48 +00:00
// Adjust X and Z speed for COBWEB temporary. This speed modification should be handled inside block handlers since we
// might have different speed modifiers according to terrain.
if ( BlockIn == E_BLOCK_COBWEB )
{
NextSpeed . x *= 0.25 ;
NextSpeed . z *= 0.25 ;
}
2014-07-17 22:15:34 +02:00
// Get water direction
2014-03-05 22:12:48 +00:00
Direction WaterDir = m_World -> GetWaterSimulator () -> GetFlowingDirection ( BlockX , BlockY , BlockZ );
2013-04-22 07:18:03 +00:00
2017-05-11 14:34:36 +02:00
m_WaterSpeed *= 0.9 ; // Reduce speed each tick
2013-04-22 07:18:03 +00:00
2014-07-21 15:19:48 +02:00
switch ( WaterDir )
2014-03-05 22:12:48 +00:00
{
case X_PLUS :
2017-05-11 14:34:36 +02:00
{
2014-03-05 22:12:48 +00:00
m_WaterSpeed . x = 0.2f ;
m_bOnGround = false ;
2013-04-22 07:18:03 +00:00
break ;
2017-05-11 14:34:36 +02:00
}
2014-03-05 22:12:48 +00:00
case X_MINUS :
2017-05-11 14:34:36 +02:00
{
2014-03-05 22:12:48 +00:00
m_WaterSpeed . x = - 0.2f ;
m_bOnGround = false ;
break ;
2017-05-11 14:34:36 +02:00
}
2014-03-05 22:12:48 +00:00
case Z_PLUS :
2017-05-11 14:34:36 +02:00
{
2014-03-05 22:12:48 +00:00
m_WaterSpeed . z = 0.2f ;
m_bOnGround = false ;
break ;
2017-05-11 14:34:36 +02:00
}
2014-03-05 22:12:48 +00:00
case Z_MINUS :
2017-05-11 14:34:36 +02:00
{
2014-03-05 22:12:48 +00:00
m_WaterSpeed . z = - 0.2f ;
m_bOnGround = false ;
break ;
2017-05-11 14:34:36 +02:00
}
default :
{
break ;
}
2014-03-05 22:12:48 +00:00
}
2013-04-22 07:18:03 +00:00
2014-03-05 22:12:48 +00:00
if ( fabs ( m_WaterSpeed . x ) < 0.05 )
{
m_WaterSpeed . x = 0 ;
}
2013-04-22 07:18:03 +00:00
2014-03-05 22:12:48 +00:00
if ( fabs ( m_WaterSpeed . z ) < 0.05 )
{
m_WaterSpeed . z = 0 ;
}
2013-04-22 07:18:03 +00:00
2014-03-05 22:12:48 +00:00
NextSpeed += m_WaterSpeed ;
2013-04-22 07:18:03 +00:00
2015-03-30 19:42:32 -04:00
if ( NextSpeed . SqrLength () > 0.0f )
2014-03-05 22:12:48 +00:00
{
2017-05-11 14:34:36 +02:00
Vector3d HitCoords ;
Vector3i HitBlockCoords ;
eBlockFace HitBlockFace ;
2017-05-28 20:56:17 +02:00
Vector3d wantNextPos = NextPos + NextSpeed * DtSec . count ();
auto isHit = cLineBlockTracer :: FirstSolidHitTrace ( * GetWorld (), NextPos , wantNextPos , HitCoords , HitBlockCoords , HitBlockFace );
if ( isHit )
2013-04-22 07:18:03 +00:00
{
2017-05-11 14:34:36 +02:00
// Set our position to where the block was hit, minus a bit:
// TODO: The real entity's m_Width should be taken into account here
NextPos = HitCoords - NextSpeed . NormalizeCopy () * 0.1 ;
2017-05-28 20:56:17 +02:00
if ( HitBlockFace == BLOCK_FACE_YP )
2013-04-22 07:18:03 +00:00
{
2017-05-11 14:34:36 +02:00
// We hit the ground, adjust the position to the top of the block:
m_bOnGround = true ;
NextPos . y = HitBlockCoords . y + 1 ;
}
// Avoid movement in the direction of the blockface that has been hit:
switch ( HitBlockFace )
{
case BLOCK_FACE_XM :
case BLOCK_FACE_XP :
2014-12-05 16:59:11 +01:00
{
2017-05-11 14:34:36 +02:00
NextSpeed . x = 0 ;
break ;
2014-12-05 16:59:11 +01:00
}
2017-05-11 14:34:36 +02:00
case BLOCK_FACE_YM :
case BLOCK_FACE_YP :
2014-12-05 16:59:11 +01:00
{
2017-05-11 14:34:36 +02:00
NextSpeed . y = 0 ;
break ;
2014-12-05 16:59:11 +01:00
}
2017-05-11 14:34:36 +02:00
case BLOCK_FACE_ZM :
case BLOCK_FACE_ZP :
2014-12-05 16:59:11 +01:00
{
2017-05-11 14:34:36 +02:00
NextSpeed . z = 0 ;
break ;
2014-12-05 16:59:11 +01:00
}
2017-05-11 14:34:36 +02:00
default :
2015-11-10 14:02:07 +01:00
{
2017-05-11 14:34:36 +02:00
break ;
2015-11-10 14:02:07 +01:00
}
2013-04-22 07:18:03 +00:00
}
}
2017-05-28 20:56:17 +02:00
else
{
// We didn't hit anything, so move:
NextPos += ( NextSpeed * DtSec . count ());
}
2017-05-11 14:34:36 +02:00
}
2014-03-05 22:12:48 +00:00
2014-04-12 13:16:48 +01:00
SetPosition ( NextPos );
SetSpeed ( NextSpeed );
2013-04-22 07:18:03 +00:00
}
2014-09-13 22:49:05 +01:00
void cEntity :: ApplyFriction ( Vector3d & a_Speed , double a_SlowdownMultiplier , float a_Dt )
{
if ( a_Speed . SqrLength () > 0.0004f )
{
a_Speed . x *= a_SlowdownMultiplier / ( 1 + a_Dt );
if ( fabs ( a_Speed . x ) < 0.05 )
{
a_Speed . x = 0 ;
}
a_Speed . z *= a_SlowdownMultiplier / ( 1 + a_Dt );
if ( fabs ( a_Speed . z ) < 0.05 )
{
a_Speed . z = 0 ;
}
}
}
2013-07-01 10:39:56 +00:00
void cEntity :: TickBurning ( cChunk & a_Chunk )
{
2013-07-01 18:37:27 +00:00
// Remember the current burning state:
bool HasBeenBurning = ( m_TicksLeftBurning > 0 );
2014-04-18 12:59:14 +01:00
2014-06-21 20:42:10 +01:00
if ( GetWorld () -> IsWeatherWetAt ( POSX_TOINT , POSZ_TOINT ))
2014-04-18 12:59:14 +01:00
{
2014-04-25 10:58:48 +04:00
if ( POSY_TOINT > m_World -> GetHeight ( POSX_TOINT , POSZ_TOINT ))
2014-04-18 12:59:14 +01:00
{
m_TicksLeftBurning = 0 ;
2014-07-17 22:59:02 +02:00
}
2014-04-18 12:59:14 +01:00
}
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
// Do the burning damage:
if ( m_TicksLeftBurning > 0 )
{
m_TicksSinceLastBurnDamage ++ ;
if ( m_TicksSinceLastBurnDamage >= BURN_TICKS_PER_DAMAGE )
{
2015-10-31 16:24:45 +01:00
if ( ! IsFireproof ())
2014-04-22 02:36:39 -10:00
{
2014-10-20 21:55:07 +01:00
TakeDamage ( dtOnFire , nullptr , BURN_DAMAGE , 0 );
2014-04-22 02:36:39 -10:00
}
2013-07-01 18:37:27 +00:00
m_TicksSinceLastBurnDamage = 0 ;
2013-07-01 10:39:56 +00:00
}
m_TicksLeftBurning -- ;
}
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
// Update the burning times, based on surroundings:
2015-07-29 09:04:03 -06:00
int MinRelX = FloorC ( GetPosX () - m_Width / 2 ) - a_Chunk . GetPosX () * cChunkDef :: Width ;
int MaxRelX = FloorC ( GetPosX () + m_Width / 2 ) - a_Chunk . GetPosX () * cChunkDef :: Width ;
int MinRelZ = FloorC ( GetPosZ () - m_Width / 2 ) - a_Chunk . GetPosZ () * cChunkDef :: Width ;
int MaxRelZ = FloorC ( GetPosZ () + m_Width / 2 ) - a_Chunk . GetPosZ () * cChunkDef :: Width ;
int MinY = Clamp ( POSY_TOINT , 0 , cChunkDef :: Height - 1 );
2015-11-04 18:00:29 +01:00
int MaxY = Clamp ( FloorC ( GetPosY () + m_Height ), 0 , cChunkDef :: Height - 1 );
2013-07-01 10:39:56 +00:00
bool HasWater = false ;
bool HasLava = false ;
bool HasFire = false ;
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
for ( int x = MinRelX ; x <= MaxRelX ; x ++ )
{
for ( int z = MinRelZ ; z <= MaxRelZ ; z ++ )
{
int RelX = x ;
int RelZ = z ;
2014-03-05 22:12:48 +00:00
2013-07-01 10:39:56 +00:00
for ( int y = MinY ; y <= MaxY ; y ++ )
{
2014-03-05 22:12:48 +00:00
BLOCKTYPE Block ;
a_Chunk . UnboundedRelGetBlockType ( RelX , y , RelZ , Block );
2016-01-16 14:16:47 +02:00
2014-03-05 22:12:48 +00:00
switch ( Block )
2013-07-01 10:39:56 +00:00
{
case E_BLOCK_FIRE :
{
HasFire = true ;
break ;
}
case E_BLOCK_LAVA :
case E_BLOCK_STATIONARY_LAVA :
{
HasLava = true ;
break ;
}
case E_BLOCK_STATIONARY_WATER :
case E_BLOCK_WATER :
{
HasWater = true ;
break ;
}
} // switch (BlockType)
} // for y
} // for z
} // for x
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
if ( HasWater )
{
// Extinguish the fire
m_TicksLeftBurning = 0 ;
}
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
if ( HasLava )
{
// Burn:
m_TicksLeftBurning = BURN_TICKS ;
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
// Periodically damage:
m_TicksSinceLastLavaDamage ++ ;
2013-07-01 18:37:27 +00:00
if ( m_TicksSinceLastLavaDamage >= LAVA_TICKS_PER_DAMAGE )
2013-07-01 10:39:56 +00:00
{
2015-10-31 16:24:45 +01:00
if ( ! IsFireproof ())
2014-04-22 02:36:39 -10:00
{
2014-10-20 21:55:07 +01:00
TakeDamage ( dtLavaContact , nullptr , LAVA_DAMAGE , 0 );
2014-04-22 02:36:39 -10:00
}
2013-07-01 10:39:56 +00:00
m_TicksSinceLastLavaDamage = 0 ;
}
}
else
{
m_TicksSinceLastLavaDamage = 0 ;
}
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
if ( HasFire )
{
// Burn:
m_TicksLeftBurning = BURN_TICKS ;
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
// Periodically damage:
m_TicksSinceLastFireDamage ++ ;
if ( m_TicksSinceLastFireDamage >= FIRE_TICKS_PER_DAMAGE )
{
2015-10-31 16:24:45 +01:00
if ( ! IsFireproof ())
2014-04-22 02:36:39 -10:00
{
2014-10-20 21:55:07 +01:00
TakeDamage ( dtFireContact , nullptr , FIRE_DAMAGE , 0 );
2014-04-22 02:36:39 -10:00
}
2013-07-01 18:37:27 +00:00
m_TicksSinceLastFireDamage = 0 ;
2013-07-01 10:39:56 +00:00
}
}
else
{
m_TicksSinceLastFireDamage = 0 ;
}
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
// If just started / finished burning, notify descendants:
if (( m_TicksLeftBurning > 0 ) && ! HasBeenBurning )
{
OnStartedBurning ();
}
else if (( m_TicksLeftBurning <= 0 ) && HasBeenBurning )
{
OnFinishedBurning ();
}
}
2013-09-10 23:01:02 +01:00
void cEntity :: TickInVoid ( cChunk & a_Chunk )
{
if ( m_TicksSinceLastVoidDamage == 20 )
{
2014-10-20 21:55:07 +01:00
TakeDamage ( dtInVoid , nullptr , 2 , 0 );
2013-09-10 23:01:02 +01:00
m_TicksSinceLastVoidDamage = 0 ;
}
else
{
m_TicksSinceLastVoidDamage ++ ;
}
}
2014-05-23 12:33:30 +02:00
void cEntity :: DetectCacti ( void )
2014-05-18 22:41:42 +01:00
{
int X = POSX_TOINT , Y = POSY_TOINT , Z = POSZ_TOINT ;
2014-05-23 12:33:30 +02:00
double w = m_Width / 2 ;
2014-05-18 22:41:42 +01:00
if (
2014-05-19 22:15:39 +01:00
(( Y > 0 ) && ( Y < cChunkDef :: Height )) &&
(((( X + 1 ) - GetPosX () < w ) && ( GetWorld () -> GetBlock ( X + 1 , Y , Z ) == E_BLOCK_CACTUS )) ||
2014-05-19 21:17:28 +01:00
(( GetPosX () - X < w ) && ( GetWorld () -> GetBlock ( X - 1 , Y , Z ) == E_BLOCK_CACTUS )) ||
2014-05-19 10:35:21 +01:00
((( Z + 1 ) - GetPosZ () < w ) && ( GetWorld () -> GetBlock ( X , Y , Z + 1 ) == E_BLOCK_CACTUS )) ||
2014-05-19 21:17:28 +01:00
(( GetPosZ () - Z < w ) && ( GetWorld () -> GetBlock ( X , Y , Z - 1 ) == E_BLOCK_CACTUS )) ||
2017-02-25 19:14:08 +01:00
((( Y + 1 ) - GetPosY () < w ) && ( GetWorld () -> GetBlock ( X , Y + 1 , Z ) == E_BLOCK_CACTUS )) ||
(( GetPosY () - Y < 1 ) && ( GetWorld () -> GetBlock ( X , Y - 1 , Z ) == E_BLOCK_CACTUS )))
2014-05-18 22:41:42 +01:00
)
{
2014-10-20 21:55:07 +01:00
TakeDamage ( dtCactusContact , nullptr , 1 , 0 );
2014-05-18 22:41:42 +01:00
}
}
2015-06-13 16:09:43 -05:00
void cEntity :: ScheduleMoveToWorld ( cWorld * a_World , Vector3d a_NewPosition , bool a_SetPortalCooldown )
2015-05-26 20:35:28 -05:00
{
m_NewWorld = a_World ;
m_NewWorldPosition = a_NewPosition ;
m_IsWorldChangeScheduled = true ;
2015-06-13 16:09:43 -05:00
m_WorldChangeSetPortalCooldown = a_SetPortalCooldown ;
2015-05-26 20:35:28 -05:00
}
2014-05-18 22:41:42 +01:00
2014-07-22 17:26:48 +01:00
bool cEntity :: DetectPortal ()
2014-05-31 22:28:51 +01:00
{
2015-05-26 20:35:28 -05:00
// If somebody scheduled a world change with ScheduleMoveToWorld, change worlds now.
if ( m_IsWorldChangeScheduled )
{
m_IsWorldChangeScheduled = false ;
2015-06-13 16:09:43 -05:00
if ( m_WorldChangeSetPortalCooldown )
{
// Delay the portal check.
m_PortalCooldownData . m_TicksDelayed = 0 ;
m_PortalCooldownData . m_ShouldPreventTeleportation = true ;
}
2015-05-26 20:35:28 -05:00
MoveToWorld ( m_NewWorld , false , m_NewWorldPosition );
return true ;
}
2014-07-20 10:46:45 +01:00
if ( GetWorld () -> GetDimension () == dimOverworld )
2014-06-04 20:00:55 +01:00
{
2015-03-21 15:20:31 +01:00
if ( GetWorld () -> GetLinkedNetherWorldName (). empty () && GetWorld () -> GetLinkedEndWorldName (). empty ())
2014-07-21 22:49:06 +01:00
{
2014-07-23 21:12:59 +01:00
// Teleportation to either dimension not enabled, don't bother proceeding
2014-07-22 17:26:48 +01:00
return false ;
2014-07-21 22:49:06 +01:00
}
}
else if ( GetWorld () -> GetLinkedOverworldName (). empty ())
{
2014-07-23 21:12:59 +01:00
// Overworld teleportation disabled, abort
2014-07-22 17:26:48 +01:00
return false ;
2014-06-04 20:00:55 +01:00
}
2014-05-31 22:28:51 +01:00
int X = POSX_TOINT , Y = POSY_TOINT , Z = POSZ_TOINT ;
if (( Y > 0 ) && ( Y < cChunkDef :: Height ))
{
switch ( GetWorld () -> GetBlock ( X , Y , Z ))
{
case E_BLOCK_NETHER_PORTAL :
{
2014-07-21 22:49:06 +01:00
if ( m_PortalCooldownData . m_ShouldPreventTeleportation )
2014-06-04 20:00:55 +01:00
{
2014-07-23 21:12:59 +01:00
// Just exited a portal, don't teleport again
2014-07-22 17:26:48 +01:00
return false ;
2014-06-04 20:00:55 +01:00
}
2015-07-29 09:04:03 -06:00
if ( IsPlayer () && ! ( reinterpret_cast < cPlayer *> ( this )) -> IsGameModeCreative () && ( m_PortalCooldownData . m_TicksDelayed != 80 ))
2014-06-12 15:21:07 +01:00
{
2014-07-23 21:12:59 +01:00
// Delay teleportation for four seconds if the entity is a non-creative player
2014-07-21 22:49:06 +01:00
m_PortalCooldownData . m_TicksDelayed ++ ;
2014-07-22 17:26:48 +01:00
return false ;
2014-06-12 15:21:07 +01:00
}
2014-07-21 22:49:06 +01:00
m_PortalCooldownData . m_TicksDelayed = 0 ;
2014-06-12 15:21:07 +01:00
2016-08-27 09:37:54 +03:00
// Nether portal in the nether
2014-08-02 21:44:02 -07:00
if ( GetWorld () -> GetDimension () == dimNether )
2014-05-31 22:28:51 +01:00
{
2014-08-02 21:44:02 -07:00
if ( GetWorld () -> GetLinkedOverworldName (). empty ())
2014-06-12 15:21:07 +01:00
{
2014-08-02 21:44:02 -07:00
return false ;
2014-06-12 15:21:07 +01:00
}
2016-08-27 09:37:54 +03:00
cWorld * DestinationWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetLinkedOverworldName ());
eDimension DestionationDim = DestinationWorld -> GetDimension ();
2014-08-02 21:44:02 -07:00
m_PortalCooldownData . m_ShouldPreventTeleportation = true ; // Stop portals from working on respawn
if ( IsPlayer ())
2014-05-31 22:28:51 +01:00
{
2015-05-09 09:25:09 +02:00
// Send a respawn packet before world is loaded / generated so the client isn't left in limbo
2016-08-27 09:37:54 +03:00
( reinterpret_cast < cPlayer *> ( this )) -> GetClientHandle () -> SendRespawn ( DestionationDim );
2014-05-31 22:28:51 +01:00
}
2015-05-26 20:35:28 -05:00
Vector3d TargetPos = GetPosition ();
TargetPos . x *= 8.0 ;
TargetPos . z *= 8.0 ;
2016-02-08 12:06:14 +02:00
cWorld * TargetWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetLinkedOverworldName ());
ASSERT ( TargetWorld != nullptr ); // The linkage checker should have prevented this at startup. See cWorld::start()
2016-08-27 09:37:54 +03:00
LOGD ( "Jumping %s -> %s" , DimensionToString ( dimNether ). c_str (), DimensionToString ( DestionationDim ). c_str ());
2016-09-18 21:43:30 +02:00
new cNetherPortalScanner ( this , TargetWorld , TargetPos , cChunkDef :: Height );
2015-06-13 16:09:43 -05:00
return true ;
2014-08-02 21:44:02 -07:00
}
2016-08-27 09:37:54 +03:00
// Nether portal in the overworld
2014-08-02 21:44:02 -07:00
else
{
2015-03-21 15:20:31 +01:00
if ( GetWorld () -> GetLinkedNetherWorldName (). empty ())
2014-08-02 21:44:02 -07:00
{
return false ;
}
2016-08-27 09:37:54 +03:00
cWorld * DestinationWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetLinkedNetherWorldName ());
eDimension DestionationDim = DestinationWorld -> GetDimension ();
2014-08-02 21:44:02 -07:00
m_PortalCooldownData . m_ShouldPreventTeleportation = true ;
if ( IsPlayer ())
{
2016-08-27 09:37:54 +03:00
if ( DestionationDim == dimNether )
{
reinterpret_cast < cPlayer *> ( this ) -> AwardAchievement ( achEnterPortal );
}
reinterpret_cast < cPlayer *> ( this ) -> GetClientHandle () -> SendRespawn ( DestionationDim );
2014-08-02 21:44:02 -07:00
}
2015-05-26 20:35:28 -05:00
Vector3d TargetPos = GetPosition ();
TargetPos . x /= 8.0 ;
TargetPos . z /= 8.0 ;
2016-02-08 12:06:14 +02:00
cWorld * TargetWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetLinkedNetherWorldName ());
ASSERT ( TargetWorld != nullptr ); // The linkage checker should have prevented this at startup. See cWorld::start()
2016-08-27 09:37:54 +03:00
LOGD ( "Jumping %s -> %s" , DimensionToString ( dimOverworld ). c_str (), DimensionToString ( DestionationDim ). c_str ());
2016-09-18 21:43:30 +02:00
new cNetherPortalScanner ( this , TargetWorld , TargetPos , ( cChunkDef :: Height / 2 ));
2015-06-13 16:09:43 -05:00
return true ;
2014-05-31 22:28:51 +01:00
}
}
case E_BLOCK_END_PORTAL :
{
2014-07-21 22:49:06 +01:00
if ( m_PortalCooldownData . m_ShouldPreventTeleportation )
2014-06-04 20:00:55 +01:00
{
2014-07-22 17:26:48 +01:00
return false ;
2014-06-04 20:00:55 +01:00
}
2016-08-27 09:37:54 +03:00
// End portal in the end
2014-08-02 21:44:02 -07:00
if ( GetWorld () -> GetDimension () == dimEnd )
2014-05-31 22:28:51 +01:00
{
2016-01-16 14:16:47 +02:00
2014-08-02 21:44:02 -07:00
if ( GetWorld () -> GetLinkedOverworldName (). empty ())
2014-05-31 22:28:51 +01:00
{
2014-08-02 21:44:02 -07:00
return false ;
}
2016-08-27 09:37:54 +03:00
cWorld * DestinationWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetLinkedOverworldName ());
eDimension DestionationDim = DestinationWorld -> GetDimension ();
2014-06-01 18:46:59 +01:00
2014-08-02 21:44:02 -07:00
m_PortalCooldownData . m_ShouldPreventTeleportation = true ;
2014-06-12 15:21:07 +01:00
2014-08-02 21:44:02 -07:00
if ( IsPlayer ())
2014-05-31 22:28:51 +01:00
{
2015-07-29 09:04:03 -06:00
cPlayer * Player = reinterpret_cast < cPlayer *> ( this );
2016-08-27 09:37:54 +03:00
if ( Player -> GetBedWorld () == DestinationWorld )
{
Player -> TeleportToCoords ( Player -> GetLastBedPos (). x , Player -> GetLastBedPos (). y , Player -> GetLastBedPos (). z );
}
else
{
Player -> TeleportToCoords ( DestinationWorld -> GetSpawnX (), DestinationWorld -> GetSpawnY (), DestinationWorld -> GetSpawnZ ());
}
Player -> GetClientHandle () -> SendRespawn ( DestionationDim );
2014-08-02 21:44:02 -07:00
}
2014-07-20 10:46:45 +01:00
2016-02-08 12:06:14 +02:00
cWorld * TargetWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetLinkedOverworldName ());
ASSERT ( TargetWorld != nullptr ); // The linkage checker should have prevented this at startup. See cWorld::start()
2016-08-27 09:37:54 +03:00
LOGD ( "Jumping %s -> %s" , DimensionToString ( dimEnd ). c_str (), DimensionToString ( DestionationDim ). c_str ());
2016-02-08 12:06:14 +02:00
return MoveToWorld ( TargetWorld , false );
2014-08-02 21:44:02 -07:00
}
2016-08-27 09:37:54 +03:00
// End portal in the overworld
2014-08-02 21:44:02 -07:00
else
{
2015-03-21 15:20:31 +01:00
if ( GetWorld () -> GetLinkedEndWorldName (). empty ())
2014-08-02 21:44:02 -07:00
{
return false ;
}
2016-08-27 09:37:54 +03:00
cWorld * DestinationWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetLinkedEndWorldName ());
eDimension DestionationDim = DestinationWorld -> GetDimension ();
2014-06-12 15:21:07 +01:00
2014-08-02 21:44:02 -07:00
m_PortalCooldownData . m_ShouldPreventTeleportation = true ;
2014-06-12 15:21:07 +01:00
2014-08-02 21:44:02 -07:00
if ( IsPlayer ())
{
2016-08-27 09:37:54 +03:00
if ( DestionationDim == dimEnd )
{
reinterpret_cast < cPlayer *> ( this ) -> AwardAchievement ( achEnterTheEnd );
}
reinterpret_cast < cPlayer *> ( this ) -> GetClientHandle () -> SendRespawn ( DestionationDim );
2014-05-31 22:28:51 +01:00
}
2014-08-02 21:44:02 -07:00
2016-02-08 12:06:14 +02:00
cWorld * TargetWorld = cRoot :: Get () -> GetWorld ( GetWorld () -> GetLinkedEndWorldName ());
ASSERT ( TargetWorld != nullptr ); // The linkage checker should have prevented this at startup. See cWorld::start()
2016-08-27 09:37:54 +03:00
LOGD ( "Jumping %s -> %s" , DimensionToString ( dimOverworld ). c_str (), DimensionToString ( DestionationDim ). c_str ());
2016-02-08 12:06:14 +02:00
return MoveToWorld ( TargetWorld , false );
2014-05-31 22:28:51 +01:00
}
2016-01-16 14:16:47 +02:00
2014-05-31 22:28:51 +01:00
}
default : break ;
}
}
2014-06-12 15:21:07 +01:00
// Allow portals to work again
2014-07-21 22:49:06 +01:00
m_PortalCooldownData . m_ShouldPreventTeleportation = false ;
2014-07-23 21:12:59 +01:00
m_PortalCooldownData . m_TicksDelayed = 0 ;
2014-07-22 17:26:48 +01:00
return false ;
2014-05-31 22:28:51 +01:00
}
2015-05-26 20:35:28 -05:00
bool cEntity :: DoMoveToWorld ( cWorld * a_World , bool a_ShouldSendRespawn , Vector3d a_NewPosition )
2014-05-31 22:28:51 +01:00
{
2014-06-12 15:21:07 +01:00
UNUSED ( a_ShouldSendRespawn );
2014-10-20 21:55:07 +01:00
ASSERT ( a_World != nullptr );
2016-02-07 19:07:14 +02:00
ASSERT ( IsTicking ());
2014-06-12 15:21:07 +01:00
2014-07-21 22:49:06 +01:00
if ( GetWorld () == a_World )
2014-05-31 22:28:51 +01:00
{
2014-06-12 15:21:07 +01:00
// Don't move to same world
2014-05-31 22:28:51 +01:00
return false ;
}
2015-05-21 12:27:54 +02:00
// Ask the plugins if the entity is allowed to changing the world
if ( cRoot :: Get () -> GetPluginManager () -> CallHookEntityChangingWorld ( * this , * a_World ))
2015-05-18 22:29:39 +02:00
{
2015-05-21 12:27:54 +02:00
// A Plugin doesn't allow the entity to changing the world
2015-05-18 22:29:39 +02:00
return false ;
}
2016-02-07 19:07:14 +02:00
// Stop ticking, in preperation for detaching from this world.
SetIsTicking ( false );
2016-02-02 17:37:21 +02:00
2016-02-07 19:07:14 +02:00
// Tell others we are gone
2014-05-31 22:28:51 +01:00
GetWorld () -> BroadcastDestroyEntity ( * this );
2016-02-07 19:07:14 +02:00
// Set position to the new position
2015-05-26 20:35:28 -05:00
SetPosition ( a_NewPosition );
2016-02-07 19:07:14 +02:00
// Stop all mobs from targeting this entity
// Stop this entity from targeting other mobs
2016-02-01 22:49:34 +02:00
if ( this -> IsMob ())
{
cMonster * Monster = static_cast < cMonster *> ( this );
Monster -> SetTarget ( nullptr );
Monster -> StopEveryoneFromTargetingMe ();
}
2016-02-07 19:07:14 +02:00
// Queue add to new world and removal from the old one
cWorld * OldWorld = GetWorld ();
cChunk * ParentChunk = GetParentChunk ();
SetWorld ( a_World ); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
OldWorld -> QueueTask ([ this , ParentChunk , a_World ]( cWorld & a_OldWorld )
{
LOGD ( "Warping entity #%i (%s) from world \" %s \" to \" %s \" . Source chunk: (%d, %d) " ,
this -> GetUniqueID (), this -> GetClass (),
a_OldWorld . GetName (). c_str (), a_World -> GetName (). c_str (),
ParentChunk -> GetPosX (), ParentChunk -> GetPosZ ()
);
2016-12-19 20:12:23 +00:00
a_World -> AddEntity ( ParentChunk -> RemoveEntity ( * this ));
2016-02-07 19:07:14 +02:00
cRoot :: Get () -> GetPluginManager () -> CallHookEntityChangedWorld ( * this , a_OldWorld );
});
2014-05-31 22:28:51 +01:00
return true ;
}
2016-03-30 11:38:45 +03:00
bool cEntity :: MoveToWorld ( cWorld * a_World , bool a_ShouldSendRespawn , Vector3d a_NewPosition )
{
return DoMoveToWorld ( a_World , a_ShouldSendRespawn , a_NewPosition );
}
bool cEntity :: MoveToWorld ( cWorld * a_World , bool a_ShouldSendRespawn )
{
return MoveToWorld ( a_World , a_ShouldSendRespawn , Vector3d ( a_World -> GetSpawnX (), a_World -> GetSpawnY (), a_World -> GetSpawnZ ()));
}
2014-07-21 22:49:06 +01:00
bool cEntity :: MoveToWorld ( const AString & a_WorldName , bool a_ShouldSendRespawn )
{
cWorld * World = cRoot :: Get () -> GetWorld ( a_WorldName );
2014-10-20 21:55:07 +01:00
if ( World == nullptr )
2014-07-21 22:49:06 +01:00
{
LOG ( "%s: Couldn't find world \" %s \" ." , __FUNCTION__ , a_WorldName . c_str ());
return false ;
}
2016-03-30 11:38:45 +03:00
return DoMoveToWorld ( World , a_ShouldSendRespawn , Vector3d ( World -> GetSpawnX (), World -> GetSpawnY (), World -> GetSpawnZ ()));
2014-07-21 22:49:06 +01:00
}
2014-01-24 23:58:51 +00:00
void cEntity :: SetSwimState ( cChunk & a_Chunk )
{
2015-07-29 09:04:03 -06:00
int RelY = FloorC ( GetPosY () + 0.1 );
2014-01-24 23:58:51 +00:00
if (( RelY < 0 ) || ( RelY >= cChunkDef :: Height - 1 ))
{
m_IsSwimming = false ;
m_IsSubmerged = false ;
return ;
}
BLOCKTYPE BlockIn ;
2014-03-05 22:12:48 +00:00
int RelX = POSX_TOINT - a_Chunk . GetPosX () * cChunkDef :: Width ;
int RelZ = POSZ_TOINT - a_Chunk . GetPosZ () * cChunkDef :: Width ;
2014-01-24 23:58:51 +00:00
// Check if the player is swimming:
if ( ! a_Chunk . UnboundedRelGetBlockType ( RelX , RelY , RelZ , BlockIn ))
{
// This sometimes happens on Linux machines
2016-01-31 02:25:03 +02:00
// Ref.: https://forum.cuberite.org/thread-1244.html
2014-04-23 21:06:46 +01:00
LOGD ( "SetSwimState failure: RelX = %d, RelZ = %d, Pos = %.02f, %.02f}" ,
RelX , RelY , GetPosX (), GetPosZ ()
2014-04-24 22:03:47 +01:00
);
2014-01-24 23:58:51 +00:00
m_IsSwimming = false ;
m_IsSubmerged = false ;
return ;
}
m_IsSwimming = IsBlockWater ( BlockIn );
// Check if the player is submerged:
VERIFY ( a_Chunk . UnboundedRelGetBlockType ( RelX , RelY + 1 , RelZ , BlockIn ));
m_IsSubmerged = IsBlockWater ( BlockIn );
}
2016-02-07 19:07:14 +02:00
void cEntity :: SetIsTicking ( bool a_IsTicking )
{
m_IsTicking = a_IsTicking ;
ASSERT ( ! ( m_IsTicking && ( m_ParentChunk == nullptr ))); // We shouldn't be ticking if we have no parent chunk
}
2014-06-16 15:12:50 +01:00
void cEntity :: DoSetSpeed ( double a_SpeedX , double a_SpeedY , double a_SpeedZ )
{
m_Speed . Set ( a_SpeedX , a_SpeedY , a_SpeedZ );
2016-01-16 14:16:47 +02:00
2014-06-16 15:12:50 +01:00
WrapSpeed ();
}
2014-01-24 23:58:51 +00:00
void cEntity :: HandleAir ( void )
{
// Ref.: http://www.minecraftwiki.net/wiki/Chunk_format
// See if the entity is /submerged/ water (block above is water)
// Get the type of block the entity is standing in:
2015-07-29 09:04:03 -06:00
int RespirationLevel = static_cast < int > ( GetEquippedHelmet (). m_Enchantments . GetLevel ( cEnchantments :: enchRespiration ));
2014-08-22 11:49:49 +02:00
2014-01-24 23:58:51 +00:00
if ( IsSubmerged ())
{
2014-07-17 22:15:34 +02:00
if ( ! IsPlayer ()) // Players control themselves
2014-06-27 23:13:26 +01:00
{
2014-07-17 22:15:34 +02:00
SetSpeedY ( 1 ); // Float in the water
2014-06-27 23:13:26 +01:00
}
2014-01-24 23:58:51 +00:00
2014-08-22 11:49:49 +02:00
if ( RespirationLevel > 0 )
{
2015-07-29 09:04:03 -06:00
reinterpret_cast < cPawn *> ( this ) -> AddEntityEffect ( cEntityEffect :: effNightVision , 200 , 5 , 0 );
2014-08-22 11:49:49 +02:00
}
2014-08-02 22:35:29 -07:00
if ( m_AirLevel <= 0 )
2014-01-24 23:58:51 +00:00
{
2014-08-03 02:20:48 -07:00
// Runs the air tick timer to check whether the player should be damaged
2014-08-02 22:35:29 -07:00
if ( m_AirTickTimer <= 0 )
2014-01-24 23:58:51 +00:00
{
2014-07-17 22:50:58 +02:00
// Damage player
2014-10-20 21:55:07 +01:00
TakeDamage ( dtDrowning , nullptr , 1 , 1 , 0 );
2014-01-24 23:58:51 +00:00
// Reset timer
m_AirTickTimer = DROWNING_TICKS ;
}
else
{
2014-04-12 13:16:48 +01:00
m_AirTickTimer -- ;
2014-01-24 23:58:51 +00:00
}
}
else
{
// Reduce air supply
2014-04-12 13:16:48 +01:00
m_AirLevel -- ;
2014-01-24 23:58:51 +00:00
}
}
else
{
// Set the air back to maximum
m_AirLevel = MAX_AIR_LEVEL ;
m_AirTickTimer = DROWNING_TICKS ;
2014-08-22 11:49:49 +02:00
if ( RespirationLevel > 0 )
{
m_AirTickTimer = DROWNING_TICKS + ( RespirationLevel * 15 * 20 );
}
2014-01-24 23:58:51 +00:00
}
}
2013-07-01 10:39:56 +00:00
void cEntity :: OnStartedBurning ( void )
{
// Broadcast the change:
2013-07-07 13:06:06 +00:00
m_World -> BroadcastEntityMetadata ( * this );
2013-07-01 10:39:56 +00:00
}
void cEntity :: OnFinishedBurning ( void )
{
// Broadcast the change:
2013-07-07 13:06:06 +00:00
m_World -> BroadcastEntityMetadata ( * this );
2013-07-01 10:39:56 +00:00
}
void cEntity :: SetMaxHealth ( int a_MaxHealth )
{
m_MaxHealth = a_MaxHealth ;
// Reset health, if too high:
2014-07-17 22:32:23 +02:00
m_Health = std :: min ( m_Health , a_MaxHealth );
2013-07-01 10:39:56 +00:00
}
2014-04-23 00:12:37 -07:00
void cEntity :: SetIsFireproof ( bool a_IsFireproof )
2014-04-22 12:59:31 -10:00
{
m_IsFireproof = a_IsFireproof ;
}
2013-07-01 10:39:56 +00:00
void cEntity :: StartBurning ( int a_TicksLeftBurning )
{
if ( m_TicksLeftBurning > 0 )
{
// Already burning, top up the ticks left burning and bail out:
m_TicksLeftBurning = std :: max ( m_TicksLeftBurning , a_TicksLeftBurning );
return ;
}
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
m_TicksLeftBurning = a_TicksLeftBurning ;
OnStartedBurning ();
}
void cEntity :: StopBurning ( void )
{
bool HasBeenBurning = ( m_TicksLeftBurning > 0 );
m_TicksLeftBurning = 0 ;
m_TicksSinceLastBurnDamage = 0 ;
m_TicksSinceLastFireDamage = 0 ;
m_TicksSinceLastLavaDamage = 0 ;
2016-01-16 14:16:47 +02:00
2013-07-01 10:39:56 +00:00
// Notify if the entity has stopped burning
if ( HasBeenBurning )
{
OnFinishedBurning ();
}
}
void cEntity :: TeleportToEntity ( cEntity & a_Entity )
{
TeleportToCoords ( a_Entity . GetPosX (), a_Entity . GetPosY (), a_Entity . GetPosZ ());
}
void cEntity :: TeleportToCoords ( double a_PosX , double a_PosY , double a_PosZ )
{
2015-03-05 11:52:42 +01:00
// ask the plugins to allow teleport to the new position.
2015-07-21 21:25:37 +01:00
if ( ! cRoot :: Get () -> GetPluginManager () -> CallHookEntityTeleport ( * this , m_LastPosition , Vector3d ( a_PosX , a_PosY , a_PosZ )))
2015-03-05 11:52:42 +01:00
{
SetPosition ( a_PosX , a_PosY , a_PosZ );
m_World -> BroadcastTeleportEntity ( * this );
}
2013-07-01 10:39:56 +00:00
}
2013-03-22 06:33:10 +00:00
void cEntity :: BroadcastMovementUpdate ( const cClientHandle * a_Exclude )
{
2014-04-23 21:06:46 +01:00
// Process packet sending every two ticks
if ( GetWorld () -> GetWorldAge () % 2 == 0 )
2013-04-07 06:53:17 +00:00
{
2014-04-23 21:06:46 +01:00
double SpeedSqr = GetSpeed (). SqrLength ();
if ( SpeedSqr == 0.0 )
2013-03-22 06:33:10 +00:00
{
2014-04-23 21:06:46 +01:00
// Speed is zero, send this to clients once only as well as an absolute position
if ( ! m_bHasSentNoSpeed )
{
m_World -> BroadcastEntityVelocity ( * this , a_Exclude );
m_World -> BroadcastTeleportEntity ( * this , a_Exclude );
m_bHasSentNoSpeed = true ;
}
2013-03-22 06:33:10 +00:00
}
else
{
2014-04-23 21:06:46 +01:00
// Movin'
m_World -> BroadcastEntityVelocity ( * this , a_Exclude );
m_bHasSentNoSpeed = false ;
}
2016-01-16 14:16:47 +02:00
2017-06-04 06:35:02 +02:00
// Only send movement if speed is not 0 and 'no speed' was sent to client
2017-07-06 12:59:48 -05:00
if ( ! m_bHasSentNoSpeed || IsPlayer ())
2014-04-23 21:06:46 +01:00
{
2017-06-04 06:35:02 +02:00
// TODO: Pickups move disgracefully if relative move packets are sent as opposed to just velocity. Have a system to send relmove only when SetPosXXX() is called with a large difference in position
int DiffX = FloorC ( GetPosX () * 32.0 ) - FloorC ( m_LastSentPosition . x * 32.0 );
int DiffY = FloorC ( GetPosY () * 32.0 ) - FloorC ( m_LastSentPosition . y * 32.0 );
int DiffZ = FloorC ( GetPosZ () * 32.0 ) - FloorC ( m_LastSentPosition . z * 32.0 );
if (( DiffX != 0 ) || ( DiffY != 0 ) || ( DiffZ != 0 )) // Have we moved?
2013-03-22 06:33:10 +00:00
{
2017-06-04 06:35:02 +02:00
if (( abs ( DiffX ) <= 127 ) && ( abs ( DiffY ) <= 127 ) && ( abs ( DiffZ ) <= 127 )) // Limitations of a Byte
2013-03-22 06:33:10 +00:00
{
2017-06-04 06:35:02 +02:00
// Difference within Byte limitations, use a relative move packet
if ( m_bDirtyOrientation )
{
m_World -> BroadcastEntityRelMoveLook ( * this , static_cast < char > ( DiffX ), static_cast < char > ( DiffY ), static_cast < char > ( DiffZ ), a_Exclude );
m_bDirtyOrientation = false ;
}
else
{
m_World -> BroadcastEntityRelMove ( * this , static_cast < char > ( DiffX ), static_cast < char > ( DiffY ), static_cast < char > ( DiffZ ), a_Exclude );
}
// Clients seem to store two positions, one for the velocity packet and one for the teleport / relmove packet
// The latter is only changed with a relmove / teleport, and m_LastSentPosition stores this position
m_LastSentPosition = GetPosition ();
2013-03-22 06:33:10 +00:00
}
else
{
2017-06-04 06:35:02 +02:00
// Too big a movement, do a teleport
m_World -> BroadcastTeleportEntity ( * this , a_Exclude );
m_LastSentPosition = GetPosition (); // See above
m_bDirtyOrientation = false ;
2013-03-22 06:33:10 +00:00
}
2014-04-23 21:06:46 +01:00
}
2013-04-02 06:48:31 +00:00
}
2014-04-23 21:06:46 +01:00
2013-04-02 06:48:31 +00:00
if ( m_bDirtyHead )
{
2014-04-23 21:06:46 +01:00
m_World -> BroadcastEntityHeadLook ( * this , a_Exclude );
2013-04-02 06:48:31 +00:00
m_bDirtyHead = false ;
2013-03-22 06:33:10 +00:00
}
2014-04-23 21:06:46 +01:00
if ( m_bDirtyOrientation )
{
// Send individual update in case above (sending with rel-move packet) wasn't done
GetWorld () -> BroadcastEntityLook ( * this , a_Exclude );
m_bDirtyOrientation = false ;
}
2013-03-22 06:33:10 +00:00
}
}
2016-07-18 13:10:00 -07:00
cEntity * cEntity :: GetAttached ()
{
return m_AttachedTo ;
}
2013-03-22 06:33:10 +00:00
2013-03-03 19:05:11 +00:00
void cEntity :: AttachTo ( cEntity * a_AttachTo )
{
if ( m_AttachedTo == a_AttachTo )
{
// Already attached to that entity, nothing to do here
return ;
}
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo != nullptr )
2014-01-12 23:23:36 +00:00
{
// Detach from any previous entity:
Detach ();
}
2013-04-02 06:48:31 +00:00
2016-05-14 12:12:42 -07:00
// Update state information
2013-03-03 19:05:11 +00:00
m_AttachedTo = a_AttachTo ;
a_AttachTo -> m_Attachee = this ;
2016-05-14 12:12:42 -07:00
if ( a_AttachTo != nullptr )
{
m_World -> BroadcastAttachEntity ( * this , * a_AttachTo );
}
2013-03-03 19:05:11 +00:00
}
void cEntity :: Detach ( void )
{
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo == nullptr )
2013-03-03 19:05:11 +00:00
{
2016-05-14 12:12:42 -07:00
// Already not attached to any entity, our work is done
2013-03-03 19:05:11 +00:00
return ;
}
2016-05-14 12:12:42 -07:00
m_World -> BroadcastDetachEntity ( * this , * m_AttachedTo );
2014-10-20 21:55:07 +01:00
m_AttachedTo -> m_Attachee = nullptr ;
m_AttachedTo = nullptr ;
2012-12-22 10:15:53 +00:00
}
2012-12-21 12:21:20 +00:00
bool cEntity :: IsA ( const char * a_ClassName ) const
2012-06-14 13:06:06 +00:00
{
2017-04-01 23:15:08 +02:00
return (( a_ClassName != nullptr ) && ( strcmp ( a_ClassName , "cEntity" ) == 0 ));
2012-06-14 13:06:06 +00:00
}
2016-02-02 14:44:10 +01:00
bool cEntity :: IsAttachedTo ( const cEntity * a_Entity ) const
{
if (( m_AttachedTo != nullptr ) && ( a_Entity -> GetUniqueID () == m_AttachedTo -> GetUniqueID ()))
{
return true ;
}
return false ;
}
2013-04-02 06:48:31 +00:00
void cEntity :: SetHeadYaw ( double a_HeadYaw )
{
m_HeadYaw = a_HeadYaw ;
m_bDirtyHead = true ;
WrapHeadYaw ();
}
2013-05-21 12:58:21 +00:00
void cEntity :: SetHeight ( double a_Height )
{
m_Height = a_Height ;
}
2013-04-28 18:54:43 +00:00
void cEntity :: SetMass ( double a_Mass )
{
2014-08-02 22:35:29 -07:00
// Make sure that mass is not zero. 1g is the default because we
// have to choose a number. It's perfectly legal to have a mass
// less than 1g as long as is NOT equal or less than zero.
m_Mass = std :: max ( a_Mass , 0.001 );
2013-04-28 18:54:43 +00:00
}
2013-11-02 17:45:48 +01:00
void cEntity :: SetYaw ( double a_Yaw )
2012-06-14 13:06:06 +00:00
{
2013-11-02 17:45:48 +01:00
m_Rot . x = a_Yaw ;
2012-06-14 13:06:06 +00:00
m_bDirtyOrientation = true ;
2013-04-03 05:11:00 +00:00
WrapRotation ();
2012-06-14 13:06:06 +00:00
}
2013-03-09 14:35:43 +00:00
void cEntity :: SetPitch ( double a_Pitch )
2012-06-14 13:06:06 +00:00
{
m_Rot . y = a_Pitch ;
m_bDirtyOrientation = true ;
2013-04-03 05:11:00 +00:00
WrapRotation ();
2012-06-14 13:06:06 +00:00
}
2013-03-09 14:35:43 +00:00
void cEntity :: SetRoll ( double a_Roll )
2012-06-14 13:06:06 +00:00
{
m_Rot . z = a_Roll ;
m_bDirtyOrientation = true ;
}
2013-03-09 14:35:43 +00:00
void cEntity :: SetSpeed ( double a_SpeedX , double a_SpeedY , double a_SpeedZ )
{
2014-06-16 15:12:50 +01:00
DoSetSpeed ( a_SpeedX , a_SpeedY , a_SpeedZ );
2013-03-09 14:35:43 +00:00
}
2013-03-22 06:33:10 +00:00
void cEntity :: SetSpeedX ( double a_SpeedX )
{
2014-06-16 15:12:50 +01:00
SetSpeed ( a_SpeedX , m_Speed . y , m_Speed . z );
2013-03-22 06:33:10 +00:00
}
void cEntity :: SetSpeedY ( double a_SpeedY )
{
2014-06-16 15:12:50 +01:00
SetSpeed ( m_Speed . x , a_SpeedY , m_Speed . z );
2013-03-22 06:33:10 +00:00
}
void cEntity :: SetSpeedZ ( double a_SpeedZ )
{
2014-06-16 15:12:50 +01:00
SetSpeed ( m_Speed . x , m_Speed . y , a_SpeedZ );
2013-03-22 06:33:10 +00:00
}
2013-03-09 14:35:43 +00:00
2013-05-21 12:58:21 +00:00
void cEntity :: SetWidth ( double a_Width )
{
m_Width = a_Width ;
}
2013-03-23 04:33:47 +00:00
void cEntity :: AddSpeed ( double a_AddSpeedX , double a_AddSpeedY , double a_AddSpeedZ )
{
2015-02-25 20:56:45 -05:00
DoSetSpeed ( m_Speed . x + a_AddSpeedX , m_Speed . y + a_AddSpeedY , m_Speed . z + a_AddSpeedZ );
2013-03-23 04:33:47 +00:00
}
void cEntity :: AddSpeedX ( double a_AddSpeedX )
{
2015-02-25 20:56:45 -05:00
AddSpeed ( a_AddSpeedX , 0 , 0 );
2013-03-23 04:33:47 +00:00
}
void cEntity :: AddSpeedY ( double a_AddSpeedY )
{
2015-02-25 20:56:45 -05:00
AddSpeed ( 0 , a_AddSpeedY , 0 );
2013-03-23 04:33:47 +00:00
}
void cEntity :: AddSpeedZ ( double a_AddSpeedZ )
2013-03-03 19:05:11 +00:00
{
2015-02-25 20:56:45 -05:00
AddSpeed ( 0 , 0 , a_AddSpeedZ );
2013-03-03 19:05:11 +00:00
}
2013-12-16 18:02:33 +01:00
void cEntity :: HandleSpeedFromAttachee ( float a_Forward , float a_Sideways )
{
Vector3d LookVector = m_Attachee -> GetLookVector ();
double AddSpeedX = LookVector . x * a_Forward + LookVector . z * a_Sideways ;
double AddSpeedZ = LookVector . z * a_Forward - LookVector . x * a_Sideways ;
SetSpeed ( AddSpeedX , 0 , AddSpeedZ );
BroadcastMovementUpdate ();
}
2013-09-06 00:04:49 +02:00
void cEntity :: SteerVehicle ( float a_Forward , float a_Sideways )
{
2014-10-20 21:55:07 +01:00
if ( m_AttachedTo == nullptr )
2013-09-06 00:04:49 +02:00
{
return ;
}
2015-03-30 19:42:32 -04:00
if (( a_Forward != 0.0f ) || ( a_Sideways != 0.0f ))
2013-09-06 00:04:49 +02:00
{
2013-12-16 18:02:33 +01:00
m_AttachedTo -> HandleSpeedFromAttachee ( a_Forward , a_Sideways );
2013-09-06 00:04:49 +02:00
}
}
2016-02-07 19:07:14 +02:00
bool cEntity :: IsTicking ( void ) const
{
ASSERT ( ! ( m_IsTicking && ( m_ParentChunk == nullptr ))); // We shouldn't be ticking if we have no parent chunk
return m_IsTicking ;
}
2014-07-17 22:15:34 +02:00
////////////////////////////////////////////////////////////////////////////////
2012-06-14 13:06:06 +00:00
// Get look vector (this is NOT a rotation!)
2013-03-09 14:35:43 +00:00
Vector3d cEntity :: GetLookVector ( void ) const
2012-06-14 13:06:06 +00:00
{
2013-03-09 14:35:43 +00:00
Matrix4d m ;
2014-04-05 22:34:05 +02:00
m . Init ( Vector3d (), 0 , m_Rot . x , - m_Rot . y );
2013-03-09 14:35:43 +00:00
Vector3d Look = m . Transform ( Vector3d ( 0 , 0 , 1 ));
2012-06-14 13:06:06 +00:00
return Look ;
}
2014-07-17 22:15:34 +02:00
////////////////////////////////////////////////////////////////////////////////
2012-06-14 13:06:06 +00:00
// Set position
2015-07-21 21:25:37 +01:00
void cEntity :: SetPosition ( const Vector3d & a_Position )
2012-06-14 13:06:06 +00:00
{
2015-07-21 21:25:37 +01:00
m_LastPosition = m_Position ;
m_Position = a_Position ;
2012-06-14 13:06:06 +00:00
}