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 "../Server.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"
2013-08-19 11:39:13 +02:00
#include "../Tracer.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"
2012-06-14 13:06:06 +00:00
int cEntity :: m_EntityCount = 0 ;
cCriticalSection cEntity :: m_CSCount ;
2013-07-01 10:39:56 +00:00
cEntity :: cEntity ( eEntityType a_EntityType , double a_X , double a_Y , double a_Z , double a_Width , double a_Height )
2013-03-03 19:05:11 +00:00
: m_UniqueID ( 0 )
2013-07-01 10:39:56 +00:00
, m_Health ( 1 )
, m_MaxHealth ( 1 )
2013-03-03 19:05:11 +00:00
, m_AttachedTo ( NULL )
, m_Attachee ( NULL )
2013-04-02 06:48:31 +00:00
, m_bDirtyHead ( true )
2013-03-03 19:05:11 +00:00
, m_bDirtyOrientation ( true )
2014-04-23 21:06:46 +01:00
, m_bHasSentNoSpeed ( true )
, m_bOnGround ( false )
, m_Gravity ( - 9.81f )
, m_LastPos ( a_X , a_Y , a_Z )
2013-12-20 16:39:20 +01:00
, m_IsInitialized ( false )
2014-07-20 10:46:45 +01:00
, m_WorldTravellingFrom ( NULL )
2012-12-21 12:52:14 +00:00
, m_EntityType ( a_EntityType )
, m_World ( NULL )
2014-04-22 12:59:31 -10:00
, m_IsFireproof ( false )
2013-07-01 10:39:56 +00:00
, m_TicksSinceLastBurnDamage ( 0 )
, m_TicksSinceLastLavaDamage ( 0 )
, m_TicksSinceLastFireDamage ( 0 )
, m_TicksLeftBurning ( 0 )
2013-12-20 17:11:51 +01:00
, m_TicksSinceLastVoidDamage ( 0 )
2014-02-05 09:43:49 -08:00
, m_IsSwimming ( false )
, m_IsSubmerged ( false )
2014-04-26 15:32:14 -07:00
, m_AirLevel ( 0 )
, m_AirTickTimer ( 0 )
2014-07-20 01:43:07 -07:00
, m_TicksAlive ( 0 )
2014-04-23 21:06:46 +01:00
, m_HeadYaw ( 0.0 )
2013-12-20 16:39:20 +01:00
, m_Rot ( 0.0 , 0.0 , 0.0 )
, m_Pos ( a_X , a_Y , a_Z )
2013-07-01 10:39:56 +00:00
, m_WaterSpeed ( 0 , 0 , 0 )
2013-12-20 17:11:51 +01:00
, m_Mass ( 0.001 ) // Default 1g
2013-07-01 10:39:56 +00:00
, m_Width ( a_Width )
, m_Height ( a_Height )
2014-04-26 16:44:15 +02:00
, m_InvulnerableTicks ( 0 )
2012-06-14 13:06:06 +00:00
{
cCSLock Lock ( m_CSCount );
m_EntityCount ++ ;
m_UniqueID = m_EntityCount ;
}
cEntity ::~ cEntity ()
{
2014-01-24 09:57:12 +01:00
// Before deleting, the entity needs to have been removed from the world, if ever added
ASSERT (( m_World == NULL ) || ! m_World -> HasEntity ( m_UniqueID ));
2013-04-13 21:02:10 +00: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
*/
2013-03-03 19:05:11 +00:00
if ( m_AttachedTo != NULL )
{
Detach ();
}
if ( m_Attachee != NULL )
{
m_Attachee -> Detach ();
}
2013-04-02 06:48:31 +00:00
2013-04-13 21:02:10 +00:00
if ( m_IsInitialized )
2012-06-14 13:06:06 +00:00
{
2013-04-13 21:02:10 +00:00
LOGWARNING ( "ERROR: Entity deallocated without being destroyed" );
2012-06-14 13:06:06 +00:00
ASSERT ( ! "Entity deallocated without being destroyed or unlinked" );
}
}
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 "" ;
}
2014-06-08 21:58:08 +02:00
bool cEntity :: Initialize ( cWorld & a_World )
2012-06-14 13:06:06 +00:00
{
2014-06-08 21:58:08 +02:00
if ( cPluginManager :: Get () -> CallHookSpawningEntity ( a_World , * this ))
2013-08-08 09:13:13 +02:00
{
return false ;
}
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
*/
2013-04-13 21:02:10 +00:00
m_IsInitialized = true ;
2014-06-08 21:58:08 +02:00
m_World = & a_World ;
2013-03-03 19:05:11 +00:00
m_World -> AddEntity ( this );
2013-08-08 09:13:13 +02:00
2014-06-08 21:58:08 +02:00
cPluginManager :: Get () -> CallHookSpawnedEntity ( a_World , * this );
2013-08-25 21:25:13 +02:00
// Spawn the entity on the clients:
2014-06-08 21:58:08 +02:00
a_World . BroadcastSpawnEntity ( * this );
2013-08-25 21:25:13 +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
2013-06-25 06:36:59 +00:00
void cEntity :: Destroy ( bool a_ShouldBroadcast )
2012-06-14 13:06:06 +00:00
{
2013-04-13 21:02:10 +00:00
if ( ! m_IsInitialized )
2012-06-14 13:06:06 +00:00
{
return ;
}
2013-06-25 06:36:59 +00:00
if ( a_ShouldBroadcast )
{
m_World -> BroadcastDestroyEntity ( * this );
}
2013-04-02 06:48:31 +00:00
2013-04-13 21:02:10 +00:00
m_IsInitialized = false ;
2012-06-14 13:06:06 +00:00
Destroyed ();
}
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 )
{
int FinalDamage = a_RawDamage - GetArmorCoverAgainst ( a_Attacker , a_DamageType , a_RawDamage );
cEntity :: TakeDamage ( a_DamageType , a_Attacker , a_RawDamage , FinalDamage , a_KnockbackAmount );
}
void cEntity :: TakeDamage ( eDamageType a_DamageType , cEntity * a_Attacker , int a_RawDamage , int a_FinalDamage , double a_KnockbackAmount )
{
TakeDamageInfo TDI ;
TDI . DamageType = a_DamageType ;
TDI . Attacker = a_Attacker ;
TDI . RawDamage = a_RawDamage ;
TDI . FinalDamage = a_FinalDamage ;
2013-12-22 20:04:17 +00:00
Vector3d Heading ( 0 , 0 , 0 );
if ( a_Attacker != NULL )
{
2014-08-04 01:34:12 +02:00
Heading = a_Attacker -> GetLookVector () * ( a_Attacker -> IsSprinting () ? 16 : 11 );
Heading . y = 1.6 ;
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 ;
if (( abs ( m_Speed . x ) < EPS ) && ( abs ( m_Speed . z ) < EPS ))
{
// 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-01-16 19:00:49 +00:00
SetYaw ( atan2 ( m_Speed . x , m_Speed . z ) * 180 / 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
2013-09-07 17:14:37 +02:00
if (( abs ( xz ) < EPS ) && ( abs ( m_Speed . y ) < EPS ))
{
// atan2() may overflow or is undefined, pick any number
SetPitch ( 0 );
return ;
}
SetPitch ( atan2 ( m_Speed . y , xz ) * 180 / PI );
}
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 ;
}
2013-12-22 20:04:17 +00:00
if (( a_TDI . Attacker != NULL ) && ( a_TDI . Attacker -> IsPlayer ()))
{
2014-05-12 21:38:52 +03:00
cPlayer * Player = ( cPlayer * ) a_TDI . Attacker ;
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 ;
2014-08-19 12:38:15 +02:00
int SharpnessLevel = Enchantments . GetLevel ( cEnchantments :: enchSharpness );
int SmiteLevel = Enchantments . GetLevel ( cEnchantments :: enchSmite );
int BaneOfArthropodsLevel = Enchantments . GetLevel ( cEnchantments :: enchBaneOfArthropods );
if ( SharpnessLevel > 0 )
{
2014-09-01 14:31:05 +02:00
a_TDI . FinalDamage += ( int ) ceil ( 1.25 * SharpnessLevel );
2014-08-19 12:38:15 +02:00
}
else if ( SmiteLevel > 0 )
{
if ( IsMob ())
{
cMonster * Monster = ( cMonster * ) this ;
switch ( Monster -> GetMobType ())
{
case cMonster :: mtSkeleton :
case cMonster :: mtZombie :
case cMonster :: mtWither :
case cMonster :: mtZombiePigman :
{
2014-09-01 14:31:05 +02:00
a_TDI . FinalDamage += ( int ) ceil ( 2.5 * SmiteLevel );
2014-08-19 12:38:15 +02:00
break ;
}
}
}
}
else if ( BaneOfArthropodsLevel > 0 )
2013-12-22 20:04:17 +00:00
{
2014-08-19 12:38:15 +02:00
if ( IsMob ())
{
cMonster * Monster = ( cMonster * ) this ;
switch ( Monster -> GetMobType ())
{
case cMonster :: mtSpider :
case cMonster :: mtCaveSpider :
case cMonster :: mtSilverfish :
{
2014-09-01 14:31:05 +02:00
a_TDI . RawDamage += ( int ) ceil ( 2.5 * BaneOfArthropodsLevel );
2014-08-22 11:49:49 +02:00
// TODO: Add slowness effect
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
}
}
}
int FireAspectLevel = Enchantments . GetLevel ( cEnchantments :: enchFireAspect );
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 ())
{
cMonster * Monster = ( cMonster * ) this ;
switch ( Monster -> GetMobType ())
{
case cMonster :: mtGhast :
case cMonster :: mtZombiePigman :
case cMonster :: 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
2014-08-22 11:49:49 +02:00
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
}
2014-08-30 00:27:33 +02:00
2014-08-22 11:49:49 +02:00
if ( ThornsLevel > 0 )
{
int Chance = ThornsLevel * 15 ;
cFastRandom Random ;
int RandomValue = Random . GenerateRandomInteger ( 0 , 100 );
if ( RandomValue <= Chance )
{
a_TDI . Attacker -> TakeDamage ( dtAttack , this , 0 , Random . GenerateRandomInteger ( 1 , 4 ), 0 );
}
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
2014-05-18 22:49:27 +02:00
Player -> GetStatManager (). AddValue ( statDamageDealt , ( StatValue ) floor ( a_TDI . FinalDamage * 10 + 0.5 ));
2013-12-22 20:04:17 +00:00
}
2014-08-22 11:49:49 +02:00
2014-08-31 11:28:42 +02:00
if ( IsPlayer ())
{
2014-08-21 12:08:38 +02:00
double TotalEPF = 0.0 ;
double EPFProtection = 0.00 ;
double EPFFireProtection = 0.00 ;
double EPFBlastProtection = 0.00 ;
double EPFProjectileProtection = 0.00 ;
double EPFFeatherFalling = 0.00 ;
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-08-21 12:08:38 +02:00
{
2014-09-01 14:35:52 +02:00
const cItem & Item = ArmorItems [ i ];
int Level = Item . m_Enchantments . GetLevel ( cEnchantments :: enchProtection );
2014-09-01 14:31:05 +02:00
if ( Level > 0 )
2014-08-22 11:49:49 +02:00
{
2014-09-01 14:31:05 +02:00
EPFProtection += ( 6 + Level * Level ) * 0.75 / 3 ;
2014-08-22 11:49:49 +02:00
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:35:52 +02:00
Level = Item . m_Enchantments . GetLevel ( cEnchantments :: enchFireProtection );
2014-09-01 14:31:05 +02:00
if ( Level > 0 )
2014-08-22 11:49:49 +02:00
{
2014-09-01 14:31:05 +02:00
EPFFireProtection += ( 6 + Level * Level ) * 1.25 / 3 ;
2014-08-22 11:49:49 +02:00
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:35:52 +02:00
Level = Item . m_Enchantments . GetLevel ( cEnchantments :: enchFeatherFalling );
2014-09-01 14:31:05 +02:00
if ( Level > 0 )
2014-08-22 11:49:49 +02:00
{
2014-09-01 14:31:05 +02:00
EPFFeatherFalling += ( 6 + Level * Level ) * 2.5 / 3 ;
2014-08-22 11:49:49 +02:00
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:35:52 +02:00
Level = Item . m_Enchantments . GetLevel ( cEnchantments :: enchBlastProtection );
2014-09-01 14:31:05 +02:00
if ( Level > 0 )
2014-08-22 11:49:49 +02:00
{
2014-09-01 14:31:05 +02:00
EPFBlastProtection += ( 6 + Level * Level ) * 1.5 / 3 ;
2014-08-22 11:49:49 +02:00
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:35:52 +02:00
Level = Item . m_Enchantments . GetLevel ( cEnchantments :: enchProjectileProtection );
2014-09-01 14:31:05 +02:00
if ( Level > 0 )
2014-08-22 11:49:49 +02:00
{
2014-09-01 14:31:05 +02:00
EPFProjectileProtection += ( 6 + Level * Level ) * 1.5 / 3 ;
2014-08-22 11:49:49 +02:00
}
2014-08-21 12:08:38 +02:00
}
2014-08-22 11:49:49 +02:00
TotalEPF = EPFProtection + EPFFireProtection + EPFFeatherFalling + EPFBlastProtection + EPFProjectileProtection ;
2014-08-21 12:08:38 +02:00
EPFProtection = EPFProtection / TotalEPF ;
EPFFireProtection = EPFFireProtection / TotalEPF ;
EPFFeatherFalling = EPFFeatherFalling / TotalEPF ;
EPFBlastProtection = EPFBlastProtection / TotalEPF ;
EPFProjectileProtection = EPFProjectileProtection / TotalEPF ;
2014-09-01 14:31:05 +02:00
if ( TotalEPF > 25 )
{
TotalEPF = 25 ;
}
2014-08-21 12:08:38 +02:00
cFastRandom Random ;
2014-09-01 14:31:05 +02:00
float RandomValue = Random . GenerateRandomInteger ( 50 , 100 ) * 0.01f ;
2014-08-21 12:08:38 +02:00
2014-08-22 11:49:49 +02:00
TotalEPF = ceil ( TotalEPF * RandomValue );
2014-08-21 12:08:38 +02:00
2014-09-01 14:31:05 +02:00
if ( TotalEPF > 20 )
{
TotalEPF = 20 ;
}
2014-08-21 12:08:38 +02:00
EPFProtection = TotalEPF * EPFProtection ;
EPFFireProtection = TotalEPF * EPFFireProtection ;
EPFFeatherFalling = TotalEPF * EPFFeatherFalling ;
EPFBlastProtection = TotalEPF * EPFBlastProtection ;
EPFProjectileProtection = TotalEPF * EPFProjectileProtection ;
int RemovedDamage = 0 ;
2014-09-01 14:31:05 +02:00
if (( a_TDI . DamageType != dtInVoid ) && ( a_TDI . DamageType != dtAdmin ))
{
RemovedDamage += ( int ) ceil ( EPFProtection * 0.04 * a_TDI . FinalDamage );
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:31:05 +02:00
if (( a_TDI . DamageType == dtFalling ) || ( a_TDI . DamageType == dtFall ) || ( a_TDI . DamageType == dtEnderPearl ))
{
RemovedDamage += ( int ) ceil ( EPFFeatherFalling * 0.04 * a_TDI . FinalDamage );
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:31:05 +02:00
if ( a_TDI . DamageType == dtBurning )
{
RemovedDamage += ( int ) ceil ( EPFFireProtection * 0.04 * a_TDI . FinalDamage );
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:31:05 +02:00
if ( a_TDI . DamageType == dtExplosion )
{
RemovedDamage += ( int ) ceil ( EPFBlastProtection * 0.04 * a_TDI . FinalDamage );
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:31:05 +02:00
if ( a_TDI . DamageType == dtProjectile )
{
RemovedDamage += ( int ) ceil ( EPFBlastProtection * 0.04 * a_TDI . FinalDamage );
}
2014-08-21 12:08:38 +02:00
2014-09-01 14:31:05 +02:00
if ( a_TDI . FinalDamage < RemovedDamage )
{
RemovedDamage = 0 ;
}
2014-08-22 11:49:49 +02:00
2014-08-21 12:08:38 +02:00
a_TDI . FinalDamage -= RemovedDamage ;
}
2014-08-22 11:49:49 +02:00
2013-07-01 10:39:56 +00:00
m_Health -= ( short ) a_TDI . FinalDamage ;
// TODO: Apply damage to armor
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:
if (( IsMob () || IsPlayer ()) && ( a_TDI . Attacker != NULL ))
2014-01-12 13:28:37 +00:00
{
2014-07-17 22:23:08 +02:00
int KnockbackLevel = a_TDI . Attacker -> GetEquippedWeapon (). m_Enchantments . GetLevel ( cEnchantments :: enchKnockback ); // More common enchantment
2014-07-17 14:32:52 +01:00
if ( KnockbackLevel < 1 )
2014-04-23 18:15:04 +04:00
{
2014-07-17 14:32:52 +01:00
// We support punch on swords and vice versa! :)
2014-04-23 18:15:04 +04:00
KnockbackLevel = a_TDI . Attacker -> GetEquippedWeapon (). m_Enchantments . GetLevel ( cEnchantments :: enchPunch );
}
2014-07-17 14:32:52 +01:00
Vector3d AdditionalSpeed ( 0 , 0 , 0 );
2014-04-23 18:15:04 +04:00
switch ( KnockbackLevel )
{
2014-07-17 14:32:52 +01:00
case 1 : AdditionalSpeed . Set ( 5 , 0.3 , 5 ); break ;
2014-07-17 23:15:53 +02:00
case 2 : AdditionalSpeed . Set ( 8 , 0.3 , 8 ); break ;
2014-07-17 14:32:52 +01:00
default : break ;
2014-04-23 18:15:04 +04:00
}
2014-08-22 11:49:49 +02:00
AddSpeed ( a_TDI . Knockback + AdditionalSpeed );
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
if ( a_TDI . Attacker != NULL )
{
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
// Ref: http://www.minecraftwiki.net/wiki/Damage#Dealing_damage as of 2012_12_20
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 ;
}
2014-04-25 19:49:08 -07:00
bool cEntity :: ArmorCoversAgainst ( eDamageType a_DamageType )
2013-07-01 10:39:56 +00:00
{
// Ref.: http://www.minecraftwiki.net/wiki/Armor#Effects as of 2012_12_20
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
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 ;
}
case dtAttack :
case dtArrowAttack :
case dtCactusContact :
case dtLavaContact :
case dtFireContact :
case dtEnderPearl :
case dtExplosion :
{
return true ;
2013-07-01 10:39:56 +00:00
}
}
2014-04-26 09:21:49 -07:00
ASSERT ( ! "Invalid damage type!" );
2014-04-27 23:12:52 +02:00
return false ;
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
// Filter out damage types that are not protected by armor:
if ( ! ArmorCoversAgainst ( a_DamageType )) return 0 ;
2013-07-01 10:39:56 +00:00
// Add up all armor points:
// Ref.: http://www.minecraftwiki.net/wiki/Armor#Defense_points as of 2012_12_20
int ArmorValue = 0 ;
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 ;
case E_ITEM_DIAMOND_HELMET : ArmorValue += 3 ; break ;
}
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 ;
case E_ITEM_DIAMOND_CHESTPLATE : ArmorValue += 8 ; break ;
}
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 ;
case E_ITEM_DIAMOND_LEGGINGS : ArmorValue += 6 ; break ;
}
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 ;
case E_ITEM_DIAMOND_BOOTS : ArmorValue += 3 ; break ;
}
// TODO: Special armor cases, such as wool, saddles, dog's collar
// Ref.: http://www.minecraftwiki.net/wiki/Armor#Mob_armor as of 2012_12_20
// Now ArmorValue is in [0, 20] range, which corresponds to [0, 80%] protection. Calculate the hitpoints from that:
return a_Damage * ( ArmorValue * 4 ) / 100 ;
}
double cEntity :: GetKnockbackAmountAgainst ( const cEntity & a_Receiver )
{
// Returns the knockback amount that the currently equipped items would cause to a_Receiver on a hit
// TODO: Enchantments
return 1 ;
}
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 );
2013-07-01 10:39:56 +00:00
if ( m_Health > 0 )
{
// Plugin wants to 'unkill' the pawn. Abort
return ;
}
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
}
2013-04-13 21:02:10 +00:00
void cEntity :: Tick ( float a_Dt , cChunk & a_Chunk )
2012-06-14 13:06:06 +00:00
{
2014-07-20 01:43:07 -07:00
m_TicksAlive ++ ;
2014-04-26 00:32:30 +02:00
if ( m_InvulnerableTicks > 0 )
{
m_InvulnerableTicks -- ;
}
2013-03-03 19:05:11 +00:00
if ( m_AttachedTo != NULL )
{
2014-05-12 21:38:52 +03:00
Vector3d DeltaPos = m_Pos - m_AttachedTo -> GetPosition ();
if ( DeltaPos . Length () > 0.5 )
2013-03-03 19:05:11 +00:00
{
SetPosition ( m_AttachedTo -> GetPosition ());
2014-05-12 21:38:52 +03:00
if ( IsPlayer ())
{
cPlayer * Player = ( cPlayer * ) this ;
Player -> UpdateMovementStats ( DeltaPos );
}
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
2014-05-18 22:41:42 +01:00
if ( IsMob () || IsPlayer () || IsPickup () || IsExpOrb ())
{
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
}
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
}
2013-04-22 07:18:03 +00:00
void cEntity :: HandlePhysics ( float a_Dt , cChunk & a_Chunk )
{
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.
2013-08-26 22:27:28 +02:00
a_Dt /= 1000 ; // Convert from msec to sec
2014-04-12 13:16:48 +01:00
Vector3d NextPos = Vector3d ( GetPosX (), GetPosY (), GetPosZ ());
Vector3d NextSpeed = Vector3d ( GetSpeedX (), GetSpeedY (), GetSpeedZ ());
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
2014-04-12 13:16:48 +01:00
AddSpeedY ( m_Gravity * a_Dt );
AddPosition ( GetSpeed () * a_Dt );
2013-06-18 08:24:34 +00:00
return ;
}
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[]
if ( IsNoAirSurrounding )
{
NextPos . y += 0.5 ;
}
2013-09-13 19:54:50 +01:00
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 )
{
float fallspeed ;
if ( IsBlockWater ( BlockIn ))
2013-04-22 07:18:03 +00:00
{
2014-03-05 22:12:48 +00:00
fallspeed = m_Gravity * a_Dt / 3 ; // Fall 3x slower in water.
}
else if ( BlockIn == E_BLOCK_COBWEB )
{
NextSpeed . y *= 0.05 ; // Reduce overall falling speed
fallspeed = 0 ; // No falling.
2013-04-22 07:18:03 +00:00
}
else
{
2014-03-05 22:12:48 +00:00
// Normal gravity
fallspeed = m_Gravity * a_Dt ;
}
NextSpeed . y += fallspeed ;
}
2014-08-04 01:34:12 +02:00
// Friction
if ( NextSpeed . SqrLength () > 0.0004f )
2014-03-05 22:12:48 +00:00
{
2014-08-04 01:34:12 +02:00
NextSpeed . x *= 0.7f / ( 1 + a_Dt );
if ( fabs ( NextSpeed . x ) < 0.05 )
2014-03-05 22:12:48 +00:00
{
2014-08-04 01:34:12 +02:00
NextSpeed . x = 0 ;
}
NextSpeed . z *= 0.7f / ( 1 + a_Dt );
if ( fabs ( NextSpeed . z ) < 0.05 )
{
NextSpeed . z = 0 ;
2013-04-22 07:18:03 +00:00
}
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
2014-07-17 22:15:34 +02:00
m_WaterSpeed *= 0.9f ; // 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 :
m_WaterSpeed . x = 0.2f ;
m_bOnGround = false ;
2013-04-22 07:18:03 +00:00
break ;
2014-03-05 22:12:48 +00:00
case X_MINUS :
m_WaterSpeed . x = - 0.2f ;
m_bOnGround = false ;
break ;
case Z_PLUS :
m_WaterSpeed . z = 0.2f ;
m_bOnGround = false ;
break ;
case Z_MINUS :
m_WaterSpeed . z = - 0.2f ;
m_bOnGround = false ;
break ;
default :
break ;
}
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
2014-04-23 21:06:46 +01:00
if ( NextSpeed . SqrLength () > 0.f )
2014-03-05 22:12:48 +00:00
{
2014-04-23 21:06:46 +01:00
cTracer Tracer ( GetWorld ());
2014-04-24 22:03:47 +01:00
// Distance traced is an integer, so we round up from the distance we should go (Speed * Delta), else we will encounter collision detection failurse
int DistanceToTrace = ( int )( ceil (( NextSpeed * a_Dt ). SqrLength ()) * 2 );
bool HasHit = Tracer . Trace ( NextPos , NextSpeed , DistanceToTrace );
2014-04-23 21:06:46 +01:00
if ( HasHit )
2013-04-22 07:18:03 +00:00
{
2014-04-23 21:06:46 +01:00
// Oh noez! We hit something: verify that the (hit position - current) was smaller or equal to the (position that we should travel without obstacles - current)
// This is because previously, we traced with a length that was rounded up (due to integer limitations), and in the case that something was hit, we don't want to overshoot our projected movement
2014-03-19 23:07:16 +00:00
if (( Tracer . RealHit - NextPos ). SqrLength () <= ( NextSpeed * a_Dt ). SqrLength ())
2013-04-22 07:18:03 +00:00
{
2014-04-23 21:06:46 +01:00
// Block hit was within our projected path
// Begin by stopping movement in the direction that we hit something. The Normal is the line perpendicular to a 2D face and in this case, stores what block face was hit through either -1 or 1.
2014-04-24 22:03:47 +01:00
// For example: HitNormal.y = -1 : BLOCK_FACE_YM; HitNormal.y = 1 : BLOCK_FACE_YP
2014-03-19 23:07:16 +00:00
if ( Tracer . HitNormal . x != 0.f ) NextSpeed . x = 0.f ;
if ( Tracer . HitNormal . y != 0.f ) NextSpeed . y = 0.f ;
if ( Tracer . HitNormal . z != 0.f ) NextSpeed . z = 0.f ;
2014-07-17 22:15:34 +02:00
if ( Tracer . HitNormal . y == 1 ) // Hit BLOCK_FACE_YP, we are on the ground
2013-04-22 07:18:03 +00:00
{
2014-03-19 23:07:16 +00:00
m_bOnGround = true ;
2013-09-12 23:31:26 +01:00
}
2014-04-23 21:06:46 +01:00
// Now, set our position to the hit block (i.e. move part way along our intended trajectory)
NextPos . Set ( Tracer . RealHit . x , Tracer . RealHit . y , Tracer . RealHit . z );
NextPos . x += Tracer . HitNormal . x * 0.1 ;
NextPos . y += Tracer . HitNormal . y * 0.05 ;
NextPos . z += Tracer . HitNormal . z * 0.1 ;
2013-04-22 07:18:03 +00:00
}
else
2013-06-18 08:24:34 +00:00
{
2014-04-23 21:06:46 +01:00
// We have hit a block but overshot our intended trajectory, move normally, safe in the warm cocoon of knowledge that we won't appear to teleport forwards on clients,
// and that this piece of software will come to be hailed as the epitome of performance and functionality in C++, never before seen, and of such a like that will never
// be henceforth seen again in the time of programmers and man alike
// </&sensationalist>
2013-04-22 07:18:03 +00:00
NextPos += ( NextSpeed * a_Dt );
}
}
2014-03-05 22:12:48 +00:00
else
2013-04-22 07:18:03 +00:00
{
2014-03-05 22:12:48 +00:00
// We didn't hit anything, so move =]
NextPos += ( NextSpeed * a_Dt );
2013-04-22 07:18:03 +00: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
}
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
}
2013-07-01 18:37:27 +00: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 )
{
2014-04-22 02:36:39 -10:00
if ( ! m_IsFireproof )
{
TakeDamage ( dtOnFire , NULL , BURN_DAMAGE , 0 );
}
2013-07-01 18:37:27 +00:00
m_TicksSinceLastBurnDamage = 0 ;
2013-07-01 10:39:56 +00:00
}
m_TicksLeftBurning -- ;
}
// Update the burning times, based on surroundings:
int MinRelX = ( int ) floor ( GetPosX () - m_Width / 2 ) - a_Chunk . GetPosX () * cChunkDef :: Width ;
int MaxRelX = ( int ) floor ( GetPosX () + m_Width / 2 ) - a_Chunk . GetPosX () * cChunkDef :: Width ;
int MinRelZ = ( int ) floor ( GetPosZ () - m_Width / 2 ) - a_Chunk . GetPosZ () * cChunkDef :: Width ;
int MaxRelZ = ( int ) floor ( GetPosZ () + m_Width / 2 ) - a_Chunk . GetPosZ () * cChunkDef :: Width ;
2014-04-17 18:50:25 +01:00
int MinY = std :: max ( 0 , std :: min ( cChunkDef :: Height - 1 , POSY_TOINT ));
2013-07-01 10:39:56 +00:00
int MaxY = std :: max ( 0 , std :: min ( cChunkDef :: Height - 1 , ( int ) ceil ( GetPosY () + m_Height )));
bool HasWater = false ;
bool HasLava = false ;
bool HasFire = false ;
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 );
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
if ( HasWater )
{
// Extinguish the fire
m_TicksLeftBurning = 0 ;
}
if ( HasLava )
{
// Burn:
m_TicksLeftBurning = BURN_TICKS ;
// 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
{
2014-04-22 02:36:39 -10:00
if ( ! m_IsFireproof )
{
TakeDamage ( dtLavaContact , NULL , LAVA_DAMAGE , 0 );
}
2013-07-01 10:39:56 +00:00
m_TicksSinceLastLavaDamage = 0 ;
}
}
else
{
m_TicksSinceLastLavaDamage = 0 ;
}
if ( HasFire )
{
// Burn:
m_TicksLeftBurning = BURN_TICKS ;
// Periodically damage:
m_TicksSinceLastFireDamage ++ ;
if ( m_TicksSinceLastFireDamage >= FIRE_TICKS_PER_DAMAGE )
{
2014-04-22 02:36:39 -10:00
if ( ! m_IsFireproof )
{
TakeDamage ( dtFireContact , NULL , FIRE_DAMAGE , 0 );
}
2013-07-01 18:37:27 +00:00
m_TicksSinceLastFireDamage = 0 ;
2013-07-01 10:39:56 +00:00
}
}
else
{
m_TicksSinceLastFireDamage = 0 ;
}
// 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 )
{
TakeDamage ( dtInVoid , NULL , 2 , 0 );
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 )) ||
2014-05-19 22:15:39 +01:00
((( GetPosY () - Y < 1 ) && ( GetWorld () -> GetBlock ( X , Y , Z ) == E_BLOCK_CACTUS ))))
2014-05-18 22:41:42 +01:00
)
{
2014-05-19 07:31:53 +01:00
TakeDamage ( dtCactusContact , NULL , 1 , 0 );
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
{
2014-07-20 10:46:45 +01:00
if ( GetWorld () -> GetDimension () == dimOverworld )
2014-06-04 20:00:55 +01:00
{
2014-07-21 22:49:06 +01:00
if ( GetWorld () -> GetNetherWorldName (). empty () && GetWorld () -> GetEndWorldName (). empty ())
{
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
}
2014-07-21 22:49:06 +01:00
if ( IsPlayer () && ! (( 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
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
}
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
{
2014-08-02 21:44:02 -07:00
(( cPlayer * ) this ) -> GetClientHandle () -> SendRespawn ( dimOverworld ); // Send a respawn packet before world is loaded/generated so the client isn't left in limbo
2014-05-31 22:28:51 +01:00
}
2014-08-02 21:44:02 -07:00
return MoveToWorld ( cRoot :: Get () -> CreateAndInitializeWorld ( GetWorld () -> GetLinkedOverworldName ()), false );
}
else
{
if ( GetWorld () -> GetNetherWorldName (). empty ())
{
return false ;
}
m_PortalCooldownData . m_ShouldPreventTeleportation = true ;
if ( IsPlayer ())
{
(( cPlayer * ) this ) -> AwardAchievement ( achEnterPortal );
(( cPlayer * ) this ) -> GetClientHandle () -> SendRespawn ( dimNether );
}
return MoveToWorld ( cRoot :: Get () -> CreateAndInitializeWorld ( GetWorld () -> GetNetherWorldName (), dimNether , GetWorld () -> GetName ()), false );
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
}
2014-08-02 21:44:02 -07:00
if ( GetWorld () -> GetDimension () == dimEnd )
2014-05-31 22:28:51 +01: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 ;
}
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
{
2014-08-02 21:44:02 -07:00
cPlayer * Player = ( cPlayer * ) this ;
Player -> TeleportToCoords ( Player -> GetLastBedPos (). x , Player -> GetLastBedPos (). y , Player -> GetLastBedPos (). z );
Player -> GetClientHandle () -> SendRespawn ( dimOverworld );
}
2014-07-20 10:46:45 +01:00
2014-08-02 21:44:02 -07:00
return MoveToWorld ( cRoot :: Get () -> CreateAndInitializeWorld ( GetWorld () -> GetLinkedOverworldName ()), false );
}
else
{
if ( GetWorld () -> GetEndWorldName (). empty ())
{
return false ;
}
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 ())
{
(( cPlayer * ) this ) -> AwardAchievement ( achEnterTheEnd );
(( cPlayer * ) this ) -> GetClientHandle () -> SendRespawn ( dimEnd );
2014-05-31 22:28:51 +01:00
}
2014-08-02 21:44:02 -07:00
return MoveToWorld ( cRoot :: Get () -> CreateAndInitializeWorld ( GetWorld () -> GetEndWorldName (), dimEnd , GetWorld () -> GetName ()), false );
2014-05-31 22:28:51 +01:00
}
2014-08-02 21:44:02 -07: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
}
2014-07-23 21:12:59 +01:00
bool cEntity :: DoMoveToWorld ( cWorld * a_World , bool a_ShouldSendRespawn )
2014-05-31 22:28:51 +01:00
{
2014-06-12 15:21:07 +01:00
UNUSED ( a_ShouldSendRespawn );
2014-07-22 17:26:48 +01:00
ASSERT ( a_World != NULL );
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 ;
}
// Remove all links to the old world
2014-07-23 21:12:59 +01:00
SetWorldTravellingFrom ( GetWorld ()); // cChunk::Tick() handles entity removal
2014-05-31 22:28:51 +01:00
GetWorld () -> BroadcastDestroyEntity ( * this );
2014-06-12 15:21:07 +01:00
// Queue add to new world
2014-07-21 22:49:06 +01:00
a_World -> AddEntity ( this );
2014-07-23 21:12:59 +01:00
SetWorld ( a_World );
2014-05-31 22:28:51 +01:00
return true ;
}
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 );
if ( World == NULL )
{
LOG ( "%s: Couldn't find world \" %s \" ." , __FUNCTION__ , a_WorldName . c_str ());
return false ;
}
2014-07-23 21:12:59 +01:00
return DoMoveToWorld ( World , a_ShouldSendRespawn );
2014-07-21 22:49:06 +01:00
}
2014-01-24 23:58:51 +00:00
void cEntity :: SetSwimState ( cChunk & a_Chunk )
{
2014-03-05 22:12:48 +00:00
int RelY = ( int ) floor ( 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
// Ref.: http://forum.mc-server.org/showthread.php?tid=1244
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 );
}
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 );
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:
2014-08-22 11:49:49 +02:00
int RespirationLevel = GetEquippedHelmet (). m_Enchantments . GetLevel ( cEnchantments :: enchRespiration );
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 )
{
(( cPawn * ) this ) -> AddEntityEffect ( cEntityEffect :: effNightVision , 200 , 5 , 0 );
}
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-01-24 23:58:51 +00:00
TakeDamage ( dtDrowning , NULL , 1 , 1 , 0 );
// 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
/// Called when the entity starts burning
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
}
/// Called when the entity finishes burning
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
}
/// Sets the maximum value for the health
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-22 12:59:31 -10:00
/// Sets whether the entity is fireproof
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
/// Puts the entity on fire for the specified amount of ticks
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 ;
}
m_TicksLeftBurning = a_TicksLeftBurning ;
OnStartedBurning ();
}
/// Stops the entity from burning, resets all burning timers
void cEntity :: StopBurning ( void )
{
bool HasBeenBurning = ( m_TicksLeftBurning > 0 );
m_TicksLeftBurning = 0 ;
m_TicksSinceLastBurnDamage = 0 ;
m_TicksSinceLastFireDamage = 0 ;
m_TicksSinceLastLavaDamage = 0 ;
// 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 )
{
SetPosition ( a_PosX , a_PosY , a_PosZ );
m_World -> BroadcastTeleportEntity ( * this );
}
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 ;
}
// 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 = ( int )( floor ( GetPosX () * 32.0 ) - floor ( m_LastPos . x * 32.0 ));
int DiffY = ( int )( floor ( GetPosY () * 32.0 ) - floor ( m_LastPos . y * 32.0 ));
int DiffZ = ( int )( floor ( GetPosZ () * 32.0 ) - floor ( m_LastPos . z * 32.0 ));
2014-07-17 22:15:34 +02:00
if (( DiffX != 0 ) || ( DiffY != 0 ) || ( DiffZ != 0 )) // Have we moved?
2014-04-23 21:06:46 +01:00
{
2014-07-17 22:15:34 +02:00
if (( abs ( DiffX ) <= 127 ) && ( abs ( DiffY ) <= 127 ) && ( abs ( DiffZ ) <= 127 )) // Limitations of a Byte
2013-03-22 06:33:10 +00:00
{
2014-04-23 21:06:46 +01:00
// Difference within Byte limitations, use a relative move packet
2013-03-22 06:33:10 +00:00
if ( m_bDirtyOrientation )
{
2014-04-23 21:06:46 +01:00
m_World -> BroadcastEntityRelMoveLook ( * this , ( char ) DiffX , ( char ) DiffY , ( char ) DiffZ , a_Exclude );
2013-03-22 06:33:10 +00:00
m_bDirtyOrientation = false ;
}
else
{
2014-04-23 21:06:46 +01:00
m_World -> BroadcastEntityRelMove ( * this , ( char ) DiffX , ( char ) DiffY , ( char ) DiffZ , a_Exclude );
2013-03-22 06:33:10 +00:00
}
2014-04-23 21:06:46 +01:00
// 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_LastPos stores this position
m_LastPos = GetPosition ();
2013-03-22 06:33:10 +00:00
}
2013-04-02 06:48:31 +00:00
else
{
2014-04-23 21:06:46 +01:00
// Too big a movement, do a teleport
m_World -> BroadcastTeleportEntity ( * this , a_Exclude );
2014-07-17 22:15:34 +02:00
m_LastPos = GetPosition (); // See above
2014-04-23 21:06:46 +01:00
m_bDirtyOrientation = false ;
}
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
}
}
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-01-12 23:23:36 +00:00
if ( m_AttachedTo != NULL )
{
// Detach from any previous entity:
Detach ();
}
2013-04-02 06:48:31 +00:00
2013-03-03 19:05:11 +00:00
// Attach to the new entity:
m_AttachedTo = a_AttachTo ;
a_AttachTo -> m_Attachee = this ;
m_World -> BroadcastAttachEntity ( * this , a_AttachTo );
}
void cEntity :: Detach ( void )
{
if ( m_AttachedTo == NULL )
{
// Attached to no entity, our work is done
return ;
}
m_AttachedTo -> m_Attachee = NULL ;
m_AttachedTo = NULL ;
m_World -> BroadcastAttachEntity ( * this , NULL );
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
{
2012-12-21 12:21:20 +00:00
return ( strcmp ( a_ClassName , "cEntity" ) == 0 );
2012-06-14 13:06:06 +00:00
}
2013-03-03 19:05:11 +00:00
void cEntity :: SetRot ( const Vector3f & a_Rot )
2012-06-14 13:06:06 +00:00
{
m_Rot = a_Rot ;
m_bDirtyOrientation = true ;
}
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 :: AddPosX ( double a_AddPosX )
{
m_Pos . x += a_AddPosX ;
}
void cEntity :: AddPosY ( double a_AddPosY )
{
m_Pos . y += a_AddPosY ;
}
void cEntity :: AddPosZ ( double a_AddPosZ )
{
m_Pos . z += a_AddPosZ ;
}
void cEntity :: AddPosition ( double a_AddPosX , double a_AddPosY , double a_AddPosZ )
{
m_Pos . x += a_AddPosX ;
m_Pos . y += a_AddPosY ;
m_Pos . z += a_AddPosZ ;
}
void cEntity :: AddSpeed ( double a_AddSpeedX , double a_AddSpeedY , double a_AddSpeedZ )
{
m_Speed . x += a_AddSpeedX ;
m_Speed . y += a_AddSpeedY ;
2014-07-17 22:50:58 +02:00
m_Speed . z += a_AddSpeedZ ;
2013-03-23 04:33:47 +00:00
WrapSpeed ();
}
void cEntity :: AddSpeedX ( double a_AddSpeedX )
{
2014-07-17 22:50:58 +02:00
m_Speed . x += a_AddSpeedX ;
2013-03-23 04:33:47 +00:00
WrapSpeed ();
}
void cEntity :: AddSpeedY ( double a_AddSpeedY )
{
2014-07-17 22:50:58 +02:00
m_Speed . y += a_AddSpeedY ;
2013-03-23 04:33:47 +00:00
WrapSpeed ();
}
void cEntity :: AddSpeedZ ( double a_AddSpeedZ )
2013-03-03 19:05:11 +00:00
{
2014-07-17 22:50:58 +02:00
m_Speed . z += a_AddSpeedZ ;
2013-03-22 06:33:10 +00:00
WrapSpeed ();
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 )
{
if ( m_AttachedTo == NULL )
{
return ;
}
if (( a_Forward != 0 ) || ( a_Sideways != 0 ))
{
2013-12-16 18:02:33 +01:00
m_AttachedTo -> HandleSpeedFromAttachee ( a_Forward , a_Sideways );
2013-09-06 00:04:49 +02:00
}
}
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
2012-12-21 10:59:59 +00:00
void cEntity :: SetPosition ( double a_PosX , double a_PosY , double a_PosZ )
2012-06-14 13:06:06 +00:00
{
2014-07-17 22:50:58 +02:00
m_Pos . Set ( a_PosX , a_PosY , a_PosZ );
2012-06-14 13:06:06 +00:00
}
2012-12-21 10:59:59 +00:00
void cEntity :: SetPosX ( double a_PosX )
2012-06-14 13:06:06 +00:00
{
2014-07-17 22:50:58 +02:00
m_Pos . x = a_PosX ;
2012-06-14 13:06:06 +00:00
}
2012-12-21 10:59:59 +00:00
void cEntity :: SetPosY ( double a_PosY )
2012-06-14 13:06:06 +00:00
{
2014-07-17 22:50:58 +02:00
m_Pos . y = a_PosY ;
2012-06-14 13:06:06 +00:00
}
2012-12-21 10:59:59 +00:00
void cEntity :: SetPosZ ( double a_PosZ )
2012-06-14 13:06:06 +00:00
{
m_Pos . z = a_PosZ ;
}