#include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" class rvMonsterBossMakron : public idAI { public: CLASS_PROTOTYPE( rvMonsterBossMakron ); rvMonsterBossMakron ( void ); void Spawn ( void ); void InitSpawnArgsVariables ( void ); bool CanTurn ( void ) const; void Save ( idSaveGame *savefile ) const; void Restore ( idRestoreGame *savefile ); void BuildActionArray ( void ); //void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); protected: bool CheckActions ( void ); bool CheckTurnActions ( void ); void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); //The Makron behaves differently, and we don't always need him sinking back into an idle after every attack //This version is actually overridden void PerformAction ( const char* stateName, int blendFrames, bool noPain ); //This just calls the parent version bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); //This performs a patterned action void PerformPatternedAction ( ); //For debug, may not need this. void OnStopAction ( void ); //stops all effects void StopAllEffects ( void ); //for the cannon fire int shots; //determines if the Makron will enter idles between attacks bool noIdle; //changes Makron from patterned, script controlled to normal AI bool patternedMode; //store the ideal yaw, because somehow between here and the state we set it gets stomped. float facingIdealYaw; float facingTime; float turnRate; //flag to indicate whether or not the Makron is in the corner of the map when he takes an action. bool flagCornerState; //tallies up the number of consecutive actions the Makron has taken in the corner of the map. //If the tally gets too high, teleport him to the map center. int cornerCount; //in the state of teleporting bool flagTeleporting; //for flying mode ---------------------------------------------------------------------------------------- void BeginSeparation ( void ); void CompleteSeparation ( void ); bool flagFlyingMode; bool flagFakeDeath; int flagUndying; rvClientEffectPtr effectHover; jointHandle_t jointHoverEffect; //for the stomp attack ---------------------------------------------------------------------------------------- float stompMaxRadius; float stompWidth; float stompSpeed; float stompRadius; //for the lightning bolt sweep attacks ------------------------------------------------------------------- void MaintainBoltSweep ( void ); void InitBoltSweep ( idVec3 idealTarget ); void LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ); void StopAllBoltEffects ( void ); rvClientEffectPtr leftBoltEffect; rvClientEffectPtr rightBoltEffect; rvClientEffectPtr leftBoltImpact; rvClientEffectPtr rightBoltImpact; rvClientEffectPtr boltMuzzleFlash; idStr boltEffectName; idVec3 leftBoltVector; idVec3 rightBoltVector; idVec3 boltVectorMin; idVec3 boltVectorMax; idMat3 boltAimMatrix; bool flagSweepDone; //these are grabbed from the def file float boltSweepTime; float boltWaitTime; //used internally float boltSweepStartTime; float boltTime; float boltNextStateTime; int stateBoltSweep; jointHandle_t jointLightningBolt; //end lightning bolt sweep attacks ------------------------------------------------------------------- //changed by script event, allows for grenades to spawn baddies. bool flagAllowSpawns; enum { MAKRON_ACTION_DMG, MAKRON_ACTION_MELEE, MAKRON_ACTION_CANNON, MAKRON_ACTION_CANNON_SWEEP, MAKRON_ACTION_GRENADE, MAKRON_ACTION_LIGHTNING_1, MAKRON_ACTION_LIGHTNING_2, MAKRON_ACTION_STOMP, MAKRON_ACTION_HEAL, MAKRON_ACTION_CHARGE, MAKRON_ACTION_KILLPLAYER, MAKRON_ACTION_COUNT, }; rvAIAction actionDMGAttack; rvAIAction actionMeleeAttack; rvAIAction actionCannonAttack; rvAIAction actionCannonSweepAttack; rvAIAction actionGrenadeAttack; rvAIAction actionLightningPattern1Attack; rvAIAction actionLightningPattern2Attack; rvAIAction actionStompAttack; rvAIAction actionHeal; rvAIAction actionCharge; rvAIAction actionKillPlayer; rvAIAction * actionArray[ MAKRON_ACTION_COUNT ]; int actionPatterned; //when the Makron is low on health, he'll use this. rvScriptFuncUtility scriptRecharge; rvScriptFuncUtility scriptTeleport; stateResult_t State_Torso_DMGAttack ( const stateParms_t& parms ); stateResult_t State_Torso_GrenadeAttack ( const stateParms_t& parms ); stateResult_t State_Torso_MeleeAttack ( const stateParms_t& parms ); stateResult_t State_Torso_CannonAttack ( const stateParms_t& parms ); stateResult_t State_Torso_CannonSweepAttack ( const stateParms_t& parms ); stateResult_t State_Torso_Lightning1Attack ( const stateParms_t& parms ); stateResult_t State_Torso_Lightning2Attack ( const stateParms_t& parms ); stateResult_t State_Torso_StompAttack ( const stateParms_t& parms ); stateResult_t State_Torso_Recharge ( const stateParms_t& parms ); stateResult_t State_Torso_Charge ( const stateParms_t& parms ); stateResult_t State_Torso_KillPlayer ( const stateParms_t& parms ); stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); //used when killed stateResult_t State_Killed ( const stateParms_t& parms ); //walking turn anims stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); //flying turn anims stateResult_t State_Torso_RotateToAngle ( const stateParms_t& parms ); stateResult_t State_ScriptedFace ( const stateParms_t& parms ); //like jesus, except with robot parts stateResult_t State_Torso_FirstDeath ( const stateParms_t& parms ); stateResult_t State_Torso_Resurrection ( const stateParms_t& parms ); //These two frames activate and deactivate the second lightning sweep stateResult_t Frame_BeginLightningSweep2 ( const stateParms_t& parms ); stateResult_t Frame_EndLightningSweep2 ( const stateParms_t& parms ); stateResult_t Frame_StompAttack ( const stateParms_t& parms ); stateResult_t Frame_Teleport ( const stateParms_t& parms ); void Event_AllowMoreSpawns ( void ); void Event_SetNextAction ( const char* actionString ); void Event_EnablePatternMode ( void ); void Event_DisablePatternMode ( void ); void Event_StompAttack ( idVec3 &origin ); void Event_Separate ( void ); void Event_FlyingRotate ( idVec3 &vecOrg ); void Event_ToggleCornerState ( float f ); CLASS_STATES_PROTOTYPE ( rvMonsterBossMakron ); }; const idEventDef EV_AllowMoreSpawns( "allowMoreSpawns" ); const idEventDef EV_SetNextAction( "setNextAction", "s", 'f' ); const idEventDef EV_EnablePatternMode( "enablePatternMode" ); const idEventDef EV_DisablePatternMode( "disablePatternMode" ); const idEventDef EV_StompAttack( "stompAttack", "v" ); const idEventDef EV_Separate( "separate" ); const idEventDef EV_FlyingRotate( "flyingRotate", "v"); const idEventDef AI_ScriptedFace ( "scriptedFace", "ed" ); const idEventDef EV_ToggleCornerState( "toggleCornerState", "f" ); CLASS_DECLARATION( idAI, rvMonsterBossMakron ) EVENT( EV_AllowMoreSpawns, rvMonsterBossMakron::Event_AllowMoreSpawns ) EVENT( EV_SetNextAction, rvMonsterBossMakron::Event_SetNextAction ) EVENT( EV_EnablePatternMode, rvMonsterBossMakron::Event_EnablePatternMode ) EVENT( EV_DisablePatternMode, rvMonsterBossMakron::Event_DisablePatternMode ) EVENT( EV_StompAttack, rvMonsterBossMakron::Event_StompAttack ) EVENT( EV_Separate, rvMonsterBossMakron::Event_Separate ) EVENT( EV_FlyingRotate, rvMonsterBossMakron::Event_FlyingRotate ) EVENT( AI_ScriptedFace, rvMonsterBossMakron::ScriptedFace ) EVENT( EV_ToggleCornerState, rvMonsterBossMakron::Event_ToggleCornerState ) END_CLASS /* ================ rvMonsterBossMakron::rvMonsterBossMakron ================ */ rvMonsterBossMakron::rvMonsterBossMakron ( void ) { //set up this action array } /* ================ rvMonsterBossMakron::BuildActionArray ( void ) ================ */ void rvMonsterBossMakron::BuildActionArray ( void ) { actionArray[ MAKRON_ACTION_DMG ] = &actionDMGAttack; actionArray[ MAKRON_ACTION_MELEE ] = &actionMeleeAttack; actionArray[ MAKRON_ACTION_CANNON ] = &actionCannonAttack; actionArray[ MAKRON_ACTION_CANNON_SWEEP ] = &actionCannonSweepAttack; actionArray[ MAKRON_ACTION_GRENADE ] = &actionGrenadeAttack; actionArray[ MAKRON_ACTION_LIGHTNING_1 ] = &actionLightningPattern1Attack; actionArray[ MAKRON_ACTION_LIGHTNING_2 ] = &actionLightningPattern2Attack; actionArray[ MAKRON_ACTION_STOMP ] = &actionStompAttack; actionArray[ MAKRON_ACTION_HEAL ] = &actionHeal; actionArray[ MAKRON_ACTION_CHARGE ] = &actionCharge; actionArray[ MAKRON_ACTION_KILLPLAYER ] = &actionKillPlayer; } /* ================ rvMonsterBossMakron::Save ================ */ void rvMonsterBossMakron::Save ( idSaveGame *savefile ) const { savefile->WriteInt( shots); savefile->WriteFloat( facingIdealYaw); savefile->WriteFloat( facingTime); savefile->WriteBool( noIdle); savefile->WriteBool( patternedMode); savefile->WriteBool( flagFlyingMode); savefile->WriteBool( flagFakeDeath); savefile->WriteInt( flagUndying ); effectHover.Save( savefile ); savefile->WriteJoint( jointHoverEffect ); // cnicholson: added unsaved var savefile->WriteFloat( stompRadius); leftBoltEffect.Save( savefile ); rightBoltEffect.Save( savefile ); leftBoltImpact.Save( savefile ); rightBoltImpact.Save( savefile ); boltMuzzleFlash.Save( savefile ); savefile->WriteString( boltEffectName); savefile->WriteVec3( leftBoltVector); savefile->WriteVec3( rightBoltVector); savefile->WriteVec3( boltVectorMin); savefile->WriteVec3( boltVectorMax); savefile->WriteMat3( boltAimMatrix); savefile->WriteBool( flagSweepDone); savefile->WriteFloat( boltSweepTime); savefile->WriteFloat( boltSweepStartTime); savefile->WriteFloat( boltTime); savefile->WriteFloat( boltNextStateTime); savefile->WriteInt( stateBoltSweep); savefile->WriteBool( flagAllowSpawns); savefile->WriteBool( flagCornerState ); savefile->WriteInt( cornerCount ); savefile->WriteBool( flagTeleporting ); actionDMGAttack.Save( savefile ); actionMeleeAttack.Save( savefile ); actionCannonAttack.Save( savefile ); actionCannonSweepAttack.Save( savefile ); actionGrenadeAttack.Save( savefile ); actionLightningPattern1Attack.Save( savefile ); actionLightningPattern2Attack.Save( savefile ); actionStompAttack.Save( savefile ); actionHeal.Save( savefile ); actionCharge.Save( savefile ); actionKillPlayer.Save( savefile ); savefile->WriteInt( actionPatterned); scriptRecharge.Save( savefile ); scriptTeleport.Save( savefile ); // cnicholson: No need to save actionArry, its rebuilt during restore } /* ================ rvMonsterBossMakron::Restore ================ */ void rvMonsterBossMakron::Restore ( idRestoreGame *savefile ) { savefile->ReadInt( shots); savefile->ReadFloat( facingIdealYaw); savefile->ReadFloat( facingTime); savefile->ReadBool( noIdle); savefile->ReadBool( patternedMode); savefile->ReadBool( flagFlyingMode); savefile->ReadBool( flagFakeDeath); savefile->ReadInt( flagUndying ); effectHover.Restore( savefile ); savefile->ReadJoint( jointHoverEffect ); // cnicholson: added unrestoed var savefile->ReadFloat( stompRadius); leftBoltEffect.Restore( savefile ); rightBoltEffect.Restore( savefile ); leftBoltImpact.Restore( savefile ); rightBoltImpact.Restore( savefile ); boltMuzzleFlash.Restore( savefile ); savefile->ReadString( boltEffectName); savefile->ReadVec3( leftBoltVector); savefile->ReadVec3( rightBoltVector); savefile->ReadVec3( boltVectorMin); savefile->ReadVec3( boltVectorMax); savefile->ReadMat3( boltAimMatrix); savefile->ReadBool( flagSweepDone); savefile->ReadFloat( boltSweepTime); savefile->ReadFloat( boltSweepStartTime); savefile->ReadFloat( boltTime); savefile->ReadFloat( boltNextStateTime); savefile->ReadInt( stateBoltSweep); savefile->ReadBool( flagAllowSpawns); savefile->ReadBool( flagCornerState ); savefile->ReadInt( cornerCount ); savefile->ReadBool( flagTeleporting ); actionDMGAttack.Restore( savefile ); actionMeleeAttack.Restore( savefile ); actionCannonAttack.Restore( savefile ); actionCannonSweepAttack.Restore( savefile ); actionGrenadeAttack.Restore( savefile ); actionLightningPattern1Attack.Restore( savefile ); actionLightningPattern2Attack.Restore( savefile ); actionStompAttack.Restore( savefile ); actionHeal.Restore( savefile ); actionCharge.Restore( savefile ); actionKillPlayer.Restore( savefile ); savefile->ReadInt( actionPatterned); scriptRecharge.Restore( savefile ); scriptTeleport.Restore( savefile ); //reload the action array, this is done in the init section but if we don't do it here our array is bunk. BuildActionArray(); InitSpawnArgsVariables(); // pre-cache decls gameLocal.FindEntityDefDict ( "monster_makron_legs" ); } /* ================ rvMonsterBossMakron::StopAllEffects ================ */ void rvMonsterBossMakron::StopAllEffects ( void ) { StopAllBoltEffects(); //stop the over effect if( effectHover ) { effectHover.GetEntity()->Stop(); effectHover = 0; } } /* ================ rvMonsterBossMakron::StopAllBoltEffects ================ */ void rvMonsterBossMakron::StopAllBoltEffects ( void ) { if( leftBoltEffect ) { leftBoltEffect.GetEntity()->Stop(); leftBoltEffect = 0; } if( rightBoltEffect ) { rightBoltEffect.GetEntity()->Stop(); rightBoltEffect = 0; } if( leftBoltImpact ) { leftBoltImpact.GetEntity()->Stop(); leftBoltImpact = 0; } if( rightBoltImpact ) { rightBoltImpact.GetEntity()->Stop(); rightBoltImpact = 0; } if( boltMuzzleFlash ) { boltMuzzleFlash .GetEntity()->Stop(); boltMuzzleFlash = 0; } } void rvMonsterBossMakron::InitSpawnArgsVariables ( void ) { //slick! jointLightningBolt = animator.GetJointHandle ( spawnArgs.GetString ( "joint_lightningBolt", "claw_muzzle" ) ); stompMaxRadius = spawnArgs.GetFloat( "stomp_max_range", "1600"); stompWidth = spawnArgs.GetFloat( "stomp_width", "32"); stompSpeed = spawnArgs.GetFloat( "stomp_speed", "32"); boltWaitTime = spawnArgs.GetFloat( "lightingsweep_wait_time", "1"); turnRate = spawnArgs.GetFloat( "fly_turnRate", "180"); } /* ================ rvMonsterBossMakron::Spawn ================ */ void rvMonsterBossMakron::Spawn ( void ) { if( spawnArgs.GetBool("passive")) { flagFakeDeath = true; } else { flagFakeDeath = false; } if( spawnArgs.GetBool("furniture") ) { fl.takedamage = false; } if( spawnArgs.GetBool("junior") ) { flagUndying = 1; } else { flagUndying = 0; } flagAllowSpawns = false; flagFlyingMode = false; patternedMode = false; noIdle = false; flagCornerState = false; cornerCount = 0; flagTeleporting = false; //start clean actionPatterned = -1; boltTime = 0; boltSweepTime = spawnArgs.GetFloat( "lightningsweep_sweep_time", "3"); InitSpawnArgsVariables(); actionDMGAttack.Init ( spawnArgs, "action_DMGAttack", "Torso_DMGAttack", AIACTIONF_ATTACK ); actionCannonAttack.Init ( spawnArgs, "action_cannonAttack", "Torso_CannonAttack", AIACTIONF_ATTACK ); actionCannonSweepAttack.Init ( spawnArgs, "action_cannonsweepAttack", "Torso_CannonSweepAttack", AIACTIONF_ATTACK ); actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", "Torso_GrenadeAttack", AIACTIONF_ATTACK ); actionLightningPattern1Attack.Init( spawnArgs, "action_lightningPattern1Attack", "Torso_Lightning1Attack", AIACTIONF_ATTACK ); actionLightningPattern2Attack.Init( spawnArgs, "action_lightningPattern2Attack", "Torso_Lightning2Attack", AIACTIONF_ATTACK ); actionStompAttack.Init ( spawnArgs, "action_stompAttack", "Torso_StompAttack", AIACTIONF_ATTACK ); actionHeal.Init( spawnArgs, "action_heal", "Torso_Recharge", AIACTIONF_ATTACK ); actionCharge.Init( spawnArgs, "action_charge", "Torso_Charge", AIACTIONF_ATTACK ); actionKillPlayer.Init( spawnArgs, "action_lightningPattern3Attack", "Torso_KillPlayer", AIACTIONF_ATTACK ); actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", "Torso_MeleeAttack", AIACTIONF_ATTACK ); const char *func; if ( spawnArgs.GetString( "script_recharge", "", &func ) ) { scriptRecharge.Init( func ); } if ( spawnArgs.GetString( "script_teleport", "", &func ) ) { scriptTeleport.Init( func ); } //build the action array BuildActionArray(); // pre-cache decls gameLocal.FindEntityDefDict ( "monster_makron_legs" ); } /* ================ rvMonsterBossMakron::CheckTurnActions ================ */ bool rvMonsterBossMakron::CheckTurnActions ( void ) { if ( !flagFakeDeath && !move.fl.moving && !move.anim_turn_angles ) { //&& gameLocal.time > combat.investigateTime ) { float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; if ( turnYaw > lookMax[YAW] * 0.75f ) { PerformAction ( "Torso_TurnRight90", 4, true ); return true; } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { PerformAction ( "Torso_TurnLeft90", 4, true ); return true; } ////gameLocal.Printf(" turn yaw == %f \n", turnYaw); } return false; } /* ================ rvMonsterBossMakron::CheckActions ================ */ bool rvMonsterBossMakron::CheckActions ( void ) { if( spawnArgs.GetFloat("furniture", "0")) { return true; } //if in the middle of teleporting, do nothing if( flagTeleporting ) { return true; } //gameLocal.Printf("Begin CheckActions \n"); if( CheckTurnActions() ) { //gameLocal.Printf("---CheckActions: TurnActions was true \n"); return true; } //If we need more baddies, do it right now regardless of range or player FOV if ( flagAllowSpawns ) { PerformAction ( actionGrenadeAttack.state,actionGrenadeAttack.blendFrames, actionGrenadeAttack.fl.noPain ); //gameLocal.Printf("---CheckActions: Spawned in more fellas \n"); return true; } //melee attacks if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || PerformAction ( &actionStompAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { //gameLocal.Printf("---CheckActions: Melee attacks \n"); return true; } //cannon sweep is good to check if we're up close. //if ( PerformAction ( &actionCannonSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { // return true; //} //If we're in total patterned mode, then don't relay on timers or ranges to perform actions. if ( patternedMode && !flagFakeDeath ) { //gameLocal.Printf("**CheckActions: PerformPatternedAction called \n"); PerformPatternedAction ( ); return true; } //gameLocal.Printf("---CheckActions: FlagFakeDeath: %d \n", flagFakeDeath ); //gameLocal.Printf("---CheckActions: PatternedMode: %d \n", patternedMode ); //ranged attacks if ( PerformAction ( &actionDMGAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || PerformAction ( &actionLightningPattern1Attack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || PerformAction ( &actionCannonAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { //gameLocal.Printf("---CheckActions: Ranged attack called \n"); return true; } //gameLocal.Printf("---CheckActions: Defaulting to AI CheckActions \n"); return idAI::CheckActions ( ); } /* ================ rvMonsterMakron::PerformPatternedAction ================ */ void rvMonsterBossMakron::PerformPatternedAction( ) { //gameLocal.Printf("PerformPatternedAction::Begin--\n"); //if we don't have an action to perform, don't. if( actionPatterned == -1) { //gameLocal.Printf("actionPatterned = -1\n"); //gameLocal.Printf("PerformPatternedAction::End--\n"); return; } //if we're in a corner, right now, then add to the corner tally. if( flagCornerState ) { cornerCount++; } else { cornerCount = 0; } //if this is our third consecutive action in the corner, maybe we should teleport? if( cornerCount == 3 ) { gameLocal.Warning("Makron is stuck in a corner!"); cornerCount = 0; SetState( "Torso_Teleport" ); PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); PostState( "State_Combat" ); return; } rvAIAction* action; rvAIActionTimer* timer; action = actionArray[ actionPatterned]; timer = &actionTimerRangedAttack; //if the attack is variable length, then do all this jive... float scale; scale = 1.0f; //gameLocal.Printf("performing the action\n"); // Perform the raw action PerformAction ( action->state, action->blendFrames, action->fl.noPain ); //Clear this out so the next attack can be queued up. actionPatterned = -1; // Restart the action timer using the length of the animation being played // Even though we don't use the action timer for this instance, we need to keep it updated and fresh. action->timer.Reset ( actionTime, action->diversity, scale ); // Restart the global action timer using the length of the animation being played if ( timer ) { timer->Reset ( actionTime, action->diversity, scale ); } } /* ================ rvMonsterMakron::CanTurn ================ */ bool rvMonsterBossMakron::CanTurn ( void ) const { if( !flagFlyingMode ) { if ( !idAI::CanTurn ( ) ) { return false; } return move.anim_turn_angles != 0.0f || move.fl.moving; } else { return idAI::CanTurn ( ); } } /* ================ rvMonsterMakron::InitBoltSweep ================ */ void rvMonsterBossMakron::InitBoltSweep ( idVec3 idealTarget ) { idVec3 origin; idMat3 axis; trace_t tr; idVec3 fwd; idVec3 right; idVec3 worldup(0,0,1); idVec3 targetPoint; //init all the lightning bolt stuff boltTime = 0; flagSweepDone = false; stateBoltSweep = 0; //get the vector from makron's gun to the target point. GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); targetPoint = idealTarget; //enemy.lastVisibleChestPosition; fwd = targetPoint - origin; fwd.Normalize(); right = fwd.Cross( worldup); boltAimMatrix[0] = fwd; boltAimMatrix[1] = right; boltAimMatrix[2] = worldup; targetPoint = origin + (fwd * 2400); //left targetPoint = targetPoint - (right * 1800); boltVectorMin = targetPoint - origin; boltVectorMin.Normalize(); leftBoltVector = boltVectorMin; //right targetPoint = targetPoint + (right * 1800 * 2); boltVectorMax = targetPoint - origin; boltVectorMax.Normalize(); rightBoltVector = boltVectorMax; } /* ================ rvMonsterMakron::MaintainBoltSweep ================ */ void rvMonsterBossMakron::MaintainBoltSweep ( void ) { enum { STATE_START, STATE_WAIT1, STATE_SWEEP1, STATE_WAIT2, STATE_SWEEP2, STATE_END, }; //advance the state when time is up. switch( stateBoltSweep ) { //init vars case STATE_START: flagSweepDone = false; boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); StopAllBoltEffects(); stateBoltSweep = STATE_WAIT1; return; //blast at the waiting points, vectors do not move case STATE_WAIT1: LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); //check for state advance if( gameLocal.time > boltNextStateTime) { stateBoltSweep++; boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); boltTime = 0; boltSweepStartTime = gameLocal.time; } return; case STATE_SWEEP1: //lerp the lightning bolt vectors boltTime = gameLocal.time - boltSweepStartTime; leftBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); rightBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); //sweep LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); //check for state advance if( gameLocal.time > boltNextStateTime) { stateBoltSweep++; boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); } return; case STATE_WAIT2: LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); //check for state advance if( gameLocal.time > boltNextStateTime) { stateBoltSweep++; boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); boltTime = 0; boltSweepStartTime = gameLocal.time; } return; case STATE_SWEEP2: //lerp the lightning bolt vectors boltTime = gameLocal.time - boltSweepStartTime; //min and max are reversed here. leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); rightBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); //sweep LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); //check for state advance if( gameLocal.time > boltNextStateTime) { stateBoltSweep++; boltNextStateTime = 0; StopAllBoltEffects(); } return; case STATE_END: flagSweepDone = 1; return; } } /* ================ rvMonsterBossMakron::LightningSweep ================ */ void rvMonsterBossMakron::LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ) { idVec3 origin; idMat3 axis; trace_t tr; GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); //trace out from origin along attackVector attackVector.Normalize(); gameLocal.TracePoint( this, tr, origin, origin + (attackVector * 25600), MASK_SHOT_RENDERMODEL, this); //gameRenderWorld->DebugLine( colorRed, origin, tr.c.point, 100, true); if ( !boltEffect ) { boltEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_fly" ), origin, attackVector.ToMat3(), true, tr.c.point); } else { boltEffect->SetOrigin ( origin ); boltEffect->SetAxis ( attackVector.ToMat3() ); boltEffect->SetEndOrigin ( tr.c.point ); } if ( !impactEffect ) { impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_impact" ), tr.c.point, tr.c.normal.ToMat3(), true, tr.c.point); } else { impactEffect->SetOrigin ( tr.c.point ); impactEffect->SetAxis ( tr.c.normal.ToMat3() ); impactEffect->SetEndOrigin ( tr.c.point ); } if ( !boltMuzzleFlash ) { boltMuzzleFlash = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_muzzle" ), origin, attackVector.ToMat3(), true, origin); } else { boltMuzzleFlash->SetOrigin ( origin ); boltMuzzleFlash->SetAxis ( attackVector.ToMat3() ); boltMuzzleFlash->SetEndOrigin ( origin ); } //hurt anything in the way idEntity* ent = gameLocal.entities[tr.c.entityNum]; if( ent) { ent->Damage ( this, this, attackVector, spawnArgs.GetString ( "def_makron_sweep_damage" ), 1.0f, 0 ); } } /* ================ rvMonsterBossMakron::PerformAction ================ */ void rvMonsterBossMakron::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { // Allow movement in actions move.fl.allowAnimMove = true; // Start the action SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); // Always call finish action when the action is done, it will clear the action flag aifl.action = true; PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); // Go back to idle when done-- sometimes. if ( !noIdle ) { PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); } // Main state will wait until action is finished before continuing InterruptState ( "Wait_ActionNoPain" ); OnStartAction ( ); } /* ================ rvMonsterBossMakron::PerformAction ================ */ bool rvMonsterBossMakron::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { return idAI::PerformAction( action, condition ,timer ); } /* ================ rvMonsterBossMakron::PerformAction ================ */ void rvMonsterBossMakron::OnStopAction( void ) { ////gameLocal.Printf("\n\nAction stopped ----------- \n"); } /* ================ rvMonsterBossMakron::Damage ================ */ void rvMonsterBossMakron::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { //Deal damage here, idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); //if the Makron has 0 or less health, make sure he's killed ///if ( health <= 0 && !flagFlyingMode && !flagFakeDeath ) { // Killed( this, this, 1, dir, location); //} } /* ================ rvMonsterBossMakron::Killed ================ */ void rvMonsterBossMakron::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { //if this is the undying Makron Jr, don't worry about death. Stop what we're doing, //Call the script function and let it ride. if( flagUndying == 1) { flagUndying = 2; ExecScriptFunction( funcs.death ); StopAnimState ( ANIMCHANNEL_TORSO ); StopAnimState ( ANIMCHANNEL_LEGS ); PerformAction ( actionKillPlayer.state, actionKillPlayer.blendFrames, actionKillPlayer.fl.noPain ); return; } if( flagUndying == 2) { return; } //stop all effects. StopAllEffects(); //if the makron isn't in flying mode, it is now. if( flagFlyingMode ) { //play the falling animation, then die. StopAnimState ( ANIMCHANNEL_TORSO ); StopAnimState ( ANIMCHANNEL_LEGS ); //SetState( "Torso_Death_Fall" ); idAI::Killed( inflictor, attacker, damage, dir, location); return; } //the Makron is in undying mode until he finishes getting up. //gameLocal.Warning("First form defeated!"); health = 1; aifl.undying = true; fl.takedamage = false; SetMoveType ( MOVETYPE_DEAD ); StopMove( MOVE_STATUS_DONE ); //gameLocal.Printf("************\n************\n************\n************\n---flagFakeDeath set to TRUE! ************\n************\n************\n************\n" ); flagFakeDeath = true; //play the death anim StopAnimState ( ANIMCHANNEL_TORSO ); StopAnimState ( ANIMCHANNEL_LEGS ); if ( head ) { StopAnimState ( ANIMCHANNEL_HEAD ); } //Call this anyway-- hopefully the script is prepared to handle multiple Makron deaths... ExecScriptFunction( funcs.death ); //make sure he goes through deadness, but we need to post FinishAction afterwards // SetState( "Torso_FirstDeath" ); // aifl.action = true; SetState( "Torso_FirstDeath" ); // PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); // PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FirstDeath", 30, 0, SFLAG_ONCLEAR ); } /* ================ rvMonsterBossMakron::BeginSeparation( void ) ================ */ void rvMonsterBossMakron::BeginSeparation( void ) { //spawn in a new Makron leg, idDict args; idEntity* newLegs; //We may need to do this at a later point //SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); args.Copy ( *gameLocal.FindEntityDefDict ( "monster_makron_legs" ) ); args.SetVector ( "origin", GetPhysics()->GetOrigin() ); args.SetInt ( "angle", move.current_yaw ); gameLocal.SpawnEntityDef ( args, &newLegs ); //store the name of the entity in the Makron's keys so we can burn it out as well. spawnArgs.Set( "legs_name", newLegs->GetName() ); } /* ================ rvMonsterBossMakron::CompleteSeparation( void ) ================ */ void rvMonsterBossMakron::CompleteSeparation( void ) { //flying mode now flagFlyingMode = true; SetMoveType ( MOVETYPE_FLY ); move.fl.noGravity = true; animPrefix = "fly"; //is there a cooler way to do this? Heal over time? //health = spawnArgs.GetFloat("health_flying", "5000" ); ExecScriptFunction( funcs.init); //makron is once again pwnable. aifl.undying = false; fl.takedamage = true; flagFakeDeath = false; patternedMode = true; //make sure he cleans up. // PostAnimState ( ANIMCHANNEL_ALL, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); // PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); // PostAnimState ( ANIMCHANNEL_LEGS, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); //gameLocal.Printf("*****\n*****\n*****\nPast 'PostAnimState' flagFakeDeath is: %d \n*****\n*****\n*****\n", flagFakeDeath); // These four lines work! PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); PostState( "State_Combat" ); } /* ================ rvMonsterBossMakron::ScriptedFace( idEntity* faceEnt, bool endWithIdle ) ================ */ /* void rvMonsterBossMakron::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { //set the ideal yaw, the change to the facing state. FaceEntity( faceEnt ); //store the ideal yaw, because somehow between here and the state we set it gets stomped. facingIdealYaw = move.ideal_yaw; //become scripted aifl.scripted = true; //This will get us close to facing the entity correctly. SetState( "State_ScriptedFace", SFLAG_ONCLEAR); } */ /* =============================================================================== Events =============================================================================== */ /* ================ rvMonsterBossMakron::Event_AllowMoreSpawns ================ */ // this will allow Makron to spawn more baddies. void rvMonsterBossMakron::Event_AllowMoreSpawns( void ) { flagAllowSpawns = true; } /* ================ rvMonsterBossMakron::Event_EnablePatternMode ================ */ // When set, the Makron will now only fight via scripted patterns void rvMonsterBossMakron::Event_EnablePatternMode( void ) { patternedMode = true; noIdle = true; flagTeleporting = false; } /* ================ rvMonsterBossMakron::Event_DisablePatternMode ================ */ void rvMonsterBossMakron::Event_DisablePatternMode( void ) { patternedMode = false; noIdle = false; } /* ================ rvMonsterBossMakron::Event_Separate ================ */ void rvMonsterBossMakron::Event_Separate( void ) { //all we need to do here is post the separation state, right? BeginSeparation(); PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Resurrection", 0, 5000, SFLAG_ONCLEAR ); } /* ================ rvMonsterBossMakron::Event_Separate ================ */ void rvMonsterBossMakron::Event_FlyingRotate( idVec3& vecOrg ) { //set move.ideal_yaw TurnToward( vecOrg ); //copy it over facingIdealYaw = move.ideal_yaw; aifl.scripted = true; //set the state SetState( "Torso_RotateToAngle"); } /* ================ rvMonsterBossMakron::Event_SetNextAction ================ */ void rvMonsterBossMakron::Event_SetNextAction( const char * actionString) { //if the next action is occupied, return false if( actionPatterned != -1) { idThread::ReturnFloat(0); return; } //otherwise, select the action from a list if( !idStr::Cmp( actionString, "actionCannon")) { actionPatterned = MAKRON_ACTION_CANNON; } else if( !idStr::Cmp( actionString, "actionCannonSweep")) { actionPatterned = MAKRON_ACTION_CANNON_SWEEP; } else if( !idStr::Cmp( actionString, "actionDMG")) { actionPatterned = MAKRON_ACTION_DMG; } else if( !idStr::Cmp( actionString, "actionDMGrenades")) { actionPatterned = MAKRON_ACTION_GRENADE; } else if( !idStr::Cmp( actionString, "actionLightningSweep1")) { actionPatterned = MAKRON_ACTION_LIGHTNING_1; } else if( !idStr::Cmp( actionString, "actionLightningSweep2")) { actionPatterned = MAKRON_ACTION_LIGHTNING_2; } else if( !idStr::Cmp( actionString, "actionStomp")) { actionPatterned = MAKRON_ACTION_STOMP; } else if( !idStr::Cmp( actionString, "actionHeal")) { actionPatterned = MAKRON_ACTION_HEAL; } else if( !idStr::Cmp( actionString, "actionCharge")) { actionPatterned = MAKRON_ACTION_CHARGE; } else if( !idStr::Cmp( actionString, "actionKillPlayer")) { actionPatterned = MAKRON_ACTION_KILLPLAYER; } else if( !idStr::Cmp( actionString, "actionEndPattern")) { actionPatterned = -1; } else { gameLocal.Error(" Bad action %s passed into MonsterMakron::SetNextAction", actionString); idThread::ReturnFloat(0); return; } idThread::ReturnFloat(1); return; } /* =============================================================================== States =============================================================================== */ CLASS_STATES_DECLARATION ( rvMonsterBossMakron ) STATE ( "Torso_DMGAttack", rvMonsterBossMakron::State_Torso_DMGAttack ) STATE ( "Torso_MeleeAttack", rvMonsterBossMakron::State_Torso_MeleeAttack ) STATE ( "Torso_CannonAttack", rvMonsterBossMakron::State_Torso_CannonAttack ) STATE ( "Torso_GrenadeAttack", rvMonsterBossMakron::State_Torso_GrenadeAttack ) STATE ( "Torso_CannonSweepAttack", rvMonsterBossMakron::State_Torso_CannonSweepAttack ) STATE ( "Torso_Lightning1Attack", rvMonsterBossMakron::State_Torso_Lightning1Attack ) STATE ( "Torso_Lightning2Attack", rvMonsterBossMakron::State_Torso_Lightning2Attack ) STATE ( "Torso_StompAttack", rvMonsterBossMakron::State_Torso_StompAttack ) STATE ( "Torso_Recharge", rvMonsterBossMakron::State_Torso_Recharge ) STATE ( "Torso_Charge", rvMonsterBossMakron::State_Torso_Charge ) STATE ( "Torso_KillPlayer", rvMonsterBossMakron::State_Torso_KillPlayer ) STATE ( "Torso_FirstDeath", rvMonsterBossMakron::State_Torso_FirstDeath ) STATE ( "Torso_Resurrection", rvMonsterBossMakron::State_Torso_Resurrection ) STATE ( "State_Killed", rvMonsterBossMakron::State_Killed ) STATE ( "Torso_Teleport", rvMonsterBossMakron::State_Torso_Teleport ) STATE ( "Torso_TurnRight90", rvMonsterBossMakron::State_Torso_TurnRight90 ) STATE ( "Torso_TurnLeft90", rvMonsterBossMakron::State_Torso_TurnLeft90 ) STATE ( "Torso_RotateToAngle", rvMonsterBossMakron::State_Torso_RotateToAngle ) STATE ( "Frame_BeginLightningSweep2", rvMonsterBossMakron::Frame_BeginLightningSweep2 ) STATE ( "Frame_EndLightningSweep2", rvMonsterBossMakron::Frame_EndLightningSweep2 ) STATE ( "Frame_StompAttack", rvMonsterBossMakron::Frame_StompAttack ) STATE ( "Frame_Teleport", rvMonsterBossMakron::Frame_Teleport ) STATE ( "State_ScriptedFace", rvMonsterBossMakron::State_ScriptedFace ) END_CLASS_STATES /* ================ rvBossMakron::State_Torso_DMGAttack ================ */ stateResult_t rvMonsterBossMakron::State_Torso_DMGAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch( parms.stage ) { case STAGE_INIT: //gameLocal.Warning("Makron DMG Go!"); //fire the DMG DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_dmg", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvBossMakron::State_Torso_Charge ================ */ stateResult_t rvMonsterBossMakron::State_Torso_Charge ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "run", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvBossMakron::State_ScriptedFace ================ */ stateResult_t rvMonsterBossMakron::State_ScriptedFace ( const stateParms_t& parms ) { //note this uses the Makron's version of FacingIdeal, if( !flagFlyingMode) { if ( !aifl.scripted || (!CheckTurnActions( ) && (!move.anim_turn_angles))) { return SRESULT_DONE; } return SRESULT_WAIT; } else { if ( !aifl.scripted || FacingIdeal() ) { return SRESULT_DONE; } return SRESULT_WAIT; } } /* enum { STAGE_INIT, STAGE_WAIT }; idStr turnAnim; float turnYaw; switch( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); //which way do we need to face? turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; if ( turnYaw > lookMax[YAW] * 0.75f ) { turnAnim = "turn_right_90"; } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { turnAnim = "turn_left_90"; } else { //guess we don't need to turn? We're done. aifl.scripted = false; return SRESULT_DONE; } PlayAnim ( ANIMCHANNEL_TORSO, turnAnim, 4 ); AnimTurn ( 90.0f, true ); return SRESULT_WAIT; case STAGE_WAIT: if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { AnimTurn ( 0, true ); combat.investigateTime = gameLocal.time + 250; //back to the start to make sure we're facing the right way. return SRESULT_STAGE ( STAGE_INIT ); } return SRESULT_WAIT; } return SRESULT_ERROR; } */ /* ================ rvBossMakron::State_Torso_FirstDeath ================ */ stateResult_t rvMonsterBossMakron::State_Torso_FirstDeath ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); //force a long blend on this anim since it will be sudden PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvBossMakron::State_Torso_Resurrection ================ */ stateResult_t rvMonsterBossMakron::State_Torso_Resurrection ( const stateParms_t& parms ) { /* enum { STAGE_INIT, STAGE_WAIT_FIRST, STAGE_RISE, STAGE_WAIT_SECOND }; switch( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); //force a long blend on this anim since it will be sudden PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); return SRESULT_STAGE ( STAGE_WAIT_FIRST); case STAGE_WAIT_FIRST: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_STAGE ( STAGE_RISE); } return SRESULT_WAIT; case STAGE_RISE: //hide the leg surface const idKeyValue* kv; kv = spawnArgs.MatchPrefix ( "surface_legs" ); HideSurface ( kv->GetValue() ); //start the effect jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT_SECOND); case STAGE_WAIT_SECOND: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { CompleteSeparation(); return SRESULT_DONE; } return SRESULT_WAIT; } */ enum { STAGE_RISE, STAGE_WAIT, }; switch( parms.stage ) { case STAGE_RISE: //hide the leg surface const idKeyValue* kv; kv = spawnArgs.MatchPrefix ( "surface_legs" ); HideSurface ( kv->GetValue() ); //start the effect jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { CompleteSeparation(); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvBossMakron::State_Lightning2Attack ================ */ stateResult_t rvMonsterBossMakron::State_Torso_Lightning2Attack ( const stateParms_t& parms ) { idVec3 boltVector; enum { STAGE_INIT, STAGE_WAIT, }; switch( parms.stage ) { case STAGE_INIT: //prep up for the bolt //gameLocal.Warning("Prepping sweep 2"); //init these values StopAllBoltEffects(); stateBoltSweep = 0; flagSweepDone = false; //set up bolt targeting at a little above the players chest-- make him duck this one. boltVector = enemy.lastVisibleEyePosition; boltVector.z += 8; InitBoltSweep( boltVector ); //play the anim DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "claw_sweep", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } if( stateBoltSweep == 1) { //fire the bolt out from the claw based on where the claw is pointing, sort of. boltTime = gameLocal.time - boltSweepStartTime; leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact ); } else if( stateBoltSweep == 2) { //gameLocal.Warning("Sweep 2 done."); StopAllBoltEffects(); stateBoltSweep = 0; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Torso_StompAttack ================ */ stateResult_t rvMonsterBossMakron::State_Torso_StompAttack( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT, }; switch( parms.stage ) { case STAGE_INIT: //can't do the stomp attack while flying-- ain't got no legs!! if ( flagFlyingMode ) { return SRESULT_DONE; } DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "shockwave_stomp", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Torso_Teleport ================ */ stateResult_t rvMonsterBossMakron::State_Torso_Teleport( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT, }; switch( parms.stage ) { case STAGE_INIT: //No teleporting in flying mode. if ( flagFlyingMode ) { return SRESULT_DONE; } DisableAnimState ( ANIMCHANNEL_LEGS ); //can't take damage in teleport anim, bad things happen! aifl.undying = true; PlayAnim ( ANIMCHANNEL_TORSO, "teleport_stomp", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { //restore former state, which is killable unless some other effect renders Makron unkillable. if( !flagUndying ) { aifl.undying = false; } return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvBossMakron::State_Torso_GrenadeAttack ================ */ stateResult_t rvMonsterBossMakron::State_Torso_GrenadeAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch( parms.stage ) { case STAGE_INIT: //gameLocal.Warning("Grenades!"); //fire the DMG DisableAnimState ( ANIMCHANNEL_LEGS ); //only allow spawns if the script tells us we can if( !flagAllowSpawns) { PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_dm", parms.blendFrames ); } else { flagAllowSpawns = false; PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_spawn", parms.blendFrames ); } return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvBossMakron::State_Torso_Recharge ================ */ stateResult_t rvMonsterBossMakron::State_Torso_Recharge ( const stateParms_t& parms ) { ExecScriptFunction( scriptRecharge ); return SRESULT_DONE; } /* ================ rvBossMakron::State_Torso_MeleeAttack ================ */ stateResult_t rvMonsterBossMakron::State_Torso_MeleeAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch( parms.stage ) { case STAGE_INIT: //swing! //gameLocal.Warning("Makron Melee Attack"); DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "melee_attack", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Torso_RotateToAngle ================ */ stateResult_t rvMonsterBossMakron::State_Torso_RotateToAngle ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT_LOOP, }; float facingTimeDelta; float turnYaw; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; if( turnYaw > 1.0f || turnYaw < -1.0f) { facingTime = MS2SEC( gameLocal.time); aifl.scripted = true; return SRESULT_STAGE ( STAGE_WAIT_LOOP ); } aifl.scripted = false; return SRESULT_DONE; case STAGE_WAIT_LOOP: turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; if( turnYaw > 1.0f || turnYaw < -1.0f) { facingTimeDelta = MS2SEC( gameLocal.time) - facingTime; idAngles ang = GetPhysics()->GetAxis().ToAngles(); ang.yaw += ( turnRate * facingTimeDelta ); SetAngles( ang); move.current_yaw = ang.yaw; return SRESULT_STAGE( STAGE_WAIT_LOOP); } aifl.scripted = false; return SRESULT_DONE; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Torso_TurnRight90 ================ */ stateResult_t rvMonsterBossMakron::State_Torso_TurnRight90 ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); AnimTurn ( 90.0f, true ); return SRESULT_STAGE ( STAGE_WAIT ); case STAGE_WAIT: if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { AnimTurn ( 0, true ); combat.investigateTime = gameLocal.time + 250; return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Torso_TurnLeft90 ================ */ stateResult_t rvMonsterBossMakron::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); AnimTurn ( 90.0f, true ); return SRESULT_STAGE ( STAGE_WAIT ); case STAGE_WAIT: if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { AnimTurn ( 0, true ); combat.investigateTime = gameLocal.time + 250; return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Torso_CannonAttack ================ */ stateResult_t rvMonsterBossMakron::State_Torso_CannonAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAITSTART, STAGE_LOOP, STAGE_WAITLOOP, STAGE_WAITEND }; //once an anim starts with the legs disabled, the rest of the anims should match that. static bool noLegs; switch ( parms.stage ) { case STAGE_INIT: //if flying, do not override legs if( !flagFlyingMode || ( flagFlyingMode && !move.fl.moving )) { noLegs = true; DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); } else { noLegs = false; PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_start", parms.blendFrames ); } shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; return SRESULT_STAGE ( STAGE_WAITSTART ); case STAGE_WAITSTART: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_LOOP: //if we're flying, and moving, fire fast! //if( flagFlyingMode && move.fl.moving ) { if( !noLegs ) { PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire_fast", 0 ); PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_fire_fast", 0 ); } else { DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire", 0 ); } return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { //if( flagFlyingMode && move.fl.moving ) { if( !noLegs ) { PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_end", 0 ); } else { DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); } return SRESULT_STAGE ( STAGE_WAITEND ); } return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_WAITEND: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Torso_Lightning1Attack ================ */ stateResult_t rvMonsterBossMakron::State_Torso_Lightning1Attack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAITSTART, STAGE_INIT_BOLT, STAGE_LOOP, STAGE_WAITLOOP, STAGE_WAITEND }; idVec3 origin; idVec3 targetPoint; idMat3 axis; trace_t tr; switch ( parms.stage ) { case STAGE_INIT: //gameLocal.Warning( "Lightningbolt!"); DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); shots = (gameLocal.random.RandomInt ( 3 ) + 2) * combat.aggressiveScale; return SRESULT_STAGE ( STAGE_WAITSTART ); case STAGE_WAITSTART: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_STAGE ( STAGE_INIT_BOLT ); } return SRESULT_WAIT; case STAGE_INIT_BOLT: //aim a little below the player's chest targetPoint = enemy.lastVisibleChestPosition; targetPoint.z -= 24; InitBoltSweep( targetPoint ); return SRESULT_STAGE ( STAGE_LOOP ); case STAGE_LOOP: PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: //sweep the blasts back and forth MaintainBoltSweep(); //keep playing the anim until the sweeping is done. if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { if ( flagSweepDone ) { PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); return SRESULT_STAGE ( STAGE_WAITEND ); } else { PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); } } return SRESULT_WAIT; case STAGE_WAITEND: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Torso_KillPlayer ================ */ stateResult_t rvMonsterBossMakron::State_Torso_KillPlayer ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAITSTART, STAGE_LOOP, STAGE_WAITLOOP, STAGE_WAITEND }; switch ( parms.stage ) { case STAGE_INIT: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); shots = 8; return SRESULT_STAGE ( STAGE_WAITSTART ); case STAGE_WAITSTART: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_LOOP: PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: //keep playing the anim until the sweeping is done. if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { shots--; if ( shots < 1) { PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); return SRESULT_STAGE ( STAGE_WAITEND ); } else { PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); } } return SRESULT_WAIT; case STAGE_WAITEND: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvBossMakron::State_Torso_CannonSweepAttack ================ */ stateResult_t rvMonsterBossMakron::State_Torso_CannonSweepAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT }; switch( parms.stage ) { case STAGE_INIT: //gameLocal.Warning("Makron CannonSweep Go!"); //sweep across with the cannon DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_cannonsweep", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAIT); case STAGE_WAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { return SRESULT_DONE; } //if the flag is up, fire. return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::State_Killed ================ */ stateResult_t rvMonsterBossMakron::State_Killed ( const stateParms_t& parms ) { enum { STAGE_FALLSTART, STAGE_FALLSTARTWAIT, STAGE_FALLLOOPWAIT, STAGE_FALLENDWAIT }; switch ( parms.stage ) { case STAGE_FALLSTART: DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "death_start", parms.blendFrames ); return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); case STAGE_FALLSTARTWAIT: if ( move.fl.onGround ) { PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 4 ); return SRESULT_STAGE ( STAGE_FALLENDWAIT ); } if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { PlayAnim ( ANIMCHANNEL_TORSO, "death_loop", 0 ); return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); } return SRESULT_WAIT; case STAGE_FALLLOOPWAIT: if ( move.fl.onGround ) { PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 0 ); return SRESULT_STAGE ( STAGE_FALLENDWAIT ); } return SRESULT_WAIT; case STAGE_FALLENDWAIT: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { disablePain = true; // At this point the Makron is killed! // Make sure all animation stops //StopAnimState ( ANIMCHANNEL_TORSO ); //StopAnimState ( ANIMCHANNEL_LEGS ); //if ( head ) { // StopAnimState ( ANIMCHANNEL_HEAD ); //} // Make sure all animations stop //animator.ClearAllAnims ( gameLocal.time, 0 ); if( spawnArgs.GetBool ( "remove_on_death" ) ){ PostState ( "State_Remove" ); } else { PostState ( "State_Dead" ); } return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterBossMakron::Frame_BeginLightningSweep2 ================ */ stateResult_t rvMonsterBossMakron::Frame_BeginLightningSweep2 ( const stateParms_t& parms ) { //begin the sweep with this flag stateBoltSweep = 1; boltSweepStartTime = gameLocal.time; boltSweepTime = 1; return SRESULT_OK; } /* ================ rvMonsterBossMakron::Frame_BeginLightningSweep2 ================ */ stateResult_t rvMonsterBossMakron::Frame_EndLightningSweep2 ( const stateParms_t& parms ) { //end the sweep with this flag stateBoltSweep = 2; return SRESULT_OK; } /* ================ rvMonsterBossMakron::Frame_Teleport ================ */ stateResult_t rvMonsterBossMakron::Frame_Teleport ( const stateParms_t& parms ) { //hide Hide(); //turn on the do-nothing teleport flag flagTeleporting = true; //call some script. ExecScriptFunction( scriptTeleport ); return SRESULT_DONE; } /* ================ rvMonsterBossMakron::Frame_StompAttack ================ */ stateResult_t rvMonsterBossMakron::Frame_StompAttack ( const stateParms_t& parms ) { idVec3 origin; idVec3 worldUp(0, 0, 1); // Eminate from Makron origin origin = this->GetPhysics()->GetOrigin(); //start radius at 256; stompRadius = spawnArgs.GetFloat("stomp_start_size", "64"); //stomp gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_stomp_wave" ), origin, worldUp.ToMat3(), false, origin); //bamf! PostEventMS( &EV_StompAttack, 0, origin); return SRESULT_OK; } /* ================ rvMonsterBossMakron::Event_ToggleCornerState ================ */ void rvMonsterBossMakron::Event_ToggleCornerState ( float f ) { if( f == 1.0f) { flagCornerState = true; } else { flagCornerState = false; } } /* ================ rvMonsterBossMakron::Event_StompAttack ================ */ void rvMonsterBossMakron::Event_StompAttack (idVec3& origin) { idVec3 targetOrigin; idVec3 worldUp; idEntity* entities[ 1024 ]; int count; int i; float stompZ; idVec3 dir; modelTrace_t result; stompZ = origin.z; worldUp.x = 0; worldUp.y = 0; worldUp.z = 1; //if the radius is too big, stop. if ( stompRadius > stompMaxRadius ) { return; } //get all enemies within radius. If they are: // within radius, // more than (radius - stompWidth) units away, // Z valued within 16 of the the stomp Z //they take stomp damage. count = gameLocal.EntitiesWithinRadius ( origin, stompRadius, entities, 1024 ); //gameRenderWorld->DebugCircle( colorRed,origin,worldUp,stompRadius,24,20,false); //gameRenderWorld->DebugCircle( colorBlue,origin,worldUp,stompRadius - stompWidth,24,20,false); for ( i = 0; i < count; i ++ ) { idEntity* ent = entities[i]; //don't stomp ourself, derp... if ( !ent || ent == this ) { continue; } // Must be an actor that takes damage to be affected if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { continue; } // Are they Z equal (about?) targetOrigin = ent->GetPhysics()->GetOrigin(); if( idMath::Abs( targetOrigin.z - origin.z) > 16) { continue; } // are they within the stomp width? if( targetOrigin.Dist( origin) < ( stompRadius - stompWidth) || targetOrigin.Dist( origin) > stompRadius ) { continue; } if( gameRenderWorld->FastWorldTrace(result, origin, ent->GetPhysics()->GetCenterMass()) ) { continue; } //ok, damage them dir = targetOrigin - origin; dir.NormalizeFast ( ); ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_makron_stomp_damage" ), 1.0f, 0 ); //gameRenderWorld->DebugArrow( colorYellow, origin, targetOrigin, 5, 1000); } //move the radius along stompRadius += stompSpeed; //run it back PostEventSec( &EV_StompAttack, stompSpeed / stompMaxRadius , origin ); }