mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2025-03-15 23:21:35 +00:00
444 lines
9.7 KiB
Text
444 lines
9.7 KiB
Text
/***********************************************************************
|
|
|
|
ai_monster_boss_guardian_seeker.script
|
|
|
|
monster_boss_guardian_seeker
|
|
|
|
***********************************************************************/
|
|
|
|
#define SEEKER_TOOCLOSE_RANGE 200
|
|
#define SEEKER_TOOFAR_RANGE 650
|
|
#define SEEKER_IDEAL_RANGE 430
|
|
#define SEEKER_MAXIMUM_RETREAT_RANGE ( SEEKER_TOOFAR_RANGE + 200 )
|
|
|
|
#define SEEKER_RUNTIME 2
|
|
#define SEEKER_GETCLOSER_DELAY 3
|
|
#define SEEKER_GETCLOSER_TIME 1
|
|
#define SEEKER_SIGHTTIME 2
|
|
#define SEEKER_ESCAPE_SPEED 800
|
|
|
|
object monster_boss_guardian_seeker : seeker_base {
|
|
//
|
|
// States
|
|
//
|
|
void state_Inactive();
|
|
void state_Idle();
|
|
void state_WatchPlayer();
|
|
void state_GetCloser();
|
|
void state_FindPlayer();
|
|
void state_Retreat();
|
|
void state_RunAway();
|
|
|
|
void init();
|
|
|
|
// Torso anim states
|
|
void Torso_Idle();
|
|
void Torso_Fly();
|
|
void Torso_Pain();
|
|
void Torso_MeleeAttack();
|
|
|
|
monster_boss_guardian guardian;
|
|
entity light1;
|
|
entity light2;
|
|
entity light_beam;
|
|
float last_enemy_sight;
|
|
float fly_speed;
|
|
};
|
|
|
|
void monster_boss_guardian_seeker::Torso_Idle() {
|
|
playCycle( ANIMCHANNEL_TORSO, "idle" );
|
|
|
|
eachFrame {
|
|
if ( AI_PAIN ) { animState( ANIMCHANNEL_TORSO, "Torso_Pain", 0 ); }
|
|
if ( AI_FORWARD ) { animState( ANIMCHANNEL_TORSO, "Torso_Fly", 4 ); }
|
|
}
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::Torso_Fly() {
|
|
playCycle( ANIMCHANNEL_TORSO, "fly" );
|
|
|
|
eachFrame {
|
|
if ( AI_PAIN ) { animState( ANIMCHANNEL_TORSO, "Torso_Pain", 0 ); }
|
|
if ( !AI_FORWARD ) { animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); }
|
|
}
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::Torso_Pain() {
|
|
string animname;
|
|
float nextpain;
|
|
float currenttime;
|
|
|
|
animname = getPainAnim();
|
|
playAnim( ANIMCHANNEL_TORSO, animname );
|
|
|
|
guardian.AI_PAIN = true;
|
|
|
|
nextpain = sys.getTime() + 0.25;
|
|
|
|
while( !animDone( ANIMCHANNEL_TORSO, 2 ) ) {
|
|
if ( AI_PAIN ) {
|
|
currenttime = sys.getTime();
|
|
if ( currenttime > nextpain ) {
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Pain", 0 );
|
|
}
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
finishAction( "pain" );
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 2 );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::Torso_MeleeAttack() {
|
|
playAnim( ANIMCHANNEL_TORSO, "melee_attack" );
|
|
|
|
while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) {
|
|
waitFrame();
|
|
}
|
|
|
|
finishAction( "melee_attack" );
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::Torso_Death() {
|
|
waitUntil( 0 );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::see_enemy() {
|
|
if ( last_enemy_sight < sys.getTime() ) {
|
|
startSound( "snd_sight", SND_CHANNEL_VOICE, false );
|
|
}
|
|
last_enemy_sight = sys.getTime() + SEEKER_SIGHTTIME;
|
|
guardian.sightEnemy();
|
|
//light1.setColor( 1, 0.8, 0.4 );
|
|
//light2.setColor( 1, 0.8, 0.4 );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::lost_enemy() {
|
|
light1.setColor( 0.5, 0.2, 0.1 );
|
|
light2.setColor( 0.5, 0.2, 0.1 );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::init() {
|
|
string name;
|
|
entity ent;
|
|
string classname;
|
|
|
|
ent = getTarget( 0 );
|
|
if ( ent ) {
|
|
guardian = getTarget( 0 );
|
|
if ( !guardian ) {
|
|
sys.error( "Entity '" + ent.getName() + "' targeted by '" + getName() + "' is not monster_boss_guardian" );
|
|
}
|
|
guardian.addSeeker( self );
|
|
}
|
|
|
|
fly_speed = getFloatKey( "fly_speed" );
|
|
|
|
// point light
|
|
//sys.setSpawnArg( "name", getName() + "_light1" );
|
|
//classname = getKey( "def_light1" );
|
|
//light1 = sys.spawn( classname );
|
|
//light1.bindToJoint( self, "head", true );
|
|
//light1.setOrigin( '0 0 30' );
|
|
|
|
// spot light
|
|
//float distance = 3096;
|
|
//sys.setSpawnArg( "light_target", "0 1 0" );
|
|
//sys.setSpawnArg( "light_up", "0 0 0.5" );
|
|
//sys.setSpawnArg( "light_right", "-0.5 0 0" );
|
|
//sys.setSpawnArg( "light_end", "0 " + distance + " 0" );
|
|
//sys.setSpawnArg( "name", getName() + "_light2" );
|
|
//classname = getKey( "def_light2" );
|
|
//light2 = sys.spawn( classname );
|
|
//light2.bindToJoint( self, "head", true );
|
|
//light2.setOrigin( '0 0 30' );
|
|
|
|
// light beam
|
|
sys.setSpawnArg( "name", getName() + "_lightbeam" );
|
|
sys.setSpawnArg( "model", getKey( "model_lightbeam" ) );
|
|
sys.setSpawnArg( "noclipmodel", "1" );
|
|
classname = getKey( "def_light2" );
|
|
light_beam = sys.spawn( "func_static" );
|
|
light_beam.bindToJoint( self, "head", true );
|
|
light_beam.setOrigin( '0 0 30' );
|
|
light_beam.setAngles( '0 0 -90' );
|
|
|
|
last_enemy_sight = 0;
|
|
lost_enemy();
|
|
|
|
setMoveType( MOVETYPE_FLY );
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 );
|
|
|
|
if ( guardian ) {
|
|
setState( "state_Inactive" );
|
|
} else {
|
|
setState( "state_Idle" );
|
|
}
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::destory() {
|
|
light_beam.remove();
|
|
//light1.remove();
|
|
//light2.remove();
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::respawnSeeker( entity enemy ) {
|
|
setEnemy( enemy );
|
|
setState( "state_Respawn" );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_Respawn() {
|
|
float health;
|
|
vector ang;
|
|
vector dir;
|
|
|
|
ang_y = sys.random( 360 );
|
|
dir = sys.angToForward( ang );
|
|
setAngles( ang );
|
|
|
|
setOrigin( guardian.spawner.getWorldOrigin() - '0 0 48' + dir * 82 );
|
|
waitUntil( canBecomeSolid() );
|
|
clearBurn();
|
|
show();
|
|
health = getFloatKey( "health" );
|
|
setHealth( health );
|
|
setMoveType( MOVETYPE_FLY );
|
|
setEnemy( guardian.getEnemy() );
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 );
|
|
|
|
moveToPosition( getOrigin() + dir * 512 );
|
|
|
|
// invulnerable for a bit so player can't just keep firing rockets at spawn location
|
|
ignoreDamage();
|
|
|
|
waitFrame();
|
|
|
|
light_beam.show();
|
|
//light1.On();
|
|
//light2.On();
|
|
|
|
sys.wait( 2 );
|
|
allowDamage();
|
|
setState( "state_Idle" );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_Inactive() {
|
|
AI_DEAD = true;
|
|
light_beam.hide();
|
|
//light1.Off();
|
|
//light2.Off();
|
|
hide();
|
|
while( 1 ) {
|
|
waitFrame();
|
|
}
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_Idle() {
|
|
wait_for_enemy();
|
|
guardian.setEnemy( getEnemy() );
|
|
setState( "state_FindPlayer" );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_Killed() {
|
|
guardian.AI_PAIN = true;
|
|
light_beam.hide();
|
|
//light1.Off();
|
|
//light2.Off();
|
|
stopMove();
|
|
animState( ANIMCHANNEL_TORSO, "Torso_Death", 0 );
|
|
burn();
|
|
startSound( "snd_burn", SND_CHANNEL_BODY, false );
|
|
sys.wait( 4 );
|
|
hide();
|
|
clearBurn();
|
|
|
|
if ( !guardian ) {
|
|
remove();
|
|
}
|
|
|
|
waitUntil( 0 );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_WatchPlayer() {
|
|
float getCloserTime;
|
|
float range;
|
|
|
|
getCloserTime = sys.getTime() + SEEKER_GETCLOSER_DELAY;
|
|
eachFrame {
|
|
faceEnemy();
|
|
moveToEnemyHeight();
|
|
if ( AI_ENEMY_DEAD ) {
|
|
enemy_dead();
|
|
continue;
|
|
}
|
|
|
|
if ( AI_DAMAGE ) {
|
|
setState( "state_RunAway" );
|
|
}
|
|
|
|
if ( !AI_ENEMY_VISIBLE ) {
|
|
lost_enemy();
|
|
setState( "state_FindPlayer" );
|
|
}
|
|
|
|
range = enemyRange2D();
|
|
if ( range < SEEKER_TOOCLOSE_RANGE ) {
|
|
lost_enemy();
|
|
setState( "state_Retreat" );
|
|
}
|
|
|
|
if ( range > SEEKER_TOOFAR_RANGE ) {
|
|
if ( sys.getTime() > getCloserTime ) {
|
|
setState( "state_GetCloser" );
|
|
}
|
|
}
|
|
|
|
if ( AI_ENEMY_IN_FOV ) {
|
|
see_enemy();
|
|
}
|
|
}
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_GetCloser() {
|
|
float getCloserTime;
|
|
float range;
|
|
|
|
setFlySpeed( fly_speed );
|
|
getCloserTime = sys.getTime() + SEEKER_GETCLOSER_TIME;
|
|
moveToEnemy();
|
|
|
|
while( !AI_ENEMY_DEAD ) {
|
|
if ( AI_MOVE_DONE && !enemyPositionValid() ) {
|
|
// Cheat
|
|
locateEnemy();
|
|
moveToEnemy();
|
|
}
|
|
|
|
if ( !AI_ENEMY_VISIBLE ) {
|
|
lost_enemy();
|
|
setState( "state_FindPlayer" );
|
|
}
|
|
if ( AI_DAMAGE ) {
|
|
setState( "state_RunAway" );
|
|
}
|
|
range = enemyRange2D() - sys.vecLength( getLinearVelocity() ) * 0.25;
|
|
if ( range < SEEKER_TOOCLOSE_RANGE ) {
|
|
lost_enemy();
|
|
setState( "state_Retreat" );
|
|
}
|
|
|
|
if ( range < SEEKER_IDEAL_RANGE ) {
|
|
setState( "state_WatchPlayer" );
|
|
}
|
|
|
|
if ( AI_ENEMY_IN_FOV ) {
|
|
see_enemy();
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
setState( "state_WatchPlayer" );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_FindPlayer() {
|
|
float visTime;
|
|
boolean visible;
|
|
|
|
visible = false;
|
|
moveToEnemy();
|
|
while( !AI_ENEMY_DEAD ) {
|
|
if ( AI_MOVE_DONE && !enemyPositionValid() ) {
|
|
// Cheat
|
|
locateEnemy();
|
|
moveToEnemy();
|
|
}
|
|
if ( AI_DAMAGE ) {
|
|
setState( "state_RunAway" );
|
|
}
|
|
// don't spot the player instantly
|
|
if ( AI_ENEMY_VISIBLE ) {
|
|
if ( !visible ) {
|
|
visTime = sys.getTime() + 0.5;
|
|
visible = true;
|
|
} else if ( sys.getTime() > visTime ) {
|
|
break;
|
|
}
|
|
} else {
|
|
visible = false;
|
|
}
|
|
|
|
waitFrame();
|
|
}
|
|
|
|
setFlySpeed( fly_speed );
|
|
|
|
setState( "state_WatchPlayer" );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_Retreat() {
|
|
float run_time;
|
|
|
|
setFlySpeed( SEEKER_ESCAPE_SPEED );
|
|
|
|
run_time = sys.getTime() + SEEKER_RUNTIME;
|
|
moveOutOfRange( getEnemy(), enemyRange2D() + 200 );
|
|
while( !AI_ENEMY_DEAD && AI_ENEMY_VISIBLE ) {
|
|
if ( AI_MOVE_DONE ) {
|
|
// can't find a place to run to so just move randomly
|
|
wander();
|
|
}
|
|
if ( AI_DAMAGE ) {
|
|
setState( "state_RunAway" );
|
|
}
|
|
if ( AI_ENEMY_IN_FOV ) {
|
|
see_enemy();
|
|
}
|
|
if ( ( sys.getTime() > run_time ) || ( ( enemyRange2D() + sys.vecLength( getLinearVelocity() ) * 0.5 ) > SEEKER_MAXIMUM_RETREAT_RANGE ) ) {
|
|
break;
|
|
}
|
|
waitFrame();
|
|
}
|
|
setState( "state_WatchPlayer" );
|
|
}
|
|
|
|
void monster_boss_guardian_seeker::state_RunAway() {
|
|
float run_time;
|
|
float lastSightTime;
|
|
|
|
setFlySpeed( SEEKER_ESCAPE_SPEED );
|
|
|
|
AI_DAMAGE = false;
|
|
sys.wait( 0.5 );
|
|
moveOutOfRange( getEnemy(), enemyRange2D() + 200 );
|
|
run_time = sys.getTime() + SEEKER_RUNTIME;
|
|
lastSightTime = sys.getTime();
|
|
while( !AI_ENEMY_DEAD ) {
|
|
if ( AI_MOVE_DONE ) {
|
|
// can't find a place to run to so just move randomly
|
|
wander();
|
|
}
|
|
// run away until we haven't seen the player for a bit
|
|
if ( AI_ENEMY_VISIBLE ) {
|
|
lastSightTime = sys.getTime();
|
|
if ( AI_ENEMY_IN_FOV ) {
|
|
see_enemy();
|
|
}
|
|
} else if ( sys.getTime() > lastSightTime + 0.5 ) {
|
|
break;
|
|
}
|
|
if ( AI_DAMAGE ) {
|
|
run_time = sys.getTime() + SEEKER_RUNTIME;
|
|
AI_DAMAGE = false;
|
|
}
|
|
if ( sys.getTime() > run_time ) {
|
|
if ( ( enemyRange2D() + sys.vecLength( getLinearVelocity() ) * 0.5 ) > SEEKER_MAXIMUM_RETREAT_RANGE ) {
|
|
break;
|
|
}
|
|
}
|
|
waitFrame();
|
|
}
|
|
|
|
setFlySpeed( fly_speed );
|
|
setState( "state_WatchPlayer" );
|
|
}
|