aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_suicide_spectres.gnut
diff options
context:
space:
mode:
authorBobTheBob <32057864+BobTheBob9@users.noreply.github.com>2021-08-31 23:14:58 +0100
committerBobTheBob <32057864+BobTheBob9@users.noreply.github.com>2021-08-31 23:14:58 +0100
commit9a96d0bff56f1969c68bb52a2f33296095bdc67d (patch)
tree4175928e488632705692e3cccafa1a38dd854615 /Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_suicide_spectres.gnut
parent27bd240871b7c0f2f49fef137718b2e3c208e3b4 (diff)
downloadNorthstarMods-9a96d0bff56f1969c68bb52a2f33296095bdc67d.tar.gz
NorthstarMods-9a96d0bff56f1969c68bb52a2f33296095bdc67d.zip
move to new mod format
Diffstat (limited to 'Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_suicide_spectres.gnut')
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_suicide_spectres.gnut576
1 files changed, 576 insertions, 0 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_suicide_spectres.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_suicide_spectres.gnut
new file mode 100644
index 000000000..f8e0652ce
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_suicide_spectres.gnut
@@ -0,0 +1,576 @@
+global function SuicideSpectres_Init
+global function MakeSuicideSpectre
+global function SpectreSuicideOnDamaged
+global function GetNPCAttackerEnt
+
+const FX_SPECTRE_EXPLOSION = $"P_drone_frag_exp"
+
+//
+// Suicide spectre script
+//
+
+const SPECTRE_EXPLOSION_DELAY = 0.25 // Delay for the first spectre in a chain to start exploding.
+const SPECTRE_DAMAGE_MULTIPLIER_BODY = 1.5
+const SPECTRE_DAMAGE_MULTIPLIER_HEAD = 6.0
+const SPECTRE_DAMAGE_MULTIPLIER_SMART_PISTOL = 2.0
+const SPECTRE_HEADSHOT_KEEP_WALKING_CHANCE = 100 // 35% chance to keep walking after a headshot to add variety
+
+struct
+{
+ int chainExplosionIndex
+ float lastChainExplosionTime
+
+ table< string, array<string> > spectreAnims
+ float nextOverloadTime
+
+} file
+
+const SFX_TICK_OVERLOAD = "corporate_spectre_overload_beep"
+const SFX_TICK_EXPLODE = "corporate_spectre_death_explode"
+
+const SFX_FRAGDRONE_OVERLOAD = "weapon_sentryfragdrone_preexplo"
+const SFX_FRAGDRONE_EXPLODE = "weapon_sentryfragdrone_explo"
+const SFX_FRAGDRONE_SUPERPURSUIT = "weapon_sentryfragdrone_superpursuit"
+
+const CHAIN_EXPLOSION_MAXINDEX = 10
+
+
+void function SuicideSpectres_Init()
+{
+ RegisterSignal( "SuicideSpectreForceExplode" )
+ RegisterSignal( "SuicideSpectreExploding" )
+ RegisterSignal( "SuicideGotEnemy" )
+ RegisterSignal( "SuicideLostEnemy" )
+
+ PrecacheParticleSystem( FX_SPECTRE_EXPLOSION )
+
+ file.spectreAnims[ "spectreSearch" ] <- []
+ file.spectreAnims[ "spectreSearch" ].append( "sp_suicide_spectre_search" )
+ file.spectreAnims[ "spectreSearch" ].append( "sp_suicide_spectre_search_B" )
+ file.spectreAnims[ "spectreSearch" ].append( "sp_suicide_spectre_search_C" )
+
+ AddDamageCallback( "npc_frag_drone", SpectreSuicideOnDamaged_Callback )
+ AddDeathCallback( "npc_frag_drone", FragDroneDeath )
+}
+
+/************************************************************************************************\
+
+ ###### ######## ######## ## ## ########
+## ## ## ## ## ## ## ##
+## ## ## ## ## ## ##
+ ###### ###### ## ## ## ########
+ ## ## ## ## ## ##
+## ## ## ## ## ## ##
+ ###### ######## ## ####### ##
+
+\************************************************************************************************/
+void function MakeSuicideSpectre( entity spectre )
+{
+ spectre.SetAimAssistAllowed( true )
+ spectre.SetAllowMelee( false )
+ DisableLeeching( spectre )
+
+ spectre.SetNPCMoveSpeedScale( 1.0 )
+
+ spectre.EnableNPCMoveFlag( NPCMF_IGNORE_CLUSTER_DANGER_TIME | NPCMF_PREFER_SPRINT )
+ spectre.DisableNPCMoveFlag( NPCMF_FOLLOW_SAFE_PATHS | NPCMF_INDOOR_ACTIVITY_OVERRIDE )
+
+ spectre.kv.allowShoot = 0
+
+ // Frag drones do suicide spectre behavior but we don't want them doing the enemy changed sounds so filter them out
+ if ( !IsFragDrone( spectre ) && !IsTick( spectre ) )
+ spectre.SetEnemyChangeCallback( SuicideSpectreEnemyChanged )
+
+ spectre.SetLookDistOverride( SPECTRE_MAX_SIGHT_DIST )
+ //spectre.SetHearingSensitivity( 10 ) //1 is default
+ spectre.EnableNPCFlag( NPC_MUTE_TEAMMATE )
+
+ spectre.ai.suicideSpectreExplosionDelay = -1
+
+ thread SpectreWaitToExplode( spectre )
+ AddAnimEvent( spectre, "frag_drone_armed", FragDroneArmed )
+}
+
+void function FragDroneArmed( entity npc )
+{
+ npc.ai.fragDroneArmed = true
+}
+
+void function FragDroneDeath( entity spectre, var damageInfo )
+{
+ FragDroneDeath_Think( spectre, damageInfo )
+}
+
+// for reloadscripts
+void function FragDroneDeath_Think( entity spectre, var damageInfo )
+{
+ vector pos = spectre.GetOrigin()
+ int tagID = spectre.LookupAttachment( "CHESTFOCUS" )
+ vector fxOrg = spectre.GetAttachmentOrigin( tagID )
+ string expSFX
+ if ( spectre.mySpawnOptions_aiSettings == "npc_frag_drone_throwable" )
+ expSFX = SFX_FRAGDRONE_EXPLODE
+ else
+ expSFX = SFX_TICK_EXPLODE
+ int expFX = GetParticleSystemIndex( FX_SPECTRE_EXPLOSION )
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity attackerEnt = GetNPCAttackerEnt( spectre, attacker )
+
+ int team = GetExplosionTeamBasedOnGamemode( spectre )
+
+ int damageDef = GetDamageDefForFragDrone( spectre )
+
+ RadiusDamage_DamageDefSimple( damageDef, pos, attackerEnt, spectre, 0 )
+ EmitSoundAtPosition( spectre.GetTeam(), pos, expSFX )
+ CreateShake( pos, 10, 105, 1.25, 768 )
+ StartParticleEffectInWorld( expFX, fxOrg, Vector( 0, 0, 0 ) )
+
+ spectre.Gib( <0, 0, 100> ) //Used to do .Destroy() on the frag drones immediately, but this meant you can't display the obiturary correctly. Instead, since it's dead already just hide it
+}
+
+entity function GetNPCAttackerEnt( entity npc, entity attacker )
+{
+ entity owner = npc.GetBossPlayer()
+ bool ownerIsPlayer = owner != null && owner.IsPlayer()
+
+ if ( IsMultiplayer() )
+ return ownerIsPlayer ? owner : npc
+
+ if ( !IsAlive( attacker ) )
+ return npc
+
+ // dont give player credit, since that does some bad things
+ if ( ownerIsPlayer )
+ return owner
+
+ if ( attacker.IsPlayer() )
+ return GetEnt( "worldspawn" )
+
+ return attacker
+}
+
+
+int function GetDamageDefForFragDrone( entity drone )
+{
+ var damageDef = drone.Dev_GetAISettingByKeyField( "damageDefOverride" )
+ if ( damageDef != null )
+ {
+ expect string( damageDef )
+ return eDamageSourceId[ damageDef ]
+ }
+
+ entity owner = drone.GetBossPlayer()
+ if ( owner != null && owner.IsPlayer() )
+ return damagedef_frag_drone_throwable_PLAYER
+
+ return damagedef_frag_drone_throwable_NPC
+}
+
+void function SuicideSpectreEnemyChanged( entity spectre )
+{
+ // Spectre "Speaks"
+ if ( ( RandomFloat( 1.0 ) ) < 0.02 )
+ EmitSoundOnEntity( spectre, "diag_imc_spectre_gs_spotenemypilot_01_1" )
+}
+
+/************************************************************************************************\
+
+######## ######## ####### ## ## #### ## ## #### ######## ## ##
+## ## ## ## ## ## ## ## ## ### ### ## ## ## ##
+## ## ## ## ## ## ## ## ## #### #### ## ## ####
+######## ######## ## ## ### ## ## ### ## ## ## ##
+## ## ## ## ## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ## ## ## ## ##
+## ## ## ####### ## ## #### ## ## #### ## ##
+
+\************************************************************************************************/
+void function SpectreWaitToExplode( entity spectre )
+{
+ Assert( spectre.IsNPC() )
+ spectre.EndSignal( "OnDeath" )
+
+ waitthread SuicideSpectre_WaittillNearEnemyOrExploding( spectre )
+
+ if ( spectre.ai.suicideSpectreExplodingAttacker == null )
+ {
+ // not exploding, so overload
+ spectre.ai.suicideSpectreExplosionDelay = GetSpectreExplosionTime( spectre )
+ waitthread SpectreOverloads( spectre )
+ }
+
+ if ( spectre.ai.suicideSpectreExplosionDelay > 0 )
+ wait spectre.ai.suicideSpectreExplosionDelay
+
+ entity attacker = spectre.ai.suicideSpectreExplodingAttacker
+ if ( !IsValid( attacker ) )
+ {
+ entity lastAttacker = GetLastAttacker( spectre )
+ if ( IsValid( lastAttacker ) )
+ {
+ attacker = lastAttacker
+ }
+ else
+ {
+ attacker = spectre
+ }
+ }
+
+ vector force = GetDeathForce()
+
+ Assert( !attacker.IsProjectile(), "Suicide Spectre attacker was a projectile! Type: " + attacker.ProjectileGetWeaponClassName() )
+
+ // JFS: sometimes the attacker is a projectile, which can cause a script error.
+ // The real solution is to figure out which weapon is passing in the projectile as the attacker and correct that.
+ if ( attacker.IsProjectile() )
+ {
+ attacker = spectre
+ }
+
+ spectre.Die( attacker, attacker, { force = force, scriptType = DF_DOOMED_HEALTH_LOSS, damageSourceId = eDamageSourceId.suicideSpectreAoE } )
+}
+
+void function SetSuicideSpectreExploding( entity spectre, entity attacker, float explodingTime )
+{
+ Assert( spectre.ai.suicideSpectreExplodingAttacker == null )
+ spectre.ai.suicideSpectreExplodingAttacker = attacker
+ spectre.ai.suicideSpectreExplosionDelay = explodingTime
+
+ spectre.Signal( "SuicideSpectreExploding" )
+}
+
+float function GetSpectreExplosionTime( entity spectre )
+{
+ if ( Time() - file.lastChainExplosionTime > 1.0 )
+ file.chainExplosionIndex = 0
+
+ float waitTime = file.chainExplosionIndex * 0.14 // RandomFloatRange( CHAIN_EXPLOSION_INTERVALMIN, CHAIN_EXPLOSION_INTERVALMAX )
+ file.lastChainExplosionTime = Time()
+ file.chainExplosionIndex++
+ return waitTime
+}
+
+void function SuicideSpectre_WaittillNearEnemyOrExploding( entity spectre )
+{
+ spectre.EndSignal( "OnDeath" )
+ spectre.EndSignal( "SuicideSpectreExploding" )
+ spectre.EndSignal( "SuicideSpectreForceExplode" )
+
+ bool pursuitSoundPlaying = false
+
+ float minScale = expect float( spectre.Dev_GetAISettingByKeyField( "minSpeedScale" ) )
+ float maxScale = expect float( spectre.Dev_GetAISettingByKeyField( "maxSpeedScale" ) )
+
+ while ( true )
+ {
+ wait 0.1
+
+ if ( !spectre.ai.fragDroneArmed )
+ continue
+
+ if ( spectre.ai.suicideSpectreExplodingAttacker != null )
+ return
+
+ //If spectre is not interrruptable, don't bother
+ if ( !spectre.IsInterruptable() )
+ continue
+
+ //If spectre is parented, don't bother
+ if ( IsValid( spectre.GetParent() ) )
+ continue
+
+ // speed up when near enemy
+ entity enemy = spectre.GetEnemy()
+ if ( IsAlive( enemy ) )
+ {
+ float dist = Distance( enemy.GetOrigin(), spectre.GetOrigin() )
+ float maxDist = 850
+ if ( spectre.mySpawnOptions_aiSettings == "npc_frag_drone_throwable" )
+ {
+ if ( dist < maxDist )
+ {
+ if ( pursuitSoundPlaying == false )
+ {
+ EmitSoundOnEntity( spectre, SFX_FRAGDRONE_SUPERPURSUIT )
+ pursuitSoundPlaying = true
+ }
+ }
+ else
+ {
+ if ( pursuitSoundPlaying == true )
+ {
+ StopSoundOnEntity( spectre, SFX_FRAGDRONE_SUPERPURSUIT )
+ pursuitSoundPlaying = false
+ }
+ }
+ }
+ float speed = GraphCapped( dist, 200, 850, maxScale, minScale )
+ spectre.SetNPCMoveSpeedScale( speed )
+ }
+
+ // offset the overload time
+ if ( Time() < file.nextOverloadTime )
+ continue
+
+ entity attacker = SuicideSpectre_NearEnemy( spectre )
+ if ( attacker != null )
+ {
+ //SetSuicideSpectreOverloading( spectre, attacker )
+ //Assert( 0 ) // never reached
+ return
+ }
+ }
+}
+
+entity function SuicideSpectre_NearEnemy( entity spectre )
+{
+ // See if any player is close eneough to trigger self-destruct
+ array<entity> enemies
+ entity closestEnemy = spectre.GetClosestEnemy()
+ if ( closestEnemy )
+ enemies.append( closestEnemy )
+
+ entity currentEnemy = spectre.GetEnemy()
+ if ( currentEnemy && currentEnemy != closestEnemy )
+ enemies.append( currentEnemy )
+
+ vector origin = spectre.GetOrigin()
+ float dist = expect float( spectre.Dev_GetAISettingByKeyField( "suicideExplosionDistance" ) )
+ foreach ( enemy in enemies )
+ {
+ if ( !IsAlive( enemy ) )
+ continue
+ if ( enemy.IsCloaked( true ) )
+ continue
+ if ( enemy.GetNoTarget() )
+ continue
+ if ( enemy.IsPlayer() && enemy.IsPhaseShifted() )
+ continue
+
+ vector enemyOrigin = enemy.GetOrigin()
+
+ if ( Distance( origin, enemyOrigin ) > dist )
+ continue
+
+ float heightDiff = enemyOrigin.z - origin.z
+
+ // dont explode because you jump over me or I am on the floor above you
+ if ( fabs( heightDiff ) > 40 )
+ {
+ // unless enemy is standing on something slightly above you and there is a clear trace
+ float curTime = Time()
+ float timeDiff = curTime - spectre.ai.suicideSpectreExplosionTraceTime
+ const float TRACE_INTERVAL = 2
+
+ if ( heightDiff > 0 && timeDiff > TRACE_INTERVAL && enemy.IsOnGround() && spectre.CanSee( enemy ) )
+ {
+ spectre.ai.suicideSpectreExplosionTraceTime = curTime
+ float frac = TraceHullSimple( origin, < origin.x, origin.y, enemyOrigin.z >, spectre.GetBoundingMins(), spectre.GetBoundingMaxs(), spectre )
+ if ( frac == 1.0 )
+ return enemy
+ }
+ continue
+ }
+
+ return enemy
+ }
+
+ return null
+}
+
+void function SpectreOverloads( entity spectre )
+{
+ spectre.EndSignal( "SuicideSpectreExploding" )
+ file.nextOverloadTime = Time() + 0.05
+
+ #if MP
+ var chaseTime = spectre.Dev_GetAISettingByKeyField( "SuicideChaseTime" )
+ if ( chaseTime != null )
+ {
+ float maxScale = expect float( spectre.Dev_GetAISettingByKeyField( "maxSpeedScale" ) )
+ spectre.SetNPCMoveSpeedScale( maxScale )
+
+ expect float( chaseTime )
+ float endChaseTime = Time() + chaseTime
+
+ for ( ;; )
+ {
+ if ( Time() >= endChaseTime )
+ break
+
+ if ( !IsAlive( spectre.GetEnemy() ) )
+ break
+
+ entity nearEnemy = SuicideSpectre_NearEnemy( spectre )
+ if ( IsAlive( nearEnemy ) )
+ {
+ if ( nearEnemy.IsTitan() && spectre.IsInterruptable() )
+ {
+ JumpAtTitan( spectre, nearEnemy )
+ spectre.ai.suicideSpectreExplosionDelay = 0.0
+ return
+ }
+ break
+ }
+
+ WaitFrame()
+ }
+ }
+ #endif
+
+ for ( ;; )
+ {
+ #if SP
+ if ( spectre.IsInterruptable() && !spectre.Anim_IsActive() )
+ break
+ #elseif MP
+ if ( spectre.IsInterruptable() && !spectre.Anim_IsActive() && spectre.IsOnGround() )
+ break
+ #endif
+
+ WaitFrame()
+ }
+
+ string overloadSF
+ bool isFragDrone = spectre.mySpawnOptions_aiSettings == "npc_frag_drone_throwable"
+ if ( isFragDrone )
+ overloadSF = SFX_FRAGDRONE_OVERLOAD
+ else
+ overloadSF = SFX_TICK_OVERLOAD
+ // Overload Sound
+ EmitSoundOnEntity( spectre, overloadSF )
+
+ AI_CreateDangerousArea_DamageDef( damagedef_frag_drone_explode, spectre, TEAM_INVALID, true, false )
+
+ // Cleanup on thread end
+ OnThreadEnd(
+ function() : ( spectre, overloadSF )
+ {
+ if ( IsValid( spectre ) )
+ {
+ StopSoundOnEntity( spectre, overloadSF )
+ }
+ }
+ )
+
+ bool jumpAtTitans = spectre.Dev_GetAISettingByKeyField( "JumpAtTitans" ) == null || spectre.Dev_GetAISettingByKeyField( "JumpAtTitans" ) == 1
+
+ entity enemy = spectre.GetEnemy()
+ if ( enemy && enemy.IsTitan() && jumpAtTitans && !spectre.IsInterruptable() )
+ {
+ JumpAtTitan( spectre, enemy )
+ }
+ else
+ {
+ string anim = "sp_suicide_spectre_explode_stand"
+ var overrideAnim = spectre.Dev_GetAISettingByKeyField( "OverrideOverloadAnim" )
+
+ if ( overrideAnim != null )
+ {
+ anim = expect string( overrideAnim )
+ }
+
+ waitthread PlayAnim( spectre, anim )
+
+ if ( !isFragDrone )
+ wait 0.25
+ }
+}
+
+void function JumpAtTitan( entity spectre, entity enemy )
+{
+ vector myOrigin = spectre.GetOrigin()
+ vector dirToEnemy = enemy.EyePosition() - myOrigin
+
+ float dist = Length( dirToEnemy )
+ if ( dist > 0 )
+ {
+ const float MAX_DIST = 100
+ dirToEnemy *= min( MAX_DIST, dist ) / dist
+ }
+
+ vector refOrigin = myOrigin + Vector( dirToEnemy.x, dirToEnemy.y, 256 )
+ vector refAngles = spectre.GetAngles() + Vector( 0, 180, 0 )
+ spectre.Anim_ScriptedPlayWithRefPoint( "sd_jump_explode", refOrigin, refAngles, 0.3 )
+ WaittillAnimDone( spectre )
+ return
+}
+
+int function GetExplosionTeamBasedOnGamemode( entity spectre )
+{
+ return spectre.GetTeam()
+}
+
+
+/************************************************************************************************\
+
+######## ### ## ## ### ###### ########
+## ## ## ## ### ### ## ## ## ## ##
+## ## ## ## #### #### ## ## ## ##
+## ## ## ## ## ### ## ## ## ## #### ######
+## ## ######### ## ## ######### ## ## ##
+## ## ## ## ## ## ## ## ## ## ##
+######## ## ## ## ## ## ## ###### ########
+
+\************************************************************************************************/
+void function SpectreSuicideOnDamaged_Callback( entity spectre, var damageInfo )
+{
+ SpectreSuicideOnDamaged( spectre, damageInfo )
+}
+
+
+void function SpectreSuicideOnDamaged( entity spectre, var damageInfo )
+{
+ //Assert( IsSuicideSpectre( spectre ) )
+
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ DamageInfo_SetCustomDamageType( damageInfo, damageType )
+
+ if ( !IsAlive( spectre ) )
+ return
+
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ // Calculate build time credit
+ if ( attacker.IsPlayer() )
+ {
+ if ( GameModeRulesShouldGiveTimerCredit( attacker, spectre, damageInfo ) && !TitanDamageRewardsTitanCoreTime() )
+ {
+ float timerCredit = CalculateBuildTimeCredit( attacker, spectre, damage, spectre.GetHealth(), spectre.GetMaxHealth(), "spectre_kill_credit", 9 )
+ if ( timerCredit )
+ DecrementBuildTimer( attacker, timerCredit )
+ }
+ }
+
+ // No pain anims for suicide spectres
+ DamageInfo_AddDamageFlags( damageInfo, DAMAGEFLAG_NOPAIN )
+
+
+ spectre.Signal( "SuicideSpectreExploding" )
+
+ if ( !IsValid( inflictor ) || !inflictor.IsPlayer() )
+ {
+ if ( spectre.ai.suicideSpectreExplodingAttacker == null )
+ {
+ if ( spectre.GetHealth() - damage <= 0 || ( IsValid( inflictor ) && IsTick( inflictor ) ) )
+ {
+ float explosionTime = GetSpectreExplosionTime( spectre )
+ SetSuicideSpectreExploding( spectre, attacker, explosionTime )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+ }
+ else
+ {
+ // already exploding
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ DamageInfo_SetDamage( damageInfo, damage )
+ }
+}