Serious-Engine/Sources/EntitiesMP/CannonStatic.es

519 lines
16 KiB
Erlang
Raw Normal View History

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;
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;
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();
};
};