2347 lines
62 KiB
C++
2347 lines
62 KiB
C++
#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 );
|
|
|
|
}
|
|
|
|
|