Numerous AI fixes.

This commit is contained in:
Justin Marshall
2026-05-29 15:55:22 -07:00
parent 3bd646d4ed
commit fae1adf30d
10 changed files with 545 additions and 87 deletions
+2 -4
View File
@@ -179,10 +179,8 @@ float rvmAIObject::RandomTime( float seconds ) const {
}
bool rvmAIObject::InfluenceActive() const {
// Some branches keep the old DoomScript influence query only in script/event glue,
// and idPlayer has no public InfluenceActive() accessor. Keep this wrapper compile-safe;
// wire it to your branch's real influence flag here if one exists.
return false;
idPlayer* player = gameLocal.GetLocalPlayer();
return player && player->GetInfluenceLevel();
}
idPlayer* rvmAIObject::Player() const {
+273 -50
View File
@@ -22,6 +22,14 @@ rvmMonsterBase::rvmMonsterBase() {
ignore_lostcombat = false;
blocked = false;
ignore_sight = false;
monsterBeginStartActive = false;
monsterBeginTeleportType = 0;
monsterBeginMoveType = MOVETYPE_ANIM;
spawnerTriggerCount = 0;
monsterBeginActionName.Clear();
ClearParms( monsterBeginActionParms );
pathActionName.Clear();
ClearParms( pathActionParms );
}
void rvmMonsterBase::Init( idAI* aiOwner ) {
@@ -36,6 +44,14 @@ void rvmMonsterBase::Init( idAI* aiOwner ) {
run = false;
blocked = false;
ignore_sight = false;
monsterBeginStartActive = false;
monsterBeginTeleportType = 0;
monsterBeginMoveType = MOVETYPE_ANIM;
spawnerTriggerCount = 0;
monsterBeginActionName.Clear();
ClearParms( monsterBeginActionParms );
pathActionName.Clear();
ClearParms( pathActionParms );
}
stateResult_t rvmMonsterBase::RunStateByName( const char* stateName, stateParms_t* parms ) {
@@ -113,12 +129,14 @@ stateResult_t rvmMonsterBase::Torso_Sight( stateParms_t* parms ) {
return SRESULT_WAIT;
}
FinishAction( "sight" );
SetNeverDormant( GetFloatKey( "neverdormant" ) != 0.0f );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", GetIntKey( "on_activate_blend", "4" ) );
return SRESULT_DONE;
}
case STAGE_WAIT:
if ( AnimDone( ANIMCHANNEL_TORSO, GetIntKey( "on_activate_blend", "4" ) ) ) {
FinishAction( "sight" );
SetNeverDormant( GetFloatKey( "neverdormant" ) != 0.0f );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", GetIntKey( "on_activate_blend", "4" ) );
return SRESULT_DONE;
}
@@ -158,12 +176,16 @@ stateResult_t rvmMonsterBase::state_Spawner( stateParms_t* parms ) {
case STAGE_START:
HideOwner();
ClearActivated();
spawnerTriggerCount = 0;
parms->stage = STAGE_LOOP;
return SRESULT_WAIT;
case STAGE_LOOP:
if ( Activated() && CanBecomeSolid() ) {
if ( Activated() ) {
spawnerTriggerCount++;
ClearActivated();
// Spawn-copy behavior is project-specific; keep this state non-blocking.
}
if ( spawnerTriggerCount > 0 && CanBecomeSolid() && SpawnFromSpawner() ) {
spawnerTriggerCount--;
}
return SRESULT_WAIT;
}
@@ -171,7 +193,18 @@ stateResult_t rvmMonsterBase::state_Spawner( stateParms_t* parms ) {
}
stateResult_t rvmMonsterBase::monster_begin( stateParms_t* parms ) {
enum { STAGE_START, STAGE_HIDDEN_WAIT_TRIGGER, STAGE_HIDDEN_WAIT_SOLID, STAGE_DELAY, STAGE_WAKE, STAGE_SIGHT };
enum {
STAGE_START,
STAGE_WAIT_TRIGGER,
STAGE_WAIT_SOLID,
STAGE_TELEPORT_FX,
STAGE_TRIGGER_ANIM,
STAGE_DELAY,
STAGE_WAKE_SELECT,
STAGE_WAKE_ACTION,
STAGE_ATTACK_PATH,
STAGE_DONE
};
switch ( parms->stage ) {
case STAGE_START:
run = false;
@@ -179,29 +212,104 @@ stateResult_t rvmMonsterBase::monster_begin( stateParms_t* parms ) {
if ( !GetIntKey( "ignore_flashlight" ) ) {
WakeOnFlashlight( true );
}
if ( GetIntKey( "hide" ) ) {
monsterBeginStartActive = false;
monsterBeginMoveType = owner ? owner->GetMoveType() : MOVETYPE_ANIM;
monsterBeginTeleportType = resurrect ? 4 : GetIntKey( "teleport" );
scratchString = resurrect ? "" : GetKey( "trigger_anim" );
if ( resurrect && owner ) {
owner->AI_ACTIVATED = true;
}
if ( GetIntKey( "spawner" ) ) {
SetState( "state_Spawner" );
return SRESULT_DONE;
}
if ( scratchString.Length() || monsterBeginTeleportType > 0 || GetIntKey( "hide" ) ) {
HideOwner();
ClearActivated();
parms->stage = STAGE_HIDDEN_WAIT_TRIGGER;
if ( !resurrect ) {
ClearActivated();
}
parms->stage = STAGE_WAIT_TRIGGER;
return SRESULT_WAIT;
}
scratchEndTime = DelayTime( GetFloatKey( "wait" ) );
parms->stage = STAGE_DELAY;
return SRESULT_WAIT;
case STAGE_WAIT_TRIGGER:
if ( !Activated() ) {
return SRESULT_WAIT;
}
ClearActivated();
parms->stage = STAGE_WAIT_SOLID;
return SRESULT_WAIT;
case STAGE_WAIT_SOLID:
if ( !CanBecomeSolid() ) {
return SRESULT_WAIT;
}
if ( monsterBeginTeleportType > 0 ) {
BecomeSolid();
monsterBeginMoveType = owner ? owner->GetMoveType() : MOVETYPE_ANIM;
SetMoveType( MOVETYPE_STATIC );
SetNeverDormant( true );
trigger_wakeup_targets();
if ( monsterBeginTeleportType == 1 ) {
StartFx( GetKey( "fx_teleport1" ) );
scratchEndTime = DelayTime( 1.6f );
} else if ( monsterBeginTeleportType == 2 ) {
StartFx( GetKey( "fx_teleport2" ) );
scratchEndTime = DelayTime( 2.6f );
} else if ( monsterBeginTeleportType == 3 ) {
StartFx( GetKey( "fx_teleport3" ) );
scratchEndTime = DelayTime( 3.6f );
} else {
StartFx( GetKey( "fx_teleport" ) );
scratchEndTime = DelayTime( 0.6f );
}
parms->stage = STAGE_TELEPORT_FX;
return SRESULT_WAIT;
}
ShowOwner();
if ( scratchString.Length() ) {
SetNeverDormant( true );
trigger_wakeup_targets();
waitActionName = "customAnim";
playCustomAnim( scratchString.c_str(), 0.0f, 4.0f );
parms->stage = STAGE_TRIGGER_ANIM;
return SRESULT_WAIT;
}
if ( ( GetIntKey( "hide" ) == 1 ) || ambush ) {
ClearEnemy();
}
scratchEndTime = DelayTime( GetFloatKey( "wait" ) );
parms->stage = STAGE_DELAY;
return SRESULT_WAIT;
case STAGE_HIDDEN_WAIT_TRIGGER:
if ( !Activated() && !Pain() ) {
return SRESULT_WAIT;
}
ClearActivated();
parms->stage = STAGE_HIDDEN_WAIT_SOLID;
return SRESULT_WAIT;
case STAGE_HIDDEN_WAIT_SOLID:
if ( !CanBecomeSolid() ) {
case STAGE_TELEPORT_FX:
if ( Time() < scratchEndTime ) {
return SRESULT_WAIT;
}
ShowOwner();
waitActionName = "customAnim";
playCustomAnim( "teleport", 0.0f, 4.0f );
parms->stage = STAGE_TRIGGER_ANIM;
return SRESULT_WAIT;
case STAGE_TRIGGER_ANIM:
if ( !ActionDone( "customAnim" ) ) {
return SRESULT_WAIT;
}
SetNeverDormant( GetFloatKey( "neverdormant" ) != 0.0f );
LocateEnemy();
if ( monsterBeginTeleportType > 0 ) {
SetMoveType( monsterBeginMoveType );
}
monsterBeginStartActive = true;
scratchEndTime = DelayTime( GetFloatKey( "wait" ) );
parms->stage = STAGE_DELAY;
return SRESULT_WAIT;
@@ -213,24 +321,59 @@ stateResult_t rvmMonsterBase::monster_begin( stateParms_t* parms ) {
if ( idEntity* enemyEnt = GetEntityKey( "enemy" ) ) {
SetEnemy( enemyEnt );
}
parms->stage = STAGE_WAKE;
return SRESULT_WAIT;
case STAGE_WAKE:
if ( GetEnemy() || Pain() || Activated() || checkForEnemy( idle_sight_fov ) ) {
trigger_wakeup_targets();
ignore_sight = false;
WakeOnFlashlight( false );
waitActionName = "sight";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 );
parms->stage = STAGE_SIGHT;
}
return SRESULT_WAIT;
case STAGE_SIGHT:
if ( !ActionDone( "sight" ) ) {
if ( monsterBeginStartActive ) {
if ( GetIntKey( "attack_path" ) ) {
current_path = owner ? owner->RandomPath() : NULL;
if ( current_path.GetEntity() ) {
run = true;
ignoreEnemies = true;
monsterBeginActionName = "idle_followPathEntities";
ClearParms( monsterBeginActionParms );
parms->stage = STAGE_ATTACK_PATH;
return SRESULT_WAIT;
}
}
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
}
parms->stage = STAGE_WAKE_SELECT;
return SRESULT_WAIT;
case STAGE_WAKE_SELECT:
if ( GetIntKey( "wake_on_attackcone" ) ) {
monsterBeginActionName = "wake_on_attackcone";
} else if ( GetIntKey( "walk_on_trigger" ) ) {
monsterBeginActionName = "walk_on_trigger";
} else if ( GetIntKey( "trigger" ) ) {
monsterBeginActionName = "wake_on_trigger";
} else {
monsterBeginActionName = "wake_on_enemy";
}
ClearParms( monsterBeginActionParms );
parms->stage = STAGE_WAKE_ACTION;
return SRESULT_WAIT;
case STAGE_WAKE_ACTION:
if ( RunStateByName( monsterBeginActionName.c_str(), &monsterBeginActionParms ) == SRESULT_WAIT ) {
return SRESULT_WAIT;
}
monsterBeginActionName.Clear();
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
case STAGE_ATTACK_PATH:
if ( RunStateByName( monsterBeginActionName.c_str(), &monsterBeginActionParms ) == SRESULT_WAIT ) {
return SRESULT_WAIT;
}
monsterBeginActionName.Clear();
ignoreEnemies = false;
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
case STAGE_DONE:
ignore_sight = false;
WakeOnFlashlight( false );
return SRESULT_DONE;
}
return SRESULT_ERROR;
@@ -409,11 +552,19 @@ stateResult_t rvmMonsterBase::walk_on_trigger( stateParms_t* parms ) {
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
StartAction( "sight_enemy" );
FaceEnemy();
if ( GetKey( "on_activate" )[0] ) {
SetNeverDormant( true );
if ( GetIntKey( "walk_on_sight" ) ) {
MoveToEnemy();
}
waitActionName = "sight";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 );
}
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
case STAGE_DONE:
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
return !ActionDone( "sight" ) ? SRESULT_WAIT : SRESULT_DONE;
}
return SRESULT_ERROR;
}
@@ -433,11 +584,19 @@ stateResult_t rvmMonsterBase::wake_on_trigger( stateParms_t* parms ) {
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
StartAction( "sight_enemy" );
FaceEnemy();
if ( GetKey( "on_activate" )[0] ) {
SetNeverDormant( true );
if ( GetIntKey( "walk_on_sight" ) ) {
MoveToEnemy();
}
waitActionName = "sight";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 );
}
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
case STAGE_DONE:
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
return !ActionDone( "sight" ) ? SRESULT_WAIT : SRESULT_DONE;
}
return SRESULT_ERROR;
}
@@ -457,11 +616,19 @@ stateResult_t rvmMonsterBase::wake_on_enemy( stateParms_t* parms ) {
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
StartAction( "sight_enemy" );
FaceEnemy();
if ( GetKey( "on_activate" )[0] ) {
SetNeverDormant( true );
if ( GetIntKey( "walk_on_sight" ) ) {
MoveToEnemy();
}
waitActionName = "sight";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 );
}
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
case STAGE_DONE:
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
return !ActionDone( "sight" ) ? SRESULT_WAIT : SRESULT_DONE;
}
return SRESULT_ERROR;
}
@@ -485,12 +652,20 @@ stateResult_t rvmMonsterBase::wake_on_attackcone( stateParms_t* parms ) {
AllowMovement( true );
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
trigger_wakeup_targets();
StartAction( "sight_enemy" );
FaceEnemy();
if ( GetKey( "on_activate" )[0] ) {
SetNeverDormant( true );
if ( GetIntKey( "walk_on_sight" ) ) {
MoveToEnemy();
}
waitActionName = "sight";
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Sight", 4 );
}
parms->stage = STAGE_DONE;
return SRESULT_WAIT;
}
case STAGE_DONE:
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
return !ActionDone( "sight" ) ? SRESULT_WAIT : SRESULT_DONE;
}
return SRESULT_ERROR;
}
@@ -514,33 +689,35 @@ stateResult_t rvmMonsterBase::idle_followPathEntities( stateParms_t* parms ) {
// Resolve common node class names into action states.
scratchString = current_path.GetEntity()->GetEntityDefName();
if ( !scratchString.Icmp( "path_corner" ) ) {
StartAction( "path_corner" );
pathActionName = "path_corner";
} else if ( !scratchString.Icmp( "path_anim" ) ) {
StartAction( "path_anim" );
pathActionName = "path_anim";
} else if ( !scratchString.Icmp( "path_cycleanim" ) ) {
StartAction( "path_cycleanim" );
pathActionName = "path_cycleanim";
} else if ( !scratchString.Icmp( "path_turn" ) ) {
StartAction( "path_turn" );
pathActionName = "path_turn";
} else if ( !scratchString.Icmp( "path_wait" ) ) {
StartAction( "path_wait" );
pathActionName = "path_wait";
} else if ( !scratchString.Icmp( "path_waitfortrigger" ) ) {
StartAction( "path_waitfortrigger" );
pathActionName = "path_waitfortrigger";
} else if ( !scratchString.Icmp( "path_hide" ) ) {
StartAction( "path_hide" );
pathActionName = "path_hide";
} else if ( !scratchString.Icmp( "path_show" ) ) {
StartAction( "path_show" );
pathActionName = "path_show";
} else if ( !scratchString.Icmp( "path_attack" ) ) {
StartAction( "path_attack" );
pathActionName = "path_attack";
} else {
current_path = next_path;
return SRESULT_WAIT;
}
ClearParms( pathActionParms );
parms->stage = STAGE_NODE_ACTION;
return SRESULT_WAIT;
case STAGE_NODE_ACTION:
if ( InAction() ) {
if ( RunStateByName( pathActionName.c_str(), &pathActionParms ) == SRESULT_WAIT ) {
return SRESULT_WAIT;
}
pathActionName.Clear();
if ( checkForEnemy( true ) ) {
return SRESULT_DONE;
}
@@ -1087,6 +1264,52 @@ void rvmMonsterBase::trigger_wakeup_targets() {
}
}
void rvmMonsterBase::TriggerEntity( idEntity* ent ) {
if ( !ent ) {
return;
}
const idEventDef* activateEvent = ent->FindEventDef( "activate", "e" );
ent->Signal( SIG_TRIGGER );
if ( activateEvent ) {
ent->ProcessEvent( activateEvent, gameLocal.GetLocalPlayer() );
}
ent->TriggerGuis();
}
idEntity* rvmMonsterBase::SpawnFromSpawner() {
if ( !owner ) {
return NULL;
}
const int maxSpawn = GetIntKey( "spawner" );
const char* baseName = owner->GetName();
int slot = 0;
for ( ; slot < maxSpawn; slot++ ) {
if ( !gameLocal.FindEntity( va( "%s%d", baseName, slot ) ) ) {
break;
}
}
if ( slot >= maxSpawn ) {
return NULL;
}
idDict args = owner->spawnArgs;
args.SetInt( "spawner", 0 );
args.Set( "name", va( "%s%d", baseName, slot ) );
const char* spawnTarget = GetKey( "spawn_target" );
if ( spawnTarget[0] ) {
args.Set( "target", spawnTarget );
}
idEntity* ent = NULL;
if ( gameLocal.SpawnEntityDef( args, &ent ) && ent ) {
TriggerEntity( ent );
return ent;
}
return NULL;
}
bool rvmMonsterBase::checkForEnemy( bool use_fov ) {
if ( InfluenceActive() ) {
return false;
+10
View File
@@ -27,6 +27,14 @@ protected:
bool ignore_lostcombat;
bool blocked;
bool ignore_sight;
bool monsterBeginStartActive;
int monsterBeginTeleportType;
int monsterBeginMoveType;
int spawnerTriggerCount;
idStr monsterBeginActionName;
stateParms_t monsterBeginActionParms;
idStr pathActionName;
stateParms_t pathActionParms;
// Animation state machines.
virtual stateResult_t Torso_CustomCycle( stateParms_t* parms );
@@ -89,4 +97,6 @@ protected:
virtual void follow_alternate_path1();
virtual void follow_alternate_path2();
virtual void follow_alternate_path3();
virtual void TriggerEntity( idEntity* ent );
virtual idEntity* SpawnFromSpawner();
};
+25 -1
View File
@@ -200,7 +200,31 @@ stateResult_t rvmMonsterBossGuardian::combat_charge( stateParms_t* parms ) {
nextAttack = DelayTime( GUARDIAN_RANGE_ATTACK_DELAY );
return SRESULT_DONE;
}
stateResult_t rvmMonsterBossGuardian::pound_impact( stateParms_t* parms ) { RadiusDamageFromJoint( rightfoot ? "r_foot" : "l_foot", "damage_guardian_footstep" ); return SRESULT_DONE; }
stateResult_t rvmMonsterBossGuardian::pound_impact( stateParms_t* parms ) {
const char* jointName = rightfoot ? "rhand" : "lhand";
idVec3 org = GetOrigin();
idMat3 axis = mat3_identity;
if ( owner ) {
jointHandle_t joint = owner->GetAnimator()->GetJointHandle( jointName );
if ( joint != INVALID_JOINT ) {
owner->GetJointWorldTransform( joint, gameLocal.time, org, axis );
}
gameLocal.RadiusDamage( org, owner, owner, spawner.GetEntity(), spawner.GetEntity(), GetKey( "def_poundgrounddamage", "damage_guardianPoundGround" ), 1.0f );
const char* entname = GetKey( "def_poundground" );
if ( entname[0] ) {
idEntity* explosion = NULL;
idDict args;
args.Set( "classname", entname );
if ( gameLocal.SpawnEntityDef( args, &explosion ) && explosion ) {
explosion->SetOrigin( org - idVec3( 0.0f, 0.0f, 32.0f ) );
explosion->SetShaderParm( SHADERPARM_TIMEOFFSET, -Time() );
explosion->PostEventSec( &EV_Remove, 0.75f );
}
}
}
return SRESULT_DONE;
}
stateResult_t rvmMonsterBossGuardian::pound_attack( stateParms_t* parms ) { AttackMelee( "damage_guardian_melee" ); return SRESULT_DONE; }
stateResult_t rvmMonsterBossGuardian::pound_attack_left( stateParms_t* parms ) { rightfoot = false; return pound_attack( parms ); }
stateResult_t rvmMonsterBossGuardian::pound_attack_right( stateParms_t* parms ) { rightfoot = true; return pound_attack( parms ); }
+96 -11
View File
@@ -209,12 +209,12 @@ int rvmMonsterBossVagary::check_attacks() {
if ( ( Time() > nextNoFOVAttack && EnemyVisible() ) || EnemyInFOV() ) {
if ( !CanReachEnemy() || currentTime >= nextAttack ) {
if ( CanHitEnemy() ) {
throwEntity = FindActorsInBounds( idVec3( -512, -512, -512 ), idVec3( 512, 512, 512 ) );
throwEntity = ChooseObjectToThrow();
if ( throwEntity.GetEntity() ) { flags |= ATTACK_MISSILE; }
}
}
}
return static_cast<float>( flags );
return flags;
}
bool rvmMonsterBossVagary::do_attack( int attack_flags ) {
@@ -231,16 +231,93 @@ bool rvmMonsterBossVagary::do_attack( int attack_flags ) {
stateResult_t rvmMonsterBossVagary::throw_object( stateParms_t* parms ) {
idEntity* ent = throwEntity.GetEntity();
if ( !ent ) { return SRESULT_DONE; }
// Hook point for the project-specific vagary_ThrowObjectAtEnemy helper.
StartSoundKey( "snd_pickup", SND_CHANNEL_WEAPON, false );
if ( GetEnemy() ) {
idVec3 dir = GetEnemyPos() - ent->GetPhysics()->GetOrigin();
dir.Normalize();
ent->GetPhysics()->SetLinearVelocity( dir * VAGARY_THROW_SPEED2 );
}
ThrowObjectAtEnemy( ent, true );
return SRESULT_DONE;
}
idEntity* rvmMonsterBossVagary::ChooseObjectToThrow() {
if ( !owner ) {
return NULL;
}
idEntity* entityList[ MAX_GENTITIES ];
idBounds checkBounds( VAGARY_THROW_MIN, VAGARY_THROW_MAX );
checkBounds.TranslateSelf( GetOrigin() );
const int numListedEntities = gameLocal.clip.EntitiesTouchingBounds( checkBounds, -1, entityList, MAX_GENTITIES );
if ( numListedEntities <= 0 ) {
return NULL;
}
idActor* enemyEnt = GetEnemy();
const idVec3 enemyEyePos = enemyEnt ? owner->lastVisibleEnemyPos + owner->lastVisibleEnemyEyeOffset : GetEnemyPos();
const idBounds& myBounds = owner->GetPhysics()->GetAbsBounds();
const int startIndex = gameLocal.random.RandomInt( numListedEntities );
for ( int i = 0; i < numListedEntities; i++ ) {
int index = startIndex + i;
if ( index >= numListedEntities ) {
index -= numListedEntities;
}
idEntity* ent = entityList[index];
if ( !ent || !ent->IsType( idMoveable::Type ) || ent->fl.hidden ) {
continue;
}
idPhysics* entPhys = ent->GetPhysics();
const idVec3& entOrg = entPhys->GetOrigin();
if ( ( entOrg - enemyEyePos ).LengthFast() < VAGARY_THROW_DIST ) {
continue;
}
idBounds expandedBounds = myBounds.Expand( entPhys->GetBounds().GetRadius() );
if ( expandedBounds.LineIntersection( entOrg, enemyEyePos ) ) {
continue;
}
idVec3 vel;
if ( idAI::PredictTrajectory( entOrg + idVec3( 0.0f, 0.0f, VAGARY_THROW_OFFSET ), enemyEyePos, VAGARY_THROW_SPEED, entPhys->GetGravity(),
entPhys->GetClipModel(), entPhys->GetClipMask(), MAX_WORLD_SIZE, NULL, enemyEnt, ai_debugTrajectory.GetBool() ? 4000 : 0, vel ) ) {
return ent;
}
}
return NULL;
}
void rvmMonsterBossVagary::ThrowObjectAtEnemy( idEntity* ent, bool delayedThrow ) {
if ( !ent ) {
return;
}
StartSoundKey( "snd_pickup", SND_CHANNEL_WEAPON, false );
idPhysics* entPhys = ent->GetPhysics();
entPhys->SetAngularVelocity( idVec3( 30.0f, 30.0f, 0.0f ) );
idVec3 throwVelocity = owner ? owner->GetPhysics()->GetAxis()[0] * VAGARY_THROW_SPEED2 : idVec3( VAGARY_THROW_SPEED2, 0.0f, 0.0f );
idActor* enemyEnt = GetEnemy();
if ( enemyEnt && owner ) {
idVec3 aimDir;
if ( idAI::PredictTrajectory( entPhys->GetOrigin(), owner->lastVisibleEnemyPos + owner->lastVisibleEnemyEyeOffset, VAGARY_THROW_SPEED2,
entPhys->GetGravity(), entPhys->GetClipModel(), entPhys->GetClipMask(), MAX_WORLD_SIZE, NULL, enemyEnt,
ai_debugTrajectory.GetBool() ? 4000 : 0, aimDir ) ) {
throwVelocity = aimDir * VAGARY_THROW_SPEED2;
}
}
if ( delayedThrow ) {
const idVec3 hoverPos = entPhys->GetOrigin() + idVec3( 0.0f, 0.0f, VAGARY_THROW_OFFSET );
entPhys->SetLinearVelocity( ( hoverPos - entPhys->GetOrigin() ) * 5.0f );
ent->PostEventSec( &EV_SetLinearVelocity, 0.5f, throwVelocity );
} else {
entPhys->SetLinearVelocity( throwVelocity );
}
if ( ent->IsType( idMoveable::Type ) ) {
static_cast<idMoveable*>( ent )->EnableDamage( true, 2.5f );
}
}
stateResult_t rvmMonsterBossVagary::combat_range( stateParms_t* parms ) {
enum { STAGE_FACE, STAGE_THROW, STAGE_COOLDOWN };
switch ( parms->stage ) {
@@ -252,12 +329,20 @@ stateResult_t rvmMonsterBossVagary::combat_range( stateParms_t* parms ) {
return SRESULT_WAIT;
case STAGE_THROW:
if ( Time() < scratchEndTime ) { return SRESULT_WAIT; }
StartAction( "throw_object" );
ThrowObjectAtEnemy( throwEntity.GetEntity(), true );
scratchCount = RandomFloat( 4.0f );
for ( int i = 1; i < idMath::Ftoi( scratchCount ); i++ ) {
idEntity* ent = ChooseObjectToThrow();
if ( !ent ) {
break;
}
ThrowObjectAtEnemy( ent, true );
}
scratchEndTime = DelayTime( 1.0f );
parms->stage = STAGE_COOLDOWN;
return SRESULT_WAIT;
case STAGE_COOLDOWN:
if ( InAction() || Time() < scratchEndTime ) { return SRESULT_WAIT; }
if ( Time() < scratchEndTime ) { return SRESULT_WAIT; }
LookAtEnemy( 1.0f );
nextAttack = DelayTime( VAGARY_ATTACK_RATE );
nextNoFOVAttack = Time() + VAGARY_NOFOVTIME;
@@ -22,6 +22,12 @@
#ifndef VAGARY_THROW_SPEED2
#define VAGARY_THROW_SPEED2 1500
#endif
#ifndef VAGARY_THROW_MIN
#define VAGARY_THROW_MIN idVec3( -512.0f, -512.0f, -512.0f )
#endif
#ifndef VAGARY_THROW_MAX
#define VAGARY_THROW_MAX idVec3( 512.0f, 512.0f, 512.0f )
#endif
#ifndef VAGARY_THROW_DIST
#define VAGARY_THROW_DIST 200
#endif
@@ -105,5 +111,7 @@
virtual stateResult_t combat_dodge_right( stateParms_t* parms );
virtual int check_attacks() override;
virtual bool do_attack( int attack_flags ) override;
virtual idEntity* ChooseObjectToThrow();
virtual void ThrowObjectAtEnemy( idEntity* ent, bool delayedThrow );
};
+76 -16
View File
@@ -6,6 +6,44 @@
#include "../Game_local.h"
static void PostEntityEventByName( idEntity* ent, const char* eventName, float delaySeconds ) {
if ( !ent ) {
return;
}
const idEventDef* eventDef = ent->FindEventDef( eventName, NULL );
if ( !eventDef ) {
return;
}
if ( delaySeconds <= 0.0f ) {
ent->ProcessEvent( eventDef );
} else {
ent->PostEventSec( eventDef, delaySeconds );
}
}
static idEntity* SpawnArchvileTimedTrigger( const char* classname, const idVec3& origin, const idAngles& angles, idAI* owner, float enableDelay, float activeTime, float removeDelay ) {
idEntity* ent = NULL;
idDict args;
args.Set( "classname", classname );
args.Set( "cinematic_remove", "1" );
if ( !gameLocal.SpawnEntityDef( args, &ent ) || !ent ) {
return NULL;
}
ent->SetOrigin( origin );
ent->SetAngles( angles );
ent->SetShaderParm( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.time ) );
ent->SetShaderParm( SHADERPARM_DIVERSITY, gameLocal.random.RandomFloat() );
if ( owner ) {
ent->ProcessEvent( &EV_SetOwner, owner );
}
PostEntityEventByName( ent, "disable", 0.0f );
PostEntityEventByName( ent, "enable", enableDelay );
PostEntityEventByName( ent, "disable", enableDelay + activeTime );
ent->PostEventSec( &EV_Remove, removeDelay );
return ent;
}
rvmMonsterDemonArchvile::rvmMonsterDemonArchvile() {
nextDodge = 0.0f;
@@ -412,47 +450,69 @@ stateResult_t rvmMonsterDemonArchvile::combat_dodge_right( stateParms_t* parms )
}
stateResult_t rvmMonsterDemonArchvile::spawn_flame_wall( stateParms_t* parms ) {
// Hook point for the original script's repeated sys.spawn of flame-wall projectiles.
// The looping behavior is represented by flame_wall_loop() below, not a blocking wait.
idAngles angles = owner ? owner->GetPhysics()->GetAxis().ToAngles() : ang_zero;
angles.yaw = RandomFloat( 360.0f );
SpawnArchvileTimedTrigger( "archvile_flamewall", flameWallOrigin, angles, owner, 0.2f, 0.5f, 2.7f );
return SRESULT_DONE;
}
stateResult_t rvmMonsterDemonArchvile::spawn_incerate( stateParms_t* parms ) {
// Hook point for the original script's incinerate projectile spawn.
idAngles angles = owner ? owner->GetPhysics()->GetAxis().ToAngles() : ang_zero;
angles.yaw = RandomFloat( 360.0f );
SpawnArchvileTimedTrigger( "archvile_incinerate", incinerateOrigin, angles, owner, 0.4f, 0.4f, 1.4f );
return SRESULT_DONE;
}
stateResult_t rvmMonsterDemonArchvile::flame_wall_loop( stateParms_t* parms ) {
enum { STAGE_INIT, STAGE_SPAWN, STAGE_DELAY };
enum { STAGE_INIT, STAGE_TRACE, STAGE_DELAY };
switch ( parms->stage ) {
case STAGE_INIT:
scratchCount = 0;
parms->stage = STAGE_SPAWN;
flameWallOrigin = GetOrigin();
scratchEntity = NULL;
parms->stage = STAGE_TRACE;
return SRESULT_WAIT;
case STAGE_SPAWN:
if ( scratchCount >= ARCH_FLAME_WALL_NUM ) { return SRESULT_DONE; }
flameWallOrigin = GetEnemyPos();
StartAction( "spawn_flame_wall" );
case STAGE_TRACE: {
if ( scratchCount >= ARCH_FLAME_WALL_NUM || !owner ) { return SRESULT_DONE; }
idAngles angles = owner->GetPhysics()->GetAxis().ToAngles();
idVec3 dir = angles.ToForward() * ARCH_FLAME_WALL_SEPERATION;
idVec3 pos = flameWallOrigin;
const idBounds bounds( idVec3( -8.0f, -8.0f, 0.0f ), idVec3( 8.0f, 8.0f, 16.0f ) );
trace_t trace;
pos.z += 48.0f;
gameLocal.clip.TraceBounds( trace, pos, pos + dir, bounds, MASK_SOLID, owner );
if ( trace.fraction < 1.0f ) {
return SRESULT_DONE;
}
pos += dir;
gameLocal.clip.TraceBounds( trace, pos, pos - idVec3( 0.0f, 0.0f, 96.0f ), bounds, MASK_SOLID, owner );
if ( trace.fraction >= 1.0f ) {
return SRESULT_DONE;
}
pos.z -= 96.0f * trace.fraction;
flameWallOrigin = pos;
spawn_flame_wall( parms );
scratchCount++;
scratchEndTime = DelayTime( 0.05f );
scratchEndTime = DelayTime( 0.075f );
parms->stage = STAGE_DELAY;
return SRESULT_WAIT;
}
case STAGE_DELAY:
if ( InAction() || Time() < scratchEndTime ) { return SRESULT_WAIT; }
parms->stage = STAGE_SPAWN;
if ( Time() < scratchEndTime ) { return SRESULT_WAIT; }
parms->stage = STAGE_TRACE;
return SRESULT_WAIT;
}
return SRESULT_ERROR;
}
stateResult_t rvmMonsterDemonArchvile::flame_wall( stateParms_t* parms ) {
if ( parms->stage == 0 ) { StartAction( "flame_wall_loop" ); parms->stage = 1; return SRESULT_WAIT; }
return InAction() ? SRESULT_WAIT : SRESULT_DONE;
return flame_wall_loop( parms );
}
stateResult_t rvmMonsterDemonArchvile::incinerate( stateParms_t* parms ) {
incinerateOrigin = GetEnemyPos();
StartAction( "spawn_incerate" );
incinerateOrigin = PredictEnemyPos( 0.4f );
spawn_incerate( parms );
return SRESULT_DONE;
}
+14 -1
View File
@@ -414,7 +414,20 @@ stateResult_t rvmMonsterDemonTrite::state_Explode( stateParms_t* parms ) {
enum { STAGE_START, STAGE_REMOVE };
switch ( parms->stage ) {
case STAGE_START:
RadiusDamageFromJoint( "", GetKey( "def_explode_damage", "damage_trite_explode" ) );
if ( owner ) {
idEntity* explosion = NULL;
idDict args;
args.Set( "classname", "func_emitter" );
args.Set( "model", GetKey( "model_explode" ) );
if ( gameLocal.SpawnEntityDef( args, &explosion ) && explosion ) {
explosion->Show();
explosion->SetOrigin( GetOrigin() );
explosion->SetShaderParm( SHADERPARM_TIMEOFFSET, -Time() );
explosion->SetShaderParm( SHADERPARM_DIVERSITY, RandomFloat( 1.0f ) );
explosion->PostEventSec( &EV_Remove, 5.0f );
}
gameLocal.RadiusDamage( GetOrigin(), owner, owner, owner, owner, GetKey( "def_explode_damage", "damage_trite_explode" ), 1.0f );
}
StartSoundKey( "snd_explode", SND_CHANNEL_BODY, false );
if ( owner ) { owner->ActivateTargets( owner ); }
HideOwner();
+40 -4
View File
@@ -23,6 +23,14 @@ void rvmMonsterDemonWraith::Init( idAI* aiOwner ) {
SetState( "state_Begin" );
}
void rvmMonsterDemonWraith::Destroy() {
if ( spawn_effect.GetEntity() ) {
spawn_effect.GetEntity()->PostEventMS( &EV_Remove, 0 );
spawn_effect = NULL;
}
rvmMonsterBase::Destroy();
}
stateResult_t rvmMonsterDemonWraith::RunStateByName( const char* stateName, stateParms_t* parms ) {
AI_DISPATCH_STATE( "Torso_Idle", Torso_Idle );
AI_DISPATCH_STATE( "Torso_Pain", Torso_Pain );
@@ -249,8 +257,22 @@ stateResult_t rvmMonsterDemonWraith::combat_dodge_right( stateParms_t* parms ) {
}
stateResult_t rvmMonsterDemonWraith::spawn_fx( stateParms_t* parms ) {
// Project-specific effect spawning is intentionally isolated here.
// Bind your wraith_spawneffect entity to spawn_effect if desired.
if ( !owner ) {
return SRESULT_DONE;
}
if ( !spawn_effect.GetEntity() ) {
idDict args;
args.Set( "classname", "wraith_spawneffect" );
idEntity* ent = NULL;
if ( gameLocal.SpawnEntityDef( args, &ent ) ) {
spawn_effect = ent;
}
}
if ( spawn_effect.GetEntity() ) {
spawn_effect.GetEntity()->SetOrigin( GetOrigin() );
spawn_effect.GetEntity()->Show();
spawn_effect.GetEntity()->SetShaderParm( SHADERPARM_TIMEOFFSET, -Time() );
}
return SRESULT_DONE;
}
@@ -270,13 +292,20 @@ stateResult_t rvmMonsterDemonWraith::combat_become_visible( stateParms_t* parms
AllowMovement( true );
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
StartSoundKey( "snd_fade_in", SND_CHANNEL_VOICE2, false );
ShowOwner();
scratchEndTime = DelayTime( 0.75f );
spawn_fx( parms );
scratchEndTime = DelayTime( 0.65f );
parms->stage = STAGE_FADE;
return SRESULT_WAIT;
case STAGE_FADE:
if ( Time() < scratchEndTime ) { return SRESULT_WAIT; }
ShowOwner();
if ( owner ) {
owner->SetShaderParm( SHADERPARM_TIME_OF_DEATH, Time() );
}
ClearBurn();
if ( spawn_effect.GetEntity() ) {
spawn_effect.GetEntity()->Hide();
}
invisible = false;
nextInvisible = RandomDelay( WRAITH_INVISIBLE_DELAY_MIN, WRAITH_INVISIBLE_DELAY_MAX );
return SRESULT_DONE;
@@ -292,6 +321,7 @@ stateResult_t rvmMonsterDemonWraith::combat_become_invisible( stateParms_t* parm
if ( invisible ) { return SRESULT_DONE; }
invisible = true;
StartSoundKey( "snd_fade_out", SND_CHANNEL_VOICE2, false );
spawn_fx( parms );
scratchEndTime = DelayTime( 0.817f );
parms->stage = STAGE_FADEOUT;
return SRESULT_WAIT;
@@ -299,7 +329,13 @@ stateResult_t rvmMonsterDemonWraith::combat_become_invisible( stateParms_t* parm
if ( Time() < scratchEndTime ) { return SRESULT_WAIT; }
PreBurn();
HideOwner();
if ( spawn_effect.GetEntity() ) {
spawn_effect.GetEntity()->Hide();
}
if ( owner ) { owner->AllowHiddenMovement( 1 ); }
if ( owner ) {
owner->SetShaderParm( SHADERPARM_TIME_OF_DEATH, Time() - 100.0f );
}
nextInvisible = RandomDelay( WRAITH_VISIBLE_DELAY_MIN, WRAITH_VISIBLE_DELAY_MAX );
return SRESULT_DONE;
}
@@ -95,6 +95,7 @@
rvmMonsterDemonWraith();
virtual void Init( idAI* aiOwner ) override;
virtual void Destroy() override;
virtual stateResult_t RunStateByName( const char* stateName, stateParms_t* parms ) override;
protected: