class ZombieBase extends DayZInfected
{
const float TARGET_CONE_ANGLE_CHASE = 20;
const float TARGET_CONE_ANGLE_FIGHT = 30;
const float ORIENTATION_SYNC_THRESHOLD = 30; //threshold for local heading/orientation sync
const float SHOCK_TO_STUN_MULTIPLIER = 2.82;
//! server / singleplayer properties
protected int m_StanceVariation = 0;
protected int m_LastMindState = -1;
protected float m_LastMovementSpeed = -1;
protected bool m_KnuckleLand = false;
protected float m_KnuckleOutTimer = 0;
protected int m_MindState = -1;
protected int m_OrientationLocal = -1; //local 'companion' value for sync checking
protected int m_OrientationSynced = -1;
protected float m_OrientationTimer;
protected float m_MovementSpeed = -1;
protected vector m_DefaultHitPosition;
protected float m_DeltaTime;
protected AbstractWave m_LastSoundVoiceAW;
protected ref InfectedSoundEventHandler m_InfectedSoundEventHandler;
protected ref array<Object> m_AllTargetObjects;
protected ref array<typename>m_TargetableObjects;
//static ref map<int,ref array<string>> m_FinisherSelectionMap; //! which selections in the FireGeometry trigger which finisher on hit (when applicable)
protected bool m_IsCrawling; //'DayZInfectedCommandCrawl' is transition to crawl only, 'DayZInfectedCommandMove' used after that, hence this VARIABLE_WET
protected bool m_FinisherInProgress = false; //is this object being backstabbed?
protected ref ArrowManagerBase m_ArrowManager;
//-------------------------------------------------------------
void ZombieBase()
{
Init();
}
void Init()
{
SetEventMask(EntityEvent.INIT | EntityEvent.CONTACT);
m_IsCrawling = false;
RegisterNetSyncVariableInt("m_MindState", -1, 4);
RegisterNetSyncVariableInt("m_OrientationSynced", 0, 359);
RegisterNetSyncVariableFloat("m_MovementSpeed", -1, 3);
RegisterNetSyncVariableBool("m_IsCrawling");
//! sets default hit position and cache it here (mainly for impact particles)
m_DefaultHitPosition = SetDefaultHitPosition(GetDayZInfectedType().GetDefaultHitPositionComponent());
//! client only
if ( !g_Game.IsDedicatedServer() )
{
m_LastSoundVoiceAW = null;
m_InfectedSoundEventHandler = new InfectedSoundEventHandler(this);
}
m_AllTargetObjects = new array<Object>;
m_TargetableObjects = new array<typename>;
m_TargetableObjects.Insert(PlayerBase);
m_TargetableObjects.Insert(AnimalBase);
m_OrientationTimer = 0;
m_ArrowManager = new ArrowManagerBase(this);
}
//! synced variable(s) handler
override void OnVariablesSynchronized()
{
DebugSound("[Infected @ " + this + "][OnVariablesSynchronized]");
HandleSoundEvents();
if ( m_OrientationLocal != m_OrientationSynced )
{
m_OrientationLocal = m_OrientationSynced;
}
}
//-------------------------------------------------------------
override void EOnInit(IEntity other, int extra)
{
if ( !g_Game.IsMultiplayer() || g_Game.IsServer() )
{
m_StanceVariation = Math.RandomInt(0, 4);
DayZInfectedCommandMove moveCommand = GetCommand_Move();
moveCommand.SetStanceVariation(m_StanceVariation);
}
}
override bool IsZombie()
{
return true;
}
override bool IsDanger()
{
return true;
}
override bool IsZombieMilitary()
{
return false;
}
bool IsMale()
{
return true;
}
override bool CanBeBackstabbed()
{
return true;
}
//-------------------------------------------------------------
override AnimBootsType GetBootsType()
{
return AnimBootsType.Boots;
}
override bool CanBeSkinned()
{
return false;
}
//-------------------------------------------------------------
override bool IsHealthVisible()
{
return false;
}
//-------------------------------------------------------------
override bool IsRefresherSignalingViable()
{
return false;
}
override bool IsSelfAdjustingTemperature()
{
return IsAlive();
}
//! returns hit component for attacking AI
override string GetHitComponentForAI()
{
return GetDayZInfectedType().GetHitComponentForAI();
}
//! returns default hit component (fallback)
override string GetDefaultHitComponent()
{
return GetDayZInfectedType().GetDefaultHitComponent();
}
override vector GetDefaultHitPosition()
{
return m_DefaultHitPosition;
}
protected vector SetDefaultHitPosition(string pSelection)
{
return GetSelectionPositionMS(pSelection);
}
//! returns suitable hit components for finisher attacks; DEPRECATED
override array<string> GetSuitableFinisherHitComponents()
{
return GetDayZInfectedType().GetSuitableFinisherHitComponents();
}
int GetMindStateSynced()
{
return m_MindState;
}
//! returns rounded zombie yaw for sync purposes
int GetOrientationSynced()
{
return m_OrientationSynced;
}
//-------------------------------------------------------------
//!
//! CommandHandler
//!
void CommandHandler(float pDt, int pCurrentCommandID, bool pCurrentCommandFinished)
{
m_DeltaTime = pDt;
//! for mods
if ( ModCommandHandlerBefore(pDt, pCurrentCommandID, pCurrentCommandFinished) )
{
return;
}
//! handle death
if ( pCurrentCommandID != DayZInfectedConstants.COMMANDID_DEATH )
{
if ( HandleDeath(pCurrentCommandID) )
return;
}
else if (!pCurrentCommandFinished)
{
return;
}
//! movement handler (just for sync now)
HandleMove(pCurrentCommandID);
HandleOrientation(pDt,pCurrentCommandID);
//! handle finished commands
if (pCurrentCommandFinished)
{
//! default behaviour after finish is to start move
DayZInfectedCommandMove moveCommand = StartCommand_Move();
moveCommand.SetStanceVariation(m_StanceVariation);
return;
}
//! for mods
if ( ModCommandHandlerInside(pDt, pCurrentCommandID, pCurrentCommandFinished) )
{
return;
}
//! crawl transition
if ( HandleCrawlTransition(pCurrentCommandID) )
{
return;
}
//! damage hits
if ( HandleDamageHit(pCurrentCommandID) )
{
return;
}
DayZInfectedInputController inputController = GetInputController();
if ( inputController )
{
if ( HandleVault(pCurrentCommandID, inputController, pDt) )
{
return;
}
if ( HandleMindStateChange(pCurrentCommandID, inputController, pDt) )
{
return;
}
if ( FightLogic(pCurrentCommandID, inputController, pDt) )
{
return;
}
}
//!
if ( ModCommandHandlerAfter(pDt, pCurrentCommandID, pCurrentCommandFinished) )
{
return;
}
}
//-------------------------------------------------------------
//!
//! CommandHandlerDebug
//!
void CommandHandlerDebug(float pDt, int pCurrentCommandID, bool pCurrentCommandFinished)
{
if ( GetPluginManager() )
{
PluginDayZInfectedDebug infectedDebug = PluginDayZInfectedDebug.Cast(GetPluginManager().GetPluginByType(PluginDayZInfectedDebug));
if ( infectedDebug )
infectedDebug.CommandHandler(this);
}
}
//-------------------------------------------------------------
//!
//! HandleMove
//!
void HandleMove(int pCurrentCommandID)
{
DayZInfectedInputController ic = GetInputController();
m_MovementSpeed = ic.GetMovementSpeed();
if (Math.AbsFloat(m_LastMovementSpeed - m_MovementSpeed) >= 0.9 && m_LastMovementSpeed != m_MovementSpeed)
{
SetSynchDirty();
}
m_LastMovementSpeed = m_MovementSpeed;
}
//-------------------------------------------------------------
//!
//! HandleOrientation
//!
void HandleOrientation(float pDt, int pCurrentCommandID)
{
m_OrientationTimer += pDt;
int yaw = Math.Round(GetOrientation()[0]);
yaw = Math.NormalizeAngle(yaw);
//atan2(sin(x-y), cos(x-y))
float angleSourceRad = m_OrientationSynced * Math.DEG2RAD;
float angleTargetRad = yaw * Math.DEG2RAD;
float angleDiffRad = Math.Atan2(Math.Sin(angleTargetRad - angleSourceRad), Math.Cos(angleSourceRad - angleTargetRad));
angleDiffRad *= Math.RAD2DEG;
angleDiffRad = Math.Round(angleDiffRad);
if (m_OrientationTimer >= 2.0 || m_OrientationSynced == -1 || Math.AbsInt(angleDiffRad) > ORIENTATION_SYNC_THRESHOLD)
{
m_OrientationTimer = 0.0;
if (m_OrientationSynced == -1 || Math.AbsInt(angleDiffRad) > 5)
{
m_OrientationSynced = yaw;
SetSynchDirty();
}
}
}
//-------------------------------------------------------------
//!
//! HandleDeath
//!
float m_DamageHitDirection = 0;
int m_DeathType = 0;
bool HandleDeath(int pCurrentCommandID)
{
if ( !IsAlive() || m_FinisherInProgress )
{
StartCommand_Death(m_DeathType, m_DamageHitDirection);
m_MovementSpeed = -1;
m_MindState = -1;
SetSynchDirty();
return true;
}
return false;
}
bool EvaluateDeathAnimationEx(EntityAI pSource, ZombieHitData data, out int pAnimType, out float pAnimHitDir)
{
bool ret = EvaluateDeathAnimation(pSource,data.m_DamageZone,data.m_AmmoType,pAnimType,pAnimHitDir);
return ret;
}
bool EvaluateDeathAnimation(EntityAI pSource, string pComponent, string pAmmoType, out int pAnimType, out float pAnimHitDir)
{
//!
bool doPhxImpulse = g_Game.ConfigGetInt("cfgAmmo " + pAmmoType + " doPhxImpulse") > 0;
//! anim type
pAnimType = doPhxImpulse;
//! direction
pAnimHitDir = ComputeHitDirectionAngle(pSource);
//! add some impulse if needed
if ( doPhxImpulse )
{
vector impulse = 80 * m_TransportHitVelocity;
impulse[1] = 80 * 1.5;
//Print("Impulse: " + impulse.ToString());
dBodyApplyImpulse(this, impulse);
}
return true;
}
//-------------------------------------------------------------
//!
//! HandleVault
//!
int m_ActiveVaultType = -1;
int GetVaultType(float height)
{
if ( height <= 0.6 )
return 0;
else if ( height <= 1.1 )
return 1;
else if ( height <= 1.6 )
return 2;
else
return 3;
}
bool HandleVault(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
{
if ( pCurrentCommandID == DayZInfectedConstants.COMMANDID_VAULT )
{
DayZInfectedCommandVault vaultCmd = GetCommand_Vault();
if ( vaultCmd && vaultCmd.WasLand() )
{
m_KnuckleOutTimer = 0;
m_KnuckleLand = true;
}
if ( m_KnuckleLand )
{
m_KnuckleOutTimer += pDt;
if ( m_KnuckleOutTimer > 2.0 )
StartCommand_Vault(-1);
}
return true;
}
if ( pInputController.IsVault() )
{
float vaultHeight = pInputController.GetVaultHeight();
int vaultType = GetVaultType(vaultHeight);
m_KnuckleLand = false;
StartCommand_Vault(vaultType);
return true;
}
return false;
}
//-------------------------------------------------------------
//!
//! Mind state change
//!
bool HandleMindStateChange(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
{
DayZInfectedCommandMove moveCommand = GetCommand_Move();
m_MindState = pInputController.GetMindState();
if ( m_LastMindState != m_MindState )
{
switch ( m_MindState )
{
case DayZInfectedConstants.MINDSTATE_CALM:
if ( moveCommand && !moveCommand.IsTurning() )
moveCommand.SetIdleState(0);
break;
case DayZInfectedConstants.MINDSTATE_DISTURBED:
if ( moveCommand && !moveCommand.IsTurning() )
moveCommand.SetIdleState(1);
break;
case DayZInfectedConstants.MINDSTATE_CHASE:
if ( moveCommand && !moveCommand.IsTurning() && (m_LastMindState < DayZInfectedConstants.MINDSTATE_CHASE) )
moveCommand.SetIdleState(2);
break;
}
m_LastMindState = m_MindState;
m_AttackCooldownTime = 0.0;
SetSynchDirty();
}
return false;
}
//-------------------------------------------------------------
//!
//! Sound (client only)
//!
protected void HandleSoundEvents()
{
//! no sound handler - bail out
if ( !m_InfectedSoundEventHandler )
{
return;
}
//! infected is dead
if ( !IsAlive() )
{
//! stop all sounds
m_InfectedSoundEventHandler.Stop();
return;
}
switch ( m_MindState )
{
case DayZInfectedConstants.MINDSTATE_CALM:
m_InfectedSoundEventHandler.PlayRequest(EInfectedSoundEventID.MINDSTATE_CALM_MOVE);
break;
case DayZInfectedConstants.MINDSTATE_ALERTED:
m_InfectedSoundEventHandler.PlayRequest(EInfectedSoundEventID.MINDSTATE_ALERTED_MOVE);
break;
case DayZInfectedConstants.MINDSTATE_DISTURBED:
m_InfectedSoundEventHandler.PlayRequest(EInfectedSoundEventID.MINDSTATE_DISTURBED_IDLE);
break
case DayZInfectedConstants.MINDSTATE_CHASE:
m_InfectedSoundEventHandler.PlayRequest(EInfectedSoundEventID.MINDSTATE_CHASE_MOVE);
break;
default:
m_InfectedSoundEventHandler.Stop();
break;
}
DebugSound("[Infected @ " + this + "][MindState]" + typename.EnumToString(DayZInfectedConstants, m_MindState));
DebugSound("[Infected @ " + this + "][SoundEventID]" + typename.EnumToString(EInfectedSoundEventID, m_InfectedSoundEventHandler.GetCurrentStateEventID()));
}
AbstractWave ProcessVoiceFX(string pSoundSetName)
{
SoundParams soundParams;
SoundObjectBuilder soundObjectBuilder;
SoundObject soundObject;
if (!g_Game.IsDedicatedServer())
{
soundParams = new SoundParams( pSoundSetName );
if ( !soundParams.IsValid() )
{
//SoundError("Invalid sound set.");
return null;
}
soundObjectBuilder = new SoundObjectBuilder( soundParams );
soundObject = soundObjectBuilder.BuildSoundObject();
AttenuateSoundIfNecessary(soundObject);
return PlaySound(soundObject, soundObjectBuilder);
}
return null;
}
override void OnSoundVoiceEvent(int event_id, string event_user_string)
{
//super.OnSoundVoiceEvent(event_id, event_user_string);
AnimSoundVoiceEvent voice_event = GetCreatureAIType().GetSoundVoiceEvent(event_id);
if (voice_event != null)
{
//! stop state sound when playing anim SoundVoice
if (m_InfectedSoundEventHandler) // && m_InfectedSoundEventHandler.IsPlaying())
{
m_InfectedSoundEventHandler.Stop();
DebugSound("[Infected @ " + this + "][SoundEvent] InfectedSoundEventHandler - stop all");
}
//! stop playing of old SoundVoice from anim (if any)
if (m_LastSoundVoiceAW != null)
{
DebugSound("[Infected @ " + this + "][AnimVoiceEvent] Stopping LastAW");
m_LastSoundVoiceAW.Stop();
}
//! play new SoundVoice from anim
ProcessSoundVoiceEvent(voice_event, m_LastSoundVoiceAW);
HandleSoundEvents();
}
}
protected void ProcessSoundVoiceEvent(AnimSoundVoiceEvent sound_event, out AbstractWave aw)
{
if (!g_Game.IsDedicatedServer())
{
SoundObjectBuilder objectBuilder = sound_event.GetSoundBuilder();
if (NULL != objectBuilder)
{
objectBuilder.AddEnvSoundVariables(GetPosition());
SoundObject soundObject = objectBuilder.BuildSoundObject();
AttenuateSoundIfNecessary(soundObject);
aw = PlaySound(soundObject, objectBuilder);
}
}
if (g_Game.IsServer())
{
if (sound_event.m_NoiseParams != NULL)
g_Game.GetNoiseSystem().AddNoise(this, sound_event.m_NoiseParams, NoiseAIEvaluate.GetNoiseReduction(g_Game.GetWeather()));
}
}
//-------------------------------------------------------------
//!
//! Combat
//!
EntityAI m_ActualTarget = null;
float m_AttackCooldownTime = 0;
DayZInfectedAttackType m_ActualAttackType = null;
bool FightLogic(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
{
if (pCurrentCommandID == DayZInfectedConstants.COMMANDID_MOVE)
{
// we attack only in chase & fight state
int mindState = pInputController.GetMindState();
if (mindState == DayZInfectedConstants.MINDSTATE_CHASE)
{
return ChaseAttackLogic(pCurrentCommandID, pInputController, pDt);
}
else if (mindState == DayZInfectedConstants.MINDSTATE_FIGHT)
{
return FightAttackLogic(pCurrentCommandID, pInputController, pDt);
}
}
else if (pCurrentCommandID == DayZInfectedConstants.COMMANDID_ATTACK)
{
DayZInfectedCommandAttack attackCommand = GetCommand_Attack();
if (attackCommand && attackCommand.WasHit())
{
if (m_ActualTarget != null)
{
if (m_ActualTarget.GetMeleeTargetType() == EMeleeTargetType.NONALIGNABLE)
return false;
bool playerInBlockStance = false;
vector targetPos = m_ActualTarget.GetPosition();
vector hitPosWS = targetPos;
vector zombiePos = GetPosition();
PlayerBase playerTarget = PlayerBase.Cast(m_ActualTarget);
if (playerTarget)
{
playerInBlockStance = playerTarget.GetMeleeFightLogic() && playerTarget.GetMeleeFightLogic().IsInBlock();
}
if (vector.DistanceSq(targetPos, zombiePos) <= m_ActualAttackType.m_Distance * m_ActualAttackType.m_Distance)
{
//! player is in block stance and facing the infected
if (playerInBlockStance && (Math.RAD2DEG * Math.AbsFloat(Math3D.AngleFromPosition(targetPos, MiscGameplayFunctions.GetHeadingVector(playerTarget), zombiePos))) <= GameConstants.AI_MAX_BLOCKABLE_ANGLE)
{
//! infected is playing heavy attack - decrease the dmg to light
if (m_ActualAttackType.m_IsHeavy == 1)
{
hitPosWS = m_ActualTarget.ModelToWorld(m_ActualTarget.GetDefaultHitPosition()); //! override hit pos by pos defined in type
DamageSystem.CloseCombatDamageName(this, m_ActualTarget, m_ActualTarget.GetHitComponentForAI(), "MeleeZombie", hitPosWS);
}
else
{
//! infected is playing light attack - do not send damage, play animation instead
hitPosWS = m_ActualTarget.ModelToWorld(m_ActualTarget.GetDefaultHitPosition());
DamageSystem.CloseCombatDamageName(this, m_ActualTarget, m_ActualTarget.GetHitComponentForAI(), "Dummy_Light", hitPosWS);
}
}
else
{
hitPosWS = m_ActualTarget.ModelToWorld(m_ActualTarget.GetDefaultHitPosition()); //! override hit pos by pos defined in type
DamageSystem.CloseCombatDamageName(this, m_ActualTarget, m_ActualTarget.GetHitComponentForAI(), m_ActualAttackType.m_AmmoType, hitPosWS);
}
}
}
}
return true;
}
return false;
}
bool ChaseAttackLogic(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
{
// always update target - it can be destroyed
m_ActualTarget = pInputController.GetTargetEntity();
//! do not attack players in vehicle - hotfix
PlayerBase pb = PlayerBase.Cast(m_ActualTarget);
if ( pb && pb.GetCommand_Vehicle() )
{
return false;
}
if ( m_ActualTarget == NULL )
return false;
vector targetPos = m_ActualTarget.GetPosition();
if ( !CanAttackToPosition(targetPos) )
return false;
float targetDist = vector.Distance(targetPos, this.GetPosition());
int pitch = GetAttackPitch(m_ActualTarget);
m_ActualAttackType = GetDayZInfectedType().ChooseAttack(DayZInfectedAttackGroupType.CHASE, targetDist, pitch);
if (m_ActualAttackType)
{
Object target = DayZPlayerUtils.GetMeleeTarget(this.GetPosition(), this.GetDirection(), TARGET_CONE_ANGLE_CHASE, m_ActualAttackType.m_Distance, -1.0, 2.0, this, m_TargetableObjects, m_AllTargetObjects);
//! target is outside the targeting cone; skip attack
if (m_ActualTarget != target)
{
m_AllTargetObjects.Clear();
return false;
}
StartCommand_Attack(m_ActualTarget, m_ActualAttackType.m_Type, m_ActualAttackType.m_Subtype);
m_AttackCooldownTime = m_ActualAttackType.m_Cooldown;
return true;
}
return false;
}
bool FightAttackLogic(int pCurrentCommandID, DayZInfectedInputController pInputController, float pDt)
{
// always update target - it can be destroyed
m_ActualTarget = pInputController.GetTargetEntity();
//! do not attack players in vehicle - hotfix
PlayerBase pb = PlayerBase.Cast(m_ActualTarget);
if (pb && pb.GetCommand_Vehicle())
return false;
if (m_AttackCooldownTime > 0)
{
m_AttackCooldownTime -= pDt * GameConstants.AI_ATTACKSPEED;
return false;
}
if (m_ActualTarget == null)
return false;
vector targetPos = m_ActualTarget.GetPosition();
float targetDist = vector.Distance(targetPos, this.GetPosition());
int pitch = GetAttackPitch(m_ActualTarget);
if (!CanAttackToPosition(targetPos))
return false;
m_ActualAttackType = GetDayZInfectedType().ChooseAttack(DayZInfectedAttackGroupType.FIGHT, targetDist, pitch);
if (m_ActualAttackType)
{
Object target = DayZPlayerUtils.GetMeleeTarget(this.GetPosition(), this.GetDirection(), TARGET_CONE_ANGLE_FIGHT, m_ActualAttackType.m_Distance, -1.0, 2.0, this, m_TargetableObjects, m_AllTargetObjects);
//! target is outside the targeting cone; skip attack
if (m_AllTargetObjects.Count() > 0 && m_AllTargetObjects[0] != m_ActualTarget)
{
m_AllTargetObjects.Clear();
return false;
}
StartCommand_Attack(m_ActualTarget, m_ActualAttackType.m_Type, m_ActualAttackType.m_Subtype);
m_AttackCooldownTime = m_ActualAttackType.m_Cooldown;
return true;
}
return false;
}
int GetAttackPitch(EntityAI target)
{
vector attackRefPos;
attackRefPos = target.GetDefaultHitPosition();
//! no default hit pos fallback
if ( attackRefPos != vector.Zero )
{
attackRefPos = target.ModelToWorld(attackRefPos);
}
else
{
attackRefPos = target.GetPosition();
}
// Now we have only erect stance, we need to get head position later too
float headPosY = GetPosition()[1];
headPosY += 1.8;
float diff = Math.AbsFloat(attackRefPos[1] - headPosY);
if ( diff < 0.3 )
return 0;
if ( headPosY > attackRefPos[1] )
return -1;
else
return 1;
}
//-------------------------------------------------------------
//!
//! Crawl transition
//!
int m_CrawlTransition = -1;
bool HandleCrawlTransition(int pCurrentCommandID)
{
if ( m_CrawlTransition != -1 )
{
StartCommand_Crawl(m_CrawlTransition);
m_CrawlTransition = -1;
m_IsCrawling = true;
SetSynchDirty();
return true;
}
return pCurrentCommandID == DayZInfectedConstants.COMMANDID_CRAWL;
}
bool EvaluateCrawlTransitionAnimation(EntityAI pSource, string pComponent, string pAmmoType, out int pAnimType)
{
pAnimType = -1;
if ( pComponent == "LeftLeg" && GetHealth(pComponent, "Health") == 0 )
pAnimType = 0;
else if ( pComponent == "RightLeg" && GetHealth(pComponent, "Health") == 0 )
pAnimType = 2;
if ( pAnimType != -1 )
{
vector targetDirection = GetDirection();
vector toSourceDirection = (pSource.GetPosition() - GetPosition());
targetDirection[1] = 0;
toSourceDirection[1] = 0;
targetDirection.Normalize();
toSourceDirection.Normalize();
float cosFi = vector.Dot(targetDirection, toSourceDirection);
if ( cosFi >= 0 ) // front
pAnimType++;
}
return pAnimType != -1;
}
//-------------------------------------------------------------
//!
//! Damage hits
//!
bool m_DamageHitToProcess = false;
bool m_DamageHitHeavy = false;
int m_DamageHitType = 0;
float m_ShockDamage = 0;
const float HIT_INTERVAL_MIN = 0.3; // Minimum time in seconds before a COMMANDID_HIT to COMMANDID_HIT transition is allowed
float m_HitElapsedTime = HIT_INTERVAL_MIN;
bool HandleDamageHit(int pCurrentCommandID)
{
if ( pCurrentCommandID == DayZInfectedConstants.COMMANDID_HIT )
{
// Throttle hit command up to a fixed rate
if ( m_HitElapsedTime < HIT_INTERVAL_MIN )
{
m_HitElapsedTime += m_DeltaTime;
m_DamageHitToProcess = false;
m_ShockDamage = 0;
return false;
}
}
if ( m_DamageHitToProcess )
{
int randNum = Math.RandomIntInclusive(0, 100);
float stunChange = SHOCK_TO_STUN_MULTIPLIER * m_ShockDamage;
if ( m_DamageHitHeavy || randNum <= stunChange || ( m_MindState == DayZInfectedConstants.MINDSTATE_CALM || m_MindState == DayZInfectedConstants.MINDSTATE_DISTURBED ) )
{
StartCommand_Hit(m_DamageHitHeavy, m_DamageHitType, m_DamageHitDirection);
m_HitElapsedTime = 0;
}
m_DamageHitToProcess = false;
m_ShockDamage = 0;
m_HeavyHitOverride = false;
return true;
}
return false;
}
//! selects animation type and direction based on damage system data
bool EvaluateDamageHitAnimation(EntityAI pSource, string pComponent, string pAmmoType, out bool pHeavyHit, out int pAnimType, out float pAnimHitDir)
{
int invertHitDir = 0; //Used to flip the heavy hit animation direction
//! heavy hit
pHeavyHit = ((g_Game.ConfigGetInt("cfgAmmo " + pAmmoType + " hitAnimation") > 0) || m_HeavyHitOverride);
invertHitDir = g_Game.ConfigGetInt("cfgAmmo " + pAmmoType + " invertHitDir");
//! anim type
pAnimType = 0; // belly
if ( !pHeavyHit )
{
if ( pComponent == "Torso" ) // body
pAnimType = 1;
else if ( pComponent == "Head" ) // head
pAnimType = 2;
}
//! direction
//pAnimHitDir = ComputeHitDirectionAngle(pSource);
pAnimHitDir = ComputeHitDirectionAngleEx(pSource, invertHitDir);
//! shock GetDamage
//m_ShockDamage = g_Game.ConfigGetFloat( "CfgAmmo " + pAmmoType + " DamageApplied " + "Shock " + "damage");
return true;
}
float ComputeHitDirectionAngle(EntityAI pSource)
{
vector targetDirection = GetDirection();
vector toSourceDirection = (pSource.GetPosition() - GetPosition());
targetDirection[1] = 0;
toSourceDirection[1] = 0;
targetDirection.Normalize();
toSourceDirection.Normalize();
float cosFi = vector.Dot(targetDirection, toSourceDirection);
vector cross = targetDirection * toSourceDirection;
float dirAngle = Math.Acos(cosFi) * Math.RAD2DEG;
if ( cross[1] < 0 )
dirAngle = -dirAngle;
return dirAngle;
}
float ComputeHitDirectionAngleEx(EntityAI pSource, int invertHitDir = 0)
{
vector targetDirection = GetDirection();
vector toSourceDirection = (pSource.GetPosition() - GetPosition());
targetDirection[1] = 0;
toSourceDirection[1] = 0;
targetDirection.Normalize();
toSourceDirection.Normalize();
float cosFi = vector.Dot(targetDirection, toSourceDirection);
vector cross = targetDirection * toSourceDirection;
float dirAngle = Math.Acos(cosFi) * Math.RAD2DEG;
// We will invert direction of the hit
if ( invertHitDir > 0 )
dirAngle -= 180;
if ( cross[1] < 0 )
dirAngle = -dirAngle;
return dirAngle;
}
//-------------------------------------------------------------
//!
//! Events from damage system
//!
override void EEHitBy(TotalDamageResult damageResult, int damageType, EntityAI source, int component, string dmgZone, string ammo, vector modelPos, float speedCoef)
{
super.EEHitBy(damageResult, damageType, source, component, dmgZone, ammo, modelPos, speedCoef);
m_TransportHitRegistered = false;
if ( !IsAlive() )
{
ZombieHitData data = new ZombieHitData;
data.m_Component = component;
data.m_DamageZone = dmgZone;
data.m_AmmoType = ammo;
EvaluateDeathAnimationEx(source, data, m_DeathType, m_DamageHitDirection);
}
else
{
int crawlTransitionType = -1;
if ( EvaluateCrawlTransitionAnimation(source, dmgZone, ammo, crawlTransitionType) )
{
m_CrawlTransition = crawlTransitionType;
return;
}
if ( EvaluateDamageHitAnimation(source, dmgZone, ammo, m_DamageHitHeavy, m_DamageHitType, m_DamageHitDirection) )
{
if ( dmgZone )
m_ShockDamage = damageResult.GetDamage( dmgZone, "Shock" );
m_DamageHitToProcess = true;
return;
}
}
}
override void EEHitByRemote(int damageType, EntityAI source, int component, string dmgZone, string ammo, vector modelPos)
{
super.EEHitByRemote(damageType, source, component, dmgZone, ammo, modelPos);
}
//! sound debug messages
protected void DebugSound(string s)
{
//Print(s);
}
//-------------------------------------------------------------
//!
//! Phx contact event
//!
override protected void EOnContact(IEntity other, Contact extra)
{
if ( !IsAlive() )
return;
Transport transport = Transport.Cast(other);
if ( transport )
{
if ( g_Game.IsServer() )
{
RegisterTransportHit(transport);
}
}
}
override bool CanReceiveAttachment(EntityAI attachment, int slotId)
{
if ( !IsAlive() )
{
return false;
}
return super.CanReceiveAttachment(attachment, slotId);
}
override vector GetCenter()
{
return GetBonePositionWS( GetBoneIndexByName( "spine3" ) );
}
//! returns true if backstab is in progress; used for suspending of AI targeting and other useful things besides
override bool IsBeingBackstabbed()
{
return m_FinisherInProgress;
}
override void SetBeingBackstabbed(int backstabType)
{
// disable AI simulation
GetAIAgent().SetKeepInIdle(true);
// select death animation
switch (backstabType)
{
case EMeleeHitType.FINISHER_LIVERSTAB:
m_DeathType = DayZInfectedDeathAnims.ANIM_DEATH_BACKSTAB;
break;
case EMeleeHitType.FINISHER_NECKSTAB:
m_DeathType = DayZInfectedDeathAnims.ANIM_DEATH_NECKSTAB;
break;
default:
m_DeathType = DayZInfectedDeathAnims.ANIM_DEATH_DEFAULT;
}
// set flag - death command will be executed
m_FinisherInProgress = true;
}
//! returns true if crawling; 'DayZInfectedCommandCrawl' is only for the transition, after that it remains 'DayZInfectedCommandMove' (unreliable)
bool IsCrawling()
{
return m_IsCrawling;
}
// called from command death when stealth attack wan't successful
void OnRecoverFromDeath()
{
// enable AI simulation again
GetAIAgent().SetKeepInIdle(false);
// reset flag
m_FinisherInProgress = false;
}
override void AddArrow(Object arrow, int componentIndex, vector closeBonePosWS, vector closeBoneRotWS)
{
CachedObjectsArrays.ARRAY_STRING.Clear();
GetActionComponentNameList(componentIndex, CachedObjectsArrays.ARRAY_STRING, "fire");
int pivot = -1;
for (int i = 0; i < CachedObjectsArrays.ARRAY_STRING.Count() && pivot == -1; i++)
{
pivot = GetBoneIndexByName(CachedObjectsArrays.ARRAY_STRING.Get(i));
}
vector parentTransMat[4];
vector arrowTransMat[4];
if (pivot == -1)
{
GetTransformWS(parentTransMat);
}
else
{
vector rotMatrix[3];
Math3D.YawPitchRollMatrix(closeBoneRotWS * Math.RAD2DEG,rotMatrix);
parentTransMat[0] = rotMatrix[0];
parentTransMat[1] = rotMatrix[1];
parentTransMat[2] = rotMatrix[2];
parentTransMat[3] = closeBonePosWS;
}
arrow.GetTransform(arrowTransMat);
Math3D.MatrixInvMultiply4(parentTransMat, arrowTransMat, arrowTransMat);
// orthogonalize matrix - parent might be skewed
Math3D.MatrixOrthogonalize4(arrowTransMat);
arrow.SetTransform(arrowTransMat);
AddChild(arrow, pivot);
}
override bool IsManagingArrows()
{
return true;
}
override ArrowManagerBase GetArrowManager()
{
return m_ArrowManager;
}
}
//! an extendable data container
class ZombieHitData
{
int m_Component;
string m_DamageZone;
string m_AmmoType;
}