2016-03-11 14:57:17 +01:00
|
|
|
/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
|
|
|
|
|
|
|
|
345
|
|
|
|
%{
|
2016-04-01 20:04:24 +02:00
|
|
|
#include "EntitiesMP/StdH/StdH.h"
|
2016-03-11 14:57:17 +01:00
|
|
|
#include "ModelsMP/Enemies/CannonStatic/Turret.h"
|
|
|
|
|
|
|
|
%}
|
|
|
|
|
|
|
|
uses "EntitiesMP/ModelHolder2";
|
|
|
|
uses "EntitiesMP/Projectile";
|
|
|
|
uses "EntitiesMP/SoundHolder";
|
|
|
|
uses "EntitiesMP/BloodSpray";
|
|
|
|
uses "EntitiesMP/CannonBall";
|
|
|
|
|
|
|
|
%{
|
|
|
|
#define CANNONS_SIZE 2.0f
|
|
|
|
|
|
|
|
// info structure
|
|
|
|
static EntityInfo eiCannonStatic = {
|
|
|
|
EIBT_WOOD, 10000.0f,
|
|
|
|
0.0f, 1.5f*CANNONS_SIZE, 0.0f, // source (eyes)
|
|
|
|
0.0f, 0.5f*CANNONS_SIZE, 0.0f, // target (body)
|
|
|
|
};
|
|
|
|
|
|
|
|
#define FIRING_POSITION_MUZZLE FLOAT3D(0.0f, 0.4f, -1.0f)
|
|
|
|
#define MUZZLE_ROTATION_SPEED 45.0f //deg/sec
|
|
|
|
|
|
|
|
%}
|
|
|
|
|
|
|
|
class CCannonStatic: CEnemyBase {
|
|
|
|
name "CannonStatic";
|
|
|
|
thumbnail "Thumbnails\\CannonStatic.tbn";
|
|
|
|
|
|
|
|
properties:
|
|
|
|
|
|
|
|
1 FLOAT m_fHealth "Cannon Health" = 100.0f,
|
|
|
|
2 RANGE m_fFiringRangeClose "Cannon Firing Close Range" = 50.0f,
|
|
|
|
3 RANGE m_fFiringRangeFar "Cannon Firing Far Range" = 150.0f,
|
|
|
|
4 FLOAT m_fShootingPeriod "Cannon Shooting Period" = 5.0f,
|
|
|
|
5 FLOAT m_fSize = CANNONS_SIZE,
|
|
|
|
6 FLOAT m_fMaxPitch "Cannon Max Pitch" = 20.0f,
|
|
|
|
7 FLOAT m_fViewAngle "Cannon View Angle" = 2.5f,
|
|
|
|
8 BOOL m_bActive "Cannon Active" = TRUE,
|
|
|
|
|
|
|
|
20 FLOAT3D m_fRotSpeedMuzzle = ANGLE3D(0.0f, 0.0f, 0.0f),
|
|
|
|
25 FLOAT m_fDistanceToPlayer = 0.0f,
|
|
|
|
26 FLOAT m_fDesiredMuzzlePitch = 0.0f,
|
|
|
|
27 INDEX m_iMuzzleDir = 1,
|
|
|
|
28 FLOAT3D m_vFiringPos = FLOAT3D(0.0f, 0.0f, 0.0f),
|
|
|
|
29 FLOAT3D m_vTarget = FLOAT3D(0.0f, 0.0f, 0.0f),
|
|
|
|
|
|
|
|
40 FLOAT3D m_aBeginMuzzleRotation = ANGLE3D(0.0f, 0.0f, 0.0f),
|
|
|
|
41 FLOAT3D m_aEndMuzzleRotation = ANGLE3D(0.0f, 0.0f, 0.0f),
|
|
|
|
|
|
|
|
components:
|
|
|
|
1 class CLASS_BASE "Classes\\EnemyBase.ecl",
|
|
|
|
2 class CLASS_BASIC_EFFECT "Classes\\BasicEffect.ecl",
|
|
|
|
3 class CLASS_PROJECTILE "Classes\\Projectile.ecl",
|
|
|
|
4 class CLASS_CANNONBALL "Classes\\CannonBall.ecl",
|
|
|
|
//5 class CLASS_BLOOD_SPRAY "Classes\\BloodSpray.ecl",
|
|
|
|
|
|
|
|
// ************** CANNON MODEL **************
|
|
|
|
10 model MODEL_TURRET "ModelsMP\\Enemies\\CannonStatic\\Turret.mdl",
|
|
|
|
11 model MODEL_CANNON "ModelsMP\\Enemies\\CannonStatic\\Cannon.mdl",
|
|
|
|
20 texture TEXTURE_TURRET "ModelsMP\\Enemies\\CannonStatic\\Turret.tex",
|
|
|
|
21 texture TEXTURE_CANNON "Models\\Weapons\\Cannon\\Body.tex",
|
|
|
|
|
|
|
|
// ************** CANNON PARTS **************
|
|
|
|
30 model MODEL_DEBRIS_MUZZLE "ModelsMP\\Enemies\\CannonStatic\\Debris\\Cannon.mdl",
|
|
|
|
31 model MODEL_DEBRIS_WHEEL "ModelsMP\\Enemies\\CannonStatic\\Debris\\Wheel.mdl",
|
|
|
|
32 model MODEL_DEBRIS_WOOD "ModelsMP\\Enemies\\CannonStatic\\Debris\\Wood.mdl",
|
|
|
|
35 model MODEL_BALL "Models\\Weapons\\Cannon\\Projectile\\CannonBall.mdl",
|
|
|
|
36 texture TEXTURE_BALL "Models\\Weapons\\Cannon\\Projectile\\IronBall.tex",
|
|
|
|
|
|
|
|
// ************** SOUNDS **************
|
|
|
|
50 sound SOUND_FIRE "ModelsMP\\Enemies\\CannonStatic\\Sounds\\Fire.wav",
|
|
|
|
|
|
|
|
functions:
|
|
|
|
|
|
|
|
virtual CTString GetPlayerKillDescription(const CTString &strPlayerName, const EDeath &eDeath)
|
|
|
|
{
|
|
|
|
CTString str;
|
2016-03-29 05:21:44 +02:00
|
|
|
str.PrintF(TRANSV("A Cannon killed %s"), (const char *) strPlayerName);
|
2016-03-11 14:57:17 +01:00
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Entity info */
|
|
|
|
void *GetEntityInfo(void) {
|
|
|
|
return &eiCannonStatic;
|
|
|
|
};
|
|
|
|
|
|
|
|
virtual const CTFileName &GetComputerMessageName(void) const {
|
|
|
|
static DECLARE_CTFILENAME(fnmCannon, "DataMP\\Messages\\Enemies\\CannonStatic.txt");
|
|
|
|
return fnmCannon;
|
|
|
|
};
|
|
|
|
|
|
|
|
void Precache(void) {
|
|
|
|
CEnemyBase::Precache();
|
|
|
|
PrecacheModel(MODEL_DEBRIS_MUZZLE);
|
|
|
|
PrecacheModel(MODEL_DEBRIS_WHEEL);
|
|
|
|
PrecacheModel(MODEL_DEBRIS_WOOD);
|
|
|
|
PrecacheModel(MODEL_BALL);
|
|
|
|
|
|
|
|
PrecacheTexture(TEXTURE_BALL);
|
|
|
|
|
|
|
|
PrecacheSound(SOUND_FIRE);
|
|
|
|
|
|
|
|
PrecacheClass(CLASS_CANNONBALL);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType,
|
|
|
|
FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection)
|
|
|
|
{
|
|
|
|
// take less damage from heavy bullets (e.g. sniper)
|
|
|
|
if(dmtType==DMT_BULLET && fDamageAmmount>100.0f)
|
|
|
|
{
|
|
|
|
fDamageAmmount*=0.5f;
|
|
|
|
}
|
|
|
|
|
|
|
|
CEnemyBase::ReceiveDamage(penInflictor, dmtType, fDamageAmmount,
|
|
|
|
vHitPoint, vDirection);
|
|
|
|
};
|
|
|
|
|
|
|
|
// damage anim
|
|
|
|
INDEX AnimForDamage(FLOAT fDamage) {
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
// death
|
|
|
|
INDEX AnimForDeath(void) {
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
};
|
|
|
|
|
|
|
|
BOOL IsInTheLineOfFire(CEntity *penEntity, FLOAT fAngle)
|
|
|
|
{
|
|
|
|
ASSERT(penEntity!=NULL);
|
|
|
|
|
|
|
|
FLOAT fCosAngle;
|
|
|
|
FLOAT3D vHeading;
|
|
|
|
FLOAT3D vToPlayer;
|
|
|
|
|
|
|
|
FLOAT3D vSide = FLOAT3D(1.0f, 0.0f, 0.0f)*GetRotationMatrix();
|
|
|
|
FLOAT3D vFront = FLOAT3D(0.0f, 0.0f, -1.0f)*GetRotationMatrix();
|
|
|
|
vToPlayer = penEntity->GetPlacement().pl_PositionVector - GetPlacement().pl_PositionVector;
|
|
|
|
vToPlayer.Normalize();
|
|
|
|
|
|
|
|
fCosAngle = vToPlayer%vSide;
|
|
|
|
|
|
|
|
// if on the firing plane
|
|
|
|
if (Abs(fCosAngle)<CosFast(90.0f-fAngle)) {
|
|
|
|
// if in front
|
|
|
|
if ((vToPlayer%vFront)>0.0f) {
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
CPlayer *AcquireTarget() {
|
|
|
|
// find actual number of players
|
|
|
|
INDEX ctMaxPlayers = GetMaxPlayers();
|
|
|
|
CEntity *penPlayer;
|
|
|
|
|
|
|
|
for(INDEX i=0; i<ctMaxPlayers; i++) {
|
|
|
|
penPlayer=GetPlayerEntity(i);
|
|
|
|
if (penPlayer!=NULL && DistanceTo(this, penPlayer)<m_fFiringRangeFar) {
|
|
|
|
// if this player is more or less directly in front of the shooter
|
|
|
|
if (IsInTheLineOfFire(penPlayer, m_fViewAngle)) {
|
|
|
|
// see if something blocks the path to the player
|
|
|
|
if (IsVisible(penPlayer)) {
|
|
|
|
return (CPlayer *)penPlayer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
};
|
|
|
|
|
|
|
|
// spawn body parts
|
|
|
|
void CannonBlowUp(void)
|
|
|
|
{
|
|
|
|
FLOAT3D vNormalizedDamage = m_vDamage-m_vDamage*(m_fBlowUpAmount/m_vDamage.Length());
|
|
|
|
vNormalizedDamage /= Sqrt(vNormalizedDamage.Length());
|
|
|
|
vNormalizedDamage *= 0.75f;
|
|
|
|
vNormalizedDamage += FLOAT3D(0.0f, 10.0f+FRnd()*10.0f, 0.0f);
|
|
|
|
|
|
|
|
FLOAT3D vBodySpeed = en_vCurrentTranslationAbsolute-en_vGravityDir*(en_vGravityDir%en_vCurrentTranslationAbsolute);
|
|
|
|
|
|
|
|
// spawn debris
|
|
|
|
Debris_Begin(EIBT_WOOD, DPT_NONE, BET_NONE, 1.0f, vNormalizedDamage, vBodySpeed, 5.0f, 2.0f);
|
|
|
|
|
|
|
|
Debris_Spawn(this, this, MODEL_DEBRIS_MUZZLE, TEXTURE_CANNON, 0, 0, 0, 0, m_fSize,
|
|
|
|
FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
|
|
|
|
Debris_Spawn(this, this, MODEL_DEBRIS_WHEEL, TEXTURE_TURRET, 0, 0, 0, 0, m_fSize,
|
|
|
|
FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
|
|
|
|
Debris_Spawn(this, this, MODEL_DEBRIS_WHEEL, TEXTURE_TURRET, 0, 0, 0, 0, m_fSize,
|
|
|
|
FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
|
|
|
|
Debris_Spawn(this, this, MODEL_DEBRIS_WOOD, TEXTURE_TURRET, 0, 0, 0, 0, m_fSize,
|
|
|
|
FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
|
|
|
|
Debris_Spawn(this, this, MODEL_DEBRIS_WOOD, TEXTURE_TURRET, 0, 0, 0, 0, m_fSize,
|
|
|
|
FLOAT3D(FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f, FRnd()*0.6f+0.2f));
|
|
|
|
Debris_Spawn(this, this, MODEL_BALL, TEXTURE_BALL, 0, 0, 0, 0, m_fSize/2.0f,
|
|
|
|
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_CANNON;
|
|
|
|
FLOAT fSize = m_fBlowUpSize*1.0f;
|
|
|
|
eSpawnEffect.vStretch = FLOAT3D(fSize,fSize,fSize);
|
|
|
|
penExplosion->Initialize(eSpawnEffect);
|
|
|
|
|
|
|
|
// spawn shockwave
|
|
|
|
plExplosion = GetPlacement();
|
|
|
|
penExplosion = CreateEntity(plExplosion, CLASS_BASIC_EFFECT);
|
|
|
|
eSpawnEffect.colMuliplier = C_WHITE|CT_OPAQUE;
|
|
|
|
eSpawnEffect.betType = BET_CANNONSHOCKWAVE;
|
|
|
|
fSize = m_fBlowUpSize*1.0f;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PreMoving() {
|
|
|
|
// manually update rotation of the attachments
|
|
|
|
UpdateAttachmentRotations();
|
|
|
|
CEnemyBase::PreMoving();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PostMoving() {
|
|
|
|
CEnemyBase::PostMoving();
|
|
|
|
// make sure this entity stays in the moving list
|
|
|
|
SetFlags(GetFlags()&~ENF_INRENDERING);
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL AdjustShadingParameters(FLOAT3D &vLightDirection, COLOR &colLight, COLOR &colAmbient) {
|
|
|
|
CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(TURRET_ATTACHMENT_CANNON);
|
|
|
|
// rotate to between-tick position
|
|
|
|
amo0.amo_plRelative.pl_OrientationAngle = Lerp(m_aBeginMuzzleRotation, m_aEndMuzzleRotation, _pTimer->GetLerpFactor());
|
|
|
|
return CEnemyBase::AdjustShadingParameters(vLightDirection, colLight, colAmbient);
|
|
|
|
};
|
|
|
|
|
|
|
|
void UpdateAttachmentRotations( void )
|
|
|
|
{
|
|
|
|
// muzzle
|
|
|
|
m_aBeginMuzzleRotation = m_aEndMuzzleRotation;
|
|
|
|
m_aEndMuzzleRotation += m_fRotSpeedMuzzle*_pTimer->TickQuantum;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UpdateFiringPos() {
|
|
|
|
FLOATmatrix3D m;
|
|
|
|
// initial position
|
|
|
|
m_vFiringPos = FIRING_POSITION_MUZZLE*m_fSize;
|
|
|
|
|
|
|
|
// pitch rotation
|
|
|
|
MakeRotationMatrixFast(m, m_aBeginMuzzleRotation);
|
|
|
|
m_vFiringPos = m_vFiringPos*m;
|
|
|
|
|
|
|
|
// add translation
|
|
|
|
CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(TURRET_ATTACHMENT_CANNON);
|
|
|
|
m_vFiringPos += amo0.amo_plRelative.pl_PositionVector;
|
|
|
|
}
|
|
|
|
|
|
|
|
procedures:
|
|
|
|
|
|
|
|
MainLoop() {
|
|
|
|
wait() {
|
|
|
|
on (EBegin) : {
|
|
|
|
call WatchPlayers();
|
|
|
|
resume;
|
|
|
|
}
|
|
|
|
on (EDeactivate) : {
|
|
|
|
jump Inactive();
|
|
|
|
}
|
|
|
|
on (EDeath eDeath) : {
|
|
|
|
jump Die(eDeath);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2016-03-29 03:03:54 +02:00
|
|
|
eScore.iPoints = (INDEX) m_iScore;
|
2016-03-11 14:57:17 +01:00
|
|
|
penKiller->SendEvent(eScore);
|
|
|
|
if( CountAsKill())
|
|
|
|
{
|
|
|
|
penKiller->SendEvent(EKilledEnemy());
|
|
|
|
}
|
|
|
|
// send computer message
|
|
|
|
EComputerMessage eMsg;
|
|
|
|
eMsg.fnmMessage = GetComputerMessageName();
|
|
|
|
if (eMsg.fnmMessage!="") {
|
|
|
|
penKiller->SendEvent(eMsg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// spawn debris
|
|
|
|
CannonBlowUp();
|
|
|
|
Destroy();
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
RotateMuzzle() {
|
|
|
|
|
|
|
|
FLOAT fDeltaP = m_fDesiredMuzzlePitch - m_aBeginMuzzleRotation(2);
|
|
|
|
|
|
|
|
// if close enough to desired rotation, don't rotate
|
|
|
|
if (Abs(fDeltaP)<5.0f) { return EReturn(); }
|
|
|
|
|
|
|
|
m_fRotSpeedMuzzle = ANGLE3D(0.0f, MUZZLE_ROTATION_SPEED*Sgn(fDeltaP), 0.0f);
|
|
|
|
autowait(Abs(fDeltaP/MUZZLE_ROTATION_SPEED));
|
|
|
|
m_fRotSpeedMuzzle = ANGLE3D(0.0f, 0.0f, 0.0f);
|
|
|
|
UpdateFiringPos();
|
|
|
|
|
|
|
|
return EReturn();
|
|
|
|
};
|
|
|
|
|
|
|
|
FireCannon() {
|
|
|
|
|
|
|
|
FLOAT3D vToTarget = m_penEnemy->GetPlacement().pl_PositionVector -
|
|
|
|
GetPlacement().pl_PositionVector + m_vFiringPos;
|
|
|
|
vToTarget.Normalize();
|
|
|
|
|
|
|
|
// get vector pointing in heading direction of the muzzle
|
|
|
|
FLOAT3D vCannonFront = FLOAT3D(0.0f, 0.0f, -1.0f)*GetRotationMatrix();
|
|
|
|
ANGLE aToPlayer = ACos(vToTarget%vCannonFront);
|
|
|
|
|
|
|
|
FLOAT fPitch = aToPlayer + 5.0f;
|
|
|
|
FLOAT3D vCannonUp = FLOAT3D(0.0, 1.0f, 0.0f)*GetRotationMatrix();
|
|
|
|
|
|
|
|
// if too far, do not fire
|
|
|
|
if (m_fDistanceToPlayer>m_fFiringRangeFar) {
|
|
|
|
return EReturn();
|
|
|
|
// if player under cannon, minimize pitch
|
|
|
|
} else if (vToTarget%vCannonUp<0.0f) {
|
|
|
|
fPitch = 5.0f;
|
|
|
|
// if in far range
|
|
|
|
} else if (m_fDistanceToPlayer>m_fFiringRangeClose) {
|
|
|
|
if (aToPlayer<m_fMaxPitch) {
|
|
|
|
fPitch = aToPlayer + m_fMaxPitch*(m_fDistanceToPlayer-m_fFiringRangeClose)/(m_fFiringRangeFar-m_fFiringRangeClose);
|
|
|
|
} else if (TRUE) {
|
|
|
|
fPitch = aToPlayer + 10.0f + m_fMaxPitch*(m_fDistanceToPlayer-m_fFiringRangeClose)/(m_fFiringRangeFar-m_fFiringRangeClose);
|
|
|
|
}
|
|
|
|
// just to make sure
|
|
|
|
fPitch = Clamp(fPitch, 1.0f, 80.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_vTarget = m_penEnemy->GetPlacement().pl_PositionVector;
|
|
|
|
|
|
|
|
m_fDesiredMuzzlePitch = fPitch;
|
|
|
|
autocall RotateMuzzle() EReturn;
|
|
|
|
|
|
|
|
FLOAT3D vShooting = GetPlacement().pl_PositionVector + m_vFiringPos;
|
|
|
|
FLOAT3D vSpeedDest = FLOAT3D(0.0f, 0.0f, 0.0f);
|
|
|
|
FLOAT fLaunchSpeed;
|
|
|
|
FLOAT fRelativeHdg;
|
|
|
|
|
|
|
|
// calculate parameters for predicted angular launch curve
|
|
|
|
EntityInfo *peiTarget = (EntityInfo*) (m_penEnemy->GetEntityInfo());
|
|
|
|
CalculateAngularLaunchParams( vShooting, peiTarget->vTargetCenter[1]-6.0f/3.0f, m_vTarget,
|
|
|
|
vSpeedDest, m_fDesiredMuzzlePitch , fLaunchSpeed, fRelativeHdg);
|
|
|
|
|
|
|
|
// target enemy body
|
|
|
|
FLOAT3D vShootTarget;
|
|
|
|
GetEntityInfoPosition(m_penEnemy, peiTarget->vTargetCenter, vShootTarget);
|
|
|
|
// launch
|
|
|
|
CPlacement3D pl;
|
|
|
|
PrepareFreeFlyingProjectile(pl, vShootTarget, m_vFiringPos, ANGLE3D( fRelativeHdg, m_fDesiredMuzzlePitch, 0));
|
|
|
|
CEntityPointer penBall = CreateEntity(pl, CLASS_CANNONBALL);
|
|
|
|
ELaunchCannonBall eLaunch;
|
|
|
|
eLaunch.penLauncher = this;
|
|
|
|
eLaunch.fLaunchPower = fLaunchSpeed;
|
|
|
|
eLaunch.cbtType = CBT_IRON;
|
|
|
|
eLaunch.fSize = 1.0f;
|
|
|
|
penBall->Initialize(eLaunch);
|
|
|
|
|
|
|
|
return EReturn();
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
WatchPlayers()
|
|
|
|
{
|
|
|
|
// this is a kind of 'sleep' mode - check to see if any player entered
|
|
|
|
// the attack radius every once in a while
|
|
|
|
while(TRUE) {
|
|
|
|
autowait(0.20f);
|
|
|
|
CPlayer *pTarget = AcquireTarget();
|
|
|
|
if (pTarget) {
|
|
|
|
if ((pTarget->GetFlags()&ENF_ALIVE) && !(pTarget->GetFlags()&ENF_DELETED)) {
|
|
|
|
m_penEnemy = pTarget;
|
|
|
|
m_fDistanceToPlayer = DistanceTo(this, pTarget);
|
|
|
|
autocall FireCannon() EReturn;
|
|
|
|
autowait(m_fShootingPeriod);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Inactive()
|
|
|
|
{
|
|
|
|
m_fRotSpeedMuzzle = ANGLE3D(0.0f, 0.0f, 0.0f);
|
|
|
|
wait() {
|
|
|
|
on (EBegin) : { resume; }
|
|
|
|
on (EActivate) : {
|
|
|
|
jump MainLoop();
|
|
|
|
}
|
|
|
|
on (EDeath eDeath) : {
|
|
|
|
jump Die(eDeath);
|
|
|
|
}
|
|
|
|
otherwise (): { resume; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Main(EVoid) {
|
|
|
|
// declare yourself as a model
|
|
|
|
InitAsModel();
|
|
|
|
SetPhysicsFlags(EPF_MODEL_WALKING|EPF_HASLUNGS);
|
|
|
|
SetCollisionFlags(ECF_MODEL);
|
|
|
|
SetFlags(GetFlags()|ENF_ALIVE);
|
|
|
|
en_fDensity = 2000.0f;
|
|
|
|
|
|
|
|
// set your appearance
|
|
|
|
SetModel(MODEL_TURRET);
|
|
|
|
SetModelMainTexture(TEXTURE_TURRET);
|
|
|
|
|
|
|
|
AddAttachment(TURRET_ATTACHMENT_CANNON, MODEL_CANNON, TEXTURE_CANNON);
|
|
|
|
// setup moving speed
|
|
|
|
m_fWalkSpeed = 0.0f;
|
|
|
|
m_aWalkRotateSpeed = 0.0f;
|
|
|
|
m_fAttackRunSpeed = 0.0f;
|
|
|
|
m_aAttackRotateSpeed = 0.0f;
|
|
|
|
m_fCloseRunSpeed = 0.0f;
|
|
|
|
m_aCloseRotateSpeed = 0.0f;
|
|
|
|
// setup attack distances
|
|
|
|
m_fStopDistance = 100.0f;
|
|
|
|
//m_fBlowUpAmount = 65.0f;
|
|
|
|
m_fBlowUpAmount = 100.0f;
|
|
|
|
m_fBodyParts = 4;
|
|
|
|
m_fDamageWounded = 0.0f;
|
|
|
|
m_iScore = 750;
|
|
|
|
m_sptType = SPT_WOOD;
|
|
|
|
|
|
|
|
// properties that modify EnemyBase properties
|
|
|
|
if (m_fHealth<=0.0f) { m_fHealth=1.0f; }
|
|
|
|
m_fCloseFireTime = m_fAttackFireTime = m_fShootingPeriod;
|
|
|
|
SetHealth(m_fHealth); m_fMaxHealth = m_fHealth;
|
|
|
|
if (m_fFiringRangeFar<m_fFiringRangeClose) { m_fFiringRangeFar=m_fFiringRangeClose+1.0f; }
|
|
|
|
|
|
|
|
// set stretch factors for height and width
|
|
|
|
GetModelObject()->StretchModel(FLOAT3D(m_fSize, m_fSize, m_fSize));
|
|
|
|
|
|
|
|
ModelChangeNotify();
|
|
|
|
StandingAnim();
|
|
|
|
|
|
|
|
// don't continue behavior in base class! - this enemy is derived
|
|
|
|
// from CEnemyBase only because of its properties
|
|
|
|
autowait(0.05f);
|
|
|
|
SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
|
|
|
|
UpdateFiringPos();
|
|
|
|
|
|
|
|
if (!m_bActive) { jump Inactive(); }
|
|
|
|
jump MainLoop();
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|