diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_codecallbacks.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_codecallbacks.gnut index 486689d38..bb7ae3233 100644 --- a/Northstar.Client/mod/scripts/vscripts/client/cl_codecallbacks.gnut +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_codecallbacks.gnut @@ -64,7 +64,7 @@ void function ClientCodeCallback_BodyGroupChanged( entity ent, int bodyGroupInde if ( IsSpectre( ent ) || IsStalker( ent ) ) { - if ( bodyGroupIndex == ent.FindBodyGroup( "removableHead" ) ) + if ( bodyGroupIndex == ent.FindBodyGroup( "head" ) || bodyGroupIndex == ent.FindBodyGroup( "removableHead" ) ) { ModelFX_DisableGroup( ent, "foe_lights" ) ModelFX_DisableGroup( ent, "friend_lights" ) diff --git a/Northstar.Client/mod/scripts/vscripts/client/objects/cl_spectre.nut b/Northstar.Client/mod/scripts/vscripts/client/objects/cl_spectre.nut new file mode 100644 index 000000000..8c6c2633c --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/client/objects/cl_spectre.nut @@ -0,0 +1,49 @@ +global function ClSpectre_Init + +struct +{ + table initialized +} file + +void function ClSpectre_Init() +{ + AddCreateCallback( "npc_spectre", CreateCallback_Spectre ) + + RegisterSignal( "SpectreGlowEYEGLOW" ) + + PrecacheParticleSystem( $"P_spectre_eye_foe" ) + PrecacheParticleSystem( $"P_spectre_eye_friend" ) +} + +void function CreateCallback_Spectre( entity spectre ) +{ + AddAnimEvent( spectre, "create_dataknife", CreateThirdPersonDataKnife ) + + int bodyGroupIndex = spectre.FindBodyGroup( "head" ) + if ( bodyGroupIndex == -1 ) + bodyGroupIndex = spectre.FindBodyGroup( "removableHead" ) + + spectre.DoBodyGroupChangeScriptCallback( true, bodyGroupIndex ) + + asset model = spectre.GetModelName() + if ( model in file.initialized ) + return + file.initialized[ model ] <- true + + //---------------------- + // model Lights - Friend + //---------------------- + ModelFX_BeginData( "friend_lights", model, "friend", true ) + ModelFX_HideFromLocalPlayer() + ModelFX_AddTagSpawnFX( "EYEGLOW", $"P_spectre_eye_friend" ) + ModelFX_EndData() + + //---------------------- + // model Lights - Foe + //---------------------- + ModelFX_BeginData( "foe_lights", model, "foe", true ) + ModelFX_HideFromLocalPlayer() + ModelFX_AddTagSpawnFX( "EYEGLOW", $"P_spectre_eye_foe" ) + ModelFX_EndData() +} + diff --git a/Northstar.Custom/mod/models/robots/spectre/imc_spectre.mdl b/Northstar.Custom/mod/models/robots/spectre/imc_spectre.mdl new file mode 100644 index 000000000..c23517653 Binary files /dev/null and b/Northstar.Custom/mod/models/robots/spectre/imc_spectre.mdl differ diff --git a/Northstar.Custom/mod/scripts/vscripts/ai/_ai_spectre.gnut b/Northstar.Custom/mod/scripts/vscripts/ai/_ai_spectre.gnut new file mode 100644 index 000000000..7d265a923 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/ai/_ai_spectre.gnut @@ -0,0 +1,134 @@ +global function AiSpectre_Init +global function NPCCarriesBattery + +void function AiSpectre_Init() +{ + //AddDamageCallback( "npc_spectre", SpectreOnDamaged ) + AddDeathCallback( "npc_spectre", SpectreOnDeath ) + //AddSpawnCallback( "npc_spectre", SpectreOnSpawned ) + + #if !SPECTRE_CHATTER_MP_ENABLED + AddCallback_OnPlayerKilled( SpectreChatter_OnPlayerKilled ) + AddCallback_OnNPCKilled( SpectreChatter_OnNPCKilled ) + #endif +} + +void function SpectreOnSpawned( entity npc ) +{ + +} + +void function SpectreOnDeath( entity npc, var damageInfo ) +{ + if ( !IsValidHeadShot( damageInfo, npc ) ) + return + + // 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( "head" ) + if ( bodyGroupIndex == -1 ) + bodyGroupIndex = npc.FindBodyGroup( "removableHead" ) + + int stateIndex = 1 // 0 = show, 1 = hide + npc.SetBodygroup( bodyGroupIndex, stateIndex ) + + DamageInfo_SetDamage( damageInfo, npc.GetMaxHealth() ) + +} + +// All damage to spectres 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 SpectreOnDamaged( entity npc, var damageInfo ) +{ + +} + +void function SpectreChatter_OnPlayerKilled( entity playerKilled, entity attacker, var damageInfo ) +{ + if ( !IsSpectre( attacker ) ) + return + + if ( playerKilled.IsTitan() ) + thread PlaySpectreChatterAfterDelay( attacker, "spectre_gs_gruntkillstitan_02_1" ) + else + thread PlaySpectreChatterAfterDelay( attacker, "spectre_gs_killenemypilot_01_1" ) + +} + +void function SpectreChatter_OnNPCKilled( entity npcKilled, entity attacker, var damageInfo ) +{ + if ( IsSpectre( npcKilled ) ) + { + string deadGuySquadName = expect string( npcKilled.kv.squadname ) + if ( deadGuySquadName == "" ) + return + + array squad = GetNPCArrayBySquad( deadGuySquadName ) + + entity speakingSquadMate = null + + foreach( squadMate in squad ) + { + if ( IsSpectre( squadMate ) ) + { + speakingSquadMate = squadMate + break + } + } + if ( speakingSquadMate == null ) + return + + if ( squad.len() == 1 ) + thread PlaySpectreChatterAfterDelay( speakingSquadMate, "spectre_gs_squaddeplete_01_1" ) + else if ( squad.len() > 0 ) + thread PlaySpectreChatterAfterDelay( speakingSquadMate, "spectre_gs_allygrundown_05_1" ) + } + else + { + if ( !IsSpectre( attacker ) ) + return + + if ( npcKilled.IsTitan() ) + thread PlaySpectreChatterAfterDelay( attacker, "spectre_gs_gruntkillstitan_02_1" ) + } +} + +void function PlaySpectreChatterAfterDelay( entity spectre, string chatterLine, float delay = 0.3 ) +{ + wait delay + + if ( !IsAlive( spectre ) ) //Really this is just an optimization thing, if the spectre is dead no point in running the same check for every player nearby in ShouldPlaySpectreChatterMPLine + return + + PlaySpectreChatterToAll( chatterLine, spectre ) +} + +void function NPCCarriesBattery( entity npc ) +{ + entity battery = Rodeo_CreateBatteryPack() + battery.SetParent( npc, "BATTERY_ATTACH" ) + battery.MarkAsNonMovingAttachment() + thread SpectreBatteryThink( npc, battery ) +} + +void function SpectreBatteryThink( entity npc, entity battery ) +{ + battery.EndSignal( "OnDestroy" ) + npc.EndSignal( "OnDestroy" ) + + OnThreadEnd( + function() : ( battery ) + { + if ( IsValid( battery ) ) + { + battery.ClearParent() + battery.SetAngles( < 0,0,0 > ) + battery.SetVelocity( < 0,0,200 > ) + } + } + ) + + npc.WaitSignal( "OnDeath" ) +} \ No newline at end of file