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"
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 )
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-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:
2013-03-02 19:57:09 +00: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 )
{
Heading = a_Attacker - > GetLookVector ( ) * ( a_Attacker - > IsSprinting ( ) ? 10 : 8 ) ;
}
2013-12-24 15:02:35 +00:00
Heading . y = 2 ;
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 ;
double xz = sqrt ( m_Speed . x * m_Speed . x + m_Speed . z * m_Speed . z ) ; // Speed XZ-plane component
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 ( cRoot : : Get ( ) - > GetPluginManager ( ) - > CallHookTakeDamage ( * this , a_TDI ) )
{
2014-04-26 00:32:30 +02:00
return false ;
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
}
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 ;
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-07-14 13:46:15 -07:00
if ( ! Player - > IsOnGround ( ) )
2013-12-22 20:04:17 +00:00
{
2014-07-14 13:46:15 -07:00
if ( ( a_TDI . DamageType = = dtAttack ) | | ( a_TDI . DamageType = = dtArrowAttack ) )
{
a_TDI . FinalDamage + = 2 ;
m_World - > BroadcastEntityAnimation ( * this , 4 ) ; // Critical hit
}
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
}
2013-07-01 10:39:56 +00:00
m_Health - = ( short ) a_TDI . FinalDamage ;
// TODO: Apply damage to armor
if ( m_Health < 0 )
{
m_Health = 0 ;
}
2014-04-23 18:15:04 +04:00
if ( ( IsMob ( ) | | IsPlayer ( ) ) & & ( a_TDI . Attacker ! = NULL ) ) // Knockback for only players and mobs
2014-01-12 13:28:37 +00:00
{
2014-07-17 14:32:52 +01:00
int KnockbackLevel = a_TDI . Attacker - > GetEquippedWeapon ( ) . m_Enchantments . GetLevel ( cEnchantments : : enchKnockback ) ; // More common enchantment
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 ;
case 2 : AdditionalSpeed . Set ( 8 , 0.3 , 8 ) ; break ;
default : break ;
2014-04-23 18:15:04 +04:00
}
2014-07-17 14:32:52 +01: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 ;
if ( m_Health > m_MaxHealth )
{
m_Health = m_MaxHealth ;
}
}
2013-07-07 15:09:05 +00:00
void cEntity : : SetHealth ( int a_Health )
{
m_Health = std : : max ( 0 , std : : min ( m_MaxHealth , a_Health ) ) ;
}
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-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-03-05 22:12:48 +00:00
}
2014-04-12 13:16:48 +01:00
// None of the above functions change position, we remain in the chunk of NextChunk
HandlePhysics ( a_Dt , * NextChunk ) ;
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-04-12 13:16:48 +01:00
// Outside of the world
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 ) ;
BLOCKTYPE BlockIn = NextChunk - > GetBlock ( RelBlockX , BlockY , RelBlockZ ) ;
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 ;
}
else
{
// Friction
if ( NextSpeed . SqrLength ( ) > 0.0004f )
{
NextSpeed . x * = 0.7f / ( 1 + a_Dt ) ;
if ( fabs ( NextSpeed . x ) < 0.05 )
2013-04-22 07:18:03 +00:00
{
2014-03-05 22:12:48 +00: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 ;
}
2013-05-21 05:51:38 +00:00
2014-03-05 22:12:48 +00:00
//Get water direction
Direction WaterDir = m_World - > GetWaterSimulator ( ) - > GetFlowingDirection ( BlockX , BlockY , BlockZ ) ;
2013-04-22 07:18:03 +00:00
2014-03-05 22:12:48 +00:00
m_WaterSpeed * = 0.9f ; //Reduce speed each tick
2013-04-22 07:18:03 +00:00
2014-03-05 22:12:48 +00:00
switch ( WaterDir )
{
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-04-23 21:06:46 +01: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-04-23 18:15:04 +04:00
if ( m_World - > IsWeatherWet ( ) )
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-04-21 13:13:25 +04: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-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:
if ( IsSubmerged ( ) )
{
2014-06-27 23:13:26 +01:00
if ( ! IsPlayer ( ) ) // Players control themselves
{
SetSpeedY ( 1 ) ; // Float in the water
}
2014-01-24 23:58:51 +00:00
// Either reduce air level or damage player
if ( m_AirLevel < 1 )
{
if ( m_AirTickTimer < 1 )
{
// Damage player
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 ;
}
}
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:
if ( m_Health > a_MaxHealth )
{
m_Health = a_MaxHealth ;
}
}
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-05-05 23:45:35 +01:00
if ( ( DiffX ! = 0 ) | | ( DiffY ! = 0 ) | | ( DiffZ ! = 0 ) ) // Have we moved?
2014-04-23 21:06:46 +01: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 ) ;
m_LastPos = GetPosition ( ) ; // See above
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 )
{
if ( a_Mass > 0 )
{
m_Mass = a_Mass ;
}
else
{
2013-08-26 22:27:28 +02: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.
2013-04-28 18:54:43 +00:00
m_Mass = 0.001 ;
}
}
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 )
{
2014-06-22 20:44:18 +01:00
m_Pos . x + = a_AddPosX ;
2013-03-23 04:33:47 +00:00
}
void cEntity : : AddPosY ( double a_AddPosY )
{
2014-06-22 20:44:18 +01:00
m_Pos . y + = a_AddPosY ;
2013-03-23 04:33:47 +00:00
}
void cEntity : : AddPosZ ( double a_AddPosZ )
{
2014-06-22 20:44:18 +01:00
m_Pos . z + = a_AddPosZ ;
2013-03-23 04:33:47 +00:00
}
void cEntity : : AddPosition ( double a_AddPosX , double a_AddPosY , double a_AddPosZ )
{
m_Pos . x + = a_AddPosX ;
m_Pos . y + = a_AddPosY ;
2014-06-22 20:44:18 +01:00
m_Pos . z + = a_AddPosZ ;
2013-03-23 04:33:47 +00:00
}
void cEntity : : AddSpeed ( double a_AddSpeedX , double a_AddSpeedY , double a_AddSpeedZ )
{
m_Speed . x + = a_AddSpeedX ;
m_Speed . y + = a_AddSpeedY ;
2014-04-23 21:06:46 +01:00
m_Speed . z + = a_AddSpeedZ ;
2013-03-23 04:33:47 +00:00
WrapSpeed ( ) ;
}
void cEntity : : AddSpeedX ( double a_AddSpeedX )
{
2014-04-23 21:06:46 +01:00
m_Speed . x + = a_AddSpeedX ;
2013-03-23 04:33:47 +00:00
WrapSpeed ( ) ;
}
void cEntity : : AddSpeedY ( double a_AddSpeedY )
{
2014-04-23 21:06:46 +01: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-04-23 21:06:46 +01: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
}
}
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 ;
}
//////////////////////////////////////////////////////////////////////////
// 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-04-23 21:06:46 +01: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-04-23 21:06:46 +01: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-04-23 21:06:46 +01: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 ;
}