Serious-Engine/Sources/EntitiesMP/EnemyBase.es

3262 lines
101 KiB
JavaScript

/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
310
%{
#include "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.5;
}
if (dmtType==DMT_DROWNING || dmtType==DMT_CLOSERANGE || dmtType==DMT_CHAINSAW) {
fKickDamage /= 10;
}
if (dmtType==DMT_BURNING)
{
fKickDamage /= 100000;
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;
};
};