aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut')
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut606
1 files changed, 0 insertions, 606 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut
deleted file mode 100644
index f49560e02..000000000
--- a/Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut
+++ /dev/null
@@ -1,606 +0,0 @@
-global function AiStalker_Init
-global function GetDeathForce
-global function StalkerGearOverloads
-global function StalkerMeltingDown
-
-global function IsStalkerLimbBlownOff
-
-const float STALKER_DAMAGE_REQUIRED_TO_HEADSHOT = 0.3
-//
-// Base npc script shared between all npc types (regular, suicide, etc.)
-//
-
-const STALKER_REACTOR_CRITIMPACT_SOUND_1P_VS_3P = "ai_stalker_bulletimpact_nukecrit_1p_vs_3p"
-const STALKER_REACTOR_CRITIMPACT_SOUND_3P_VS_3P = "ai_stalker_bulletimpact_nukecrit_3p_vs_3p"
-const STALKER_REACTOR_CRITICAL_SOUND = "ai_stalker_nukedestruct_warmup_3p"
-const STALKER_REACTOR_CRITICAL_FX = $"P_spectre_suicide_warn"
-
-void function AiStalker_Init()
-{
- PrecacheImpactEffectTable( "exp_stalker_powersupply" )
- PrecacheImpactEffectTable( "exp_small_stalker_powersupply" )
- PrecacheParticleSystem( STALKER_REACTOR_CRITICAL_FX )
- AddDamageCallback( "npc_stalker", StalkerOnDamaged )
- AddDeathCallback( "npc_stalker", StalkerOnDeath )
- AddSpawnCallback( "npc_stalker", StalkerOnSpawned )
-}
-
-void function StalkerOnSpawned( entity npc )
-{
- StalkerOnSpawned_Think( npc )
-}
-
-void function StalkerOnSpawned_Think( entity npc )
-{
- npc.SetCanBeMeleeExecuted( false )
-
- for ( int hitGroup = 0; hitGroup < HITGROUP_COUNT; hitGroup++ )
- {
- npc.ai.stalkerHitgroupDamageAccumulated[ hitGroup ] <- 0
- npc.ai.stalkerHitgroupLastHitTime[ hitGroup ] <- 0
- }
-
- if ( npc.Dev_GetAISettingByKeyField( "ScriptSpawnAsCrawler" ) == 1 )
- {
- EnableStalkerCrawlingBehavior( npc )
- PlayCrawlingAnim( npc, "ACT_RUN" )
- npc.Anim_Stop() // start playing a crawl anim then cut it off so it doesnt loop
- }
-}
-
-void function StalkerOnDeath( entity npc, var damageInfo )
-{
- thread StalkerOnDeath_Internal( npc, damageInfo )
-
- #if MP
- int sourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
- if ( sourceId == eDamageSourceId.damagedef_titan_step )
- {
- Explosion_DamageDefSimple(
- damagedef_stalker_powersupply_explosion_large_at,
- npc.GetOrigin(),
- npc,
- npc,
- npc.GetOrigin()
- )
- }
- #endif
-
-}
-
-void function StalkerOnDeath_Internal( entity npc, var damageInfo )
-{
- int customDamageFlags = DamageInfo_GetCustomDamageType( damageInfo )
- bool allowDismemberment = bool( customDamageFlags & DF_DISMEMBERMENT )
- if ( allowDismemberment )
- {
- int hitGroup = GetHitGroupFromDamageInfo( npc, damageInfo )
- if ( hitGroup >= HITGROUP_GENERIC )
- {
- entity attacker = DamageInfo_GetAttacker( damageInfo )
- TryDismemberStalker( npc, damageInfo, attacker, hitGroup )
- }
- }
-
- if ( IsCrawling( npc ) )
- {
- WaitFrame() // or head won't disappear
- if ( IsValid( npc ) )
- npc.BecomeRagdoll( Vector( 0, 0, 0 ), false )
- return
- }
-}
-
-
-// All damage to stalkers comes here for modification and then either branches out to other npc types (Suicide, etc) for custom stuff or it just continues like normal.
-void function StalkerOnDamaged( entity npc, var damageInfo )
-{
- StalkerOnDamaged_Internal( npc, damageInfo )
-}
-
-void function StalkerOnDamaged_Internal( entity npc, var damageInfo )
-{
- if ( !IsAlive( npc ) )
- return
-
- if ( StalkerMeltingDown( npc ) )
- {
- DamageInfo_ScaleDamage( damageInfo, 0.0 )
- return
- }
-
- // can't shoot, don't blow off limbs
- if ( IsCrawling( npc ) )
- {
- if ( Time() - npc.ai.startCrawlingTime < 0.75 )
- {
- DamageInfo_SetDamage( damageInfo, 0 )
- return
- }
- }
-
- int hitGroup = GetHitGroupFromDamageInfo( npc, damageInfo )
- if ( hitGroup < HITGROUP_GENERIC )
- hitGroup = HITGROUP_GENERIC
-
- float damage = DamageInfo_GetDamage( damageInfo )
-
- // limb dead yet?
- npc.ai.stalkerHitgroupDamageAccumulated[ hitGroup ] += int( damage )
- npc.ai.stalkerHitgroupLastHitTime[ hitGroup ] = Time()
-
- entity attacker = DamageInfo_GetAttacker( damageInfo )
-
- if ( PlayerHitGear( npc, damageInfo, hitGroup ) )
- {
- // don't die from damage
- float damage = DamageInfo_GetDamage( damageInfo )
- damage = npc.GetHealth() - 1.0
- DamageInfo_SetDamage( damageInfo, damage )
-
- thread StalkerGearOverloads( npc, attacker )
- return
- }
-
- int customDamageFlags = DamageInfo_GetCustomDamageType( damageInfo )
- bool allowDismemberment = bool( customDamageFlags & DF_DISMEMBERMENT )
- if ( !allowDismemberment )
- return
-
- bool canBeStaggered = TryDismemberStalker( npc, damageInfo, attacker, hitGroup )
-
- if ( canBeStaggered && !IsCrawling( npc ) && !npc.ai.transitioningToCrawl )
- {
- if ( npc.GetHealth().tofloat() / npc.GetMaxHealth().tofloat() <= 0.5 )
- {
- thread AttemptStandToStaggerAnimation( npc )
- npc.SetActivityModifier( ACT_MODIFIER_STAGGER, true )
- }
- }
-}
-
-bool function TryDismemberStalker( entity npc, var damageInfo, entity attacker, int hitGroup )
-{
- string fpSound
- string tpSound
-
- switch ( hitGroup )
- {
- case HITGROUP_CHEST:
- case HITGROUP_STOMACH:
- fpSound = "AndroidArmored.BulletImpact_1P_vs_3P"
- tpSound = "AndroidArmored.BulletImpact_3P_vs_3P"
- break
-
- default:
- fpSound = "AndroidVulnerable.BulletImpact_1P_vs_3P"
- tpSound = "AndroidVulnerable.BulletImpact_3P_vs_3P"
- break
- }
-
- if ( IsAlive( attacker ) && attacker.IsPlayer() )
- {
- EmitSoundOnEntityOnlyToPlayer( npc, attacker, fpSound )
- EmitSoundOnEntityExceptToPlayer( npc, attacker, tpSound )
- }
- else
- {
- EmitSoundOnEntity( npc, tpSound )
- }
-
- bool justAFleshWound = true
-
- switch ( hitGroup )
- {
- case HITGROUP_HEAD:
- thread StalkerHeadShot( npc, damageInfo, hitGroup )
- justAFleshWound = false
- break
-
- case HITGROUP_LEFTARM:
- if ( StalkerLimbBlownOff( npc, damageInfo, hitGroup, 0.085, "left_arm", [ "left_arm", "l_hand" ], "Spectre.Arm.Explode" ) )
- {
- npc.SetActivityModifier( ACT_MODIFIER_ONEHANDED, true )
-
- // Some of his synced melees depend on using his left arm
- npc.SetCapabilityFlag( bits_CAP_SYNCED_MELEE_ATTACK, false )
- }
- break
-
- case HITGROUP_LEFTLEG:
- justAFleshWound = TryLegBlownOff( npc, damageInfo, hitGroup, 0.17, "left_leg", [ "left_leg", "foot_L_sole" ], "Spectre.Leg.Explode" )
- break
-
- case HITGROUP_RIGHTLEG:
- justAFleshWound = TryLegBlownOff( npc, damageInfo, hitGroup, 0.17, "right_leg", [ "right_leg", "foot_R_sole" ], "Spectre.Leg.Explode" )
- break
- }
-
- return justAFleshWound
-}
-
-bool function PlayerHitGear( entity npc, var damageInfo, int hitGroup )
-{
- entity attacker = DamageInfo_GetAttacker( damageInfo )
-
- if ( !attacker.IsPlayer() )
- return false
-
- if ( hitGroup != HITGROUP_GEAR )
- return false
-
- if ( !( DamageInfo_GetCustomDamageType( damageInfo ) & DF_BULLET ) )
- return false
-
- return true
-}
-
-int function GetHitGroupFromDamageInfo( entity npc, var damageInfo )
-{
- int hitGroup = DamageInfo_GetHitGroup( damageInfo )
-
- if ( hitGroup <= HITGROUP_GENERIC )
- {
- int hitBox = DamageInfo_GetHitBox( damageInfo )
- if ( hitBox >= 0 )
- return GetHitgroupForHitboxOnEntity( npc, hitBox )
- }
-
- return hitGroup
-}
-
-bool function StalkerMeltingDown( entity npc )
-{
- int bodyGroup = npc.FindBodyGroup( "gear" )
- Assert( bodyGroup != -1 )
-
- // gear already blown up?
- return npc.GetBodyGroupState( bodyGroup ) != 0
-}
-
-void function StalkerGearOverloads( entity npc, entity attacker = null )
-{
- Assert( !StalkerMeltingDown( npc ) )
-
- if ( !IsCrawling( npc ) && StalkerCanCrawl( npc ) )
- thread FallAndBecomeCrawlingStalker( npc )
-
- int bodyGroup = npc.FindBodyGroup( "gear" )
-
- // hide gear
- npc.SetBodygroup( bodyGroup, 1 )
-
- string attachment = "CHESTFOCUS"
-
- npc.EndSignal( "OnDestroy" )
- npc.EndSignal( "OnDeath" )
-
- entity nukeFXInfoTarget = CreateEntity( "info_target" )
- nukeFXInfoTarget.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
- DispatchSpawn( nukeFXInfoTarget )
-
- nukeFXInfoTarget.SetParent( npc, attachment )
-
- if ( attacker != null )
- {
- EmitSoundOnEntityOnlyToPlayer( nukeFXInfoTarget, attacker, STALKER_REACTOR_CRITIMPACT_SOUND_1P_VS_3P )
- EmitSoundOnEntityExceptToPlayer( nukeFXInfoTarget, attacker, STALKER_REACTOR_CRITIMPACT_SOUND_3P_VS_3P )
- }
- else
- {
- EmitSoundOnEntity( nukeFXInfoTarget, STALKER_REACTOR_CRITIMPACT_SOUND_3P_VS_3P )
- }
-
- EmitSoundOnEntity( nukeFXInfoTarget, STALKER_REACTOR_CRITICAL_SOUND )
-
- AI_CreateDangerousArea_DamageDef( damagedef_stalker_powersupply_explosion_small, nukeFXInfoTarget, TEAM_INVALID, true, false )
-
- entity fx = PlayFXOnEntity( STALKER_REACTOR_CRITICAL_FX, nukeFXInfoTarget )
-
- OnThreadEnd(
- function() : ( nukeFXInfoTarget, fx, npc, attacker )
- {
- if ( IsValid( npc ) )
- StopSoundOnEntity( nukeFXInfoTarget, STALKER_REACTOR_CRITICAL_SOUND )
-
- if ( IsValid( nukeFXInfoTarget ) )
- nukeFXInfoTarget.Destroy()
-
- if ( IsValid( fx ) )
- fx.Destroy()
-
- if ( IsAlive( npc ) )
- {
- entity damageAttacker
- if ( IsValid( attacker ) )
- damageAttacker = attacker
- else
- damageAttacker = npc
-
- vector force = GetDeathForce()
- npc.Die( damageAttacker, npc, { force = force, scriptType = DF_GIB, damageSourceId = eDamageSourceId.suicideSpectreAoE } )
- }
- }
- )
-
- wait 1.0
-
- float duration = 2.1
- float endTime = Time() + duration
- float startTime = Time()
-
- int tagID = npc.LookupAttachment( "CHESTFOCUS" )
-
- for ( ;; )
- {
- float timePassed = Time() - startTime
- float explodeMin = Graph( timePassed, 0, duration, 0.4, 0.1 )
- float explodeMax = explodeMin + Graph( timePassed, 0, duration, 0.21, 0.1 )
- wait RandomFloatRange( explodeMin, explodeMax )
-
- entity damageAttacker = GetNPCAttackerEnt( npc, attacker )
-
- // origin = npc.GetWorldSpaceCenter()
- vector origin = npc.GetAttachmentOrigin( tagID )
-
- if ( Time() >= endTime )
- {
- Explosion_DamageDefSimple( damagedef_stalker_powersupply_explosion_large, origin, damageAttacker, npc, origin )
- break
- }
- else
- {
- Explosion_DamageDefSimple( damagedef_stalker_powersupply_explosion_small, origin, damageAttacker, npc, origin )
- }
- }
-}
-
-bool function StalkerCanCrawl( entity npc )
-{
- if ( !IsAlive( npc ) )
- return false
-
- if ( npc.Anim_IsActive() )
- return false
-
- return true
-}
-
-bool function TryLegBlownOff( entity npc, var damageInfo, int hitGroup, float limbHealthPercentOfMax, string leg, array<string> fxTags, string sound )
-{
- if ( IsCrawling( npc ) )
- {
- // can blow off leg if stalker is already crawling
- StalkerLimbBlownOff( npc, damageInfo, hitGroup, limbHealthPercentOfMax, leg, fxTags, sound )
- return true
- }
-
- if ( !StalkerCanCrawl( npc ) )
- return true
-
- if ( StalkerLimbBlownOff( npc, damageInfo, hitGroup, limbHealthPercentOfMax, leg, fxTags, sound ) )
- {
- thread FallAndBecomeCrawlingStalker( npc )
- return false
- }
-
- return true
-}
-
-void function EnableStalkerCrawlingBehavior( entity npc )
-{
- Assert( StalkerCanCrawl( npc ) )
- Assert( !IsCrawling( npc ) )
-
- DisableLeeching( npc )
-
- DisableMinionUsesHeavyWeapons( npc )
-
- string crawlingSettings = string ( npc.Dev_GetAISettingByKeyField( "crawlingSettingsWrapper" ) )
-
- // Changing the setting file includes changing the behavior file to "behavior_stalker_crawling"
- SetAISettingsWrapper( npc, crawlingSettings )
-
- npc.ai.crawling = true
- npc.ai.startCrawlingTime = Time()
- npc.DisableGrappleAttachment()
- npc.EnableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS )
- npc.SetCapabilityFlag( bits_CAP_MOVE_TRAVERSE | bits_CAP_MOVE_SHOOT | bits_CAP_WEAPON_RANGE_ATTACK1 | bits_CAP_AIM_GUN, false )
- npc.SetActivityModifier( ACT_MODIFIER_CRAWL, true )
- npc.SetActivityModifier( ACT_MODIFIER_STAGGER, false )
- npc.SetCanBeGroundExecuted( true )
- npc.ClearMoveAnim()
-
- npc.SetHealth( npc.GetMaxHealth() * 0.5 )
-
- npc.SetAimAssistForcePullPitchEnabled( true )
-
- thread SelfTerminateAfterDelay( npc )
-}
-
-void function SelfTerminateAfterDelay( entity npc )
-{
- const float lifeSupportDuration = 8
- float deathTime = Time() + (lifeSupportDuration * 2)
-
- npc.EndSignal( "OnDeath" )
- for ( ;; )
- {
- entity enemy = npc.GetEnemy()
- if ( IsAlive( enemy ) )
- {
- if ( Distance( npc.GetEnemyLKP(), npc.GetOrigin() ) < 500 )
- {
- if ( npc.TimeSinceSeen( enemy ) < 3 )
- deathTime = max( Time() + lifeSupportDuration, deathTime )
- }
- }
-
- if ( Time() > deathTime )
- {
- npc.Die()
- return
- }
-
- wait 1.0
- }
-}
-
-void function FallAndBecomeCrawlingStalker( entity npc )
-{
- // finish what he's doing
- npc.EndSignal( "OnDeath" )
-
- npc.ai.transitioningToCrawl = true
-
- // Workaround for Bug 114372
- WaitFrame()
-
- for ( ;; )
- {
- if ( npc.IsInterruptable() )
- break
- WaitFrame()
- }
-
- if ( !StalkerCanCrawl( npc ) )
- return
-
- if ( IsCrawling( npc ) )
- return
-
- EnableStalkerCrawlingBehavior( npc )
-
- npc.Anim_Stop() // stop leeching, etc.
-
- PlayCrawlingAnim( npc, "ACT_STAND_TO_CRAWL" )
-}
-
-void function PlayCrawlingAnim( entity npc, string animation )
-{
- npc.Anim_ScriptedPlayActivityByName( animation, true, 0.1 )
- npc.UseSequenceBounds( true )
-}
-
-void function AttemptStandToStaggerAnimation( entity npc )
-{
- // Check if we are already staggered
- if ( npc.IsActivityModifierActive( ACT_MODIFIER_STAGGER ) )
- return
-
- if ( !npc.IsInterruptable() )
- return
-
- if ( npc.ContextAction_IsBusy() )
- return
-
- // Are we blocking additional pain animations
- if ( npc.GetNPCFlag( NPC_NO_PAIN ) )
- return
-
- // finish what he's doing
- npc.EndSignal( "OnDeath" )
-
- // Workaround for Bug 114372
- WaitFrame()
-
- for ( ;; )
- {
- if ( npc.IsInterruptable() )
- break
-
- WaitFrame()
- }
-
- if ( IsCrawling( npc ) || npc.ai.transitioningToCrawl )
- return
-
- npc.Anim_ScriptedPlayActivityByName( "ACT_STAND_TO_STAGGER", true, 0.1 )
- npc.UseSequenceBounds( true )
- npc.EnableNPCFlag( NPC_PAIN_IN_SCRIPTED_ANIM )
-}
-
-bool function IsStalkerLimbBlownOff( entity npc, string limbName )
-{
- int bodyGroup = npc.FindBodyGroup( limbName )
- if ( npc.GetBodyGroupState( bodyGroup ) != 0 )
- return true
-
- return false
-}
-
-bool function StalkerLimbBlownOff( entity npc, var damageInfo, int hitGroup, float limbHealthPercentOfMax, string limbName, array<string> fxTags, string sound )
-{
- int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
- switch ( damageSourceId )
- {
- case eDamageSourceId.mp_weapon_grenade_emp:
- case eDamageSourceId.mp_weapon_proximity_mine:
- return false
- }
-
- int bodyGroup = npc.FindBodyGroup( limbName )
- if ( bodyGroup == -1 )
- return false
-
- if ( IsStalkerLimbBlownOff( npc, limbName ) )
- return false
-
- EmitSoundOnEntity( npc, sound )
-
- // blow off limb
- npc.SetBodygroup( bodyGroup, 1 )
-
- return true
-}
-
-void function StalkerHeadShot( entity npc, var damageInfo, int hitGroup )
-{
- // random chance to blow up head
-// if ( DamageInfo_GetDamage( damageInfo ) < 100 && RandomFloat( 100 ) <= 66 )
-// return
-
- if ( !IsValidHeadShot( damageInfo, npc ) )
- return
-
- // only players score headshots
- entity attacker = DamageInfo_GetAttacker( damageInfo )
- if ( !IsAlive( attacker ) )
- return
- if ( !attacker.IsPlayer() )
- return
-
- if ( DamageInfo_GetDamage( damageInfo ) < npc.GetHealth() )
- {
- // force lethal if we have done more than this much damage
- if ( npc.ai.stalkerHitgroupDamageAccumulated[ hitGroup ] < npc.GetMaxHealth() * STALKER_DAMAGE_REQUIRED_TO_HEADSHOT )
- return
- }
-
- npc.Anim_Stop() // stop leeching, etc.
- npc.ClearParent()
-
- //DisableLeeching( npc )
-
- // No pain anims
- //DamageInfo_AddDamageFlags( damageInfo, DAMAGEFLAG_NOPAIN )
-
- // Set these so cl_player knows to kill the eye glow and play the right SFX
- DamageInfo_AddCustomDamageType( damageInfo, DF_HEADSHOT )
- DamageInfo_AddCustomDamageType( damageInfo, DF_KILLSHOT )
-
- EmitSoundOnEntityExceptToPlayer( npc, attacker, "SuicideSpectre.BulletImpact_HeadShot_3P_vs_3P" )
-
- int bodyGroupIndex = npc.FindBodyGroup( "removableHead" )
- int stateIndex = 1 // 0 = show, 1 = hide
- npc.SetBodygroup( bodyGroupIndex, stateIndex )
-
- DamageInfo_SetDamage( damageInfo, npc.GetMaxHealth() )
-}
-
-vector function GetDeathForce()
-{
- vector angles = <RandomFloatRange(-45,-75),RandomFloat(360),0>
- vector forward = AnglesToForward( angles )
- return forward * RandomFloatRange( 0.25, 0.75 )
-} \ No newline at end of file