/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ 347 %{ #include "EntitiesMP/StdH/StdH.h" #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) { if( slPropertyOffset == _offsetof(Classname, propert_var) { 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; str.PrintF(TRANSV("%s was -*blown away*- by an Air Elemental"), (const char *) strPlayerName); 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 INDEX i; for (i=0; i<AIRBOSS_MAX_TA; i++) { FLOAT fHealth = afTriggerArray[i]*m_fMaxHealth; // triggers if (fHealth<=fOldHealth && fHealth>fNewHealth) { if (penTrigger[i].ep_pen != NULL) { 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.0f, 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(); }; };