mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-11-24 19:30:26 +01:00
3275 lines
102 KiB
C++
Executable File
3275 lines
102 KiB
C++
Executable File
/* Copyright (c) 2002-2012 Croteam Ltd.
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of version 2 of the GNU General Public License as published by
|
|
the Free Software Foundation
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
|
|
|
|
310
|
|
%{
|
|
#include "EntitiesMP/StdH/StdH.h"
|
|
#include "EntitiesMP/Common/PathFinding.h"
|
|
#include "EntitiesMP/NavigationMarker.h"
|
|
#include "EntitiesMP/TacticsHolder.h"
|
|
extern void JumpFromBouncer(CEntity *penToBounce, CEntity *penBouncer);
|
|
extern INDEX ent_bReportBrokenChains;
|
|
%}
|
|
|
|
uses "EntitiesMP/Watcher";
|
|
uses "EntitiesMP/BasicEffects";
|
|
uses "EntitiesMP/Projectile";
|
|
uses "EntitiesMP/Debris";
|
|
uses "EntitiesMP/EnemyMarker";
|
|
uses "EntitiesMP/MusicHolder";
|
|
uses "EntitiesMP/BloodSpray";
|
|
|
|
event ERestartAttack {
|
|
};
|
|
|
|
// self sent in Active loop to reconsider what enemy should do
|
|
event EReconsiderBehavior {
|
|
};
|
|
|
|
// force wound
|
|
event EForceWound {
|
|
};
|
|
|
|
enum TargetType {
|
|
0 TT_NONE "", // no target
|
|
1 TT_SOFT "", // soft target - only spoted player but not heavily angry at him
|
|
2 TT_HARD "", // hard target - player has provoked enemy and it is very angry
|
|
};
|
|
|
|
enum DestinationType {
|
|
0 DT_PLAYERCURRENT "", // go to where player is now
|
|
1 DT_PLAYERSPOTTED "", // go to where player was last seen
|
|
2 DT_PATHTEMPORARY "", // go to navigation marker - temporary, only until you spot player again
|
|
3 DT_PATHPERSISTENT "", // go to navigation marker - until you really get there
|
|
};
|
|
|
|
%{
|
|
#define MF_MOVEZ (1L<<0)
|
|
#define MF_ROTATEH (1L<<1)
|
|
#define MF_MOVEXZY (1L<<2)
|
|
%}
|
|
|
|
class export CEnemyBase : CMovableModelEntity {
|
|
name "Enemy Base";
|
|
thumbnail "";
|
|
features "HasName", "IsTargetable", "CanBePredictable";
|
|
|
|
properties:
|
|
1 CEntityPointer m_penWatcher, // watcher
|
|
2 FLOAT3D m_vStartPosition = FLOAT3D(0,0,0), // start position
|
|
3 CEntityPointer m_penEnemy, // current enemy
|
|
4 enum TargetType m_ttTarget = TT_NONE, // type of target
|
|
5 CTString m_strDescription = "Enemy base",
|
|
6 CTString m_strName "Name" 'N' = "Enemy base",
|
|
7 CSoundObject m_soSound,
|
|
8 FLOAT3D m_vStartDirection = FLOAT3D(0,0,-1), // for returning to start
|
|
9 BOOL m_bOnStartPosition = TRUE,
|
|
29 FLOAT m_fFallHeight "Fall height" = 8.0f,
|
|
31 FLOAT m_fStepHeight "Step height" = -1.0f,
|
|
17 RANGE m_fSenseRange "Sense Range" = 0.0f, // immediately spots any player that gets closer than this
|
|
28 FLOAT m_fViewAngle "View angle" 'V' = 360.0f, // view frustum angle for spotting players
|
|
|
|
// moving/attack properties - CAN BE SET
|
|
// these following must be ordered exactly like this for GetProp() to function
|
|
10 FLOAT m_fWalkSpeed = 1.0f, // walk speed
|
|
11 ANGLE m_aWalkRotateSpeed = AngleDeg(10.0f), // walk rotate speed
|
|
12 FLOAT m_fAttackRunSpeed = 1.0f, // attack run speed
|
|
13 ANGLE m_aAttackRotateSpeed = AngleDeg(10.0f), // attack rotate speed
|
|
14 FLOAT m_fCloseRunSpeed = 1.0f, // close run speed
|
|
15 ANGLE m_aCloseRotateSpeed = AngleDeg(10.0f), // close rotate speed
|
|
20 FLOAT m_fAttackDistance = 50.0f, // attack distance mode
|
|
21 FLOAT m_fCloseDistance = 10.0f, // close distance mode
|
|
22 FLOAT m_fAttackFireTime = 2.0f, // attack distance fire time
|
|
23 FLOAT m_fCloseFireTime = 1.0f, // close distance fire time
|
|
24 FLOAT m_fStopDistance = 0.0f, // stop moving toward enemy if closer than stop distance
|
|
25 FLOAT m_fIgnoreRange = 200.0f, // cease attack if enemy farther
|
|
26 FLOAT m_fLockOnEnemyTime = 0.0f, // time needed to fire
|
|
|
|
// damage/explode properties - CAN BE SET
|
|
40 FLOAT m_fBlowUpAmount = 0.0f, // damage in minus for blow up
|
|
41 INDEX m_fBodyParts = 4, // number of spawned body parts
|
|
42 FLOAT m_fDamageWounded = 0.0f, // damage amount to be wounded
|
|
43 FLOAT3D m_vDamage = FLOAT3D(0,0,0), // current damage impact
|
|
44 FLOAT m_tmLastDamage = -1000.0f,
|
|
46 BOOL m_bRobotBlowup = FALSE, // set for robots parts blowup, otherwise blowup flesh
|
|
47 FLOAT m_fBlowUpSize = 2.0f,
|
|
|
|
// logic temporary variables -> DO NOT USE
|
|
133 FLOAT m_fMoveTime = 0.0f,
|
|
52 FLOAT3D m_vDesiredPosition = FLOAT3D(0,0,0),
|
|
53 enum DestinationType m_dtDestination = DT_PLAYERCURRENT, // type of current desired position
|
|
59 CEntityPointer m_penPathMarker, // current path finding marker
|
|
18 FLOAT3D m_vPlayerSpotted = FLOAT3D(0,0,0), // where player was last spotted
|
|
54 FLOAT m_fMoveFrequency = 0.0f,
|
|
55 FLOAT m_fMoveSpeed = 0.0f,
|
|
56 ANGLE m_aRotateSpeed = 0,
|
|
57 FLOAT m_fLockStartTime = 0.0f,
|
|
58 FLOAT m_fRangeLast = 0.0f,
|
|
130 BOOL m_bFadeOut = FALSE,
|
|
131 FLOAT m_fFadeStartTime = 0.0f,
|
|
132 FLOAT m_fFadeTime = 0.0f,
|
|
|
|
// attack temporary -> DO NOT USE
|
|
60 FLOAT m_fShootTime = 0.0f, // time when entity will try to shoot on enemy
|
|
61 FLOAT m_fDamageConfused = 0.0f, // damage amount when entity shoot concentration is spoiled
|
|
62 INDEX m_iChargeHitAnimation = 0, // charge hit (close attack) properties
|
|
63 FLOAT m_fChargeHitDamage = 0.0f,
|
|
64 FLOAT m_fChargeHitAngle = 0.0f,
|
|
65 FLOAT m_fChargeHitSpeed = 0.0f,
|
|
|
|
// editor variables
|
|
83 CEntityPointer m_penSpawnerTarget, // for re-spawning
|
|
84 CEntityPointer m_penDeathTarget "Death target" 'D', // death target
|
|
85 enum EventEType m_eetDeathType "Death event type" 'F' = EET_TRIGGER, // death event type
|
|
86 BOOL m_bTemplate "Template" = FALSE, // template enemy for spawning new enemies
|
|
88 RANGE m_fAttackRadius "Radius of attack" 'A' = 10000.0f, // attack sphere range radius
|
|
89 COLOR m_colColor "Color" 'L' = 0x00, // color
|
|
90 BOOL m_bDeaf "Deaf" = FALSE, // deaf
|
|
91 BOOL m_bBlind "Blind" = FALSE, // blind
|
|
92 FLOAT m_tmGiveUp "Give up time" = 5.0f, // how fast enemy gives up attack
|
|
93 FLOAT m_tmReflexMin "Reflex Min" = 0.0f, // how much to wait before reacting on spotting the player
|
|
94 FLOAT m_tmReflexMax "Reflex Max" = 0.0f,
|
|
95 FLOAT m_fActivityRange "Activity Range" = 0.0f,
|
|
|
|
// random values variables
|
|
106 BOOL m_bApplyRandomStretch "Apply random stretch" = FALSE, // apply random stretch
|
|
107 FLOAT m_fRandomStretchFactor "Random stretch factor" = 0.1f, // random stretch
|
|
108 FLOAT m_fStretchMultiplier "Stretch multiplier" = 1.0f, // stretch multiplier
|
|
109 FLOAT m_fRandomStretchMultiplier = 1.0f, // calculated random stretch
|
|
|
|
// marker variables
|
|
120 CEntityPointer m_penMarker "Marker" 'M' COLOR(C_RED|0xFF), // enemy marker pointer
|
|
|
|
// fuss variables
|
|
140 CEntityPointer m_penMainMusicHolder,
|
|
141 FLOAT m_tmLastFussTime = 0.0f,
|
|
|
|
142 FLOAT m_iScore = -100000, // how many points this enemy gives when killed
|
|
143 FLOAT m_fMaxHealth = -1.0f, // must set this because of crosshair colorizing
|
|
144 BOOL m_bBoss = FALSE, // set for bosses (for health display)
|
|
|
|
145 FLOAT m_fSpiritStartTime = 0.0f, // time when spirit effect has started
|
|
|
|
146 FLOAT m_tmSpraySpawned = 0.0f, // time when damage has been applied
|
|
147 FLOAT m_fSprayDamage = 0.0f, // total ammount of damage
|
|
148 CEntityPointer m_penSpray, // the blood spray
|
|
149 FLOAT m_fMaxDamageAmmount = 0.0f, // max ammount of damage received in in last few ticks
|
|
150 FLOAT3D m_vLastStain = FLOAT3D(0,0,0), // where last stain was left
|
|
151 enum SprayParticlesType m_sptType = SPT_BLOOD, // type of particles
|
|
|
|
160 CEntityPointer m_penTacticsHolder "Tactics Holder",
|
|
161 BOOL m_bTacticActive = FALSE,
|
|
162 FLOAT m_tmTacticsActivation = 0.0f,
|
|
// warning! tactic variables are also used for dust spawning
|
|
163 FLOAT3D m_vTacticsStartPosition = FLOAT3D(0,0,0),
|
|
165 FLOAT m_fTacticVar1 = 0.0f,
|
|
166 FLOAT m_fTacticVar2 = 0.0f,
|
|
167 FLOAT m_fTacticVar3 = 0.0f,
|
|
168 FLOAT m_fTacticVar4 = 0.0f,
|
|
169 FLOAT m_fTacticVar5 = 0.0f,
|
|
170 BOOL m_bTacticsStartOnSense "Tactics start on sense" = FALSE,
|
|
180 COLOR m_colBurning = COLOR(C_WHITE|CT_OPAQUE), // color applied when burning
|
|
|
|
181 BOOL m_bResizeAttachments "Stretch attachments" = FALSE, // for small enemies with big guns
|
|
|
|
//171 INDEX m_iTacticsRetried = 0,
|
|
|
|
{
|
|
TIME m_tmPredict; // time to predict the entity to
|
|
}
|
|
|
|
|
|
components:
|
|
|
|
1 class CLASS_WATCHER "Classes\\Watcher.ecl",
|
|
2 class CLASS_PROJECTILE "Classes\\Projectile.ecl",
|
|
3 class CLASS_DEBRIS "Classes\\Debris.ecl",
|
|
4 class CLASS_BASIC_EFFECT "Classes\\BasicEffect.ecl",
|
|
5 class CLASS_BLOOD_SPRAY "Classes\\BloodSpray.ecl",
|
|
|
|
// ************** FLESH PARTS **************
|
|
10 model MODEL_FLESH "Models\\Effects\\Debris\\Flesh\\Flesh.mdl",
|
|
11 model MODEL_FLESH_APPLE "Models\\Effects\\Debris\\Fruits\\Apple.mdl",
|
|
12 model MODEL_FLESH_BANANA "Models\\Effects\\Debris\\Fruits\\Banana.mdl",
|
|
13 model MODEL_FLESH_BURGER "Models\\Effects\\Debris\\Fruits\\CheeseBurger.mdl",
|
|
14 model MODEL_FLESH_LOLLY "Models\\Effects\\Debris\\Fruits\\LollyPop.mdl",
|
|
15 model MODEL_FLESH_ORANGE "Models\\Effects\\Debris\\Fruits\\Orange.mdl",
|
|
|
|
20 texture TEXTURE_FLESH_RED "Models\\Effects\\Debris\\Flesh\\FleshRed.tex",
|
|
21 texture TEXTURE_FLESH_GREEN "Models\\Effects\\Debris\\Flesh\\FleshGreen.tex",
|
|
22 texture TEXTURE_FLESH_APPLE "Models\\Effects\\Debris\\Fruits\\Apple.tex",
|
|
23 texture TEXTURE_FLESH_BANANA "Models\\Effects\\Debris\\Fruits\\Banana.tex",
|
|
24 texture TEXTURE_FLESH_BURGER "Models\\Effects\\Debris\\Fruits\\CheeseBurger.tex",
|
|
25 texture TEXTURE_FLESH_LOLLY "Models\\Effects\\Debris\\Fruits\\LollyPop.tex",
|
|
26 texture TEXTURE_FLESH_ORANGE "Models\\Effects\\Debris\\Fruits\\Orange.tex",
|
|
|
|
// ************** MACHINE PARTS **************
|
|
31 model MODEL_MACHINE "Models\\Effects\\Debris\\Stone\\Stone.mdl",
|
|
32 texture TEXTURE_MACHINE "Models\\Effects\\Debris\\Stone\\Stone.tex",
|
|
|
|
|
|
functions:
|
|
|
|
void CEnemyBase(void)
|
|
{
|
|
m_tmPredict = 0;
|
|
}
|
|
|
|
// called by other entities to set time prediction parameter
|
|
void SetPredictionTime(TIME tmAdvance) // give time interval in advance to set
|
|
{
|
|
ASSERT(!IsPredictor());
|
|
m_tmPredict = _pTimer->CurrentTick()+tmAdvance;
|
|
}
|
|
|
|
// called by engine to get the upper time limit
|
|
TIME GetPredictionTime(void) // return moment in time up to which to predict this entity
|
|
{
|
|
return m_tmPredict;
|
|
}
|
|
|
|
// describe how this enemy killed player
|
|
virtual CTString GetPlayerKillDescription(const CTString &strPlayerName, const EDeath &eDeath)
|
|
{
|
|
CTString str;
|
|
str.PrintF(TRANSV("%s killed %s"), (const char *) GetClass()->ec_pdecDLLClass->dec_strName, (const char *) strPlayerName);
|
|
return str;
|
|
}
|
|
|
|
virtual FLOAT GetCrushHealth(void)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
// if should be counted as kill
|
|
virtual BOOL CountAsKill(void)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
virtual BOOL ForcesCannonballToExplode(void)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// overridable function for access to different properties of derived classes (flying/diving)
|
|
virtual FLOAT &GetProp(FLOAT &m_fBase)
|
|
{
|
|
return m_fBase;
|
|
}
|
|
|
|
// overridable function to get range for switching to another player
|
|
virtual FLOAT GetThreatDistance(void)
|
|
{
|
|
// closer of close or stop range
|
|
return Max(GetProp(m_fCloseDistance), GetProp(m_fStopDistance));
|
|
}
|
|
|
|
// check if we maybe switch to some other player (for large beasts in coop)
|
|
void MaybeSwitchToAnotherPlayer(void)
|
|
{
|
|
// if in single player
|
|
if (GetSP()->sp_bSinglePlayer) {
|
|
// no need to check
|
|
return;
|
|
}
|
|
|
|
// if current player is inside threat distance
|
|
if (CalcDist(m_penEnemy)<GetThreatDistance()) {
|
|
// do not switch
|
|
return;
|
|
}
|
|
// maybe switch
|
|
CEntity *penNewEnemy = GetWatcher()->CheckAnotherPlayer(m_penEnemy);
|
|
if (penNewEnemy!=m_penEnemy && penNewEnemy!=NULL) {
|
|
m_penEnemy = penNewEnemy;
|
|
SendEvent(EReconsiderBehavior());
|
|
}
|
|
}
|
|
|
|
class CWatcher *GetWatcher(void)
|
|
{
|
|
ASSERT(m_penWatcher!=NULL);
|
|
return (CWatcher*)&*m_penWatcher;
|
|
}
|
|
export void Copy(CEntity &enOther, ULONG ulFlags)
|
|
{
|
|
CMovableModelEntity::Copy(enOther, ulFlags);
|
|
CEnemyBase *penOther = (CEnemyBase *)(&enOther);
|
|
}
|
|
|
|
void Precache(void)
|
|
{
|
|
PrecacheModel(MODEL_FLESH);
|
|
PrecacheModel(MODEL_FLESH_APPLE);
|
|
PrecacheModel(MODEL_FLESH_BANANA);
|
|
PrecacheModel(MODEL_FLESH_BURGER);
|
|
PrecacheModel(MODEL_MACHINE);
|
|
PrecacheTexture(TEXTURE_MACHINE);
|
|
PrecacheTexture(TEXTURE_FLESH_RED);
|
|
PrecacheTexture(TEXTURE_FLESH_GREEN);
|
|
PrecacheTexture(TEXTURE_FLESH_APPLE);
|
|
PrecacheTexture(TEXTURE_FLESH_BANANA);
|
|
PrecacheTexture(TEXTURE_FLESH_BURGER);
|
|
PrecacheTexture(TEXTURE_FLESH_LOLLY);
|
|
PrecacheTexture(TEXTURE_FLESH_ORANGE);
|
|
PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODSPILL);
|
|
PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODSTAIN);
|
|
PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODSTAINGROW);
|
|
PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODEXPLODE);
|
|
PrecacheClass(CLASS_BASIC_EFFECT, BET_BOMB);
|
|
PrecacheClass(CLASS_BASIC_EFFECT, BET_EXPLOSIONSTAIN);
|
|
PrecacheClass(CLASS_DEBRIS);
|
|
}
|
|
|
|
// get position you would like to go to when following player
|
|
virtual FLOAT3D PlayerDestinationPos(void)
|
|
{
|
|
return m_penEnemy->GetPlacement().pl_PositionVector;
|
|
}
|
|
|
|
// calculate delta to given entity
|
|
FLOAT3D CalcDelta(CEntity *penEntity)
|
|
{
|
|
ASSERT(penEntity!=NULL);
|
|
// find vector from you to target
|
|
return penEntity->GetPlacement().pl_PositionVector - GetPlacement().pl_PositionVector;
|
|
};
|
|
// calculate distance to given entity
|
|
FLOAT CalcDist(CEntity *penEntity)
|
|
{
|
|
return CalcDelta(penEntity).Length();
|
|
};
|
|
|
|
BOOL IfTargetCrushed(CEntity *penOther, const FLOAT3D &vDirection)
|
|
{
|
|
if( IsOfClass(penOther, "ModelHolder2"))
|
|
{
|
|
FLOAT fCrushHealth = GetCrushHealth();
|
|
if( fCrushHealth>((CRationalEntity &)*penOther).GetHealth())
|
|
{
|
|
InflictDirectDamage(penOther, this,
|
|
DMT_EXPLOSION, fCrushHealth, GetPlacement().pl_PositionVector, vDirection);
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// calculate delta to given entity in current gravity plane
|
|
FLOAT3D CalcPlaneDelta(CEntity *penEntity)
|
|
{
|
|
ASSERT(penEntity!=NULL);
|
|
FLOAT3D vPlaneDelta;
|
|
// find vector from you to target in XZ plane
|
|
GetNormalComponent(
|
|
penEntity->GetPlacement().pl_PositionVector - GetPlacement().pl_PositionVector,
|
|
en_vGravityDir, vPlaneDelta);
|
|
return vPlaneDelta;
|
|
};
|
|
|
|
// calculate distance to given entity in current gravity plane
|
|
FLOAT CalcPlaneDist(CEntity *penEntity)
|
|
{
|
|
return CalcPlaneDelta(penEntity).Length();
|
|
};
|
|
|
|
// get cos of angle in direction
|
|
FLOAT GetFrustumAngle(const FLOAT3D &vDir)
|
|
{
|
|
// find front vector
|
|
FLOAT3D vFront = -GetRotationMatrix().GetColumn(3);
|
|
// make dot product to determine if you can see target (view angle)
|
|
return (vDir/vDir.Length())%vFront;
|
|
}
|
|
|
|
// get cos of angle in direction in current gravity plane
|
|
FLOAT GetPlaneFrustumAngle(const FLOAT3D &vDir)
|
|
{
|
|
FLOAT3D vPlaneDelta;
|
|
// find vector from you to target in XZ plane
|
|
GetNormalComponent(vDir, en_vGravityDir, vPlaneDelta);
|
|
// find front vector
|
|
FLOAT3D vFront = -GetRotationMatrix().GetColumn(3);
|
|
FLOAT3D vPlaneFront;
|
|
GetNormalComponent(vFront, en_vGravityDir, vPlaneFront);
|
|
// make dot product to determine if you can see target (view angle)
|
|
vPlaneDelta.SafeNormalize();
|
|
vPlaneFront.SafeNormalize();
|
|
return vPlaneDelta%vPlaneFront;
|
|
}
|
|
|
|
// determine if you can see something in given direction
|
|
BOOL IsInFrustum(CEntity *penEntity, FLOAT fCosHalfFrustum)
|
|
{
|
|
// get direction to the entity
|
|
FLOAT3D vDelta = CalcDelta(penEntity);
|
|
// find front vector
|
|
FLOAT3D vFront = -GetRotationMatrix().GetColumn(3);
|
|
// make dot product to determine if you can see target (view angle)
|
|
FLOAT fDotProduct = (vDelta/vDelta.Length())%vFront;
|
|
return fDotProduct >= fCosHalfFrustum;
|
|
};
|
|
|
|
// determine if you can see something in given direction in current gravity plane
|
|
BOOL IsInPlaneFrustum(CEntity *penEntity, FLOAT fCosHalfFrustum)
|
|
{
|
|
// get direction to the entity
|
|
FLOAT3D vPlaneDelta = CalcPlaneDelta(penEntity);
|
|
// find front vector
|
|
FLOAT3D vFront = -GetRotationMatrix().GetColumn(3);
|
|
FLOAT3D vPlaneFront;
|
|
GetNormalComponent(vFront, en_vGravityDir, vPlaneFront);
|
|
// make dot product to determine if you can see target (view angle)
|
|
vPlaneDelta.SafeNormalize();
|
|
vPlaneFront.SafeNormalize();
|
|
FLOAT fDot = vPlaneDelta%vPlaneFront;
|
|
return fDot >= fCosHalfFrustum;
|
|
};
|
|
|
|
// cast a ray to entity checking only for brushes
|
|
BOOL IsVisible(CEntity *penEntity)
|
|
{
|
|
ASSERT(penEntity!=NULL);
|
|
// get ray source and target
|
|
FLOAT3D vSource, vTarget;
|
|
GetPositionCastRay(this, penEntity, vSource, vTarget);
|
|
|
|
// cast the ray
|
|
CCastRay crRay(this, vSource, vTarget);
|
|
crRay.cr_ttHitModels = CCastRay::TT_NONE; // check for brushes only
|
|
crRay.cr_bHitTranslucentPortals = FALSE;
|
|
en_pwoWorld->CastRay(crRay);
|
|
|
|
// if hit nothing (no brush) the entity can be seen
|
|
return (crRay.cr_penHit==NULL);
|
|
};
|
|
|
|
// cast a ray to entity checking all
|
|
BOOL IsVisibleCheckAll(CEntity *penEntity)
|
|
{
|
|
ASSERT(penEntity!=NULL);
|
|
// get ray source and target
|
|
FLOAT3D vSource, vTarget;
|
|
GetPositionCastRay(this, penEntity, vSource, vTarget);
|
|
|
|
// cast the ray
|
|
CCastRay crRay(this, vSource, vTarget);
|
|
crRay.cr_ttHitModels = CCastRay::TT_COLLISIONBOX; // check for model collision box
|
|
crRay.cr_bHitTranslucentPortals = FALSE;
|
|
en_pwoWorld->CastRay(crRay);
|
|
|
|
// if the ray hits wanted entity
|
|
return crRay.cr_penHit==penEntity;
|
|
};
|
|
|
|
/* calculates launch velocity and heading correction for angular launch */
|
|
void CalculateAngularLaunchParams(
|
|
FLOAT3D vShooting, FLOAT fShootHeight,
|
|
FLOAT3D vTarget, FLOAT3D vSpeedDest,
|
|
ANGLE aPitch,
|
|
FLOAT &fLaunchSpeed,
|
|
FLOAT &fRelativeHdg)
|
|
{
|
|
FLOAT3D vNewTarget = vTarget;
|
|
FLOAT3D &vGravity = en_vGravityDir;
|
|
FLOAT fYt;
|
|
FLOAT fXt;
|
|
FLOAT fA = TanFast(AngleDeg(aPitch));
|
|
FLOAT fTime = 0.0f;
|
|
FLOAT fLastTime = 0.0f;
|
|
|
|
INDEX iIterations = 0;
|
|
do
|
|
{
|
|
iIterations++;
|
|
FLOAT3D vDistance = vNewTarget-vShooting;
|
|
FLOAT3D vXt, vYt;
|
|
GetParallelAndNormalComponents(vDistance, vGravity, vYt, vXt);
|
|
fYt = vYt.Length();
|
|
if (vGravity%vYt>0) {
|
|
fYt=-fYt;
|
|
}
|
|
fXt = vXt.Length();
|
|
fLastTime=fTime;
|
|
fTime = Sqrt(2.0f)*Sqrt((fA*fXt+fShootHeight-fYt)/en_fGravityA);
|
|
vNewTarget = vTarget+vSpeedDest*fTime;
|
|
}
|
|
while( (Abs(fTime-fLastTime) > _pTimer->TickQuantum) && (iIterations<10) );
|
|
|
|
// calculate launch speed
|
|
fLaunchSpeed = fXt/(fTime*Cos(aPitch));
|
|
|
|
// calculate heading correction
|
|
FLOAT fHdgTargetNow = GetRelativeHeading( (vTarget-vShooting).SafeNormalize());
|
|
FLOAT fHdgTargetMoved = GetRelativeHeading( (vNewTarget-vShooting).SafeNormalize());
|
|
fRelativeHdg = fHdgTargetMoved-fHdgTargetNow;
|
|
}
|
|
|
|
/* calculates predicted position for propelled projectile */
|
|
FLOAT3D CalculatePredictedPosition( FLOAT3D vShootPos, FLOAT3D vTarget,
|
|
FLOAT fSpeedSrc, FLOAT3D vSpeedDst, FLOAT fClampY)
|
|
{
|
|
FLOAT3D vNewTarget = vTarget;
|
|
FLOAT3D &vGravity = en_vGravityDir;
|
|
FLOAT fTime = 0.0f;
|
|
FLOAT fLastTime = 0.0f;
|
|
INDEX iIterations = 0;
|
|
FLOAT3D vDistance = vNewTarget-vShootPos;
|
|
|
|
// iterate to obtain accurate position
|
|
do
|
|
{
|
|
iIterations++;
|
|
fLastTime=fTime;
|
|
fTime = vDistance.Length()/fSpeedSrc;
|
|
vNewTarget = vTarget + vSpeedDst*fTime + vGravity*0.5f*fTime*fTime;
|
|
vNewTarget(2) = ClampDn( vNewTarget(2), fClampY);
|
|
vDistance = vNewTarget-vShootPos;
|
|
}
|
|
while( (Abs(fTime-fLastTime) > _pTimer->TickQuantum) && (iIterations<10) );
|
|
return vNewTarget;
|
|
}
|
|
|
|
/* Check if entity is moved on a route set up by its targets. */
|
|
BOOL MovesByTargetedRoute(CTString &strTargetProperty) const {
|
|
strTargetProperty = "Marker";
|
|
return TRUE;
|
|
};
|
|
/* Check if entity can drop marker for making linked route. */
|
|
BOOL DropsMarker(CTFileName &fnmMarkerClass, CTString &strTargetProperty) const {
|
|
fnmMarkerClass = CTFILENAME("Classes\\EnemyMarker.ecl");
|
|
strTargetProperty = "Marker";
|
|
return TRUE;
|
|
}
|
|
const CTString &GetDescription(void) const {
|
|
((CTString&)m_strDescription).PrintF("-><none>");
|
|
if (m_penMarker!=NULL) {
|
|
((CTString&)m_strDescription).PrintF("->%s", (const char *) m_penMarker->GetName());
|
|
}
|
|
return m_strDescription;
|
|
}
|
|
|
|
virtual const CTFileName &GetComputerMessageName(void) const {
|
|
static CTFileName fnm(CTString(""));
|
|
return fnm;
|
|
}
|
|
|
|
// add to prediction any entities that this entity depends on
|
|
void AddDependentsToPrediction(void)
|
|
{
|
|
m_penSpray->AddToPrediction();
|
|
if (m_penWatcher!=NULL) {
|
|
GetWatcher()->AddToPrediction();
|
|
}
|
|
}
|
|
|
|
// create a checksum value for sync-check
|
|
void ChecksumForSync(ULONG &ulCRC, INDEX iExtensiveSyncCheck) {
|
|
CMovableModelEntity::ChecksumForSync(ulCRC, iExtensiveSyncCheck);
|
|
}
|
|
// dump sync data to text file
|
|
void DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck) // throw char *
|
|
{
|
|
CMovableModelEntity ::DumpSync_t(strm, iExtensiveSyncCheck);
|
|
strm.FPrintF_t("enemy: ");
|
|
if (m_penEnemy!=NULL) {
|
|
strm.FPrintF_t("id: %08X\n", m_penEnemy->en_ulID);
|
|
} else {
|
|
strm.FPrintF_t("none\n");
|
|
}
|
|
|
|
/*INDEX ctStates = en_stslStateStack.Count();
|
|
strm.FPrintF_t("state stack @%gs:\n", _pTimer->CurrentTick());
|
|
for(INDEX iState=ctStates-1; iState>=0; iState--) {
|
|
SLONG slState = en_stslStateStack[iState];
|
|
strm.FPrintF_t(" 0x%08x %s\n", slState, en_pecClass->ec_pdecDLLClass->HandlerNameForState(slState));
|
|
}*/
|
|
|
|
}
|
|
|
|
/* Read from stream. */
|
|
void Read_t( CTStream *istr) {
|
|
CMovableModelEntity::Read_t(istr);
|
|
|
|
// add to fuss if needed
|
|
if (m_penMainMusicHolder!=NULL) {
|
|
((CMusicHolder&)*m_penMainMusicHolder).m_cenFussMakers.Add(this);
|
|
}
|
|
};
|
|
|
|
/* Fill in entity statistics - for AI purposes only */
|
|
BOOL FillEntityStatistics(EntityStats *pes)
|
|
{
|
|
pes->es_strName = GetClass()->ec_pdecDLLClass->dec_strName;
|
|
if (m_bTemplate) {
|
|
pes->es_ctCount = 0;
|
|
} else {
|
|
pes->es_ctCount = 1;
|
|
}
|
|
pes->es_ctAmmount = 1;
|
|
pes->es_fValue = GetHealth();
|
|
pes->es_iScore = (INDEX) m_iScore;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Receive damage */
|
|
void ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType,
|
|
FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection)
|
|
{
|
|
// if template
|
|
if (m_bTemplate) {
|
|
// do nothing
|
|
return;
|
|
}
|
|
|
|
FLOAT fNewDamage = fDamageAmmount;
|
|
|
|
// adjust damage
|
|
fNewDamage *=DamageStrength( ((EntityInfo*)GetEntityInfo())->Eeibt, dmtType);
|
|
// apply game extra damage per enemy and per player
|
|
fNewDamage *=GetGameDamageMultiplier();
|
|
|
|
// if no damage
|
|
if (fNewDamage==0) {
|
|
// do nothing
|
|
return;
|
|
}
|
|
FLOAT fKickDamage = fNewDamage;
|
|
if( (dmtType == DMT_EXPLOSION) || (dmtType == DMT_IMPACT) || (dmtType == DMT_CANNONBALL_EXPLOSION) )
|
|
{
|
|
fKickDamage*=1.5f;
|
|
}
|
|
if (dmtType==DMT_DROWNING || dmtType==DMT_CLOSERANGE || dmtType==DMT_CHAINSAW) {
|
|
fKickDamage /= 10.0f;
|
|
}
|
|
if (dmtType==DMT_BURNING)
|
|
{
|
|
fKickDamage /= 100000.0f;
|
|
UBYTE ubR, ubG, ubB, ubA;
|
|
FLOAT fColorFactor=fNewDamage/m_fMaxHealth*255.0f;
|
|
ColorToRGBA(m_colBurning, ubR, ubG, ubB, ubA);
|
|
ubR=(UBYTE)ClampDn(ubR-fColorFactor, 32.0f);
|
|
m_colBurning=RGBAToColor(ubR, ubR, ubR, ubA);
|
|
}
|
|
|
|
// get passed time since last damage
|
|
TIME tmNow = _pTimer->CurrentTick();
|
|
TIME tmDelta = tmNow-m_tmLastDamage;
|
|
m_tmLastDamage = tmNow;
|
|
|
|
// fade damage out
|
|
if (tmDelta>=_pTimer->TickQuantum*3) {
|
|
m_vDamage=FLOAT3D(0,0,0);
|
|
}
|
|
// add new damage
|
|
FLOAT3D vDirectionFixed;
|
|
if (vDirection.ManhattanNorm()>0.5f) {
|
|
vDirectionFixed = vDirection;
|
|
} else {
|
|
vDirectionFixed = -en_vGravityDir;
|
|
}
|
|
FLOAT3D vDamageOld = m_vDamage;
|
|
/* if( (dmtType == DMT_EXPLOSION) || (dmtType == DMT_CANNONBALL_EXPLOSION) )
|
|
{
|
|
m_vDamage+=(vDirectionFixed/2-en_vGravityDir/2)*fKickDamage;
|
|
}
|
|
else*/
|
|
{
|
|
m_vDamage+=(vDirectionFixed-en_vGravityDir/2)*fKickDamage;
|
|
}
|
|
|
|
FLOAT fOldLen = vDamageOld.Length();
|
|
FLOAT fNewLen = m_vDamage.Length();
|
|
FLOAT fOldRootLen = Sqrt(fOldLen);
|
|
FLOAT fNewRootLen = Sqrt(fNewLen);
|
|
|
|
FLOAT fMassFactor = 300.0f/((EntityInfo*)GetEntityInfo())->fMass;
|
|
|
|
if( !(en_ulFlags & ENF_ALIVE))
|
|
{
|
|
fMassFactor /= 3;
|
|
}
|
|
|
|
if(fOldLen != 0.0f)
|
|
{
|
|
// cancel last push
|
|
GiveImpulseTranslationAbsolute( -vDamageOld/fOldRootLen*fMassFactor);
|
|
}
|
|
|
|
//-en_vGravityDir*fPushStrength/10
|
|
|
|
// push it back
|
|
GiveImpulseTranslationAbsolute( m_vDamage/fNewRootLen*fMassFactor);
|
|
|
|
/*if ((m_tmSpraySpawned<=_pTimer->CurrentTick()-_pTimer->TickQuantum ||
|
|
m_fSprayDamage+fNewDamage>50.0f)
|
|
&& m_fSpiritStartTime==0) {*/
|
|
|
|
if( m_fMaxDamageAmmount<fDamageAmmount)
|
|
{
|
|
m_fMaxDamageAmmount = fDamageAmmount;
|
|
}
|
|
// if it has no spray, or if this damage overflows it, and not already disappearing
|
|
if ((m_tmSpraySpawned<=_pTimer->CurrentTick()-_pTimer->TickQuantum*8 ||
|
|
m_fSprayDamage+fNewDamage>50.0f)
|
|
&& m_fSpiritStartTime==0 &&
|
|
dmtType!=DMT_CHAINSAW &&
|
|
!(dmtType==DMT_BURNING && GetHealth()<0) ) {
|
|
|
|
// spawn blood spray
|
|
CPlacement3D plSpray = CPlacement3D( vHitPoint, ANGLE3D(0, 0, 0));
|
|
m_penSpray = CreateEntity( plSpray, CLASS_BLOOD_SPRAY);
|
|
if(m_sptType != SPT_ELECTRICITY_SPARKS)
|
|
{
|
|
m_penSpray->SetParent( this);
|
|
}
|
|
|
|
ESpawnSpray eSpawnSpray;
|
|
eSpawnSpray.colBurnColor=C_WHITE|CT_OPAQUE;
|
|
|
|
if( m_fMaxDamageAmmount > 10.0f)
|
|
{
|
|
eSpawnSpray.fDamagePower = 3.0f;
|
|
}
|
|
else if(m_fSprayDamage+fNewDamage>50.0f)
|
|
{
|
|
eSpawnSpray.fDamagePower = 2.0f;
|
|
}
|
|
else
|
|
{
|
|
eSpawnSpray.fDamagePower = 1.0f;
|
|
}
|
|
|
|
eSpawnSpray.sptType = m_sptType;
|
|
eSpawnSpray.fSizeMultiplier = 1.0f;
|
|
|
|
// setup direction of spray
|
|
FLOAT3D vHitPointRelative = vHitPoint - GetPlacement().pl_PositionVector;
|
|
FLOAT3D vReflectingNormal;
|
|
GetNormalComponent( vHitPointRelative, en_vGravityDir, vReflectingNormal);
|
|
vReflectingNormal.SafeNormalize();
|
|
|
|
vReflectingNormal(1)/=5.0f;
|
|
|
|
FLOAT3D vProjectedComponent = vReflectingNormal*(vDirection%vReflectingNormal);
|
|
FLOAT3D vSpilDirection = vDirection-vProjectedComponent*2.0f-en_vGravityDir*0.5f;
|
|
|
|
eSpawnSpray.vDirection = vSpilDirection;
|
|
eSpawnSpray.penOwner = this;
|
|
|
|
/*if (dmtType==DMT_BURNING && GetHealth()<0)
|
|
{
|
|
eSpawnSpray.fDamagePower = 1.0f;
|
|
}*/
|
|
|
|
// initialize spray
|
|
m_penSpray->Initialize( eSpawnSpray);
|
|
m_tmSpraySpawned = _pTimer->CurrentTick();
|
|
m_fSprayDamage = 0.0f;
|
|
m_fMaxDamageAmmount = 0.0f;
|
|
}
|
|
m_fSprayDamage+=fNewDamage;
|
|
|
|
CMovableModelEntity::ReceiveDamage(penInflictor,
|
|
dmtType, fNewDamage, vHitPoint, vDirection);
|
|
};
|
|
|
|
|
|
/************************************************************
|
|
* FADE OUT *
|
|
************************************************************/
|
|
|
|
BOOL AdjustShadingParameters(FLOAT3D &vLightDirection, COLOR &colLight, COLOR &colAmbient)
|
|
{
|
|
colAmbient = AddColors( colAmbient, m_colColor);
|
|
if( m_bFadeOut) {
|
|
FLOAT fTimeRemain = m_fFadeStartTime + m_fFadeTime - _pTimer->CurrentTick();
|
|
if( fTimeRemain < 0.0f) { fTimeRemain = 0.0f; }
|
|
COLOR colAlpha;
|
|
if(en_RenderType == RT_SKAMODEL || en_RenderType == RT_SKAEDITORMODEL) {
|
|
colAlpha = GetModelInstance()->GetModelColor();
|
|
colAlpha = (colAlpha&0xFFFFFF00) + (COLOR(fTimeRemain/m_fFadeTime*0xFF)&0xFF);
|
|
GetModelInstance()->SetModelColor(colAlpha);
|
|
}
|
|
else {
|
|
colAlpha = GetModelObject()->mo_colBlendColor;
|
|
colAlpha = (colAlpha&0xFFFFFF00) + (COLOR(fTimeRemain/m_fFadeTime*0xFF)&0xFF);
|
|
GetModelObject()->mo_colBlendColor = colAlpha;
|
|
}
|
|
|
|
} else {
|
|
if (GetSP()->sp_bMental) {
|
|
if (GetHealth()<=0) {
|
|
if(en_RenderType == RT_SKAMODEL || en_RenderType == RT_SKAEDITORMODEL) {
|
|
GetModelInstance()->SetModelColor(C_WHITE&0xFF);
|
|
} else {
|
|
GetModelObject()->mo_colBlendColor = C_WHITE&0xFF;
|
|
}
|
|
} else {
|
|
extern FLOAT ent_tmMentalIn ;
|
|
extern FLOAT ent_tmMentalOut ;
|
|
extern FLOAT ent_tmMentalFade;
|
|
FLOAT tmIn = ent_tmMentalIn ;
|
|
FLOAT tmOut = ent_tmMentalOut ;
|
|
FLOAT tmFade = ent_tmMentalFade;
|
|
FLOAT tmExist = tmFade+tmIn+tmFade;
|
|
FLOAT tmTotal = tmFade+tmIn+tmFade+tmOut;
|
|
|
|
FLOAT tmTime = _pTimer->GetLerpedCurrentTick();
|
|
FLOAT fFactor = 1;
|
|
if (tmTime>0.1f) {
|
|
tmTime += en_ulID*123.456f;
|
|
tmTime = fmod(tmTime, tmTotal);
|
|
fFactor = CalculateRatio(tmTime, 0, tmExist, tmFade/tmExist, tmFade/tmExist);
|
|
}
|
|
|
|
if(en_RenderType == RT_SKAMODEL || en_RenderType == RT_SKAEDITORMODEL) {
|
|
GetModelInstance()->SetModelColor(C_WHITE|INDEX(0xFF*fFactor));
|
|
} else {
|
|
GetModelObject()->mo_colBlendColor = C_WHITE|INDEX(0xFF*fFactor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(m_colBurning!=COLOR(C_WHITE|CT_OPAQUE))
|
|
{
|
|
colAmbient = MulColors( colAmbient, m_colBurning);
|
|
colLight = MulColors( colLight, m_colBurning);
|
|
}
|
|
return CMovableModelEntity::AdjustShadingParameters(vLightDirection, colLight, colAmbient);
|
|
};
|
|
|
|
|
|
// fuss functions
|
|
void AddToFuss(void)
|
|
{
|
|
if (IsPredictor()) {
|
|
// remember last fuss time
|
|
m_tmLastFussTime = _pTimer->CurrentTick();
|
|
return;
|
|
}
|
|
|
|
// if no music holder remembered - not in fuss
|
|
if (m_penMainMusicHolder==NULL) {
|
|
// find main music holder
|
|
m_penMainMusicHolder = _pNetwork->GetEntityWithName("MusicHolder", 0);
|
|
// if no music holder found
|
|
if (m_penMainMusicHolder==NULL) {
|
|
// just remember last fuss time
|
|
m_tmLastFussTime = _pTimer->CurrentTick();
|
|
// cannot make fuss
|
|
return;
|
|
}
|
|
// add to end of fuss list
|
|
((CMusicHolder&)*m_penMainMusicHolder).m_cenFussMakers.Add(this);
|
|
// if boss set as boss
|
|
if (m_bBoss) {
|
|
((CMusicHolder&)*m_penMainMusicHolder).m_penBoss = this;
|
|
}
|
|
// remember last fuss time
|
|
m_tmLastFussTime = _pTimer->CurrentTick();
|
|
|
|
// if music holder remembered - still in fuss
|
|
} else {
|
|
// must be in list
|
|
ASSERT(((CMusicHolder&)*m_penMainMusicHolder).m_cenFussMakers.IsMember(this));
|
|
// if boss set as boss
|
|
if (m_bBoss) {
|
|
((CMusicHolder&)*m_penMainMusicHolder).m_penBoss = this;
|
|
}
|
|
// just remember last fuss time
|
|
m_tmLastFussTime = _pTimer->CurrentTick();
|
|
}
|
|
}
|
|
void RemoveFromFuss(void)
|
|
{
|
|
if (IsPredictor()) {
|
|
return;
|
|
}
|
|
// if no music holder remembered
|
|
if (m_penMainMusicHolder==NULL) {
|
|
// not in fuss
|
|
return;
|
|
}
|
|
// just remove from list
|
|
((CMusicHolder&)*m_penMainMusicHolder).m_cenFussMakers.Remove(this);
|
|
// if boss, clear boss
|
|
if (m_bBoss) {
|
|
if (((CMusicHolder&)*m_penMainMusicHolder).m_penBoss != this) {
|
|
CPrintF(TRANSV("More than one boss active!\n"));
|
|
((CMusicHolder&)*m_penMainMusicHolder).m_penBoss = NULL;
|
|
}
|
|
}
|
|
m_penMainMusicHolder = NULL;
|
|
}
|
|
|
|
// check if should give up attacking
|
|
BOOL ShouldCeaseAttack(void)
|
|
{
|
|
// if there is no valid the enemy
|
|
if (m_penEnemy==NULL ||
|
|
!(m_penEnemy->GetFlags()&ENF_ALIVE) ||
|
|
(m_penEnemy->GetFlags()&ENF_DELETED)) {
|
|
// cease attack
|
|
return TRUE;
|
|
}
|
|
// if not active in fighting
|
|
if (_pTimer->CurrentTick()>m_tmLastFussTime+m_tmGiveUp) {
|
|
// cease attack
|
|
return TRUE;
|
|
}
|
|
// otherwise, continue
|
|
return FALSE;
|
|
}
|
|
|
|
// Stretch model - MUST BE DONE BEFORE SETTING MODEL!
|
|
virtual void SizeModel(void)
|
|
{
|
|
FLOAT3D vStretch = GetModelStretch();
|
|
|
|
// apply defined stretch
|
|
vStretch *= m_fStretchMultiplier;
|
|
|
|
// if should apply random stretch
|
|
if (m_bApplyRandomStretch)
|
|
{
|
|
// will be done only when user clicks "Apply random" switch
|
|
m_bApplyRandomStretch = FALSE;
|
|
// get rnd for random stretch
|
|
m_fRandomStretchMultiplier = (FRnd()-0.5f)*m_fRandomStretchFactor+1.0f;
|
|
}
|
|
|
|
// apply random stretch
|
|
vStretch *= m_fRandomStretchMultiplier;
|
|
|
|
if (m_bResizeAttachments) {
|
|
StretchModel( vStretch);
|
|
} else if (TRUE) {
|
|
StretchSingleModel( vStretch);
|
|
}
|
|
ModelChangeNotify();
|
|
};
|
|
|
|
// check if an entity is valid for being your new enemy
|
|
BOOL IsValidForEnemy(CEntity *penPlayer)
|
|
{
|
|
return
|
|
penPlayer!=NULL &&
|
|
IsDerivedFromClass(penPlayer, "Player") &&
|
|
penPlayer->GetFlags()&ENF_ALIVE;
|
|
}
|
|
|
|
// unset target
|
|
void SetTargetNone(void)
|
|
{
|
|
m_ttTarget = TT_NONE;
|
|
m_dtDestination = DT_PLAYERCURRENT;
|
|
m_penEnemy = NULL;
|
|
}
|
|
|
|
// set new player as soft target if possible
|
|
BOOL SetTargetSoft(CEntity *penPlayer)
|
|
{
|
|
// if invalid target
|
|
if (!IsValidForEnemy(penPlayer)) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
// if we already have any kind of target
|
|
if (m_ttTarget!=TT_NONE) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
// remember new soft target
|
|
CEntity *penOld = m_penEnemy;
|
|
m_ttTarget = TT_SOFT;
|
|
m_dtDestination = DT_PLAYERCURRENT;
|
|
m_penEnemy = penPlayer;
|
|
return penOld!=penPlayer;
|
|
}
|
|
|
|
// set new player as hard target if possible
|
|
BOOL SetTargetHard(CEntity *penPlayer)
|
|
{
|
|
// if invalid target
|
|
if (!IsValidForEnemy(penPlayer)) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
// if we already have hard target
|
|
if (m_ttTarget==TT_HARD) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
// remember new hard target
|
|
CEntity *penOld = m_penEnemy;
|
|
m_ttTarget = TT_HARD;
|
|
m_dtDestination = DT_PLAYERCURRENT;
|
|
m_penEnemy = penPlayer;
|
|
return penOld!=penPlayer;
|
|
}
|
|
|
|
// force new player to be hard target
|
|
BOOL SetTargetHardForce(CEntity *penPlayer)
|
|
{
|
|
// if invalid target
|
|
if (!IsValidForEnemy(penPlayer)) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
// remember new hard target
|
|
CEntity *penOld = m_penEnemy;
|
|
m_ttTarget = TT_HARD;
|
|
m_dtDestination = DT_PLAYERCURRENT;
|
|
m_penEnemy = penPlayer;
|
|
return penOld!=penPlayer;
|
|
}
|
|
|
|
/************************************************************
|
|
* MOVING FUNCTIONS *
|
|
************************************************************/
|
|
|
|
// get movement frequency for attack
|
|
virtual FLOAT GetAttackMoveFrequency(FLOAT fEnemyDistance)
|
|
{
|
|
if (fEnemyDistance>GetProp(m_fCloseDistance)) {
|
|
return 0.5f;
|
|
} else {
|
|
return 0.25f;
|
|
}
|
|
}
|
|
|
|
// set speeds for movement towards desired position
|
|
virtual void SetSpeedsToDesiredPosition(const FLOAT3D &vPosDelta, FLOAT fPosDist, BOOL bGoingToPlayer)
|
|
{
|
|
FLOAT fEnemyDistance = CalcDist(m_penEnemy);
|
|
FLOAT fCloseDistance = GetProp(m_fCloseDistance);
|
|
FLOAT fStopDistance = GetProp(m_fStopDistance);
|
|
// find relative direction angle
|
|
FLOAT fCos = GetPlaneFrustumAngle(vPosDelta);
|
|
// if may move and
|
|
if (MayMoveToAttack() &&
|
|
// more or less ahead and
|
|
fCos>CosFast(45.0f) &&
|
|
// not too close
|
|
fEnemyDistance>fStopDistance) {
|
|
// move and rotate towards it
|
|
if (fEnemyDistance<fCloseDistance) {
|
|
m_fMoveSpeed = GetProp(m_fCloseRunSpeed);
|
|
m_aRotateSpeed = GetProp(m_aCloseRotateSpeed);
|
|
} else {
|
|
m_fMoveSpeed = GetProp(m_fAttackRunSpeed);
|
|
m_aRotateSpeed = GetProp(m_aAttackRotateSpeed);
|
|
}
|
|
|
|
// otherwise if following tactics, move anyway
|
|
} else if (m_bTacticActive) {
|
|
// move and rotate towards it
|
|
if (fEnemyDistance<fCloseDistance) {
|
|
m_fMoveSpeed = GetProp(m_fCloseRunSpeed);
|
|
m_aRotateSpeed = GetProp(m_aCloseRotateSpeed);
|
|
} else {
|
|
m_fMoveSpeed = GetProp(m_fAttackRunSpeed);
|
|
m_aRotateSpeed = GetProp(m_aAttackRotateSpeed);
|
|
}
|
|
|
|
// otherwise if not exactly in front
|
|
} else if (fCos<CosFast(15.0f)) {
|
|
// just rotate towards it
|
|
m_fMoveSpeed = 0;
|
|
if (fEnemyDistance<fCloseDistance) {
|
|
m_aRotateSpeed = GetProp(m_aCloseRotateSpeed);
|
|
} else {
|
|
m_aRotateSpeed = GetProp(m_aAttackRotateSpeed);
|
|
}
|
|
|
|
// otherwise (stop range)
|
|
} else {
|
|
// if going towards player, or would leave attackradius
|
|
if (bGoingToPlayer || !WouldNotLeaveAttackRadius()) {
|
|
// stand in place
|
|
m_fMoveSpeed = 0;
|
|
m_aRotateSpeed = 0;
|
|
// if going to some other location (some pathfinding AI scheme)
|
|
} else {
|
|
m_fMoveSpeed = GetProp(m_fCloseRunSpeed);
|
|
m_aRotateSpeed = GetProp(m_aCloseRotateSpeed);
|
|
}
|
|
}
|
|
}
|
|
|
|
// get movement animation for given flags with current movement type
|
|
virtual void MovementAnimation(ULONG ulFlags)
|
|
{
|
|
if (ulFlags&MF_MOVEZ) {
|
|
if (m_fMoveSpeed==GetProp(m_fAttackRunSpeed) || m_fMoveSpeed==GetProp(m_fCloseRunSpeed)
|
|
|| m_fMoveSpeed>GetProp(m_fWalkSpeed)) {
|
|
RunningAnim();
|
|
} else {
|
|
WalkingAnim();
|
|
}
|
|
} else if (ulFlags&MF_ROTATEH) {
|
|
RotatingAnim();
|
|
} else {
|
|
if (m_penEnemy!=NULL) {
|
|
StandingAnimFight();
|
|
} else {
|
|
StandingAnim();
|
|
}
|
|
}
|
|
}
|
|
|
|
// set desired rotation and translation to go/orient towards desired position
|
|
// and get the resulting movement type
|
|
virtual ULONG SetDesiredMovement(void)
|
|
{
|
|
ULONG ulFlags = 0;
|
|
|
|
// get delta to desired position
|
|
FLOAT3D vDelta = m_vDesiredPosition - GetPlacement().pl_PositionVector;
|
|
|
|
if (m_dtDestination==DT_PLAYERCURRENT) {
|
|
ApplyTactics(vDelta);
|
|
}
|
|
|
|
// if we may rotate
|
|
if (m_aRotateSpeed>0.0f) {
|
|
// get desired heading orientation
|
|
FLOAT3D vDir = vDelta;
|
|
vDir.SafeNormalize();
|
|
ANGLE aWantedHeadingRelative = GetRelativeHeading(vDir);
|
|
|
|
// normalize it to [-180,+180] degrees
|
|
aWantedHeadingRelative = NormalizeAngle(aWantedHeadingRelative);
|
|
|
|
ANGLE aHeadingRotation;
|
|
// if desired position is left
|
|
if (aWantedHeadingRelative < -m_aRotateSpeed*m_fMoveFrequency) {
|
|
// start turning left
|
|
aHeadingRotation = -m_aRotateSpeed;
|
|
// if desired position is right
|
|
} else if (aWantedHeadingRelative > m_aRotateSpeed*m_fMoveFrequency) {
|
|
// start turning right
|
|
aHeadingRotation = +m_aRotateSpeed;
|
|
// if desired position is more-less ahead
|
|
} else {
|
|
// keep just the adjusting fraction of speed
|
|
aHeadingRotation = aWantedHeadingRelative/m_fMoveFrequency;
|
|
}
|
|
// start rotating
|
|
SetDesiredRotation(ANGLE3D(aHeadingRotation, 0, 0));
|
|
|
|
if (Abs(aHeadingRotation)>1.0f) {
|
|
ulFlags |= MF_ROTATEH;
|
|
}
|
|
|
|
// if we may not rotate
|
|
} else {
|
|
// stop rotating
|
|
SetDesiredRotation(ANGLE3D(0, 0, 0));
|
|
}
|
|
|
|
// if we may move
|
|
if (m_fMoveSpeed>0.0f) {
|
|
// determine translation speed
|
|
FLOAT3D vTranslation(0.0f, 0.0f, 0.0f);
|
|
vTranslation(3) = -m_fMoveSpeed;
|
|
|
|
// start moving
|
|
SetDesiredTranslation(vTranslation);
|
|
|
|
ulFlags |= MF_MOVEZ;
|
|
|
|
// if we may not move
|
|
} else {
|
|
// stop translating
|
|
SetDesiredTranslation(FLOAT3D(0, 0, 0));
|
|
}
|
|
|
|
return ulFlags;
|
|
};
|
|
|
|
// stop moving entity
|
|
void StopMoving()
|
|
{
|
|
StopRotating();
|
|
StopTranslating();
|
|
};
|
|
|
|
// stop desired rotation
|
|
void StopRotating()
|
|
{
|
|
SetDesiredRotation(ANGLE3D(0, 0, 0));
|
|
};
|
|
|
|
// stop desired translation
|
|
void StopTranslating()
|
|
{
|
|
SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
|
|
};
|
|
|
|
// calc distance to entity in one plane (relative to owner gravity)
|
|
FLOAT CalcDistanceInPlaneToDestination(void)
|
|
{
|
|
// find vector from you to target in XZ plane
|
|
FLOAT3D vNormal;
|
|
GetNormalComponent(m_vDesiredPosition - GetPlacement().pl_PositionVector, en_vGravityDir, vNormal);
|
|
return vNormal.Length();
|
|
};
|
|
|
|
// initialize path finding
|
|
virtual void StartPathFinding(void)
|
|
{
|
|
ASSERT(m_dtDestination==DT_PATHPERSISTENT || m_dtDestination==DT_PATHTEMPORARY);
|
|
|
|
CEntity *penMarker;
|
|
FLOAT3D vPath;
|
|
// find first marker to go to
|
|
PATH_FindFirstMarker(this,
|
|
GetPlacement().pl_PositionVector, m_penEnemy->GetPlacement().pl_PositionVector,
|
|
penMarker, vPath);
|
|
// if not found, or not visible
|
|
if (penMarker==NULL || !IsVisible(penMarker)) {
|
|
// no path finding
|
|
m_dtDestination=DT_PLAYERSPOTTED;
|
|
// remember as if spotted position
|
|
m_vPlayerSpotted = PlayerDestinationPos();
|
|
return;
|
|
}
|
|
// remember the marker and position
|
|
m_vDesiredPosition = vPath,
|
|
m_penPathMarker = penMarker;
|
|
}
|
|
|
|
// find next navigation marker to go to
|
|
virtual void FindNextPathMarker(void)
|
|
{
|
|
// if invalid situation
|
|
if (m_penPathMarker==NULL) {
|
|
// this should not happen
|
|
ASSERT(FALSE);
|
|
// no path finding
|
|
m_dtDestination=DT_PLAYERCURRENT;
|
|
return;
|
|
}
|
|
|
|
// find first marker to go to
|
|
CEntity *penMarker = m_penPathMarker;
|
|
FLOAT3D vPath;
|
|
PATH_FindNextMarker(this,
|
|
GetPlacement().pl_PositionVector, m_penEnemy->GetPlacement().pl_PositionVector,
|
|
penMarker, vPath);
|
|
|
|
// if not found
|
|
if (penMarker==NULL || !IsVisible(penMarker)) {
|
|
// no path finding
|
|
m_dtDestination=DT_PLAYERSPOTTED;
|
|
// remember as if spotted position
|
|
m_vPlayerSpotted = PlayerDestinationPos();
|
|
return;
|
|
}
|
|
|
|
// remember the marker and position
|
|
m_vDesiredPosition = vPath,
|
|
m_penPathMarker = penMarker;
|
|
}
|
|
|
|
// check if a touch event triggers pathfinding
|
|
BOOL CheckTouchForPathFinding(const ETouch &eTouch)
|
|
{
|
|
// if no enemy
|
|
if (m_penEnemy==NULL) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
|
|
// if already path finding
|
|
if (m_dtDestination==DT_PATHPERSISTENT || m_dtDestination==DT_PATHTEMPORARY) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
|
|
FLOAT3D vDir = en_vDesiredTranslationRelative;
|
|
vDir.SafeNormalize();
|
|
vDir*=GetRotationMatrix();
|
|
// if the touched plane is more or less orthogonal to the current velocity
|
|
if ((eTouch.plCollision%vDir)<-0.5f) {
|
|
if (m_penEnemy!=NULL && IsVisible(m_penEnemy)) {
|
|
m_dtDestination = DT_PATHPERSISTENT;
|
|
} else {
|
|
m_dtDestination = DT_PATHTEMPORARY;
|
|
}
|
|
StartPathFinding();
|
|
return m_penPathMarker!=NULL;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// check if a wouldfall event triggers pathfinding
|
|
BOOL CheckFallForPathFinding(const EWouldFall &eWouldFall)
|
|
{
|
|
// if no enemy
|
|
if (m_penEnemy==NULL) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
|
|
// if already path finding
|
|
if (m_dtDestination==DT_PATHPERSISTENT || m_dtDestination==DT_PATHTEMPORARY) {
|
|
// do nothing
|
|
return FALSE;
|
|
}
|
|
|
|
if (m_penEnemy!=NULL && IsVisible(m_penEnemy)) {
|
|
m_dtDestination = DT_PATHPERSISTENT;
|
|
} else {
|
|
m_dtDestination = DT_PATHTEMPORARY;
|
|
}
|
|
StartPathFinding();
|
|
|
|
return m_penPathMarker!=NULL;
|
|
}
|
|
|
|
/************************************************************
|
|
* TACTICS FUNCTIONS *
|
|
************************************************************/
|
|
|
|
void InitializeTactics( void ) {
|
|
|
|
// return if there is no tactics manager or if it points to wrong type of entity
|
|
// or if there is no enemy
|
|
if (m_penTacticsHolder==NULL || !IsOfClass(m_penTacticsHolder, "TacticsHolder")
|
|
|| m_penEnemy==NULL) {
|
|
return;
|
|
}
|
|
|
|
CTacticsHolder *penTactics = &(CTacticsHolder &)*m_penTacticsHolder;
|
|
|
|
//m_tmTacticsActivation = penTactics->m_tmLastActivation;
|
|
m_tmTacticsActivation = _pTimer->CurrentTick();
|
|
m_vTacticsStartPosition = GetPlacement().pl_PositionVector;
|
|
//m_iTacticsRetried = penTactics->m_bRetryCount;
|
|
|
|
FLOAT fSign;
|
|
// sign for randomized parameters
|
|
if (Sgn(penTactics->m_fParam2)>0 && Sgn(penTactics->m_fParam1)>0) {
|
|
fSign = +1.0f;
|
|
} else if (Sgn(penTactics->m_fParam2)<0 && Sgn(penTactics->m_fParam1)<0) {
|
|
fSign = -1.0f;
|
|
} else {
|
|
fSign = Sgn(FRnd()-0.5f);
|
|
}
|
|
|
|
switch (penTactics->m_tctType) {
|
|
case TCT_DAMP_ANGLE_STRIFE: {
|
|
// 1) random angle (<max, >min)
|
|
m_fTacticVar1=Lerp(Abs(penTactics->m_fParam1), Abs(penTactics->m_fParam2), FRnd())*fSign;
|
|
// 2) time dump
|
|
m_fTacticVar2=penTactics->m_fParam4;
|
|
// 3) dump factor, factor (0-1) of min distance when linear behaviour begins
|
|
m_fTacticVar3=penTactics->m_fParam3;
|
|
// 4) initial distance
|
|
m_fTacticVar4=(m_penEnemy->GetPlacement().pl_PositionVector - m_vTacticsStartPosition).Length();
|
|
// 5) tactics stop distance
|
|
m_fTacticVar5=penTactics->m_fParam5;
|
|
break; }
|
|
|
|
case TCT_PARALLEL_RANDOM_DISTANCE:
|
|
// 1) randomized distance
|
|
m_fTacticVar1=Lerp(penTactics->m_fParam4, penTactics->m_fParam5, FRnd());
|
|
// 4) emission angle
|
|
m_fTacticVar4=Lerp(Abs(penTactics->m_fParam1), Abs(penTactics->m_fParam2), FRnd())*fSign;
|
|
// 2) tolerance strip width
|
|
m_fTacticVar2=m_fAttackRunSpeed*2.0f*90.0f/m_aAttackRotateSpeed;
|
|
//m_fTacticVar2=2.0f*m_fAttackRunSpeed;
|
|
// 3) fade in/out ratio
|
|
m_fTacticVar3=penTactics->m_fParam3;
|
|
// 5) initial distance
|
|
m_fTacticVar5=(GetPlacement().pl_PositionVector - m_penEnemy->GetPlacement().pl_PositionVector).Length();
|
|
// as a precausion, assume minimal strip width of 2m
|
|
m_fTacticVar2=ClampDn(m_fTacticVar2, 2.0f);
|
|
|
|
break;
|
|
|
|
case TCT_STATIC_RANDOM_V_DISTANCE:
|
|
// 1) starting angle
|
|
m_fTacticVar1=Lerp(Abs(penTactics->m_fParam1), Abs(penTactics->m_fParam2), FRnd())*fSign;
|
|
// 2) time to run in the desired V direction
|
|
m_fTacticVar2=Lerp(penTactics->m_fParam3, penTactics->m_fParam4, FRnd());
|
|
break;
|
|
}
|
|
}
|
|
|
|
virtual void ApplyTactics( FLOAT3D &vDesiredPos) {
|
|
|
|
// return if there is no tactics manager or if it points to wrong type of entity
|
|
// or if there is no enemy
|
|
if (m_penTacticsHolder==NULL || !IsOfClass(m_penTacticsHolder, "TacticsHolder")
|
|
|| m_penEnemy==NULL) {
|
|
return;
|
|
}
|
|
|
|
CTacticsHolder *penTactics = &(CTacticsHolder &)*m_penTacticsHolder;
|
|
|
|
// See if the last activation time of TacticsHolder is greater then the activation
|
|
// time of this monster. If so, start (or reinitialize) the tactics.
|
|
if (penTactics->m_tmLastActivation==-1 || penTactics->m_tctType==TCT_NONE) {
|
|
m_bTacticActive = FALSE;
|
|
} else if (m_tmTacticsActivation < penTactics->m_tmLastActivation) {
|
|
InitializeTactics();
|
|
m_bTacticActive = TRUE;
|
|
}
|
|
|
|
if (m_bTacticActive) {
|
|
|
|
// calculate shared parameters
|
|
FLOAT3D vEnemyDistance=m_vTacticsStartPosition - m_penEnemy->GetPlacement().pl_PositionVector;
|
|
FLOAT fEnemyDistance=vEnemyDistance.Length();
|
|
vEnemyDistance.SafeNormalize();
|
|
ANGLE3D angEnemy = ANGLE3D(0.0f, 0.0f, 0.0f);
|
|
//DirectionVectorToAngles(vEnemyDistance, angEnemy);
|
|
|
|
FLOAT fDistanceRatio = 0.0f;
|
|
FLOAT fTimeRatio = 0.0f;
|
|
|
|
switch(penTactics->m_tctType)
|
|
{
|
|
case TCT_DAMP_ANGLE_STRIFE: {
|
|
// if very close to player, stop using tactics
|
|
if (CalcDist(m_penEnemy)<m_fTacticVar5) {
|
|
m_bTacticActive = FALSE;
|
|
}
|
|
|
|
fDistanceRatio=1.0f;
|
|
if(m_fTacticVar3>0) {
|
|
// get enemy distance
|
|
FLOAT fClamped=Clamp(CalcDist(m_penEnemy)-(m_fTacticVar4*m_fTacticVar3), 0.0f, m_fTacticVar4);
|
|
fDistanceRatio=fClamped/(m_fTacticVar4*(1-m_fTacticVar3));
|
|
}
|
|
|
|
fTimeRatio=1.0f;
|
|
if(m_fTacticVar2>0) {
|
|
fTimeRatio=1.0f-(ClampUp((_pTimer->CurrentTick() - m_tmTacticsActivation)/m_fTacticVar2, 1.0f));
|
|
}
|
|
|
|
angEnemy(1) = m_fTacticVar1*fDistanceRatio*fTimeRatio;
|
|
angEnemy(2) = 0.0f;
|
|
angEnemy(3) = 0.0f;
|
|
|
|
FLOATmatrix3D mHeading;
|
|
MakeRotationMatrixFast(mHeading, angEnemy);
|
|
vDesiredPos = vDesiredPos*!en_mRotation;
|
|
vDesiredPos = vDesiredPos*mHeading;
|
|
vDesiredPos = vDesiredPos*en_mRotation;
|
|
|
|
break; }
|
|
|
|
case TCT_PARALLEL_RANDOM_DISTANCE: {
|
|
// line from spawner to player
|
|
FLOAT3D vLinePlayerToSpawn = m_vTacticsStartPosition - m_penEnemy->GetPlacement().pl_PositionVector;
|
|
// line from *this to player
|
|
FLOAT3D vLinePlayerToThis = GetPlacement().pl_PositionVector - m_penEnemy->GetPlacement().pl_PositionVector;
|
|
|
|
FLOAT fThisOnLine = (vLinePlayerToThis%vLinePlayerToSpawn)/vLinePlayerToSpawn.Length();
|
|
FLOAT3D vThisOnLine = m_penEnemy->GetPlacement().pl_PositionVector + vLinePlayerToSpawn.SafeNormalize()*fThisOnLine;
|
|
|
|
FLOAT fLineDist = (GetPlacement().pl_PositionVector - vThisOnLine).Length();
|
|
|
|
FLOATmatrix3D mHeading;
|
|
|
|
//CPrintF("line dst = %f at %f\n", fLineDist, _pTimer->CurrentTick());
|
|
// if close enough to enemy stop tactics and go linear
|
|
if (vLinePlayerToThis.Length()<m_fTacticVar1) {
|
|
m_bTacticActive = FALSE;
|
|
// if closer to spawner-enemy line then supposed to
|
|
} else if (fLineDist<m_fTacticVar1) {
|
|
if (fLineDist<1.0f) { fLineDist=1.0f; }
|
|
angEnemy(1) = m_fTacticVar4/fLineDist;
|
|
angEnemy(2) = 0.0f;
|
|
angEnemy(3) = 0.0f;
|
|
|
|
MakeRotationMatrixFast(mHeading, angEnemy);
|
|
vDesiredPos = vDesiredPos*!en_mRotation;
|
|
vDesiredPos = vDesiredPos*mHeading;
|
|
vDesiredPos = vDesiredPos*en_mRotation;
|
|
// if further from spawner-enemy line then supposed to
|
|
} else if (fLineDist>m_fTacticVar1+m_fTacticVar2) {
|
|
if (fLineDist<1.0f) { fLineDist=1.0f; }
|
|
angEnemy(1) = -m_fTacticVar4/fLineDist;
|
|
angEnemy(2) = 0.0f;
|
|
angEnemy(3) = 0.0f;
|
|
|
|
MakeRotationMatrixFast(mHeading, angEnemy);
|
|
vDesiredPos = vDesiredPos*!en_mRotation;
|
|
vDesiredPos = vDesiredPos*mHeading;
|
|
vDesiredPos = vDesiredPos*en_mRotation;
|
|
// right on the line
|
|
} else {
|
|
vDesiredPos = -vLinePlayerToSpawn;
|
|
}
|
|
break; }
|
|
|
|
case TCT_STATIC_RANDOM_V_DISTANCE: {
|
|
if (_pTimer->CurrentTick()<m_tmTacticsActivation+m_fTacticVar2) {
|
|
angEnemy(1) = m_fTacticVar1;
|
|
angEnemy(2) = 0.0f;
|
|
angEnemy(3) = 0.0f;
|
|
} else {
|
|
m_bTacticActive = FALSE;
|
|
}
|
|
|
|
FLOATmatrix3D mHeading;
|
|
MakeRotationMatrixFast(mHeading, angEnemy);
|
|
vDesiredPos = vDesiredPos*!en_mRotation;
|
|
vDesiredPos = vDesiredPos*mHeading;
|
|
vDesiredPos = vDesiredPos*en_mRotation;
|
|
|
|
break; }
|
|
}
|
|
}
|
|
}
|
|
|
|
void StartTacticsNow ( void ) {
|
|
m_tmTacticsActivation = -1.0f;
|
|
}
|
|
|
|
/************************************************************
|
|
* ATTACK SPECIFIC *
|
|
************************************************************/
|
|
// can attack (shoot) at entity in plane - ground support
|
|
BOOL CanAttackEnemy(CEntity *penTarget, FLOAT fCosAngle) {
|
|
if (IsInPlaneFrustum(penTarget, fCosAngle)) {
|
|
if (IsVisibleCheckAll(penTarget)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
};
|
|
|
|
// close attack if possible
|
|
virtual BOOL CanHitEnemy(CEntity *penTarget, FLOAT fCosAngle) {
|
|
if (IsInFrustum(penTarget, fCosAngle)) {
|
|
return IsVisibleCheckAll(penTarget);
|
|
}
|
|
return FALSE;
|
|
};
|
|
|
|
// see entity
|
|
BOOL SeeEntity(CEntity *pen, FLOAT fCosAngle) {
|
|
if (IsInFrustum(pen, fCosAngle)) {
|
|
return IsVisible(pen);
|
|
}
|
|
return FALSE;
|
|
};
|
|
|
|
// see entity in plane
|
|
BOOL SeeEntityInPlane(CEntity *pen, FLOAT fCosAngle) {
|
|
CalcPlaneDist(pen);
|
|
if (IsInPlaneFrustum(pen, fCosAngle)) {
|
|
return IsVisible(pen);
|
|
}
|
|
return FALSE;
|
|
};
|
|
|
|
// prepare propelled projectile
|
|
void PreparePropelledProjectile(CPlacement3D &plProjectile, const FLOAT3D vShootTarget,
|
|
const FLOAT3D &vOffset, const ANGLE3D &aOffset)
|
|
{
|
|
FLOAT3D vDiff = (vShootTarget - (GetPlacement().pl_PositionVector + vOffset*GetRotationMatrix())).SafeNormalize();
|
|
|
|
// find orientation towards target
|
|
FLOAT3D mToTargetX, mToTargetY, mToTargetZ;
|
|
mToTargetZ = -vDiff;
|
|
mToTargetY = -en_vGravityDir;
|
|
mToTargetX = (mToTargetY*mToTargetZ).SafeNormalize();
|
|
mToTargetY = (mToTargetZ*mToTargetX).SafeNormalize();
|
|
FLOATmatrix3D mToTarget;
|
|
mToTarget(1,1) = mToTargetX(1); mToTarget(1,2) = mToTargetY(1); mToTarget(1,3) = mToTargetZ(1);
|
|
mToTarget(2,1) = mToTargetX(2); mToTarget(2,2) = mToTargetY(2); mToTarget(2,3) = mToTargetZ(2);
|
|
mToTarget(3,1) = mToTargetX(3); mToTarget(3,2) = mToTargetY(3); mToTarget(3,3) = mToTargetZ(3);
|
|
|
|
// calculate placement of projectile to be at given offset
|
|
plProjectile.pl_PositionVector = GetPlacement().pl_PositionVector + vOffset*GetRotationMatrix();
|
|
FLOATmatrix3D mDirection;
|
|
MakeRotationMatrixFast(mDirection, aOffset);
|
|
DecomposeRotationMatrixNoSnap(plProjectile.pl_OrientationAngle, mToTarget*mDirection);
|
|
};
|
|
|
|
// prepare free flying projectile
|
|
void PrepareFreeFlyingProjectile(CPlacement3D &plProjectile, const FLOAT3D vShootTarget,
|
|
const FLOAT3D &vOffset, const ANGLE3D &aOffset)
|
|
{
|
|
FLOAT3D vDiff = (vShootTarget - (GetPlacement().pl_PositionVector + vOffset*GetRotationMatrix())).SafeNormalize();
|
|
|
|
// find orientation towards target
|
|
FLOAT3D mToTargetX, mToTargetY, mToTargetZ;
|
|
mToTargetZ = -vDiff;
|
|
mToTargetY = -en_vGravityDir;
|
|
mToTargetX = (mToTargetY*mToTargetZ).SafeNormalize();
|
|
mToTargetZ = (mToTargetX*mToTargetY).SafeNormalize();
|
|
FLOATmatrix3D mToTarget;
|
|
mToTarget(1,1) = mToTargetX(1); mToTarget(1,2) = mToTargetY(1); mToTarget(1,3) = mToTargetZ(1);
|
|
mToTarget(2,1) = mToTargetX(2); mToTarget(2,2) = mToTargetY(2); mToTarget(2,3) = mToTargetZ(2);
|
|
mToTarget(3,1) = mToTargetX(3); mToTarget(3,2) = mToTargetY(3); mToTarget(3,3) = mToTargetZ(3);
|
|
|
|
// calculate placement of projectile to be at given offset
|
|
plProjectile.pl_PositionVector = GetPlacement().pl_PositionVector + vOffset*GetRotationMatrix();
|
|
FLOATmatrix3D mDirection;
|
|
MakeRotationMatrixFast(mDirection, aOffset);
|
|
DecomposeRotationMatrixNoSnap(plProjectile.pl_OrientationAngle, mToTarget*mDirection);
|
|
};
|
|
|
|
// shoot projectile on enemy
|
|
CEntity *ShootProjectile(enum ProjectileType pt, const FLOAT3D &vOffset, const ANGLE3D &aOffset) {
|
|
ASSERT(m_penEnemy != NULL);
|
|
|
|
// target enemy body
|
|
EntityInfo *peiTarget = (EntityInfo*) (m_penEnemy->GetEntityInfo());
|
|
FLOAT3D vShootTarget;
|
|
GetEntityInfoPosition(m_penEnemy, peiTarget->vTargetCenter, vShootTarget);
|
|
|
|
// launch
|
|
CPlacement3D pl;
|
|
PreparePropelledProjectile(pl, vShootTarget, vOffset, aOffset);
|
|
CEntityPointer penProjectile = CreateEntity(pl, CLASS_PROJECTILE);
|
|
ELaunchProjectile eLaunch;
|
|
eLaunch.penLauncher = this;
|
|
eLaunch.fStretch=1.0f;
|
|
eLaunch.prtType = pt;
|
|
penProjectile->Initialize(eLaunch);
|
|
|
|
return penProjectile;
|
|
};
|
|
|
|
// shoot projectile at an exact spot
|
|
CEntity *ShootProjectileAt(FLOAT3D vShootTarget, enum ProjectileType pt, const FLOAT3D &vOffset, const ANGLE3D &aOffset) {
|
|
|
|
// launch
|
|
CPlacement3D pl;
|
|
PreparePropelledProjectile(pl, vShootTarget, vOffset, aOffset);
|
|
CEntityPointer penProjectile = CreateEntity(pl, CLASS_PROJECTILE);
|
|
ELaunchProjectile eLaunch;
|
|
eLaunch.penLauncher = this;
|
|
eLaunch.prtType = pt;
|
|
penProjectile->Initialize(eLaunch);
|
|
|
|
return penProjectile;
|
|
};
|
|
|
|
// shoot projectile on enemy
|
|
CEntity *ShootPredictedProjectile(enum ProjectileType pt, const FLOAT3D vPredictedPos, const FLOAT3D &vOffset, const ANGLE3D &aOffset) {
|
|
ASSERT(m_penEnemy != NULL);
|
|
|
|
// target enemy body (predicted)
|
|
EntityInfo *peiTarget = (EntityInfo*) (m_penEnemy->GetEntityInfo());
|
|
FLOAT3D vShootTarget = vPredictedPos;
|
|
if (peiTarget != NULL)
|
|
{
|
|
// get body center vector
|
|
FLOAT3D vBody = FLOAT3D(peiTarget->vTargetCenter[0],peiTarget->vTargetCenter[1],peiTarget->vTargetCenter[2]);
|
|
FLOATmatrix3D mRotation;
|
|
MakeRotationMatrixFast(mRotation, m_penEnemy->GetPlacement().pl_OrientationAngle);
|
|
vShootTarget = vPredictedPos + vBody*mRotation;
|
|
}
|
|
// launch
|
|
CPlacement3D pl;
|
|
PreparePropelledProjectile(pl, vShootTarget, vOffset, aOffset);
|
|
CEntityPointer penProjectile = CreateEntity(pl, CLASS_PROJECTILE);
|
|
ELaunchProjectile eLaunch;
|
|
eLaunch.penLauncher = this;
|
|
eLaunch.prtType = pt;
|
|
penProjectile->Initialize(eLaunch);
|
|
|
|
return penProjectile;
|
|
};
|
|
|
|
BOOL WouldNotLeaveAttackRadius(void)
|
|
{
|
|
if (m_fAttackRadius<=0) {
|
|
return FALSE;
|
|
}
|
|
// test if we are inside radius
|
|
BOOL bInsideNow = (m_vStartPosition-GetPlacement().pl_PositionVector).Length() < m_fAttackRadius;
|
|
// test if going towards enemy leads us to center of attack radius circle
|
|
BOOL bEnemyTowardsCenter =
|
|
(m_vStartPosition-m_penEnemy->GetPlacement().pl_PositionVector).Length() <
|
|
(GetPlacement().pl_PositionVector-m_penEnemy->GetPlacement().pl_PositionVector).Length();
|
|
return bInsideNow || bEnemyTowardsCenter;
|
|
}
|
|
|
|
// check whether may move while attacking
|
|
virtual BOOL MayMoveToAttack(void)
|
|
{
|
|
// check if enemy is diving
|
|
CMovableEntity *pen = (CMovableEntity *)&*m_penEnemy;
|
|
CContentType &ctUp = pen->en_pwoWorld->wo_actContentTypes[pen->en_iUpContent];
|
|
BOOL bEnemyDiving = !(ctUp.ct_ulFlags&CTF_BREATHABLE_LUNGS);
|
|
// may move if can go to enemy without leaving attack radius, and entity is not diving
|
|
return WouldNotLeaveAttackRadius() && !bEnemyDiving;
|
|
};
|
|
|
|
|
|
/************************************************************
|
|
* BLOW UP FUNCTIONS *
|
|
************************************************************/
|
|
// should this enemy blow up (spawn debris)
|
|
virtual BOOL ShouldBlowUp(void)
|
|
{
|
|
// exotech larva boss allways blows up
|
|
if (IsOfClass(this, "ExotechLarva")) { return TRUE; }
|
|
|
|
// blow up if
|
|
return
|
|
// allowed
|
|
GetSP()->sp_bGibs &&
|
|
// dead and
|
|
GetHealth()<=0 &&
|
|
// has received large enough damage lately and
|
|
m_vDamage.Length() > m_fBlowUpAmount &&
|
|
// not already disappearing
|
|
m_fSpiritStartTime==0;
|
|
}
|
|
|
|
|
|
// base function for blowing up
|
|
void BlowUpBase(void)
|
|
{
|
|
// call derived function
|
|
BlowUp();
|
|
}
|
|
|
|
|
|
// spawn body parts
|
|
virtual void BlowUp(void)
|
|
{
|
|
// blow up notify
|
|
BlowUpNotify();
|
|
const BOOL bGibs = GetSP()->sp_bGibs;
|
|
|
|
FLOAT3D vNormalizedDamage = m_vDamage-m_vDamage*(m_fBlowUpAmount/m_vDamage.Length());
|
|
vNormalizedDamage /= Sqrt(vNormalizedDamage.Length());
|
|
vNormalizedDamage *= 0.75f;
|
|
FLOAT3D vBodySpeed = en_vCurrentTranslationAbsolute-en_vGravityDir*(en_vGravityDir%en_vCurrentTranslationAbsolute);
|
|
|
|
// if allowed and fleshy
|
|
if( bGibs && !m_bRobotBlowup)
|
|
{
|
|
// readout blood type
|
|
const INDEX iBloodType = GetSP()->sp_iBlood;
|
|
// determine debris texture (color)
|
|
ULONG ulFleshTexture = TEXTURE_FLESH_GREEN;
|
|
ULONG ulFleshModel = MODEL_FLESH;
|
|
if( iBloodType==2) { ulFleshTexture = TEXTURE_FLESH_RED; }
|
|
// spawn debris
|
|
Debris_Begin(EIBT_FLESH, DPT_BLOODTRAIL, BET_BLOODSTAIN, m_fBlowUpSize, vNormalizedDamage, vBodySpeed, 1.0f, 0.0f);
|
|
for( INDEX iDebris = 0; iDebris<m_fBodyParts; iDebris++) {
|
|
// flowerpower mode?
|
|
if( iBloodType==3) {
|
|
switch( IRnd()%5) {
|
|
case 1: { ulFleshModel = MODEL_FLESH_APPLE; ulFleshTexture = TEXTURE_FLESH_APPLE; break; }
|
|
case 2: { ulFleshModel = MODEL_FLESH_BANANA; ulFleshTexture = TEXTURE_FLESH_BANANA; break; }
|
|
case 3: { ulFleshModel = MODEL_FLESH_BURGER; ulFleshTexture = TEXTURE_FLESH_BURGER; break; }
|
|
case 4: { ulFleshModel = MODEL_FLESH_LOLLY; ulFleshTexture = TEXTURE_FLESH_LOLLY; break; }
|
|
default: { ulFleshModel = MODEL_FLESH_ORANGE; ulFleshTexture = TEXTURE_FLESH_ORANGE; break; }
|
|
}
|
|
}
|
|
Debris_Spawn( this, this, ulFleshModel, ulFleshTexture, 0, 0, 0, IRnd()%4, 0.5f,
|
|
FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
|
|
}
|
|
// leave a stain beneath
|
|
LeaveStain(FALSE);
|
|
}
|
|
|
|
// if allowed and robot/machine
|
|
if( bGibs && m_bRobotBlowup)
|
|
{
|
|
// spawn debris
|
|
Debris_Begin(EIBT_ROBOT, DPR_SMOKETRAIL, BET_EXPLOSIONSTAIN, m_fBlowUpSize, vNormalizedDamage, vBodySpeed, 1.0f, 0.0f);
|
|
for( INDEX iDebris = 0; iDebris<m_fBodyParts; iDebris++) {
|
|
Debris_Spawn( this, this, MODEL_MACHINE, TEXTURE_MACHINE, 0, 0, 0, IRnd()%4, 0.2f,
|
|
FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
|
|
}
|
|
// spawn explosion
|
|
CPlacement3D plExplosion = GetPlacement();
|
|
CEntityPointer penExplosion = CreateEntity(plExplosion, CLASS_BASIC_EFFECT);
|
|
ESpawnEffect eSpawnEffect;
|
|
eSpawnEffect.colMuliplier = C_WHITE|CT_OPAQUE;
|
|
eSpawnEffect.betType = BET_BOMB;
|
|
FLOAT fSize = m_fBlowUpSize*0.3f;
|
|
eSpawnEffect.vStretch = FLOAT3D(fSize,fSize,fSize);
|
|
penExplosion->Initialize(eSpawnEffect);
|
|
}
|
|
|
|
// hide yourself (must do this after spawning debris)
|
|
SwitchToEditorModel();
|
|
SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
|
|
SetCollisionFlags(ECF_IMMATERIAL);
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* CLASS SUPPORT FUNCTIONS *
|
|
************************************************************/
|
|
|
|
// leave stain
|
|
virtual void LeaveStain( BOOL bGrow)
|
|
{
|
|
ESpawnEffect ese;
|
|
FLOAT3D vPoint;
|
|
FLOATplane3D vPlaneNormal;
|
|
FLOAT fDistanceToEdge;
|
|
// get your size
|
|
FLOATaabbox3D box;
|
|
GetBoundingBox(box);
|
|
|
|
// on plane
|
|
if( GetNearestPolygon(vPoint, vPlaneNormal, fDistanceToEdge)) {
|
|
// if near to polygon and away from last stain point
|
|
if( (vPoint-GetPlacement().pl_PositionVector).Length()<0.5f
|
|
&& (m_vLastStain-vPoint).Length()>1.0f ) {
|
|
m_vLastStain = vPoint;
|
|
FLOAT fStretch = box.Size().Length();
|
|
ese.colMuliplier = C_WHITE|CT_OPAQUE;
|
|
// stain
|
|
if (bGrow) {
|
|
ese.betType = BET_BLOODSTAINGROW;
|
|
ese.vStretch = FLOAT3D( fStretch*1.5f, fStretch*1.5f, 1.0f);
|
|
} else {
|
|
ese.betType = BET_BLOODSTAIN;
|
|
ese.vStretch = FLOAT3D( fStretch*0.75f, fStretch*0.75f, 1.0f);
|
|
}
|
|
ese.vNormal = FLOAT3D( vPlaneNormal);
|
|
ese.vDirection = FLOAT3D( 0, 0, 0);
|
|
FLOAT3D vPos = vPoint+ese.vNormal/50.0f*(FRnd()+0.5f);
|
|
CEntityPointer penEffect = CreateEntity( CPlacement3D(vPos, ANGLE3D(0,0,0)), CLASS_BASIC_EFFECT);
|
|
penEffect->Initialize(ese);
|
|
}
|
|
}
|
|
};
|
|
|
|
virtual void AdjustDifficulty(void)
|
|
{
|
|
FLOAT fMoveSpeed = GetSP()->sp_fEnemyMovementSpeed;
|
|
FLOAT fAttackSpeed = GetSP()->sp_fEnemyMovementSpeed;
|
|
// m_fWalkSpeed *= fMoveSpeed;
|
|
// m_aWalkRotateSpeed *= fMoveSpeed;
|
|
m_fAttackRunSpeed *= fMoveSpeed;
|
|
m_aAttackRotateSpeed *= fMoveSpeed;
|
|
m_fCloseRunSpeed *= fMoveSpeed;
|
|
m_aCloseRotateSpeed *= fMoveSpeed;
|
|
m_fAttackFireTime *= 1/fAttackSpeed;
|
|
m_fCloseFireTime *= 1/fAttackSpeed;
|
|
/*
|
|
CSessionProperties::GameDificulty gd = GetSP()->sp_gdGameDificulty;
|
|
|
|
switch(gd) {
|
|
case CSessionProperties::GD_EASY: {
|
|
} break;
|
|
case CSessionProperties::GD_NORMAL: {
|
|
} break;
|
|
case CSessionProperties::GD_HARD: {
|
|
} break;
|
|
case CSessionProperties::GD_SERIOUS: {
|
|
} break;
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* SOUND VIRTUAL FUNCTIONS *
|
|
************************************************************/
|
|
|
|
// wounded -> yell
|
|
void WoundedNotify(const EDamage &eDamage)
|
|
{
|
|
// if no enemy
|
|
if (m_penEnemy==NULL) {
|
|
// do nothing
|
|
return;
|
|
}
|
|
|
|
// if not killed from short distance
|
|
if (eDamage.dmtType!=DMT_CLOSERANGE && eDamage.dmtType!=DMT_CHAINSAW) {
|
|
// yell
|
|
ESound eSound;
|
|
eSound.EsndtSound = SNDT_YELL;
|
|
eSound.penTarget = m_penEnemy;
|
|
SendEventInRange(eSound, FLOATaabbox3D(GetPlacement().pl_PositionVector, 25.0f));
|
|
}
|
|
};
|
|
|
|
// see enemy -> shout
|
|
void SeeNotify()
|
|
{
|
|
// if no enemy
|
|
if (m_penEnemy==NULL) {
|
|
// do nothing
|
|
return;
|
|
}
|
|
// yell
|
|
ESound eSound;
|
|
eSound.EsndtSound = SNDT_SHOUT;
|
|
eSound.penTarget = m_penEnemy;
|
|
SendEventInRange(eSound, FLOATaabbox3D(GetPlacement().pl_PositionVector, 50.0f));
|
|
};
|
|
|
|
|
|
|
|
/************************************************************
|
|
* VIRTUAL FUNCTIONS THAT NEED OVERRIDE *
|
|
************************************************************/
|
|
virtual void StandingAnim(void) {};
|
|
virtual void StandingAnimFight(void) { StandingAnim(); };
|
|
virtual void WalkingAnim(void) {};
|
|
virtual void RunningAnim(void) {};
|
|
virtual void RotatingAnim(void) {};
|
|
virtual void ChargeAnim(void) {};
|
|
virtual INDEX AnimForDamage(FLOAT fDamage) { return 0; };
|
|
virtual void BlowUpNotify(void) {};
|
|
virtual INDEX AnimForDeath(void) { return 0; };
|
|
virtual FLOAT WaitForDust(FLOAT3D &vStretch) { return -1; };
|
|
virtual void DeathNotify(void) {};
|
|
virtual void IdleSound(void) {};
|
|
virtual void SightSound(void) {};
|
|
virtual void WoundSound(void) {};
|
|
virtual void DeathSound(void) {};
|
|
virtual FLOAT GetLockRotationSpeed(void) { return 2000.0f;};
|
|
|
|
|
|
// render particles
|
|
void RenderParticles(void) {
|
|
// no particles when not existing
|
|
if (GetRenderType()!=CEntity::RT_MODEL && GetRenderType()!=CEntity::RT_SKAMODEL) {
|
|
return;
|
|
}
|
|
// if is dead
|
|
if( m_fSpiritStartTime != 0.0f)
|
|
{
|
|
// const FLOAT tmNow = _pTimer->CurrentTick();
|
|
// Particles_ModelGlow(this, tmNow + 20,PT_STAR08, 0.15f, 2, 0.03f, 0xff00ff00);
|
|
Particles_Death(this, m_fSpiritStartTime);
|
|
}
|
|
}
|
|
|
|
// adjust sound and watcher parameters here if needed
|
|
virtual void EnemyPostInit(void) {};
|
|
|
|
/* Handle an event, return false if the event is not handled. */
|
|
BOOL HandleEvent(const CEntityEvent &ee)
|
|
{
|
|
if (ee.ee_slEvent==EVENTCODE_ETouch)
|
|
{
|
|
if( GetCrushHealth() != 0.0f)
|
|
{
|
|
ETouch eTouch = ((ETouch &) ee);
|
|
if (IsOfClass(eTouch.penOther, "ModelHolder2") ||
|
|
IsOfClass(eTouch.penOther, "MovingBrush") ||
|
|
IsOfClass(eTouch.penOther, "DestroyableArchitecture") )
|
|
{
|
|
InflictDirectDamage(eTouch.penOther, this, DMT_EXPLOSION, GetCrushHealth(),
|
|
eTouch.penOther->GetPlacement().pl_PositionVector, -(FLOAT3D&)eTouch.plCollision);
|
|
}
|
|
}
|
|
}
|
|
return CMovableModelEntity::HandleEvent(ee);
|
|
}
|
|
|
|
// returns length of animation
|
|
FLOAT GetAnimLength(int iAnim)
|
|
{
|
|
if(en_RenderType==RT_SKAMODEL) {
|
|
return GetModelInstance()->GetAnimLength(iAnim);
|
|
} else {
|
|
return GetModelObject()->GetAnimLength(iAnim);
|
|
}
|
|
}
|
|
|
|
// returns lenght of current anim length
|
|
FLOAT GetCurrentAnimLength()
|
|
{
|
|
if(en_RenderType==RT_SKAMODEL) {
|
|
return 0.5f;
|
|
} else {
|
|
return GetModelObject()->GetCurrentAnimLength();
|
|
}
|
|
}
|
|
|
|
// is animation finished
|
|
BOOL IsAnimFinished()
|
|
{
|
|
if(en_RenderType==RT_SKAMODEL) {
|
|
return TRUE;
|
|
} else {
|
|
return GetModelObject()->IsAnimFinished();
|
|
}
|
|
}
|
|
|
|
//
|
|
FLOAT GetPassedTime()
|
|
{
|
|
if(en_RenderType==RT_SKAMODEL) {
|
|
return 0.0f;
|
|
} else {
|
|
return GetModelObject()->GetPassedTime();
|
|
}
|
|
}
|
|
|
|
FLOAT3D &GetModelStretch()
|
|
{
|
|
if(en_RenderType==RT_SKAMODEL) {
|
|
return GetModelInstance()->mi_vStretch;
|
|
} else {
|
|
return GetModelObject()->mo_Stretch;
|
|
}
|
|
}
|
|
|
|
// Stretch model
|
|
void StretchModel(FLOAT3D vStretch)
|
|
{
|
|
if(en_RenderType==RT_SKAMODEL) {
|
|
GetModelInstance()->StretchModel( vStretch);
|
|
} else {
|
|
GetModelObject()->StretchModel( vStretch);
|
|
}
|
|
}
|
|
|
|
// Stretch single model
|
|
void StretchSingleModel( FLOAT3D vStretch)
|
|
{
|
|
if(en_RenderType==RT_SKAMODEL) {
|
|
GetModelInstance()->StretchSingleModel( vStretch);
|
|
} else {
|
|
GetModelObject()->StretchSingleModel( vStretch);
|
|
}
|
|
}
|
|
|
|
|
|
// returns bytes of memory used by this object
|
|
SLONG GetUsedMemory(void)
|
|
{
|
|
// initial
|
|
SLONG slUsedMemory = sizeof(CEnemyBase) - sizeof(CMovableModelEntity) + CMovableModelEntity::GetUsedMemory();
|
|
// add some more
|
|
slUsedMemory += m_strDescription.Length();
|
|
slUsedMemory += m_strName.Length();
|
|
slUsedMemory += 1* sizeof(CSoundObject);
|
|
return slUsedMemory;
|
|
}
|
|
|
|
|
|
|
|
procedures:
|
|
|
|
//**********************************************************
|
|
// MOVEMENT PROCEDURES
|
|
//**********************************************************
|
|
|
|
// move to given destination position
|
|
MoveToDestination(EVoid)
|
|
{
|
|
// setup parameters
|
|
m_fMoveFrequency = 0.25f;
|
|
m_fMoveTime = _pTimer->CurrentTick() + 45.0f;
|
|
// while not there and time not expired
|
|
while (CalcDistanceInPlaneToDestination()>m_fMoveSpeed*m_fMoveFrequency*2.0f &&
|
|
m_fMoveTime>_pTimer->CurrentTick()) {
|
|
// every once in a while
|
|
wait (m_fMoveFrequency) {
|
|
on (EBegin) : {
|
|
// adjust direction and speed
|
|
ULONG ulFlags = SetDesiredMovement();
|
|
MovementAnimation(ulFlags);
|
|
resume;
|
|
}
|
|
on (ETimer) : { stop; }
|
|
}
|
|
}
|
|
|
|
// return to the caller
|
|
return EReturn();
|
|
};
|
|
|
|
// go to next randomly choosen position inside patrol range
|
|
MoveToRandomPatrolPosition(EVoid)
|
|
{
|
|
// if the marker is invalid
|
|
if (!IsOfClass(m_penMarker, "Enemy Marker")) {
|
|
// this should not happen
|
|
ASSERT(FALSE);
|
|
// return to caller
|
|
return EReturn();
|
|
}
|
|
// get the marker
|
|
CEnemyMarker *pem = (CEnemyMarker *)&*m_penMarker;
|
|
|
|
// get random destination position
|
|
FLOAT fMin = pem->m_fPatrolAreaInner;
|
|
FLOAT fMax = pem->m_fPatrolAreaOuter;
|
|
if (fMin<0) {
|
|
fMin = 0;
|
|
}
|
|
if (fMax<fMin) {
|
|
fMax = fMin;
|
|
}
|
|
FLOAT fR = Lerp(fMin, fMax, FRnd());
|
|
FLOAT fA = FRnd()*360.0f;
|
|
FLOAT3D vOffsetDir;
|
|
GetHeadingDirection(fA, vOffsetDir);
|
|
m_vDesiredPosition = m_vStartPosition+vOffsetDir*fR;
|
|
|
|
// use walking to get there
|
|
m_fMoveSpeed = GetProp(m_fWalkSpeed);
|
|
m_aRotateSpeed = GetProp(m_aWalkRotateSpeed);
|
|
WalkingAnim();
|
|
|
|
// go
|
|
autocall MoveToDestination() EReturn;
|
|
|
|
// return to caller
|
|
return EReturn();
|
|
};
|
|
|
|
// patrol around start position
|
|
DoPatrolling()
|
|
{
|
|
// repeat forever
|
|
while(TRUE) {
|
|
// stop where you are
|
|
StopMoving();
|
|
StandingAnim();
|
|
// wait a bit
|
|
autowait(0.5f + FRnd()/2);
|
|
// patrol to a random position
|
|
autocall MoveToRandomPatrolPosition() EReturn;
|
|
}
|
|
}
|
|
|
|
// just wait until something happens
|
|
BeIdle(EVoid)
|
|
{
|
|
// start watching
|
|
GetWatcher()->SendEvent(EStart());
|
|
|
|
// stand in place
|
|
StandingAnim();
|
|
|
|
// repeat forever
|
|
while(TRUE) {
|
|
// wait some time
|
|
autowait(Lerp(5.0f, 20.0f, FRnd()));
|
|
// play idle sound
|
|
IdleSound();
|
|
}
|
|
}
|
|
|
|
// return to start position
|
|
ReturnToStartPosition(EVoid)
|
|
{
|
|
jump BeIdle();
|
|
/*
|
|
// start watching
|
|
GetWatcher()->SendEvent(EStart());
|
|
|
|
m_vDesiredPosition = m_vStartPosition;
|
|
m_vStartDirection = (GetPlacement().pl_PositionVector-m_vStartPosition).SafeNormalize();
|
|
m_fMoveSpeed = GetProp(m_fAttackRunSpeed);
|
|
m_aRotateSpeed = GetProp(m_aAttackRotateSpeed);
|
|
RunningAnim();
|
|
autocall MoveToDestination() EReturn;
|
|
|
|
WalkingAnim();
|
|
m_vDesiredAngle = m_vStartDirection;
|
|
StopTranslating();
|
|
|
|
autocall RotateToStartDirection() EReturn;
|
|
|
|
StopMoving();
|
|
StandingAnim();
|
|
|
|
jump BeIdle();
|
|
*/
|
|
};
|
|
/*
|
|
// rotate to destination
|
|
RotateToStartDirection(EVoid)
|
|
{
|
|
|
|
m_fMoveFrequency = 0.1f;
|
|
m_fMoveTime = _pTimer->CurrentTick() + 45.0f;
|
|
while (Abs(GetRelativeHeading(GetDesiredPositionDir()))>GetProp(m_aRotateSpeed)*m_fMoveFrequency*1.5f &&
|
|
m_fMoveTime>_pTimer->CurrentTick()) {
|
|
autowait(m_fMoveFrequency);
|
|
}
|
|
|
|
return EReturn();
|
|
};
|
|
*/
|
|
|
|
|
|
// move through markers
|
|
MoveThroughMarkers()
|
|
{
|
|
// start watching
|
|
GetWatcher()->SendEvent(EStart());
|
|
|
|
// while there is a valid marker, take values from it
|
|
while (m_penMarker!=NULL && IsOfClass(m_penMarker, "Enemy Marker")) {
|
|
CEnemyMarker *pem = (CEnemyMarker *)&*m_penMarker;
|
|
|
|
// the marker position is our new start position for attack range
|
|
m_vStartPosition = m_penMarker->GetPlacement().pl_PositionVector;
|
|
// make a random position to walk to at the marker
|
|
FLOAT fR = FRnd()*pem->m_fMarkerRange;
|
|
FLOAT fA = FRnd()*360.0f;
|
|
m_vDesiredPosition = m_vStartPosition+FLOAT3D(CosFast(fA)*fR, 0, SinFast(fA)*fR);
|
|
// if running
|
|
if (pem->m_betRunToMarker==BET_TRUE) {
|
|
// use attack speeds
|
|
m_fMoveSpeed = GetProp(m_fAttackRunSpeed);
|
|
m_aRotateSpeed = GetProp(m_aAttackRotateSpeed);
|
|
// start running anim
|
|
RunningAnim();
|
|
// if not running
|
|
} else {
|
|
// use walk speeds
|
|
m_fMoveSpeed = GetProp(m_fWalkSpeed);
|
|
m_aRotateSpeed = GetProp(m_aWalkRotateSpeed);
|
|
// start walking anim
|
|
WalkingAnim();
|
|
}
|
|
|
|
// move to the new destination position
|
|
autocall MoveToDestination() EReturn;
|
|
|
|
// read new blind/deaf values
|
|
CEnemyMarker *pem = (CEnemyMarker *)&*m_penMarker;
|
|
SetBoolFromBoolEType(m_bBlind, pem->m_betBlind);
|
|
SetBoolFromBoolEType(m_bDeaf, pem->m_betDeaf);
|
|
|
|
// if should start tactics
|
|
if (pem->m_bStartTactics){
|
|
// start to see/hear
|
|
m_bBlind = FALSE;
|
|
m_bDeaf = FALSE;
|
|
// unconditional tactics start
|
|
StartTacticsNow();
|
|
}
|
|
|
|
// if should patrol there
|
|
if (pem->m_fPatrolTime>0.0f) {
|
|
// spawn a reminder to notify us when the time is up
|
|
SpawnReminder(this, pem->m_fPatrolTime, 0);
|
|
// wait
|
|
wait() {
|
|
// initially
|
|
on (EBegin) : {
|
|
// start patroling
|
|
call DoPatrolling();
|
|
}
|
|
// if time is up
|
|
on (EReminder) : {
|
|
// stop patroling
|
|
stop;
|
|
}
|
|
}
|
|
}
|
|
|
|
CEnemyMarker *pem = (CEnemyMarker *)&*m_penMarker;
|
|
// if should wait on the marker
|
|
if (pem->m_fWaitTime > 0.0f) {
|
|
// stop there
|
|
StopMoving();
|
|
StandingAnim();
|
|
// wait
|
|
autowait(pem->m_fWaitTime);
|
|
}
|
|
|
|
// wait a bit always (to prevent eventual busy-looping)
|
|
autowait(0.05f);
|
|
|
|
// take next marker in loop
|
|
m_penMarker = ((CEnemyMarker&)*m_penMarker).m_penTarget;
|
|
} // when no more markers
|
|
|
|
// stop where you are
|
|
StopMoving();
|
|
StandingAnim();
|
|
|
|
// return to called
|
|
return EReturn();
|
|
};
|
|
|
|
|
|
//**********************************************************
|
|
// ATTACK PROCEDURES
|
|
//**********************************************************
|
|
|
|
// sequence that is activated when a new player is spotted visually or heard
|
|
NewEnemySpotted()
|
|
{
|
|
// calculate reflex time
|
|
FLOAT tmReflex = Lerp(m_tmReflexMin, m_tmReflexMax, FRnd());
|
|
tmReflex = ClampDn(tmReflex, 0.0f);
|
|
|
|
// if should wait
|
|
if (tmReflex>=_pTimer->TickQuantum) {
|
|
// stop where you are
|
|
StopMoving();
|
|
StandingAnim();
|
|
|
|
// wait the reflex time
|
|
wait(tmReflex) {
|
|
on (ETimer) : { stop; }
|
|
// pass all damage events
|
|
on (EDamage) : { pass; }
|
|
// pass space beam hit
|
|
on (EHitBySpaceShipBeam) : { pass;}
|
|
// ignore all other events
|
|
otherwise () : { resume; }
|
|
}
|
|
}
|
|
|
|
// play sight sound
|
|
SightSound();
|
|
|
|
// return to caller
|
|
return EReturn();
|
|
}
|
|
|
|
// stop attack
|
|
StopAttack(EVoid) {
|
|
// start watching
|
|
GetWatcher()->SendEvent(EStart());
|
|
// no damager
|
|
SetTargetNone();
|
|
m_fDamageConfused = 0.0f;
|
|
// stop moving
|
|
StopMoving();
|
|
|
|
return EReturn();
|
|
};
|
|
|
|
// initial preparation
|
|
InitializeAttack(EVoid)
|
|
{
|
|
// disable blind/deaf mode
|
|
m_bBlind = FALSE;
|
|
m_bDeaf = FALSE;
|
|
|
|
SeeNotify(); // notify seeing
|
|
GetWatcher()->SendEvent(EStop()); // stop looking
|
|
// make fuss
|
|
AddToFuss();
|
|
// remember spotted position
|
|
m_vPlayerSpotted = PlayerDestinationPos();
|
|
|
|
// set timers
|
|
if (CalcDist(m_penEnemy)<GetProp(m_fCloseDistance)) {
|
|
m_fShootTime = 0.0f;
|
|
} else {
|
|
m_fShootTime = _pTimer->CurrentTick() + FRnd();
|
|
}
|
|
m_fDamageConfused = m_fDamageWounded;
|
|
|
|
return EReturn();
|
|
};
|
|
|
|
|
|
// attack enemy
|
|
AttackEnemy(EVoid) {
|
|
// initial preparation
|
|
autocall InitializeAttack() EReturn;
|
|
|
|
// while you have some enemy
|
|
while (m_penEnemy != NULL) {
|
|
// attack it
|
|
autocall PerformAttack() EReturn;
|
|
}
|
|
|
|
// stop attack
|
|
autocall StopAttack() EReturn;
|
|
|
|
// return to Active() procedure
|
|
return EBegin();
|
|
};
|
|
|
|
// repeat attacking enemy until enemy is dead or you give up
|
|
PerformAttack()
|
|
{
|
|
// reset last range
|
|
m_fRangeLast = 1E9f;
|
|
|
|
// set player's position as destination position
|
|
m_vDesiredPosition = PlayerDestinationPos();
|
|
m_dtDestination = DT_PLAYERCURRENT;
|
|
|
|
// repeat
|
|
while(TRUE)
|
|
{
|
|
// if attacking is futile
|
|
if (ShouldCeaseAttack()) {
|
|
// cease it
|
|
SetTargetNone();
|
|
return EReturn();
|
|
}
|
|
|
|
// get distance from enemy
|
|
FLOAT fEnemyDistance = CalcDist(m_penEnemy);
|
|
// if just entered close range
|
|
if (m_fRangeLast>GetProp(m_fCloseDistance) && fEnemyDistance<=GetProp(m_fCloseDistance)) {
|
|
// reset shoot time to make it attack immediately
|
|
m_fShootTime = 0.0f;
|
|
}
|
|
m_fRangeLast = fEnemyDistance;
|
|
|
|
// determine movement frequency depending on enemy distance or path finding
|
|
m_fMoveFrequency = GetAttackMoveFrequency(fEnemyDistance);
|
|
if (m_dtDestination==DT_PATHPERSISTENT||m_dtDestination==DT_PATHTEMPORARY) {
|
|
m_fMoveFrequency = 0.1f;
|
|
}
|
|
|
|
// wait one time interval
|
|
wait(m_fMoveFrequency) {
|
|
on (ETimer) : { stop; }
|
|
// initially
|
|
on (EBegin) : {
|
|
|
|
// if you haven't fired/hit enemy for some time
|
|
if (_pTimer->CurrentTick() > m_fShootTime) {
|
|
|
|
// if you have new player visible closer than current and in threat distance
|
|
CEntity *penNewEnemy = GetWatcher()->CheckCloserPlayer(m_penEnemy, GetThreatDistance());
|
|
if (penNewEnemy!=NULL) {
|
|
// switch to that player
|
|
SetTargetHardForce(penNewEnemy);
|
|
// start new behavior
|
|
SendEvent(EReconsiderBehavior());
|
|
stop;
|
|
}
|
|
|
|
// if you can see player
|
|
if (IsVisible(m_penEnemy)) {
|
|
// remember spotted position
|
|
m_vPlayerSpotted = PlayerDestinationPos();
|
|
// if going to player spotted or temporary path position
|
|
if (m_dtDestination==DT_PLAYERSPOTTED||m_dtDestination==DT_PATHTEMPORARY) {
|
|
// switch to player current position
|
|
m_dtDestination=DT_PLAYERCURRENT;
|
|
}
|
|
|
|
// if you cannot see player
|
|
} else {
|
|
// if going to player's current position
|
|
if (m_dtDestination==DT_PLAYERCURRENT) {
|
|
// switch to position where player was last seen
|
|
m_dtDestination=DT_PLAYERSPOTTED;
|
|
}
|
|
}
|
|
|
|
// try firing/hitting again now
|
|
call FireOrHit();
|
|
|
|
// if between two fire/hit moments
|
|
} else {
|
|
// if going to player spotted or temporary path position and you just seen the player
|
|
if ((m_dtDestination==DT_PLAYERSPOTTED||m_dtDestination==DT_PATHTEMPORARY)
|
|
&& IsVisible(m_penEnemy)) {
|
|
// switch to player current position
|
|
m_dtDestination=DT_PLAYERCURRENT;
|
|
// remember spotted position
|
|
m_vPlayerSpotted = PlayerDestinationPos();
|
|
}
|
|
}
|
|
|
|
// if you are not following the player and you are near current destination position
|
|
FLOAT fAllowedError = m_fMoveSpeed*m_fMoveFrequency*2.0f;
|
|
if (m_dtDestination==DT_PATHPERSISTENT||m_dtDestination==DT_PATHTEMPORARY) {
|
|
fAllowedError = ((CNavigationMarker&)*m_penPathMarker).m_fMarkerRange;
|
|
}
|
|
if (m_dtDestination!=DT_PLAYERCURRENT &&
|
|
(CalcDistanceInPlaneToDestination()<fAllowedError || fAllowedError<0.1f)) {
|
|
// if going to where player was last spotted
|
|
if (m_dtDestination==DT_PLAYERSPOTTED) {
|
|
// if you see the player
|
|
if (IsVisible(m_penEnemy)) {
|
|
// switch to following player
|
|
m_dtDestination=DT_PLAYERCURRENT;
|
|
// if you don't see him
|
|
} else {
|
|
// switch to temporary path finding
|
|
m_dtDestination=DT_PATHTEMPORARY;
|
|
StartPathFinding();
|
|
}
|
|
// if using pathfinding
|
|
} else if (m_dtDestination==DT_PATHTEMPORARY||m_dtDestination==DT_PATHPERSISTENT) {
|
|
// find next path marker
|
|
FindNextPathMarker();
|
|
}
|
|
}
|
|
|
|
// if following player
|
|
if (m_dtDestination==DT_PLAYERCURRENT) {
|
|
// set player's position as destination position
|
|
m_vDesiredPosition = PlayerDestinationPos();
|
|
|
|
// if going to where player was last seen
|
|
} else if (m_dtDestination==DT_PLAYERSPOTTED) {
|
|
// use that as destination position
|
|
m_vDesiredPosition = m_vPlayerSpotted;
|
|
}
|
|
|
|
// set speeds for movement towards desired position
|
|
FLOAT3D vPosDelta = m_vDesiredPosition-GetPlacement().pl_PositionVector;
|
|
FLOAT fPosDistance = vPosDelta.Length();
|
|
|
|
SetSpeedsToDesiredPosition(vPosDelta, fPosDistance, m_dtDestination==DT_PLAYERCURRENT);
|
|
|
|
// adjust direction and speed
|
|
ULONG ulFlags = SetDesiredMovement();
|
|
MovementAnimation(ulFlags);
|
|
resume;
|
|
}
|
|
// if touched something
|
|
on (ETouch eTouch) : {
|
|
if( IfTargetCrushed(eTouch.penOther, (FLOAT3D&)eTouch.plCollision))
|
|
{
|
|
resume;
|
|
}
|
|
// if pathfinding must begin
|
|
else if (CheckTouchForPathFinding(eTouch)) {
|
|
// stop the loop
|
|
stop;
|
|
// if touch is ignored
|
|
} else if (m_bTacticActive) {
|
|
// reinitialize tactics?
|
|
if (eTouch.penOther->GetRenderType()==CEntity::RT_BRUSH) {
|
|
FLOAT3D vDir = en_vDesiredTranslationRelative;
|
|
vDir.SafeNormalize();
|
|
vDir*=GetRotationMatrix();
|
|
// if the touched plane is more or less orthogonal to the current velocity
|
|
if ((eTouch.plCollision%vDir)<-0.5f) { m_bTacticActive = 0; }
|
|
resume;
|
|
} else {
|
|
resume;
|
|
}
|
|
} else {
|
|
// pass the event
|
|
pass;
|
|
}
|
|
}
|
|
// if came to an edge
|
|
on (EWouldFall eWouldFall) : {
|
|
// if pathfinding must begin
|
|
if (CheckFallForPathFinding(eWouldFall)) {
|
|
// stop the loop
|
|
stop;
|
|
} else if (m_bTacticActive) {
|
|
// stop tactics
|
|
m_bTacticActive = 0;
|
|
resume;
|
|
// if edge is ignored
|
|
} else {
|
|
// pass the event
|
|
pass;
|
|
}
|
|
}
|
|
on (ESound) : { resume; } // ignore all sounds
|
|
on (EWatch) : { resume; } // ignore watch
|
|
on (EReturn) : { stop; } // returned from subprocedure
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// fire or hit the enemy if possible
|
|
FireOrHit()
|
|
{
|
|
// if player is in close range and in front
|
|
if (CalcDist(m_penEnemy)<GetProp(m_fCloseDistance) && CanHitEnemy(m_penEnemy, Cos(AngleDeg(45.0f)))) {
|
|
// make fuss
|
|
AddToFuss();
|
|
// stop moving (rotation and translation)
|
|
StopMoving();
|
|
// set next shoot time
|
|
m_fShootTime = _pTimer->CurrentTick() + GetProp(m_fCloseFireTime)*(1.0f + FRnd()/3.0f);
|
|
// hit
|
|
autocall Hit() EReturn;
|
|
|
|
// else if player is in fire range and in front
|
|
} else if (CalcDist(m_penEnemy)<GetProp(m_fAttackDistance) && CanAttackEnemy(m_penEnemy, Cos(AngleDeg(45.0f)))) {
|
|
// make fuss
|
|
AddToFuss();
|
|
// stop moving (rotation and translation)
|
|
StopMoving();
|
|
// set next shoot time
|
|
if (CalcDist(m_penEnemy)<GetProp(m_fCloseDistance)) {
|
|
m_fShootTime = _pTimer->CurrentTick() + GetProp(m_fCloseFireTime)*(1.0f + FRnd()/3.0f);
|
|
} else {
|
|
m_fShootTime = _pTimer->CurrentTick() + GetProp(m_fAttackFireTime)*(1.0f + FRnd()/3.0f);
|
|
}
|
|
// fire
|
|
autocall Fire() EReturn;
|
|
|
|
// if cannot fire nor hit
|
|
} else {
|
|
// make sure we don't retry again too soon
|
|
m_fShootTime = _pTimer->CurrentTick() + 0.25f;
|
|
}
|
|
|
|
// return to caller
|
|
return EReturn();
|
|
};
|
|
|
|
//**********************************************************
|
|
// COMBAT IMPLEMENTATION
|
|
//**********************************************************
|
|
|
|
// this is called to hit the player when near
|
|
Hit(EVoid)
|
|
{
|
|
return EReturn();
|
|
}
|
|
|
|
// this is called to shoot at player when far away or otherwise unreachable
|
|
Fire(EVoid)
|
|
{
|
|
return EReturn();
|
|
}
|
|
|
|
//**********************************************************
|
|
// COMBAT HELPERS
|
|
//**********************************************************
|
|
|
|
// call this to lock on player for some time - set m_fLockOnEnemyTime before calling
|
|
LockOnEnemy(EVoid)
|
|
{
|
|
// stop moving
|
|
StopMoving();
|
|
// play animation for locking
|
|
ChargeAnim();
|
|
// wait charge time
|
|
m_fLockStartTime = _pTimer->CurrentTick();
|
|
while (m_fLockStartTime+GetProp(m_fLockOnEnemyTime) > _pTimer->CurrentTick()) {
|
|
// each tick
|
|
m_fMoveFrequency = 0.05f;
|
|
wait (m_fMoveFrequency) {
|
|
on (ETimer) : { stop; }
|
|
on (EBegin) : {
|
|
m_vDesiredPosition = PlayerDestinationPos();
|
|
// if not heading towards enemy
|
|
if (!IsInPlaneFrustum(m_penEnemy, CosFast(5.0f))) {
|
|
m_fMoveSpeed = 0.0f;
|
|
m_aRotateSpeed = GetLockRotationSpeed();
|
|
// if heading towards enemy
|
|
} else {
|
|
m_fMoveSpeed = 0.0f;
|
|
m_aRotateSpeed = 0.0f;
|
|
}
|
|
// adjust direction and speed
|
|
ULONG ulFlags = SetDesiredMovement();
|
|
//MovementAnimation(ulFlags); don't do this, or they start to jive
|
|
resume;
|
|
}
|
|
}
|
|
}
|
|
// stop rotating
|
|
StopRotating();
|
|
|
|
// return to caller
|
|
return EReturn();
|
|
};
|
|
|
|
// call this to jump onto player - set charge properties before calling and spawn a reminder
|
|
ChargeHitEnemy(EVoid)
|
|
{
|
|
// wait for length of hit animation
|
|
wait(GetAnimLength(m_iChargeHitAnimation)) {
|
|
on (EBegin) : { resume; }
|
|
on (ETimer) : { stop; }
|
|
// ignore damages
|
|
on (EDamage) : { resume; }
|
|
// if user-set reminder expired
|
|
on (EReminder) : {
|
|
// stop moving
|
|
StopMoving();
|
|
resume;
|
|
}
|
|
// if you touch some entity
|
|
on (ETouch etouch) : {
|
|
// if it is alive and in front
|
|
if ((etouch.penOther->GetFlags()&ENF_ALIVE) && IsInPlaneFrustum(etouch.penOther, CosFast(60.0f))) {
|
|
// get your direction
|
|
FLOAT3D vSpeed;
|
|
GetHeadingDirection(m_fChargeHitAngle, vSpeed);
|
|
// damage entity in that direction
|
|
InflictDirectDamage(etouch.penOther, this, DMT_CLOSERANGE, m_fChargeHitDamage, FLOAT3D(0, 0, 0), vSpeed);
|
|
// push it away
|
|
vSpeed = vSpeed * m_fChargeHitSpeed;
|
|
KickEntity(etouch.penOther, vSpeed);
|
|
// stop waiting
|
|
stop;
|
|
}
|
|
pass;
|
|
}
|
|
}
|
|
// if the anim is not yet finished
|
|
if (!IsAnimFinished()) {
|
|
// wait the rest of time till the anim end
|
|
wait(GetCurrentAnimLength() - GetPassedTime()) {
|
|
on (EBegin) : { resume; }
|
|
on (ETimer) : { stop; }
|
|
// if timer expired
|
|
on (EReminder) : {
|
|
// stop moving
|
|
StopMoving();
|
|
resume;
|
|
}
|
|
}
|
|
}
|
|
|
|
// return to caller
|
|
return EReturn();
|
|
};
|
|
|
|
//**********************************************************
|
|
// WOUNDING AND DYING PROCEDURES
|
|
//**********************************************************
|
|
|
|
// play wounding animation
|
|
BeWounded(EDamage eDamage)
|
|
{
|
|
StopMoving();
|
|
// determine damage anim and play the wounding
|
|
autowait(GetAnimLength(AnimForDamage(eDamage.fAmount)));
|
|
return EReturn();
|
|
};
|
|
|
|
// we get here once the enemy is dead
|
|
Die(EDeath eDeath)
|
|
{
|
|
// not alive anymore
|
|
SetFlags(GetFlags()&~ENF_ALIVE);
|
|
|
|
// find the one who killed, or other best suitable player
|
|
CEntityPointer penKiller = eDeath.eLastDamage.penInflictor;
|
|
if (penKiller==NULL || !IsOfClass(penKiller, "Player")) {
|
|
penKiller = m_penEnemy;
|
|
}
|
|
|
|
if (penKiller==NULL || !IsOfClass(penKiller, "Player")) {
|
|
penKiller = FixupCausedToPlayer(this, penKiller, /*bWarning=*/FALSE);
|
|
}
|
|
|
|
// if killed by someone
|
|
if (penKiller!=NULL) {
|
|
// give him score
|
|
EReceiveScore eScore;
|
|
eScore.iPoints = (INDEX) m_iScore;
|
|
penKiller->SendEvent(eScore);
|
|
if( CountAsKill())
|
|
{
|
|
penKiller->SendEvent(EKilledEnemy());
|
|
}
|
|
// send computer message if in coop
|
|
if (GetSP()->sp_bCooperative) {
|
|
EComputerMessage eMsg;
|
|
eMsg.fnmMessage = GetComputerMessageName();
|
|
if (eMsg.fnmMessage!="") {
|
|
penKiller->SendEvent(eMsg);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// destroy watcher class
|
|
GetWatcher()->SendEvent(EStop());
|
|
GetWatcher()->SendEvent(EEnd());
|
|
|
|
// send event to death target
|
|
SendToTarget(m_penDeathTarget, m_eetDeathType, penKiller);
|
|
// send event to spawner if any
|
|
// NOTE: trigger's penCaused has been changed from penKiller to THIS;
|
|
if (m_penSpawnerTarget) {
|
|
SendToTarget(m_penSpawnerTarget, EET_TRIGGER, this);
|
|
}
|
|
|
|
|
|
// wait
|
|
wait() {
|
|
// initially
|
|
on (EBegin) : {
|
|
// if should already blow up
|
|
if (ShouldBlowUp()) {
|
|
// blow up now
|
|
BlowUpBase();
|
|
// stop waiting
|
|
stop;
|
|
// if shouldn't blow up yet
|
|
} else {
|
|
// invoke death animation sequence
|
|
call DeathSequence();
|
|
}
|
|
}
|
|
// if damaged
|
|
on (EDamage) : {
|
|
// if should already blow up
|
|
if (ShouldBlowUp()) {
|
|
// blow up now
|
|
BlowUpBase();
|
|
// stop waiting
|
|
stop;
|
|
}
|
|
// otherwise, ignore the damage
|
|
resume;
|
|
}
|
|
// if death sequence is over
|
|
on (EEnd) : {
|
|
// stop waiting
|
|
stop;
|
|
}
|
|
}
|
|
|
|
// stop making fuss
|
|
RemoveFromFuss();
|
|
// cease to exist
|
|
Destroy();
|
|
|
|
// all is over now, entity will be deleted
|
|
return;
|
|
};
|
|
|
|
Death(EVoid)
|
|
{
|
|
StopMoving(); // stop moving
|
|
DeathSound(); // death sound
|
|
LeaveStain(FALSE);
|
|
|
|
// set physic flags
|
|
SetPhysicsFlags(EPF_MODEL_CORPSE);
|
|
SetCollisionFlags(ECF_CORPSE);
|
|
SetFlags(GetFlags() | ENF_SEETHROUGH);
|
|
|
|
// stop making fuss
|
|
RemoveFromFuss();
|
|
|
|
// death notify (usually change collision box and change body density)
|
|
DeathNotify();
|
|
|
|
// start death anim
|
|
INDEX iAnim = AnimForDeath();
|
|
// use tactic variables for temporary data
|
|
m_vTacticsStartPosition=FLOAT3D(1,1,1);
|
|
m_fTacticVar4=WaitForDust(m_vTacticsStartPosition);
|
|
// remember start time
|
|
m_fTacticVar5=_pTimer->CurrentTick();
|
|
// mark that we didn't spawned dust yet
|
|
m_fTacticVar3=-1;
|
|
// if no dust should be spawned
|
|
if( m_fTacticVar4<0)
|
|
{
|
|
autowait(GetAnimLength(iAnim));
|
|
}
|
|
// should spawn dust
|
|
else if( TRUE)
|
|
{
|
|
while(_pTimer->CurrentTick()<m_fTacticVar5+GetCurrentAnimLength())
|
|
{
|
|
autowait(_pTimer->TickQuantum);
|
|
if(en_penReference!=NULL && _pTimer->CurrentTick()>=m_fTacticVar5+m_fTacticVar4 && m_fTacticVar3<0)
|
|
{
|
|
// spawn dust effect
|
|
CPlacement3D plFX=GetPlacement();
|
|
ESpawnEffect ese;
|
|
ese.colMuliplier = C_WHITE|CT_OPAQUE;
|
|
ese.vStretch = m_vTacticsStartPosition;
|
|
ese.vNormal = FLOAT3D(0,1,0);
|
|
ese.betType = BET_DUST_FALL;
|
|
CPlacement3D plSmoke=plFX;
|
|
plSmoke.pl_PositionVector+=FLOAT3D(0,0.35f*m_vTacticsStartPosition(2),0);
|
|
CEntityPointer penFX = CreateEntity(plSmoke, CLASS_BASIC_EFFECT);
|
|
penFX->Initialize(ese);
|
|
penFX->SetParent(this);
|
|
// mark that we spawned dust
|
|
m_fTacticVar3=1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return EEnd();
|
|
};
|
|
|
|
DeathSequence(EVoid)
|
|
{
|
|
// entity death
|
|
autocall Death() EEnd;
|
|
|
|
// start bloody stain growing out from beneath the corpse
|
|
LeaveStain(TRUE);
|
|
|
|
// check if you have attached flame
|
|
CEntityPointer penFlame = GetChildOfClass("Flame");
|
|
if (penFlame!=NULL)
|
|
{
|
|
// send the event to stop burning
|
|
EStopFlaming esf;
|
|
esf.m_bNow=FALSE;
|
|
penFlame->SendEvent(esf);
|
|
}
|
|
|
|
autowait(2.0f);
|
|
|
|
// start fading out and turning into stardust effect
|
|
m_fSpiritStartTime = _pTimer->CurrentTick();
|
|
m_fFadeStartTime = _pTimer->CurrentTick();
|
|
m_fFadeTime = 1.0f,
|
|
m_bFadeOut = TRUE;
|
|
// become passable even if very large corpse
|
|
SetCollisionFlags(ECF_CORPSE&~((ECBI_PROJECTILE_MAGIC|ECBI_PROJECTILE_SOLID)<<ECB_TEST));
|
|
// wait for fading
|
|
autowait(m_fFadeTime);
|
|
// wait for the stardust effect
|
|
autowait(6.0f);
|
|
|
|
return EEnd();
|
|
}
|
|
//**********************************************************
|
|
// MAIN LOOP PROCEDURES
|
|
//**********************************************************
|
|
|
|
// move
|
|
Active(EVoid)
|
|
{
|
|
m_fDamageConfused = 0.0f;
|
|
// logic loop
|
|
wait () {
|
|
// initially
|
|
on (EBegin) : {
|
|
// start new behavior
|
|
SendEvent(EReconsiderBehavior());
|
|
resume;
|
|
}
|
|
// if new behavior is requested
|
|
on (EReconsiderBehavior) : {
|
|
// if we have an enemy
|
|
if (m_penEnemy!=NULL) {
|
|
// attack it
|
|
call AttackEnemy();
|
|
// if we have a marker to walk to
|
|
} else if (m_penMarker != NULL) {
|
|
// go to the marker
|
|
call MoveThroughMarkers();
|
|
// if on start position
|
|
} else if (m_bOnStartPosition) {
|
|
// just wait here
|
|
m_bOnStartPosition = FALSE;
|
|
call BeIdle();
|
|
// otherwise
|
|
} else {
|
|
// return to start position
|
|
call ReturnToStartPosition();
|
|
}
|
|
resume;
|
|
}
|
|
// on return from some of the sub-procedures
|
|
on (EReturn) : {
|
|
// start new behavior
|
|
SendEvent(EReconsiderBehavior());
|
|
resume;
|
|
}
|
|
// if attack restart is requested
|
|
on (ERestartAttack) : {
|
|
// start new behavior
|
|
SendEvent(EReconsiderBehavior());
|
|
resume;
|
|
}
|
|
// if enemy has been seen
|
|
on (EWatch eWatch) : {
|
|
// if new enemy
|
|
if (SetTargetSoft(eWatch.penSeen)) {
|
|
// if blind till now and start tactics on sense
|
|
if (m_bBlind && m_bTacticsStartOnSense ) {
|
|
StartTacticsNow();
|
|
}
|
|
// react to it
|
|
call NewEnemySpotted();
|
|
}
|
|
resume;
|
|
}
|
|
// if you get damaged by someone
|
|
on (EDamage eDamage) : {
|
|
// eventually set new hard target
|
|
SetTargetHard(eDamage.penInflictor);
|
|
|
|
// if confused
|
|
m_fDamageConfused -= eDamage.fAmount;
|
|
if (m_fDamageConfused < 0.001f) {
|
|
m_fDamageConfused = m_fDamageWounded;
|
|
// notify wounding to others
|
|
WoundedNotify(eDamage);
|
|
// make pain sound
|
|
WoundSound();
|
|
// play wounding animation
|
|
call BeWounded(eDamage);
|
|
}
|
|
resume;
|
|
}
|
|
on (EForceWound) :
|
|
{
|
|
call BeWounded(EDamage());
|
|
resume;
|
|
}
|
|
// if you hear something
|
|
on (ESound eSound) : {
|
|
// if deaf
|
|
if (m_bDeaf) {
|
|
// ignore the sound
|
|
resume;
|
|
}
|
|
|
|
// if the target is visible and can be set as new enemy
|
|
if (IsVisible(eSound.penTarget) && SetTargetSoft(eSound.penTarget)) {
|
|
// react to it
|
|
call NewEnemySpotted();
|
|
}
|
|
resume;
|
|
}
|
|
// on touch
|
|
on (ETouch eTouch) : {
|
|
// set the new target if needed
|
|
BOOL bTargetChanged = SetTargetHard(eTouch.penOther);
|
|
// if target changed
|
|
if (bTargetChanged) {
|
|
// make sound that you spotted the player
|
|
SightSound();
|
|
// start new behavior
|
|
SendEvent(EReconsiderBehavior());
|
|
}
|
|
pass;
|
|
}
|
|
// if triggered manually
|
|
on (ETrigger eTrigger) : {
|
|
CEntity *penCaused = FixupCausedToPlayer(this, eTrigger.penCaused);
|
|
// if can set the trigerer as soft target
|
|
if (SetTargetSoft(penCaused)) {
|
|
// make sound that you spotted the player
|
|
SightSound();
|
|
// start new behavior
|
|
SendEvent(EReconsiderBehavior());
|
|
}
|
|
resume;
|
|
}
|
|
// on stop -> stop enemy
|
|
on (EStop) : {
|
|
jump Inactive();
|
|
}
|
|
|
|
// warn for all obsolete events
|
|
on (EStartAttack) : {
|
|
//CPrintF("%s: StartAttack event is obsolete!\n", GetName());
|
|
resume;
|
|
}
|
|
on (EStopAttack) : {
|
|
//CPrintF("%s: StopAttack event is obsolete!\n", GetName());
|
|
resume;
|
|
}
|
|
}
|
|
};
|
|
|
|
// not doing anything, waiting until some player comes close enough to start patroling or similar
|
|
Inactive(EVoid)
|
|
{
|
|
// stop moving
|
|
StopMoving();
|
|
StandingAnim();
|
|
// start watching
|
|
GetWatcher()->SendEvent(EStart());
|
|
// wait forever
|
|
wait() {
|
|
on (EBegin) : { resume; }
|
|
// if watcher detects that a player is near
|
|
on (EStart) : {
|
|
// become active (patroling etc.)
|
|
jump Active();
|
|
}
|
|
// if returned from wounding
|
|
on (EReturn) : {
|
|
// become active (this will attack the enemy)
|
|
jump Active();
|
|
}
|
|
// if triggered manually
|
|
on (ETrigger eTrigger) : {
|
|
CEntity *penCaused = FixupCausedToPlayer(this, eTrigger.penCaused);
|
|
// if can set the trigerer as soft target
|
|
if (SetTargetSoft(penCaused)) {
|
|
// become active (this will attack the player)
|
|
jump Active();
|
|
}
|
|
}
|
|
// if you get damaged by someone
|
|
on (EDamage eDamage) : {
|
|
// if can set the damager as hard target
|
|
if (SetTargetHard(eDamage.penInflictor)) {
|
|
// notify wounding to others
|
|
WoundedNotify(eDamage);
|
|
// make pain sound
|
|
WoundSound();
|
|
// play wounding animation
|
|
call BeWounded(eDamage);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
// overridable called before main enemy loop actually begins
|
|
PreMainLoop(EVoid)
|
|
{
|
|
return EReturn();
|
|
}
|
|
|
|
// main entry point for enemy behavior
|
|
MainLoop(EVoid)
|
|
{
|
|
// setup some model parameters that are global for all enemies
|
|
SizeModel();
|
|
// check that max health is properly set
|
|
ASSERT(m_fMaxHealth==GetHealth() || IsOfClass(this, "Devil") || IsOfClass(this, "ExotechLarva") || IsOfClass(this, "AirElemental") || IsOfClass(this, "Summoner"));
|
|
|
|
// normalize parameters
|
|
if (m_tmReflexMin<0) {
|
|
m_tmReflexMin = 0.0f;
|
|
}
|
|
if (m_tmReflexMin>m_tmReflexMax) {
|
|
m_tmReflexMax = m_tmReflexMin;
|
|
}
|
|
|
|
// adjust falldown and step up values
|
|
if (m_fStepHeight==-1) {
|
|
m_fStepHeight = 2.0f;
|
|
}
|
|
|
|
// if this is a template
|
|
if (m_bTemplate) {
|
|
// do nothing at all
|
|
return;
|
|
}
|
|
|
|
// wait for just one tick
|
|
// NOTES:
|
|
// if spawned, we have to wait a bit after spawning for spawner to teleport us into proper place
|
|
// if placed directly, we have to wait for game to start (not to start behaving while in editor)
|
|
// IMPORTANT:
|
|
// this wait amount has to be lower than the one in music holder, so that the enemies are initialized before
|
|
// they get counted
|
|
autowait(_pTimer->TickQuantum);
|
|
|
|
// spawn your watcher
|
|
m_penWatcher = CreateEntity(GetPlacement(), CLASS_WATCHER);
|
|
EWatcherInit eInitWatcher;
|
|
eInitWatcher.penOwner = this;
|
|
GetWatcher()->Initialize(eInitWatcher);
|
|
|
|
// switch to next marker (enemies usually point to the marker they stand on)
|
|
if (m_penMarker!=NULL && IsOfClass(m_penMarker, "Enemy Marker")) {
|
|
CEnemyMarker *pem = (CEnemyMarker *)&*m_penMarker;
|
|
m_penMarker = pem->m_penTarget;
|
|
}
|
|
|
|
|
|
// store starting position
|
|
m_vStartPosition = GetPlacement().pl_PositionVector;
|
|
|
|
// set sound default parameters
|
|
m_soSound.Set3DParameters(80.0f, 5.0f, 1.0f, 1.0f);
|
|
|
|
// adjust falldown and step up values
|
|
en_fStepUpHeight = m_fStepHeight+0.01f;
|
|
en_fStepDnHeight = m_fFallHeight+0.01f;
|
|
|
|
// let derived class(es) adjust parameters if needed
|
|
EnemyPostInit();
|
|
|
|
// adjust your difficulty
|
|
AdjustDifficulty();
|
|
|
|
// check enemy params
|
|
ASSERT(m_fStopDistance>=0);
|
|
ASSERT(m_fCloseDistance>=0);
|
|
ASSERT(m_fAttackDistance>m_fCloseDistance);
|
|
ASSERT(m_fIgnoreRange>m_fAttackDistance);
|
|
|
|
SetPredictable(TRUE);
|
|
|
|
autocall PreMainLoop() EReturn;
|
|
|
|
jump StandardBehavior();
|
|
}
|
|
|
|
StandardBehavior(EVoid)
|
|
{
|
|
// this is the main enemy loop
|
|
wait() {
|
|
// initially
|
|
on (EBegin) : {
|
|
// start in active or inactive state
|
|
if (m_penEnemy!=NULL) {
|
|
call Active();
|
|
} else {
|
|
call Inactive();
|
|
}
|
|
};
|
|
// if dead
|
|
on (EDeath eDeath) : {
|
|
// die
|
|
jump Die(eDeath);
|
|
}
|
|
// if an entity exits a teleport nearby
|
|
on (ETeleport et) : {
|
|
// proceed message to watcher (so watcher can quickly recheck for players)
|
|
GetWatcher()->SendEvent(et);
|
|
resume;
|
|
}
|
|
// if should stop being blind
|
|
on (EStopBlindness) : {
|
|
// stop being blind
|
|
m_bBlind = FALSE;
|
|
resume;
|
|
}
|
|
// if should stop being deaf
|
|
on (EStopDeafness) : {
|
|
// stop being deaf
|
|
m_bDeaf = FALSE;
|
|
resume;
|
|
}
|
|
// support for jumping using bouncers
|
|
on (ETouch eTouch) : {
|
|
IfTargetCrushed(eTouch.penOther, (FLOAT3D&)eTouch.plCollision);
|
|
if (IsOfClass(eTouch.penOther, "Bouncer")) {
|
|
JumpFromBouncer(this, eTouch.penOther);
|
|
}
|
|
resume;
|
|
}
|
|
}
|
|
};
|
|
|
|
// dummy main - never called
|
|
Main(EVoid) {
|
|
return;
|
|
};
|
|
};
|