From 207facbc402f5639cbcd31f079214351ef605cf2 Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Tue, 22 Jun 2021 14:30:49 +0100 Subject: initial commit after moving to new repo --- .../scripts/vscripts/ai/_ai_suicide_spectres.gnut | 576 +++++++++++++++++++++ 1 file changed, 576 insertions(+) create mode 100644 Northstar.CustomServers/scripts/vscripts/ai/_ai_suicide_spectres.gnut (limited to 'Northstar.CustomServers/scripts/vscripts/ai/_ai_suicide_spectres.gnut') diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_suicide_spectres.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_suicide_spectres.gnut new file mode 100644 index 000000000..f8e0652ce --- /dev/null +++ b/Northstar.CustomServers/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 > 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 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 ) + } +} -- cgit v1.2.3