/* 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)<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-fFullDamagefNewHealth) { if (penTrigger[i].ep_pen != NULL) { SendToTarget(&*penTrigger[i], EET_TRIGGER, FixupCausedToPlayer(this, m_penEnemy)); } } } // see if we have to grow for (i=0; ifNewHealth) { 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; iCurrentTick()>(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_fLastSizeGetPlacement().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 && fMiddleSizeTickQuantum); } 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(); }; };