0
0
Fork 0
mirror of https://github.com/id-Software/DOOM-3-BFG.git synced 2025-03-15 23:21:35 +00:00
doom3-bfg/base/script/ai_monster_boss_maledict.script
2022-08-27 13:19:00 +02:00

814 lines
16 KiB
Text

/***********************************************************************
ai_monster_boss_maledict.script
monster_boss_maledict
***********************************************************************/
#define MALEDICT_ATTACK_RATE 2.5
#define MALEDICT_FLAME_WALL_NUM 24
#define MALEDICT_FLAME_WALL_SEPERATION 64
#define MALEDICT_SWOOP_HORIZ 0
#define MALEDICT_SWOOP_VERT 1
// anim blend times
#define MALEDICT_BLEND_FRAMES 0
#define MALEDICT_COMBAT_STAGE1 0
#define MALEDICT_COMBAT_STAGE2 1
object monster_boss_maledict : monster_base {
float nextAttack;
float attackCount;
float currentPt;
float swoopDir;
boolean damageSkin;
float damageHealth;
float combatStage;
float stageCounter;
float currentDamageCap;
float numForgottens;
boolean previousAttackForgottens;
//
// Initialize
//
void init();
//
// States
//
void state_Begin();
void state_Idle();
void state_Intro();
void state_Down();
void state_Swoop();
void state_Combat();
void state_Perch();
void state_FakeDeath();
// attacks
float check_attacks();
void do_attack( float attack_flags );
void combat_range();
void combat_forgotten();
void forgotten_spawn();
void forgotten_killed();
void move_to_position( float newPt );
void spawn_flame_wall( vector org );
vector flame_wall_start_pos( float dir );
void flame_wall_sound_end( entity sEnt );
void flame_wall_loop();
void flame_wall();
void asteroid_loop();
void asteroid_attack();
// torso anim states
void Torso_Idle();
void Torso_RangeAttack();
void Torso_ForgottenAttack();
void Torso_SwoopAttack();
void Torso_PerchAttack();
};
/***********************************************************************
Torso animation control
***********************************************************************/
void monster_boss_maledict::Torso_Idle() {
idleAnim( ANIMCHANNEL_LEGS, "idle" );
}
/*
===========================
Attacks
===========================
*/
void monster_boss_maledict::Torso_RangeAttack() {
playAnim( ANIMCHANNEL_LEGS, "attack_a" );
while( !animDone( ANIMCHANNEL_LEGS, MALEDICT_BLEND_FRAMES ) ) {
// Check for transition to damaged skin
if ( !damageSkin && getHealth() < damageHealth ) {
setSkin( "skins/models/monsters/maledict_burning_nohead" );
damageSkin = true;
}
lookAt( getEnemy(), 1 );
waitFrame();
}
finishAction( "range_attack" );
animState( ANIMCHANNEL_LEGS, "Torso_Idle", MALEDICT_BLEND_FRAMES );
}
void monster_boss_maledict::Torso_ForgottenAttack() {
vector clearAngles;
lookAt( self, 0 );
clearAngles_x = 0;
clearAngles_y = 0;
clearAngles_z = 0;
setAngles( clearAngles );
playAnim( ANIMCHANNEL_LEGS, "forgotten" );
while( !animDone( ANIMCHANNEL_LEGS, MALEDICT_BLEND_FRAMES ) ) {
// Check for transition to damaged skin
if ( !damageSkin && getHealth() < damageHealth ) {
setSkin( "skins/models/monsters/maledict_burning_nohead" );
damageSkin = true;
}
waitFrame();
}
finishAction( "forgotten_attack" );
animState( ANIMCHANNEL_LEGS, "Torso_Idle", MALEDICT_BLEND_FRAMES );
}
void monster_boss_maledict::Torso_SwoopAttack() {
vector clearAngles;
vector spot;
vector playerOrg;
vector newPos;
float startTime;
lookAt( self, 0 );
clearAngles_x = 0;
clearAngles_y = 0;
clearAngles_z = 0;
setAngles( clearAngles );
playerOrg = $player1.getOrigin();
if ( swoopDir == MALEDICT_SWOOP_HORIZ ) {
spot = $spot_A.getOrigin();
newPos = spot;
newPos_y = playerOrg_y;
playAnim( ANIMCHANNEL_LEGS, "swoop_horiz" );
} else if ( swoopDir == MALEDICT_SWOOP_VERT ) {
spot = $spot_C.getOrigin();
newPos = spot;
newPos_x = playerOrg_x;
playAnim( ANIMCHANNEL_LEGS, "swoop_vert" );
} else {
spot = $spot_C.getOrigin();
newPos = spot;
playAnim( ANIMCHANNEL_LEGS, "charge" );
}
setOrigin( newPos );
startTime = sys.getTime();
while( !animDone( ANIMCHANNEL_LEGS, MALEDICT_BLEND_FRAMES ) ) {
// Check for transition to damaged skin
if ( !damageSkin && getHealth() < damageHealth ) {
setSkin( "skins/models/monsters/maledict_burning_nohead" );
damageSkin = true;
}
// Update maledict position
if ( sys.getTime() < ( startTime + 1.7 ) ) {
playerOrg = $player1.getOrigin();
if ( swoopDir == MALEDICT_SWOOP_HORIZ ) {
newPos_y = playerOrg_y;
} else if ( swoopDir == MALEDICT_SWOOP_VERT ) {
newPos_x = playerOrg_x;
}
setOrigin( newPos );
}
waitFrame();
}
finishAction( "swoop_attack" );
animState( ANIMCHANNEL_LEGS, "Torso_Idle", MALEDICT_BLEND_FRAMES );
}
void monster_boss_maledict::Torso_PerchAttack() {
vector clearAngles;
lookAt( self, 0 );
clearAngles_x = 0;
clearAngles_y = 0;
clearAngles_z = 0;
setAngles( clearAngles );
playAnim( ANIMCHANNEL_LEGS, "perch" );
while( !animDone( ANIMCHANNEL_LEGS, MALEDICT_BLEND_FRAMES ) ) {
waitFrame();
}
finishAction( "perch_attack" );
animState( ANIMCHANNEL_LEGS, "Torso_Idle", MALEDICT_BLEND_FRAMES );
}
/***********************************************************************
AI
***********************************************************************/
/*
=====================
monster_boss_maledict::init
=====================
*/
void monster_boss_maledict::init() {
attackCount = 0;
currentPt = 0;
swoopDir = MALEDICT_SWOOP_HORIZ;
damageSkin = false;
damageHealth = getHealth() / 4;
combatStage = MALEDICT_COMBAT_STAGE1;
currentDamageCap = damageHealth + 100;
setDamageCap( currentDamageCap );
stageCounter = 0;
numForgottens = 0;
previousAttackForgottens = false;
setState( "state_Begin" );
}
/***********************************************************************
States
***********************************************************************/
/*
=====================
monster_boss_maledict::state_Begin
=====================
*/
void monster_boss_maledict::state_Begin() {
animState( ANIMCHANNEL_LEGS, "Torso_Idle", MALEDICT_BLEND_FRAMES );
monster_begin();
setMoveType( MOVETYPE_ANIM );
setState( "state_Idle" );
}
/*
=====================
monster_boss_maledict::state_Idle
=====================
*/
void monster_boss_maledict::state_Idle() {
wait_for_enemy();
nextAttack = DelayTime( MALEDICT_ATTACK_RATE );
setState( "state_Intro" );
}
/*
=====================
monster_boss_maledict::state_Intro
=====================
*/
void monster_boss_maledict::state_Intro() {
move_to_position( 0 );
swoopDir = -1;
animState( ANIMCHANNEL_LEGS, "Torso_SwoopAttack", MALEDICT_BLEND_FRAMES );
waitAction( "swoop_attack" );
swoopDir = 0;
setState( "state_Down" );
}
/*
=====================
monster_boss_maledict::state_Down
=====================
*/
void monster_boss_maledict::state_Down() {
animState( ANIMCHANNEL_LEGS, "Torso_Idle", MALEDICT_BLEND_FRAMES );
hide();
// Check for Death
if ( combatStage == MALEDICT_COMBAT_STAGE2 && getHealth() <= 2 ) {
setState( "state_FakeDeath" );
}
// Update Battle stage
if ( damageSkin && combatStage == MALEDICT_COMBAT_STAGE1 ) {
combatStage = MALEDICT_COMBAT_STAGE2;
stageCounter = 0;
currentDamageCap = 100;
setDamageCap( currentDamageCap );
setHealth( 3000 );
}
// Update Damage Cap
if ( currentDamageCap > 1 ) {
if ( stageCounter > 2 ) {
if ( combatStage == MALEDICT_COMBAT_STAGE1 ) {
currentDamageCap = 1;
} else {
currentDamageCap = 1;
}
setDamageCap( currentDamageCap );
}
}
stageCounter++;
if ( combatStage == MALEDICT_COMBAT_STAGE2 ) {
// Choose attack type
if ( attackCount > 0 ) {
attackCount = 0;
sys.wait( 1 );
setState( "state_Swoop" );
} else {
sys.wait( 1 );
setState( "state_Perch" );
}
} else {
// Choose attack type
if ( attackCount >= 2 ) {
attackCount = 0;
sys.wait( 1.5 );
setState( "state_Swoop" );
} else {
sys.wait( 1.5 );
setState( "state_Combat" );
}
}
}
/*
=====================
monster_boss_maledict::state_Combat
=====================
*/
void monster_boss_maledict::state_Combat() {
float attack_flags;
float bestPt;
// Choose best attack position
bestPt = sys.randomInt(5) + 1;
move_to_position( bestPt );
show();
eachFrame {
faceEnemy();
if ( AI_ENEMY_IN_FOV ) {
lookAtEnemy( 1 );
}
if ( sys.influenceActive() ) {
continue;
}
if ( AI_ENEMY_DEAD ) {
enemy_dead();
}
// Do attacks!
attack_flags = check_attacks();
if ( attack_flags ) {
do_attack( attack_flags );
setState( "state_Down" );
}
}
}
/*
=====================
monster_boss_maledict::state_Swoop
=====================
*/
void monster_boss_maledict::state_Swoop() {
float bestPt;
previousAttackForgottens = false;
show();
eachFrame {
if ( sys.influenceActive() ) {
continue;
}
if ( AI_ENEMY_DEAD ) {
enemy_dead();
}
animState( ANIMCHANNEL_LEGS, "Torso_SwoopAttack", MALEDICT_BLEND_FRAMES );
waitAction( "swoop_attack" );
if ( swoopDir == MALEDICT_SWOOP_HORIZ ) {
swoopDir = MALEDICT_SWOOP_VERT;
} else {
swoopDir = MALEDICT_SWOOP_HORIZ;
}
setState( "state_Down" );
}
}
/*
=====================
monster_boss_maledict::state_Perch
=====================
*/
void monster_boss_maledict::state_Perch() {
float bestPt;
show();
eachFrame {
if ( sys.influenceActive() ) {
continue;
}
if ( AI_ENEMY_DEAD ) {
enemy_dead();
}
// The perch always starts from spot C
move_to_position( 0 );
animState( ANIMCHANNEL_LEGS, "Torso_PerchAttack", MALEDICT_BLEND_FRAMES );
waitAction( "perch_attack" );
attackCount++;
setState( "state_Down" );
}
}
/*
=====================
monster_boss_maledict::state_FakeDeath
=====================
*/
void monster_boss_maledict::state_FakeDeath() {
animState( ANIMCHANNEL_LEGS, "Torso_Idle", MALEDICT_BLEND_FRAMES );
kill();
}
/***********************************************************************
attacks
***********************************************************************/
/*
=====================
monster_boss_maledict::do_attack
=====================
*/
void monster_boss_maledict::do_attack( float attack_flags ) {
if ( attack_flags & ATTACK_SPECIAL1 ) {
combat_forgotten();
} else if ( attack_flags & ATTACK_MISSILE ) {
combat_range();
}
}
/*
=====================
monster_boss_maledict::check_attacks
=====================
*/
float monster_boss_maledict::check_attacks() {
float currentTime;
float attack_flags;
attack_flags = 0;
currentTime = sys.getTime();
// Attack!
if ( !previousAttackForgottens && numForgottens <= 0 ) {
attack_flags |= ATTACK_SPECIAL1;
} else if ( currentTime >= nextAttack ) {
attack_flags |= ATTACK_MISSILE;
}
return attack_flags;
}
/*
=====================
monster_boss_maledict::combat_range
=====================
*/
void monster_boss_maledict::combat_range() {
previousAttackForgottens = false;
faceEnemy();
animState( ANIMCHANNEL_LEGS, "Torso_RangeAttack", MALEDICT_BLEND_FRAMES );
waitAction( "range_attack" );
attackCount++;
// don't attack for a bit
nextAttack = DelayTime( MALEDICT_ATTACK_RATE );
}
/*
=====================
monster_boss_maledict::combat_forgotten
=====================
*/
void monster_boss_maledict::combat_forgotten() {
previousAttackForgottens = true;
move_to_position( 0 );
animState( ANIMCHANNEL_LEGS, "Torso_ForgottenAttack", MALEDICT_BLEND_FRAMES );
waitAction( "forgotten_attack" );
// don't attack for a bit
nextAttack = DelayTime( MALEDICT_ATTACK_RATE );
}
/*
=====================
monster_boss_maledict::forgotten_spawn
=====================
*/
void monster_boss_maledict::forgotten_spawn() {
entity ent;
entity spot;
sys.setSpawnArg( "teleport", "1" );
sys.setSpawnArg( "target", "forgotten_dead" );
ent = sys.spawn( "monster_flying_forgotten" );
ent.setKey( "cinematic_remove", "1" ); // make sure it gets removed in cinematics
if ( numForgottens < 2 ) {
spot = $spot_forgotten1;
} else {
spot = $spot_forgotten2;
}
ent.setOrigin( spot.getOrigin() );
ent.setAngles( spot.getAngles() );
sys.trigger( ent );
numForgottens++;
}
/*
=====================
monster_boss_maledict::forgotten_killed
=====================
*/
void monster_boss_maledict::forgotten_killed() {
numForgottens--;
}
/*
=====================
monster_boss_maledict::move_to_position
=====================
*/
void monster_boss_maledict::move_to_position( float newPt ) {
entity spot;
if ( newPt == 0 ) {
spot = $spot_C;
} else if ( newPt == 1 ) {
spot = $spot_A;
} else if ( newPt == 2 ) {
spot = $spot_B;
} else if ( newPt == 3 ) {
spot = $spot_D;
} else if ( newPt == 4 ) {
spot = $spot_E;
} else if ( newPt == 5 ) {
spot = $spot_F;
}
currentPt = newPt;
setOrigin( spot.getOrigin() );
setAngles( spot.getAngles() );
}
/*
=====================
monster_boss_maledict::spawn_flame_wall
=====================
*/
void monster_boss_maledict::spawn_flame_wall( vector org ) {
entity ent;
vector ang;
float flameNum;
string flameEntName;
flameNum = sys.randomInt(3) + 1;
flameEntName = "maledict_flamewall" + flameNum;
ang_y = sys.random( 360 );
ent = sys.spawn( flameEntName );
ent.setKey( "cinematic_remove", "1" ); // make sure it gets removed in cinematics
ent.setShaderParm( 4, -sys.getTime() );
ent.setShaderParm( 5, sys.random( 1 ) );
ent.setOrigin( org );
ent.setAngles( ang );
ent.setOwner( self );
ent.disable();
sys.wait( 0.2 );
ent.enable();
sys.wait( 0.8 );
ent.disable();
sys.wait( 1.8 );
ent.remove();
}
/*
=====================
monster_boss_maledict::flame_wall_start_pos
=====================
*/
vector monster_boss_maledict::flame_wall_start_pos( float dir ) {
float i;
float frac;
vector finalPos;
vector startPos;
vector pos;
vector offset;
if ( swoopDir == MALEDICT_SWOOP_HORIZ ) {
offset = '-64 0 0';
} else {
offset = '0 64 0';
}
startPos = $player1.getOrigin();
pos = startPos + offset;
finalPos = pos;
for( i = 0; i < MALEDICT_FLAME_WALL_NUM; i++ ) {
pos_z += 128;
if ( sys.trace( pos, pos + offset, '-8 -8 0', '8 8 16', MASK_SOLID, self ) < 1 ) {
break;
}
pos = pos + offset;
frac = sys.trace( pos, pos - '0 0 256', '-8 -8 0', '8 8 16', MASK_SOLID, self );
if ( frac == 1 ) {
break;
}
pos_z -= 256 * frac;
finalPos = pos;
}
return finalPos;
}
/*
=====================
monster_boss_maledict::flame_wall_sound_end
=====================
*/
void monster_boss_maledict::flame_wall_sound_end( entity sEnt ) {
sys.wait( 1 );
sEnt.fadeSound( SND_CHANNEL_ANY, -60, 2 );
sys.wait( 2 );
sEnt.Off();
sEnt.remove();
}
/*
=====================
monster_boss_maledict::flame_wall_loop
=====================
*/
void monster_boss_maledict::flame_wall_loop() {
float i;
vector dir;
vector ang;
vector pos;
float frac;
entity soundEnt;
if ( swoopDir == MALEDICT_SWOOP_HORIZ ) {
pos = flame_wall_start_pos( swoopDir );
ang = '0 0 0';
} else if ( swoopDir == MALEDICT_SWOOP_VERT ) {
pos = flame_wall_start_pos( swoopDir );
ang = '0 270 0';
} else {
pos = $flamewall_intro.getOrigin();
ang = $flamewall_intro.getAngles();
}
dir = sys.angToForward( ang ) * MALEDICT_FLAME_WALL_SEPERATION;
sys.wait( 0.5 );
soundEnt = sys.spawn( "maledict_flamewall_sound" );
soundEnt.On();
for( i = 0; i < MALEDICT_FLAME_WALL_NUM; i++ ) {
pos_z += 256;
if ( sys.trace( pos, pos + dir, '-8 -8 0', '8 8 16', MASK_SOLID, self ) < 1 ) {
break;
}
pos = pos + dir;
frac = sys.trace( pos, pos - '0 0 512', '-8 -8 0', '8 8 16', MASK_SOLID, self );
if ( frac == 1 ) {
break;
}
pos_z -= 512 * frac;
pos_z += 2;
soundEnt.setOrigin( pos );
thread spawn_flame_wall( pos );
sys.wait( 0.075 );
}
thread flame_wall_sound_end( soundEnt );
}
/*
=====================
monster_boss_maledict::flame_wall
=====================
*/
void monster_boss_maledict::flame_wall() {
thread flame_wall_loop();
}
/*
=====================
monster_boss_maledict::asteroid_loop
=====================
*/
void monster_boss_maledict::asteroid_loop() {
float i;
float delay;
string fxName;
entity fxEnt;
for( i = 1; i < 15; i++ ) {
fxName = "nrv_func_fx_" + i;
fxEnt = sys.getEntity( fxName );
sys.trigger( fxEnt );
delay = 0.5 + sys.random( 0.3 );
//delay = 0.10 + sys.random( 0.3 );
sys.wait( delay );
}
}
/*
=====================
monster_boss_maledict::asteroid_attack
=====================
*/
void monster_boss_maledict::asteroid_attack() {
thread asteroid_loop();
}