2016-03-11 14:57:17 +01:00
|
|
|
/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
|
|
|
|
|
|
|
|
347
|
|
|
|
%{
|
2016-04-01 20:04:24 +02:00
|
|
|
#include "EntitiesMP/StdH/StdH.h"
|
2016-03-11 14:57:17 +01:00
|
|
|
#include "ModelsMP/Enemies/AirElemental/AirElemental.h"
|
|
|
|
#include "ModelsMP/Enemies/AirElemental/Elemental.h"
|
|
|
|
#include "Models/Enemies/Elementals/Twister.h"
|
|
|
|
%}
|
|
|
|
|
|
|
|
uses "EntitiesMP/EnemyBase";
|
|
|
|
uses "EntitiesMP/Twister";
|
|
|
|
//uses "EntitiesMP/AirShockwave";
|
|
|
|
|
|
|
|
event EElementalGrow {
|
|
|
|
};
|
|
|
|
|
|
|
|
%{
|
|
|
|
#define ECF_AIR ( \
|
|
|
|
((ECBI_BRUSH|ECBI_MODEL|ECBI_CORPSE|ECBI_ITEM|ECBI_PROJECTILE_MAGIC|ECBI_PROJECTILE_SOLID)<<ECB_TEST) |\
|
|
|
|
((ECBI_MODEL|ECBI_CORPSE|ECBI_ITEM|ECBI_PROJECTILE_MAGIC|ECBI_PROJECTILE_SOLID)<<ECB_PASS) |\
|
|
|
|
((ECBI_MODEL)<<ECB_IS))
|
|
|
|
|
|
|
|
#define AIRBOSS_EYES_HEIGHT 1.7f
|
|
|
|
#define AIRBOSS_BODY_HEIGHT 1.0f
|
|
|
|
|
|
|
|
// info structure
|
|
|
|
static EntityInfo eiAirElemental = {
|
|
|
|
EIBT_AIR, 1500.0f,
|
|
|
|
0.0f, AIRBOSS_EYES_HEIGHT, 0.0f,
|
|
|
|
0.0f, AIRBOSS_BODY_HEIGHT, 0.0f,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define RAND_05 (FLOAT(rand())/RAND_MAX-0.5f)
|
|
|
|
#define FIREPOS_TWISTER FLOAT3D(-0.04f, 0.91f, -1.06f)
|
|
|
|
#define SIZE_NORMAL 1
|
|
|
|
#define SIZE_BIG01 2
|
|
|
|
#define SIZE_BIG02 3
|
|
|
|
#define SIZE_BIG03 4
|
|
|
|
|
|
|
|
#define AIRBOSS_MAX_TA 10
|
|
|
|
#define AIRBOSS_MAX_GA 3
|
|
|
|
|
|
|
|
FLOAT afTriggerArray[AIRBOSS_MAX_TA] = { 0.9f, 0.8f, 0.7f, 0.6f, 0.5f,
|
|
|
|
0.4f, 0.3f, 0.2f, 0.1f, 0.05f };
|
|
|
|
FLOAT afGrowArray[AIRBOSS_MAX_GA][2] = { 0.8f, 25.0f,
|
|
|
|
0.6f, 50.0f,
|
|
|
|
0.4f, 100.0f };
|
|
|
|
%}
|
|
|
|
|
|
|
|
class CAirElemental : CEnemyBase {
|
|
|
|
name "AirElemental";
|
|
|
|
thumbnail "Thumbnails\\AirElemental.tbn";
|
|
|
|
|
|
|
|
properties:
|
|
|
|
|
|
|
|
2 BOOL m_bFloat = FALSE,
|
|
|
|
3 FLOAT m_fAttPosY = 0.0f,
|
|
|
|
|
|
|
|
10 BOOL m_bInitialAnim = FALSE,
|
|
|
|
|
|
|
|
20 CEntityPointer m_penTrigger01 "AirBoss 90% Trigger" ,
|
|
|
|
21 CEntityPointer m_penTrigger02 "AirBoss 80% Trigger" ,
|
|
|
|
22 CEntityPointer m_penTrigger03 "AirBoss 70% Trigger" ,
|
|
|
|
23 CEntityPointer m_penTrigger04 "AirBoss 60% Trigger" ,
|
|
|
|
24 CEntityPointer m_penTrigger05 "AirBoss 50% Trigger" ,
|
|
|
|
25 CEntityPointer m_penTrigger06 "AirBoss 40% Trigger" ,
|
|
|
|
26 CEntityPointer m_penTrigger07 "AirBoss 30% Trigger" ,
|
|
|
|
27 CEntityPointer m_penTrigger08 "AirBoss 20% Trigger" ,
|
|
|
|
28 CEntityPointer m_penTrigger09 "AirBoss 10% Trigger" ,
|
|
|
|
29 CEntityPointer m_penTrigger10 "AirBoss 05% Trigger" ,
|
|
|
|
|
|
|
|
30 FLOAT m_fAttSizeCurrent = 0.0f,
|
|
|
|
31 FLOAT m_fAttSizeBegin = 12.5f, //(25)
|
|
|
|
32 FLOAT m_fAttSizeEnd = 100.0f, //(200)
|
|
|
|
33 FLOAT m_fAttSizeRequested = 0.0f,
|
|
|
|
34 BOOL m_bAttGrow = FALSE,
|
|
|
|
35 INDEX m_iSize = 0, // index of size in afGrowArray array
|
|
|
|
36 FLOAT m_fLastSize = 0.0f,
|
|
|
|
37 FLOAT m_fTargetSize = 0.0f,
|
|
|
|
47 FLOAT m_fGrowSpeed "AirBoss Grow Speed" = 2.0f, // m/sec
|
|
|
|
|
|
|
|
// 40 FLOAT m_tmLastShockwave = 0.0f,
|
|
|
|
// 41 FLOAT m_fShockwaveTreshold "AirBoss Shockwave Treshold" = 50.0f, // fire shockwave when someone closer then this
|
|
|
|
// 42 FLOAT m_fShockwavePeriod "AirBoss Shockwave Period" = 3.0f,
|
|
|
|
|
|
|
|
43 FLOAT m_tmWindNextFire = 0.0f,
|
|
|
|
44 FLOAT m_fWindFireTimeMin "AirBoss Wind Fire Min. Time" = 10.0f,
|
|
|
|
45 FLOAT m_fWindFireTimeMax "AirBoss Wind Fire Max. Time" = 20.0f,
|
|
|
|
46 INDEX m_iWind = 0, // temp index for wind firing
|
|
|
|
|
|
|
|
50 BOOL m_bDying = FALSE, // are we currently dying
|
|
|
|
51 FLOAT m_tmDeath = 1e6f, // time when death begins
|
|
|
|
52 FLOAT m_fDeathDuration = 0.0f, // length of death (for particles)
|
|
|
|
|
|
|
|
60 FLOAT3D m_fWindBlastFirePosBegin = FLOAT3D(-0.44f, 0.7f, -0.94f),
|
|
|
|
61 FLOAT3D m_fWindBlastFirePosEnd = FLOAT3D(0.64f, 0.37f, -0.52f),
|
|
|
|
|
|
|
|
70 FLOAT m_tmLastAnimation=0.0f,
|
|
|
|
|
|
|
|
// temporary variables for reconstructing lost events
|
|
|
|
90 CEntityPointer m_penDeathInflictor,
|
|
|
|
91 BOOL m_bRenderParticles=FALSE,
|
|
|
|
|
|
|
|
100 CSoundObject m_soFire, // sound channel for firing
|
|
|
|
101 CSoundObject m_soVoice, // sound channel for voice
|
|
|
|
|
|
|
|
110 COLOR m_colParticles "Color of particles" = COLOR(C_WHITE|CT_OPAQUE),
|
|
|
|
// 51 INDEX m_ctSpawned = 0,
|
|
|
|
|
|
|
|
{
|
|
|
|
CEmiter m_emEmiter;
|
|
|
|
}
|
|
|
|
|
|
|
|
components:
|
|
|
|
0 class CLASS_BASE "Classes\\EnemyBase.ecl",
|
|
|
|
1 class CLASS_TWISTER "Classes\\Twister.ecl",
|
|
|
|
2 class CLASS_BLOOD_SPRAY "Classes\\BloodSpray.ecl",
|
|
|
|
3 class CLASS_PROJECTILE "Classes\\Projectile.ecl",
|
|
|
|
// 3 class CLASS_AIRSHOCKWAVE "Classes\\AirShockwave.ecl",
|
|
|
|
|
|
|
|
|
|
|
|
// air
|
|
|
|
10 model MODEL_INVISIBLE "ModelsMP\\Enemies\\AirElemental\\AirElemental.mdl",
|
|
|
|
11 model MODEL_ELEMENTAL "ModelsMP\\Enemies\\AirElemental\\Elemental.mdl",
|
|
|
|
12 texture TEXTURE_ELEMENTAL "ModelsMP\\Enemies\\AirElemental\\Elemental.tex",
|
|
|
|
13 texture TEXTURE_DETAIL_ELEM "ModelsMP\\Enemies\\AirElemental\\Detail.tex",
|
|
|
|
|
|
|
|
// ************** SOUNDS **************
|
|
|
|
200 sound SOUND_FIREWINDBLAST "ModelsMP\\Enemies\\AirElemental\\Sounds\\BlastFire.wav",
|
|
|
|
201 sound SOUND_FIRETWISTER "ModelsMP\\Enemies\\AirElemental\\Sounds\\Fire.wav",
|
|
|
|
202 sound SOUND_ROAR "ModelsMP\\Enemies\\AirElemental\\Sounds\\Anger.wav",
|
|
|
|
203 sound SOUND_DEATH "ModelsMP\\Enemies\\AirElemental\\Sounds\\Death.wav",
|
|
|
|
204 sound SOUND_EXPLOSION "ModelsMP\\Enemies\\AirElemental\\Sounds\\Explosion.wav",
|
|
|
|
|
|
|
|
functions:
|
|
|
|
void Read_t( CTStream *istr) // throw char *
|
|
|
|
{
|
|
|
|
CEnemyBase::Read_t(istr);
|
|
|
|
m_emEmiter.Read_t(*istr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Write_t( CTStream *istr) // throw char *
|
|
|
|
{
|
|
|
|
CEnemyBase::Write_t(istr);
|
|
|
|
m_emEmiter.Write_t(*istr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*BOOL IsTargetValid(SLONG slPropertyOffset, CEntity *penTarget)
|
|
|
|
{
|
2016-03-29 03:03:54 +02:00
|
|
|
if( slPropertyOffset == _offsetof(Classname, propert_var) {
|
2016-03-11 14:57:17 +01:00
|
|
|
if (IsOfClass(penTarget, "???")) { return TRUE; }
|
|
|
|
else { return FALSE; }
|
|
|
|
return CEntity::IsTargetValid(slPropertyOffset, penTarget);
|
|
|
|
}*/
|
|
|
|
|
|
|
|
// describe how this enemy killed player
|
|
|
|
virtual CTString GetPlayerKillDescription(const CTString &strPlayerName, const EDeath &eDeath)
|
|
|
|
{
|
|
|
|
CTString str;
|
2016-03-29 05:21:44 +02:00
|
|
|
str.PrintF(TRANSV("%s was -*blown away*- by an Air Elemental"), (const char *) strPlayerName);
|
2016-03-11 14:57:17 +01:00
|
|
|
return str;
|
|
|
|
}
|
|
|
|
virtual const CTFileName &GetComputerMessageName(void) const {
|
|
|
|
static DECLARE_CTFILENAME(fnm, "DataMP\\Messages\\Enemies\\AirElemental.txt");
|
|
|
|
return fnm;
|
|
|
|
};
|
|
|
|
|
|
|
|
void Precache(void)
|
|
|
|
{
|
|
|
|
CEnemyBase::Precache();
|
|
|
|
|
|
|
|
PrecacheClass(CLASS_TWISTER );
|
|
|
|
PrecacheClass(CLASS_BLOOD_SPRAY );
|
|
|
|
PrecacheClass(CLASS_PROJECTILE, PRT_AIRELEMENTAL_WIND );
|
|
|
|
|
|
|
|
PrecacheModel(MODEL_INVISIBLE );
|
|
|
|
PrecacheModel(MODEL_ELEMENTAL );
|
|
|
|
|
|
|
|
PrecacheTexture(TEXTURE_ELEMENTAL );
|
|
|
|
|
|
|
|
PrecacheSound(SOUND_FIREWINDBLAST );
|
|
|
|
PrecacheSound(SOUND_FIRETWISTER );
|
|
|
|
PrecacheSound(SOUND_ROAR );
|
|
|
|
PrecacheSound(SOUND_DEATH );
|
|
|
|
PrecacheSound(SOUND_EXPLOSION );
|
|
|
|
};
|
|
|
|
|
|
|
|
// Entity info
|
|
|
|
void *GetEntityInfo(void) {
|
|
|
|
return &eiAirElemental;
|
|
|
|
};
|
|
|
|
|
|
|
|
// get the attachment that IS the AirElemental
|
|
|
|
CModelObject *ElementalModel(void) {
|
|
|
|
CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(AIRELEMENTAL_ATTACHMENT_BODY);
|
|
|
|
return &(amo0.amo_moModelObject);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Receive damage
|
|
|
|
void ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType,
|
|
|
|
FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection)
|
|
|
|
{
|
|
|
|
// nothing can harm elemental during initial animation
|
|
|
|
if (m_bInitialAnim) { return; }
|
|
|
|
|
|
|
|
// make sure we don't trigger another growth while growing
|
|
|
|
FLOAT fHealth = GetHealth();
|
|
|
|
FLOAT fFullDamage = fDamageAmmount * DamageStrength( ((EntityInfo*)GetEntityInfo())->Eeibt, dmtType) * GetGameDamageMultiplier();
|
|
|
|
if (m_bAttGrow && m_iSize<2) {
|
|
|
|
if (fHealth-fFullDamage<afGrowArray[m_iSize+1][0]*m_fMaxHealth) {
|
|
|
|
CEnemyBase::ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection);
|
|
|
|
SetHealth(fHealth);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (m_bAttGrow && m_iSize==2) {
|
|
|
|
if (fHealth-fFullDamage<1.0f) {
|
|
|
|
CEnemyBase::ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection);
|
|
|
|
SetHealth(fHealth);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// elemental can't harm elemental
|
|
|
|
if(IsOfClass(penInflictor, "AirElemental")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// boss cannot be telefragged
|
|
|
|
if(dmtType==DMT_TELEPORT)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// air elemental cannot be harmed by following kinds of damage:
|
|
|
|
if(dmtType==DMT_CLOSERANGE ||
|
|
|
|
dmtType==DMT_BULLET ||
|
|
|
|
dmtType==DMT_IMPACT ||
|
|
|
|
dmtType==DMT_CHAINSAW)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// cannonballs inflict less damage then the default
|
|
|
|
if(dmtType==DMT_CANNONBALL)
|
|
|
|
{
|
|
|
|
fDamageAmmount *= 0.6f;
|
|
|
|
}
|
|
|
|
|
|
|
|
FLOAT fOldHealth = GetHealth();
|
|
|
|
CEnemyBase::ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection);
|
|
|
|
FLOAT fNewHealth = GetHealth();
|
|
|
|
|
|
|
|
CEntityPointer *penTrigger = &m_penTrigger01;
|
|
|
|
// see if any triggers have to be set
|
2016-03-29 03:03:54 +02:00
|
|
|
INDEX i;
|
2016-03-11 14:57:17 +01:00
|
|
|
for (i=0; i<AIRBOSS_MAX_TA; i++) {
|
|
|
|
FLOAT fHealth = afTriggerArray[i]*m_fMaxHealth;
|
|
|
|
// triggers
|
|
|
|
if (fHealth<=fOldHealth && fHealth>fNewHealth)
|
|
|
|
{
|
|
|
|
if (&*penTrigger[i]) {
|
|
|
|
SendToTarget(&*penTrigger[i], EET_TRIGGER, FixupCausedToPlayer(this, m_penEnemy));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// see if we have to grow
|
|
|
|
for (i=0; i<AIRBOSS_MAX_GA; i++) {
|
|
|
|
FLOAT fHealth = afGrowArray[i][0]*m_fMaxHealth;
|
|
|
|
// growing
|
|
|
|
if (fHealth<=fOldHealth && fHealth>fNewHealth)
|
|
|
|
{
|
|
|
|
m_fAttSizeRequested = afGrowArray[i][1];
|
|
|
|
m_iSize = i;
|
|
|
|
EElementalGrow eeg;
|
|
|
|
SendEvent(eeg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// bosses don't darken when burning
|
|
|
|
m_colBurning=COLOR(C_WHITE|CT_OPAQUE);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
// damage anim
|
|
|
|
INDEX AnimForDamage(FLOAT fDamage) {
|
|
|
|
INDEX iAnim = ELEMENTAL_ANIM_IDLE;
|
|
|
|
ElementalModel()->PlayAnim(iAnim, 0);
|
|
|
|
return iAnim;
|
|
|
|
};
|
|
|
|
|
|
|
|
void StandingAnimFight(void) {
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_IDLE, AOF_LOOPING|AOF_NORESTART);
|
|
|
|
};
|
|
|
|
|
|
|
|
// virtual anim functions
|
|
|
|
void StandingAnim(void) {
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_IDLE, AOF_LOOPING|AOF_NORESTART);
|
|
|
|
};
|
|
|
|
|
|
|
|
void WalkingAnim(void)
|
|
|
|
{
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_IDLE, AOF_LOOPING|AOF_NORESTART);
|
|
|
|
};
|
|
|
|
|
|
|
|
void RunningAnim(void)
|
|
|
|
{
|
|
|
|
WalkingAnim();
|
|
|
|
};
|
|
|
|
|
|
|
|
void RotatingAnim(void) {
|
|
|
|
WalkingAnim();
|
|
|
|
};
|
|
|
|
|
|
|
|
INDEX AnimForDeath(void)
|
|
|
|
{
|
|
|
|
INDEX iAnim;
|
|
|
|
iAnim = ELEMENTAL_ANIM_IDLE;
|
|
|
|
ElementalModel()->PlayAnim(iAnim, 0);
|
|
|
|
return iAnim;
|
|
|
|
};
|
|
|
|
|
|
|
|
// virtual sound functions
|
|
|
|
void IdleSound(void) {
|
|
|
|
//PlaySound(m_soSound, SOUND_IDLE, SOF_3D);
|
|
|
|
};
|
|
|
|
void WoundSound(void) {
|
|
|
|
//PlaySound(m_soSound, SOUND_WOUND, SOF_3D);
|
|
|
|
};
|
|
|
|
|
|
|
|
void SizeModel(void)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
// per-frame adjustments
|
|
|
|
BOOL AdjustShadingParameters(FLOAT3D &vLightDirection, COLOR &colLight, COLOR &colAmbient) {
|
|
|
|
return CMovableModelEntity::AdjustShadingParameters(vLightDirection, colLight, colAmbient);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* BLOW UP FUNCTIONS *
|
|
|
|
************************************************************/
|
|
|
|
// spawn body parts
|
|
|
|
void BlowUp(void) {
|
|
|
|
// get your size
|
|
|
|
/*FLOATaabbox3D box;
|
|
|
|
GetBoundingBox(box);
|
|
|
|
FLOAT fEntitySize = box.Size().MaxNorm()/2;
|
|
|
|
|
|
|
|
INDEX iCount = 7;
|
|
|
|
FLOAT3D vNormalizedDamage = m_vDamage-m_vDamage*(m_fBlowUpAmount/m_vDamage.Length());
|
|
|
|
vNormalizedDamage /= Sqrt(vNormalizedDamage.Length());
|
|
|
|
vNormalizedDamage *= 1.75f;
|
|
|
|
FLOAT3D vBodySpeed = en_vCurrentTranslationAbsolute-en_vGravityDir*(en_vGravityDir%en_vCurrentTranslationAbsolute);
|
|
|
|
|
|
|
|
// hide yourself (must do this after spawning debris)
|
|
|
|
SwitchToEditorModel();
|
|
|
|
SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
|
|
|
|
SetCollisionFlags(ECF_IMMATERIAL);*/
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// adjust sound and watcher parameters here if needed
|
|
|
|
void EnemyPostInit(void)
|
|
|
|
{
|
|
|
|
m_soFire.Set3DParameters(600.0f, 150.0f, 2.0f, 1.0f);
|
|
|
|
m_soVoice.Set3DParameters(600.0f, 150.0f, 2.0f, 1.0f);
|
|
|
|
m_soSound.Set3DParameters(600.0f, 150.0f, 2.0f, 1.0f);
|
|
|
|
};
|
|
|
|
|
|
|
|
void LaunchTwister(FLOAT3D vEnemyOffset)
|
|
|
|
{
|
|
|
|
// calculate parameters for predicted angular launch curve
|
|
|
|
FLOAT3D vFirePos = FIREPOS_TWISTER*m_fAttSizeCurrent*GetRotationMatrix();
|
|
|
|
FLOAT3D vShooting = GetPlacement().pl_PositionVector + vFirePos;
|
|
|
|
FLOAT3D vTarget = m_penEnemy->GetPlacement().pl_PositionVector;
|
|
|
|
FLOAT fLaunchSpeed;
|
|
|
|
FLOAT fRelativeHdg;
|
|
|
|
|
|
|
|
// shoot in front of the enemy
|
|
|
|
EntityInfo *peiTarget = (EntityInfo*) (m_penEnemy->GetEntityInfo());
|
|
|
|
|
|
|
|
// adjust target position
|
|
|
|
vTarget += vEnemyOffset;
|
|
|
|
|
|
|
|
CPlacement3D pl;
|
|
|
|
CalculateAngularLaunchParams( vShooting, peiTarget->vTargetCenter[1]-6.0f/3.0f,
|
|
|
|
vTarget, FLOAT3D(0.0f, 0.0f, 0.0f), 0.0f, fLaunchSpeed, fRelativeHdg);
|
|
|
|
|
|
|
|
PrepareFreeFlyingProjectile(pl, vTarget, vFirePos, ANGLE3D( fRelativeHdg, 0.0f, 0.0f));
|
|
|
|
|
|
|
|
ETwister et;
|
|
|
|
CEntityPointer penTwister = CreateEntity(pl, CLASS_TWISTER);
|
|
|
|
et.penOwner = this;
|
|
|
|
// et.fSize = FRnd()*15.0f+5.0f;
|
|
|
|
et.fSize = FRnd()*10.0f+m_fAttSizeCurrent/5.0f+3.0f;
|
|
|
|
et.fDuration = 15.0f + FRnd()+5.0f;
|
|
|
|
et.sgnSpinDir = (INDEX)(Sgn(FRnd()-0.5f));
|
|
|
|
et.bGrow = TRUE;
|
|
|
|
et.bMovingAllowed=TRUE;
|
|
|
|
penTwister->Initialize(et);
|
|
|
|
|
|
|
|
((CMovableEntity &)*penTwister).LaunchAsFreeProjectile(FLOAT3D(0.0f, 0.0f, -fLaunchSpeed), (CMovableEntity*)(CEntity*)this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PreMoving() {
|
|
|
|
|
|
|
|
// TODO: decomment this when shockwave is fixed
|
|
|
|
/*// see if any of the players are really close to us
|
|
|
|
INDEX ctMaxPlayers = GetMaxPlayers();
|
|
|
|
CEntity *penPlayer;
|
|
|
|
|
|
|
|
for(INDEX i=0; i<ctMaxPlayers; i++) {
|
|
|
|
penPlayer=GetPlayerEntity(i);
|
|
|
|
if (penPlayer!=NULL) {
|
|
|
|
if (DistanceTo(this, penPlayer)<m_fShockwaveTreshold &&
|
|
|
|
_pTimer->CurrentTick()>(m_tmLastShockwave+m_fShockwavePeriod)) {
|
|
|
|
EAirShockwave eas;
|
|
|
|
CEntityPointer penShockwave = CreateEntity(GetPlacement(), CLASS_AIRSHOCKWAVE);
|
|
|
|
eas.penLauncher = this;
|
|
|
|
eas.fHeight = 15.0f + m_iSize*10.0f;
|
|
|
|
eas.fEndWidth = 80.0f + m_iSize*30.0f;
|
|
|
|
eas.fDuration = 3.0f;
|
|
|
|
penShockwave->Initialize(eas);
|
|
|
|
m_tmLastShockwave = _pTimer->CurrentTick();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}*/
|
|
|
|
CEnemyBase::PreMoving();
|
|
|
|
};
|
|
|
|
|
|
|
|
void GetAirElementalAttachmentData(INDEX iAttachment, FLOATmatrix3D &mRot, FLOAT3D &vPos)
|
|
|
|
{
|
|
|
|
MakeRotationMatrixFast(mRot, ANGLE3D(0.0f, 0.0f, 0.0f));
|
|
|
|
vPos=FLOAT3D(0.0f, 0.0f, 0.0f);
|
|
|
|
GetModelObject()->GetAttachmentTransformations(AIRELEMENTAL_ATTACHMENT_BODY, mRot, vPos, FALSE);
|
|
|
|
// next in hierarchy
|
|
|
|
CAttachmentModelObject *pamo = GetModelObject()->GetAttachmentModel(AIRELEMENTAL_ATTACHMENT_BODY);
|
|
|
|
pamo->amo_moModelObject.GetAttachmentTransformations( iAttachment, mRot, vPos, TRUE);
|
|
|
|
vPos=GetPlacement().pl_PositionVector+vPos*GetRotationMatrix();
|
|
|
|
}
|
|
|
|
|
|
|
|
FLOAT GetCurrentStretchRatio(void)
|
|
|
|
{
|
|
|
|
CAttachmentModelObject &amo=*GetModelObject()->GetAttachmentModel(AIRELEMENTAL_ATTACHMENT_BODY);
|
|
|
|
FLOAT fCurrentStretch=amo.amo_moModelObject.mo_Stretch(1);
|
|
|
|
FLOAT fStretch=(fCurrentStretch-m_fAttSizeBegin)/(m_fAttSizeEnd-m_fAttSizeBegin);
|
|
|
|
return fStretch;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenderParticles(void)
|
|
|
|
{
|
|
|
|
static TIME tmLastGrowTime = 0.0f;
|
|
|
|
|
|
|
|
if (m_bFloat) {
|
|
|
|
FLOAT fTime = _pTimer->GetLerpedCurrentTick();
|
|
|
|
CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(AIRELEMENTAL_ATTACHMENT_BODY);
|
|
|
|
amo0.amo_plRelative.pl_PositionVector(2) = m_fAttPosY + pow(sin(fTime*2.0f),2.0f)*m_fAttSizeCurrent*2.0f/m_fAttSizeBegin;
|
|
|
|
}
|
|
|
|
if (m_bAttGrow) {
|
|
|
|
FLOAT fSize = Lerp(m_fLastSize, m_fTargetSize, _pTimer->GetLerpFactor());
|
|
|
|
ElementalModel()->StretchModel(FLOAT3D(fSize, fSize, fSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
if(m_bRenderParticles)
|
|
|
|
{
|
|
|
|
FLOAT fStretchRatio=GetCurrentStretchRatio();
|
|
|
|
FLOAT fStretch=1.0f+(fStretchRatio)*6.0f;
|
|
|
|
Particles_AirElemental(this, fStretch, 1.0f, m_tmDeath, m_colParticles);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
procedures:
|
|
|
|
|
|
|
|
Die(EDeath eDeath) : CEnemyBase::Die {
|
|
|
|
|
|
|
|
SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
|
|
|
|
PlaySound(m_soFire, SOUND_DEATH, SOF_3D);
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_DEATH, AOF_NORESTART);
|
|
|
|
m_tmDeath = _pTimer->CurrentTick()+ElementalModel()->GetAnimLength(ELEMENTAL_ANIM_DEATH);
|
|
|
|
m_bFloat = FALSE;
|
|
|
|
autowait(ElementalModel()->GetAnimLength(ELEMENTAL_ANIM_DEATH)-0.1f);
|
|
|
|
|
|
|
|
PlaySound(m_soVoice, SOUND_EXPLOSION, SOF_3D);
|
|
|
|
m_bDying = TRUE;
|
|
|
|
m_fDeathDuration = 4.0f;
|
|
|
|
|
|
|
|
autowait(m_fDeathDuration);
|
|
|
|
|
|
|
|
EDeath eDeath;
|
|
|
|
eDeath.eLastDamage.penInflictor = m_penDeathInflictor;
|
|
|
|
jump CEnemyBase::Die(eDeath);
|
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* FIRE PROCEDURES *
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
Fire(EVoid) : CEnemyBase::Fire {
|
|
|
|
|
|
|
|
if (m_tmWindNextFire<_pTimer->CurrentTick()) {
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_FIREPROJECTILES, AOF_NORESTART);
|
|
|
|
m_iWind = 0;
|
|
|
|
PlaySound(m_soFire, SOUND_FIREWINDBLAST, SOF_3D);
|
|
|
|
|
|
|
|
autowait(1.8f);
|
|
|
|
while(m_iWind<5)
|
|
|
|
{
|
|
|
|
FLOAT3D vFirePos;
|
|
|
|
vFirePos = Lerp(m_fWindBlastFirePosBegin*m_fAttSizeCurrent,
|
|
|
|
m_fWindBlastFirePosEnd*m_fAttSizeCurrent,
|
|
|
|
(FLOAT)m_iWind*0.25f);
|
|
|
|
ShootProjectile(PRT_AIRELEMENTAL_WIND, vFirePos,
|
|
|
|
ANGLE3D(30.0f-m_iWind*10.0, 0.0f, 0.0f));
|
|
|
|
m_iWind++;
|
|
|
|
autowait(0.1f);
|
|
|
|
}
|
|
|
|
m_tmWindNextFire = _pTimer->CurrentTick() + Lerp(m_fWindFireTimeMin, m_fWindFireTimeMax, FRnd());
|
|
|
|
|
|
|
|
autowait(ElementalModel()->GetAnimLength(ELEMENTAL_ANIM_FIREPROJECTILES)-1.75f);
|
|
|
|
// stand a while
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_IDLE, AOF_LOOPING|AOF_SMOOTHCHANGE);
|
|
|
|
|
|
|
|
autowait(0.05f);
|
|
|
|
|
|
|
|
return EReturn();
|
|
|
|
}
|
|
|
|
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_FIRETWISTER, AOF_NORESTART);
|
|
|
|
//wait to get into twister emitting position
|
|
|
|
PlaySound(m_soFire, SOUND_FIRETWISTER, SOF_3D);
|
|
|
|
autowait(4.0f);
|
|
|
|
|
|
|
|
FLOAT3D vOffset;
|
|
|
|
// static enemy
|
|
|
|
if (((CMovableEntity &)*m_penEnemy).en_vCurrentTranslationAbsolute.Length()==0.0f) {
|
|
|
|
// almost directly at the enemy
|
|
|
|
FLOAT3D vPlayerToThis = GetPlacement().pl_PositionVector - m_penEnemy->GetPlacement().pl_PositionVector;
|
|
|
|
vPlayerToThis.Normalize();
|
|
|
|
vOffset = FLOAT3D(vPlayerToThis*(FRnd()*10.0f+5.0f));
|
|
|
|
LaunchTwister(vOffset);
|
|
|
|
// to the left
|
|
|
|
vOffset = FLOAT3D(-(FRnd()*5.0f+15.0f), 0.0f, (FRnd()-0.5f)*20.0f)*((CMovableEntity &)*m_penEnemy).GetRotationMatrix();
|
|
|
|
LaunchTwister(vOffset);
|
|
|
|
// to the right
|
|
|
|
vOffset = FLOAT3D(+(FRnd()*5.0f+15.0f), 0.0f, 20.0f)*((CMovableEntity &)*m_penEnemy).GetRotationMatrix();
|
|
|
|
LaunchTwister(vOffset);
|
|
|
|
// moving enemy
|
|
|
|
} else {
|
|
|
|
FLOAT3D vPlayerSpeed = ((CMovableEntity &)*m_penEnemy).en_vCurrentTranslationAbsolute;
|
|
|
|
if (vPlayerSpeed.Length()>15.0f) {
|
|
|
|
vPlayerSpeed.Normalize();
|
|
|
|
vPlayerSpeed = vPlayerSpeed*15.0f;
|
|
|
|
}
|
|
|
|
vOffset = vPlayerSpeed*(2.0f+FRnd());
|
|
|
|
FLOAT3D vToPlayer = ((CMovableEntity &)*m_penEnemy).GetPlacement().pl_PositionVector - GetPlacement().pl_PositionVector;
|
|
|
|
vToPlayer.Normalize();
|
|
|
|
vToPlayer*=15.0f + FRnd()*5.0f;
|
|
|
|
vOffset -= vToPlayer;
|
|
|
|
LaunchTwister(vOffset);
|
|
|
|
//LaunchTwister(vOffset+FLOAT3D(-5.0f-FRnd()*5.0f, 0.0f, -15.0f-FRnd()*5.0f));
|
|
|
|
LaunchTwister(FLOAT3D(0.0f, 0.0f, 0.0f));
|
|
|
|
LaunchTwister(vOffset+FLOAT3D(+5.0f+FRnd()*5.0f, 0.0f, -15.0f-FRnd()*5.0f));
|
|
|
|
}
|
|
|
|
|
|
|
|
//PlaySound(m_soSound, SOUND_FIRE, SOF_3D);
|
|
|
|
|
|
|
|
autowait(ElementalModel()->GetAnimLength(ELEMENTAL_ANIM_FIRETWISTER)-4.0f);
|
|
|
|
// stand a while
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_IDLE, AOF_LOOPING|AOF_SMOOTHCHANGE);
|
|
|
|
autowait(0.05f);
|
|
|
|
|
|
|
|
return EReturn();
|
|
|
|
};
|
|
|
|
|
|
|
|
Hit(EVoid) : CEnemyBase::Hit {
|
|
|
|
jump Fire();
|
|
|
|
return EReturn();
|
|
|
|
};
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* D E A T H *
|
|
|
|
************************************************************/
|
|
|
|
Death(EVoid) : CEnemyBase::Death
|
|
|
|
{
|
|
|
|
m_fFadeStartTime = _pTimer->CurrentTick();
|
|
|
|
m_bFadeOut = TRUE;
|
|
|
|
m_fFadeTime = 2.0f;
|
|
|
|
autowait(m_fFadeTime);
|
|
|
|
autocall CEnemyBase::Death() EEnd;
|
|
|
|
//GetModelObject()->mo_toBump.SetData( NULL);
|
|
|
|
return EEnd();
|
|
|
|
};
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
* M A I N *
|
|
|
|
************************************************************/
|
|
|
|
|
|
|
|
Grow() {
|
|
|
|
// we can only grow, never shrink
|
|
|
|
ASSERT(m_fAttSizeRequested>m_fAttSizeCurrent);
|
|
|
|
m_fLastSize = m_fTargetSize = m_fAttSizeCurrent;
|
|
|
|
|
|
|
|
PlaySound(m_soSound, SOUND_ROAR, SOF_3D);
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_IDLE, AOF_NORESTART);
|
|
|
|
|
|
|
|
// stop rotations
|
|
|
|
SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
|
|
|
|
m_bAttGrow = TRUE;
|
|
|
|
while (m_fLastSize<m_fAttSizeRequested) {
|
|
|
|
// keep turning towards target
|
|
|
|
if (m_penEnemy) {
|
|
|
|
FLOAT3D vToTarget;
|
|
|
|
ANGLE3D aToTarget;
|
|
|
|
vToTarget = m_penEnemy->GetPlacement().pl_PositionVector - GetPlacement().pl_PositionVector;
|
|
|
|
vToTarget.Normalize();
|
|
|
|
DirectionVectorToAngles(vToTarget, aToTarget);
|
|
|
|
aToTarget(1) = aToTarget(1) - GetPlacement().pl_OrientationAngle(1);
|
|
|
|
aToTarget(1) = NormalizeAngle(aToTarget(1));
|
|
|
|
SetDesiredRotation(FLOAT3D(aToTarget(1)/2.0f, 0.0f, 0.0f));
|
|
|
|
}
|
|
|
|
|
|
|
|
// grow
|
|
|
|
m_fLastSize = m_fTargetSize;
|
|
|
|
m_fTargetSize += m_fGrowSpeed*_pTimer->TickQuantum;
|
|
|
|
|
|
|
|
// change collision box in the middle of growth
|
|
|
|
// NOTE: collision box definitions and animations in AirElemental.h
|
|
|
|
// have to be ordered so that the one with value 0 represents
|
|
|
|
// the initial one, then the boxes from 1-3 represent
|
|
|
|
// the scaled versions of the original, in order
|
|
|
|
FLOAT fMiddleSize = Lerp(m_fAttSizeCurrent, m_fAttSizeRequested, 0.33f);
|
|
|
|
if (m_fLastSize<=fMiddleSize && fMiddleSize<m_fTargetSize) {
|
|
|
|
if (m_iSize<2) {
|
|
|
|
ChangeCollisionBoxIndexWhenPossible(m_iSize+1);
|
|
|
|
} else if (TRUE) {
|
|
|
|
ForceCollisionBoxIndexChange(m_iSize+1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
autowait(_pTimer->TickQuantum);
|
|
|
|
}
|
|
|
|
m_bAttGrow = FALSE;
|
|
|
|
|
|
|
|
m_fAttSizeCurrent = afGrowArray[m_iSize][1];
|
|
|
|
|
|
|
|
m_fGrowSpeed *= 2.0f;
|
|
|
|
if (m_iSize==1) {
|
|
|
|
GetModelObject()->PlayAnim(AIRELEMENTAL_ANIM_SIZE50, AOF_LOOPING);
|
|
|
|
}
|
|
|
|
|
|
|
|
jump CEnemyBase::MainLoop();
|
|
|
|
}
|
|
|
|
|
|
|
|
ElementalLoop() {
|
|
|
|
wait () {
|
|
|
|
on (EBegin) :
|
|
|
|
{
|
|
|
|
call CEnemyBase::MainLoop();
|
|
|
|
}
|
|
|
|
on (EElementalGrow) :
|
|
|
|
{
|
|
|
|
call Grow();
|
|
|
|
}
|
|
|
|
otherwise (): {
|
|
|
|
resume;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Main(EVoid) {
|
|
|
|
|
|
|
|
// declare yourself as a model
|
|
|
|
InitAsEditorModel();
|
|
|
|
|
|
|
|
SetCollisionFlags(ECF_IMMATERIAL);
|
|
|
|
SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
|
|
|
|
SetFlags(GetFlags()|ENF_ALIVE);
|
|
|
|
|
|
|
|
en_fDensity = 10000.0f;
|
|
|
|
m_fDamageWounded = 1e6f;
|
|
|
|
|
|
|
|
m_sptType = SPT_AIRSPOUTS;
|
|
|
|
m_bBoss = TRUE;
|
|
|
|
SetHealth(15000.0f);
|
|
|
|
m_fMaxHealth = 15000.0f;
|
|
|
|
// setup moving speed
|
|
|
|
m_fWalkSpeed = 0.0f;
|
|
|
|
m_aWalkRotateSpeed = AngleDeg(FRnd()*10.0f + 245.0f);
|
|
|
|
m_fAttackRunSpeed = m_fWalkSpeed;
|
|
|
|
m_aAttackRotateSpeed = m_aWalkRotateSpeed;
|
|
|
|
m_fCloseRunSpeed = m_fWalkSpeed;
|
|
|
|
m_aCloseRotateSpeed = m_aWalkRotateSpeed;
|
|
|
|
// setup attack distances
|
|
|
|
m_fAttackDistance = 500.0f;
|
|
|
|
m_fCloseDistance = 60.0f;
|
|
|
|
m_fStopDistance = 30.0f;
|
|
|
|
m_fAttackFireTime = 4.0f;
|
|
|
|
m_fCloseFireTime = 4.0f;
|
|
|
|
m_fIgnoreRange = 1000.0f;
|
|
|
|
m_iScore = 500000;
|
|
|
|
|
|
|
|
eiAirElemental.vSourceCenter[1] = AIRBOSS_EYES_HEIGHT*m_fAttSizeBegin;
|
|
|
|
eiAirElemental.vTargetCenter[1] = AIRBOSS_BODY_HEIGHT*m_fAttSizeBegin;
|
|
|
|
|
|
|
|
// set your appearance
|
|
|
|
SetModel(MODEL_INVISIBLE);
|
|
|
|
AddAttachmentToModel(this, *GetModelObject(), AIRELEMENTAL_ATTACHMENT_BODY, MODEL_ELEMENTAL, TEXTURE_ELEMENTAL, 0, 0, TEXTURE_DETAIL_ELEM);
|
|
|
|
CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(AIRELEMENTAL_ATTACHMENT_BODY);
|
|
|
|
m_fAttPosY = amo0.amo_plRelative.pl_PositionVector(2);
|
|
|
|
StandingAnim();
|
|
|
|
|
|
|
|
m_fAttSizeCurrent = m_fAttSizeBegin;
|
|
|
|
|
|
|
|
GetModelObject()->StretchModel(FLOAT3D(1.0f, 1.0f, 1.0f));
|
|
|
|
ModelChangeNotify();
|
|
|
|
ElementalModel()->StretchModel(FLOAT3D(m_fAttSizeBegin, m_fAttSizeBegin, m_fAttSizeBegin));
|
|
|
|
|
|
|
|
m_bRenderParticles=FALSE;
|
|
|
|
autowait(_pTimer->TickQuantum);
|
|
|
|
|
|
|
|
m_emEmiter.Initialize(this);
|
|
|
|
m_emEmiter.em_etType=ET_AIR_ELEMENTAL;
|
|
|
|
|
|
|
|
m_tmDeath = 1e6f;
|
|
|
|
|
|
|
|
/*
|
|
|
|
CPlacement3D pl=GetPlacement();
|
|
|
|
ETwister et;
|
|
|
|
CEntityPointer penTwister = CreateEntity(pl, CLASS_TWISTER);
|
|
|
|
et.penOwner = this;
|
|
|
|
et.fSize = 6.0f;
|
|
|
|
et.fDuration = 1e6;
|
|
|
|
et.sgnSpinDir = (INDEX)(Sgn(FRnd()-0.5f));
|
|
|
|
et.bMovingAllowed=FALSE;
|
|
|
|
et.bGrow = FALSE;
|
|
|
|
penTwister->Initialize(et);
|
|
|
|
penTwister->SetParent(this);
|
|
|
|
*/
|
|
|
|
|
|
|
|
// wait to be triggered
|
|
|
|
wait() {
|
|
|
|
on (EBegin) : { resume; }
|
|
|
|
on (ETrigger) : { stop; }
|
|
|
|
otherwise (): { resume; }
|
|
|
|
}
|
|
|
|
|
|
|
|
SetCollisionFlags(ECF_AIR);
|
|
|
|
SetPhysicsFlags(EPF_MODEL_WALKING);
|
|
|
|
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_RAISE, AOF_NORESTART);
|
|
|
|
m_bRenderParticles=TRUE;
|
|
|
|
//SwitchToModel();
|
|
|
|
m_bInitialAnim = TRUE;
|
|
|
|
// TODO: start particle animation
|
|
|
|
autowait(ElementalModel()->GetAnimLength(ELEMENTAL_ANIM_RAISE));
|
|
|
|
// TODO: stop particle animation
|
|
|
|
ChangeCollisionBoxIndexWhenPossible(AIRELEMENTAL_COLLISION_BOX_COLLISION01);
|
|
|
|
|
|
|
|
// deafult size
|
|
|
|
GetModelObject()->PlayAnim(AIRELEMENTAL_ANIM_DEFAULT, AOF_LOOPING);
|
|
|
|
|
|
|
|
ElementalModel()->PlayAnim(ELEMENTAL_ANIM_IDLE, AOF_LOOPING);
|
|
|
|
m_bInitialAnim = FALSE;
|
|
|
|
m_bFloat = TRUE;
|
|
|
|
|
|
|
|
m_tmWindNextFire = _pTimer->CurrentTick() + 10.0f;
|
|
|
|
|
|
|
|
// one state under base class to intercept some events
|
|
|
|
jump ElementalLoop();
|
|
|
|
//jump CEnemyBase::MainLoop();
|
|
|
|
};
|
|
|
|
};
|