aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobTheBob <32057864+BobTheBob9@users.noreply.github.com>2021-06-22 14:30:49 +0100
committerBobTheBob <32057864+BobTheBob9@users.noreply.github.com>2021-06-22 14:30:49 +0100
commit207facbc402f5639cbcd31f079214351ef605cf2 (patch)
tree4710b2a88dd64f3dfea1609d31a5de9141640951
parentc2d438568df6d98cf731807e30eaa7da31e5ea52 (diff)
downloadNorthstarMods-207facbc402f5639cbcd31f079214351ef605cf2.tar.gz
NorthstarMods-207facbc402f5639cbcd31f079214351ef605cf2.zip
initial commit after moving to new repo
-rw-r--r--Northstar.Client/mod.json15
-rw-r--r--Northstar.Client/scripts/vscripts/ui/menu_main.nut608
-rw-r--r--Northstar.Client/scripts/vscripts/ui/menu_private_match.nut777
-rw-r--r--Northstar.Client/scripts/vscripts/ui/panel_mainmenu.nut829
-rw-r--r--Northstar.Coop/mod.json24
-rw-r--r--Northstar.Coop/scripts/vscripts/client/cl_respawnselect_sp.gnut110
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/_base_gametype_sp.gnut657
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut177
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/_gamestate_sp.gnut793
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut1054
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/_savegame.gnut860
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_spoke02.nut3891
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_utility.nut7253
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/sh_coop_sp_utils.gnut34
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/sh_sp_objective.gnut537
-rw-r--r--Northstar.Coop/scripts/vscripts/sp/sp_training.nut7554
-rw-r--r--Northstar.Custom/mod.json186
-rw-r--r--Northstar.Custom/playlists_v2.txt5772
-rw-r--r--Northstar.Custom/scripts/levels/mp_box.rson8
-rw-r--r--Northstar.Custom/scripts/vscripts/_northstar_devcommands.gnut60
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_arena.gnut99
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_fastball.gnut194
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut208
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_gg.gnut111
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_inf.gnut176
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_kr.gnut126
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_sbox.gnut49
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_tt.gnut71
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/_riff_instagib.gnut65
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut35
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut96
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut26
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut75
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut241
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut26
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut58
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut0
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut45
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut73
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut164
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut56
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut53
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut20
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut34
-rw-r--r--Northstar.Custom/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut262
-rw-r--r--Northstar.Custom/scripts/vscripts/mp/levels/mp_box.nut6
-rw-r--r--Northstar.Custom/scripts/vscripts/northstar_custom_autoprecache.gnut14
-rw-r--r--Northstar.Custom/scripts/vscripts/titan/sh_first_person_embark.gnut41
-rw-r--r--Northstar.Custom/scripts/vscripts/titan/sh_titan_embark.gnut2253
-rw-r--r--Northstar.Custom/scripts/vscripts/weapons/mp_weapon_peacekraber.nut162
-rw-r--r--Northstar.Custom/scripts/vscripts/weapons/mp_weapon_toolgun.nut39
-rw-r--r--Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut30
-rw-r--r--Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut24
-rw-r--r--Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut196
-rw-r--r--Northstar.Custom/scripts/weapons/melee_pilot_emptyhanded.txt149
-rw-r--r--Northstar.Custom/scripts/weapons/melee_pilot_kunai.txt149
-rw-r--r--Northstar.Custom/scripts/weapons/mp_weapon_peacekraber.txt347
-rw-r--r--Northstar.Custom/scripts/weapons/mp_weapon_sniper.txt651
-rw-r--r--Northstar.Custom/scripts/weapons/mp_weapon_toolgun.txt587
-rw-r--r--Northstar.CustomServers/cfg/server/persistent_player_data_version_231.pdef1515
-rw-r--r--Northstar.CustomServers/mod.json56
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_drone.txt33
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_dropship.txt21
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_flyer.txt33
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_frag_drone.txt27
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_goliath.txt31
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_gunship.txt30
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_marvin.txt22
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan.txt58
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_enhanced.txt59
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_enhanced_guard.txt38
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_guard.txt43
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite.txt63
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin.txt57
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin_cqb.txt64
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin_sniper.txt64
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_prowler.txt42
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_prowler_cqb.txt38
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_soldier.txt53
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_sp_auto_titan.txt79
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_sp_npc_titan_proto_stasisgun.txt60
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_sp_soldier.txt57
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_spectre.txt47
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_stalker.txt39
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_stalker_crawling.txt32
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_super_spectre.txt58
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan.txt81
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_buddy.txt81
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_long_range.txt79
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_melee.txt80
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_melee_core.txt38
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_meteor.txt81
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_minigun.txt77
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_minigun_nuke.txt77
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_rocketeer.txt83
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_shotgun.txt82
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behavior_titan_sniper.txt82
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/behaviors.txt36
-rw-r--r--Northstar.CustomServers/scripts/aibehavior/common_schedules.txt2681
-rw-r--r--Northstar.CustomServers/scripts/aisettings/base_vehicle.txt35
-rw-r--r--Northstar.CustomServers/scripts/aisettings/classes.txt149
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_bullseye.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone.txt119
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_beam.txt9
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_cloaked.txt15
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_plasma.txt9
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_plasma_fast.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_plasma_fd.txt20
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_rocket.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_rocket_fast.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_shield.txt9
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_worker.txt99
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_drone_worker_fast.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_dropship.txt49
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_dropship_dogfighter.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_dropship_hero.txt9
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_frag_drone.txt92
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_frag_drone_fd.txt23
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_frag_drone_throwable.txt19
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_gunship.txt83
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_gunship_scripted.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_marvin.txt73
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_pilot_elite.txt107
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin.txt89
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin_cqb.txt16
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin_sniper.txt14
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_s2s.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_prowler.txt148
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier.txt201
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_bish.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_blisk.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_drone_summoner.txt4
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_hero_bear.txt14
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_hero_sarah.txt12
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_eliteguard.txt12
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_sandbox.txt7
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_specialist.txt45
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_shield_captain.txt27
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_sidearm.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_specialist.txt43
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_specialist_militia.txt44
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_spyglass.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_soldier_training_sentry.txt24
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_spectre.txt216
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_spectre_mortar.txt45
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_stalker.txt162
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling.txt33
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling_fd.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling_mossy.txt11
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_stalker_fd.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_stalker_zombie.txt15
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_stalker_zombie_mossy.txt9
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_super_spectre.txt210
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_super_spectre_aitdm.txt17
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_super_spectre_burnmeter.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_super_spectre_calmer.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_super_spectre_fd.txt17
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan.txt172
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_arc.txt10
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas.txt21
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_ion_prime_bounty.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb.txt19
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb_boss_fd.txt23
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb_bounty.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker.txt19
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_boss_fd.txt23
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_bounty.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_fd_sniper.txt7
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_mortar.txt10
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard.txt19
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard_boss_fd.txt23
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard_bounty.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto.txt43
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas.txt15
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_ion_prime.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_rocketeer.txt10
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_stickybomb.txt12
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_tone_prime.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_tracker.txt12
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_vanguard.txt12
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre.txt13
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_fighter.txt10
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_legion_prime.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_meteor.txt11
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_minigun.txt17
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_scorch_prime.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder.txt11
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_arc.txt10
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_leadwall.txt12
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_northstar_prime.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_ronin_prime.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_sniper.txt12
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_buddy.txt25
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_buddy_s2s.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_buddy_skyway.txt7
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_mortar.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_nuke.txt9
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre.txt22
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_fighter.txt26
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_fighter_berserker_core.txt34
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_legion_prime_bounty.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor.txt24
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_boss_fd.txt27
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_bounty.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_nuke.txt11
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun.txt27
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_boss_fd.txt31
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_bounty.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_nuke.txt13
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_scorch_prime_bounty.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_proto_stasisgun.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_sarah.txt21
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_sniper.txt20
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder.txt20
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_arc.txt7
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall.txt41
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_arc.txt11
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_boss_fd.txt45
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_bounty.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_shift_core.txt38
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_northstar_prime_bounty.txt6
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_rocketeer.txt24
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_rocketeer_dash_core.txt11
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper.txt22
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_boss_fd.txt26
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_bounty.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_fd.txt7
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_titan_vanguard.txt24
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_mega.txt69
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_mega_attrition.txt18
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_mega_fortwar.txt42
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_mega_frontierdefense.txt42
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_mega_nowindup.txt5
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_mega_old.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_mega_windup.txt13
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry.txt72
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_ap.txt16
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_ap_fd.txt23
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_at.txt16
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_at_fd.txt17
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_plasma.txt9
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_plasma_skyway.txt14
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_tactical_ability.txt7
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_tday.txt70
-rw-r--r--Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_windup.txt8
-rw-r--r--Northstar.CustomServers/scripts/aisettings/synced_melee_data.rson628
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_anim.gnut1395
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_auto_precache.gnut771
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut524
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_codecallbacks_common.gnut853
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_codecallbacks_player_input.gnut552
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_control_panel.gnut727
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_dogfighter.gnut343
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_entitystructs.gnut692
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_global_entities.gnut343
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_harvester.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_health_regen.gnut172
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_init.gnut40
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_loadouts_mp.gnut248
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_mapspawn.gnut217
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_menu_callbacks.gnut14
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_misc.gnut45
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_networkvars.gnut169
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_objective.gnut108
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_on_spawned.gnut508
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_pain_death_sounds.gnut455
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_passives.gnut1657
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_ping.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_powerup.gnut102
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_remote_functions_mp.gnut943
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_script_movers.gnut1783
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_script_movers_light.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_script_triggers.gnut318
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_side_notifications.gnut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_store.gnut38
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_trigger_functions.gnut585
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_utility.gnut4394
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_utility_shared.nut4069
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_viewcone.gnut520
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_vscript.gnut84
-rw-r--r--Northstar.CustomServers/scripts/vscripts/_xp.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_chatter.gnut129
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_cloak_drone.gnut678
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_drone.gnut1388
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_emp_titans.gnut181
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_gunship.gnut97
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_lethality.gnut97
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_marvin_faces.gnut226
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_marvin_jobs.gnut600
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_marvins.gnut141
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_mortar_spectres.gnut7
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_mortar_titans.gnut395
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_nuke_titans.gnut129
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_personal_shield.gnut371
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_pilots.gnut808
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_sniper_titans.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_soldiers.gnut787
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_soldiers_mp.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_spawn.gnut696
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_spawn_content.gnut879
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_spectre.gnut131
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut606
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_stationary_firing_positions.gnut261
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_suicide_spectres.gnut576
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_turret.gnut24
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_ai_utility.gnut558
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_droppod.gnut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_droppod_fireteam.gnut246
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_squad_spawn.gnut167
-rw-r--r--Northstar.CustomServers/scripts/vscripts/ai/_titan_npc_behavior.gnut404
-rw-r--r--Northstar.CustomServers/scripts/vscripts/burnmeter/_burnmeter.gnut42
-rw-r--r--Northstar.CustomServers/scripts/vscripts/class/CHardPointEntity.nut15
-rw-r--r--Northstar.CustomServers/scripts/vscripts/class/cai_basenpc.nut272
-rw-r--r--Northstar.CustomServers/scripts/vscripts/class/cbasecombatcharacter.nut28
-rw-r--r--Northstar.CustomServers/scripts/vscripts/class/cbaseentity.nut229
-rw-r--r--Northstar.CustomServers/scripts/vscripts/class/cplayer.nut355
-rw-r--r--Northstar.CustomServers/scripts/vscripts/class/ctitansoul.nut50
-rw-r--r--Northstar.CustomServers/scripts/vscripts/conversation/_battle_chatter.gnut25
-rw-r--r--Northstar.CustomServers/scripts/vscripts/conversation/_conversation_schedule.gnut629
-rw-r--r--Northstar.CustomServers/scripts/vscripts/conversation/_faction_dialogue.gnut46
-rw-r--r--Northstar.CustomServers/scripts/vscripts/conversation/_grunt_chatter_mp.gnut18
-rw-r--r--Northstar.CustomServers/scripts/vscripts/conversation/_spectre_chatter_mp.gnut18
-rw-r--r--Northstar.CustomServers/scripts/vscripts/earn_meter/sv_earn_meter.gnut508
-rw-r--r--Northstar.CustomServers/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut128
-rw-r--r--Northstar.CustomServers/scripts/vscripts/evac/_evac.gnut315
-rw-r--r--Northstar.CustomServers/scripts/vscripts/faction_xp.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_ai_frontline.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_ai_gamemodes.gnut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_capture_point.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_frontline.gnut159
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_aitdm.nut12
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_at.nut18
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_coliseum.nut12
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_cp.nut12
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ctf.nut512
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_fd.nut12
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ffa.nut14
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_lts.nut235
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_mfd.nut230
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ps.nut12
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_speedball.nut137
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_tdm.nut19
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ttdm.nut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_hardpoints.gnut33
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_riff_floor_is_lava.nut102
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/_spawnpoints.gnut0
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemode_mfd.nut66
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemodes.gnut816
-rw-r--r--Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemodes_custom.gnut20
-rw-r--r--Northstar.CustomServers/scripts/vscripts/item_inventory/sv_item_inventory.gnut60
-rw-r--r--Northstar.CustomServers/scripts/vscripts/lobby/_lobby.gnut37
-rw-r--r--Northstar.CustomServers/scripts/vscripts/lobby/_private_lobby.gnut156
-rw-r--r--Northstar.CustomServers/scripts/vscripts/melee/_melee.gnut89
-rw-r--r--Northstar.CustomServers/scripts/vscripts/melee/_melee_rewards.gnut74
-rw-r--r--Northstar.CustomServers/scripts/vscripts/melee/_melee_synced_human.gnut588
-rw-r--r--Northstar.CustomServers/scripts/vscripts/melee/_melee_synced_titan.gnut1543
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_ai_mp.gnut41
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_ai_mp.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_ai_superspectre.nut736
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_base_gametype.gnut2179
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_base_gametype_mp.gnut586
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_battery_port.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut403
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_challenges.gnut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_changemap.nut24
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_classic_mp.nut67
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut230
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_classic_mp_no_intro.gnut46
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut999
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_dropship_spawn_common.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_gamestate.nut144
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_gamestate_mp.nut707
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_goblin_dropship.nut784
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_lasermesh.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_loadout_crate.nut183
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_mp_mapspawn.gnut65
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_music.gnut107
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut1195
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_pickups_glow.gnut53
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_playlist.gnut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut352
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_score.nut133
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_serverflags.nut35
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_sniper_spectres.nut485
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_spawn_functions.nut60
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_spectre_rack.nut395
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_stats.nut78
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_titan_npc.nut818
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_titan_tether.gnut307
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_titan_transfer.nut641
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_tonecontroller.nut189
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_utility_mp.gnut18
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_vr.nut66
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/_lf_maps_shared.gnut8
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_angel_city.nut11
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_angel_city_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_black_water_canal.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_black_water_canal_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_coliseum.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_coliseum_column.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_colony02.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_colony02_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_complex3.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_crashsite3.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_drydock.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_drydock_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_eden.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_forwardbase_kodai.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_forwardbase_kodai_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_glitch.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_glitch_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_grave.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_grave_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_homestead.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_homestead_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_deck.nut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_meadow.nut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_stacks.nut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_township.nut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_traffic.nut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_uma.nut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_relic02.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_relic02_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_rise.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_rise_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_thaw.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_thaw_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_wargames.nut415
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/levels/mp_wargames_fd.nut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/pintelemetry.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/player_cloak.nut184
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/spawn.nut428
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/spawn_debug.gnut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/spawn_on_friendly.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/spawn_wave.gnut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/spawn_wave_dropship.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/pilot/_leeching.gnut493
-rw-r--r--Northstar.CustomServers/scripts/vscripts/pilot/_pilot_leeching.gnut610
-rw-r--r--Northstar.CustomServers/scripts/vscripts/pilot/_slamzoom.nut85
-rw-r--r--Northstar.CustomServers/scripts/vscripts/pilot/_zipline.gnut838
-rw-r--r--Northstar.CustomServers/scripts/vscripts/pilot/class_wallrun.gnut224
-rw-r--r--Northstar.CustomServers/scripts/vscripts/rodeo/_rodeo.gnut545
-rw-r--r--Northstar.CustomServers/scripts/vscripts/rodeo/_rodeo_titan.gnut2456
-rw-r--r--Northstar.CustomServers/scripts/vscripts/sh_calling_cards.gnut424
-rw-r--r--Northstar.CustomServers/scripts/vscripts/sh_loadouts_mp.nut19
-rw-r--r--Northstar.CustomServers/scripts/vscripts/sh_remote_functions_mp_custom.gnut20
-rw-r--r--Northstar.CustomServers/scripts/vscripts/sh_stats.gnut526
-rw-r--r--Northstar.CustomServers/scripts/vscripts/superbar/orbitalstrike.nut167
-rw-r--r--Northstar.CustomServers/scripts/vscripts/superbar/smokescreen.nut417
-rw-r--r--Northstar.CustomServers/scripts/vscripts/sv_globals.gnut0
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/_battery_generator.gnut128
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/_replacement_titans.gnut1183
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/_replacement_titans_drop.gnut443
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/_titan_commands.gnut49
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/_titan_health.gnut1072
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/_titan_hints.gnut267
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/_titan_hotdrop.gnut778
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/_titan_triple_health.gnut524
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan/class_titan.gnut77
-rw-r--r--Northstar.CustomServers/scripts/vscripts/titan_xp.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/vehicle/_vehicle_behavior.gnut6
-rw-r--r--Northstar.CustomServers/scripts/vscripts/vehicle/_vehicle_dropship_new.nut528
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapon_xp.gnut1
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_arc_cannon.nut1032
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_at_turrets.gnut284
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_ball_lightning.gnut363
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_cloaker.gnut121
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut604
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_particle_wall.gnut460
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_team_emp.gnut38
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_vortex.nut1983
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_weapon_dialogue.nut44
-rw-r--r--Northstar.CustomServers/scripts/vscripts/weapons/_weapon_utility.nut3966
-rw-r--r--bobthebob.testing/mod.json40
-rw-r--r--bobthebob.testing/scripts/vscripts/_bobtestingfunctions_mp.gnut239
-rw-r--r--bobthebob.testing/scripts/vscripts/_bobtestingfunctions_sp.gnut13
-rw-r--r--bobthebob.testing/scripts/vscripts/lobby/sh_lobby.gnut356
-rw-r--r--bobthebob.testing/scripts/vscripts/mp/levels/mp_box.nut24
-rw-r--r--bobthebob.testing/scripts/vscripts/mp/sh_revive.gnut19
-rw-r--r--bobthebob.testing/scripts/vscripts/sh_bleedout_test.gnut34
-rw-r--r--bobthebob.testing/scripts/vscripts/sh_bobtestingfunctions_mp.gnut128
-rw-r--r--bobthebob.testing/scripts/vscripts/sh_northstar_utils.gnut49
-rw-r--r--bobthebob.testing/scripts/vscripts/ui/menu_dev.nut707
-rw-r--r--bobthebob.testing/scripts/vscripts/ui/menu_map_select.nut200
-rw-r--r--bobthebob.testing/scripts/vscripts/ui/menu_mode_select.nut138
-rw-r--r--bobthebob.testing/scripts/vscripts/ui/menu_team_titan_select.nut722
486 files changed, 126898 insertions, 0 deletions
diff --git a/Northstar.Client/mod.json b/Northstar.Client/mod.json
new file mode 100644
index 000000000..f9a6fea5b
--- /dev/null
+++ b/Northstar.Client/mod.json
@@ -0,0 +1,15 @@
+{
+ "ApiId" : "Northstar.Client",
+ "Name" : "Northstar.Client",
+ "Description" : "Various ui and client changes to fix bugs and add better support for mods",
+ "Authors" : [
+ "BobTheBob"
+ ],
+ "Contacts" : [
+ "BobTheBob#1150"
+ ],
+ "Version" : "0.1",
+ "CustomScripts": [
+
+ ]
+} \ No newline at end of file
diff --git a/Northstar.Client/scripts/vscripts/ui/menu_main.nut b/Northstar.Client/scripts/vscripts/ui/menu_main.nut
new file mode 100644
index 000000000..5f292f5c4
--- /dev/null
+++ b/Northstar.Client/scripts/vscripts/ui/menu_main.nut
@@ -0,0 +1,608 @@
+global function InitMainMenu
+global function EULA_Dialog
+global function UpdateDataCenterFooter
+global function LaunchGamePurchase
+global function SP_Trial_LaunchGamePurchase
+global function LaunchSPNew
+global function LaunchSPContinue
+global function LaunchSPMissionSelect
+global function LaunchMP
+global function LaunchGame
+global function LaunchSPTrialMission
+global function GetUserSignInState
+
+struct
+{
+ var menu
+ var versionDisplay
+ var trialLabel
+} file
+
+
+void function InitMainMenu()
+{
+ RegisterSignal( "EndOnMainMenu_Open" )
+
+ var menu = GetMenu( "MainMenu" )
+ file.menu = menu
+
+ AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnMainMenu_Open )
+ AddMenuEventHandler( menu, eUIEvent.MENU_NAVIGATE_BACK, OnMainMenu_NavigateBack )
+
+ var titleRui = Hud_GetRui( Hud_GetChild( file.menu, "TitleRui" ) )
+ RuiSetImage( titleRui, "basicImage", $"rui/menu/main_menu/title")
+
+ file.versionDisplay = Hud_GetChild( menu, "versionDisplay" )
+ file.trialLabel = Hud_GetChild( menu, "TrialLabel" )
+
+ #if CONSOLE_PROG
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT", "", null, IsConsoleSignedIn )
+ #if DURANGO_PROG
+ AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_SWITCH_PROFILE", "", null, IsConsoleSignedIn )
+ #endif // DURANGO_PROG
+
+ AddMenuVarChangeHandler( "CONSOLE_isSignedIn", UpdateFooterOptions )
+ #endif // CONSOLE_PROG
+
+ #if PC_PROG
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT", "" )
+ #endif // PC_PROG
+
+ AddMenuFooterOption( menu, BUTTON_X, "#X_BUTTON_INBOX_ACCEPT", "#INBOX_ACCEPT", OpenDataCenterDialog, IsDataCenterFooterValid, UpdateDataCenterFooter )
+
+#if DEV
+ if ( DevStartPoints() )
+ AddMenuFooterOption( menu, BUTTON_Y, "#Y_BUTTON_DEV_MENU", "#DEV_MENU", OpenSinglePlayerDevMenu )
+#endif // DEV
+}
+
+#if CONSOLE_PROG
+ bool function IsConsoleSignedIn()
+ {
+ return ( GetMenuVarBool( "CONSOLE_isSignedIn" ) )
+ }
+#endif // CONSOLE_PROG
+
+void function OnMainMenu_Open()
+{
+ Signal( uiGlobal.signalDummy, "EndOnMainMenu_Open" )
+ EndSignal( uiGlobal.signalDummy, "EndOnMainMenu_Open" )
+
+ UpdatePromoData() // On script restarts this gives us the last data until the new request is complete
+ RequestMainMenuPromos() // This will be ignored if there was a recent request. "infoblock_requestInterval"
+
+ TryUnlockCollectiblesAchievement()
+ TryUnlockCompletedGameAchievements()
+
+ Hud_SetText( file.versionDisplay, GetPublicGameVersion() )
+ Hud_Show( file.versionDisplay )
+
+ thread UpdateTrialLabel()
+
+#if PC_PROG
+ ActivatePanel( GetPanel( "MainMenuPanel" ) )
+ return
+#endif // PC_PROG
+
+ int state
+ int lastState = -1
+ var panel
+ var lastPanel
+
+ while ( GetTopNonDialogMenu() == file.menu )
+ {
+ state = GetUserSignInState()
+
+ if ( state != lastState )
+ {
+ if ( state == userSignInState.SIGNED_IN )
+ panel = GetPanel( "MainMenuPanel" )
+ else
+ panel = GetPanel( "EstablishUserPanel" )
+
+ if ( panel != lastPanel )
+ {
+ ActivatePanel( panel )
+ lastPanel = panel
+ }
+ }
+
+ lastState = state
+
+ WaitFrame()
+ }
+}
+
+void function ActivatePanel( var panel )
+{
+ Assert( panel != null )
+
+ array<var> elems = GetElementsByClassname( file.menu, "MainMenuPanelClass" )
+ foreach ( elem in elems )
+ {
+ if ( elem != panel && Hud_IsVisible( elem ) )
+ HidePanel( elem )
+ }
+
+ ShowPanel( panel )
+}
+
+void function OnMainMenu_NavigateBack()
+{
+#if DURANGO_PROG
+ Durango_ShowAccountPicker()
+#endif // DURANGO_PROG
+}
+
+int function GetUserSignInState()
+{
+#if DURANGO_PROG
+ if ( Durango_InErrorScreen() )
+ {
+ return userSignInState.ERROR
+ }
+ else if ( Durango_IsSigningIn() )
+ {
+ return userSignInState.SIGNING_IN
+ }
+ else if ( !Console_IsSignedIn() && !Console_SkippedSignIn() )
+ {
+ //printt( "Console_IsSignedIn():", Console_IsSignedIn(), "Console_SkippedSignIn:", Console_SkippedSignIn() )
+ return userSignInState.SIGNED_OUT
+ }
+
+ Assert( Console_IsSignedIn() || Console_SkippedSignIn() )
+#endif
+ return userSignInState.SIGNED_IN
+}
+
+void function UpdateDataCenterFooter( InputDef data )
+{
+ EndSignal( uiGlobal.signalDummy, "EndFooterUpdateFuncs" )
+
+ int index = int( Hud_GetScriptID( data.vguiElem ) )
+ int ping
+ string name
+
+ while ( data.conditionCheckFunc() )
+ {
+ ping = GetDatacenterPing()
+ name = GetDatacenterName()
+
+ if ( ping > 0 )
+ {
+ if ( IsControllerModeActive() )
+ SetFooterText( file.menu, index, Localize( "#X_BUTTON_DATACENTER_INFO", name, ping ) )
+ else
+ SetFooterText( file.menu, index, Localize( "#DATACENTER_INFO", name, ping ) )
+ }
+ else
+ {
+ if ( IsControllerModeActive() )
+ SetFooterText( file.menu, index, "#X_BUTTON_DATACENTER_CALCULATING" )
+ else
+ SetFooterText( file.menu, index, "#DATACENTER_CALCULATING" )
+ }
+
+ WaitFrame()
+ }
+}
+
+bool function IsDataCenterFooterValid()
+{
+ #if PC_PROG
+ return ( uiGlobal.activeMenu == file.menu )
+ #else
+ return ( uiGlobal.activeMenu == file.menu ) && Console_IsOnline() && Console_IsSignedIn()
+ #endif
+}
+
+void function SP_Trial_LaunchGamePurchase()
+{
+ Disconnect()
+ LaunchGamePurchase()
+}
+
+void function LaunchGamePurchase()
+{
+ ShowGamePurchaseStore()
+}
+
+void function LaunchSPNew()
+{
+ uiGlobal.launching = eLaunching.SINGLEPLAYER_NEW
+ LaunchGame()
+}
+
+void function LaunchSPContinue()
+{
+ uiGlobal.launching = eLaunching.SINGLEPLAYER_CONTINUE
+ LaunchGame()
+}
+
+void function LaunchSPMissionSelect()
+{
+ uiGlobal.launching = eLaunching.SINGLEPLAYER_MISSION_SELECT
+ LaunchGame()
+}
+
+void function LaunchSPTrialMission()
+{
+ uiGlobal.launching = eLaunching.SINGLEPLAYER_MISSION_SELECT
+ SPTrialMission_Start()
+}
+
+void function LaunchMP()
+{
+ uiGlobal.launching = eLaunching.MULTIPLAYER
+ LaunchGame()
+}
+
+void function LaunchGame()
+{
+ Assert( uiGlobal.launching == eLaunching.SINGLEPLAYER_NEW ||
+ uiGlobal.launching == eLaunching.SINGLEPLAYER_CONTINUE ||
+ uiGlobal.launching == eLaunching.SINGLEPLAYER_MISSION_SELECT ||
+ uiGlobal.launching == eLaunching.MULTIPLAYER ||
+ uiGlobal.launching == eLaunching.MULTIPLAYER_INVITE )
+
+ if ( uiGlobal.activeMenu == GetMenu( "PlayVideoMenu" ) )
+ {
+ SetVideoCompleteFunc( null )
+ CloseActiveMenu()
+ }
+
+ if ( !IsGamePartiallyInstalled() )
+ {
+ DoGameNeedsToInstallDialog()
+ return
+ }
+
+ // Because accepting an invite tries to launch the game we need this here
+ if ( !IsGameFullyInstalled() )
+ {
+ printt( "Game is not fully installed." )
+
+ if ( uiGlobal.launching == eLaunching.SINGLEPLAYER_CONTINUE )
+ {
+ string saveName = GetSaveName()
+ string mapName = SaveGame_GetMapName( saveName )
+ int startPointIndex = SaveGame_GetStartPoint( saveName )
+
+ printt( mapName )
+ printt( startPointIndex )
+
+ bool isInTraining = (mapName == "sp_training" && startPointIndex < 5) // "Titanfall" start point
+
+ if ( !isInTraining )
+ {
+ DoGameNeedsToInstallDialog()
+ return
+ }
+
+ printt( "Allowing 'continue' option to load into training." )
+ }
+ else if ( uiGlobal.launching != eLaunching.SINGLEPLAYER_NEW )
+ {
+ DoGameNeedsToInstallDialog()
+ return
+ }
+ }
+
+ #if CONSOLE_PROG
+ if ( !Console_IsSignedIn() )
+ {
+ printt( "Not signed in." )
+ return
+ }
+
+ if ( GetEULAVersionAccepted() < 1 ) // Treat as binary for now, as discussed with Preston.
+ {
+ if ( uiGlobal.activeMenu == GetMenu( "EULADialog" ) )
+ return
+
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ CloseActiveMenu( true )
+
+ EULA_Dialog()
+ return
+ }
+
+ if ( Nucleus_GetState() == NUCLEUS_STATE_INACTIVE )
+ Nucleus_Login()
+
+ if ( !uiGlobal.triedNucleusRegistration && uiGlobal.launching == eLaunching.MULTIPLAYER && !Nucleus_GetSkipRegistration() )
+ {
+ uiGlobal.triedNucleusRegistration = true
+ thread Nucleus_HandleLoginResponse()
+ return
+ }
+
+ if ( !GetConVarBool( "gamma_adjusted" ) )
+ {
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ CloseActiveMenu( true )
+
+ AdvanceMenu( GetMenu( "GammaMenu" ) )
+ return
+ }
+ #endif // CONSOLE_PROG
+
+ if ( ( uiGlobal.launching == eLaunching.MULTIPLAYER || uiGlobal.launching == eLaunching.MULTIPLAYER_INVITE ) && !IsAdvocateGiftComplete() )
+ {
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ CloseActiveMenu( true )
+
+ AdvanceMenu( GetMenu( "AdvocateGiftDialog" ) )
+ return
+ }
+
+ SetMenuWasMultiplayerPlayedLast( true )
+ if ( uiGlobal.launching == eLaunching.SINGLEPLAYER_NEW )
+ NewGameSelected()
+ else if ( uiGlobal.launching == eLaunching.SINGLEPLAYER_CONTINUE )
+ LoadLastCheckpoint()
+ else if ( uiGlobal.launching == eLaunching.SINGLEPLAYER_MISSION_SELECT )
+ AdvanceMenu( GetMenu( "SinglePlayerMenu" ) )
+ else
+ thread StartSearchForPartyServer()
+
+ uiGlobal.launching = eLaunching.FALSE
+}
+
+void function StartSearchForPartyServer()
+{
+ printt( "StartSearchForPartyServer" )
+
+#if DURANGO_PROG
+ // IMPORTANT: As a safety measure leave any party view we are in at this point.
+ // Otherwise, if you are unlucky enough to get stuck in a party view, you will
+ // trash its state by pointing it to your private lobby.
+ Durango_LeaveParty()
+
+ // IMPORTANT: It's possible that you have permission to play multiplayer
+ // because your friend is signed in with his gold account on your machine,
+ // but once that guy signs out, you shouldn't be able to play like you have
+ // xboxlive gold anymore. To fix this, we need to check permissions periodically.
+ // One of the places where we do this periodic check is when you press "PLAY"
+ printt( "Durango - verifying MP permissions" )
+ if ( !Console_HasPermissionToPlayMultiplayer() )
+ Durango_VerifyMultiplayerPermissions()
+#endif // DURANGO_PROG
+
+ Signal( uiGlobal.signalDummy, "OnCancelConnect" )
+ EndSignal( uiGlobal.signalDummy, "OnCancelConnect" )
+
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ CloseActiveMenu( true )
+ OpenConnectingDialog()
+
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "" )
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, "" )
+
+ Hud_Show( uiGlobal.ConfirmMenuMessage )
+ Hud_Show( uiGlobal.ConfirmMenuErrorCode )
+
+#if DURANGO_PROG
+ if( !Console_IsOnline() )
+ {
+ printt( "Durango - finding empty party server failed - not online" )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#DURANGO_NOT_ONLINE" )
+ return
+ }
+#endif // DURANGO_PROG
+
+#if PS4_PROG
+ if( !Console_IsOnline() )
+ {
+ printt( "PS4 - finding empty party server failed - not online" )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#INTERNET_NOT_FOUND" )
+ return
+ }
+
+ if ( PS4_isNetworkingDown() )
+ {
+ printt( "PS4 - finding empty party server failed - networking is down" )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_CANNOT_CONNECT" )
+ return
+ }
+
+ if( !PS4_isUserNetworkingEnabled() )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_CHECKING_USABILITY" )
+ PS4_ScheduleUserNetworkingEnabledTest()
+ WaitFrame()
+
+ if( !PS4_isUserNetworkingResolved() )
+ {
+ printt( "PS4 - finding empty party server stalled - networking isn't resolved yet" )
+ // offer cancel ??
+ while( !PS4_isUserNetworkingResolved())
+ WaitFrame()
+ }
+
+ if( PS4_getUserNetworkingResolution() == PS4_NETWORK_STATUS_NOT_LOGGED_IN )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, string(PS4_getUserNetworkingErrorStatus()) )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_LOGIN" )
+
+ Ps4_LoginDialog_Schedule()
+ while( Ps4_LoginDialog_Running() )
+ WaitFrame()
+
+ PS4_ScheduleUserNetworkingEnabledTest()
+ WaitFrame()
+ if( !PS4_isUserNetworkingResolved() )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, "" )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_CHECKING_USABILITY" )
+ while( !PS4_isUserNetworkingResolved())
+ WaitFrame()
+ }
+ }
+
+ if( PS4_getUserNetworkingResolution() == PS4_NETWORK_STATUS_AGE_RESTRICTION )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, string(PS4_getUserNetworkingErrorStatus()) )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#MULTIPLAYER_AGE_RESTRICTED" )
+ return
+ }
+
+ if( PS4_getUserNetworkingResolution() == PS4_NETWORK_STATUS_IN_ERROR )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, string(PS4_getUserNetworkingErrorStatus()) )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_HAD_ERROR" )
+ return
+ }
+
+ if( !PS4_isUserNetworkingEnabled() )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, string(PS4_getUserNetworkingErrorStatus()) )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_NOT_ALLOWED" )
+ return
+ }
+
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, "" )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "" )
+ }
+
+ if ( !Ps4_PSN_Is_Loggedin() )
+ {
+ Ps4_LoginDialog_Schedule()
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_LOGIN" )
+
+ while( Ps4_LoginDialog_Running() )
+ WaitFrame()
+
+ if ( !Ps4_PSN_Is_Loggedin() )
+ return
+ }
+
+ if( Ps4_CheckPlus_Schedule() )
+ {
+ while( Ps4_CheckPlus_Running() )
+ WaitFrame()
+ if( !Ps4_CheckPlus_Allowed() )
+ {
+ if( Ps4_CheckPlus_GetLastRequestResults() != 0 )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, string( Ps4_CheckPlus_GetLastRequestResults()) )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_HAD_ERROR" )
+ return
+ }
+
+ if( Ps4_ScreenPlusDialog_Schedule() )
+ {
+ while( Ps4_ScreenPlusDialog_Running() )
+ WaitFrame()
+ if( !Ps4_ScreenPlusDialog_Allowed() )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#PSN_MUST_BE_PLUS_USER" )
+ return
+ }
+ }
+ else
+ {
+ return
+ }
+ }
+ }
+
+ Hud_SetText( uiGlobal.ConfirmMenuErrorCode, "" )
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "" )
+#endif // #if PS4_PROG
+
+ printt( "Checking if this user has permission to play MP\n" )
+ if ( !Console_HasPermissionToPlayMultiplayer() )
+ {
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#MULTIPLAYER_NOT_AVAILABLE" )
+ return
+ }
+
+ Plat_ShowUGCRestrictionNotice()
+ while ( Plat_IsSystemMessageDialogOpen() )
+ WaitFrame()
+
+ Plat_ShowChatRestrictionNotice()
+ while ( Plat_IsSystemMessageDialogOpen() )
+ WaitFrame()
+
+#if PC_PROG
+ if ( Origin_IsEnabled() )
+ {
+ Origin_RequestTicket()
+ Hud_SetText( uiGlobal.ConfirmMenuMessage, "#WAITING_FOR_ORIGIN" )
+
+ while ( !Origin_IsReady() )
+ WaitFrame()
+ }
+#endif // PC_PROG
+
+ printt( "SearchForPartyServer" )
+ SetMenuWasMultiplayerPlayedLast( true )
+ SearchForPartyServer()
+
+ Hud_SetAutoText( uiGlobal.ConfirmMenuMessage, "", HATT_MATCHMAKING_EMPTY_SERVER_SEARCH_STATE, 0 )
+ Hud_SetAutoText( uiGlobal.ConfirmMenuErrorCode, "", HATT_MATCHMAKING_EMPTY_SERVER_SEARCH_ERROR, 0 )
+}
+
+void function EULA_Dialog()
+{
+ if ( GetUserSignInState() != userSignInState.SIGNED_IN )
+ return
+
+ if ( GetEULAVersionAccepted() >= 1 )
+ return
+
+ AdvanceMenu( GetMenu( "EULADialog" ) )
+}
+
+void function DoGameNeedsToInstallDialog()
+{
+ DialogData dialogData
+ dialogData.header = "#MENU_WAIT_FOR_INTALL"
+
+ int installProgress = int( GetGameFullyInstalledProgress()*100 )
+
+ if ( uiGlobal.launching == eLaunching.MULTIPLAYER && IsGamePartiallyInstalled() && !Script_IsRunningTrialVersion() )
+ {
+ dialogData.message = Localize("#MENU_WAIT_FOR_INTALL_HINT", installProgress )
+ AddDialogButton( dialogData, "#YES", LaunchSPNew )
+ AddDialogButton( dialogData, "#NO" )
+ }
+ else
+ {
+ dialogData.message = Localize("#MENU_WAIT_FOR_INTALL_HINT_NOTRAINING", installProgress )
+ AddDialogButton( dialogData, "#OK" )
+ }
+
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+ AddDialogFooter( dialogData, "#B_BUTTON_CANCEL" )
+
+ OpenDialog( dialogData )
+}
+
+void function UpdateTrialLabel()
+{
+ bool isTrialVersion
+ bool lastIsTrialVersion = Script_IsRunningTrialVersion()
+
+ Hud_SetVisible( file.trialLabel, lastIsTrialVersion )
+
+ while ( GetTopNonDialogMenu() == file.menu )
+ {
+ isTrialVersion = Script_IsRunningTrialVersion()
+
+ if ( isTrialVersion != lastIsTrialVersion )
+ Hud_SetVisible( file.trialLabel, isTrialVersion )
+
+ lastIsTrialVersion = isTrialVersion
+
+ WaitFrame()
+ }
+}
+
+void function OpenSinglePlayerDevMenu( var button )
+{
+ AdvanceMenu( GetMenu( "SinglePlayerDevMenu" ) )
+}
diff --git a/Northstar.Client/scripts/vscripts/ui/menu_private_match.nut b/Northstar.Client/scripts/vscripts/ui/menu_private_match.nut
new file mode 100644
index 000000000..13218cd3c
--- /dev/null
+++ b/Northstar.Client/scripts/vscripts/ui/menu_private_match.nut
@@ -0,0 +1,777 @@
+untyped
+
+global function MenuPrivateMatch_Init
+
+global function InitPrivateMatchMenu
+
+global function HandleLockedCustomMenuItem
+global function GetMapImageForMapName
+
+struct
+{
+ var menu
+
+ array matchStatusRuis
+
+ array MMDevStringElems
+
+ array teamSlotBackgrounds
+ array teamSlotBackgroundsNeutral
+
+ var enemyTeamBackgroundPanel
+ var friendlyTeamBackgroundPanel
+ var enemyTeamBackground
+ var friendlyTeamBackground
+ var enemyPlayersPanel
+ var friendlyPlayersPanel
+
+ var listFriendlies
+ var listEnemies
+
+ var nextMapNameLabel
+ var nextGameModeLabel
+
+ var inviteRoomButton
+ var inviteFriendsButton
+
+ int inboxHeaderIndex
+
+ int customizeHeaderIndex
+ var pilotButton
+ var titanButton
+ var boostsButton
+ var storeButton
+ var factionButton
+ var bannerButton
+ var patchButton
+ var statsButton
+ var dpadCommsButton
+
+ var playHeader
+ var customizeHeader
+ var callsignHeader
+ var storeHeader
+
+ var startMatchButton
+ var selectMapButton
+ var selectModeButton
+ var matchSettingsButton
+
+ var callsignCard
+
+ var spectatorLabel
+
+ var matchSettingsPanel
+
+ ComboStruct &lobbyComboStruct
+} file
+
+const table<asset> mapImages =
+{
+ mp_forwardbase_kodai = $"loadscreens/mp_forwardbase_kodai_lobby",
+ mp_grave = $"loadscreens/mp_grave_lobby",
+ mp_homestead = $"loadscreens/mp_homestead_lobby",
+ mp_thaw = $"loadscreens/mp_thaw_lobby",
+ mp_black_water_canal = $"loadscreens/mp_black_water_canal_lobby",
+ mp_eden = $"loadscreens/mp_eden_lobby",
+ mp_drydock = $"loadscreens/mp_drydock_lobby",
+ mp_crashsite3 = $"loadscreens/mp_crashsite3_lobby",
+ mp_complex3 = $"loadscreens/mp_complex3_lobby",
+ mp_angel_city = $"loadscreens/mp_angle_city_r2_lobby",
+ mp_colony02 = $"loadscreens/mp_colony02_lobby",
+ mp_glitch = $"loadscreens/mp_glitch_lobby",
+ mp_lf_stacks = $"loadscreens/mp_stacks_lobby",
+ mp_lf_meadow = $"loadscreens/mp_meadow_lobby",
+ mp_lf_deck = $"loadscreens/mp_lf_deck_lobby",
+ mp_lf_traffic = $"loadscreens/mp_lf_traffic_lobby",
+ mp_coliseum = $"loadscreens/mp_coliseum_lobby",
+ mp_coliseum_column = $"loadscreens/mp_coliseum_column_lobby",
+ mp_relic02 = $"loadscreens/mp_relic02_lobby",
+ mp_wargames = $"loadscreens/mp_wargames_lobby",
+ mp_rise = $"loadscreens/mp_rise_lobby",
+ mp_lf_township = $"loadscreens/mp_lf_township_lobby",
+ mp_lf_uma = $"loadscreens/mp_lf_uma_lobby",
+}
+
+void function MenuPrivateMatch_Init()
+{
+ PrecacheHUDMaterial( $"ui/menu/common/menu_background_neutral" )
+ PrecacheHUDMaterial( $"ui/menu/common/menu_background_imc" )
+ PrecacheHUDMaterial( $"ui/menu/common/menu_background_militia" )
+ PrecacheHUDMaterial( $"ui/menu/common/menu_background_imc_blur" )
+ PrecacheHUDMaterial( $"ui/menu/common/menu_background_militia_blur" )
+ PrecacheHUDMaterial( $"ui/menu/common/menu_background_neutral_blur" )
+ PrecacheHUDMaterial( $"ui/menu/common/menu_background_blackMarket" )
+ PrecacheHUDMaterial( $"ui/menu/rank_menus/ranked_FE_background" )
+
+ PrecacheHUDMaterial( $"ui/menu/lobby/friendly_slot" )
+ PrecacheHUDMaterial( $"ui/menu/lobby/friendly_player" )
+ PrecacheHUDMaterial( $"ui/menu/lobby/enemy_slot" )
+ PrecacheHUDMaterial( $"ui/menu/lobby/enemy_player" )
+ PrecacheHUDMaterial( $"ui/menu/lobby/neutral_slot" )
+ PrecacheHUDMaterial( $"ui/menu/lobby/neutral_player" )
+ PrecacheHUDMaterial( $"ui/menu/lobby/player_hover" )
+ PrecacheHUDMaterial( $"ui/menu/main_menu/motd_background" )
+ PrecacheHUDMaterial( $"ui/menu/main_menu/motd_background_happyhour" )
+
+ AddUICallback_OnLevelInit( OnPrivateLobbyLevelInit )
+}
+
+asset function GetMapImageForMapName( string mapName )
+{
+ if ( mapName in mapImages )
+ return mapImages[mapName]
+
+ return $""
+}
+
+
+void function InitPrivateMatchMenu()
+{
+ var menu = GetMenu( "PrivateLobbyMenu" )
+ file.menu = menu
+
+ AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnPrivateMatchMenu_Open )
+ AddMenuEventHandler( menu, eUIEvent.MENU_CLOSE, OnLobbyMenu_Close )
+ AddMenuEventHandler( menu, eUIEvent.MENU_NAVIGATE_BACK, OnLobbyMenu_NavigateBack )
+
+ file.startMatchButton = Hud_GetChild( menu, "StartMatchButton" )
+ Hud_AddEventHandler( file.startMatchButton, UIE_CLICK, OnStartMatchButton_Activate )
+
+ RegisterUIVarChangeCallback( "privatematch_map", Privatematch_map_Changed )
+ RegisterUIVarChangeCallback( "privatematch_mode", Privatematch_mode_Changed )
+ RegisterUIVarChangeCallback( "privatematch_starting", Privatematch_starting_Changed )
+ RegisterUIVarChangeCallback( "gameStartTime", GameStartTime_Changed )
+
+ file.matchStatusRuis = GetElementsByClassnameForMenus( "MatchmakingStatusRui", uiGlobal.allMenus )
+ file.MMDevStringElems = GetElementsByClassnameForMenus( "MMDevStringClass", uiGlobal.allMenus )
+
+ file.friendlyPlayersPanel = Hud_GetChild( menu, "MatchFriendliesPanel" )
+ file.enemyPlayersPanel = Hud_GetChild( menu, "MatchEnemiesPanel" )
+
+ file.listFriendlies = Hud_GetChild( file.friendlyPlayersPanel, "ListFriendlies" )
+ file.listEnemies = Hud_GetChild( file.enemyPlayersPanel, "ListEnemies" )
+
+ file.friendlyTeamBackgroundPanel = Hud_GetChild( file.friendlyPlayersPanel, "LobbyFriendlyTeamBackground" )
+ file.enemyTeamBackgroundPanel = Hud_GetChild( file.enemyPlayersPanel, "LobbyEnemyTeamBackground" )
+
+#if PC_PROG
+ var panelSize = Hud_GetSize( file.enemyPlayersPanel )
+ Hud_SetSize( Hud_GetChild( menu, "LobbyChatBox" ), panelSize[0], panelSize[1] )
+#endif // #if PC_PROG
+
+ file.friendlyTeamBackground = Hud_GetChild( file.friendlyTeamBackgroundPanel, "TeamBackground" )
+ file.enemyTeamBackground = Hud_GetChild( file.enemyTeamBackgroundPanel, "TeamBackground" )
+
+ file.teamSlotBackgrounds = GetElementsByClassnameForMenus( "LobbyTeamSlotBackgroundClass", uiGlobal.allMenus )
+ file.teamSlotBackgroundsNeutral = GetElementsByClassnameForMenus( "LobbyTeamSlotBackgroundNeutralClass", uiGlobal.allMenus )
+
+ file.nextMapNameLabel = Hud_GetChild( menu, "NextMapName" )
+ file.nextGameModeLabel = Hud_GetChild( menu, "NextGameModeName" )
+
+ file.callsignCard = Hud_GetChild( menu, "CallsignCard" )
+
+ file.spectatorLabel = Hud_GetChild( menu, "SpectatorLabel" )
+
+ file.matchSettingsPanel = Hud_GetChild( menu, "MatchSettings" )
+
+ SetupComboButtons( menu, file.startMatchButton, file.listFriendlies )
+
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT", "" )
+ AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
+
+ AddMenuFooterOption( menu, BUTTON_Y, "#Y_BUTTON_SWITCH_TEAMS", "#SWITCH_TEAMS", PCSwitchTeamsButton_Activate, CanSwitchTeams )
+ AddMenuFooterOption( menu, BUTTON_X, "#X_BUTTON_MUTE", "#MOUSE2_MUTE", null, CanMute )
+ AddMenuFooterOption( menu, BUTTON_SHOULDER_RIGHT, "#RB_TRIGGER_TOGGLE_SPECTATE", "#SPECTATE_TEAM", PCToggleSpectateButton_Activate, CanSwitchTeams )
+
+ AddMenuVarChangeHandler( "focus", UpdateFooterOptions )
+ AddMenuVarChangeHandler( "isFullyConnected", UpdateFooterOptions )
+ AddMenuVarChangeHandler( "isPartyLeader", UpdateFooterOptions )
+ AddMenuVarChangeHandler( "isPrivateMatch", UpdateFooterOptions )
+ #if DURANGO_PROG
+ AddMenuVarChangeHandler( "DURANGO_canInviteFriends", UpdateFooterOptions )
+ AddMenuVarChangeHandler( "DURANGO_isJoinable", UpdateFooterOptions )
+ AddMenuVarChangeHandler( "DURANGO_isGameFullyInstalled", UpdateFooterOptions )
+ #elseif PS4_PROG
+ AddMenuVarChangeHandler( "PS4_canInviteFriends", UpdateFooterOptions )
+ #elseif PC_PROG
+ AddMenuVarChangeHandler( "ORIGIN_isEnabled", UpdateFooterOptions )
+ AddMenuVarChangeHandler( "ORIGIN_isJoinable", UpdateFooterOptions )
+ #endif
+}
+
+
+void function OnSelectMapButton_Activate( var button )
+{
+ if ( Hud_IsLocked( button ) )
+ return
+
+ AdvanceMenu( GetMenu( "MapsMenu" ) )
+}
+
+void function OnSelectModeButton_Activate( var button )
+{
+ if ( Hud_IsLocked( button ) )
+ return
+
+ AdvanceMenu( GetMenu( "ModesMenu" ) )
+}
+
+void function OnSelectMatchSettings_Activate( var button )
+{
+ if ( Hud_IsLocked( button ) )
+ return
+
+ AdvanceMenu( GetMenu( "MatchSettingsMenu" ) )
+}
+
+void function SetupComboButtons( var menu, var navUpButton, var navDownButton )
+{
+ ComboStruct comboStruct = ComboButtons_Create( menu )
+ file.lobbyComboStruct = comboStruct
+
+ comboStruct.navUpButton = navUpButton
+ comboStruct.navDownButton = navDownButton
+
+ int headerIndex = 0
+ int buttonIndex = 0
+ file.playHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_PRIVATE_MATCH" )
+ var selectModeButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_SELECT_MODE" )
+ file.selectModeButton = selectModeButton
+ Hud_AddEventHandler( selectModeButton, UIE_CLICK, OnSelectModeButton_Activate )
+ var selectMapButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_SELECT_MAP" )
+ file.selectMapButton = selectMapButton
+ Hud_AddEventHandler( selectMapButton, UIE_CLICK, OnSelectMapButton_Activate )
+
+ file.matchSettingsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_MATCH_SETTINGS" )
+ Hud_AddEventHandler( file.matchSettingsButton, UIE_CLICK, OnSelectMatchSettings_Activate )
+
+ var friendsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_INVITE_FRIENDS" )
+ file.inviteFriendsButton = friendsButton
+ Hud_AddEventHandler( friendsButton, UIE_CLICK, InviteFriendsIfAllowed )
+
+ headerIndex++
+ buttonIndex = 0
+ file.customizeHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_LOADOUTS" )
+ file.customizeHeaderIndex = headerIndex
+ var pilotButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_PILOT" )
+ file.pilotButton = pilotButton
+
+ Hud_AddEventHandler( pilotButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "EditPilotLoadoutsMenu" ) ) )
+ var titanButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_TITAN" )
+ file.titanButton = titanButton
+ Hud_AddEventHandler( titanButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "EditTitanLoadoutsMenu" ) ) )
+ file.boostsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_BOOSTS" )
+ Hud_AddEventHandler( file.boostsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "BurnCardMenu" ) ) )
+ file.dpadCommsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_COMMS" )
+ Hud_AddEventHandler( file.dpadCommsButton, UIE_CLICK, OnDpadCommsButton_Activate )
+
+ headerIndex++
+ buttonIndex = 0
+ file.callsignHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_CALLSIGN" )
+ file.bannerButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_BANNER" )
+ Hud_AddEventHandler( file.bannerButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "CallsignCardSelectMenu" ) ) )
+ file.patchButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_PATCH" )
+ Hud_AddEventHandler( file.patchButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "CallsignIconSelectMenu" ) ) )
+ file.factionButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_FACTION" )
+ Hud_AddEventHandler( file.factionButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "FactionChoiceMenu" ) ) )
+ file.statsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_STATS" )
+ Hud_AddEventHandler( file.statsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ViewStatsMenu" ) ) )
+
+ file.callsignCard = Hud_GetChild( menu, "CallsignCard" )
+
+ headerIndex++
+ buttonIndex = 0
+ file.storeHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_STORE" )
+ file.storeButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_STORE_BROWSE" )
+ Hud_AddEventHandler( file.storeButton, UIE_CLICK, OnStoreButton_Activate )
+ var storeNewReleasesButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_STORE_NEW_RELEASES" )
+ Hud_AddEventHandler( storeNewReleasesButton, UIE_CLICK, OnStoreNewReleasesButton_Activate )
+ var storeBundlesButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_STORE_BUNDLES" )
+ Hud_AddEventHandler( storeBundlesButton, UIE_CLICK, OnStoreBundlesButton_Activate )
+
+ headerIndex++
+ buttonIndex = 0
+ var settingsHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_SETTINGS" )
+ var controlsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_CONTROLS" )
+ Hud_AddEventHandler( controlsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ControlsMenu" ) ) )
+ #if CONSOLE_PROG
+ var avButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#AUDIO_VIDEO" )
+ Hud_AddEventHandler( avButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "AudioVideoMenu" ) ) )
+ #elseif PC_PROG
+ var videoButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#AUDIO" )
+ Hud_AddEventHandler( videoButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "AudioMenu" ) ) )
+ var soundButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#VIDEO" )
+ Hud_AddEventHandler( soundButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "VideoMenu" ) ) )
+ #endif
+ var knbButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#KNB_MENU_HEADER" )
+ Hud_AddEventHandler( knbButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "KnowledgeBaseMenu" ) ) )
+
+ ComboButtons_Finalize( comboStruct )
+}
+
+
+bool function IsPlayerListFocused()
+{
+ var focusedItem = GetFocus()
+
+ // The check for GetScriptID existing isn't ideal, but if the text chat text output element has focus it will script error otherwise
+ return ( (focusedItem != null) && ("GetScriptID" in focusedItem) && (Hud_GetScriptID( focusedItem ) == "PlayerListButton") )
+}
+
+bool function MatchResultsExist()
+{
+ return true // TODO
+}
+
+bool function CanSwitchTeams()
+{
+ return ( GetMenuVarBool( "isPrivateMatch" ) && ( level.ui.privatematch_starting != ePrivateMatchStartState.STARTING ) )
+}
+
+bool function CanMute()
+{
+ return IsPlayerListFocused()
+}
+
+void function OnLobbyMenu_Open()
+{
+ Assert( IsConnected() )
+
+ UpdatePrivateMatchButtons()
+
+ thread UpdateLobbyUI()
+ thread LobbyMenuUpdate( GetMenu( "PrivateLobbyMenu" ) )
+
+ if ( uiGlobal.activeMenu == GetMenu( "PrivateLobbyMenu" ) )
+ UI_SetPresentationType( ePresentationType.NO_MODELS )
+
+ if ( IsFullyConnected() )
+ {
+ entity player = GetUIPlayer()
+ if ( !IsValid( player ) )
+ return
+
+ while ( player.GetPersistentVarAsInt( "initializedVersion" ) < PERSISTENCE_INIT_VERSION )
+ {
+ WaitFrame()
+ }
+
+ UpdateCallsignElement( file.callsignCard )
+ RefreshCreditsAvailable()
+
+ bool emotesAreEnabled = EmotesEnabled()
+ // "Customize"
+ {
+ bool anyNewPilotItems = HasAnyNewPilotItems( player )
+ bool anyNewTitanItems = HasAnyNewTitanItems( player )
+ bool anyNewBoosts = HasAnyNewBoosts( player )
+ bool anyNewCommsIcons = false // emotesAreEnabled ? HasAnyNewDpadCommsIcons( player ) : false
+ bool anyNewCustomizeHeader = (anyNewPilotItems || anyNewTitanItems || anyNewBoosts || anyNewCommsIcons)
+
+ RuiSetBool( Hud_GetRui( file.customizeHeader ), "isNew", anyNewCustomizeHeader )
+ ComboButton_SetNew( file.pilotButton, anyNewPilotItems )
+ ComboButton_SetNew( file.titanButton, anyNewTitanItems )
+ ComboButton_SetNew( file.boostsButton, anyNewBoosts )
+ ComboButton_SetNew( file.dpadCommsButton, anyNewCommsIcons )
+
+ if ( !emotesAreEnabled )
+ {
+ Hud_Hide( file.dpadCommsButton )
+ ComboButtons_ResetColumnFocus( file.lobbyComboStruct )
+ }
+ else
+ {
+ Hud_Show( file.dpadCommsButton )
+ }
+ }
+
+ // "Store"
+ {
+ bool storeIsNew = DLCStoreShouldBeMarkedAsNew()
+ RuiSetBool( Hud_GetRui( file.storeHeader ), "isNew", storeIsNew )
+ ComboButton_SetNew( file.storeButton, storeIsNew )
+ }
+
+ // "Callsign"
+ {
+ bool anyNewBanners = HasAnyNewCallsignBanners( player )
+ bool anyNewPatches = HasAnyNewCallsignPatches( player )
+ bool anyNewFactions = HasAnyNewFactions( player )
+ bool anyNewCallsignHeader = (anyNewBanners || anyNewPatches || anyNewFactions)
+
+ RuiSetBool( Hud_GetRui( file.callsignHeader ), "isNew", anyNewCallsignHeader )
+ ComboButton_SetNew( file.bannerButton, anyNewBanners )
+ ComboButton_SetNew( file.patchButton, anyNewPatches )
+ ComboButton_SetNew( file.factionButton, anyNewFactions )
+ }
+ }
+}
+
+void function LobbyMenuUpdate( var menu )
+{
+ EndSignal( uiGlobal.signalDummy, "CleanupInGameMenus" )
+
+ while ( GetTopNonDialogMenu() == menu )
+ {
+ WaitFrame()
+ }
+}
+
+
+
+void function OnLobbyMenu_Close()
+{
+ Signal( uiGlobal.signalDummy, "OnCloseLobbyMenu" )
+}
+
+void function OnLobbyMenu_NavigateBack()
+{
+ LeaveDialog()
+}
+
+function GameStartTime_Changed()
+{
+ printt( "GameStartTime_Changed", level.ui.gameStartTime )
+ UpdateGameStartTimeCounter()
+}
+
+function UpdateGameStartTimeCounter()
+{
+ if ( level.ui.gameStartTime == null )
+ {
+ MatchmakingSetSearchText( "" )
+ MatchmakingSetCountdownTimer( 0.0 )
+ HideMatchmakingStatusIcons()
+ return
+ }
+
+ MatchmakingSetSearchText( "#STARTING_IN_LOBBY" )
+ MatchmakingSetCountdownTimer( expect float( level.ui.gameStartTime + 0.0 ) )
+ ShowMatchmakingStatusIcons()
+}
+
+function UpdateDebugStatus()
+{
+ EndSignal( uiGlobal.signalDummy, "CleanupInGameMenus" )
+
+ OnThreadEnd(
+ function() : ()
+ {
+ foreach ( elem in file.MMDevStringElems )
+ Hud_Hide( elem )
+ }
+ )
+
+ foreach ( elem in file.MMDevStringElems )
+ Hud_Show( elem )
+
+ while ( true )
+ {
+ local strstr = GetLobbyDevString()
+ foreach ( elem in file.MMDevStringElems )
+ Hud_SetText( elem, strstr )
+
+ WaitFrameOrUntilLevelLoaded()
+ }
+}
+
+void function SetMapInfo( string mapName )
+{
+ var nextMapImage = Hud_GetChild( file.menu, "NextMapImage" )
+
+ asset mapImage = GetMapImageForMapName( mapName )
+ RuiSetImage( Hud_GetRui( nextMapImage ), "basicImage", mapImage )
+ Hud_Show( nextMapImage )
+
+ Hud_SetText( file.nextMapNameLabel, GetMapDisplayName( mapName ) )
+}
+
+void function SetModeInfo( string modeName )
+{
+ var nextModeIcon = Hud_GetChild( file.menu, "NextModeIcon" )
+ RuiSetImage( Hud_GetRui( nextModeIcon ), "basicImage", GetPlaylistThumbnailImage( modeName ) )
+ Hud_Show( nextModeIcon )
+
+ Hud_SetText( file.nextGameModeLabel, GetGameModeDisplayName( modeName ) )
+}
+
+function Privatematch_map_Changed()
+{
+ if ( !IsPrivateMatch() )
+ return
+ if ( !IsLobby() )
+ return
+
+ string mapName = PrivateMatch_GetSelectedMap()
+ SetMapInfo( mapName )
+}
+
+function Privatematch_mode_Changed()
+{
+ if ( !IsPrivateMatch() )
+ return
+ if ( !IsLobby() )
+ return
+
+ string modeName = PrivateMatch_GetSelectedMode()
+ SetModeInfo( modeName )
+
+ UpdatePrivateMatchButtons()
+ UpdateMatchSettingsForGamemode()
+}
+
+
+function Privatematch_starting_Changed()
+{
+ if ( !IsPrivateMatch() )
+ return
+ if ( !IsLobby() )
+ return
+
+ UpdatePrivateMatchButtons()
+ UpdateFooterOptions()
+}
+
+
+function UpdatePrivateMatchButtons()
+{
+ var menu = file.menu
+
+ if ( level.ui.privatematch_starting == ePrivateMatchStartState.STARTING )
+ {
+ RHud_SetText( file.startMatchButton, "#STOP_MATCH" )
+ Hud_SetLocked( file.selectMapButton, true )
+ Hud_SetLocked( file.selectModeButton, true )
+ Hud_SetLocked( file.matchSettingsButton, true )
+ Hud_SetLocked( file.inviteFriendsButton, true )
+ }
+ else
+ {
+ RHud_SetText( file.startMatchButton, "#START_MATCH" )
+ Hud_SetLocked( file.selectMapButton, false )
+ Hud_SetLocked( file.selectModeButton, false )
+ Hud_SetLocked( file.inviteFriendsButton, false )
+
+ string modeName = PrivateMatch_GetSelectedMode()
+ bool settingsLocked = IsFDMode( modeName )
+
+ if ( settingsLocked && uiGlobal.activeMenu == GetMenu( "MatchSettingsMenu" ) )
+ CloseActiveMenu()
+
+ Hud_SetLocked( file.matchSettingsButton, settingsLocked )
+ }
+}
+
+function UpdateLobbyUI()
+{
+ if ( uiGlobal.updatingLobbyUI )
+ return
+ uiGlobal.updatingLobbyUI = true
+
+ thread UpdateLobby()
+ thread UpdateDebugStatus()
+ thread UpdatePlayerInfo()
+
+ if ( uiGlobal.EOGOpenInLobby )
+ EOGOpen()
+
+ WaitSignal( uiGlobal.signalDummy, "CleanupInGameMenus" )
+ uiGlobal.updatingLobbyUI = false
+}
+
+function UpdateLobby()
+{
+ EndSignal( uiGlobal.signalDummy, "CleanupInGameMenus" )
+
+ var menu = file.menu
+
+ WaitFrameOrUntilLevelLoaded()
+
+ while ( true )
+ {
+ if ( !IsConnected() )
+ {
+ WaitFrameOrUntilLevelLoaded()
+ continue
+ }
+
+ menu.RunAnimationScript( "PrivateMatchLobby" )
+ // Force the animation scripts (which have zero duration) to complete before anything can cancel them.
+ ForceUpdateHUDAnimations()
+
+ UpdatePrivateMatchButtons()
+
+ int gamemodeIdx = expect int( level.ui.privatematch_mode )
+ int numPlaylistOverrides = GetPlaylistVarOverridesCount()
+ string playlistOverridesDesc = ""
+ for ( int varIdx = 0; varIdx < numPlaylistOverrides; ++varIdx )
+ {
+ // temp fix for playlistoverrides that aren't handled by private match
+ string varName = GetPlaylistVarOverrideNameByIndex( varIdx )
+
+ if ( varName in MatchSettings_PlaylistVarLabels )
+ {
+ float varOrigVal = float( GetCurrentPlaylistGamemodeByIndexVar( gamemodeIdx, varName, false ) )
+ float varOverrideVal = float( GetCurrentPlaylistGamemodeByIndexVar( gamemodeIdx, varName, true ) )
+ if ( varOrigVal == varOverrideVal )
+ continue
+
+ string label = Localize( MatchSettings_PlaylistVarLabels[varName] ) + ": "
+ string value = MatchSettings_FormatPlaylistVarValue( varName, varOverrideVal )
+ playlistOverridesDesc = playlistOverridesDesc + label + "`2" + value + " `0\n"
+ }
+ }
+
+ if ( playlistOverridesDesc.len() )
+ {
+ RuiSetString( Hud_GetRui( file.matchSettingsPanel ), "description", playlistOverridesDesc )
+ Hud_Show( file.matchSettingsPanel )
+ }
+ else
+ {
+ Hud_Hide( file.matchSettingsPanel )
+ }
+
+ if ( GetUIPlayer() && GetPersistentVar( "privateMatchState" ) == 1 )
+ Hud_SetVisible( file.spectatorLabel, true )
+ else
+ Hud_SetVisible( file.spectatorLabel, false )
+
+ WaitFrameOrUntilLevelLoaded()
+ }
+}
+
+void function OnSettingsButton_Activate( var button )
+{
+ if ( level.ui.privatematch_starting == ePrivateMatchStartState.STARTING )
+ return
+
+ AdvanceMenu( GetMenu( "MatchSettingsMenu" ) )
+}
+
+
+void function OnPrivateMatchButton_Activate( var button )
+{
+ ShowPrivateMatchConnectDialog()
+ ClientCommand( "match_playlist private_match" )
+ ClientCommand( "StartPrivateMatchSearch" )
+}
+
+void function OnStartMatchButton_Activate( var button )
+{
+ if ( AmIPartyLeader() || GetPartySize() == 1 )
+ ClientCommand( "PrivateMatchLaunch" )
+}
+
+function HandleLockedCustomMenuItem( menu, button, tipInfo, hideTip = false )
+{
+ array<var> elements = GetElementsByClassname( menu, "HideWhenLocked" )
+ var buttonTooltip = Hud_GetChild( menu, "ButtonTooltip" )
+ var toolTipLabel = Hud_GetChild( buttonTooltip, "Label" )
+
+ if ( Hud_IsLocked( button ) && !hideTip )
+ {
+ foreach( elem in elements )
+ Hud_Hide( elem )
+
+ local tipArray = clone tipInfo
+ tipInfo.resize( 6, null )
+
+ Hud_SetText( toolTipLabel, tipInfo[0], tipInfo[1], tipInfo[2], tipInfo[3], tipInfo[4], tipInfo[5] )
+
+ local buttonPos = button.GetAbsPos()
+ local buttonHeight = button.GetHeight()
+ local tooltipHeight = buttonTooltip.GetHeight()
+ local yOffset = ( tooltipHeight - buttonHeight ) / 2.0
+
+ buttonTooltip.SetPos( buttonPos[0] + button.GetWidth() * 0.9, buttonPos[1] - yOffset )
+ Hud_Show( buttonTooltip )
+
+ return true
+ }
+ else
+ {
+ foreach( elem in elements )
+ Hud_Show( elem )
+ Hud_Hide( buttonTooltip )
+ }
+ return false
+}
+
+void function PrivateMatchSwitchTeams( button )
+{
+ if ( !IsPrivateMatch() )
+ return
+
+ if ( !IsConnected() )
+ return
+
+ if ( uiGlobal.activeMenu != file.menu )
+ return
+
+ EmitUISound( "Menu_GameSummary_ScreenSlideIn" )
+
+ ClientCommand( "PrivateMatchSwitchTeams" )
+}
+
+void function HideMatchmakingStatusIcons()
+{
+ foreach ( element in file.matchStatusRuis )
+ RuiSetBool( Hud_GetRui( element ), "iconVisible", false )
+}
+
+void function ShowMatchmakingStatusIcons()
+{
+ foreach ( element in file.matchStatusRuis )
+ RuiSetBool( Hud_GetRui( element ), "iconVisible", true )
+}
+
+void function MatchmakingSetSearchText( string searchText, var param1 = "", var param2 = "", var param3 = "", var param4 = "" )
+{
+ foreach ( element in file.matchStatusRuis )
+ {
+ RuiSetBool( Hud_GetRui( element ), "statusHasText", searchText != "" )
+
+ RuiSetString( Hud_GetRui( element ), "statusText", Localize( searchText, param1, param2, param3, param4 ) )
+ }
+}
+
+
+void function MatchmakingSetCountdownTimer( float time )
+{
+ foreach ( element in file.matchStatusRuis )
+ {
+ RuiSetBool( Hud_GetRui( element ), "timerHasText", time != 0.0 )
+ RuiSetGameTime( Hud_GetRui( element ), "timerEndTime", time )
+ }
+}
+
+
+void function OnPrivateLobbyLevelInit()
+{
+ UpdateCallsignElement( file.callsignCard )
+ RefreshCreditsAvailable()
+}
+
+
+function UpdatePlayerInfo()
+{
+ EndSignal( uiGlobal.signalDummy, "CleanupInGameMenus" )
+
+ var menu = file.menu
+
+ WaitFrameOrUntilLevelLoaded()
+
+ while ( true )
+ {
+ RefreshCreditsAvailable()
+ WaitFrame()
+ }
+}
+
+void function OnPrivateMatchMenu_Open()
+{
+ Lobby_SetFDMode( false )
+ OnLobbyMenu_Open()
+} \ No newline at end of file
diff --git a/Northstar.Client/scripts/vscripts/ui/panel_mainmenu.nut b/Northstar.Client/scripts/vscripts/ui/panel_mainmenu.nut
new file mode 100644
index 000000000..49c71bc50
--- /dev/null
+++ b/Northstar.Client/scripts/vscripts/ui/panel_mainmenu.nut
@@ -0,0 +1,829 @@
+untyped
+
+global function InitMainMenuPanel
+global function UpdatePromoData
+
+global function UICodeCallback_GetOnPartyServer
+global function UICodeCallback_MainMenuPromosUpdated
+
+struct
+{
+ var menu
+ var panel
+ array<var> spButtons
+ array<void functionref()> spButtonFuncs
+ var mpButton
+ var fdButton
+ void functionref() mpButtonActivateFunc = null
+ var buttonData
+ array<var> menuButtons
+ var activeProfile
+ var serviceStatus
+
+ MainMenuPromos& promoData
+ var whatsNew
+ var spotlightPanel
+ array<var> spotlightButtons
+
+ bool installing = false
+} file
+
+const DEBUG_PERMISSIONS = false
+
+void function InitMainMenuPanel()
+{
+ RegisterSignal( "EndShowMainMenuPanel" )
+
+ file.panel = GetPanel( "MainMenuPanel" )
+ file.menu = GetParentMenu( file.panel )
+
+ AddPanelEventHandler( file.panel, eUIEvent.PANEL_SHOW, OnShowMainMenuPanel )
+ AddPanelEventHandler( file.panel, eUIEvent.PANEL_HIDE, OnHideMainMenuPanel )
+
+ file.menuButtons = GetElementsByClassname( file.menu, "MainMenuButtonClass" )
+ AddEventHandlerToButtonClass( file.menu, "MainMenuButtonClass", UIE_CLICK, MainMenuButton_Activate )
+
+ file.spotlightPanel = Hud_GetChild( file.panel, "SpotlightPanel" )
+ file.spotlightButtons = GetElementsByClassname( file.menu, "SpotlightButtonClass" )
+ foreach ( button in file.spotlightButtons )
+ button.s.link <- ""
+ AddEventHandlerToButtonClass( file.menu, "SpotlightButtonClass", UIE_CLICK, SpotlightButton_Activate )
+
+ file.activeProfile = Hud_GetChild( file.panel, "ActiveProfile" )
+ file.serviceStatus = Hud_GetRui( Hud_GetChild( file.panel, "ServiceStatus" ) )
+ file.whatsNew = Hud_GetRui( Hud_GetChild( file.panel, "WhatsNew" ) )
+
+ ComboStruct comboStruct = ComboButtons_Create( file.panel )
+
+ int headerIndex = 0
+ int buttonIndex = 0
+ var campaignHeader = AddComboButtonHeader( comboStruct, headerIndex, "#GAMEMODE_SOLO" )
+ file.spButtons.append( AddComboButton( comboStruct, headerIndex, buttonIndex, "" ) )
+ file.spButtonFuncs.append( DoNothing() )
+ Hud_AddEventHandler( file.spButtons[buttonIndex], UIE_CLICK, RunSPButton0 )
+ buttonIndex++
+ file.spButtons.append( AddComboButton( comboStruct, headerIndex, buttonIndex, "" ) )
+ file.spButtonFuncs.append( DoNothing() )
+ Hud_AddEventHandler( file.spButtons[buttonIndex], UIE_CLICK, RunSPButton1 )
+ buttonIndex++
+ file.spButtons.append( AddComboButton( comboStruct, headerIndex, buttonIndex, "" ) )
+ file.spButtonFuncs.append( DoNothing() )
+ Hud_AddEventHandler( file.spButtons[buttonIndex], UIE_CLICK, RunSPButton2 )
+ buttonIndex++
+ UpdateSPButtons()
+
+ headerIndex++
+ buttonIndex = 0
+ var multiplayerHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MULTIPLAYER_ALLCAPS" )
+ file.mpButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MULTIPLAYER_LAUNCH" )
+ Hud_AddEventHandler( file.mpButton, UIE_CLICK, OnPlayMPButton_Activate )
+ file.fdButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#GAMEMODE_COOP" )
+ Hud_AddEventHandler( file.fdButton, UIE_CLICK, OnPlayFDButton_Activate )
+
+ headerIndex++
+ buttonIndex = 0
+ var settingsHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_SETTINGS" )
+ var controlsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#CONTROLS" )
+ Hud_AddEventHandler( controlsButton, UIE_CLICK, ActivateControlsMenu )
+ #if CONSOLE_PROG
+ var avButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#AUDIO_VIDEO" )
+ Hud_AddEventHandler( avButton, UIE_CLICK, ActivateAudioVisualMenu )
+ #elseif PC_PROG
+ var audioButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#AUDIO" )
+ Hud_AddEventHandler( audioButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "AudioMenu" ) ) )
+ var videoButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#VIDEO" )
+ Hud_AddEventHandler( videoButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "VideoMenu" ) ) )
+ #endif
+
+ var spotlightLargeButton = Hud_GetChild( file.spotlightPanel, "SpotlightLarge" )
+ spotlightLargeButton.SetNavLeft( file.spButtons[0] )
+
+ var spotlightSmall0Button = Hud_GetChild( file.spotlightPanel, "SpotlightSmall0" )
+ spotlightSmall0Button.SetNavLeft( file.spButtons[0] )
+
+ file.buttonData = []
+
+ #if PC_PROG
+ file.buttonData.append( { name = "#QUIT", activateFunc = OnQuitButton_Activate } )
+ #endif // PC_PROG
+
+ if ( file.buttonData.len() )
+ {
+ comboStruct.navUpButton = file.menuButtons[ expect int( file.buttonData.len() ) - 1 ]
+ comboStruct.navDownButton = file.menuButtons[0]
+
+ foreach ( button in file.menuButtons )
+ button.SetNavRight( spotlightLargeButton )
+ }
+
+ comboStruct.navRightButton = spotlightLargeButton
+
+ ComboButtons_Finalize( comboStruct )
+
+ //AddPanelFooterOption( file.panel, BUTTON_A, "#A_BUTTON_SELECT" )
+ //AddPanelFooterOption( file.panel, BUTTON_B, "#B_BUTTON_CLOSE", "#CLOSE" )
+ //AddPanelFooterOption( file.panel, BUTTON_BACK, "", "", ClosePostGameMenu )
+
+ thread TrackInstallProgress()
+}
+
+void function OnShowMainMenuPanel()
+{
+ Signal( uiGlobal.signalDummy, "EndShowMainMenuPanel" )
+ EndSignal( uiGlobal.signalDummy, "EndShowMainMenuPanel" )
+
+ foreach ( button in file.menuButtons )
+ {
+ int buttonID = int( Hud_GetScriptID( button ) )
+
+ if ( buttonID < file.buttonData.len() )
+ {
+ if ( "updateFunc" in file.buttonData[buttonID] )
+ file.buttonData[buttonID].updateFunc.call( this, button )
+ else
+ Hud_SetEnabled( button, true )
+
+ RuiSetString( Hud_GetRui( button ), "buttonText", file.buttonData[buttonID].name )
+ Hud_Show( button )
+ }
+ else
+ {
+ Hud_Hide( button )
+ }
+ }
+
+ #if PS4_PROG
+ thread EnableCheckPlus()
+ #endif // PS4_PROG
+
+ UpdateSPButtons()
+ thread UpdatePlayButton( file.mpButton )
+ thread MonitorTrialVersionChange()
+
+ #if DURANGO_PROG
+ SetLabelRuiText( file.activeProfile, Durango_GetGameDisplayName() )
+ Hud_Show( file.activeProfile )
+ #endif // DURANGO_PROG
+
+ ExecCurrentGamepadButtonConfig()
+ ExecCurrentGamepadStickConfig()
+
+
+ string defaultButtonRowFocus = "ButtonRow0x0"
+ bool shouldFocusMultiplayer = GetMenuWasMultiplayerPlayedLast()
+ if ( shouldFocusMultiplayer )
+ defaultButtonRowFocus = "ButtonRow1x0"
+
+ SetPanelDefaultFocus( file.panel, Hud_GetChild( file.panel, defaultButtonRowFocus ) )
+ PanelFocusDefault( file.panel )
+}
+
+void function EnableCheckPlus()
+{
+ WaitFrame() // ???: doesn't work without a wait
+ if ( !Ps4_CheckPlus_Allowed() && !IsSingleplayer() )
+ {
+ printt( "scheduling plus check" )
+ Ps4_CheckPlus_Schedule()
+ }
+}
+
+void function OnHideMainMenuPanel()
+{
+ Signal( uiGlobal.signalDummy, "EndShowMainMenuPanel" )
+}
+
+void function UpdatePlayButton( var button )
+{
+ #if CONSOLE_PROG
+ bool isOnline
+ bool hasPermission
+ bool isFullyInstalled
+ #endif
+
+ #if DURANGO_PROG
+ bool isGuest
+ #elseif PS4_PROG
+ bool isPSNConnected
+ bool isOverAge
+ bool hasPlus
+ bool hasLatestPatch
+ #elseif PC_PROG
+ bool isOriginConnected
+ bool hasLatestPatch
+ #endif
+
+ bool isStryderAuthenticated
+ bool isMPAllowed
+ bool isLocked
+ string buttonText
+ string message
+ bool isMessageVisible
+
+ while ( GetTopNonDialogMenu() == file.menu )
+ {
+ bool isSpotlightReady = file.promoData.version != 0 ? true : false
+ Hud_SetVisible( file.spotlightPanel, isSpotlightReady )
+
+ if ( !Hud_IsFocused( button ) )
+ {
+ RuiSetBool( file.serviceStatus, "isVisible", false )
+ WaitFrame()
+ continue
+ }
+
+ #if DURANGO_PROG
+ isFullyInstalled = IsGameFullyInstalled()
+ isOnline = Console_IsOnline()
+ isGuest = Durango_IsGuest()
+ hasPermission = Console_HasPermissionToPlayMultiplayer()
+ isStryderAuthenticated = IsStryderAuthenticated()
+ isMPAllowed = IsStryderAllowingMP()
+
+ if ( DEBUG_PERMISSIONS )
+ {
+ printt( "isFullyInstalled:", isFullyInstalled )
+ printt( "isOnline:", isOnline )
+ printt( "isGuest:", isGuest )
+ printt( "hasPermission:", hasPermission )
+ printt( "isStryderAuthenticated:", isStryderAuthenticated )
+ printt( "isMPAllowedByStryder:", isMPAllowed )
+ }
+
+ buttonText = "#MULTIPLAYER_LAUNCH"
+ message = ""
+
+ if ( !isOnline )
+ {
+ message = "#INTERNET_NOT_FOUND"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( isGuest )
+ {
+ buttonText = "#SWITCH_PROFILE"
+ message = "#GUESTS_NOT_SUPPORTED"
+ file.mpButtonActivateFunc = XB1_SwitchAccount
+ }
+ else if ( !hasPermission || !isMPAllowed )
+ {
+ message = "#MULTIPLAYER_NOT_AVAILABLE"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( !isStryderAuthenticated )
+ {
+ message = "#CONTACTING_RESPAWN_SERVERS"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( !isFullyInstalled )
+ {
+ //message = "#INSTALL_IN_PROGRESS"
+ file.mpButtonActivateFunc = LaunchMP
+ }
+ else
+ {
+ file.mpButtonActivateFunc = LaunchMP
+ }
+
+ isLocked = file.mpButtonActivateFunc == null ? true : false
+ Hud_SetLocked( button, isLocked )
+
+ #elseif PS4_PROG
+
+ isFullyInstalled = IsGameFullyInstalled()
+ hasLatestPatch = HasLatestPatch()
+ isOnline = Console_IsOnline()
+ isPSNConnected = Ps4_PSN_Is_Loggedin()
+ hasPermission = Console_HasPermissionToPlayMultiplayer()
+ isOverAge = !PS4_is_NetworkStatusAgeRestriction()
+ hasPlus = Ps4_CheckPlus_Allowed()
+ isStryderAuthenticated = IsStryderAuthenticated()
+ isMPAllowed = IsStryderAllowingMP()
+
+ if ( DEBUG_PERMISSIONS )
+ {
+ printt( "isFullyInstalled:", isFullyInstalled )
+ printt( "hasLatestPatch:", hasLatestPatch )
+ printt( "isOnline:", isOnline )
+ printt( "isPSNConnected:", isPSNConnected )
+ printt( "hasPermission:", hasPermission )
+ printt( "isOverAge:", isOverAge )
+ printt( "hasPlus:", hasPlus )
+ printt( "isStryderAuthenticated:", isStryderAuthenticated )
+ printt( "isMPAllowedByStryder:", isMPAllowed )
+ }
+
+ buttonText = "#MULTIPLAYER_LAUNCH"
+ message = ""
+
+ if ( !isOnline )
+ {
+ message = "#INTERNET_NOT_FOUND"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( PS4_getUserNetworkingResolution() == PS4_NETWORK_STATUS_UNKNOWN )
+ {
+ message = "#INTERNET_NOT_FOUND"
+ file.mpButtonActivateFunc = LaunchMP
+ }
+ else if ( !hasLatestPatch )
+ {
+ message = "#UPDATE_AVAILABLE"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( PS4_getUserNetworkingResolution() == PS4_NETWORK_STATUS_IN_ERROR )
+ {
+ message = "#PSN_HAD_ERROR"
+ file.mpButtonActivateFunc = LaunchMP
+ }
+ else if ( !isPSNConnected )
+ {
+ buttonText = "#PS4_SIGN_IN"
+ message = "#PS4_DISCONNECT_NOT_SIGNED_IN_TO_PSN"
+ file.mpButtonActivateFunc = PS4_PSNSignIn
+ }
+ else if ( !isFullyInstalled )
+ {
+ //message = "#INSTALL_IN_PROGRESS"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( !isOverAge )
+ {
+ message = "#MULTIPLAYER_AGE_RESTRICTED"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( !hasPermission || !isMPAllowed ) // A more general permission check. Can fail if not patched, underage profile logged in to another controller, network issue, etc.
+ {
+ message = "#MULTIPLAYER_NOT_AVAILABLE"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( !hasPlus )
+ {
+ //buttonText = "#PS4_GET_PLAYSTATION_PLUS"
+ //message = "#PSN_MUST_BE_PLUS_USER"
+ //file.mpButtonActivateFunc = PS4_PlusSignUp
+ // Their is a race on this. The function may not be completed.
+
+
+ // The LaunchMP handles this race and will retry/ issue an error dialog if needed.
+ file.mpButtonActivateFunc = LaunchMP
+ }
+ else if ( !isStryderAuthenticated )
+ {
+ message = "#CONTACTING_RESPAWN_SERVERS"
+ file.mpButtonActivateFunc = null
+ }
+ else
+ {
+ file.mpButtonActivateFunc = LaunchMP
+ }
+
+ isLocked = file.mpButtonActivateFunc == null ? true : false
+ Hud_SetLocked( button, isLocked )
+
+ #elseif PC_PROG
+
+ hasLatestPatch = Origin_IsUpToDate()
+ isOriginConnected = Origin_IsEnabled() ? Origin_IsOnline() : true
+ isStryderAuthenticated = IsStryderAuthenticated()
+ isMPAllowed = IsStryderAllowingMP()
+
+ if ( DEBUG_PERMISSIONS )
+ {
+ printt( "isOriginConnected:", isOriginConnected )
+ printt( "isStryderAuthenticated:", isStryderAuthenticated )
+ }
+
+ buttonText = "#MULTIPLAYER_LAUNCH"
+ message = ""
+
+ if ( !isOriginConnected )
+ {
+ message = "#ORIGIN_IS_OFFLINE"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( !isStryderAuthenticated )
+ {
+ message = "#CONTACTING_RESPAWN_SERVERS"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( !isMPAllowed )
+ {
+ message = "#MULTIPLAYER_NOT_AVAILABLE"
+ file.mpButtonActivateFunc = null
+ }
+ else if ( !hasLatestPatch )
+ {
+ message = "#ORIGIN_UPDATE_AVAILABLE"
+ file.mpButtonActivateFunc = null
+ }
+ else
+ {
+ file.mpButtonActivateFunc = LaunchMP
+ }
+
+ isLocked = file.mpButtonActivateFunc == null ? true : false
+ Hud_SetLocked( button, isLocked )
+ #endif
+
+ if ( Script_IsRunningTrialVersion() && !IsTrialPeriodActive() && file.mpButtonActivateFunc != LaunchGamePurchase )
+ {
+ buttonText = "#MENU_GET_THE_FULL_GAME"
+ file.mpButtonActivateFunc = LaunchGamePurchase
+ Hud_SetLocked( button, false )
+ message = ""
+ }
+
+ ComboButton_SetText( file.mpButton, buttonText )
+
+ //if ( Hud_IsLocked( button ) || buttonText == "#MENU_GET_THE_FULL_GAME" )
+ //{
+ // ComboButton_SetText( file.fdButton, "" )
+ // Hud_SetEnabled( file.fdButton, false )
+ //}
+ //else
+ //{
+ //ComboButton_SetText( file.fdButton, "#MULTIPLAYER_LAUNCH_FD" )
+ ComboButton_SetText( file.fdButton, "Launch Northstar" ) // this needs to use localised text at some point when we have a modular way of doing that
+ Hud_SetEnabled( file.fdButton, true )
+ //}
+
+ if ( file.installing )
+ message = ""
+ else if ( message == "" )
+ message = GetConVarString( "rspn_motd" )
+
+ RuiSetString( file.serviceStatus, "messageText", message )
+
+ isMessageVisible = message != "" ? true : false
+ RuiSetBool( file.serviceStatus, "isVisible", isMessageVisible )
+
+ WaitFrame()
+ //wait 2
+ }
+}
+
+void function XB1_SwitchAccount()
+{
+ Durango_ShowAccountPicker()
+}
+
+void function PS4_PSNSignIn()
+{
+ Ps4_LoginDialog_Schedule()
+}
+
+void function PS4_PlusSignUp()
+{
+ if ( Ps4_ScreenPlusDialog_Schedule() )
+ {
+ while ( Ps4_ScreenPlusDialog_Running() )
+ WaitFrame()
+
+ if ( Ps4_ScreenPlusDialog_Allowed() )
+ Ps4_CheckPlus_Schedule()
+ }
+}
+
+void function MainMenuButton_Activate( var button )
+{
+ int buttonID = int( Hud_GetScriptID( button ) )
+
+ Assert( file.buttonData )
+
+ if ( file.buttonData[buttonID].activateFunc )
+ file.buttonData[buttonID].activateFunc.call( this )
+}
+
+void function OnPlayFDButton_Activate( var button ) // repurposed for launching northstar lobby
+{
+ //if ( file.mpButtonActivateFunc == null )
+ // printt( "file.mpButtonActivateFunc is null" )
+
+ if ( !Hud_IsLocked( button ) )// && file.mpButtonActivateFunc != null )
+ {
+ //Lobby_SetAutoFDOpen( true )
+ //// Lobby_SetFDMode( true )
+ //thread file.mpButtonActivateFunc()
+
+ ClientCommand( "everything_unlocked 1" ) // todo super temp, need this removed when server autoexecs are good
+ ClientCommand( "map mp_lobby" )
+ }
+}
+
+void function OnPlayMPButton_Activate( var button )
+{
+ if ( file.mpButtonActivateFunc == null )
+ printt( "file.mpButtonActivateFunc is null" )
+
+ if ( !Hud_IsLocked( button ) && file.mpButtonActivateFunc != null )
+ {
+ Lobby_SetAutoFDOpen( false )
+ // Lobby_SetFDMode( false )
+ thread file.mpButtonActivateFunc()
+ }
+}
+
+void function UICodeCallback_GetOnPartyServer()
+{
+ uiGlobal.launching = eLaunching.MULTIPLAYER_INVITE
+ Lobby_SetAutoFDOpen( false )
+ // Lobby_SetFDMode( false )
+ LaunchGame()
+}
+
+#if PC_PROG
+void function OnQuitButton_Activate()
+{
+ DialogData dialogData
+ dialogData.header = "#MENU_QUIT_GAME_CONFIRM"
+
+ AddDialogButton( dialogData, "#CANCEL" )
+ AddDialogButton( dialogData, "#QUIT", Quit )
+
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+ AddDialogFooter( dialogData, "#B_BUTTON_CANCEL" )
+
+ OpenDialog( dialogData )
+}
+
+void function Quit()
+{
+ ClientCommand( "quit" )
+}
+#endif // #if PC_PROG
+
+void function MonitorTrialVersionChange()
+{
+ bool isTrialVersion
+ bool lastIsTrialVersion = Script_IsRunningTrialVersion()
+
+ while ( GetTopNonDialogMenu() == file.menu )
+ {
+ isTrialVersion = Script_IsRunningTrialVersion()
+
+ if ( isTrialVersion != lastIsTrialVersion )
+ UpdateSPButtons()
+
+ lastIsTrialVersion = isTrialVersion
+
+ WaitFrame()
+ }
+}
+
+void function UpdateSPButtons()
+{
+ foreach( button in file.spButtons )
+ {
+ ComboButton_SetText( button, "" )
+ Hud_SetEnabled( button, false )
+ }
+
+ int buttonIndex = 0
+
+ if ( Script_IsRunningTrialVersion() )
+ {
+ if ( HasStartedGameEver() )
+ AddSPButton( buttonIndex, TrainingModeSelect, "#SP_TRIAL_MENU_TRAINING" )
+ else
+ AddSPButton( buttonIndex, LaunchSPNew, "#SP_TRIAL_MENU_TRAINING" )
+ buttonIndex++
+
+ if ( HasStartedGameEver() )
+ {
+ AddSPButton( buttonIndex, TrialMissionSelect, "#SP_TRIAL_MENU_MISSION" )
+ buttonIndex++
+ }
+
+ AddSPButton( buttonIndex, LaunchGamePurchase, "#MENU_GET_THE_FULL_GAME" )
+ buttonIndex++
+ }
+ else
+ {
+ if ( HasValidSaveGame() )
+ {
+ AddSPButton( buttonIndex, LaunchSPContinue, "#MENU_CONTINUE_GAME" )
+ buttonIndex++
+ }
+
+ if ( HasStartedGameEver() )
+ {
+ AddSPButton( buttonIndex, LaunchSPMissionSelect, "#MENU_MISSION_SELECT" )
+ buttonIndex++
+ }
+
+ AddSPButton( buttonIndex, LaunchSPNew, "#MENU_NEW_GAME" )
+ }
+}
+
+void function AddSPButton( int index, void functionref() func, string text )
+{
+ var button = file.spButtons[ index ]
+ ComboButton_SetText( button, text )
+ Hud_SetEnabled( button, true )
+ file.spButtonFuncs[ index ] = func
+}
+
+void function DoNothing()
+{
+}
+
+void function RunSPButton0( var button )
+{
+ void functionref() func = file.spButtonFuncs[ 0 ]
+ func()
+}
+
+void function RunSPButton1( var button )
+{
+ void functionref() func = file.spButtonFuncs[ 1 ]
+ func()
+}
+
+void function RunSPButton2( var button )
+{
+ void functionref() func = file.spButtonFuncs[ 2 ]
+ func()
+}
+
+void function ActivateControlsMenu( var button )
+{
+ #if CONSOLE_PROG
+ if ( GetEULAVersionAccepted() < 1 ) // Treat as binary for now, as discussed with Preston.
+ {
+ if ( uiGlobal.activeMenu == GetMenu( "EULADialog" ) )
+ return
+
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ CloseActiveMenu( true )
+
+ uiGlobal.consoleSettingMenu = eConsoleSettingsMenu.CONTROLS_MENU
+
+ EULA_Dialog()
+ return
+ }
+ else
+ {
+ AdvanceMenu( GetMenu( "ControlsMenu" ) )
+ return
+ }
+ #endif
+
+ #if PC_PROG
+ AdvanceMenu( GetMenu( "ControlsMenu" ) )
+ #endif
+}
+
+void function ActivateAudioVisualMenu( var button ) //This is only run on console
+{
+ if ( GetEULAVersionAccepted() < 1 ) // Treat as binary for now, as discussed with Preston.
+ {
+ if ( uiGlobal.activeMenu == GetMenu( "EULADialog" ) )
+ return
+
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ CloseActiveMenu( true )
+
+ uiGlobal.consoleSettingMenu = eConsoleSettingsMenu.AUDIO_VISUAL_MENU
+
+ EULA_Dialog()
+ return
+ }
+ else
+ {
+ AdvanceMenu( GetMenu( "AudioVideoMenu" ) )
+ return
+ }
+}
+
+void function TrackInstallProgress()
+{
+ var rui = Hud_GetRui( Hud_GetChild( file.panel, "InstallProgress" ) )
+
+ while ( GetGameFullyInstalledProgress() < 1.0 )
+ {
+ file.installing = true
+ RuiSetFloat( rui, "installProgress", GetGameFullyInstalledProgress() )
+ wait 0.5
+ }
+
+ file.installing = false
+ RuiSetFloat( rui, "installProgress", 1.0 )
+}
+
+bool function IsStryderAuthenticated()
+{
+ return GetConVarInt( "mp_allowed" ) != -1
+}
+
+bool function IsStryderAllowingMP()
+{
+ return GetConVarInt( "mp_allowed" ) == 1
+}
+
+#if PS4_PROG
+bool function HasLatestPatch()
+{
+ int status = PS4_getUserNetworkingErrorStatus()
+
+ if ( status == -2141913073 ) // SCE_NP_ERROR_LATEST_PATCH_PKG_EXIST
+ return false
+
+ return true
+}
+#endif // PS4_PROG
+
+void function UpdatePromoData()
+{
+ file.promoData = GetMainMenuPromos()
+
+ UpdateWhatsNewData()
+ UpdateSpotlightData()
+}
+
+void function UICodeCallback_MainMenuPromosUpdated()
+{
+ printt( "MainMenuPromos updated" )
+
+ UpdatePromoData()
+}
+
+void function UpdateWhatsNewData()
+{
+ // file.promoData.newInfo_ImageIndex
+ //RuiSetString( file.whatsNew, "line1Text", "`2%$rui/menu/main_menu/whats_new_bulletpoint%`0 Updated Live Fire Maps!\n`2%$rui/menu/main_menu/whats_new_bulletpoint%`0 Prime Titans`0 in the Store\n`2%$rui/menu/main_menu/whats_new_bulletpoint% DOUBLE XP`0 weekend!" )//file.promoData.newInfo_Title1 )
+ RuiSetString( file.whatsNew, "line1Text", file.promoData.newInfo_Title1 )
+ RuiSetString( file.whatsNew, "line2Text", file.promoData.newInfo_Title2 )
+ RuiSetString( file.whatsNew, "line3Text", file.promoData.newInfo_Title3 )
+
+ bool isVisible = true
+ if ( file.promoData.newInfo_Title1 == "" && file.promoData.newInfo_Title2 == "" && file.promoData.newInfo_Title3 == "" )
+ isVisible = false
+
+ RuiSetBool( file.whatsNew, "isVisible", isVisible )
+}
+
+void function UpdateSpotlightData()
+{
+ SetSpotlightButtonData( file.spotlightButtons[0], file.promoData.largeButton_Url, file.promoData.largeButton_ImageIndex, file.promoData.largeButton_Title, file.promoData.largeButton_Text )
+ SetSpotlightButtonData( file.spotlightButtons[1], file.promoData.smallButton1_Url, file.promoData.smallButton1_ImageIndex, file.promoData.smallButton1_Title )
+ SetSpotlightButtonData( file.spotlightButtons[2], file.promoData.smallButton2_Url, file.promoData.smallButton2_ImageIndex, file.promoData.smallButton2_Title )
+}
+
+void function SetSpotlightButtonData( var button, string link, int imageIndex, string title, string details = "skip" )
+{
+ var rui = Hud_GetRui( button )
+
+ var dataTable = GetDataTable( $"datatable/spotlight_images.rpak" )
+ asset image = GetDataTableAsset( dataTable, imageIndex, GetDataTableColumnByName( dataTable, "image" ) )
+
+ RuiSetImage( rui, "buttonImage", image )
+ RuiSetString( rui, "titleText", title )
+
+ if ( details != "skip" )
+ RuiSetString( rui, "detailsText", details )
+
+ button.s.link = link
+}
+
+void function SpotlightButton_Activate( var button )
+{
+ string link = expect string( button.s.link )
+
+ if ( link == "" )
+ return
+
+ if ( link.find( "menu:" ) == 0 )
+ {
+ var menu
+
+ switch ( link )
+ {
+ //case "menu:new":
+ // menu = GetMenu( "StoreMenu_NewReleases" )
+ // break
+
+ case "menu:new":
+ case "menu:weaponskins":
+ menu = GetMenu( "StoreMenu_WeaponSkins" )
+ break
+
+ //case "menu:limited":
+ // menu = GetMenu( "StoreMenu_Limited" )
+ // break
+
+ case "menu:sales":
+ menu = GetMenu( "StoreMenu_Sales" )
+ break
+ }
+
+ if ( menu != null )
+ {
+ uiGlobal.menuToOpenFromPromoButton = menu
+ LaunchMP()
+ }
+ }
+ else
+ {
+ LaunchExternalWebBrowser( link, WEBBROWSER_FLAG_MUTEGAME )
+ }
+}
diff --git a/Northstar.Coop/mod.json b/Northstar.Coop/mod.json
new file mode 100644
index 000000000..9f657cdd7
--- /dev/null
+++ b/Northstar.Coop/mod.json
@@ -0,0 +1,24 @@
+disabled{
+ "ApiId" : "Northstar.Coop",
+ "Name" : "Northstar.Coop",
+ "Description" : "Various script patches to fix functionality for coop servers",
+ "Authors" : [
+ "BobTheBob"
+ ],
+ "Contacts" : [
+ "BobTheBob#1150"
+ ],
+ "Version" : "0.1",
+ "CustomScripts": [
+ {
+ "Path": "sp/_coop_sp_utils.gnut",
+ "RunOn": "SERVER && SP",
+ "ServerCallback": "CoopSpUtils_Init"
+ },
+ {
+ "Path": "sp/sh_coop_sp_utils.gnut",
+ "RunOn": "( CLIENT || SERVER ) && SP",
+ "ServerCallback": "ClCoopSpUtils_Init"
+ }
+ ]
+} \ No newline at end of file
diff --git a/Northstar.Coop/scripts/vscripts/client/cl_respawnselect_sp.gnut b/Northstar.Coop/scripts/vscripts/client/cl_respawnselect_sp.gnut
new file mode 100644
index 000000000..eab80e986
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/client/cl_respawnselect_sp.gnut
@@ -0,0 +1,110 @@
+global function ShowRespawnSelect_SP
+global function ClRespawnselect_SP_Init
+global function DisableDeathBlur
+global function DisplayRespawnPrompt
+
+struct
+{
+ bool deathBlur = false // death blur will stay after respawn if we don't remove it
+ bool canRespawn = false
+ var respawnHintRui
+} file
+
+
+void function ClRespawnselect_SP_Init()
+{
+ Assert( !IsMultiplayer() )
+
+ AddCallback_OnPlayerLifeStateChanged( Callback_PlayerLifestateChanged )
+ file.respawnHintRui = RuiCreate( $"ui/respawn_hint.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, RUI_SORT_SCREENFADE + 100 )
+ RuiSetResolutionToScreenSize( file.respawnHintRui )
+
+ // register these early so we don't end up registering multiple times and crashing later
+ RegisterButtonPressedCallback( KEY_SPACE, PlayerPressed_RespawnPilotSP )
+ RegisterButtonPressedCallback( BUTTON_X, PlayerPressed_RespawnPilotSP )
+}
+
+void function Callback_PlayerLifestateChanged( entity player, int oldLifeState, int newLifeState )
+{
+ if ( player.GetPlayerSettings() == "spectator" )
+ {
+ if ( !IsTestMap() )
+ {
+ ScreenFade( GetLocalViewPlayer(), 0, 0, 0, 255, 0.5, 1.0, FFADE_IN )
+ //printt( "cl SCREENFADE: " + 0.5 + " " + 1.0 )
+ }
+ return
+ }
+
+ if ( oldLifeState == newLifeState )
+ return
+
+ if ( player != GetLocalViewPlayer() )
+ return
+
+ //if ( newLifeState == LIFE_DEAD )
+ // thread ShowRespawnSelect_SP()
+}
+
+void function DisableDeathBlur()
+{
+ file.deathBlur = false
+}
+
+void function ShowRespawnSelect_SP()
+{
+}
+
+void function PlayerPressed_RespawnPilotSP( entity player )
+{
+ if ( file.canRespawn )
+ {
+ player.ClientCommand( "RespawnNowSP" )
+ RuiSetBool( file.respawnHintRui, "isVisible", false )
+ file.canRespawn = false
+ }
+}
+
+void function DisplayRespawnPrompt()
+{
+ // move this out of here since otherwise the client will attempt to bind it multiple times which crashes
+ //RegisterButtonPressedCallback( KEY_SPACE, PlayerPressed_RespawnPilotSP )
+ //RegisterButtonPressedCallback( BUTTON_X, PlayerPressed_RespawnPilotSP )
+
+ // delay this so it coincides with serverside respawn delay
+ //RuiSetBool( file.respawnHintRui, "isVisible", true )
+
+ print( "DisplayRespawnPrompt()" )
+ if ( !AreAllPlayersDead() )
+ thread WaitToDisplayRespawnPrompt()
+ else
+ thread LevelFailedEffect()
+}
+
+void function WaitToDisplayRespawnPrompt()
+{
+ print( "waittodisplayrespawnprompt()" )
+ // display respawn ui
+ RuiSetBool( file.respawnHintRui, "isVisible", true )
+
+ entity player = GetLocalViewPlayer()
+ RuiSetGameTime( file.respawnHintRui, "nextSpawnTime", expect float( player.nv.nextRespawnTime ) )
+
+ // wait until we can respawn
+ wait expect float( player.nv.nextRespawnTime ) - Time()
+
+ // wait until server will allow a respawn to enable it
+ file.canRespawn = true
+}
+
+void function LevelFailedEffect()
+{
+ print( "levelfailedeffect()")
+ // display the fade that gets displayed in vanilla on death
+
+ wait 1.5
+ SetScreenBlur( 1.0, 1.0, EASING_LINEAR )
+
+ wait 0.25
+ ScreenFade( GetLocalViewPlayer(), 0, 0, 0, 255, 0.8, 5, FFADE_OUT )
+} \ No newline at end of file
diff --git a/Northstar.Coop/scripts/vscripts/sp/_base_gametype_sp.gnut b/Northstar.Coop/scripts/vscripts/sp/_base_gametype_sp.gnut
new file mode 100644
index 000000000..bdfa895a9
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/_base_gametype_sp.gnut
@@ -0,0 +1,657 @@
+untyped
+global function BaseGametype_Init_MPSP
+global function CodeCallback_OnWeaponAttack
+global function CodeCallback_OnPlayerMatchmakingChanged
+global function CodeCallback_OnClientConnectionCompleted
+global function CodeCallback_OnClientDisconnected
+global function CodeCallback_OnPlayerRespawned
+global function CodeCallback_OnWeaponTouch
+global function CodeCallback_WeaponDropped
+global function DecideRespawnPlayer
+global function ShouldEntTakeDamage_SPMP
+global function ShouldUseReplacementSpawn
+global function TryGameModeAnnouncement
+global function CodeCallback_OnClientConnectionStarted
+global function CodeCallback_OnPlayerKilled
+global function CreateNoSpawnArea
+global function DeleteNoSpawnArea
+global function IsSpawnpointValidDrop
+global function ClientCommand_OpenDifficultyMenu
+global function CodeCallback_PlayerHasBeenConnectedForDuration
+global function OnPlayerKilled_DeathNotify
+global function GetSPLevelEnumForMapname
+
+global void functionref( entity, entity ) hackRespawnPlayerFunc
+
+struct
+{
+ float lastObitMsgTime = 0.0
+ int wallRunKills = 0
+ int slideKills = 0
+ float lastMultiKillStartTime = 0.0
+ int multiKillCount = 0
+} file
+
+void function BaseGametype_Init_MPSP()
+{
+ AddClientCommandCallback( "ClientCommand_OpenDifficultyMenu", ClientCommand_OpenDifficultyMenu )
+
+ AddCallback_OnPlayerKilled( OnPlayerKilled_DeathNotify )
+ AddCallback_OnPlayerInventoryChanged( RefreshWeaponHighlights )
+ AddCallback_OnPilotBecomesTitan( RefreshWeaponHighlightTitanTransfer )
+ AddCallback_OnTitanBecomesPilot( RefreshWeaponHighlightTitanTransfer )
+
+ if ( GetMapName() != "sp_training" )
+ {
+ AddDeathCallback( "npc_soldier", OnNPCKilled )
+ AddDeathCallback( "npc_spectre", OnNPCKilled )
+ AddDeathCallback( "npc_prowler", OnNPCKilled )
+ AddDeathCallback( "npc_titan", OnNPCKilled )
+ AddDeathCallback( "npc_stalker", OnNPCKilled )
+ AddDeathCallback( "npc_drone", OnNPCKilled )
+ AddDeathCallback( "npc_frag_drone", OnNPCKilled )
+ AddDeathCallback( "npc_turret_sentry", OnNPCKilled )
+ }
+
+ RegisterSignal( "RevertToRegularHighlight" )
+
+ FlagInit( "WeaponDropsAllowed" )
+ FlagSet( "WeaponDropsAllowed" )
+
+ hackRespawnPlayerFunc = HackRespawnPlayer
+}
+
+void function HackRespawnPlayer( entity player, entity respawnOn )
+{
+ player.RespawnPlayer( null )
+}
+
+void function CodeCallback_OnWeaponAttack( entity player, entity weapon, string weaponName, int ammoUsed )
+{
+
+}
+
+void function CodeCallback_OnPlayerMatchmakingChanged( entity player )
+{
+
+}
+
+// playerconnected
+void function CodeCallback_OnClientConnectionCompleted( entity player )
+{
+ if ( IsLobby() )
+ {
+ level.Lobby_OnClientConnectionCompleted( player )
+ return
+ }
+
+ player.hasConnected = true
+
+ InitMeleeAnimEventCallbacks( player )
+ ZiplineInit( player )
+
+ FinishClientScriptInitialization( player )
+
+ // Added via AddCallback_OnClientConnected
+ foreach ( callbackFunc in svGlobal.onClientConnectedCallbacks )
+ {
+ callbackFunc( player )
+ }
+
+ if ( !Flag( "PlayerDidSpawn") )
+ {
+ __PlayerDidSpawn( player )
+ svGlobal.levelEnt.Signal( "PlayerDidSpawn", { player = player } )
+ }
+
+ // Player was already positioned at info_player_start in SpPlayerConnecting.
+ // Don't reposition him, in case movers have already pushed him.
+ player.RespawnPlayer( null )
+ AddPlayerMovementEventCallback( player, ePlayerMovementEvents.BEGIN_WALLRUN, Callback_WallrunBegin )
+}
+
+void function CodeCallback_OnClientDisconnected( entity player, string reason )
+{
+ if ( IsLobby() )
+ {
+ player.Signal( "_disconnectedInternal" )
+ UpdateBadRepPresent()
+ return
+ }
+
+ if ( !player.hasConnected )
+ return
+
+ // Added via AddCallback_OnClientDisconnected
+ foreach ( callbackFunc in svGlobal.onClientDisconnectedCallbacks )
+ {
+ callbackFunc( player )
+ }
+
+ player.Disconnected()
+ player.p.isDisconnected = true
+ player.CleanupMPClasses()
+}
+
+int function GetSPLevelEnumForMapname( string mapName )
+{
+ switch ( mapName )
+ {
+ case "sp_training":
+ return eSPLevel.TRAINING
+
+ case "sp_crashsite":
+ return eSPLevel.WILDS
+
+ case "sp_sewers1":
+ return eSPLevel.SEWERS
+
+ case "sp_boomtown_start":
+ case "sp_boomtown":
+ case "sp_boomtown_end":
+ return eSPLevel.BOOM_TOWN
+
+ case "sp_hub_timeshift":
+ case "sp_timeshift_spoke02":
+ return eSPLevel.TIME_SHIFT
+
+ case "sp_beacon":
+ case "sp_beacon_spoke0":
+ return eSPLevel.BEACON
+
+ case "sp_tday":
+ return eSPLevel.TDAY
+
+ case "sp_s2s":
+ return eSPLevel.SHIP2SHIP
+
+ case "sp_skyway_v1":
+ return eSPLevel.SKYWAY
+
+ default:
+ return eSPLevel.UNKNOWN
+ }
+
+ unreachable
+}
+
+bool function ShouldGiveFullGrenades()
+{
+ int levelID = GetSPLevelEnumForMapname( GetMapName() )
+ if ( levelID == eSPLevel.UNKNOWN )
+ return true
+
+ LevelTransitionStruct ornull trans = GetLevelTransitionStruct()
+ if ( trans == null )
+ return true
+
+ expect LevelTransitionStruct( trans )
+ if ( trans.levelID != levelID )
+ return true
+
+ return false
+}
+
+void function CodeCallback_OnPlayerRespawned( entity player )
+{
+ SetHumanRagdollImpactTable( player )
+
+ player.s.respawnCount++
+ player.s.respawnTime = Time()
+ ClearRecentDamageHistory( player )
+
+ player.Signal( "OnRespawned" )
+
+ PilotLoadoutDef loadout = GetPilotLoadoutForCurrentMapSP()
+ if ( !IsTestMap() )
+ PopulatePilotLoadoutFromLevelTrans( loadout )
+ GivePilotLoadout( player, loadout )
+
+ if ( ShouldGiveFullGrenades() )
+ {
+ entity ordnanceWeapon = player.GetOffhandWeapon( OFFHAND_ORDNANCE )
+ if ( IsValid( ordnanceWeapon ) )
+ {
+ int maxClip = ordnanceWeapon.GetWeaponPrimaryClipCountMax()
+ if ( maxClip > 0 )
+ ordnanceWeapon.SetWeaponPrimaryClipCount( maxClip )
+ }
+ }
+
+
+ LevelTransitionStruct ornull trans = GetLevelTransitionStruct()
+ if ( trans != null )
+ {
+ expect LevelTransitionStruct( trans )
+ if ( trans.pilotHasBattery )
+ {
+ entity battery = Rodeo_CreateBatteryPack()
+ Rodeo_PilotPicksUpBattery_Silent( player, battery )
+ }
+ }
+
+ NPCTitanInitModeOnPlayerRespawn( player )
+
+ // Added via AddCallback_OnPlayerRespawned
+ foreach ( callbackFunc in svGlobal.onPlayerRespawnedCallbacks )
+ {
+ callbackFunc( player )
+ }
+
+ player.e.lastAttacker = null
+}
+
+void function DecideRespawnPlayer( entity player )
+{
+}
+
+
+// Returns bool or nothing
+function RespawnTitanPilot( entity player )
+{
+}
+
+bool function ShouldEntTakeDamage_SPMP( entity ent, var damageInfo )
+{
+ return true
+}
+
+bool function ShouldUseReplacementSpawn( entity player )
+{
+ return false
+}
+
+void function TryGameModeAnnouncement( entity player )
+{
+
+}
+
+void function CodeCallback_OnClientConnectionStarted( entity player )
+{
+ // not a real player?
+ #if DEV
+ if ( player.GetPlayerName() == "Replay" )
+ return
+ #endif
+
+ if ( IsLobby() )
+ {
+ level.Lobby_OnClientConnectionStarted( player )
+ return
+ }
+
+// ScreenFade( player, 0, 0, 0, 255, 2.0, 0.5, FFADE_IN | FFADE_PURGE )
+
+ SetTargetName( player, "player" + player.entindex() )
+
+ player.p.controllableProjectiles_scriptManagedID = CreateScriptManagedEntArray()
+ player.p.npcFollowersArrayID = CreateScriptManagedEntArray()
+
+ player.s = {}
+ player.s.attackerInfo <- {}
+ player.p.clientScriptInitialized = player.IsBot()
+ player.s.inPostDeath <- null
+ player.s.respawnCount <- 0
+ player.s.respawnTime <- 0
+ player.s.lostTitanTime <- 0
+ player.s.cloakedShotsAllowed <- 0
+ player.s.startDashMeleeTime <- 0
+ player.s.respawnSelectionDone <- true // this gets set to false in postdeaththread but we need it to be true when connecting
+ player.s.waveSpawnProtection <- false
+
+ player.s.nextStatUpdateFunc <- null
+
+ player.s.activeTrapArrayId <- CreateScriptManagedEntArray()
+
+ player.s.restartBurnCardEffectOnSpawn <- false
+ player.s.replacementDropInProgress <- false
+
+ player.s.inGracePeriod <- true
+
+ // should I just add these when playing coop?
+ player.s.usedLoadoutCrate <- false
+ player.s.restockAmmoTime <- 0
+ player.s.restockAmmoCrate <- null
+
+ player.s.autoTitanLastEngageCalloutTime <- 0
+ player.s.autoTitanLastEngageCallout <- null
+ player.s.lastAIConversationTime <- {} // when was a conversation last played?
+
+ player.s.updatedPersistenceOnDisconnect <- false
+
+ player.s.lastFriendlySpawnedOn <- null
+ player.s.nextWaveSpawnTime <- 0.0
+
+ player.s.meleeSlowMoEndTime <- 0.0
+
+ Assert( !player._entityVars )
+
+ // Added via AddCallback_OnClientConnecting
+ foreach ( callbackFunc in svGlobal.onClientConnectingCallbacks )
+ {
+ callbackFunc( player )
+ }
+
+ // do this after callbacks so we can define our own player script vars in callbacks
+ InitEntityVars( player )
+
+ printl( "Player connect started: " + player )
+
+ thread PilotHealthRegenThinkSP( player )
+}
+
+void function CodeCallback_OnPlayerKilled( entity player, var damageInfo )
+{
+ thread PostDeathThread_SP( player, damageInfo )
+}
+
+function PostDeathThread_SP( entity player, damageInfo )
+{
+ if ( player.p.watchingPetTitanKillReplay )
+ return
+
+ float timeOfDeath = Time()
+ player.p.postDeathThreadStartTime = Time()
+
+ Assert( IsValid( player ), "Not a valid player" )
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnRespawned" )
+
+ player.p.deathOrigin = player.GetOrigin()
+ player.p.deathAngles = player.GetAngles()
+
+ player.s.inPostDeath = true
+ player.s.respawnSelectionDone = false
+
+ player.cloakedForever = false
+ player.stimmedForever = false
+ player.SetNoTarget( false )
+ player.SetNoTargetSmartAmmo( false )
+ player.ClearExtraWeaponMods()
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ int methodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ player.p.rematchOrigin = player.p.deathOrigin
+ if ( IsValid( attacker ) && methodOfDeath == eDamageSourceId.titan_execution )
+ {
+ // execution can throw you out of the map
+ player.p.rematchOrigin = attacker.GetOrigin()
+ }
+
+ int attackerViewIndex = attacker.GetIndexForEntity()
+
+ player.SetPredictionEnabled( false )
+ player.Signal( "RodeoOver" )
+ player.ClearParent()
+ bool showedDeathHint = ShowDeathHintSP( player, damageInfo )
+
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( damageSource != eDamageSourceId.fall )
+ {
+ player.StartObserverMode( OBS_MODE_DEATHCAM )
+ if ( ShouldSetObserverTarget( attacker ) )
+ player.SetObserverTarget( attacker )
+ else
+ player.SetObserverTarget( null )
+ }
+
+ foreach( callbackFunc in svGlobal.onPlayerKilledCallbacks )
+ {
+ callbackFunc( player, attacker, damageInfo )
+ }
+
+ float reloadExtraDelay
+ if ( showedDeathHint )
+ reloadExtraDelay = 3.2
+
+ ReloadForMissionFailure( reloadExtraDelay )
+}
+
+
+
+
+function FinalPlayerUpdate( entity player )
+{
+ // every player runs this either after match/evac, or when they disconnect
+
+ // in case you disconnect during final scoreboard
+ if ( "ranFinalPlayerUpdate" in player.s )
+ return
+ player.s.ranFinalPlayerUpdate <- true
+}
+
+/*------------------------------------------------------------------------
+DUMPSITE FROM OTHER FILES
+--------------------------------------------------------------------------*/
+
+string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam, vector origin, float timeout, float length, width = null, angles = null )
+{
+ return ""
+}
+
+void function DeleteNoSpawnArea( string id )
+{
+
+}
+
+
+bool function IsSpawnpointValidDrop( entity spawnpoint, int team )
+{
+ return true
+}
+
+bool function ClientCommand_OpenDifficultyMenu( entity player, array<string> args )
+{
+ if ( IsValid( player ) )
+ Remote_CallFunction_UI( player, "ServerCallback_OpenDifficultyMenu" )
+ return true
+}
+
+
+void function CodeCallback_PlayerHasBeenConnectedForDuration( entity player, float durationInSeconds ) //Empty function declaration to stop load error.
+{
+}
+
+void function OnPlayerKilled_DeathNotify( entity player, entity attacker, var damageInfo )
+{
+ EmitSoundOnEntity( player, "Player_Death_Begin" )
+}
+
+void function CodeCallback_WeaponDropped( entity weapon )
+{
+ if ( !IsValid( weapon ) )
+ return
+
+ if ( !Flag( "WeaponDropsAllowed" ) )
+ {
+ weapon.Destroy()
+ return
+ }
+
+ int loadoutIndex = GetSPTitanLoadoutIndexForWeapon( weapon.GetWeaponClassName() )
+ if ( loadoutIndex >= 0 )
+ {
+ if ( IsBTLoadoutUnlocked( loadoutIndex ) )
+ {
+ weapon.Destroy()
+ return
+ }
+ else
+ {
+ weapon.MarkAsLoadoutPickup()
+ }
+ }
+
+ SetTeam( weapon, TEAM_UNASSIGNED )
+
+ if ( weapon.IsLoadoutPickup() )
+ {
+ HighlightWeapon( weapon )
+ }
+ else
+ {
+ Highlight_SetOwnedHighlight( weapon, "weapon_drop_active" )
+ Highlight_SetNeutralHighlight( weapon, "weapon_drop_fresh" )
+ thread RevertToRegularHighlight( weapon )
+ }
+}
+
+void function RevertToRegularHighlight( entity weapon )
+{
+ weapon.Signal( "RevertToRegularHighlight" )
+ weapon.EndSignal( "RevertToRegularHighlight" )
+ weapon.EndSignal( "OnDestroy" )
+ wait 4.0
+ HighlightWeapon( weapon )
+}
+
+void function RefreshWeaponHighlightTitanTransfer( entity player, entity titan )
+{
+ RefreshWeaponHighlights( player )
+}
+
+void function CodeCallback_OnWeaponTouch( entity player, entity weapon, int ammoRecieved )
+{
+ if ( !IsAlive( player ) )
+ return
+
+ if ( Time() - file.lastObitMsgTime < 0.1 )
+ return
+
+ if ( ammoRecieved == 0 )
+ {
+ if ( !PlayerCanUseWeapon( player, weapon.GetWeaponClass() ) )
+ return
+
+ if ( !PlayerHasWeapon( player, weapon.GetWeaponClassName() ) )
+ return
+
+ MessageToPlayer( player, eEventNotifications.WEAP_AmmoFull, weapon, ammoRecieved )
+ }
+ else
+ {
+ MessageToPlayer( player, eEventNotifications.WEAP_GotAmmo, weapon, ammoRecieved )
+ }
+
+ file.lastObitMsgTime = Time()
+}
+void function Callback_WallrunBegin( entity player )
+{
+ file.wallRunKills = 0
+}
+
+void function OnNPCKilled( entity npc, var damageInfo )
+{
+ entity player = DamageInfo_GetAttacker( damageInfo )
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( !player.IsPlayer() )
+ return
+
+ if ( player.IsTitan() )
+ {
+ if ( IsTitanCrushDamage( damageInfo ) )
+ {
+ thread PlayerTitanCrushKill_TryRumble( player, npc )
+ }
+
+ if ( IsHumanSized( npc ) )
+ {
+ TryInfantryMultiKill( player )
+ }
+
+ return
+ }
+
+ entity weapon = DamageInfo_GetWeapon( damageInfo )
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( !IsValid( weapon ) )
+ return
+
+ if ( weapon.IsWeaponOffhand() && damageSource != eDamageSourceId.sp_weapon_arc_tool ) // special case let arc tool through
+ return
+
+ if ( player.IsWallRunning() )
+ file.wallRunKills++
+
+ if ( player.IsSliding() )
+ {
+ if ( file.slideKills == 0 )
+ {
+ thread TrackSlideEnd( player )
+ }
+ file.slideKills++
+ }
+
+ printt( "Wall Run Kills: " + file.wallRunKills )
+ printt( "Slide Kills: " + file.slideKills )
+
+ if ( file.wallRunKills >= 3 )
+ UnlockAchievement( player, achievements.WALLRUN_KILLS )
+
+ if ( file.slideKills >= 3 )
+ UnlockAchievement( player, achievements.SLIDE_KILLS )
+}
+
+void function PlayerTitanCrushKill_TryRumble( entity player, entity victim )
+{
+ player.EndSignal( "OnDeath" )
+
+ if ( !IsHumanSized( victim ) && !IsProwler( victim ) )
+ return
+
+ float rumbleAmplitude = 80.0
+ float rumbleFrequency = 150.0
+ float rumbleDuration = 0.4
+
+ // bigger rumble if victim is tougher than a Grunt
+ if ( victim.IsMechanical() || IsProwler( victim ) )
+ {
+ rumbleAmplitude = 140.0
+ rumbleFrequency = 100.0
+ rumbleDuration = 0.6
+ }
+
+ // delay makes it feel better with the SFX
+ // (sound starts at same time, but takes a moment to get loud)
+ wait 0.1
+
+ CreateAirShakeRumbleOnly( player.GetOrigin(), rumbleAmplitude, rumbleFrequency, rumbleDuration )
+ //printt( "CRUSH RUMBLE", rumbleAmplitude )
+}
+
+void function TrackSlideEnd( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( )
+ {
+ file.slideKills = 0
+ }
+ )
+
+ while ( player.IsSliding() )
+ {
+ wait 0.1
+ }
+}
+
+void function TryInfantryMultiKill( entity player )
+{
+ if ( file.lastMultiKillStartTime + 2.0 < Time() )
+ {
+ file.lastMultiKillStartTime = Time()
+ file.multiKillCount = 0
+ }
+
+ file.multiKillCount++
+
+ printt( "Multi Kills: " + file.multiKillCount )
+
+ if ( file.multiKillCount >= 25 )
+ UnlockAchievement( player, achievements.INFANTRY_MULTIKILL )
+} \ No newline at end of file
diff --git a/Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut b/Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut
new file mode 100644
index 000000000..5783f28fc
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/_coop_sp_utils.gnut
@@ -0,0 +1,177 @@
+untyped
+global const float COOP_RESPAWN_DELAY = 5.0
+
+global function CoopSpUtils_Init
+
+global function GetPlayerToSpawnOn
+global function RestartWithoutDroppingPlayers
+global function TeleportToEntitySafe
+global function GetEntitiesByEditorClass
+
+global function GetPlayersInTimeline
+
+void function CoopSpUtils_Init()
+{
+ AddCallback_OnClientConnecting( OnClientConnecting )
+ AddDeathCallback( "player", OnPlayerDeath )
+}
+
+void function OnClientConnecting( entity player )
+{
+ // add custom script vars
+ if ( IsPlayingTimeshiftLevel() )
+ {
+ player.s.timeline <- 1 // TIMEZONE_NIGHT
+ player.s.isTimeTraveling <- false
+ player.s.lastGoodTimeshiftPosOvergrown <- <0, 0, 0>
+ player.s.lastGoodTimeshiftPosPristine <- <0, 0, 0>
+ }
+}
+
+void function OnPlayerDeath( entity player, var damageInfo )
+{
+ // add respawn delay, use networked entity var for client respawn timer
+ player.nv.nextRespawnTime = Time() + COOP_RESPAWN_DELAY
+
+ if ( AreAllPlayersDead() )
+ thread FailLevel( COOP_RESPAWN_DELAY )
+}
+
+void function FailLevel( float respawnTime )
+{
+ wait respawnTime
+ RestartWithoutDroppingPlayers()
+}
+
+entity ornull function GetPlayerToSpawnOn()
+{
+ array< entity > possiblePlayers
+ foreach ( entity player in GetPlayerArray())
+ {
+ if ( IsAlive( player ))
+ possiblePlayers.append( player )
+ }
+
+ if ( possiblePlayers.len() == 0 )
+ return null // everyone is dead
+
+ return possiblePlayers[ RandomInt( possiblePlayers.len() ) ]
+}
+
+void function RestartWithoutDroppingPlayers()
+{
+ // todo: make this deal with checkpoints/saves
+ ServerCommand( "changelevel " + GetMapName() )
+}
+
+void function TeleportToEntitySafe( entity teleported, entity target )
+{
+ // teleport to other entities without getting stuck in ceilings and shit
+ // PLEASE dont call this often, it's weird when used on living players
+ // was designed only to be used on spawn/respawn code
+
+ vector targetPos = target.GetOrigin()
+
+ // intelligent checks
+ // is the spot already valid?
+ if ( !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos ) )
+ {
+ print( "TeleportToEntitySafe: pos was valid first time" )
+ teleported.SetOrigin( targetPos )
+ teleported.SetAngles( target.GetAngles() )
+ return
+ }
+ else if ( target.IsCrouched() && !teleported.IsCrouched() && !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos, true ) )
+ {
+ // teleporting to sliding players while not sliding can cause clipping
+ // so if we set the player to be sliding maybe it'll kinda sorta work out
+
+ // check if we can just offset ourselves downwards to a valid pos without crouching
+ // 25 is the diff in collision height between stood up/crouched
+ if ( !PlayerPosInSolidIgnoreOtherPlayers( teleported, targetPos + <0, 0, -25> ) )
+ {
+ print( "TeleportToEntitySafe: offset to valid position to account for crouch!" )
+ teleported.SetOrigin( targetPos + <0, 0, -25> )
+ teleported.SetAngles( target.GetAngles() )
+ return
+ }
+ else
+ {
+ print( "TeleportToEntitySafe: couldn't offset to valid pos for crouch, waiting for crouch to finish" )
+ teleported.ForceCrouch()
+ }
+ }
+
+ // last resort - give up and wait for the pos to be valid
+ // given this is mainly used for teleporting to players it can probably be abused
+
+ float startTime = Time()
+ float timeout = 5.0
+
+ print( "TeleportToEntitySafe: couldn't get a safe pos with starting conditions, waiting for pos to be valid" )
+ while ( PlayerPosInSolidIgnoreOtherPlayers( teleported, target.GetOrigin() ) )
+ {
+ WaitFrame()
+
+ if ( Time() - startTime > timeout )
+ {
+ // if we take too long, we probably aren't gonna teleport anyway, so why bother wasting cpu time on it
+ print( "TeleportToEntitySafe: timed out waiting for successful teleport attempt" )
+ return
+ }
+ }
+
+ print( "TeleportToEntitySafe: done waiting for pos to be valid!" )
+ teleported.SetOrigin( target.GetOrigin() )
+ teleported.SetAngles( target.GetAngles() )
+ teleported.UnforceCrouch()
+}
+
+bool function PlayerPosInSolidIgnoreOtherPlayers( entity player, vector targetPos, bool fakeCrouch = false )
+{
+ // playerposinsolid by default doesn't ignore other players, only the player they're checking for
+ // this reimplements it, but ignores all players
+ // don't wanna patch the original function because the original behaviour could be useful
+
+ vector maxs = player.GetPlayerMaxs()
+
+ if ( maxs.z == 32 )
+ maxs.z = 72 // maxs seem broken when connecting so make sure they're normal for this
+
+ if ( fakeCrouch )
+ maxs.z = 47 // 47 when crouched, 72 uncrouched
+
+ TraceResults traceResult = TraceHull( targetPos, targetPos + <0, 0, 1>, player.GetPlayerMins(), maxs, GetPlayerArray(), TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER )
+ return traceResult.startSolid
+}
+
+array< entity > function GetEntitiesByEditorClass( string editorClass )
+{
+ array< entity > ret
+ for ( int i = 0; i < 2048 /*max ents in source*/; i++ )
+ {
+ entity ent = GetEntByIndex( i )
+ if ( ent == null )
+ continue
+
+ if ( GetEditorClass( ent ) == editorClass )
+ ret.append( ent )
+ }
+
+ return ret
+}
+
+
+//EFFECT AND CAUSE/TIMESHIFT STUFF
+
+array< entity > function GetPlayersInTimeline( int timeline )
+{
+ array< entity > playersInTimeline
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( player.s.timeline == timeline )
+ playersInTimeline.append( player )
+ }
+
+ return playersInTimeline
+} \ No newline at end of file
diff --git a/Northstar.Coop/scripts/vscripts/sp/_gamestate_sp.gnut b/Northstar.Coop/scripts/vscripts/sp/_gamestate_sp.gnut
new file mode 100644
index 000000000..ac741370f
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/_gamestate_sp.gnut
@@ -0,0 +1,793 @@
+const RESPAWN_TRIG_RECHECK_WAIT = 1
+const DEFAULT_SP_ARRIVAL_TOLERANCE = 200
+const DEFAULT_SP_ASSAULT_TIMEOUT = 0
+
+// move these to auto precache
+global const TEAM_MIL_GRUNT_MODEL = $"models/humans/grunts/mlt_grunt_rifle.mdl"
+global const TEAM_MIL_GRUNT_MODEL_LMG = $"models/humans/grunts/mlt_grunt_lmg.mdl"
+global const TEAM_MIL_GRUNT_MODEL_RIFLE = $"models/humans/grunts/mlt_grunt_rifle.mdl"
+global const TEAM_MIL_GRUNT_MODEL_ROCKET = $"models/humans/grunts/mlt_grunt_rifle.mdl"
+global const TEAM_MIL_GRUNT_MODEL_SHOTGUN = $"models/humans/grunts/mlt_grunt_shotgun.mdl"
+global const TEAM_MIL_GRUNT_MODEL_SMG = $"models/humans/grunts/mlt_grunt_smg.mdl"
+
+
+global function GamemodeSp_Init
+global function GiveBatteryChargeTool
+global function HasBatteryChargeTool
+global function FriendlyFire_MissionFailure
+global function DamageAlwaysLethal
+global function ServerRestartMission
+global function DisableFriendlyHighlight
+global function EnableFriendlyHighlight
+global function SP_PlayerLastSlideTime
+
+//global function CodeCallback_Ping
+
+global function SetGameState
+global function GetGameModeAnnouncement
+
+global function CodeCallback_GamerulesThink
+
+global struct SvGlobalsSP
+{
+ int gruntCombatState = eGruntCombatState.IDLE
+
+ array<void functionref( entity )> onLoadSaveGameCallbacks
+}
+
+global SvGlobalsSP svGlobalSP
+
+//////////////////////////////
+
+global function DEV_ToggleFriendlyHighlight
+
+//////////////////////////////
+
+struct
+{
+ LevelTransitionStruct ornull storedLevelTransitionStruct
+ entity spPetTitanStart
+ bool friendlyHighlightEnabled = true
+ float playerLastSlideTime
+} file
+
+void function GamemodeSp_Init()
+{
+ FlagInit( "FriendlyFireStrict" )
+ FlagInit( "TitanCanSavePlayer" )
+ FlagInit( "TitanDeathPenalityDisabled" )
+ FlagInit( "SaveGame_Enabled", true )
+ FlagInit( "MissionFailed" )
+ RegisterSignal( "TitanSavesPlayer" )
+
+ FlagSet( "PilotBot" )
+
+ AddSoulDeathCallback( SoulDeath_ReloadOnPetTitanDeath )
+ AddClientCommandCallback( "RestartMission", ClientCommand_RestartMission )
+
+ SetRoundBased( false )
+
+ AddCallback_OnClientConnecting( SpPlayerConnecting )
+ AddCallback_OnClientConnected( SpPlayerConnected )
+
+ AddSpawnCallback( "npc_soldier", SpNpcCommon )
+ AddSpawnCallback( "npc_spectre", SpNpcCommon )
+ AddSpawnCallback( "npc_titan", SpNpcCommon )
+
+ AddSpawnCallback( "npc_soldier", SpNpcCommonGrunt )
+
+ AddSpawnCallbackEditorClass( "info_target", "info_pet_titan_start", PetTitanStartSpawnInit )
+ AddCallback_OnPlayerRespawned( GameStateSP_OnPlayerRespawn )
+
+ AddDamageCallbackSourceID( eDamageSourceId.damagedef_titan_fall, PreventFriendlyTitanfallDamage )
+ AddDamageCallbackSourceID( eDamageSourceId.damagedef_reaper_fall, PreventFriendlyTitanfallDamage )
+
+ shGlobal.proto_soldierShieldRegenDelay = 3000.0
+ svGlobal.cloakBreaksOnMelee = true
+ svGlobal.defaultPilotLeechTime = 2.0
+
+ //AddDamageCallback( "player", DiminishPlayerComboDamage )
+
+ SPTitanLoadout_SetupForLevelStart()
+
+ SpSharedInit()
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad_SpGameState )
+ AddDeathCallback( "npc_soldier", SoldierFriendlyFireCheck_OnKilled )
+
+ AddCallback_OnPilotBecomesTitan( PilotBecomesTitanStoreWeaponVar )
+
+ // SAVE THIS LEVEL IF IT'S IN THE LEVEL LIST
+ var dataTable = GetDataTable( $"datatable/sp_levels.rpak" )
+ string mapName = GetMapName()
+ string startPoint = GetStartPoint()
+ int numRows = GetDatatableRowCount( dataTable )
+ int lastLevelNum = 0
+
+ int bspCol = GetDataTableColumnByName( dataTable, "level" )
+ int levelCol = GetDataTableColumnByName( dataTable, "levelNum" )
+
+ for ( int i=0; i<numRows; i++ )
+ {
+ string levelBsp = GetDataTableString( dataTable, i, bspCol )
+ int levelNum = GetDataTableInt( dataTable, i, levelCol )
+
+ if ( levelBsp == mapName )
+ {
+ #if DEV
+ printt( "Setting this level as unlocked" )
+ printt( "BSP: " + levelBsp )
+ printt( "Level Num: " + levelNum )
+ #endif
+
+ SetConVarInt( "sp_lastMission", levelNum )
+ lastLevelNum = levelNum
+ break
+ }
+ }
+
+ int farthestLevelUnlocked = GetConVarInt( "sp_unlockedMission" )
+
+ if( Script_IsRunningTrialVersion() )
+ SetConVarInt( "sp_unlockedMission", 1 ) // Set to sp_crashsite so if they buy the game, it starts there.
+ else
+ SetConVarInt( "sp_unlockedMission", maxint(farthestLevelUnlocked, lastLevelNum) )
+
+ foreach( entity player in GetPlayerArray() )
+ UpdateHeroStatsForPlayer( player )
+
+ if ( IsTestMap() )
+ {
+ PilotLoadoutDef loadout = GetPilotLoadoutForCurrentMapSP()
+ foreach ( model in GetModelsFromSetFile( loadout.setFile ) )
+ {
+ PrecacheModel( model )
+ }
+
+ TitanLoadoutDef titanLoadout = GetTitanLoadoutForCurrentMap()
+ foreach ( model in GetModelsFromSetFile( titanLoadout.setFile ) )
+ {
+ PrecacheModel( model )
+ }
+ }
+}
+
+
+void function EntitiesDidLoad_SpGameState()
+{
+ SetGameState( eGameState.Playing )
+ FlagSet( "ReadyToStartMatch" )
+
+ file.storedLevelTransitionStruct = GetLevelTransitionStruct()
+
+ SetCustomIntroLength( 0 )
+
+// level.nv.replayDisabled = true //HACK - remove once the bug about replay hud is fixed
+ level.nv.minimapState = eMinimapState.Hidden
+
+ level.nv.replayDisabled = true
+ if ( !IsTestMap() )
+ level.nv.titanAvailability = eTitanAvailability.Never
+
+ ServerCommand( "sv_weapon_despawn_Time 180" )
+}
+
+/************************************************************************************************\
+
+ ###### ####### ## ## ## ## ######## ###### ########
+## ## ## ## ### ## ### ## ## ## ## ##
+## ## ## #### ## #### ## ## ## ##
+## ## ## ## ## ## ## ## ## ###### ## ##
+## ## ## ## #### ## #### ## ## ##
+## ## ## ## ## ### ## ### ## ## ## ##
+ ###### ####### ## ## ## ## ######## ###### ##
+
+\************************************************************************************************/
+void function SpPlayerConnecting( entity player )
+{
+ if ( GetPlayerArray().len() > 1 )
+ {
+ CodeWarning( "Can't play SP with more or less than 1 player" )
+ return
+ }
+
+ SetTeam( player, TEAM_MILITIA )
+ InitPassives( player )
+
+ player.SetInventoryChangedCallbackEnabled( true )
+
+ entity ornull spawningOn = GetPlayerToSpawnOn()
+ if ( spawningOn == null )
+ {
+ // vanilla spawning logic - spawn first player at the level's spawnpoint
+ entity start = GetEnt( "info_player_start" )
+ player.SetOrigin( start.GetOrigin() )
+ player.SetAngles( start.GetAngles() )
+ }
+ else
+ {
+ expect entity( spawningOn )
+ // use safe func otherwise we can get stuck in the ceiling and shit
+ thread TeleportToEntitySafe( player, spawningOn )
+ }
+
+ if ( IsValid( file.spPetTitanStart ) )
+ {
+ CreatePetTitan( player )
+ entity titan = player.GetPetTitan()
+ if ( titan != null )
+ titan.kv.alwaysAlert = false
+ }
+}
+
+void function GameStateSP_OnPlayerRespawn( entity player )
+{
+ UpdateSpDifficulty( player )
+ thread TrackPlayerLastSlideTime( player )
+
+ if ( !IsTestMap() )
+ {
+ CheckPoint_ForcedSilent()
+ }
+}
+
+void function SpPlayerConnected( entity player )
+{
+ thread SpPlayerConnected_Thread( player )
+}
+
+void function SpPlayerConnected_Thread( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ if ( IsTestMap() )
+ return
+
+ string levelName = GetMapName()
+ int startIndex = GetStartPointIndexFromName( levelName, GetCurrentStartPoint() )
+
+ string startPointEnum = GetStartPointNameFromIndex( levelName, startIndex )
+ if ( !StartPointHasDetente( levelName, startPointEnum ) )
+ return
+
+ var dataTable = GetDataTable( $"datatable/sp_introscreen.rpak" )
+ int row = GetDataTableRowMatchingStringValue( dataTable, GetDataTableColumnByName( dataTable, "level" ), levelName )
+ if ( row == -1 )
+ return
+
+ float delay = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, "bgFadeDelay" ) )
+ float fadeTime = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, "bgFadeTime" ) )
+
+ printt( "Detent fade in " + delay + " blend " + fadeTime )
+
+ WaitFrame()
+ WaitFrame() // wait two frames to fix ScreenFade bug
+
+ ScreenFade( player, 0, 0, 0, 255, fadeTime, delay, FFADE_IN | FFADE_PURGE )
+ //printt( "sv SCREENFADE: " + fadeTime + " " + delay )
+}
+
+float function SP_PlayerLastSlideTime()
+{
+ return file.playerLastSlideTime
+}
+
+void function PetTitanStartSpawnInit( entity spawn )
+{
+ file.spPetTitanStart = spawn
+}
+
+void function TrackPlayerLastSlideTime( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ for ( ;; )
+ {
+ if ( player.IsSliding() )
+ file.playerLastSlideTime = Time()
+ wait 0.5
+ }
+}
+
+void function CreatePetTitan( entity player )
+{
+ Assert( IsValid( file.spPetTitanStart ) )
+ // Player is in his Titan, don't create a titan
+ if ( player.IsTitan() )
+ return
+
+ // Player already has a pet titan, don't create one
+ entity petTitan = player.GetPetTitan()
+ if ( IsValid( petTitan ) )
+ return
+
+ // Make a pet titan at the spawn point
+ TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap()
+ entity titanStart = file.spPetTitanStart
+ entity titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, titanStart.GetOrigin(), titanStart.GetAngles() )
+ DispatchSpawn( titan )
+ player.SetPetTitan( titan )
+
+ titan.DisableHibernation()
+}
+
+/************************************************************************************************\
+
+######## ### ## ## ### ###### ########
+## ## ## ## ### ### ## ## ## ## ##
+## ## ## ## #### #### ## ## ## ##
+## ## ## ## ## #### ## ## ## ## ### ######
+## ## ######### ## ## ## ######### ## ## ##
+## ## ## ## ## ## ## ## ## ## ##
+######## ## ## ## ## ## ## ###### ########
+
+\************************************************************************************************/
+
+// putting this stuff here for now since it's only for SP at this point
+
+
+
+
+bool function TryTitanSavesPlayer( entity player )
+{
+ if ( !Flag( "TitanCanSavePlayer" ) )
+ return false
+
+ if ( !IsAlive( player.GetPetTitan() ) )
+ return false
+
+ entity titan = player.GetPetTitan()
+ if ( TitanIsCurrentlyEmbarkableForPlayer( player, titan ) )
+ {
+ thread TitanSavesPlayer( player, titan )
+ return true
+ }
+
+ return false
+}
+
+void function TitanSavesPlayer( entity player, entity titan )
+{
+ player.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDeath" )
+
+ player.SetNoTarget( true )
+ player.SetInvulnerable()
+ titan.SetInvulnerable()
+
+ player.DisableWeapon()
+ player.FreezeControlsOnServer()
+ player.ForceCrouch()
+
+ OnThreadEnd(
+ function() : ( player, titan )
+ {
+ if ( IsValid( player ) )
+ {
+ if ( !IsPlayerEmbarking( player ) )
+ {
+ player.EnableWeapon()
+ }
+ player.UnforceCrouch()
+ player.UnfreezeControlsOnServer()
+ player.ClearInvulnerable()
+ player.SetNoTarget( false )
+ }
+
+ if ( IsValid( titan ) )
+ titan.ClearInvulnerable()
+ }
+ )
+
+ wait 0.25
+
+ float fadeTime = 2
+ float blackTime_pre = 4.0
+ float blackTime_post = 1.0
+
+ thread ScreenFadeToBlack( player, 2.0, blackTime_pre )
+
+ wait blackTime_pre * 0.5
+ string titanAlias = GenerateTitanOSAlias( player, "briefCriticalDamage" )
+ thread EmitSoundOnEntity( player, titanAlias )
+ wait blackTime_pre * 0.5
+
+ vector ornull clampedPos = NavMesh_ClampPointForAI( player.GetOrigin(), titan )
+ if ( clampedPos != null )
+ titan.SetOrigin( expect vector( clampedPos ) )
+
+ titan.SetAngles( Vector( 0, player.GetAngles().y, 0 ) )
+
+ entity soul = titan.GetTitanSoul()
+ SetStanceKneel( soul )
+
+ table criteria = {
+ embark = "front",
+ titanCanStandRequired = false
+ }
+
+ var embarkAction
+ embarkAction = FindEmbarkActionForCriteria( criteria )
+ if ( embarkAction == null )
+ embarkAction = GetRandomEmbarkAction()
+
+ table action = expect table( GenerateEmbarkActionTable( player, titan, embarkAction ) )
+ thread PlayerEmbarksTitan( player, titan, action )
+ thread ScreenFadeFromBlack( player, 12.0, blackTime_post )
+
+ player.Signal( "TitanSavesPlayer" )
+}
+
+bool function DamageAlwaysLethal( var damageInfo )
+{
+ const damageMask = DF_INSTANT | DF_IMPACT | DF_TITAN_STEP | DF_KILLSHOT | DF_MELEE // | DF_EXPLOSION
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & damageMask )
+ return true
+// not quite sure what this returns. It doesn't seem to be the flags listed in death_package.nut
+// if ( DamageInfo_GetDamageType( damageInfo ) & damageMask )
+// return false
+
+ // damage type doesn't seem to be correct for most things so I'm forced to check GetDamageSourceIdentifier -Roger
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ case damagedef_crush:
+ case damagedef_nuclear_core:
+ case damagedef_suicide:
+ case damagedef_titan_fall:
+ case damagedef_titan_hotdrop:
+ case eDamageSourceId.bubble_shield:
+ case eDamageSourceId.burn:
+ case eDamageSourceId.droppod_impact:
+ case eDamageSourceId.evac_dropship_explosion:
+ case eDamageSourceId.fall:
+ case eDamageSourceId.flash_surge:
+ case eDamageSourceId.floor_is_lava:
+ case eDamageSourceId.human_execution:
+ case eDamageSourceId.human_melee:
+ case eDamageSourceId.indoor_inferno:
+ case eDamageSourceId.outOfBounds:
+ case eDamageSourceId.round_end:
+ case eDamageSourceId.splat:
+ case eDamageSourceId.stuck:
+ case eDamageSourceId.submerged:
+ case eDamageSourceId.switchback_trap:
+ case eDamageSourceId.team_switch:
+ case eDamageSourceId.titan_execution:
+ case eDamageSourceId.titan_explosion:
+ case eDamageSourceId.wall_smash:
+// case eDamageSourceId.grunt_melee:
+// case eDamageSourceId.prowler_melee:
+// case eDamageSourceId.spectre_melee:
+ return true
+
+ default:
+ break
+ }
+
+ return false
+}
+
+/************************************************************************************************\
+
+ ## ## ######## ######
+ ### ## ## ## ## ##
+ #### ## ## ## ##
+ ## ## ## ######## ##
+ ## #### ## ##
+ ## ### ## ## ##
+ ## ## ## ######
+
+\************************************************************************************************/
+void function SpNpcCommon( entity npc )
+{
+ if ( npc.GetTeam() == TEAM_MILITIA )
+ {
+ if ( !npc.IsTitan() )
+ HideName( npc )
+
+ // this is a temporary stop gap until we get skins
+ if ( file.friendlyHighlightEnabled )
+ Highlight_SetFriendlyHighlight( npc, "sp_friendly_pilot" )
+ }
+}
+
+void function SpNpcCommonGrunt( entity npc )
+{
+ // heros clear this setting so they should keep their names
+ if ( npc.GetTeam() == TEAM_MILITIA )
+ {
+
+ string title
+ if ( npc.Dev_GetAISettingByKeyField( "IsGenericGrunt" ) == 0 )
+ {
+ title = npc.GetSettingTitle()
+ }
+ else
+ {
+ title = GetMilitiaTitle()
+ }
+
+ npc.SetTitle( title )
+ ShowName( npc )
+ }
+}
+
+void function DiminishPlayerComboDamage( entity player, var damageInfo )
+{
+ if ( !IsPilot( player ) )
+ return
+
+// printt( "damage " + DamageInfo_GetDamage( damageInfo ) )
+// printt( "shield health " + player.GetShieldHealth() )
+ if ( player.GetShieldHealth() > 0 )
+ return
+
+ // blunt damage from combos
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ float recentDamage = TotalDamageOverTime_BlendedOut( player, 0.5, 1.5 )
+
+ // damage is ramped down based on how much damage was taken recently
+ float damageMod = GraphCapped( recentDamage, 70, 140, 1.0, 0.1 )
+ DamageInfo_ScaleDamage( damageInfo, damageMod )
+}
+
+void function SoulDeath_ReloadOnPetTitanDeath( entity soul, var damageInfo )
+{
+ if ( Flag( "TitanDeathPenalityDisabled" ) )
+ return
+
+ if ( IsTestMap() )
+ return
+
+ thread SoulDeath_ReloadOnPetTitanDeath_Thread( soul, damageInfo )
+}
+
+void function SoldierFriendlyFireCheck_OnKilled( entity npc, var damageInfo )
+{
+ if ( !Flag( "FriendlyFireStrict" ) )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !attacker.IsPlayer() )
+ return
+
+ if ( npc.GetTeam() != attacker.GetTeam() )
+ return
+
+ thread SoldierFriendlyFireCheck_OnKilledDelayed()
+}
+
+void function SoldierFriendlyFireCheck_OnKilledDelayed()
+{
+ FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost
+ wait 0.75
+ FriendlyFire_MissionFailure()
+}
+
+void function FriendlyFire_MissionFailure()
+{
+ if ( Flag( "MissionFailed" ) )
+ return
+
+ foreach ( player in GetPlayerArray() )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_FriendlyFire_MissionFailure" )
+ }
+
+ ReloadForMissionFailure()
+}
+
+void function SoulDeath_ReloadOnPetTitanDeath_Thread( entity soul, var damageInfo )
+{
+ entity player = soul.GetBossPlayer()
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( !level.nv.replayDisabled )
+ {
+ if ( player.p.watchingPetTitanKillReplay )
+ return
+
+ player.p.watchingPetTitanKillReplay = true
+ }
+
+ if ( damageInfo == null )
+ {
+ printt( "ServerCallback_TitanDied with null sourceid" )
+ ReloadForMissionFailure()
+ return
+ }
+
+ int source = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_TitanDied", source )
+ #if DEV
+ printt( "ServerCallback_TitanDied with sourceid " + source + " and source string " + GetObitFromDamageSourceID( source ) )
+ #endif
+
+ if ( IsInstantDeath( damageInfo ) )
+ {
+ ReloadForMissionFailure()
+ return
+ }
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) )
+ {
+ ReloadForMissionFailure()
+ return
+ }
+
+ int index = attacker.GetIndexForEntity()
+ entity titan = soul.GetTitan()
+
+ FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost
+
+ if ( !level.nv.replayDisabled )
+ {
+ /*
+ wait 3.8
+ float replayDelay = 6.5
+ player.SetKillReplayDelay( replayDelay )
+ player.SetViewIndex( index )
+
+ if ( IsValid( titan ) )
+ player.SetKillReplayVictim( titan )
+
+ ReloadForMissionFailure( replayDelay + 2.5 )
+ */
+ }
+ else
+ {
+ ReloadForMissionFailure() // replayDelay + 2.5
+ }
+}
+
+bool function HasBatteryChargeTool( entity player )
+{
+ switch ( CHARGE_TOOL )
+ {
+ case "sp_weapon_proto_battery_charger_offhand":
+ return HasOffhandWeapon( player, CHARGE_TOOL )
+
+ case "sp_weapon_arc_tool":
+ return HasWeapon( player, CHARGE_TOOL )
+ }
+
+ unreachable
+}
+
+void function GiveBatteryChargeTool( entity player )
+{
+ switch ( CHARGE_TOOL )
+ {
+ case "sp_weapon_proto_battery_charger_offhand":
+ player.GiveOffhandWeapon( CHARGE_TOOL, OFFHAND_SPECIAL )
+ break
+
+ case "sp_weapon_arc_tool":
+ player.GiveWeapon( CHARGE_TOOL )
+ break
+ }
+ UpdateArcConnectorHints()
+}
+
+/*
+void function CodeCallback_Ping( entity player )
+{
+
+}
+*/
+
+/* ----------------------------------------------------------------------------------------------
+
+STUFF FORM MP THAT WE WILL SLOWLY GET RID OF
+
+-------------------------------------------------------------------------------------------------*/
+
+string function GetGameModeAnnouncement()
+{
+ return ""
+}
+
+void function SetGameState( int newState )
+{
+ level.nv.gameStartTime = Time()
+ level.nv.gameStateChangeTime = Time()
+ level.nv.gameState = newState
+ svGlobal.levelEnt.Signal( "GameStateChanged" )
+
+ foreach ( callbackFunc in svGlobal.gameStateEnterCallbacks[ newState ] )
+ {
+ callbackFunc()
+ }
+}
+
+void function CodeCallback_GamerulesThink()
+{
+
+}
+
+void function DEV_ToggleFriendlyHighlight()
+{
+ file.friendlyHighlightEnabled = !file.friendlyHighlightEnabled
+ UpdateFriendlyHighlight()
+}
+
+void function DisableFriendlyHighlight()
+{
+ file.friendlyHighlightEnabled = false
+ UpdateFriendlyHighlight()
+}
+
+void function EnableFriendlyHighlight()
+{
+ file.friendlyHighlightEnabled = true
+ UpdateFriendlyHighlight()
+}
+
+void function UpdateFriendlyHighlight()
+{
+ if ( file.friendlyHighlightEnabled )
+ {
+ foreach ( npc in GetNPCArrayOfTeam( TEAM_MILITIA ) )
+ Highlight_SetFriendlyHighlight( npc, "sp_friendly_pilot" )
+ }
+ else
+ {
+ foreach ( npc in GetNPCArrayOfTeam( TEAM_MILITIA ) )
+ Highlight_ClearFriendlyHighlight( npc )
+ }
+}
+
+bool function ClientCommand_RestartMission( entity player, array<string> args )
+{
+ ServerRestartMission( player )
+ return true
+}
+
+void function ServerRestartMission( entity player )
+{
+ if ( IsTestMap() )
+ {
+ ServerCommand( "reload" )
+ return
+ }
+
+ string mapName = GetMapName()
+ LevelTransitionStruct ornull trans = file.storedLevelTransitionStruct
+ if ( trans == null )
+ {
+ int startIndex = 0
+ #if DEV
+ string startName = Dev_GetStartCommandLine( mapName )
+ if ( startName != "" )
+ startIndex = GetStartPointIndexFromName( mapName, startName )
+ #endif
+ // loaded a level manually in dev or are in sp_training
+ ExecuteLoadingClientCommands_SetStartPoint( mapName, startIndex )
+ ClientCommand( player, "map " + mapName )
+ return
+ }
+
+ expect LevelTransitionStruct( trans )
+ ExecuteLoadingClientCommands_SetStartPoint( mapName, trans.startPointIndex )
+ ChangeLevel( mapName, trans )
+}
+
+void function PilotBecomesTitanStoreWeaponVar( entity pilot, entity npc_titan )
+{
+ entity weapon = pilot.GetMainWeapons()[0]
+ int index = GetTitanLoadoutIndex( weapon.GetWeaponClassName() )
+ SetConVarInt( "sp_titanLoadoutCurrent", index )
+}
+
+void function PreventFriendlyTitanfallDamage( entity ent, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( attacker.GetTeam() == ent.GetTeam() )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+} \ No newline at end of file
diff --git a/Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut b/Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut
new file mode 100644
index 000000000..4cde30941
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/_gauntlet.gnut
@@ -0,0 +1,1054 @@
+global function Gauntlet_ServerInit
+global function EnableAllGauntlets
+global function DisableAllGauntlets
+global function EnableGauntlet
+global function DisableGauntlet
+global function Gauntlet_HideLeaderboard
+global function Gauntlet_ShowLeaderboard
+global function Gauntlet_NPC_PostSpawn
+global function ClientCommand_Gauntlet_PlayerRestartedFromMenu
+global function Gauntlet_StartGhostPlayback
+global function Gauntlet_StopGhostPlayback
+global function Gauntlet_ChallengeLeaderboardGhosts
+#if DEV
+global function Gauntlet_Player_GhostRecordOrPlayback
+#endif
+
+const float GAUNTLET_ENEMY_MISSED_TIME_PENALTY = 2.0
+const float GAUNTLET_TARGET_DISSOLVE_TIME = 1.0
+const float GAUNTLET_TARGET_DISSOLVE_TIME_MS = GAUNTLET_TARGET_DISSOLVE_TIME * 100
+
+void function Gauntlet_ServerInit()
+{
+ AddClientCommandCallback( "Gauntlet_PlayerRestartedFromMenu", ClientCommand_Gauntlet_PlayerRestartedFromMenu )
+ AddCallback_EntitiesDidLoad( Gauntlet_PostEntityLoadSetup )
+ AddCallback_OnClientConnected( Gauntlet_PlayerConnected )
+ AddCallback_OnLoadSaveGame( Gauntlet_OnLoadSaveGame )
+
+ RegisterSignal( "trigStart_OnStartTouch" )
+ RegisterSignal( "trigStart_OnEndTouch" )
+ RegisterSignal( "trigStart2_OnStartTouch" )
+ RegisterSignal( "trigStart2_OnEndTouch" )
+
+ RegisterSignal( "Gauntlet_PlayerHitStartTrig" )
+ RegisterSignal( "Gauntlet_PlayerWentBackwardsThroughStartTrig" )
+ RegisterSignal( "Gauntlet_PlayerHitFinishTrig" )
+ RegisterSignal( "Gauntlet_CheckpointHit" )
+ RegisterSignal( "Gauntlet_ForceRestart" )
+ RegisterSignal( "GhostAnimationPlayback_Start" )
+ RegisterSignal( "GhostAnimationPlayback_Stop" )
+ RegisterSignal( "Gauntlet_PlayerBeatChallengeGhost" )
+ RegisterSignal( "Gauntlet_PlayerBeatAllChallengeGhosts" )
+ #if DEV
+ RegisterSignal( "RecordAnimation_Start")
+ RegisterSignal( "Player_StartRecordingGhost_HintStart" )
+ #endif
+}
+
+void function Gauntlet_PostEntityLoadSetup()
+{
+ foreach ( gauntlet in GetGauntlets() )
+ {
+ InitGauntlet( gauntlet )
+
+ if ( gauntlet.startEnabled )
+ EnableGauntlet( gauntlet )
+ }
+}
+
+void function Gauntlet_PlayerConnected( entity player )
+{
+ foreach ( gauntlet in GetGauntlets() )
+ {
+ // send ghost duration data to client- only server can read the anim durations
+ foreach ( ghost in gauntlet.ghosts )
+ Remote_CallFunction_Replay( player, "ScriptCallback_Gauntlet_SetGhostDuration", gauntlet.id, ghost.id, ghost.duration )
+
+ if ( gauntlet.showLeaderboard )
+ {
+ Gauntlet_ShowLeaderboard( gauntlet )
+
+ if ( gauntlet.activeGhostID != -1 )
+ Gauntlet_RefreshActiveGhostID( gauntlet )
+ }
+ }
+}
+
+void function Gauntlet_OnLoadSaveGame( entity player )
+{
+ thread Gauntlet_OnLoadSaveGame_Thread( player )
+}
+
+void function Gauntlet_OnLoadSaveGame_Thread( entity player )
+{
+ wait 1.0
+ Gauntlet_PlayerConnected( player )
+}
+
+// turns on a particular gauntlet
+void function EnableGauntlet( GauntletInfo gauntlet )
+{
+ Assert( gauntlet.isInited, "Must run InitGauntlet before enabling" )
+
+ if ( gauntlet.isEnabled )
+ return
+
+ Gauntlet_CreateSignalEnt( gauntlet )
+
+ foreach ( player in GetPlayerArray() )
+ Remote_CallFunction_Replay( player, "ScriptCallback_EnableGauntlet", gauntlet.id )
+
+ thread Gauntlet_Think( gauntlet )
+
+ gauntlet.isEnabled = true
+}
+
+// turns off a particular gauntlet
+void function DisableGauntlet( GauntletInfo gauntlet )
+{
+ if ( !gauntlet.isEnabled )
+ return
+
+ gauntlet.signalEnt.Signal( "DisableGauntlet" )
+
+ Gauntlet_CleanupSignalEnt( gauntlet )
+ Gauntlet_ClearSpawnedNPCs( gauntlet )
+ thread ClearDroppedWeapons( GAUNTLET_TARGET_DISSOLVE_TIME + 0.1 ) // needs to be longer than gauntlet ghost dissolve time so weapons drop
+
+ foreach ( player in GetPlayerArray() )
+ Remote_CallFunction_Replay( player, "ScriptCallback_DisableGauntlet", gauntlet.id )
+
+ gauntlet.isEnabled = false
+}
+
+void function Gauntlet_HideLeaderboard( GauntletInfo gauntlet )
+{
+ Gauntlet_SetLeaderboardEnabled( gauntlet, false )
+
+ foreach ( player in GetPlayerArray() )
+ Remote_CallFunction_Replay( player, "ScriptCallback_HideLeaderboard", gauntlet.id )
+}
+
+void function Gauntlet_ShowLeaderboard( GauntletInfo gauntlet )
+{
+ Gauntlet_SetLeaderboardEnabled( gauntlet, true )
+
+ foreach ( player in GetPlayerArray() )
+ Remote_CallFunction_Replay( player, "ScriptCallback_ShowLeaderboard", gauntlet.id )
+}
+
+void function Gauntlet_Checkpoints( GauntletInfo gauntlet )
+{
+ if ( !gauntlet.checkpoints.len() )
+ return
+
+ foreach ( trig in gauntlet.checkpoints )
+ thread Gauntlet_CheckpointTrig_WaitForPlayer( gauntlet, trig )
+}
+
+void function Gauntlet_CheckpointTrig_WaitForPlayer( GauntletInfo gauntlet, entity trig )
+{
+ gauntlet.player.EndSignal( "OnDestroy" )
+ gauntlet.player.EndSignal( "Gauntlet_RunStarted" )
+ gauntlet.player.EndSignal( "Gauntlet_RunStopped" )
+ trig.EndSignal( "OnDestroy" )
+
+ table result
+ entity activator
+
+ while ( 1 )
+ {
+ result = trig.WaitSignal( "OnStartTouch" )
+ activator = expect entity( result.activator )
+
+ if ( !activator.IsPlayer() )
+ continue
+
+ if ( !IsAlive( activator ) )
+ continue
+
+ if ( activator.IsTitan() )
+ continue
+
+ break
+ }
+
+ gauntlet.checkpointsHit++
+ activator.Signal( "Gauntlet_CheckpointHit" )
+}
+
+void function Gauntlet_ClearSpawnedNPCs( GauntletInfo gauntlet )
+{
+ foreach ( guy in gauntlet.spawned )
+ {
+ if ( IsAlive( guy ) )
+ {
+ Gauntlet_UnfreezeNPC( guy )
+ guy.Die()
+ }
+ }
+
+ gauntlet.spawned = []
+}
+
+void function Gauntlet_SpawnNPCs( GauntletInfo gauntlet )
+{
+ Gauntlet_ClearSpawnedNPCs( gauntlet )
+
+ array<entity> spawned = SpawnFromSpawnerArray( gauntlet.spawners )
+ foreach ( guy in spawned )
+ thread Gauntlet_NPC_PostSpawn( guy, gauntlet )
+
+ gauntlet.spawned = spawned
+}
+
+void function Gauntlet_NPC_PostSpawn( entity npc, GauntletInfo gauntlet )
+{
+ if ( IsGrunt( npc ) )
+ {
+ // TODO- pulse as player runs through course, so the effect highlights the different ranges where the enemies are
+ Highlight_SetEnemyHighlightWithParam1( npc, "gauntlet_target_highlight", npc.EyePosition() )
+
+ npc.SetHealth( 1 )
+
+ npc.SetCanBeMeleeExecuted( false )
+ }
+
+ npc.EndSignal( "OnDeath" )
+ thread Gauntlet_NPC_DeathWait( npc, gauntlet )
+
+ AddEntityCallback_OnDamaged( npc, Gauntlet_NPC_Damaged )
+
+ npc.SetNoTarget( true )
+ npc.SetEfficientMode( true )
+ npc.SetHologram()
+ npc.SetDeathActivity( "ACT_DIESIMPLE" )
+
+ wait RandomFloatRange( 0.5, 1.0 ) // This is no good, too variable. TODO put in a pose instead
+
+ npc.Freeze()
+}
+
+void function Gauntlet_NPC_Damaged( entity npc, var damageInfo )
+{
+ printt( "NPC Damaged!", npc.GetHealth() )
+
+ float dmg = DamageInfo_GetDamage( damageInfo )
+ float finalHealth = npc.GetHealth() - dmg
+
+ if ( finalHealth <= 0 )
+ Gauntlet_UnfreezeNPC( npc )
+}
+
+void function Gauntlet_NPC_DeathWait( entity npc, GauntletInfo gauntlet )
+{
+ gauntlet.signalEnt.EndSignal( "DisableGauntlet" )
+
+ npc.WaitSignal( "OnDeath" )
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, npc.GetOrigin(), "holopilot_impacts_training" )
+ npc.Dissolve( ENTITY_DISSOLVE_PHASESHIFT, Vector( 0, 0, 0 ), GAUNTLET_TARGET_DISSOLVE_TIME_MS )
+
+ if ( !gauntlet.isActive )
+ return
+
+ if ( gauntlet.runFinished )
+ return
+
+ gauntlet.enemiesKilled++
+
+ Remote_CallFunction_Replay( gauntlet.player, "ScriptCallback_Gauntlet_SetEnemyInfo", gauntlet.id, gauntlet.spawners.len(), gauntlet.enemiesKilled )
+}
+
+void function Gauntlet_UnfreezeNPC( entity npc )
+{
+ if ( !npc.IsFrozen() )
+ return
+
+ npc.Unfreeze()
+}
+
+string function EnableAllGauntlets()
+{
+ foreach ( idx, gauntlet in GetGauntlets() )
+ EnableGauntlet( gauntlet )
+
+ return( "All gauntlets enabled" )
+}
+
+string function DisableAllGauntlets()
+{
+ foreach ( idx, gauntlet in GetGauntlets() )
+ DisableGauntlet( gauntlet )
+
+ return( "All gauntlets disabled" )
+}
+
+void function Gauntlet_Think( GauntletInfo gauntlet )
+{
+ gauntlet.signalEnt.EndSignal( "DisableGauntlet" )
+
+ OnThreadEnd(
+ function() : ( gauntlet )
+ {
+ Gauntlet_ResetTrackerStats( gauntlet )
+
+ if ( gauntlet.player && !gauntlet.runFinished )
+ Gauntlet_AbortRun( gauntlet )
+ }
+ )
+
+ while ( 1 )
+ {
+ thread Gauntlet_StartTrigThink( gauntlet )
+
+ waitthread Gauntlet_WaitForPlayerToStart( gauntlet )
+
+ Gauntlet_ResetTrackerStats( gauntlet )
+
+ Gauntlet_StartRun( gauntlet )
+
+ thread Gauntlet_HandlePlayerForceRestart( gauntlet )
+
+ waitthread Gauntlet_WaitForStop( gauntlet )
+
+ waitthread Gauntlet_StopRun( gauntlet )
+ }
+}
+
+void function Gauntlet_StartRun( GauntletInfo gauntlet )
+{
+ printt( "Gauntlet Run Started for player " + gauntlet.player )
+
+ RestockPlayerAmmo( gauntlet.player )
+ EmitSoundOnEntityOnlyToPlayer( gauntlet.player, gauntlet.player, "training_scr_gaunlet_start" )
+
+ gauntlet.isActive = true
+ gauntlet.startTime = Time()
+ gauntlet.player.Signal( "Gauntlet_RunStarted" )
+ gauntlet.signalEnt.Signal( "Gauntlet_RunStarted" )
+ level.ui.playerRunningGauntlet = true
+
+ Remote_CallFunction_Replay( gauntlet.player, "ScriptCallback_Gauntlet_StartRun", gauntlet.id )
+ Remote_CallFunction_Replay( gauntlet.player, "ScriptCallback_Gauntlet_SetEnemyInfo", gauntlet.id, gauntlet.spawners.len(), 0 )
+
+ thread Gauntlet_SpawnNPCs( gauntlet )
+
+ thread Gauntlet_Checkpoints( gauntlet )
+}
+
+void function Gauntlet_StopRun( GauntletInfo gauntlet )
+{
+ gauntlet.isActive = false
+ level.ui.playerRunningGauntlet = false
+
+ string feedbackSound = ""
+
+ ResetPlayerHealthAndStatus( gauntlet.player )
+
+ if ( !gauntlet.runFinished )
+ {
+ Gauntlet_AbortRun( gauntlet )
+ feedbackSound = "training_scr_gaunlet_abort"
+ }
+ else
+ {
+ Gauntlet_FinishRun( gauntlet )
+
+ if ( gauntlet.lastRunDefeatedGhost )
+ feedbackSound = "training_scr_gaunlet_high_score"
+ else if ( gauntlet.lastRunBestTime )
+ feedbackSound = "training_scr_gaunlet_high_score"
+ else
+ feedbackSound = "training_scr_gaunlet_end"
+
+ if ( feedbackSound != "" && IsAlive( gauntlet.player ) )
+ EmitSoundOnEntityOnlyToPlayer( gauntlet.player, gauntlet.player, feedbackSound )
+ }
+
+ wait 0.1 // let the gauntlet finish and count NPCs remaining before killing the remainder
+ Gauntlet_ClearSpawnedNPCs( gauntlet )
+ thread ClearDroppedWeapons( GAUNTLET_TARGET_DISSOLVE_TIME + 0.1 ) // needs to be longer than gauntlet ghost dissolve time so weapons drop
+
+ if ( IsValid( gauntlet.player ) )
+ ClearActiveProjectilesForTeam( gauntlet.player.GetTeam() )
+
+ // need to wait before firing final signal, so this signal doesn't kill Gauntlet_HandlePlayerForceRestart
+ if ( IsValid( gauntlet.player ) )
+ gauntlet.player.Signal( "Gauntlet_RunStopped" )
+
+ if ( IsValid( gauntlet.signalEnt ) )
+ gauntlet.signalEnt.Signal( "Gauntlet_RunStopped" )
+
+ wait 0.1 // let other threads catch the signals and check the gauntlet struct before ResetTrackerStats
+}
+
+void function ResetPlayerHealthAndStatus( entity player )
+{
+ if ( !IsAlive( player ) )
+ return
+
+ player.SetHealth( player.GetMaxHealth() )
+
+ array<int> statusEffectsToStop = []
+ statusEffectsToStop.append( eStatusEffect.emp )
+ statusEffectsToStop.append( eStatusEffect.move_slow )
+ statusEffectsToStop.append( eStatusEffect.turn_slow )
+
+ foreach ( statusEffect in statusEffectsToStop )
+ {
+ if ( StatusEffect_Get( player, statusEffect ) > 0.0 )
+ StatusEffect_StopAll( player, statusEffect ) // arc grenade stun
+ }
+}
+
+void function Gauntlet_FinishRun( GauntletInfo gauntlet )
+{
+ RestockPlayerAmmo( gauntlet.player )
+
+ float elapsedTime = Time() - gauntlet.startTime
+ printt( "Gauntlet Run Finished, elapsed time", elapsedTime )
+
+ // time penalties for missed enemies
+ float enemiesMissedTimePenalty = 0.0
+ if ( gauntlet.spawners.len() > gauntlet.enemiesKilled )
+ {
+ int numEnemiesRemaining = gauntlet.spawners.len() - gauntlet.enemiesKilled
+ enemiesMissedTimePenalty = ( numEnemiesRemaining.tofloat() * GAUNTLET_ENEMY_MISSED_TIME_PENALTY )
+
+ elapsedTime += enemiesMissedTimePenalty
+ }
+
+ // check if new best time was set
+ gauntlet.lastRunTime = elapsedTime
+ if ( gauntlet.bestTime == -1.0 || elapsedTime < gauntlet.bestTime )
+ {
+ printt( "New best time!" )
+ gauntlet.bestTime = elapsedTime
+ gauntlet.lastRunBestTime = true
+
+ // if there's a player ghost (for leaderboard), update its duration
+ if ( gauntlet.hasPlayerGhost )
+ {
+ // update player ghost
+ GauntletGhost playerGhost = Gauntlet_GetPlayerGhost( gauntlet )
+ Gauntlet_SetGhostDuration( gauntlet, playerGhost, gauntlet.bestTime )
+ }
+ }
+
+ // did player beat a ghost racer?
+ if ( Gauntlet_HasActiveGhost( gauntlet ) )
+ {
+ GauntletGhost activeGhost = Gauntlet_GetActiveGhost( gauntlet )
+
+ if ( gauntlet.lastRunTime < activeGhost.duration )
+ {
+ printt( "player beat active ghost!" )
+ gauntlet.lastRunDefeatedGhost = true
+ }
+ }
+
+ Remote_CallFunction_Replay( gauntlet.player, "ScriptCallback_Gauntlet_FinishRun", gauntlet.id, elapsedTime, gauntlet.bestTime, enemiesMissedTimePenalty )
+}
+
+void function Gauntlet_AbortRun( GauntletInfo gauntlet )
+{
+ entity player = gauntlet.player
+ if ( !IsValid( player ) )
+ return
+
+ RestockPlayerAmmo_Silent( gauntlet.player )
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "training_scr_gaunlet_abort" )
+
+ Remote_CallFunction_Replay( player, "ScriptCallback_Gauntlet_AbortRun", gauntlet.id )
+}
+
+void function Gauntlet_WaitForPlayerToStart( GauntletInfo gauntlet )
+{
+ WaitSignal( gauntlet.signalEnt, "Gauntlet_PlayerHitStartTrig" )
+ Assert( IsValid( gauntlet.player ) )
+}
+
+entity function Gauntlet_StartTrigThink( GauntletInfo gauntlet )
+{
+ entity trigStart = gauntlet.trigStart
+ entity trigStart2 = gauntlet.trigStart2
+
+ EndSignal( gauntlet.signalEnt, "OnDestroy" )
+ EndSignal( trigStart, "OnDestroy" )
+ EndSignal( trigStart2, "OnDestroy" )
+ EndSignal( gauntlet.signalEnt, "Gauntlet_RunStopped" )
+
+ table result
+ string signal
+ entity player
+
+ //printt( "WaitForPlayerToHitStartTrig started" )
+
+ // "trigStart_OnStartTouch", "trigStart_OnEndTouch", "trigStart2_OnStartTouch", "trigStart2_OnEndTouch"
+ thread Gauntlet_PlayerStartSignals( gauntlet, trigStart, "trigStart_" )
+ thread Gauntlet_PlayerStartSignals( gauntlet, trigStart2, "trigStart2_" )
+
+ while ( 1 )
+ {
+ entity alreadyTouchingEnt = null
+ foreach ( p in GetPlayerArray() )
+ {
+ if ( trigStart.IsTouching( p ) && Gauntlet_EntCanActivateGauntletTrigger( p ) )
+ {
+ alreadyTouchingEnt = p
+ break
+ }
+ }
+
+ if ( IsValid( alreadyTouchingEnt ) )
+ {
+ player = alreadyTouchingEnt
+ }
+ else
+ {
+ //printt( "Waiting for trigStart OnStartTouch" )
+
+ result = WaitSignal( trigStart, "OnStartTouch" )
+ player = expect entity( result.activator )
+ }
+
+ if ( !Gauntlet_EntCanActivateGauntletTrigger( player ) )
+ continue
+
+ if ( !IsAlive( player ) )
+ continue
+
+ //printt( "WAITING for trigStart_OnEndTouch" )
+
+ while ( IsAlive( player ) )
+ {
+ WaitSignal( player, "trigStart_OnEndTouch" )
+
+ //printt( "RECEIVED trigStart_OnEndTouch" )
+
+ // player exited start trig without running gauntlet
+ if ( !trigStart2.IsTouching( player ) )
+ {
+ player.Signal( "Gauntlet_PlayerWentBackwardsThroughStartTrig" )
+ continue
+ }
+
+ //printt( "WAITING for trigStart_OnStartTouch or trigStart2_OnEndTouch" )
+
+ // player is now in trig2
+ result = WaitSignal( player, "trigStart_OnStartTouch", "trigStart2_OnEndTouch" )
+ signal = expect string( result.signal )
+ if ( signal == "trigStart2_OnEndTouch" )
+ {
+ //printt( "RECEIVED trigStart2_OnEndTouch" )
+
+ // player exited trig2 without touching trig1, so we know they started the gauntlet
+ if ( !trigStart.IsTouching( player ) )
+ {
+ //printt( "SENDING Gauntlet_PlayerHitStartTrig" )
+ gauntlet.signalEnt.Signal( "Gauntlet_PlayerHitStartTrig" )
+ gauntlet.player = player
+ }
+ }
+ }
+ }
+}
+
+void function Gauntlet_PlayerStartSignals( GauntletInfo gauntlet, entity trig, string signalPrefix )
+{
+ EndSignal( trig, "OnDestroy" )
+ EndSignal( gauntlet.signalEnt, "Gauntlet_RunStopped" )
+
+ /*
+ OnThreadEnd(
+ function() : ( )
+ {
+ printt( "Gauntlet_PlayerStartSignals ENDED" )
+ }
+ )
+
+ printt( "PlayerStartSignals started" )
+ */
+
+ while ( 1 )
+ {
+ table result = WaitSignal( trig, "OnStartTouch", "OnEndTouch" )
+ string signal = expect string( result.signal )
+ entity activator = expect entity( result.activator )
+
+ if ( !Gauntlet_EntCanActivateGauntletTrigger( activator ) )
+ continue
+
+ string outboundSignal = signalPrefix
+ if ( signal == "OnStartTouch" )
+ outboundSignal += "OnStartTouch"
+ else if ( signal == "OnEndTouch" )
+ outboundSignal += "OnEndTouch"
+
+ Assert( outboundSignal != signalPrefix )
+
+ Signal( activator, outboundSignal )
+ }
+}
+
+void function Gauntlet_WaitForStop( GauntletInfo gauntlet )
+{
+ gauntlet.player.EndSignal( "OnDeath" )
+ gauntlet.player.EndSignal( "Gauntlet_PlayerWentBackwardsThroughStartTrig" )
+ gauntlet.player.EndSignal( "Gauntlet_ForceRestart" )
+ gauntlet.signalEnt.EndSignal( "DisableGauntlet" )
+
+ table result
+ entity activator
+
+ while ( 1 )
+ {
+ result = gauntlet.trigFinish.WaitSignal( "OnStartTouch" )
+ activator = expect entity( result.activator )
+
+ if ( !activator.IsPlayer() )
+ continue
+
+ if ( activator != gauntlet.player )
+ continue
+
+ gauntlet.player.Signal( "Gauntlet_PlayerHitFinishTrig" )
+
+ gauntlet.runFinished = true
+ if ( gauntlet.checkpoints.len() && gauntlet.checkpointsHit < gauntlet.checkpoints.len() )
+ gauntlet.runFinished = false
+
+ break
+ }
+}
+
+bool function Gauntlet_EntCanActivateGauntletTrigger( entity ent )
+{
+ if ( !ent.IsPlayer() )
+ return false
+
+ if ( !IsAlive( ent ) )
+ return false
+
+ if ( ent.IsTitan() )
+ return false
+
+ return true
+}
+
+void function Gauntlet_HandlePlayerForceRestart( GauntletInfo gauntlet )
+{
+ gauntlet.player.EndSignal( "OnDestroy" )
+ gauntlet.player.EndSignal( "Gauntlet_RunStopped" )
+
+ gauntlet.player.WaitSignal( "Gauntlet_ForceRestart" )
+
+ thread Gauntlet_TeleportPlayerToStart( gauntlet )
+}
+
+void function Gauntlet_TeleportPlayerToStart( GauntletInfo gauntlet )
+{
+ entity player = gauntlet.player
+ entity startpoint = gauntlet.startpoint
+
+ if ( !IsAlive( player ) )
+ return
+
+ if ( !IsValid( startpoint ) )
+ return
+
+ EndSignal( player, "OnDestroy" )
+
+ // wait for quick death to finish before continuing
+ printt( "player doing quick death (1)?", player.p.doingQuickDeath )
+ while ( player.p.doingQuickDeath )
+ wait 0.1
+
+ //printt( "starting reset fade" )
+
+ float fadeTime = 0.1
+ float holdTime = 0.3
+ ScreenFadeToBlack( player, fadeTime, holdTime )
+ player.FreezeControlsOnServer()
+ player.SetVelocity( <0,0,0> )
+
+ OnThreadEnd(
+ function() : ( player, gauntlet )
+ {
+ if ( IsValid( player ) )
+ {
+ player.UnfreezeControlsOnServer()
+ player.UnforceStand()
+ thread Gauntlet_TeleportFailsafe( player, gauntlet )
+ }
+ }
+ )
+
+ wait fadeTime
+
+ // again, wait for quick death to finish before continuing since it could have started during fadeTime
+ printt( "player doing quick death (2)?", player.p.doingQuickDeath )
+ while ( player.p.doingQuickDeath )
+ wait 0.1
+
+ printt( "moving player back to start" )
+
+ player.FreezeControlsOnServer() // just in case they were unfrozen by quick death ending since we started waiting
+ player.SetOrigin( OriginToGround( startpoint.GetOrigin() + <0,0,1> ) )
+ player.SetAngles( startpoint.GetAngles() )
+ player.SetVelocity( <0,0,0> )
+ player.ForceStand()
+
+ wait holdTime
+}
+
+// HACK this is in case the quick death teleport happens on the exact same server frame as the gauntlet restart teleport
+void function Gauntlet_TeleportFailsafe( entity player, GauntletInfo gauntlet )
+{
+ // HACK this breaks in other levels that don't have the flag trigger
+ // in the future set this up as a gauntlet setting
+ if ( GetMapName() != "sp_training" )
+ return
+
+ EndSignal( player, "OnDestroy" )
+
+ wait 0.5
+
+ if ( !gauntlet.isActive && !Flag( "PlayerInGauntletEntryway" ) )
+ {
+ printt( "Gauntlet reset FAILSAFE!" )
+ thread Gauntlet_TeleportPlayerToStart( gauntlet )
+ }
+}
+
+bool function ClientCommand_Gauntlet_PlayerRestartedFromMenu( entity player, array<string> args )
+{
+ player.Signal( "Gauntlet_ForceRestart" )
+ return true
+}
+
+void function Gauntlet_CreateSignalEnt( GauntletInfo gauntlet )
+{
+ Assert( !IsValid( gauntlet.signalEnt ) )
+
+ entity signalEnt = CreateEntity( "info_target" )
+ DispatchSpawn( signalEnt )
+
+ gauntlet.signalEnt = signalEnt
+}
+
+void function Gauntlet_CleanupSignalEnt( GauntletInfo gauntlet )
+{
+ gauntlet.signalEnt.Destroy()
+ gauntlet.signalEnt = null
+}
+
+
+void function Gauntlet_ResetTrackerStats( GauntletInfo gauntlet )
+{
+ gauntlet.startTime = -1
+ gauntlet.runFinished = false
+ gauntlet.lastRunBestTime = false
+ gauntlet.lastRunDefeatedGhost = false
+ gauntlet.enemiesKilled = 0
+}
+
+
+// ===== GHOST RECORDINGS =====
+void function Gauntlet_StartGhostPlayback( GauntletInfo gauntlet, string ghostFileName, string ghostDisplayName = "" )//, bool waitForPlayerToStartFirstRun = true )
+{
+ gauntlet.signalEnt.Signal( "GhostAnimationPlayback_Start" )
+ gauntlet.signalEnt.EndSignal( "GhostAnimationPlayback_Start" )
+ gauntlet.signalEnt.EndSignal( "GhostAnimationPlayback_Stop" )
+ gauntlet.signalEnt.EndSignal( "DisableGauntlet" )
+
+ GauntletGhost ghostInfo = Gauntlet_GetGhostByFileName( gauntlet, ghostFileName )
+ var rec = LoadRecordedAnimation( ghostInfo.fileAsset )
+ float duration = GetRecordedAnimationDuration( rec )
+ printt( "duration is", duration )
+
+ Gauntlet_SetActiveGhostID( gauntlet, ghostInfo.id )
+
+ entity animRef = gauntlet.startpoint
+
+ bool createdIdleRef = false
+ entity idleRef
+ if ( gauntlet.ghostAttractSpot != null )
+ {
+ idleRef = gauntlet.ghostAttractSpot
+ }
+ else
+ {
+ createdIdleRef = true
+ idleRef = CreateScriptMover( animRef.GetOrigin(), animRef.GetAngles() )
+ DropToGround( idleRef )
+ }
+
+ table<int,entity> g = {}
+
+ OnThreadEnd(
+ function() : ( g, idleRef, createdIdleRef, gauntlet )
+ {
+ if ( IsValid( g[0] ) )
+ {
+ g[0].Anim_Stop()
+ StopSoundOnEntity( g[0], "PathHologram_Sustain_Loop_3P" )
+ DissolveGhost( g[0] )
+ }
+
+ if ( createdIdleRef && IsValid( idleRef ) )
+ idleRef.Destroy()
+
+ Gauntlet_ClearActiveGhost( gauntlet )
+ }
+ )
+
+ entity ghost
+ entity ghostWeapon
+
+ bool isFirstRun = true
+
+ while ( 1 )
+ {
+ if ( IsValid( ghost ) )
+ {
+ StopSoundOnEntity( ghost, "PathHologram_Sustain_Loop_3P" )
+ DissolveGhost( ghost )
+ }
+
+ ghost = CreateGhost( idleRef.GetOrigin(), ghostDisplayName )
+ g[0] <- ghost
+ //ghost.SetTitle( "Ghost Runner" )
+ //ShowName( ghost ) // not working
+
+ ghostWeapon = Ghost_GetWeaponEnt( ghost )
+ ghostWeapon.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY
+
+ thread PlayAnimTeleport( ghost, "pt_OG_training_stand", idleRef )
+
+ if ( !gauntlet.isActive )
+ gauntlet.signalEnt.WaitSignal( "Gauntlet_RunStarted" )
+
+ float startTime = Time()
+
+ ghostWeapon.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+
+ EmitSoundOnEntity( ghost, "PathHologram_Sustain_Loop_3P" )
+
+ ghost.Anim_Stop()
+ ghost.PlayRecordedAnimation( rec, <0,0,0>, <0,0,0>, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, animRef )
+
+ thread GhostPlayback_HideGhostIfPlayerIsNear( ghost, ghostWeapon )
+
+ float ghostFadeTime = 1.2
+ float waitBeforeFade = duration - ghostFadeTime
+ WaitSignalTimeout( gauntlet.signalEnt, waitBeforeFade, "Gauntlet_RunStopped" )
+
+ // ended prematurely
+ if ( Time() - startTime < waitBeforeFade )
+ ghost.Anim_Stop()
+
+ isFirstRun = false
+ }
+}
+
+void function GhostPlayback_HideGhostIfPlayerIsNear( entity ghost, entity ghostWeapon )
+{
+ EndSignal( ghost, "OnDestroy" )
+ EndSignal( ghostWeapon, "OnDestroy" )
+
+ const float TICK_WAIT = 0.1
+
+ while ( 1 )
+ {
+ wait TICK_WAIT
+
+ entity nearbyPlayer
+
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ if ( !IsAlive( player ) )
+ continue
+
+ if ( PlayerTooCloseToGhost( player, ghost ) )
+ {
+ nearbyPlayer = player
+ break
+ }
+ }
+
+ if ( IsValid( nearbyPlayer ) )
+ {
+ ghost.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY
+ ghostWeapon.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY
+
+ while ( PlayerTooCloseToGhost( nearbyPlayer, ghost ) )
+ wait TICK_WAIT
+
+ ghost.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ ghostWeapon.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ }
+
+ }
+}
+
+bool function PlayerTooCloseToGhost( entity player, entity ghost )
+{
+ if ( !IsAlive( player ) )
+ return false
+
+ const float CLOSE_DIST = 64.0
+
+ if ( Distance( player.EyePosition(), ghost.GetOrigin() ) <= CLOSE_DIST )
+ return true
+
+ if ( Distance( player.EyePosition(), ghost.EyePosition() ) <= CLOSE_DIST )
+ return true
+
+ return false
+}
+
+void function Gauntlet_StopGhostPlayback( GauntletInfo gauntlet )
+{
+ gauntlet.signalEnt.Signal( "GhostAnimationPlayback_Stop" )
+}
+
+
+// - Player climbs the leaderboard as her best run time improves
+// - Skips challenging ghosts whose times are worse than the player's
+void function Gauntlet_ChallengeLeaderboardGhosts( entity player, GauntletInfo gauntlet, string endFlag )
+{
+ if ( Flag( endFlag ) )
+ return
+
+ FlagEnd( endFlag )
+
+ player.EndSignal( "OnDestroy" )
+ gauntlet.signalEnt.EndSignal( "OnDestroy" )
+
+ GauntletGhost playerGhost = Gauntlet_GetPlayerGhost( gauntlet )
+
+ int currPlayerIdx = GAUNTLET_LEADERBOARD_MAX_ENTRIES - 1
+ int nextGhostIdx = currPlayerIdx - 1
+
+ while ( currPlayerIdx > 0 )
+ {
+ array<GauntletGhost> leaderboard = Gauntlet_GetLeaderboard( gauntlet )
+
+ // get current player leaderboard position
+ int maxLeaderboardIdx = leaderboard.len() - 1
+ if ( currPlayerIdx >= maxLeaderboardIdx )
+ currPlayerIdx = maxLeaderboardIdx
+
+ foreach ( idx, leaderboardGhost in leaderboard )
+ {
+ if ( leaderboardGhost.fileName == playerGhost.fileName )
+ currPlayerIdx = idx
+ }
+
+ // player is top of the leaderboard, stop racing ghosts
+ if ( currPlayerIdx <= 0 )
+ break
+
+ // if player is not top of leaderboard, cue the ghost above player leaderboard position
+ int nextGhostIdx = currPlayerIdx - 1
+ GauntletGhost ghost = leaderboard[ nextGhostIdx ]
+
+ Assert( ghost.fileName != GHOST_NAME_PLAYER, "Can't race against own player ghost- no anim recording asset" )
+
+ thread Gauntlet_StartGhostPlayback( gauntlet, ghost.fileName, ghost.displayName )
+
+ if ( !gauntlet.isActive )
+ WaitSignal( player, "Gauntlet_RunStarted" )
+
+ // wait for run to stop
+ WaitSignal( player, "Gauntlet_RunStopped" )
+ }
+
+ Gauntlet_ClearActiveGhost( gauntlet )
+
+ gauntlet.allGhostsDefeated = true
+}
+
+#if DEV
+void function Gauntlet_Player_GhostRecordOrPlayback( entity player, GauntletInfo gauntlet, string ghostFileName )
+{
+ if ( GetBugReproNum() == 55 )
+ {
+ thread Gauntlet_Player_StartRecordingGhost( player, gauntlet, ghostFileName )
+ }
+ else
+ {
+ thread Gauntlet_StartGhostPlayback( gauntlet, ghostFileName )
+
+ GauntletGhost ghost = Gauntlet_GetGhostByFileName( gauntlet, ghostFileName )
+ Dev_PrintMessage( player, "Ghost Playback:", "TO RECORD, set bug_reproNum 55", 4.0 )
+ wait 4.0
+ Dev_PrintMessage( player, ghost.displayName, "TO RECORD, set bug_reproNum 55", 4.0 )
+ }
+}
+
+void function Gauntlet_Player_StartRecordingGhost( entity player, GauntletInfo gauntlet, string ghostFileName )
+{
+ player.Signal( "RecordAnimation_Start" )
+ player.EndSignal( "RecordAnimation_Start" )
+ player.EndSignal( "OnDestroy" )
+
+ entity animRef = gauntlet.startpoint
+ GauntletGhost ghost = Gauntlet_GetGhostByFileName( gauntlet, ghostFileName )
+
+ thread Gauntlet_StartGhostPlayback( gauntlet, ghostFileName )
+
+ while ( 1 )
+ {
+ #if PC_PROG
+ thread Gauntlet_Player_StartRecordingGhost_Hints( player, gauntlet, ghost )
+ #endif
+
+ printt( "READY TO RECORD:", ghost.fileName )
+
+ player.WaitSignal( "Gauntlet_RunStarted" )
+
+ player.StartRecordingAnimation( animRef.GetOrigin(), animRef.GetAngles() )
+ printt( "RECORDING STARTED:", ghost.fileName )
+
+ player.WaitSignal( "Gauntlet_RunStopped" )
+
+ var recording = player.StopRecordingAnimation()
+
+ if ( !gauntlet.runFinished )
+ continue
+
+ if ( gauntlet.enemiesKilled < gauntlet.spawners.len() )
+ {
+ Dev_PrintMessage( player, "RECORDING NOT SAVED!", "Must kill all the enemies on your run to save.", 7.0 )
+ printt( "!!!! RECORDED ANIM NOT SAVED!!!!" )
+ continue
+ }
+
+ #if PC_PROG
+ SaveRecordedAnimation( recording, ghost.fileName )
+ Dev_PrintMessage( player, "Anim Data Saved", "BAKE and CLEAR BUG REPRO NUM and RELOAD LEVEL to play it back.", 5.5 )
+ printt( "RECORDED ANIM SAVED:", ghost.fileName )
+
+ wait 5.5
+
+ thread Gauntlet_StartGhostPlayback( gauntlet, ghostFileName )
+ #endif
+ }
+}
+
+void function Gauntlet_Player_StartRecordingGhost_Hints( entity player, GauntletInfo gauntlet, GauntletGhost ghost )
+{
+ player.Signal( "Player_StartRecordingGhost_HintStart" )
+ player.EndSignal( "Player_StartRecordingGhost_HintStart" )
+ player.EndSignal( "OnDestroy" )
+
+ Dev_PrintMessage( player, "Ready To Record Ghost:", "FINISH Gauntlet and kill ALL TARGETS to SAVE GHOST.", 3.0 )
+ wait 3.0
+ Dev_PrintMessage( player, ghost.displayName, "FINISH Gauntlet and kill ALL TARGETS to SAVE GHOST.", 5.0 )
+}
+#endif //DEV \ No newline at end of file
diff --git a/Northstar.Coop/scripts/vscripts/sp/_savegame.gnut b/Northstar.Coop/scripts/vscripts/sp/_savegame.gnut
new file mode 100644
index 000000000..f4f290035
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/_savegame.gnut
@@ -0,0 +1,860 @@
+global function AddCallback_OnLoadSaveGame
+global function CheckPoint
+global function CheckPoint_Forced
+global function CheckPoint_ForcedSilent
+global function CheckPoint_Silent
+global function RestartFromLevelTransition
+global function CodeCallback_IsSaveGameSafeToCommit
+global function CodeCallback_OnLoadSaveGame
+global function CodeCallback_OnSavedSaveGame
+global function InitSaveGame
+global function LoadSaveTimeDamageMultiplier
+global function ReloadForMissionFailure
+global function GetLastCheckPointLoadTime
+global function SafeForCheckPoint
+global function SafeToSpawnAtOrigin
+global function JustLoadedFromCheckpoint
+global function GetFirstPlayer
+
+const float LAST_SAVE_DEBOUNCE = 4.0
+const float TITAN_SAFE_DIST = 1200
+const SAVE_DEBUG = true
+global const SAVE_NEW = false
+global const MAX_SAFELOCATION_DIST = 5000
+global const float SAVE_SPAWN_VOFFSET = 2.0
+
+global struct CheckPointData
+{
+ float searchTime = 5.0
+ bool onGroundRequired = true
+ array<entity> safeLocations
+ bool forced
+ bool skipDelayedIsAliveCheck
+ bool skipSaveToActualPlayerLocation
+ bool functionref( entity ) ornull additionalCheck = null
+}
+
+struct
+{
+ bool nextSaveSilent
+ table signalDummy
+ float loadSaveTime
+
+ float lastSaveTime
+
+ array<StoredWeapon> saveStoredWeapons
+ string storedTitanWeapon
+
+ vector saveOrigin
+ vector saveAngles
+ bool saveRestoreResetsPlayer
+
+} file
+
+void function InitSaveGame()
+{
+ Assert( MAX_SAFELOCATION_DIST < TIME_ZOFFSET * 0.8 )
+
+ RegisterSignal( "StopSearchingForSafeSave" )
+ RegisterSignal( "RespawnNow" )
+
+ FlagInit( "OnLoadSaveGame_PlayerRecoveryEnabled", true )
+ FlagInit( "SaveRequires_PlayerIsTitan" )
+ AddClientCommandCallback( "RestartFromLevelTransition", RestartFromLevelTransition )
+ AddClientCommandCallback( "RespawnNowSP", RespawnNowSP )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// GLOBAL COMMANDS
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void function CheckPoint_Silent( CheckPointData ornull checkPointData = null )
+{
+ CheckPointData data = GetCheckPointDataOrDefaults( checkPointData )
+ CheckPoint_Silent_FromData( data )
+}
+
+void function CheckPoint_Silent_FromData( CheckPointData checkPointData )
+{
+ file.nextSaveSilent = true
+ CheckPoint( checkPointData )
+}
+
+void function CheckPoint_ForcedSilent()
+{
+ file.nextSaveSilent = true
+ CheckPoint_Forced()
+}
+
+void function CheckPoint_Forced()
+{
+ CheckPointData checkPointData
+ checkPointData.forced = true
+ CheckPoint_FromData( checkPointData )
+}
+
+void function CheckPoint( CheckPointData ornull checkPointData = null )
+{
+ CheckPointData data = GetCheckPointDataOrDefaults( checkPointData )
+ thread CheckPoint_FromData( data )
+}
+
+void function CheckPoint_FromData( CheckPointData checkPointData )
+{
+ printt( "SAVEGAME: New save attempt (8/3/2016)" )
+
+ float searchTime = checkPointData.searchTime
+ bool onGroundRequired = checkPointData.onGroundRequired
+ bool skipDelayedIsAliveCheck = checkPointData.skipDelayedIsAliveCheck
+ bool skipSaveToActualPlayerLocation = checkPointData.skipSaveToActualPlayerLocation
+ bool forced = checkPointData.forced
+ array<entity> safeLocations = checkPointData.safeLocations
+ bool functionref( entity ) ornull additionalCheck = checkPointData.additionalCheck
+
+ if ( !Flag( "SaveGame_Enabled" ) )
+ {
+ printt( "SAVEGAME: failed: Saves are disabled" )
+ return
+ }
+
+ entity player = GetFirstPlayer()
+ entity safeLocation
+
+ if ( forced )
+ {
+ printt( "SAVEGAME: creating: Forced" )
+ }
+ else
+ {
+ if ( Flag( "SaveRequires_PlayerIsTitan" ) && !player.IsTitan() )
+ {
+ printt( "SAVEGAME: failed: Flag SaveRequires_PlayerIsTitan and player is not a Titan" )
+ return
+ }
+
+ printt( "SAVEGAME: creating: Safe for check point?" )
+ EndSignal( file.signalDummy, "StopSearchingForSafeSave" )
+
+ bool regularSafetyCheck = GetBugReproNum() != 184641 // respawned with no UI
+ bool functionref( entity ) safeCheck
+ if ( onGroundRequired )
+ safeCheck = SafeForCheckPoint_OnGround
+ else
+ safeCheck = SafeForCheckPoint
+
+
+ if ( skipSaveToActualPlayerLocation )
+ {
+ printt( "SAVEGAME: skipSaveToActualPlayerLocation." )
+ }
+ else
+ {
+ float rate = 0.333
+ int reps = int( searchTime / rate )
+
+ if ( regularSafetyCheck )
+ {
+ // wait a few seconds until it is safe to save
+ for ( int i = 0; i < reps; i++ )
+ {
+ if ( safeCheck( player ) )
+ break
+
+ wait rate
+ }
+ }
+ else
+ {
+ printt( "SAVEGAME: regularSafetyCheck disabled for bug repro" )
+ }
+ }
+
+ if ( !regularSafetyCheck || skipSaveToActualPlayerLocation || !safeCheck( player ) )
+ {
+ printt( "SAVEGAME: Not safe to save actual player position." )
+ if ( !safeLocations.len() )
+ {
+ printt( "SAVEGAME: failed: no alternate safe locations." )
+ return
+ }
+
+ // titan cores do funky stuff with weapons and make it hard to resume to a new position safely
+ if ( !IsTitanCoreFiring( player ) )
+ safeLocation = GetBestSaveLocationEnt( safeLocations )
+
+ if ( safeLocation == null )
+ {
+ printt( "SAVEGAME: failed: couldn't find a safe location from those provided." )
+ return
+ }
+
+ printt( "SAVEGAME: Found safe location " + safeLocation.GetOrigin() )
+ }
+ else
+ {
+ printt( "SAVEGAME: Safe to save at actual player location: " + player.GetOrigin() )
+ }
+
+ if ( Time() - file.lastSaveTime < 3 )
+ {
+ printt( "SAVEGAME: failed: last save was too recent." )
+ return
+ }
+
+ if ( additionalCheck != null )
+ {
+ expect bool functionref(entity)( additionalCheck )
+ if ( !additionalCheck( player ) )
+ {
+ printt( "SAVEGAME: failed: custom check failed." )
+ return
+ }
+ }
+ }
+
+ if ( IsValid( safeLocation ) )
+ {
+ printt( "SAVEGAME: saveRestoreResetsPlayer = true" )
+ WriteRestoreLocationFromEntity( safeLocation )
+ file.saveRestoreResetsPlayer = true
+ }
+ else
+ {
+ printt( "SAVEGAME: saveRestoreResetsPlayer = false" )
+ file.saveRestoreResetsPlayer = false
+ }
+
+ int startPointIndex = GetCurrentStartPointIndex()
+
+ if ( !IsAlive( player ) )
+ {
+ printt( "SAVEGAME: failed: Tried to save while player was dead" )
+ return
+ }
+
+ if ( skipDelayedIsAliveCheck || forced )
+ {
+ printt( "SAVEGAME: SaveGame_Create" )
+ file.lastSaveTime = Time()
+ SaveGame_Create( GetSaveName(), SAVEGAME_VERSION, startPointIndex )
+ }
+ else
+ {
+ printt( "SAVEGAME: SaveGame_CreateWithCommitDelay" )
+ file.lastSaveTime = Time()
+ SaveGame_CreateWithCommitDelay( GetSaveName(), SAVEGAME_VERSION, 3.5, 1, startPointIndex )
+ }
+
+ // kill any ongoing attempts to save. Note this also may end this thread, so it is called last.
+ Signal( file.signalDummy, "StopSearchingForSafeSave" )
+}
+
+CheckPointData function GetCheckPointDataOrDefaults( CheckPointData ornull checkPointData = null )
+{
+ if ( checkPointData != null )
+ return expect CheckPointData( checkPointData )
+
+ CheckPointData data
+ return data
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// CODE CALLBACKS
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool function CodeCallback_IsSaveGameSafeToCommit()
+{
+ if ( !Flag( "SaveGame_Enabled" ) )
+ return false
+
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( !IsAlive( player ) )
+ return false
+ }
+
+ return true
+}
+
+
+void function ClearPlayerVelocityOnContext( entity player )
+{
+ if ( player.IsTitan() )
+ return
+
+ switch ( GetCurrentStartPoint() )
+ {
+ case "Rising World Jump":
+ return
+ }
+
+ player.SetVelocity( <0,0,0> )
+}
+
+
+void function CodeCallback_OnLoadSaveGame()
+{
+ printt( "SaveGame: OnLoadSaveGame" )
+ file.loadSaveTime = Time()
+
+ UpdateCollectiblesAfterLoadSaveGame()
+ array<entity> players = GetPlayerArray()
+
+ Assert( players.len() == 1 )
+ entity player = players[0]
+
+ // restore health on load from save
+ if ( !IsAlive( player ) )
+ return
+
+ ClearPlayerVelocityOnContext( player )
+
+ thread UpdateUI( player.IsTitan(), player )
+
+ // run on load save game callbacks
+ foreach ( callbackFunc in svGlobalSP.onLoadSaveGameCallbacks )
+ {
+ callbackFunc( player )
+ }
+
+ thread DelayedOnLoadSetup( player )
+}
+
+void function DelayedOnLoadSetup( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ WaitFrame() // fixes crash bug, also can't issue a remote call on this codecallback until waiting a frame.
+
+ Remote_CallFunction_UI( player, "ServerCallback_GetObjectiveReminderOnLoad" ) // show objective reminder
+
+ bool forceStanding = file.saveRestoreResetsPlayer
+ if ( file.saveRestoreResetsPlayer )
+ {
+ file.saveRestoreResetsPlayer = false
+
+ printt( "SAVEGAME: Restoring player to safe location " + file.saveOrigin )
+ player.SetOrigin( file.saveOrigin )
+ player.SetAngles( file.saveAngles )
+ player.SetVelocity( <0,0,0> )
+
+ TakeAllWeapons( player )
+
+ player.ForceStand()
+
+ if ( IsPilot( player ) )
+ {
+ GiveWeaponsFromStoredArray( player, file.saveStoredWeapons )
+ }
+ else
+ {
+ player.GiveWeapon( file.storedTitanWeapon )
+ }
+ }
+
+ if ( Flag( "OnLoadSaveGame_PlayerRecoveryEnabled" ) )
+ {
+ if ( player.IsTitan() )
+ {
+ RestoreTitan( player )
+
+ int index = GetConVarInt( "sp_titanLoadoutCurrent" )
+ if ( index > -1 && IsBTLoadoutUnlocked( index ) )
+ {
+ string weapon = GetTitanWeaponFromIndex( index )
+ TakeAllWeapons( player )
+ player.GiveWeapon( weapon )
+ }
+ }
+ else
+ {
+ player.SetHealth( player.GetMaxHealth() )
+ entity offhand = player.GetOffhandWeapon( OFFHAND_SPECIAL )
+ if ( IsValid( offhand ) )
+ {
+ // restore offhand, so for example, cloak will be ready to use
+ int max = offhand.GetWeaponPrimaryClipCountMax()
+ offhand.SetWeaponPrimaryClipCount(max)
+ }
+
+ entity petTitan = player.GetPetTitan()
+ if ( IsAlive( petTitan ) )
+ {
+ RestoreTitan( petTitan )
+ }
+ }
+ }
+
+ WaitFrame()
+
+ if ( forceStanding )
+ player.UnforceStand()
+
+}
+
+void function CodeCallback_OnSavedSaveGame( bool saved )
+{
+ printt( "SAVEGAME: Success (OnSavedSaveGame)" )
+
+ // failed to save
+ if ( !saved )
+ return
+
+ // tell clients about the save
+ foreach ( player in GetPlayerArray() )
+ {
+ Remote_CallFunction_UI( player, "ServerCallback_ClearObjectiveReminderOnLoad" ) // dont need objective reminder again on load
+ }
+
+ BroadcastCheckpointMessage()
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// SAVE UTILITIES
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+entity function GetFirstPlayer()
+{
+ foreach ( player in GetPlayerArray() )
+ {
+ return player
+ }
+
+ unreachable
+}
+
+bool function SafeToSpawnAtOrigin( entity player, vector origin )
+{
+ if ( player.IsTitan() )
+ {
+ array<entity> titans = GetNPCArrayEx( "npc_titan", TEAM_ANY, TEAM_MILITIA, origin, TITAN_SAFE_DIST )
+ if ( titans.len() > 0 )
+ return false
+
+ array<entity> superSpectres = GetNPCArrayEx( "npc_super_spectre", TEAM_ANY, TEAM_MILITIA, origin, TITAN_SAFE_DIST )
+ if ( superSpectres.len() > 0 )
+ return false
+ }
+ else
+ {
+ array<entity> enemies = GetNPCArrayEx( "any", TEAM_ANY, TEAM_MILITIA, origin, 300 )
+ if ( enemies.len() > 0 )
+ return false
+ }
+
+ return IsPlayerSafeFromProjectiles( player, origin )
+}
+
+entity function GetBestSaveLocationEnt( array<entity> safeLocations )
+{
+ entity player = GetFirstPlayer()
+ vector mins = player.GetBoundingMins()
+ vector maxs = player.GetBoundingMaxs()
+
+ vector playerOrg = player.GetOrigin()
+ array<entity> orderedLocations = ArrayClosest( safeLocations, player.GetOrigin() )
+ foreach ( ent in orderedLocations )
+ {
+ vector org = ent.GetOrigin() + <0,0,SAVE_SPAWN_VOFFSET>
+
+ // too far to spawn
+ if ( Distance( playerOrg, org ) > MAX_SAFELOCATION_DIST )
+ break
+
+ if ( !SafeToSpawnAtOrigin( player, ent.GetOrigin() ) )
+ continue
+
+ // blocked at that origin?
+ TraceResults result = TraceHull( org, org + Vector( 0, 0, 1), mins, maxs, [ player ], TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER )
+ if ( result.startSolid )
+ continue
+ if ( result.fraction != 1.0 )
+ continue
+
+ return ent
+ }
+
+ return null
+}
+
+void function WriteRestoreLocationFromEntity( entity safeLocation )
+{
+ Assert( safeLocation != null )
+ entity player = GetFirstPlayer()
+ Assert( IsAlive( player ), "Tried to save while player was dead" )
+
+ if ( IsPilot( player ) )
+ {
+ file.saveStoredWeapons = StoreWeapons( player )
+ }
+ else
+ {
+ array<entity> weapons = player.GetMainWeapons()
+ Assert( weapons.len() == 1, "Player had multiple titan weapons" )
+ file.storedTitanWeapon = weapons[0].GetWeaponClassName()
+ }
+
+ file.saveOrigin = safeLocation.GetOrigin() + <0,0,SAVE_SPAWN_VOFFSET>
+ file.saveAngles = safeLocation.GetAngles()
+}
+
+bool function RestartFromLevelTransition( entity player, array<string> args )
+{
+ thread ReloadFromLevelStart()
+
+ return true
+}
+
+void function ReloadForMissionFailure( float extraDelay = 0 )
+{
+ // prevent automatic restarts
+ //thread ReloadForMissionFailure_Thread( extraDelay )
+}
+
+void function ReloadForMissionFailure_Thread( float extraDelay )
+{
+ FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost
+ FlagSet( "MissionFailed" )
+
+ Signal( file.signalDummy, "StopSearchingForSafeSave" )
+
+ entity player = GetFirstPlayer()
+ if ( IsValid( player ) )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "4_second_fadeout" )
+
+ wait 1
+
+ waitthread WaitForRespawnNowOrTimePass( player, 2.8 + extraDelay )
+
+ ReloadFromSave_RestartFallback()
+}
+
+void function WaitForRespawnNowOrTimePass( entity player, float delay )
+{
+ player.EndSignal( "RespawnNow" )
+ wait delay
+}
+
+void function SkipReloadDelay_Thread( entity player )
+{
+ EmitSoundOnEntityOnlyToPlayer( player, player, "1_second_fadeout" )
+ ScreenFadeToBlackForever( player, 0.5 )
+ wait 1.0
+ player.Signal( "RespawnNow" )
+}
+
+bool function RespawnNowSP( entity player, array<string> args )
+{
+ // this clientcommand is always bound now so we need to make sure it isn't called while player is alive
+ if ( IsAlive( player ))
+ return true
+
+ // wait a few seconds before we allow respawns
+ if ( player.nv.nextRespawnTime > Time() )
+ return true
+
+ //thread SkipReloadDelay_Thread( player )
+
+ // do our respawning code
+ entity ornull respawningOn = GetPlayerToSpawnOn()
+
+ if ( respawningOn == null )
+ {
+ // todo: logic for restarting level without dropping clients
+ print( "everyone is dead! restarting from last checkpoint..." )
+ RestartWithoutDroppingPlayers()
+ }
+ else
+ {
+ expect entity( respawningOn )
+ // HACK: compiler has a fit if we directly call player.RespawnPlayer in this file
+ // so we need to make a functionref in another file that does it
+
+ hackRespawnPlayerFunc( player, respawningOn )
+ // honestly unsure if the respawningOn arg works with players so manually set pos too
+ thread TeleportToEntitySafe( player, respawningOn )
+ }
+
+
+ return true
+}
+
+void function ReloadFromSave_RestartFallback()
+{
+ if ( LoadedFromSave() )
+ return
+
+ ReloadFromLevelStart()
+}
+
+void function ReloadFromLevelStart()
+{
+ array<entity> players = GetPlayerArray()
+ Assert( players.len() == 1 )
+ entity player = players[0]
+ ServerRestartMission( player )
+}
+
+bool function SafeForCheckPoint_OnGround( entity player )
+{
+ Assert( player.IsPlayer() )
+
+ if ( !player.IsOnGround() )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: !player.IsOnGround()" )
+ #endif
+ return false
+ }
+
+ if ( player.IsWallRunning() )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: player.IsWallRunning()" )
+ #endif
+ return false
+ }
+
+ return SafeForCheckPoint( player )
+}
+
+bool function SafeForCheckPoint( entity player )
+{
+ Assert( player.IsPlayer() )
+
+ if ( Flag( "CheckPointDisabled" ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: Flag( \"CheckPointDisabled\" )" )
+ #endif
+ return false
+ }
+
+ if ( !IsAlive( player ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: !IsAlive( player )" )
+ #endif
+ return false
+ }
+
+ if ( IsPlayerEmbarking( player ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: IsPlayerEmbarking( player )" )
+ #endif
+ return false
+ }
+
+ if ( IsPlayerDisembarking( player ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: IsPlayerDisembarking( player )" )
+ #endif
+ return false
+ }
+
+ if ( player.p.doingQuickDeath )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: player.p.doingQuickDeath" )
+ #endif
+ return false
+ }
+
+ if ( EntityIsOutOfBounds( player ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: EntityIsOutOfBounds( player )" )
+ #endif
+ return false
+ }
+
+
+ float range = 300
+ if ( player.IsTitan() )
+ {
+ range = 1000
+
+ // awkward to resume into a core-in-progress
+ if ( IsTitanCoreFiring( player ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: IsTitanCoreFiring( player )" )
+ #endif
+ return false
+ }
+ }
+ else
+ {
+ entity weapon = player.GetActiveWeapon()
+ if ( IsValid( weapon ) )
+ {
+ // cooking grenade?
+ if ( player.GetOffhandWeapon( OFFHAND_ORDNANCE ) == weapon )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: Cooking grenade" )
+ #endif
+ return false
+ }
+ }
+
+ if ( player.IsPhaseShifted() )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: player.IsPhaseShifted()" )
+ #endif
+ return false
+ }
+ }
+
+ array<entity> enemies = GetNPCArrayEx( "any", TEAM_ANY, TEAM_MILITIA, player.GetOrigin(), range )
+ if ( enemies.len() > 0 )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: Enemy within " + range + " units" )
+ #endif
+ return false
+ }
+
+ if ( player.IsTitan() )
+ {
+
+ array<entity> titans = GetNPCArrayEx( "npc_titan", TEAM_ANY, TEAM_MILITIA, player.GetOrigin(), 3000 )
+ foreach ( titan in titans )
+ {
+ if ( titan.GetEnemy() == player )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: Enemy Titan within 3000 units" )
+ #endif
+ return false
+ }
+ }
+ }
+
+ if ( !IsPlayerSafeFromNPCs( player ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: !IsPlayerSafeFromNPCs( player )" )
+ #endif
+ return false
+ }
+
+ if ( !IsPlayerSafeFromProjectiles( player, player.GetOrigin() ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: !IsPlayerSafeFromProjectiles( player )" )
+ #endif
+ return false
+ }
+
+ if ( WasRecentlyHitByDamageSourceId( player, eDamageSourceId.toxic_sludge, 3.0 ) )
+ {
+ #if SAVE_DEBUG
+ printt( "SaveGame: Failed: WasRecentlyHitByDamageSourceId( player, eDamageSourceId.toxic_sludge, 3.0 )" )
+ #endif
+ return false
+ }
+
+ return true
+}
+
+void function BroadcastCheckpointMessage()
+{
+ if ( file.nextSaveSilent )
+ {
+ file.nextSaveSilent = false
+ return
+ }
+
+ // tell clients about the save
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( !IsAlive( player ) )
+ continue
+
+ Remote_CallFunction_NonReplay( player, "SCB_CheckPoint" )
+ }
+}
+
+
+void function AddCallback_OnLoadSaveGame( void functionref( entity ) callbackFunc )
+{
+ Assert( !svGlobalSP.onLoadSaveGameCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnLoadSaveGame" )
+ svGlobalSP.onLoadSaveGameCallbacks.append( callbackFunc )
+}
+
+// Why does this need to be threaded?? I don't know! But it doesn't work if I don't wait a bit.
+void function UpdateUI( bool isTitan, entity player )
+{
+ if ( !IsValid( player ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+
+ wait 0.1
+
+ UpdatePauseMenuMissionLog( player )
+
+ if ( isTitan )
+ {
+ UI_NotifySPTitanLoadoutChange( player )
+ NotifyUI_ShowTitanLoadout( player, null )
+ }
+ else
+ {
+ NotifyUI_HideTitanLoadout( player, null )
+ }
+}
+
+float function LoadSaveTimeDamageMultiplier()
+{
+ return GraphCapped( Time() - file.loadSaveTime, 2.5, 4.0, 0.0, 1.0 )
+}
+
+float function GetLastCheckPointLoadTime()
+{
+ return file.loadSaveTime
+}
+
+bool function JustLoadedFromCheckpoint()
+{
+ return Time() - file.loadSaveTime < 1.0
+}
+
+bool function LoadedFromSave()
+{
+ printt( "SAVEGAME: Trying to load saveName" )
+ if ( !HasValidSaveGame() )
+ {
+ printt( "SAVEGAME: !HasValidSaveGame" )
+ return false
+ }
+
+ string saveName = GetSaveName()
+ printt( "SAVEGAME: Did load " + saveName )
+
+ // set the correct loadscreen
+ string mapName = SaveGame_GetMapName( saveName )
+ int startPointIndex = SaveGame_GetStartPoint( saveName )
+
+ array<string> clientCommands = GetLoadingClientCommands( mapName, startPointIndex, DETENT_FORCE_DISABLE, false )
+ ExecuteClientCommands( clientCommands )
+
+ if ( GetBugReproNum() != 196356 )
+ {
+ printt( "LOADPROGRESS" )
+ ClientCommand( GetFirstPlayer(), "show_loading_progress" )
+ WaitFrame()
+ }
+
+ SaveGame_LoadWithStartPointFallback()
+ return true
+}
diff --git a/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_spoke02.nut b/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_spoke02.nut
new file mode 100644
index 000000000..bdb291d7b
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_spoke02.nut
@@ -0,0 +1,3891 @@
+untyped
+
+global function CodeCallback_MapInit
+global function TransitionSpoke1
+
+const DUMMY_MODEL = $"models/Robots/stalker/robot_stalker.mdl"
+const TIMEZONE_DAY = 0
+const TIMEZONE_NIGHT = 1
+const TIMEZONE_ALL = 2
+
+const ANDERSON_MODEL = $"models/humans/heroes/mlt_hero_anderson.mdl"
+const IMC_CORPSE_MODEL_CIV = $"models/levels_terrain/sp_timeshift/civilian_eng_v2_corpse_static_20.mdl"
+
+//const FX_HUMAN_DOOR_OPEN = $"steam_leak_SM_CH_end"
+const FX_ANDERSON_DEVICE_FX = $"P_timeshift_gauntlet_hld"
+const FX_ARK_LAUNCH_GLOW = $"P_ts_core_hld_sm"
+const FX_ARK_LAUNCH_IN_PLACE = $"P_ts_core_hld_sm_lock"
+
+struct
+{
+ int elevatorDudesDead
+ array <entity> elevatorAnimNodes
+ array <entity> elevatorDoors
+
+} file
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+//
+//
+// TIMESHIFT SPOKE 2 - MAP INIT
+//
+//
+/////////////////////////////////////////////////////////////////////////////////////////
+void function CodeCallback_MapInit()
+{
+ ShSpTimeshiftSpoke2CommonInit()
+ RegisterSignal( "AnimTimeshift" )
+ RegisterSignal( "PlayerInsideCage" )
+ RegisterSignal( "ReleaseLabRat")
+
+ PrecacheModel( DUMMY_MODEL )
+ PrecacheModel( ANDERSON_MODEL )
+ PrecacheModel( IMC_CORPSE_MODEL_CIV )
+
+ //PrecacheParticleSystem( FX_HUMAN_DOOR_OPEN )
+ PrecacheParticleSystem( FX_ARK_LAUNCH_GLOW )
+ PrecacheParticleSystem( FX_ARK_LAUNCH_IN_PLACE )
+ PrecacheParticleSystem( FX_ANDERSON_DEVICE_FX )
+
+ //PrecacheModel( LABRAT_MODEL )
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+ AddPlayerDidLoad( TimeShiftHub_PlayerDidLoad )
+ AddSpawnCallback( "npc_soldier", OnSpawnedLevelNPC )
+ AddSpawnCallback( "npc_prowler", OnSpawnedLevelNPC )
+ AddSpawnCallback( "npc_spectre", OnSpawnedLevelNPC )
+ AddSpawnCallback( "prop_dynamic", OnSpawnedPropDynamic )
+
+ AddDeathCallback( "npc_prowler", OnDeathProwlerAcheivement )
+
+
+ SPTimeshiftUtilityInit()
+
+ //------------------
+ // Flags
+ //-----------------
+ FlagInit( "SwappedToFrozenWorld" )
+ FlagInit( "DoneWithFanDropSequence")
+ FlagInit( "ProwlerAcheivementUnlocked" )
+ FlagInit( "BrokeOutOfFanDropMusicLoop")
+ FlagInit( "ProwlerAmbushTriggered" )
+ FlagInit( "AndersomHologram2AboutToStart" )
+ FlagInit( "AcheivementUnlockedLabProwler" )
+ FlagInit( "ForceFlyerTakeoff" )
+ FlagInit( "RingVistaSequenceComplete" )
+ FlagInit( "AllProwlersSpawnedInHumanControlRoom" )
+ FlagInit( "IntelRoom1Finished" )
+ FlagInit( "LabRatAcheivementUnlocked" )
+ FlagInit( "AllElevatorProwlersSpawned" )
+ FlagInit( "player_back_in_amenities_lobby" )
+ FlagInit( "StartAndersonHologram1" )
+ FlagInit( "StartAndersonHologram2" )
+ FlagInit( "StartAndersonHologram3" )
+ FlagInit( "StartSphereRoomGunship" )
+ FlagInit( "LabBravoEnemiesDead" )
+ FlagInit( "human_bridge_soldiers_dead" )
+ FlagInit( "CampusReturnConversationFinished" )
+ FlagInit( "PlayerPickingUpDevice" )
+ FlagInit( "HidePlayerWeaponsDuringShifts")
+ FlagInit( "AllElevatorDudesDead" )
+ FlagInit( "ElevatorDudesDead1" )
+ FlagInit( "ElevatorDudesDead2" )
+ FlagInit( "ElevatorDudesDead3" )
+ FlagInit( "ElevatorDudesDead4" )
+ FlagInit( "CinematicTimeshiftSequenceFinished")
+ FlagInit( "retract_bridge_human_01" )
+ FlagInit( "retract_bridge_control_panel_pressed" )
+ FlagInit( "door_open_amenities_lobby_return_pristine" )
+ FlagInit( "finishedHumanVistaSequence" )
+ FlagInit( "spawnHumanBridgeEnemies" )
+ FlagInit( "player_looking_at_reactor_window" )
+ FlagInit( "open_door_lobby_main_overgrown" )
+ FlagInit( "PlayerLookingTowardsElevators" )
+ FlagInit( "ConcoursePanelHacked01" )
+ FlagInit( "ConcoursePanelHacked02" )
+ FlagInit( "PlayerPickedUpTimeshiftDevice" )
+ FlagInit( "FirstSpectreDeployedLobbyReduxPristine" )
+ FlagInit( "FirstSpectreDeployedLobbyReduxOvergrown" )
+ FlagInit( "SeveralElevatorDudesDead" )
+ FlagInit( "ShowMobilityGhostTurretFirepit" )
+ FlagInit( "ShowMobilityGhostElevatorShaft" )
+ FlagInit( "ShowMobilityGhostTurretFlank" )
+ FlagInit( "ShowMobilityGhostHumanLillypad01" )
+ FlagInit( "ShowMobilityGhostHumanLillypad02" )
+ FlagInit( "ShowMobilityGhostHumanWallrunChain" )
+ FlagInit( "ShowMobilityGhostPowertech" )
+
+ //------------------
+ // Start points
+ //------------------
+ //startPoint, mainFunc, setupFunc skipFunc
+ AddStartPoint( "Timeshift Device", AA_TimeshiftDeviceThread, TimeshiftDeviceStartPointSetup, TimeshiftDeviceSkipped )
+ AddStartPoint( "WILDLIFE RESEARCH", AA_WildlifeResearchThread, WildlifeResearchStartPointSetup, WildlifeResearchSkipped )
+ AddStartPoint( "First Timeshift Fight", AA_FirstTimeshiftFightThread, FirstTimeshiftFightStartPointSetup, FirstTimeshiftFightSkipped )
+ AddStartPoint( "Elevator Fight", AA_ElevatorFightThread, ElevatorFightStartPointSetup, ElevatorFightSkipped )
+ AddStartPoint( "HUMAN RESEARCH", AA_ElevatorTopThread, ElevatorTopStartPointSetup, ElevatorTopSkipped )
+ AddStartPoint( "Sphere Room", AA_SphereRoomThread, SphereRoomStartPointSetup, SphereRoomSkipped ) //checkpointIntelRoom2
+ AddStartPoint( "Human Room", AA_HumanResearchThread, HumanResearchStartPointSetup, HumanResearchSkipped )
+ AddStartPoint( "CAMPUS RETURN", AA_CampusReturnThread, CampusReturnStartPointSetup, CampusReturnSkipped )
+ AddStartPoint( "Fan Drop", AA_FanDropThread, FanDropStartPointSetup, FanDropSkipped )
+ AddStartPoint( "Fan Drop End", AA_FanDropEndThread, FanDropEndStartPointSetup, FanDropEndSkipped )
+
+ AddMobilityGhost( $"anim_recording/timeshift_turret_firepit_overgrown.rpak", "ShowMobilityGhostTurretFirepit" )
+ AddMobilityGhost( $"anim_recording/timeshift_elevator_shaft_overgrown.rpak", "ShowMobilityGhostElevatorShaft" )
+ AddMobilityGhost( $"anim_recording/timeshift_elevator_shaft_pristine.rpak", "ShowMobilityGhostElevatorShaft" )
+ AddMobilityGhost( $"anim_recording/timeshift_turret_flank_overgrown.rpak", "ShowMobilityGhostTurretFlank" )
+ AddMobilityGhost( $"anim_recording/timeshift_lillypad01_overgrown.rpak", "ShowMobilityGhostHumanLillypad01" )
+ AddMobilityGhost( $"anim_recording/timeshift_lillypad01_pristine.rpak", "ShowMobilityGhostHumanLillypad01" )
+ AddMobilityGhost( $"anim_recording/timeshift_lillypad02_overgrown.rpak", "ShowMobilityGhostHumanLillypad02" )
+ AddMobilityGhost( $"anim_recording/timeshift_lillypad02_pristine.rpak", "ShowMobilityGhostHumanLillypad02" )
+ AddMobilityGhost( $"anim_recording/timeshift_wallchain_overgrown.rpak", "ShowMobilityGhostHumanWallrunChain" )
+ AddMobilityGhost( $"anim_recording/timeshift_wallchain_pristine.rpak", "ShowMobilityGhostHumanWallrunChain" )
+ AddMobilityGhost( $"anim_recording/timeshift_powertech_overgrown.rpak", "ShowMobilityGhostPowertech" )
+ AddMobilityGhost( $"anim_recording/timeshift_powertech_pristine.rpak", "ShowMobilityGhostPowertech" )
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function EntitiesDidLoad()
+{
+ FlagSet( "retract_bridge_human_01" )
+
+ HideStuff( "human_bridge_overgrown" )
+ HideStuff( "elevator_doors_upper_overgrown" )
+ HideStuff( "blocker_fandrop_pristine" )
+ HideStuff( "blocker_fandrop_overgrown" )
+
+
+ thread SkyboxStart()
+ thread RingsThink()
+
+ array <entity> elevatorAnimNodesLocal = GetEntArrayByScriptName( "node_elevator_anim" )
+ Assert( elevatorAnimNodesLocal.len() == 4 )
+ file.elevatorAnimNodes = elevatorAnimNodesLocal
+
+ array <entity> elevatorDoorsLocal = GetEntArrayByScriptName( "elevator_door_exec_housing_access" )
+ Assert( elevatorDoorsLocal.len() == 4 )
+ file.elevatorDoors = elevatorDoorsLocal
+
+
+ //navmesh sep to keep prowlers away from half-open elevators
+ entity navmesh_blocker_elevator_overgrown = GetEntByScriptName( "navmesh_blocker_elevator_overgrown" )
+ navmesh_blocker_elevator_overgrown.NotSolid() //ok if it's notsolid, will still disconnect the navmesh
+
+
+ //CleanupEnts( "triggers_instadeath_humanroom" )
+ //CleanupEnts( "triggers_quickdeath_humanroom" )
+ //CleanupEnts( "trigger_quickdeath_checkpoint_humanroom" )
+
+ thread AndersonSetup()
+
+ // coop code: fuck with entities so we can prevent some weird flags
+ //array< entity > doors =
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TimeShiftHub_PlayerDidLoad( entity player )
+{
+ /*
+ This will run before any start points run.
+ Useful for doing common player-specific setup,
+ saving you from having to put a common player
+ setup function in each of your start points
+ */
+
+ //---------------------
+ // Timeshift thread
+ //----------------------
+ thread TimeshiftPlayerThink( player )
+
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗ ██████╗███████╗
+â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘████╗ ████║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•
+ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║██║██║ █████╗
+ ██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║██╔â•â•â• ██║ ██║ ██║██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•‘██║ ██╔â•â•â•
+ ██║ ██║██║ â•šâ•â• ██║███████╗███████║██║ ██║██║██║ ██║ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ╚████╔╠██║╚██████╗███████╗
+ â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗ ██████╗███████╗
+â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘████╗ ████║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•
+ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║██║██║ █████╗
+ ██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║██╔â•â•â• ██║ ██║ ██║██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•‘██║ ██╔â•â•â•
+ ██║ ██║██║ â•šâ•â• ██║███████╗███████║██║ ██║██║██║ ██║ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ╚████╔╠██║╚██████╗███████╗
+ â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗ ██████╗███████╗
+â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘████╗ ████║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•
+ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║██║██║ █████╗
+ ██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║██╔â•â•â• ██║ ██║ ██║██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•‘██║ ██╔â•â•â•
+ ██║ ██║██║ â•šâ•â• ██║███████╗███████║██║ ██║██║██║ ██║ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ╚████╔╠██║╚██████╗███████╗
+ â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗ ██████╗███████╗
+â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘████╗ ████║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•
+ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║██║██║ █████╗
+ ██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║██╔â•â•â• ██║ ██║ ██║██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•‘██║ ██╔â•â•â•
+ ██║ ██║██║ â•šâ•â• ██║███████╗███████║██║ ██║██║██║ ██║ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ╚████╔╠██║╚██████╗███████╗
+ â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â•â•â•â•â•â•
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TimeshiftDeviceStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointTimeshiftDevice" ) )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TimeshiftDeviceSkipped( entity player )
+{
+ FlagSet( "open_creature_door_start_pristine" )
+ FlagSet( "PlayerPickedUpTimeshiftDevice" )
+ GiveTimeshiftAbility( player )
+ FlagClear( "open_skybridge_door_end_pristine" )
+ FlagClear( "open_skybridge_door_end_overgrown" )
+ FlagSet( "CinematicTimeshiftSequenceFinished")
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_TimeshiftDeviceThread( entity player )
+{
+ InitBoyleAudioLogs()
+
+ //FlagSet( "open_skybridge_door_end_overgrown" )
+ //FlagSet( "open_creature_door_start_pristine" )
+ //-----------------------------------------
+ // Get timeshift device
+ //-----------------------------------------
+ wait 1
+
+ thread MusicGetTimeshiftDevice( player )
+ thread DialogueCreatureLabAnderson( player )
+
+ thread PickupTimeShiftDevice( player )
+
+
+
+ FlagWait( "PlayerPickedUpTimeshiftDevice" )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicGetTimeshiftDevice( entity player )
+{
+ StopMusic()
+ PlayMusic( "music_timeshift_15_gettemporaldevice" )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueCreatureLabAnderson( entity player )
+{
+ entity anderson = GetEntByScriptName( "anderson_other_half" )
+ local attach_id = anderson.LookupAttachment( "L_HAND" )
+ vector objectivePos = anderson.GetAttachmentOrigin( attach_id )
+
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_TAKE_TIME_DEVICE", objectivePos )
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+ wait 10
+
+ if ( Flag( "PlayerPickingUpDevice" ) )
+ return
+
+
+ Objective_Remind()
+ //timeshift device nags
+ string nagAlias
+ int nextNagNumber = 1
+
+
+ while( !Flag( "PlayerPickingUpDevice") )
+ {
+ wait( RandomFloatRange( 35, 45 ) )
+
+ if ( Flag( "PlayerPickingUpDevice" ) )
+ break
+
+
+ if ( nextNagNumber == 1 )
+ {
+ //BT Well done, Pilot. You located the wrist mounted device. SRS intel suggests if equipped you can withstand and manipulate the temporal shifts.
+ nagAlias = "diag_sp_wildlifeStudy_TS191_01_01_mcor_bt"
+ }
+
+ if ( nextNagNumber == 2 )
+ {
+ //BT Pilot, recommend you equip the device.
+ nagAlias = "diag_sp_wildlifeStudy_TS191_09_01_mcor_bt"
+ }
+ else if ( nextNagNumber == 3 )
+ {
+ //BT The wrist mounted device may be useful. Recommend you equip the device.
+ nagAlias = "diag_sp_wildlifeStudy_TS191_10_01_mcor_bt"
+ }
+ else if ( nextNagNumber == 4 )
+ {
+ //BT Advisory: Pilot, I recommend you equip the wrist mounted device.
+ nagAlias = "diag_sp_wildlifeStudy_TS191_11_01_mcor_bt"
+ }
+
+ waitthread PlayBTDialogue( nagAlias )
+
+ if ( Flag( "PlayerPickingUpDevice" ) )
+ break
+ Objective_Remind()
+ //Objective_Clear()
+ //TimeshiftSetObjective( player, "#TIMESHIFT_OBJECTIVE_TAKE_TIME_DEVICE", objectivePos )
+ nextNagNumber++
+ if ( nextNagNumber > 4 )
+ nextNagNumber = 1
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AndersonSetup()
+{
+ entity node = GetEntByScriptName( "org_get_device_sequence" )
+ entity anderson = GetEntByScriptName( "anderson_other_half" )
+ anderson.SetModel( ANDERSON_MODEL )
+ thread PlayAnimTeleport( anderson, "pt_timeshift_device_equip_corpse_sequence_idle", node )
+
+ entity AndersonDeviceFX = PlayLoopFXOnEntity( FX_ANDERSON_DEVICE_FX, anderson, "L_BACKHAND" )
+
+
+ int bodyGroupIndex = anderson.FindBodyGroup( "watch" )
+ anderson.SetBodygroup( bodyGroupIndex, 1 )
+
+ bodyGroupIndex = anderson.FindBodyGroup( "watch_ts" )
+ anderson.SetBodygroup( bodyGroupIndex, 1 )
+
+ FlagWait( "PlayerPickingUpDevice")
+
+ wait 1
+
+ if ( IsValid( AndersonDeviceFX ) )
+ {
+ StopFX( AndersonDeviceFX )
+ EntFireByHandle( AndersonDeviceFX, "Stop", "", 0, null, null )
+ AndersonDeviceFX.Destroy()
+ }
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function PickupTimeShiftDevice( entity player )
+{
+ entity node = GetEntByScriptName( "org_get_device_sequence" )
+ entity anderson = GetEntByScriptName( "anderson_other_half" )
+
+ wait 0.5
+
+ local attach_id = anderson.LookupAttachment( "L_HAND" )
+ vector origin = anderson.GetAttachmentOrigin( attach_id )
+ vector angles = anderson.GetAttachmentAngles( attach_id )
+
+
+ //entity useDummy = CreatePropDynamic( TempModel, origin, angles, 6 ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+ entity useDummy = CreatePropDynamic( DUMMY_MODEL, origin, Vector( 0, 0, 0 ), 2 ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+ useDummy.SetOrigin( origin )
+
+ useDummy.SetUsable()
+ useDummy.Hide()
+ useDummy.SetUsableByGroup( "pilot" )
+ useDummy.SetUsePrompts( "#TIMESHIFT_HINT_TAKE_DEVICE" , "#TIMESHIFT_HINT_TAKE_DEVICE_PC" )
+
+ local playerActivator
+ while( true )
+ {
+ playerActivator = useDummy.WaitSignal( "OnPlayerUse" ).player
+ if ( IsValid( playerActivator ) && playerActivator.IsPlayer() && !playerActivator.IsTitan() )
+ {
+ // set player to whoever actually picked up the device, not host
+ expect entity( playerActivator )
+ player = playerActivator
+ break
+ }
+ }
+
+ //Cooper: Sorry Anderson
+ FlagSet( "PlayerPickingUpDevice" )
+
+ Objective_Clear()
+ delaythread ( 1 ) PlayDialogue( "diag_sp_extra_GB101_65_01_mcor_player", player )
+
+
+ useDummy.UnsetUsable()
+ useDummy.Destroy()
+
+ player.DisableWeaponWithSlowHolster()
+ player.SetInvulnerable()
+ //player.FreezeControlsOnServer()
+ player.ContextAction_SetBusy()
+
+ FlagSet( "DoingCinematicTimeshift" )
+
+ //----------------------------------
+ // Player takes device off Anderson
+ //------------------------------------
+ entity mover = CreateOwnedScriptMover( node ) //need a mover for first person sequence
+
+ FirstPersonSequenceStruct sequenceTakeDevice
+ //sequenceTakeDevice.blendTime = 1
+ sequenceTakeDevice.attachment = "ref"
+ sequenceTakeDevice.firstPersonAnim = "ptpov_timeshift_device_equip_sequence"
+ sequenceTakeDevice.thirdPersonAnim = "pt_timeshift_device_equip_sequence"
+ sequenceTakeDevice.viewConeFunction = ViewConeTight
+
+ thread PlayAndersonCorpseAnims( anderson, node )
+ waitthread FirstPersonSequence( sequenceTakeDevice, player, mover )
+
+ FlagSet( "PlayerPickedUpTimeshiftDevice" )
+ //---------------------------
+ // Player equips device
+ //----------------------------
+ player.ClearParent()
+ mover.Destroy()
+ entity mover2 = CreateOwnedScriptMover( node ) //need a mover for first person sequence
+
+ FirstPersonSequenceStruct sequenceEquipDevice
+ sequenceEquipDevice.blendTime = 0
+ sequenceEquipDevice.attachment = "ref"
+ sequenceEquipDevice.firstPersonAnim = "ptpov_timeshift_device_equip_sequence_02"
+ sequenceEquipDevice.thirdPersonAnim = "pt_timeshift_device_equip_sequence_02"
+ sequenceEquipDevice.viewConeFunction = ViewConeTight
+
+ //WaitFrame()
+
+ GiveTimeshiftAbility( player )
+ thread TimeshiftSequenceShifts( player, mover2 )
+
+ entity proxy = player.GetFirstPersonProxy()
+ int bodyGroupIndex = proxy.FindBodyGroup( "glove_default" )
+ proxy.SetBodygroup( bodyGroupIndex, 1 ) // 0 = show, 1 = hide
+
+ int bodyGroupIndex2 = proxy.FindBodyGroup( "glove_animated" )
+ proxy.SetBodygroup( bodyGroupIndex2, 1 ) // 0 = show, 1 = hide
+
+ waitthread FirstPersonSequence( sequenceEquipDevice, player, mover2 )
+
+ SetTimeshiftArmDeviceSkin( 1 )
+
+ //player.UnfreezeControlsOnServer()
+ player.ClearInvulnerable()
+ player.Anim_Stop()
+ player.ClearParent()
+ ClearPlayerAnimViewEntity( player )
+ if ( player.ContextAction_IsBusy() )
+ player.ContextAction_ClearBusy()
+ player.EnableWeaponWithSlowDeploy()
+
+ FlagClear( "DoingCinematicTimeshift" )
+
+ FlagSet( "CinematicTimeshiftSequenceFinished")
+
+
+ proxy = player.GetFirstPersonProxy()
+ if ( proxy == null )
+ return
+
+ bodyGroupIndex = proxy.FindBodyGroup( "glove_default" )
+ proxy.SetBodygroup( bodyGroupIndex, 0 ) // 0 = show, 1 = hide
+
+ //bodyGroupIndex2= proxy.FindBodyGroup( "glove_animated" )
+ //proxy.SetBodygroup( bodyGroupIndex2, 1 ) // 0 = show, 1 = hide
+
+}
+
+void function PlayAndersonCorpseAnims( entity anderson, entity node )
+{
+ waitthread PlayAnim( anderson, "pt_timeshift_device_equip_corpse_sequence", node )
+
+ //watch
+ //int bodyGroupIndex = anderson.FindBodyGroup( "watch" )
+ //anderson.SetBodygroup( bodyGroupIndex, 1 )
+
+ int bodyGroupIndex = anderson.FindBodyGroup( "watch_ts" )
+ anderson.SetBodygroup( bodyGroupIndex, 0 )
+
+ thread PlayAnim( anderson, "pt_timeshift_device_equip_corpse_sequence_02", node )
+
+}
+void function TimeshiftSequenceShifts( entity player, entity node )
+{
+ player.EndSignal( "OnDeath" )
+ entity proxy = player.GetFirstPersonProxy()
+ vector nodeOrigin = node.GetOrigin()
+ vector playerOrigin = player.GetOrigin()
+
+
+ WaitSignal( proxy, "AnimTimeshift" )
+ SwapTimelines( player, TIMEZONE_DAY )
+ node.SetAbsOrigin( Vector( nodeOrigin.x, nodeOrigin.y, nodeOrigin.z + TIME_ZOFFSET ) )
+ //player.SetAbsOrigin( Vector( playerOrigin.x, playerOrigin.y, playerOrigin.z + TIME_ZOFFSET ) )
+
+ wait 0.1
+ //EmitSoundOnEntity( player, "Pilot_PhaseShift_End_3P" )
+
+ WaitSignal( proxy, "AnimTimeshift" )
+ SwapTimelines( player, TIMEZONE_NIGHT )
+ node.SetAbsOrigin( nodeOrigin )
+ //player.SetAbsOrigin( playerOrigin )
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗
+██║ ██║██║██║ ██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║
+██║ █╗ ██║██║██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ███████╗█████╗ ███████║██████╔â•â–ˆâ–ˆâ•‘ ███████║
+██║███╗██║██║██║ ██║ ██║██║ ██║██╔â•â•â• ██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â–ˆâ–ˆâ•‘
+╚███╔███╔â•â–ˆâ–ˆâ•‘███████╗██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—██║██║ ███████╗ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║
+ â•šâ•â•â•â•šâ•â•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•
+ */
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗
+██║ ██║██║██║ ██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║
+██║ █╗ ██║██║██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ███████╗█████╗ ███████║██████╔â•â–ˆâ–ˆâ•‘ ███████║
+██║███╗██║██║██║ ██║ ██║██║ ██║██╔â•â•â• ██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â–ˆâ–ˆâ•‘
+╚███╔███╔â•â–ˆâ–ˆâ•‘███████╗██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—██║██║ ███████╗ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║
+ â•šâ•â•â•â•šâ•â•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•
+ */
+/////////////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+/*
+██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗
+██║ ██║██║██║ ██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║
+██║ █╗ ██║██║██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ███████╗█████╗ ███████║██████╔â•â–ˆâ–ˆâ•‘ ███████║
+██║███╗██║██║██║ ██║ ██║██║ ██║██╔â•â•â• ██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â–ˆâ–ˆâ•‘
+╚███╔███╔â•â–ˆâ–ˆâ•‘███████╗██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—██║██║ ███████╗ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║
+ â•šâ•â•â•â•šâ•â•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•
+ */
+/////////////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+/*
+██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███████╗███████╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗
+██║ ██║██║██║ ██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║
+██║ █╗ ██║██║██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ███████╗█████╗ ███████║██████╔â•â–ˆâ–ˆâ•‘ ███████║
+██║███╗██║██║██║ ██║ ██║██║ ██║██╔â•â•â• ██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â–ˆâ–ˆâ•‘
+╚███╔███╔â•â–ˆâ–ˆâ•‘███████╗██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—██║██║ ███████╗ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║
+ â•šâ•â•â•â•šâ•â•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•
+ */
+/////////////////////////////////////////////////////////////////////////////////////////
+void function WildlifeResearchStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointWildlifeResearch" ) )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function WildlifeResearchSkipped( entity player )
+{
+ CleanupEnts( "flyer_lab" )
+ CleanupEnts( "civilian_evac_firehall" )
+ //thread MusicWildlifeResearch( player )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_WildlifeResearchThread( entity player )
+{
+ FlagWait( "CinematicTimeshiftSequenceFinished")
+
+ vector objectivePos = GetEntByScriptName( "obj_creature_labs_after_fire_hall" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos )
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayGloveGlow", TIMEZONE_NIGHT )
+
+ CheckPoint_Forced()
+
+ thread MusicWildlifeResearch( player )
+ //CheckPoint()
+
+ wait 1
+
+ thread DisplayOnscreenHint( player, "timeshift_hint_default", 3.0 )
+
+ //failsafeFlagToStart lookAtEnt = null
+ thread QuickSkit( player, GetEntByScriptName( "node_evac_firehall01" ), "headed_into_fire_hall" )
+ thread QuickSkit( player, GetEntByScriptName( "node_evac_firehall02" ), "headed_into_fire_hall" )
+ thread QuickSkit( player, GetEntByScriptName( "node_creature_soldiers_surprised" ), "open_door_creature_labs_part2" )
+
+ //-------------------------------------------------
+ // Use timeshift to get past electricity
+ //------------------------------------------------
+ thread DialogueTimeShiftEquipped( player )
+ thread DialogueCivilianEvacCreatureLabs( player )
+ thread DialogueCreaturLabsIMC( player )
+
+ thread TimeshiftHint( player, TIMEZONE_NIGHT, "player_entered_creature_lab", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_first_ts_hazard" ) )
+
+
+ FlagWait( "player_entered_creature_lab" )
+ CleanupEnts( "civilian_walker_courtyard" )
+ CleanupEnts( "civilian_actor_firehall01" )
+ CleanupEnts( "civilian_actor_firehall02" )
+
+
+ // remove this so we don't softlock other players lol
+ //FlagClear( "open_creature_door_start_pristine" )
+
+ objectivePos = GetEntByScriptName( "objective_spoke1_breadcrumb000" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ CheckPoint_Forced()
+
+ thread TimeshiftHint( player, TIMEZONE_DAY, "player_past_creature_first_laser", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_creature_hazard" ) )
+
+ thread ObjectiveRemindUntilFlag( "player_past_creature_first_laser" )
+
+ FlagWait( "player_past_creature_first_laser" )
+
+ objectivePos = GetEntByScriptName( "objective_spoke1_breadcrumb00" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ thread TimeshiftHint( player, TIMEZONE_NIGHT, "player_past_creature_second_obstacle", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_creature_hazard2" ) )
+
+ CheckPoint_Forced()
+
+ //lookAtEnt
+ thread QuickSkit( player, GetEntByScriptName( "node_evac_creaturelabs_2dudes" ), "player_past_creature_second_obstacle" )
+ thread QuickSkit( player, GetEntByScriptName( "node_evac_creaturelabs_bench2" ), "player_past_creature_second_obstacle" )
+
+ thread ObjectiveRemindUntilFlag( "player_past_creature_second_obstacle" )
+
+ FlagWait( "player_past_creature_second_obstacle" )
+
+
+ thread TimeshiftHint( player, TIMEZONE_DAY, "player_entered_creature_fans", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_creature_fan_hazard" ) )
+
+ objectivePos = GetEntByScriptName( "objective_spoke1_breadcrumb00aa" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+
+ //---------------------------------------
+ // Get past laser door in pristine via fans
+ //---------------------------------------
+
+ thread ObjectiveRemindUntilFlag( "player_exited_creature_labs" )
+
+ FlagWait( "player_entered_creature_fans" )
+
+ CleanupEnts( "civilian_evac_firehall" )
+ thread TimeshiftHint( player, TIMEZONE_NIGHT, "player_exited_creature_labs", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_creature_fan_blockage" ) )
+
+ FlagWait( "player_exited_creature_labs" )
+
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicWildlifeResearch( entity player )
+{
+ if ( Flag( "entered_amenities_elevator_room" ) )
+ return
+
+ FlagEnd( "entered_amenities_elevator_room" )
+
+
+ //waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ StopMusic()
+ SetGlobalNetBool( "music14LoopPausable", false )
+ PlayMusicThatCantBeStopped( "music_timeshift_14_pastloop" )
+
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+ //Until the elevator fight, whenever the player switches in the PRESENT - Play music_timeshift_16_explorepresent
+ thread PlayMusicInTimezoneUntilFlag( "music_timeshift_16_explorepresent", TIMEZONE_NIGHT, "entered_amenities_elevator_room" )
+
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function OnDeathProwlerAcheivement( entity npc, var damageInfo )
+{
+ if ( Flag( "ProwlerAcheivementUnlocked" ) )
+ return
+
+ if ( Flag( "crossed_elevator_fire_chasm" ) )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( !IsValid ( attacker ) )
+ return
+
+ if ( !attacker.IsPlayer() )
+ return
+
+ entity player = attacker
+
+ string classname = npc.GetClassName()
+
+ if ( !npc.HasKey( "script_noteworthy") )
+ return
+
+ string scriptNoteworthy = expect string( npc.kv.script_noteworthy )
+
+ if ( scriptNoteworthy != "prowler_acheivement" )
+ return
+
+ FlagSet( "ProwlerAcheivementUnlocked" )
+ UnlockAchievement( player, achievements.TIMESHIFT_PROWLER )
+
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueTimeShiftEquipped( entity player )
+{
+ FlagWait( "player_entered_creature_lab" )
+
+ wait 1
+
+ //BT Pilot Cooper, I have transferred some of my AI functions to the device, in order to permit communication across temporal shifts.
+ thread PlayBTDialogue( "diag_sp_wildlifeStudy_TS191_12_01_mcor_bt" )
+
+ FlagWait( "player_nearing_creature_fans" )
+
+ array <entity> grunts
+ entity gruntSpeaker
+ int gruntLines = 6
+ int gruntLinesPlayed = 0
+ string gruntAlias
+
+ while( true )
+ {
+ if ( gruntLinesPlayed >= gruntLines )
+ break
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+ gruntSpeaker = GetClosestGrunt( player, TIMEZONE_DAY )
+ if ( !IsValid( gruntSpeaker ) )
+ continue
+ if ( Distance( player.GetOrigin(), gruntSpeaker.GetOrigin() ) > 512 )
+ continue
+
+ switch( gruntLinesPlayed )
+ {
+ case 0:
+ //IMC Grunt 2 (Radio) What the hell? He was just over there!
+ gruntAlias = "diag_sp_wildlifeStudy_TS191_17_01_imc_grunt2"
+ break
+ case 1:
+ //security3 (radio comms, agitated): Who has visual?
+ gruntAlias = "diag_sp_securityComs_TS104_01_01_imc_security3"
+ break
+ case 2:
+ //security1 (radio comms, agitated): Can't track him, he's bouncing all over the place.
+ gruntAlias = "diag_sp_securityComs_TS104_02_01_imc_security1"
+ break
+ case 3:
+ //security1 (radio comms, agitated): Someone give me a location on this guy!
+ gruntAlias = "diag_sp_securityComs_TS106_01_01_imc_security1"
+ break
+ case 4:
+ //security3 (radio comms, agitated): Where the hell did he go?
+ gruntAlias = "diag_sp_securityComs_TS106_03_01_imc_security3"
+ break
+ case 5:
+ //security2 (radio comms, agitated): Watch your back! Watch your back!
+ gruntAlias = "diag_sp_securityComs_TS106_02_01_imc_security2"
+ break
+ }
+
+ waitthread PlayTimeShiftDialogue( player, gruntSpeaker, gruntAlias )
+ gruntLinesPlayed++
+
+ //Don't do next line till you hit elevator fight
+ FlagWait( "crossed_elevator_fire_chasm" )
+
+ }
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueCivilianEvacCreatureLabs( entity player )
+{
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ //IMC Security 3 - PA ...ask that all non-combat personnel and research teams proceed directly to the nearest
+ //Emergency Shelter access point due to a minor security breach. Please remain calm and contact the nearest automated security personnel for assistance.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_13_01_imc_security3" )
+
+ wait 1
+
+ soundEnt.Destroy()
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueCreaturLabsIMC( entity player )
+{
+ FlagWait( "player_past_creature_first_laser" )
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ SetGlobalForcedDialogueOnly( true )
+
+ //IMC Grunt 1 (Radio) ….last spotted him headed towards Wildlife research. Get the rest of the eggheads evacuated and set up a choke point.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_14_01_imc_grunt1" )
+
+ SetGlobalForcedDialogueOnly( false )
+
+ FlagWait( "player_past_creature_second_obstacle" )
+ //IMC Base (Radio) Additional laser meshes coming online. Let's box him in.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_15_01_imc_command" )
+
+ FlagWait( "player_entered_creature_fans" )
+
+ SetGlobalForcedDialogueOnly( true )
+
+ //IMC Grunt 3 (Radio) Control, we have initiated contact with the intruder but his movement is...erratic.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_18_01_imc_grunt3" )
+
+ SetGlobalForcedDialogueOnly( false )
+
+ soundEnt.Destroy()
+}
+
+
+void function CourtyardSoldiersThink( entity npc )
+{
+ if ( Flag( "player_entered_creature_lab") )
+ return
+ FlagEnd( "player_entered_creature_lab" )
+ npc.EnableNPCFlag( NPC_IGNORE_ALL )
+ npc.SetNoTarget( true )
+
+ OnThreadEnd(
+ function() : ( npc )
+ {
+ if ( IsValid( npc ) )
+ npc.Destroy()
+ }
+ )
+
+ WaitSignal( npc, "OnFinishedAssault" )
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗██████╗ ███████╗████████╗ ████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘████╗ ████║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â•â• ██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•
+█████╗ ██║██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ █████╗ ██║██║ ███╗███████║ ██║
+██╔â•â•â• ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║██╔â•â•â• ██║ ██╔â•â•â• ██║██║ ██║██╔â•â•â–ˆâ–ˆâ•‘ ██║
+██║ ██║██║ ██║███████║ ██║ ██║ ██║██║ â•šâ•â• ██║███████╗███████║██║ ██║██║██║ ██║ ██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║
+â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗██████╗ ███████╗████████╗ ████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘████╗ ████║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â•â• ██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•
+█████╗ ██║██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ █████╗ ██║██║ ███╗███████║ ██║
+██╔â•â•â• ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║██╔â•â•â• ██║ ██╔â•â•â• ██║██║ ██║██╔â•â•â–ˆâ–ˆâ•‘ ██║
+██║ ██║██║ ██║███████║ ██║ ██║ ██║██║ â•šâ•â• ██║███████╗███████║██║ ██║██║██║ ██║ ██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║
+â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗██████╗ ███████╗████████╗ ████████╗██╗███╗ ███╗███████╗███████╗██╗ ██╗██╗███████╗████████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘████╗ ████║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██║██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â• ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â•â• ██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•
+█████╗ ██║██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║ ██║██╔████╔██║█████╗ ███████╗███████║██║█████╗ ██║ █████╗ ██║██║ ███╗███████║ ██║
+██╔â•â•â• ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║██╔â•â•â• ██║ ██╔â•â•â• ██║██║ ██║██╔â•â•â–ˆâ–ˆâ•‘ ██║
+██║ ██║██║ ██║███████║ ██║ ██║ ██║██║ â•šâ•â• ██║███████╗███████║██║ ██║██║██║ ██║ ██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║
+â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function FirstTimeshiftFightStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointFirstTimeshiftFight" ) )
+ vector objectivePos = GetEntByScriptName( "objective_amenities_elevator_fight" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function FirstTimeshiftFightSkipped( entity player )
+{
+ FlagClear( "ShowMobilityGhostTurretFirepit" )
+ CleanupEnts( "flyer_lab" )
+ CleanupEnts( "lab_prowlers" )
+ thread LoudspeakerThread( player )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_FirstTimeshiftFightThread( entity player )
+{
+ FlagWait( "player_exited_creature_labs" )
+
+ vector objectivePos = GetEntByScriptName( "objective_spoke1_breadcrumb01" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+
+ thread LoudspeakerThread( player )
+ FlagSet( "ShowMobilityGhostTurretFirepit" )
+
+ CheckPoint()
+
+ thread DialogueToElevatorFight( player )
+ thread SecurityRoom( player )
+ //-------------------------------
+ // Hallway turret setup
+ //-------------------------------
+
+ array <entity> turrets = GetEntArrayByScriptName( "turrets_hallway_to_elevators" )
+ foreach( turret in turrets )
+ {
+ //bool hasShield = true
+ //thread HACK_DisableTurret( turret, hasShield )
+ turret.SetDumbFireMode( true )
+ }
+
+ FlagWait( "open_door_elevator_fight_hallway_both" )
+
+ CheckPoint()
+
+ objectivePos = GetEntByScriptName( "objective_amenities_elevator_fight" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ //-------------------------------
+ // Hallway turret flank
+ //-------------------------------
+
+ foreach( turret in turrets )
+ {
+ //bool hasShield = true
+ //thread HACK_EnableTurret( turret )
+ }
+
+ FlagWait( "player_entered_turret_to_elevator_hallway" )
+
+ FlagWait( "crossed_elevator_fire_chasm" )
+
+ //FlagClear( "open_door_elevator_fight_hallway_both" )
+ FlagClear( "ShowMobilityGhostTurretFirepit" )
+ CleanupEnts( "flyer_lab" )
+ CleanupEnts( "lab_prowlers" )
+
+ FlagWait( "entered_amenities_elevator_room" )
+
+}
+
+
+void function CreatureSurprisedSoldiersThink( entity npc )
+{
+ npc.EndSignal( "OnDeath" )
+
+ FlagWait( "player_past_creature_second_obstacle" )
+
+ npc.Anim_Stop()
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function SecurityRoom( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ entity deadCivilian = CreatePropDynamic( IMC_CORPSE_MODEL_CIV, Vector( 3686, -3820, -764 ), Vector( 0, 29.0586, 0 ), 0 ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+
+ deadCivilian.Hide()
+ int shiftCount = 0
+ int shiftRequirement = 88
+ entity triggerOvergrown = GetEntByScriptName( "trigger_security_underground_overgrown" )
+ entity triggerPristine = GetEntByScriptName( "trigger_security_underground_pristine" )
+
+ while( true )
+ {
+ wait 0.1
+ if ( shiftCount >= shiftRequirement )
+ break
+ FlagWait( "in_fire_pit" )
+
+ if ( level.timeZone == TIMEZONE_DAY )
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+ else
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+
+ if ( ( triggerOvergrown.IsTouching( player ) ) || ( triggerPristine.IsTouching( player ) ) )
+ shiftCount++
+ else
+ continue
+ }
+
+ FlagSet( "open_door_security_underground" )
+
+ deadCivilian.Show()
+
+
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â•â• ██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•
+█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╠█████╗ ██║██║ ███╗███████║ ██║
+██╔â•â•â• ██║ ██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â• ██║██║ ██║██╔â•â•â–ˆâ–ˆâ•‘ ██║
+███████╗███████╗███████╗ ╚████╔╠██║ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║
+â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â•â• ██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•
+█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╠█████╗ ██║██║ ███╗███████║ ██║
+██╔â•â•â• ██║ ██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â• ██║██║ ██║██╔â•â•â–ˆâ–ˆâ•‘ ██║
+███████╗███████╗███████╗ ╚████╔╠██║ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║
+â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â•â• ██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•
+█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╠█████╗ ██║██║ ███╗███████║ ██║
+██╔â•â•â• ██║ ██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â• ██║██║ ██║██╔â•â•â–ˆâ–ˆâ•‘ ██║
+███████╗███████╗███████╗ ╚████╔╠██║ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║
+â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â•â• ██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•
+█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╠█████╗ ██║██║ ███╗███████║ ██║
+██╔â•â•â• ██║ ██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â• ██║██║ ██║██╔â•â•â–ˆâ–ˆâ•‘ ██║
+███████╗███████╗███████╗ ╚████╔╠██║ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║
+â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+void function ElevatorFightStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointElevatorFight" ) )
+ vector objectivePos = GetEntByScriptName( "objective_amenities_elevator_fight" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function ElevatorFightSkipped( entity player )
+{
+ FlagClear( "ShowMobilityGhostElevatorShaft" )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_ElevatorFightThread( entity player )
+{
+
+ FlagWait( "entered_amenities_elevator_room" )
+
+ thread DialogueElevatorRoom( player )
+
+ FlagSet( "ShowMobilityGhostElevatorShaft" )
+
+ thread MusicElevatorFight( player )
+
+ //FlagClear( "open_door_elevator_fight_hallway_both" )
+
+ CheckPoint()
+
+ vector objectivePos = GetEntByScriptName( "objective_amenities_elevator" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+
+ thread SetFlagWhenPlayerLookingAtEnt( player, "PlayerLookingTowardsElevators", GetEntByScriptName( "elevator_look_target"), GetEntByScriptName( "trigger_look_elevators" ) )
+
+ array< entity > propSpawners = GetEntArrayByScriptNameInInstance( "spectre_door_spawner", "spectre_spawner_amenities_elevator_overgrown_upper_02" )
+ float delayMin = 0.5
+ float delayMax = 0.6
+ int maxToSpawn = 1
+ string flagToAbort = ""
+ thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, "", delayMin, delayMax )
+
+ FlagWait( "time_shifted_to_elevator_room_past" )
+
+ //-------------------------------
+ // Elevator fight
+ //-------------------------------
+ thread OpenElevatorDoorsOvergrownThink()
+ thread TurretEnemiesComeIntoElevatorRoom()
+ FlagWait( "PlayerLookingTowardsElevators" )
+
+ thread AllElevatorDudesDead()
+ FlagSet( "DisplayTheDamageHint" )
+ thread ElevatorObjectiveReminder( player )
+ //-------------------------------
+ // Token prowlers elevator room
+ //-------------------------------
+ int difficulty = GetSpDifficulty()
+ if ( difficulty < DIFFICULTY_MASTER )
+ maxToSpawn = 5 //max for this room is 10
+ else
+ maxToSpawn = 8 //max for this room is 10
+
+ propSpawners = GetEntArrayByScriptName( "prowler_spawnvents_elevator_room" )
+ delayMin = 3.5
+ delayMax = 6
+ maxToSpawn = 5 //max for this room is 10
+ flagToAbort = "AllElevatorDudesDead"
+ string flagToSetWhenAllAreSpawned = "AllElevatorProwlersSpawned"
+ bool requiresLookAt = true
+ thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, flagToSetWhenAllAreSpawned, delayMin, delayMax, "", requiresLookAt )
+
+ //------------------------------------------
+ // Player figures out elevator time puzzle
+ //-------------------------------------------
+ FlagWait( "entered_amenities_elevator" )
+
+ objectivePos = GetEntByScriptName( "objective_elevator_top" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ FlagClear( "DisplayTheDamageHint" )
+
+ FlagWait( "at_elevatorshaft_top" )
+
+ FlagClear( "ShowMobilityGhostElevatorShaft" )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueElevatorRoom( entity player )
+{
+
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+ FlagWait( "entered_amenities_elevator" )
+
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ if( Flag( "exited_elevator_run") )
+ return
+ FlagEnd( "exited_elevator_run" )
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+
+ if ( Flag( "AllElevatorDudesDead" ) )
+ {
+
+ //IMC Base (Radio) Security, Beta 4, do you copy? Beta 4! What is your position?
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_16_01_imc_command" )
+ }
+ else
+ {
+ //IMC Base (Radio) Has the intruder been neutralized?
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_22_01_imc_command" )
+
+ //IMC Grunt 6 (Radio) Standby, we can't get a lock on his position
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_23_01_imc_grunt6" )
+ }
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function HallwayTurretDudesThink( entity npc )
+{
+ npc.EndSignal( "OnDeath" )
+
+ //Kill these guys if player has finished elevator fight
+ FlagWait( "AllElevatorDudesDead" )
+
+ array <entity> players = GetPlayerArray()
+ if ( players.len() <= 0 )
+ return
+ entity player = players[ 0 ]
+ thread DeleteNpcWhenOutOfSight( npc, player )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function ElevatorObjectiveReminder( entity player )
+{
+ if( Flag( "at_elevatorshaft_top") )
+ return
+ FlagEnd( "at_elevatorshaft_top" )
+
+ FlagWait( "AllElevatorDudesDead" )
+
+ wait 5
+
+ while( true )
+ {
+ if ( level.timeZone == TIMEZONE_DAY )
+ Objective_Remind()
+ else if ( ( level.timeZone == TIMEZONE_NIGHT ) && ( !Flag( "enemies_inside_elevator_room_trigger_overgrown") ) )
+ Objective_Remind()
+
+ wait ( RandomFloatRange( 30, 40 ) )
+ }
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function GruntElevatorThink( entity npc )
+{
+ if ( !IsValid( npc ) )
+ return
+ npc.EndSignal( "OnDeath" )
+
+ npc.EnableNPCFlag( NPC_NO_MOVING_PLATFORM_DEATH )
+
+ OnThreadEnd(
+ function() : ( )
+ {
+ file.elevatorDudesDead++
+ if ( file.elevatorDudesDead >= 4 )
+ FlagSet( "SeveralElevatorDudesDead" )
+ }
+ )
+
+ if ( npc.HasKey( "script_noteworthy") )
+ {
+ string anim = expect string( npc.kv.script_noteworthy )
+ string animIdle = anim + "_idle"
+ entity node = GetClosest( file.elevatorAnimNodes, npc.GetOrigin() )
+ Assert( IsValid( node ) )
+ thread PlayAnimTeleport( npc, animIdle, node )
+
+ entity elevatorDoor = GetClosest( file.elevatorDoors, npc.GetOrigin() )
+ Assert( IsValid( elevatorDoor ) )
+
+ string flagToWaitFor = expect string( elevatorDoor.kv.script_flag )
+
+ if ( !Flag( flagToWaitFor ) )
+ {
+ FlagWait( flagToWaitFor )
+ //wait 0.5
+ }
+
+ thread PlayAnimTeleport( npc, anim, node )
+
+ }
+
+ WaitForever()
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TurretEnemiesComeIntoElevatorRoom()
+{
+ /*
+ entity goal_lobby_middle = GetEntByScriptName( "goal_lobby_middle" )
+ array <entity> grunts = GetNPCArrayBySquad( "kfiejfff" )
+ foreach( grunt in grunts )
+ {
+ if ( !IsAlive( grunt ) )
+ continue
+ grunt.AssaultPoint( goal_lobby_middle.GetOrigin() )
+ }
+ */
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function OpenElevatorDoorsOvergrownThink()
+{
+ FlagWait( "AllElevatorDudesDead" )
+ HideStuff( "doors_and_blockers_elevator_overgrown" )
+ entity navmesh_blocker_elevator_overgrown = GetEntByScriptName( "navmesh_blocker_elevator_overgrown" )
+ navmesh_blocker_elevator_overgrown.Hide()
+ navmesh_blocker_elevator_overgrown.NotSolid()
+ ToggleNPCPathsForEntity( navmesh_blocker_elevator_overgrown, true )
+
+
+//ToggleNPCPathsForEntity( GetEntByScriptName( "navmesh_blocker_elevator_overgrown" ), true )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicElevatorFight( entity player )
+{
+ FlagWait( "entered_amenities_elevator_room" )
+
+ wait 0.1
+
+ StopMusic()
+ PlayMusic( "music_timeshift_17_enterelevatorarea" )
+
+ FlagWait( "elevator_open_d" )
+
+ StopMusic()
+ PlayMusic( "music_timeshift_18_startelevatorfight" )
+ SetGlobalNetBool( "music14LoopPausable", true )
+
+ if( Flag( "entered_amenities_elevator") )
+ return
+
+ FlagEnd( "entered_amenities_elevator" )
+
+
+ //When you climb out of the roof of the elevator - Play music_timeshift_21b_climboutofelevator
+ //also need to stop the old music_timeshift_14_pastloop - Just play music_timeshift_21c_pastloop_stop
+ OnThreadEnd(
+ function() : ( player )
+ {
+ StopMusic()
+ PlayMusicThatCantBeStopped( "music_timeshift_21c_pastloop_stop" )
+ if ( IsValid( player ) )
+ StopSoundOnEntity( player, "music_timeshift_21_combatpresentdone" )
+ PlayMusic( "music_timeshift_21b_climboutofelevator" )
+ if ( IsValid( player ) )
+ {
+ StopSoundOnEntity( player, "music_timeshift_14_pastloop" )
+ printl( "manually stopping music: music_timeshift_14_pastloop")
+ }
+ SetGlobalNetBool( "music14LoopPausable", false )
+ }
+ )
+
+
+
+
+
+ //-----------------------------------------------------------------------------------------------
+ // Don't start playing different time period-specific tracks till the player starts time traveling
+ //-----------------------------------------------------------------------------------------------
+ if ( level.timeZone == TIMEZONE_NIGHT )
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+ else
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+
+
+ while( true )
+ {
+
+
+ if ( level.timeZone == TIMEZONE_NIGHT )
+ {
+ //----------------------
+ // Enemies still alive TIMEZONE_NIGHT
+ //----------------------
+ if ( ( Flag( "enemies_inside_elevator_room_trigger_overgrown") ) || ( !Flag( "AllElevatorProwlersSpawned" ) ) )
+ {
+ //During the combat, if the player switches to PRESENT - Play music_timeshift_20_combatpresent
+ //StopMusic()
+ StopMusicTrack( "music_timeshift_18_startelevatorfight" )
+ PlayMusicThatCantBeStopped( "music_timeshift_20_combatpresent" )
+
+ //While in TIMEZONE_DAY, check to see when all are dead, so we can stop combat music
+ while( level.timeZone == TIMEZONE_NIGHT )
+ {
+ wait 0.1
+ if ( ( !Flag( "enemies_inside_elevator_room_trigger_overgrown" ) )
+ && ( Flag( "AllElevatorProwlersSpawned" ) )
+ )
+ break
+ }
+ }
+
+ //------------------------------------------------
+ // If all enemies spawned and dead in TIMEZONE_NIGHT, play the "done" track
+ //------------------------------------------------
+ if ( ( !Flag( "enemies_inside_elevator_room_trigger_overgrown") ) && ( Flag( "AllElevatorProwlersSpawned" ) ) && ( level.timeZone == TIMEZONE_NIGHT ) )
+ {
+ //Each time the player switches to the PRESENT where there are no more enemies - Play music_timeshift_21_combatpresentdone
+ //StopMusic()
+ if ( IsValid( player ) )
+ StopSoundOnEntity( player, "music_timeshift_21_combatpresentdone" )
+ PlayMusicThatCantBeStopped( "music_timeshift_21_combatpresentdone" )
+ }
+
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+ }
+ else //TIMEZONE_DAY
+ {
+ //----------------------
+ // Enemies still alive TIMEZONE_DAY
+ //----------------------
+ if ( ( !Flag( "AllElevatorDudesDead" ) ) && ( level.timeZone == TIMEZONE_DAY ) )
+ {
+ //During the combat, if the player switches to PAST - Play music_timeshift_19_combatpast
+ PlayMusic( "music_timeshift_19_combatpast" )
+
+ //While in TIMEZONE_DAY, check to see when all are dead, so we can stop combat music
+ while( level.timeZone == TIMEZONE_DAY )
+ {
+ wait 0.1
+ if ( Flag( "AllElevatorDudesDead" ) )
+ break
+ }
+
+ }
+
+ //------------------------------------------------
+ //If all enemies dead in TIMEZONE_DAY, play the "done" track
+ //------------------------------------------------
+ if ( ( Flag( "AllElevatorDudesDead" ) ) && ( level.timeZone == TIMEZONE_DAY ) )
+ {
+ //Each time the player switches to the PAST where there are no more enemies - Play music_timeshift_21a_combatpastdone
+ //StopMusic()
+ PlayMusic( "music_timeshift_21a_combatpastdone" )
+ }
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+ }
+ }
+
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void function DialogueToElevatorFight( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ FlagWait( "open_door_elevator_fight_hallway_both" )
+
+ SetGlobalForcedDialogueOnly( true )
+
+ //IMC Grunt 8 (Radio) In position. Activating turrets at south-east corridor.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_26_01_imc_grunt8" )
+
+ //IMC Base (Radio) We have an intruder, heavily armed and dangerous. All units in the area proceed to the south-east elevator banks on level 4 to intercept.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_19_01_imc_command" )
+
+
+ if ( !Flag( "entered_amenities_elevator_room" ) )
+ {
+ //IMC Grunt 4 (Radio) Copy that, control!
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_20_01_imc_grunt4" )
+
+ }
+
+ if ( !Flag( "entered_amenities_elevator_room" ) )
+ {
+ //IMC Grunt 5 (Radio) On our way, control!
+ thread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_21_01_imc_grunt5" )
+
+ }
+
+ FlagWait( "SeveralElevatorDudesDead" )
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ if ( Flag( "entered_amenities_elevator" ) || Flag( "AllElevatorDudesDead" ) )
+ {
+ SetGlobalForcedDialogueOnly( false )
+ return
+ }
+
+ //IMC Base (Radio) Sitrep on the unknown target.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_24_01_imc_command" )
+
+
+ if ( Flag( "entered_amenities_elevator" ) || Flag( "AllElevatorDudesDead" ) )
+ {
+ SetGlobalForcedDialogueOnly( false )
+ return
+ }
+
+ entity soldier = GetClosestGrunt( player, TIMEZONE_DAY, "grunts_elevator" )
+
+ if ( IsValid( soldier ) )
+ {
+ //IMC Grunt 7 (Radio) We're getting our asses kicked out here, that's the bloody SitRep!
+ waitthread PlayTimeShiftDialogue( player, soldier, "diag_sp_wildlifeStudy_TS191_25_01_imc_grunt7" )
+ }
+
+ SetGlobalForcedDialogueOnly( false )
+
+ soundEnt.Destroy()
+
+}
+
+void function AllElevatorDudesDead()
+{
+ FlagWaitAll( "ElevatorDudesDead1", "ElevatorDudesDead2", "ElevatorDudesDead3", "ElevatorDudesDead4" )
+ FlagSet( "AllElevatorDudesDead" )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ████████╗ ██████╗ ██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╠██║ ██║ ██║██████╔â•
+██╔â•â•â• ██║ ██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•— ██║ ██║ ██║██╔â•â•â•â•
+███████╗███████╗███████╗ ╚████╔╠██║ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘
+â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ████████╗ ██████╗ ██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╠██║ ██║ ██║██████╔â•
+██╔â•â•â• ██║ ██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•— ██║ ██║ ██║██╔â•â•â•â•
+███████╗███████╗███████╗ ╚████╔╠██║ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘
+â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ███████╗██╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ████████╗ ██████╗ ██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ██║ █████╗ ██║ ██║███████║ ██║ ██║ ██║██████╔╠██║ ██║ ██║██████╔â•
+██╔â•â•â• ██║ ██╔â•â•â• ╚██╗ ██╔â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•‘ ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•— ██║ ██║ ██║██╔â•â•â•â•
+███████╗███████╗███████╗ ╚████╔╠██║ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘
+â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void function ElevatorTopStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointElevatorTop" ) )
+ vector objectivePos = GetEntByScriptName( "objective_intel_data_panel01" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function ElevatorTopSkipped( entity player )
+{
+ FlagSet( "LabBravoEnemiesDead" )
+ FlagSet( "entered_hallways_to_human_research" )
+ FlagSet( "open_door_elevator_top_lab" )
+ FlagSet( "StartAndersonHologram1" )
+ FlagClear( "open_door_lab_civilian_escape_01" )
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_ElevatorTopThread( entity player )
+{
+ FlagWait( "at_elevatorshaft_top" )
+ FlagClear( "open_door_elevator_top_lab" )
+ //failsafeFlagToStart
+ thread QuickSkit( player, GetEntByScriptName( "node_holo1_evac" ), "exited_elevator_run" )
+
+ thread LabSoldierA( player )
+
+ FlagWait( "exited_elevator_run" )
+
+ SetGlobalForcedDialogueOnly( true )
+
+ thread MusicElevatorTop( player )
+ thread DialogueLabAlphaEvac( player )
+ thread DialogueLabAlpha( player )
+ thread ProwlerGagLabAlpha( player )
+ thread AndersonHologramSequence( player, "node_hologram_lab1", "StartAndersonHologram1" )
+ thread AchievementAndersonsFirstLog( player )
+
+ vector objectivePos = GetEntByScriptName( "objective_intel_data_panel01" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ CheckPoint()
+
+ FlagWait( "open_door_elevator_top_lab" )
+
+ CheckPoint()
+
+ CleanupEnts( "grunts_elevator" )
+
+ thread CleanupAI( player )
+
+ ShowStuff( "elevator_doors_upper_overgrown" )
+
+ wait 2
+
+ SetGlobalForcedDialogueOnly( false )
+
+ FlagClear( "open_door_lab_civilian_escape_01" )
+
+ FlagWait( "AndersonHologram1Finished" )
+
+
+ FlagSet( "IntelRoom1Finished" )
+
+ if ( !Flag( "back_in_hall_after_anderson_first_log" ) )
+ CheckPoint()
+
+ objectivePos = GetEntByScriptName( "objective_sphere_room_breadcrumb01" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ thread ObjectiveRemindUntilFlag( "entered_hallways_to_human_research" )
+
+ FlagWait( "entered_hallways_to_human_research" )
+
+}
+
+void function AchievementAndersonsFirstLog( entity player )
+{
+ FlagWait( "AndersonHologram1Playing" )
+ UnlockAchievement( player, achievements.VIEW_LOG )
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void function LabSoldierA( entity player )
+{
+ entity node = GetEntByScriptName( "node_sandtable_soldier_react" )
+ Assert( IsValid( node ) )
+ entity spawner = GetEntByScriptName( "labA_soldier" )
+ Assert( IsValid( spawner ) )
+ entity npc = spawner.SpawnEntity()
+ DispatchSpawn( npc )
+ Assert( IsValid( npc ) )
+
+ npc.EndSignal( "OnDeath" )
+
+ if( Flag( "open_door_elevator_top_lab") )
+ return
+ FlagEnd( "open_door_elevator_top_lab" )
+
+ OnThreadEnd(
+ function() : ( npc )
+ {
+ if ( IsValid( npc ) )
+ npc.Destroy()
+ }
+ )
+
+ npc.EnableNPCFlag( NPC_IGNORE_ALL )
+ npc.SetNoTarget( true )
+ thread PlayAnimTeleport( npc, "pt_cloak_react_app_far_point_timeshift_idle", node )
+
+ FlagWait( "exited_elevator_run" )
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ waitthread PlayAnim( npc, "pt_cloak_react_app_far_point_timeshift", node )
+ npc.AssaultPoint( Vector( 4804.74, -3668.88, 11744 ) )
+
+ WaitSignal( npc, "OnFinishedAssault" )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicElevatorTop( entity player )
+{
+ FlagWait( "exited_elevator_run" )
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ StopMusic()
+ PlayMusicThatCantBeStopped( "music_timeshift_22_panicburst" )
+
+ FlagWait( "StartAndersonHologram1" )
+
+ StopMusic()
+ PlayMusic( "music_timeshift_23_andersonlog02" )
+
+ thread MusicHologram1Ambush()
+
+ FlagWait( "AndersonHologram1Finished" )
+
+
+ SetGlobalNetBool( "music14LoopPausable", false )
+ PlayMusicThatCantBeStopped( "music_timeshift_14_pastloop" )
+ //Following the ambush, when the player switches to the PRESENT - Play music_timeshift_16_explorepresent
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+ thread PlayMusicInTimezoneUntilFlag( "music_timeshift_16_explorepresent", TIMEZONE_NIGHT, "StartAndersonHologram2" )
+
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function MusicHologram1Ambush()
+{
+ //Ambush could happen in either past of present
+ FlagWaitAny( "open_door_lab_reinforcements_pristine", "ProwlerAmbushTriggered" )
+
+ //StopMusic()
+ PlayMusicThatCantBeStopped( "music_timeshift_24_ambush" )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function ProwlerGagLabAlpha( entity player )
+{
+ entity door = GetEntByScriptName( "door_prowler_lab_a" )
+ entity spawner = GetEntByScriptName( "prowler_lab_a" )
+ vector origin = door.GetOrigin()
+ vector angles = door.GetAngles()
+
+ entity prowler = spawner.SpawnEntity()
+ DispatchSpawn( prowler )
+
+ prowler.EnableNPCFlag( NPC_NO_MOVING_PLATFORM_DEATH )
+ prowler.Hide()
+ prowler.NotSolid()
+ MakeInvincible( prowler )
+
+ thread PlayAnimTeleport( prowler, "pr_timeshift_door_tease_01_idle", origin, angles )
+ thread PlayAnimTeleport( door, "door_door_spawn_core_idle", origin, angles )
+
+ string animDoor
+ string animProwler
+
+ FlagWait( "near_lab_alpha_prowler_skit" )
+
+ if ( !Flag( "IntelRoom1Finished" ) )
+ {
+ prowler.Show()
+ prowler.Solid()
+ thread PlayAnimTeleport( door, "door_timeshift_prowler_tease_01", origin, angles )
+ waitthread PlayAnimTeleport( prowler, "pr_timeshift_door_tease_01", origin, angles )
+ prowler.Hide()
+ prowler.NotSolid()
+ thread PlayAnimTeleport( prowler, "pr_timeshift_door_tease_01_idle", origin, angles )
+ thread PlayAnimTeleport( door, "door_door_spawn_core_idle", origin, angles )
+ FlagWait( "AndersonHologram1Finished" )
+ FlagWait( "near_lab_alpha_prowler_skit" )
+ }
+
+ FlagSet( "ProwlerAmbushTriggered" )
+ prowler.Show()
+ prowler.Solid()
+ ClearInvincible( prowler )
+ thread PlayAnimTeleport( prowler, "pr_timeshift_door_spawn_01", origin, angles )
+ thread PlayAnimTeleport( door, "door_timeshift_prowler_spawn_01", origin, angles )
+ DisableNavmeshSeperatorTargetedByEnt( door )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueLabAlphaEvac( entity player )
+{
+ if( Flag( "open_door_elevator_top_lab") )
+ return
+ FlagEnd( "open_door_elevator_top_lab" )
+
+
+ //entity panel_intel_room1 = GetEntByScriptName( "panel_intel_room1" )
+ entity loudspeakerEnt1 = CreateLoudspeakerEnt( Vector( 5488,-3718, 11736 ) )
+ entity loudspeakerEnt2 = CreateLoudspeakerEnt( Vector( 5488,-3718, 11736 ) )
+ entity loudspeakerEnt3 = CreateLoudspeakerEnt( Vector( 5488,-3718, 11736 ) )
+
+
+ OnThreadEnd(
+ function() : ( loudspeakerEnt1, loudspeakerEnt2, loudspeakerEnt3 )
+ {
+ loudspeakerEnt1.Destroy()
+ loudspeakerEnt2.Destroy()
+ loudspeakerEnt3.Destroy()
+ }
+ )
+
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ //Scientist 1 What about the intruder?!
+ //thread PlayTimeShiftDialogue( player, loudspeakerEnt1, "diag_sp_humanStudy_TS201_03_01_imc_scientist1" )
+
+ wait 0.5
+
+ //He's here!
+ //thread PlayTimeShiftDialogue( player, loudspeakerEnt, "diag_sp_humanStudy_TS202_01_01_imc_scientist1" )
+
+
+ //Grunt: Intruder spotted! Code 83!
+ thread PlayTimeShiftDialogue( player, loudspeakerEnt2, "diag_sp_humanStudy_TS201_14_01_imc_security1" )
+
+ wait 1.8
+
+ //Scientist 2 We have to leave!
+ thread PlayTimeShiftDialogue( player, loudspeakerEnt3, "diag_sp_humanStudy_TS201_04_01_imc_scientist2" )
+
+ wait 1
+
+
+ //General Marder We're going forward with this. The test must be completed.
+ waitthread PlayTimeShiftDialogue( player, player, "diag_sp_humanStudy_TS201_01_01_imc_genMarder" )
+
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueLabAlpha( entity player )
+{
+ FlagWait( "open_door_elevator_top_lab" )
+
+ wait 1
+
+
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+ FlagSet( "StartAndersonHologram1" )
+
+ //BT Pilot, a fragment of Anderson's damaged log may be relevant here. Activating log playback...
+ waitthread PlayBTDialogue( "diag_sp_humanStudy_TS201_11_01_imc_bt" )
+
+
+
+ FlagWaitAny( "AndersonHologram1Finished" )
+
+ wait 0.5
+
+
+ FlagWait( "entered_hallways_to_human_research" )
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+ if ( !Flag( "player_inside_intel_room2" ) )
+ {
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+ //General Marder If we don't test the Sculptor Core now we may never have another chance. Zulu Team, prep the Sculptor Core for delivery.
+ waitthread PlayTimeShiftDialogue( player, player, "diag_sp_humanStudy_TS201_02_01_imc_genMarder" )
+ }
+
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function LabAlphaScientistThink( entity npc )
+{
+ npc.EndSignal( "OnDeath" )
+ thread DestroyNPCOnFlag( npc, "open_door_elevator_top_lab" )
+
+ WaitSignal( npc, "OnFinishedAssault" )
+
+ if ( IsValid( npc ) )
+ npc.Destroy()
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██████╗ ██╗ ██╗███████╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—████╗ ████║
+███████╗██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•‘█████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██████╔â•â–ˆâ–ˆâ•‘ ██║██║ ██║██╔████╔██║
+â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██║ ██║██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘
+███████║██║ ██║ ██║███████╗██║ ██║███████╗ ██║ ██║╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ•‘ â•šâ•â• ██║
+â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██████╗ ██╗ ██╗███████╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—████╗ ████║
+███████╗██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•‘█████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██████╔â•â–ˆâ–ˆâ•‘ ██║██║ ██║██╔████╔██║
+â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██║ ██║██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘
+███████║██║ ██║ ██║███████╗██║ ██║███████╗ ██║ ██║╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ•‘ â•šâ•â• ██║
+â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██████╗ ██╗ ██╗███████╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—████╗ ████║
+███████╗██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•‘█████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██████╔â•â–ˆâ–ˆâ•‘ ██║██║ ██║██╔████╔██║
+â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██║ ██║██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘
+███████║██║ ██║ ██║███████╗██║ ██║███████╗ ██║ ██║╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ•‘ â•šâ•â• ██║
+â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void function SphereRoomStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointIntelRoom2" ) )
+ vector objectivePos = GetEntByScriptName( "objective_intel_data_panel02" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function SphereRoomSkipped( entity player )
+{
+ FlagSet( "RingsShouldBeSpinning" )
+ FlagSet( "player_inside_intel_room2" )
+ //thread HumanPodsThink( "biodoors_terminal01_pristine", "biodoors_terminal01_overgrown", player )
+ //thread HumanPodsThinkNoHack( "biodoors_terminal01_pristine", "biodoors_terminal02_overgrown", player )
+ //thread HumanPodsThinkNoHack( "biodoors_terminal02_pristine", "biodoors_terminal02_overgrown", player )
+
+ FlagClear( "ShowMobilityGhostTurretFlank" )
+ thread QuickSkit( player, GetEntByScriptName( "node_reactor_flyers" ) )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_SphereRoomThread( entity player )
+{
+ FlagWait( "entered_hallways_to_human_research" )
+
+ FlagSet( "ShowMobilityGhostTurretFlank" )
+
+ thread MusicSphereRoom( player )
+ thread DialogueLabBravo( player )
+ thread DialogueHumanAnteroom( player )
+ thread DialogueGunshipDeploys( player )
+ thread GunshipPadSequenceWait( player )
+ thread GunshipSequence( "gunship_pad", player, "node_gunship_intel_room_2", "pad", "StartSphereRoomGunship" )
+ thread AndersonHologramSequence( player, "node_hologram_lab2", "StartAndersonHologram2" )
+
+ CheckPoint()
+
+ FlagWait( "dropped_into_fire_hallways" )
+ vector objectivePos = GetEntByScriptName( "objective_intel_data_panel02" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ FlagWait( "player_inside_intel_room2" )
+
+ thread TurretNotargetHack( player )
+ FlagWait( "AndersonHologram2Finished" )
+ CheckPoint()
+ FlagSet( "open_door_diorama2_exit_pristine" )
+ FlagSet( "open_door_diorama2_exit_overgrown" )
+
+ thread CheckpointSphereRoomOvergrown( player )
+ thread CheckpointSphereRoomPristine( player )
+
+ objectivePos = GetEntByScriptName( "objective_human_research_breadcrumb_01a" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ thread ObjectiveRemindUntilFlag( "exited_intel_room2" )
+
+ FlagWait( "exited_intel_room2" )
+
+ objectivePos = GetEntByScriptName( "objective_human_research_breadcrumb_02" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ FlagWait( "entering_human_hallway_room" )
+
+ //thread HumanPodsThink( "biodoors_terminal01_pristine", "biodoors_terminal01_overgrown", player )
+ //thread HumanPodsThinkNoHack( "biodoors_terminal01_pristine", "biodoors_terminal02_overgrown", player )
+ //thread HumanPodsThinkNoHack( "biodoors_terminal02_pristine", "biodoors_terminal02_overgrown", player )
+
+ //-------------------------------
+ // Token stalkers
+ //-------------------------------
+ array< entity > propSpawners = GetEntArrayByScriptName( "stalker_spawnvents_human_hallway_room" )
+ float delayMin = 1.3
+ float delayMax = 3
+ int maxToSpawn = 3
+ string flagToAbort = "entering_human_main_room"
+ string flagToSetWhenAllAreSpawned = ""
+ bool requiresLookAt = false
+ thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, flagToSetWhenAllAreSpawned, delayMin, delayMax, "", requiresLookAt )
+
+ FlagWait( "past_human_hall_turrets" )
+ thread QuickSkit( player, GetEntByScriptName( "node_reactor_flyers" ) )
+ CheckPoint()
+
+ FlagClear( "ShowMobilityGhostTurretFlank" )
+
+
+ FlagWait( "entering_human_anteroom" )
+
+ //entity rings_pristine = GetEntByScriptName( "rings_pristine" )
+
+ //Spawn a sound dummy model to attach the sound to since doing it on an info_target
+ //or "AtPosition" is unreliable with Timeshift teleports (can get culled)
+ entity lookEnt = GetEntByScriptName( "lookent_rings" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, lookEnt.GetOrigin(), "timeshift_scr_rings_spin_slow_lp" )
+
+ FlagSet( "RingsShouldBeSpinning" )
+
+ CheckPoint()
+
+
+ objectivePos = GetEntByScriptName( "objective_human_research_main_door" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ FlagWait( "entering_human_main_room" )
+
+}
+
+
+
+
+
+
+void function GruntsSphereRoomThink( entity npc )
+{
+ //TODO: make these guys constanly get the player as an enemy
+ if ( !IsValid( npc ) )
+ return
+
+ npc.EndSignal( "OnDeath" )
+
+ while( true )
+ {
+ AttackPlayer( npc )
+ wait RandomFloatRange( 2, 5 )
+ }
+
+
+
+}
+void function CheckpointSphereRoomOvergrown( entity player )
+{
+ FlagEnd( "entering_human_anteroom" )
+ FlagWait( "sphere_room_stalkers_dead" )
+ CheckPoint()
+}
+
+void function CheckpointSphereRoomPristine( entity player )
+{
+ FlagEnd( "entering_human_anteroom" )
+ FlagWait( "sphere_room_pristine_enemies_dead" )
+ CheckPoint()
+}
+
+void function DialogueHumanAnteroom( entity player )
+{
+ FlagWait( "entering_human_anteroom" )
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ //General Marder - Radio Spin up the outer rings. Test sequence will commence once Sculptor Core is in place.
+ thread PlayTimeShiftDialogue( player, player, "diag_sp_miniSculptor_TS221_02_01_imc_genMarder" )
+
+
+}
+
+void function TurretNotargetHack( entity player )
+{
+ if( Flag( "entering_human_main_room") )
+ return
+ FlagEnd( "entering_human_main_room" )
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ player.SetNoTarget( false )
+ }
+ )
+ while( true )
+ {
+ FlagWait( "player_no_target" )
+ player.SetNoTarget( true )
+ FlagWaitClear( "player_no_target" )
+ player.SetNoTarget( false )
+ }
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicSphereRoom( entity player )
+{
+ FlagWait( "StartAndersonHologram2" )
+
+ //FlagWait( "AndersonHologram2Playing" )
+
+ // When the third Anderson log starts - Play music_timeshift_25_andersonlog03
+ // At the same time - Play music_timeshift_21c_pastloop_stop
+ StopMusic()
+ PlayMusic( "music_timeshift_25_andersonlog03" )
+
+ if ( IsValid( player ) )
+ {
+ StopSoundOnEntity( player, "music_timeshift_14_pastloop" )
+ printl( "manually stopping music: music_timeshift_14_pastloop")
+ }
+
+ PlayMusicThatCantBeStopped( "music_timeshift_21c_pastloop_stop" )
+
+ //When the door drops down revealing the dudes
+ FlagWaitAny( "open_door_diorama2_exit_pristine", "open_door_diorama2_exit_overgrown" )
+
+ StopMusic()
+ PlayMusicThatCantBeStopped( "music_timeshift_24_ambush" )
+ SetGlobalNetBool( "music14LoopPausable", false )
+ PlayMusicThatCantBeStopped( "music_timeshift_14_pastloop" )
+
+ // Following the ambush, when the player switches to the PRESENT - Play music_timeshift_16_explorepresent
+ FlagWait( "AndersonHologram2Finished" )
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+
+ thread PlayMusicInTimezoneUntilFlag( "music_timeshift_16_explorepresent", TIMEZONE_NIGHT, "entering_human_anteroom" )
+
+
+ FlagWait( "entering_human_anteroom" )
+
+ // When you enter human testing - Play music_timeshift_26_humanresearch
+ // At the same time - Play music_timeshift_21c_pastloop_stop
+
+ wait 0.1
+
+ StopMusic()
+ if ( IsValid( player ) )
+ {
+ StopSoundOnEntity( player, "music_timeshift_14_pastloop" )
+ printl( "manually stopping music: music_timeshift_14_pastloop")
+ }
+
+ PlayMusicThatCantBeStopped( "music_timeshift_21c_pastloop_stop" )
+
+ PlayMusic( "music_timeshift_26_humanresearch" )
+
+
+
+}
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueLabBravo( entity player )
+{
+ FlagWaitAny( "LabBravoEnemiesDead", "player_near_intel_room_2_landing_pad" )
+
+ FlagWait( "player_inside_intel_room2" )
+
+ FlagWait( "player_inside_intel_room2_halfway" )
+
+ FlagClear( "open_doors_sphere_room_entrance_all" )
+
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+ FlagSet( "StartAndersonHologram2" )
+ FlagSetDelayed( "AndersomHologram2AboutToStart", 3)
+
+
+ FlagWait( "AndersonHologram2Finished" )
+
+ wait 0.5
+
+
+
+}
+
+
+void function DialogueGunshipDeploys( entity player )
+{
+
+ /*
+ FlagWaitAny( "LabBravoEnemiesDead", "player_inside_intel_room2_halfway" )
+
+
+ //FlagWait( "LookingAtPad" )
+
+ entity soundEnt = CreateLoudspeakerEnt( GetEntByScriptName( "lookent_pad" ).GetOrigin() )
+
+ FlagWait( "AndersonHologram2Finished" )
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ //waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ if ( !Flag( "AndersomHologram2AboutToStart" ) )
+ {
+ //Scientist 5 (Radio) Sculptor Core prepped for delivery and en route to the test chamber.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_miniSculptor_TS221_01_01_imc_scientist5" )
+ }
+
+ */
+
+ FlagWait( "StartSphereRoomGunship")
+
+ wait 1
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+ entity soundEnt = CreateLoudspeakerEnt( GetEntByScriptName( "lookent_pad" ).GetOrigin() )
+
+ //Scientist 5 (Radio) Sculptor Core prepped for delivery and en route to the test chamber.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_miniSculptor_TS221_01_01_imc_scientist5" )
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function GunshipPadSequenceWait( entity player )
+{
+ FlagEnd( "exited_intel_room2" )
+
+ FlagWait( "AndersonHologram2Finished" )
+
+ entity lookEnt = GetEntByScriptName( "lookent_pad" )
+ //doTrace degrees minDist timeOut trigger, failsafeFlag )
+ waitthread WaitTillLookingAt( player, lookEnt, false, 30, 0, 0, null, "player_near_intel_room_2_landing_pad" )
+
+ FlagSet( "StartSphereRoomGunship" )
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+██╗ ██╗██╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗
+██║ ██║██║ ██║████╗ ████║██╔â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║
+███████║██║ ██║██╔████╔██║███████║██╔██╗ ██║ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ███████╗█████╗ ███████║██████╔â•â–ˆâ–ˆâ•‘ ███████║
+██╔â•â•â–ˆâ–ˆâ•‘██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â–ˆâ–ˆâ•‘
+██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ â•šâ•â• ██║██║ ██║██║ ╚████║ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║
+â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+██╗ ██╗██╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗
+██║ ██║██║ ██║████╗ ████║██╔â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║
+███████║██║ ██║██╔████╔██║███████║██╔██╗ ██║ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ███████╗█████╗ ███████║██████╔â•â–ˆâ–ˆâ•‘ ███████║
+██╔â•â•â–ˆâ–ˆâ•‘██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â–ˆâ–ˆâ•‘
+██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ â•šâ•â• ██║██║ ██║██║ ╚████║ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║
+â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+/*
+██╗ ██╗██╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗
+██║ ██║██║ ██║████╗ ████║██╔â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║
+███████║██║ ██║██╔████╔██║███████║██╔██╗ ██║ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ███████╗█████╗ ███████║██████╔â•â–ˆâ–ˆâ•‘ ███████║
+██╔â•â•â–ˆâ–ˆâ•‘██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â–ˆâ–ˆâ•‘
+██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ â•šâ•â• ██║██║ ██║██║ ╚████║ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║
+â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+/*
+██╗ ██╗██╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗ ██████╗ ███████╗███████╗███████╗ █████╗ ██████╗ ██████╗██╗ ██╗
+██║ ██║██║ ██║████╗ ████║██╔â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║
+███████║██║ ██║██╔████╔██║███████║██╔██╗ ██║ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ███████╗█████╗ ███████║██████╔â•â–ˆâ–ˆâ•‘ ███████║
+██╔â•â•â–ˆâ–ˆâ•‘██║ ██║██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â–ˆâ–ˆâ•‘
+██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ â•šâ•â• ██║██║ ██║██║ ╚████║ ██║ ██║███████╗███████║███████╗██║ ██║██║ ██║╚██████╗██║ ██║
+â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+void function HumanResearchStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointHumanResearch" ) )
+ vector objectivePos = GetEntByScriptName( "objective_human_research_vista" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function HumanResearchSkipped( entity player )
+{
+ FlagClear( "ShowMobilityGhostHumanLillypad01" )
+ FlagClear( "ShowMobilityGhostHumanLillypad02" )
+ thread ElectricalScreenEffects( player, "inside_human_control_room" )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_HumanResearchThread( entity player )
+{
+
+ FlagWait( "entering_human_main_room" )
+ FlagSet( "ShowMobilityGhostHumanLillypad01" )
+ FlagSet( "ShowMobilityGhostHumanLillypad02" )
+
+ //thread DebugX( player )
+
+ thread FlyersHumanRoom()
+ thread MusicHumanRoom( player )
+ thread DialogueHumanRoomStart( player )
+ vector objectivePos = GetEntByScriptName( "objective_human_research_tower1" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ //thread HumanPanelHacks( player )
+ CheckPoint()
+
+ delaythread ( 60 ) TimeshiftHint( player, TIMEZONE_DAY, "reached_concourse_tower1", "timeshift_hint_default", GetEntByScriptName( "trig_player_near_first_liilypad_puzzle_pristine" ) )
+
+
+ FlagWait( "reached_concourse_tower1" )
+
+ objectivePos = GetEntByScriptName( "objective_human_research_fight" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ thread ElectricalScreenEffects( player, "inside_human_control_room" )
+
+ FlagClear( "ShowMobilityGhostHumanLillypad01" )
+
+
+ CheckPoint()
+
+ FlagWait( "inside_human_control_room" )
+
+ objectivePos = GetEntByScriptName( "objective_human_research_tower2" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ FlagSet( "DisplayTheDamageHint" )
+ thread HumanRoomCheckpointSoldiers()
+ thread HumanRoomCheckpointProwlers()
+
+ CheckPoint()
+
+ //-------------------------------
+ // Token prowlers
+ //-------------------------------
+ array< entity > propSpawners = GetEntArrayByScriptName( "prowler_spawnvents_human_control_room" )
+ float delayMin = 2.5
+ float delayMax = 5
+ int maxToSpawn = 1
+ string flagToAbort = "reached_concourse_tower2"
+ string flagToSetWhenAllAreSpawned = "AllProwlersSpawnedInHumanControlRoom"
+ bool requiresLookAt = true
+ thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, flagToSetWhenAllAreSpawned, delayMin, delayMax, "", requiresLookAt )
+
+ FlagWait( "reached_concourse_tower2" )
+
+ FlagClear( "DisplayTheDamageHint" )
+ FlagClear( "ShowMobilityGhostHumanLillypad02" )
+}
+
+void function FlyersHumanRoom()
+{
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+ FlagSet( "ForceFlyerTakeoff")
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function HumanRoomCheckpointSoldiers()
+{
+ FlagWait( "inside_human_control_room" )
+ if ( Flag( "reached_concourse_tower2") )
+ return
+ FlagEnd( "reached_concourse_tower2" )
+ FlagWait( "human_room_soldiers_dead" )
+ CheckPoint()
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function HumanRoomCheckpointProwlers()
+{
+ FlagWait( "inside_human_control_room" )
+ if ( Flag( "reached_concourse_tower2") )
+ return
+ FlagEnd( "reached_concourse_tower2" )
+
+ FlagWait( "AllProwlersSpawnedInHumanControlRoom" )
+ FlagWaitClear( "prowlers_alive_in_human_control_room")
+
+ CheckPoint()
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicHumanRoom( entity player )
+{
+
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void function GunshipRingSequenceWait( entity player )
+{
+ FlagWait( "crossed_wallrun_chain" )
+
+ wait 0.25
+
+ entity lookEnt = GetEntByScriptName( "lookent_rings" )
+ //doTrace degrees minDist timeOut trigger, failsafeFlag )
+ waitthread WaitTillLookingAt( player, lookEnt, false, 30, 0, 0, null, "player_crossing_human_bridge" )
+
+ FlagSet( "player_looking_at_reactor_window" )
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueHumanRoomStart( entity player )
+{
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+
+ FlagWait( "entering_human_main_room" )
+
+ //wait 1
+ //Player Option A Were they doing experiments on these people?
+ //BT Option A SRS files indicated large-scale cryogenic stasis for testing on humans for unknown reasons. They are likely not volunteers.
+
+ //Player Option B Who are these people?
+ //BT Option B Scanning... The genetic makeup matches those captured from the distant planet Colony led by former Militia leader MacAllan.
+ //waitthread PlayerConversation( "TsHumanExperiments", player )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function HumanPanelHacks( entity player )
+{
+ //--------------------------------------------
+ // Override all security terminals (optional)
+ //----------------------------------------------
+ array< entity > controlPanels
+ controlPanels.append( GetEntByScriptName( "security_panel_concourse_01" ) )
+ //controlPanels.append( GetEntByScriptName( "security_panel_concourse_02" ) )
+ foreach( panel in controlPanels )
+ thread SecurityPanelConcourseThink( panel, player )
+
+
+}
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+void function SecurityPanelConcourseThink( entity panel, entity player )
+{
+ panel.WaitSignal( "PanelReprogram_Success" )
+
+ string flagToSetWhenHacked
+
+ string scriptName = panel.GetScriptName()
+ string laserMeshInstanceName
+ if ( scriptName == "security_panel_concourse_01" )
+ {
+ laserMeshInstanceName = "laser_mesh_concourse_terminal_01"
+ flagToSetWhenHacked = "ConcoursePanelHacked01"
+ }
+ else if ( scriptName == "security_panel_concourse_02" )
+ {
+ laserMeshInstanceName = "laser_mesh_concourse_terminal_02"
+ flagToSetWhenHacked = "ConcoursePanelHacked02"
+ }
+ else
+ Assert( 0, "Can't find lasermesh trigger associated with " + scriptName )
+
+ FlagSet( flagToSetWhenHacked )
+
+ if ( !Flag( "LabRatAcheivementUnlocked" ) )
+ {
+ FlagSet( "LabRatAcheivementUnlocked" )
+ Dev_PrintMessage( player, "#TIMESHIFT_ACHEIVEMENT_HEADING", "#TIMESHIFT_ACHEIVEMENT_TEXT_LAB_RATS", 5 )
+ }
+
+ wait 1.5
+
+ CheckPoint()
+
+ LaserMeshDestroyByInstanceName( laserMeshInstanceName )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_LabRatLasers" )
+
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function HumanPodsThink( string instanceNamePristine, string instanceNameOvergrown, entity player )
+{
+ array< entity > doorsPristine = GetEntArrayByScriptNameInInstance( "bio_pod_door", instanceNamePristine )
+ array< entity > doorsOvergrown = GetEntArrayByScriptNameInInstance( "bio_pod_door", instanceNameOvergrown )
+ Assert( doorsPristine.len() == 4 )
+ Assert( doorsOvergrown.len() == 4 )
+
+ string flagToReleasePrisoners = "security_concourse_01_hacked"
+ //Militia Prisoner 1 What? What where am I?
+ string prisonerDialogue = "diag_sp_humanStudy_TS202_03_01_mcor_prisoner1"
+ if ( instanceNamePristine == "biodoors_terminal02_pristine" )
+ {
+ //Militia Prisoner 2 What's going on? This isn't Colony....
+ string prisonerDialogue = "diag_sp_humanStudy_TS202_04_01_mcor_prisoner2"
+ flagToReleasePrisoners = "security_concourse_02_hacked"
+
+ }
+
+ entity dialogueEnt
+
+ foreach( door in doorsPristine )
+ {
+ if ( !IsValid( dialogueEnt ) )
+ dialogueEnt = CreateLoudspeakerEnt( door.GetOrigin() )
+ thread HumanPodThinkPristine( door )
+ }
+ foreach( door in doorsOvergrown )
+ thread HumanPodThinkOvergrown( door )
+
+ FlagWait( flagToReleasePrisoners )
+
+ foreach( door in doorsOvergrown )
+ door.Signal( "ReleaseLabRat")
+ foreach( door in doorsPristine )
+ door.Signal( "ReleaseLabRat")
+
+ wait 5
+
+ thread PlayTimeShiftDialogue( player, dialogueEnt, prisonerDialogue )
+}
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function HumanPodsThinkNoHack( string instanceNamePristine, string instanceNameOvergrown, entity player )
+{
+ array< entity > spawners = GetEntArrayByScriptNameInInstance( "bio_pod_body", instanceNamePristine )
+ foreach( spawner in spawners )
+ thread HumanPodThinkPristine( null, spawner )
+}
+&*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function HumanPodThinkPristine( entity door = null, entity bodySpawner = null )
+{
+ entity spawner
+ if ( !bodySpawner )
+ spawner = door.GetLinkEnt()
+ else
+ spawner = bodySpawner
+ string animIdle
+ string animWakeUp
+ string animIdleEnd
+ entity labRat = spawner.SpawnEntity()
+ DispatchSpawn( labRat )
+ Assert( IsValid( labRat) )
+ entity node = CreateScriptRef( labRat.GetOrigin(), labRat.GetAngles() )
+ vector originOffset = PositionOffsetFromEnt( node, -15, 0, 0 )
+ node.SetOrigin( originOffset )
+
+ MakeInvincible( labRat )
+ MakeCivilian( labRat )
+ labRat.SetNoTarget( true )
+ SetTeam( labRat, TEAM_MILITIA )
+ labRat.SetModel( LABRAT_MODEL )
+ Assert( labRat.HasKey( "script_noteworthy") )
+ animIdle = labRat.kv.script_noteworthy + "_start_idle"
+ animWakeUp = labRat.kv.script_noteworthy + "_start"
+ animIdleEnd = labRat.kv.script_noteworthy + "_end_idle"
+
+ thread PlayAnimTeleport( labRat, animIdle, node )
+
+ if ( !door )
+ return
+
+ door.WaitSignal( "ReleaseLabRat" )
+
+ waitthread OpenLabRatDoor( door, labRat )
+
+ labRat.EndSignal( "OnDeath" )
+ wait 0.25
+
+ ClearInvincible( labRat )
+ labRat.e.forceRagdollDeath = true
+
+ waitthread PlayAnim( labRat, animWakeUp, node )
+ thread PlayAnim( labRat, animIdleEnd, node )
+}
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function HumanPodThinkOvergrown( entity door )
+{
+ entity body = door.GetLinkEnt()
+
+ door.WaitSignal( "ReleaseLabRat" )
+
+ body.Destroy()
+ OpenLabRatDoor( door, body )
+}
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function OpenLabRatDoor( entity door, entity body )
+{
+ entity soundDummy = CreateInfoTarget( body.GetOrigin() + Vector( 0, 0, 72 ), Vector( 0, 0, 0 ) )
+ vector soundOrigin = soundDummy.GetOrigin()
+ entity lookEnt = CreateInfoTarget( body.GetOrigin() + Vector( 0, 0, 72 ), Vector( 0, 0, 0 ) )
+ vector originOffset = PositionOffsetFromEnt( lookEnt, 15, 0, 0 )
+ lookEnt.SetOrigin( originOffset )
+
+ vector fxOrigin = body.GetOrigin()
+ vector fxAngles = body.GetAngles()
+
+ bool snapToOpen = false
+ if ( GetEntityTimelinePosition( door ) == TIMEZONE_NIGHT )
+ snapToOpen = true
+
+ vector startPos = door.GetOrigin()
+ vector endPos = startPos + Vector( 0, 0, -105 )
+
+ if ( snapToOpen == true )
+ {
+ door.SetOrigin( endPos )
+ return
+ }
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ array <entity> players = GetPlayerArray()
+ if ( players.len() <= 0 )
+ return
+ entity player = players[ 0 ]
+ player.EndSignal( "OnDeath" )
+
+ //WaitTillLookingAt( entity player, entity ent, bool doTrace, float degrees, float minDist = 0, float timeOut = 0, entity trigger = null, string failsafeFlag = "" )
+ waitthread WaitTillLookingAt( player, lookEnt, true, 45, 0, 3 )
+
+ wait( RandomFloatRange( 0, 0.75 ) )
+ entity mover = CreateScriptMover( door.GetOrigin(), door.GetAngles() )
+ door.SetParent( mover )
+ float moveTime = 5
+
+ mover.NonPhysicsMoveTo( endPos, moveTime, moveTime*0.4, moveTime*0.4 )
+
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, soundOrigin, "Timeshift_Scr_LabRatPodDoor_Open" )
+ //EmitSoundOnEntity( soundDummy, "door_open_loop" )
+ PlayFX( FX_HUMAN_DOOR_OPEN, fxOrigin, fxAngles )
+
+ wait moveTime
+
+ //StopSoundOnEntity( soundDummy, "door_open_loop" )
+ //EmitSoundAtPosition( TEAM_UNASSIGNED, soundOrigin, "door_stop" )
+
+}
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ ██████╗ █████╗ ███╗ ███╗██████╗ ██╗ ██╗███████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ████║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—████╗ ██║
+██║ ███████║██╔████╔██║██████╔â•â–ˆâ–ˆâ•‘ ██║███████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•”██╗ ██║
+██║ ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â•â• ██║ ██║╚â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║╚██╗██║
+╚██████╗██║ ██║██║ â•šâ•â• ██║██║ ╚██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•‘ ██║ ██║███████╗ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║██║ ╚████║
+ â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ ██████╗ █████╗ ███╗ ███╗██████╗ ██╗ ██╗███████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ████║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—████╗ ██║
+██║ ███████║██╔████╔██║██████╔â•â–ˆâ–ˆâ•‘ ██║███████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•”██╗ ██║
+██║ ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â•â• ██║ ██║╚â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║╚██╗██║
+╚██████╗██║ ██║██║ â•šâ•â• ██║██║ ╚██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•‘ ██║ ██║███████╗ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║██║ ╚████║
+ â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ ██████╗ █████╗ ███╗ ███╗██████╗ ██╗ ██╗███████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ████║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—████╗ ██║
+██║ ███████║██╔████╔██║██████╔â•â–ˆâ–ˆâ•‘ ██║███████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•”██╗ ██║
+██║ ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â•â• ██║ ██║╚â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║╚██╗██║
+╚██████╗██║ ██║██║ â•šâ•â• ██║██║ ╚██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•‘ ██║ ██║███████╗ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║██║ ╚████║
+ â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â•
+ */
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+ ██████╗ █████╗ ███╗ ███╗██████╗ ██╗ ██╗███████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███╗ ██╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ████║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•â• ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—████╗ ██║
+██║ ███████║██╔████╔██║██████╔â•â–ˆâ–ˆâ•‘ ██║███████╗ ██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•”██╗ ██║
+██║ ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╔â•â–ˆâ–ˆâ•‘██╔â•â•â•â• ██║ ██║╚â•â•â•â•â–ˆâ–ˆâ•‘ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║╚██╗██║
+╚██████╗██║ ██║██║ â•šâ•â• ██║██║ ╚██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•‘ ██║ ██║███████╗ ██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ██║██║ ╚████║
+ â•šâ•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â•
+ */
+/////////////////////////////////////////////////////////////////////////////////////////
+
+
+void function CampusReturnStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointCampusReturn" ) )
+ vector objectivePos = GetEntByScriptName( "objective_human_research_vista" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_LAB_EXPLORE", objectivePos )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function CampusReturnSkipped( entity player )
+{
+ FlagSet( "CampusReturnConversationFinished" )
+ FlagClear( "ShowMobilityGhostHumanWallrunChain" )
+ FlagClear( "ShowMobilityGhostPowertech" )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_CampusReturnThread( entity player )
+{
+ FlagWait( "reached_concourse_tower2" )
+
+ vector objectivePos = GetEntByScriptName( "objective_human_research_vista" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ thread ObjectiveRemindUntilFlag( "crossed_wallrun_chain" )
+
+ FlagSet( "ShowMobilityGhostHumanWallrunChain" )
+ FlagSet( "ShowMobilityGhostPowertech" )
+
+ CheckPoint()
+ thread ArtifactLaunchThink( player )
+ thread BridgeThink()
+ thread MusicCampusReturn( player )
+ thread GunshipRingSequenceWait( player )
+ thread GunshipSequence( "gunship_rings", player, "node_gunship_rings", "rings", "player_looking_at_reactor_window" )
+
+ FlagWait( "crossed_wallrun_chain" )
+
+ wait 0.25
+
+ //Checkpoint done with a trigger with safe spot here
+
+ objectivePos = GetEntByScriptName( "objective_return_breadcrumb00" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ thread DialogueHumanRoomEnd( player )
+
+ FlagSet( "finishedHumanVistaSequence" )
+
+
+ thread HumanBridgeEnemies( player )
+ thread HumanBridgeOvergrownThink( player )
+
+
+ FlagWait( "entered_powertech_return" )
+
+ objectivePos = GetEntByScriptName( "objective_return_breadcrumb01" ).GetOrigin()
+ TimeshiftSetObjective( player, "#TIMESHIFT_OBJECTIVE_RETURN", objectivePos )
+
+ CheckPoint()
+
+ FlagWait( "approaching_fan_drop" )
+}
+
+void function BridgeThink()
+{
+ FlagWait( "bridge_control_panel_pressed" )
+ FlagClear( "retract_bridge_human_01" )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function HumanBridgeOvergrownThink( entity player )
+{
+ //Show it when enemies extend it in other time zone
+ FlagWaitClear( "retract_bridge_human_01" )
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ ShowStuff( "human_bridge_overgrown" )
+
+
+ //Hide it again if player manually retracts it
+ //FlagWait( "retract_bridge_human_01" )
+
+ //waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ //HideStuff( "human_bridge_overgrown" )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicCampusReturn( entity player )
+{
+ FlagWait( "crossing_wallrun_chain" )
+
+ StopMusic()
+ PlayMusic( "music_timeshift_27_towardthemachine" )
+
+ FlagWait( "spawnHumanBridgeEnemies" )
+
+ //Only play drone spawn music if player is actually in the trigger spawning the drones
+
+ if ( !Flag( "player_touching_drone_spawn_area_bridge_pristine") )
+ return
+
+ StopMusic()
+ PlayMusic( "music_timeshift_28_bridge" )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void function ArtifactLaunchThink( entity player )
+{
+ entity arkShell = GetEntByScriptName( "core_model_pristine" )
+ entity arkSphere = GetEntByScriptName( "core_glow_model_pristine" )
+ vector startPos = arkShell.GetOrigin()
+ vector startAng = arkShell.GetAngles()
+ vector endPos = GetEntByScriptName( "artifact_ring_startpoint" ).GetOrigin()
+
+ entity fxLaunchGlow = PlayLoopFXOnEntity( FX_ARK_LAUNCH_GLOW, arkShell )
+ entity mover = CreateScriptMover( startPos, startAng )
+ arkSphere.SetParent( arkShell )
+ arkShell.SetParent( mover )
+ float moveTime = 2
+
+ FlagWait( "crossed_wallrun_chain" )
+
+ wait 0.25
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+ wait 0.1
+
+ mover.NonPhysicsMoveTo( endPos, moveTime, moveTime*0.4, moveTime*0.4 )
+ EmitSoundOnEntity( player, "timeshift_scr_core_rise_start" )
+
+ wait moveTime
+
+ entity fxLaunchInPlace = PlayFXOnEntity( FX_ARK_LAUNCH_IN_PLACE, arkShell )
+
+ //StopSoundOnEntity( player, "timeshift_scr_core_rise_start" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, endPos, "timeshift_scr_core_rise_end" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, endPos, "timeshift_scr_core_pulse" )
+ //CreateShake( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
+ thread CreateAirShake( startPos, 32, 200, 1.5, 10000 )
+ thread CreateShakeWhileFlagSet( 0.5, 0.5, 1, "player_near_rings", "DoneWithFanDropSequence" )
+
+ FlagSet( "RingVistaSequenceComplete" )
+
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueHumanRoomEnd( entity player )
+{
+
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+ FlagWait( "crossed_wallrun_chain" )
+
+ wait 0.25
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ //Scientist 5 PA Ark successfully delivered to fold weapon rings. Commencing test run.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_humanStudy_TS203_01_01_imc_sci" )
+
+ //FlagWaitAny( "human_bridge_soldiers_dead", "entered_powertech_return" )
+
+ FlagWaitAny( "RingVistaSequenceComplete", "player_crossing_human_bridge" )
+
+
+ //BT: Pilot, the rings at my location contain a large amount of residual energy. This was the Ark's final destination.
+ waitthread PlayBTDialogue( "diag_sp_targeting_TS231_01_01_mcor_bt" )
+
+ //BT: Anderson's plan indicated a recon mission within close proximity to the center of the active rings.
+ waitthread PlayBTDialogue( "diag_sp_targeting_TS231_07_01_mcor_bt" )
+
+ if ( !Flag( "approaching_fan_drop" ) )
+ {
+ // Player (Option A) You want me to do what?
+ // Player (Option B) Are you sure I'll be safe over there? (diag_sp_pristine_TS242_03_01_mcor_player)
+
+ // BT (Option A) If we can obtain the Ark's energy signature, the Militia fleet will be able to track its current location in the present day.
+ // BT (Option B) No. But a true Militia Pilot takes risks for the welfare of others. As did Major Anderson and Captain Lastimosa before you. (diag_sp_pristine_TS242_04_01_mcor_bt)
+
+ waitthread PlayerConversationStopOnFlag( "TsSeeingCoreFlownToRings", player, "entered_powertech_return" )
+ }
+
+ FlagSet( "CampusReturnConversationFinished" )
+
+ FlagSet( "spawnHumanBridgeEnemies" )
+
+
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function HumanBridgeEnemies( entity player )
+{
+ FlagWait( "finishedHumanVistaSequence" )
+
+ entity lookEnt = GetEntByScriptName( "look_ent_human_bridge" )
+ entity trigger = GetEntByScriptName( "trig_player_at_human_vista_pristine" )
+ //doTrace degrees minDist timeOut trigger, failsafeFlag )
+ waitthread WaitTillLookingAt( player, lookEnt, true, 30, 0, 0, trigger, "player_crossing_human_bridge" )
+
+
+ //entity triggerBridgeVolume = GetEntByScriptName( "trigger_bridge_human_volume" )
+ //entity bridge = GetEntByScriptName( "human_bridge" )
+ //FlagSet( "spawnHumanBridgeEnemies" )
+
+ /*
+ //FlagClearDelayed( "retract_bridge_human_01", 3 )
+
+ FlagWait( "retract_bridge_control_panel_pressed" )
+ FlagSet( "retract_bridge_human_01" )
+ bridge.NotSolid()
+
+ SetGlobalForcedDialogueOnly( true )
+ bool screamPlayed = false
+ array<entity> ai = GetNPCArrayByClass( "npc_soldier" )
+ entity tempNode
+ float delayTime = 0
+ string floorDropAnim = "pt_react_bridgedrop_A"
+ foreach( guy in ai )
+ {
+ if ( !triggerBridgeVolume.IsTouching( guy ) )
+ continue
+
+ if ( IsAlive( guy ) )
+ {
+ //guy.NotSolid()
+ if ( screamPlayed == false )
+ {
+ EmitSoundOnEntity( guy, "Grunt_DroppedByFlyer" )
+ screamPlayed = true
+ }
+ if ( CoinFlip() )
+ floorDropAnim = "pt_react_bridgedrop_B"
+ tempNode = CreateScriptMover( guy.GetOrigin(), guy.GetAngles() )
+ guy.SetParent( tempNode )
+ delayTime = RandomFloatRange( 0, 0.3 )
+ delaythread ( delayTime ) PlayAnim( guy, floorDropAnim, tempNode )
+ //guy.TakeDamage( guy.GetHealth() / 2, null, null, { damageSourceId=damagedef_suicide } )
+ }
+ }
+ if ( screamPlayed == true )
+ {
+ delaythread ( 2 ) Dev_PrintMessage( player, "#TIMESHIFT_ACHEIVEMENT_HEADING", "#TIMESHIFT_ACHEIVEMENT_TEXT_BRIDGE_FALL", 5 )
+ }
+
+ wait 1
+
+ SetGlobalForcedDialogueOnly( false )
+
+ DestroyIfValid( "human_bridge" )
+ */
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•‘ ██║██████╔â•
+██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•
+██║ ██║ ██║██║ ╚████║ ██████╔â•â–ˆâ–ˆâ•‘ ██║╚██████╔â•â–ˆâ–ˆâ•‘
+â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•‘ ██║██████╔â•
+██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•
+██║ ██║ ██║██║ ╚████║ ██████╔â•â–ˆâ–ˆâ•‘ ██║╚██████╔â•â–ˆâ–ˆâ•‘
+â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•‘ ██║██████╔â•
+██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•
+██║ ██║ ██║██║ ╚████║ ██████╔â•â–ˆâ–ˆâ•‘ ██║╚██████╔â•â–ˆâ–ˆâ•‘
+â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•‘ ██║██████╔â•
+██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â•
+██║ ██║ ██║██║ ╚████║ ██████╔â•â–ˆâ–ˆâ•‘ ██║╚██████╔â•â–ˆâ–ˆâ•‘
+â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void function FanDropStartPointSetup( entity player )
+{
+ //entity temp = CreateInfoTarget( Vector( 3536, -4641, -127), Vector( 35.5, -90, 0 ) )
+ //TeleportPlayerToEnt( player, temp )
+
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointFanDrop" ) )
+ vector objectivePos = GetEntByScriptName( "objective_concourse_panel" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_RETURN", objectivePos )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function FanDropSkipped( entity player )
+{
+ CleanupEnts( "rings_pristine" )
+ FlagSet( "approaching_fan_drop" )
+ FlagSet( "DoneWithFanDropSequence" )
+ thread QuickSkit( player, GetEntByScriptName( "node_labC_deathpose_overgrown" ), "approaching_fan_drop" )
+ thread QuickSkit( player, GetEntByScriptName( "node_labC_deathpose_pristine" ), "approaching_fan_drop" )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_FanDropThread( entity player )
+{
+ FlagWait( "approaching_fan_drop" )
+
+ thread MusicFanDrop( player )
+ thread FanDropTeleport( player )
+ thread QuickSkit( player, GetEntByScriptName( "node_labC_deathpose_overgrown" ), "approaching_fan_drop" )
+ thread QuickSkit( player, GetEntByScriptName( "node_labC_deathpose_pristine" ), "approaching_fan_drop" )
+ thread FanDropLanding( player )
+
+ vector objectivePos = GetEntByScriptName( "objective_return_breadcrumb01" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+
+
+ FlagWait( "doing_fan_drop_pristine" )
+
+
+ while( true )
+ {
+ FlagWait( "exited_fan_drop" )
+
+ WaitEndFrame() //even if player hit the "exited_fan_drop", he might already be starting a quickDeath
+
+ if ( player.p.doingQuickDeath )
+ {
+ printl( "player doing quickDeath...waiting 2 secs")
+ wait 2
+ printl( "Waiting to exit fan drop..." )
+ continue
+ }
+ else
+ {
+ if ( IsAlive( player ) )
+ thread FanDropQuickdeathFailsafe( player )
+ break
+ }
+
+ }
+
+
+ FlagSet( "DoneWithFanDropSequence" )
+
+}
+
+void function FanDropQuickdeathFailsafe( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ //If player gets into a state where the game thinks he has exited the fan sequence, but is in fact doing a quick death, restart from last checkpoint
+ wait 0.5
+ if ( player.p.doingQuickDeath )
+ player.TakeDamage( 9999, null, null, { damageSourceId=damagedef_suicide } )
+}
+
+void function FanDropLanding( entity player )
+{
+
+ entity node = GetEntByScriptName( "checkpointFanDropEnd" )
+ entity nodePristine = CreateScriptRef( node.GetOrigin() + Vector( 0, 0, TIME_ZOFFSET ), node.GetAngles() )
+
+ FlagWait( "DoneWithFanDropSequence" )
+
+
+
+ printl( "Done with fan drop...playing land anim" )
+
+ player.FreezeControlsOnServer()
+ level.allowTimeTravel = false
+ ShowStuff( "blocker_fandrop_pristine" )
+ ShowStuff( "blocker_fandrop_overgrown" )
+
+ player.DisableWeapon()
+ player.ForceStand()
+
+
+ waitthread AdjustPlayerTimelineHack( player, true )
+
+ WaitFrame()
+ if ( GetEntityTimelinePosition( player ) == TIMEZONE_NIGHT )
+ waitthread PlayerDropLand( player, node, true )
+ else
+ waitthread PlayerDropLand( player, nodePristine, true )
+
+ waitthread AdjustPlayerTimelineHack( player )
+
+ wait 0.5
+ player.UnfreezeControlsOnServer()
+ level.allowTimeTravel = true
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AdjustPlayerTimelineHack( entity player, bool isBeforePlayerLandAnim = false )
+{
+ vector newPos
+ var playerCurrentTimeline = GetEntityTimelinePosition( player )
+
+ if ( playerCurrentTimeline != level.timeZone )
+ {
+ printl( "*********WARNING***************" )
+ printl( "Rare bug: Player timeline and level timeline don't match....adjusting player pos" )
+ printl( "Adjusting before player land animation: " + isBeforePlayerLandAnim )
+ printl( "*******************************" )
+ newPos = GetPosInOtherTimeline( player.GetOrigin() )
+ player.SetOrigin( newPos )
+ }
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function FanDropTeleport( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ FlagEnd( "DoneWithFanDropSequence" )
+ entity orgFanDropOvergrownMain = GetEntByScriptName( "fandrop_start_overgrown_main" )
+ entity orgFanDropOvergrownAlt = GetEntByScriptName( "fandrop_start_overgrown_alt" )
+ vector vectorOffset = orgFanDropOvergrownAlt.GetOrigin() - orgFanDropOvergrownMain.GetOrigin()
+
+
+ while( true )
+ {
+ //wait till player timeshifts past first fire
+ FlagWait( "doing_fan_drop_pristine" )
+
+ //now wait till he switches back to dodge the spinning fan
+ FlagWait( "past_first_spinning_fan_and_back_in_overgrown" )
+ //WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+
+ //if we haven't died yet, do the teleport while in overgrown
+ if ( CanDoFanTeleport( player, vectorOffset ) )
+ player.SetOrigin( player.GetOrigin() + vectorOffset )
+
+ //otherwise, we died, wait for player to be in reset pos
+ else
+ FlagWait( "standing_at_top_of_fan" )
+
+ }
+
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+bool function CanDoFanTeleport( entity player, vector vectorOffset )
+{
+ //trying to teleport while in overgrown
+
+ if ( !IsValid( player ) )
+ return false
+
+ player.EndSignal( "OnDeath" )
+
+ if ( Flag( "standing_at_top_of_fan" ) )
+ return false
+ if ( !Flag( "dropping_down_fan" ) )
+ return false
+ if ( player.p.doingQuickDeath )
+ return false
+ if ( GetEntityTimelinePosition( player ) != TIMEZONE_NIGHT )
+ return false
+ if ( GetTimelinePosition( player.GetOrigin() + vectorOffset ) != TIMEZONE_NIGHT )
+ return false
+
+ return true
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicFanDrop( entity player )
+{
+ if ( Flag( "StartAndersonHologram3") )
+ return
+
+ FlagEnd( "StartAndersonHologram3")
+
+ OnThreadEnd(
+ function() : ()
+ {
+ FlagSet( "BrokeOutOfFanDropMusicLoop")
+ }
+ )
+ while( true )
+ {
+ FlagWait( "dropping_down_fan" )
+ StopMusic()
+ PlayMusic( "music_timeshift_29_downtherabbithole" )
+ wait 2
+ FlagWait( "standing_at_top_of_fan" )
+ StopMusic()
+
+ }
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███╗ ██╗██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â•â•â•â–ˆâ–ˆâ–ˆâ–ˆâ•— ██║██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•‘ ██║██████╔╠█████╗ ██╔██╗ ██║██║ ██║
+██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â• ██╔â•â•â• ██║╚██╗██║██║ ██║
+██║ ██║ ██║██║ ╚████║ ██████╔â•â–ˆâ–ˆâ•‘ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ███████╗██║ ╚████║██████╔â•
+â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•â•â•šâ•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███╗ ██╗██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â•â•â•â–ˆâ–ˆâ–ˆâ–ˆâ•— ██║██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•‘ ██║██████╔╠█████╗ ██╔██╗ ██║██║ ██║
+██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â• ██╔â•â•â• ██║╚██╗██║██║ ██║
+██║ ██║ ██║██║ ╚████║ ██████╔â•â–ˆâ–ˆâ•‘ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ███████╗██║ ╚████║██████╔â•
+â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•â•â•šâ•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗███╗ ██╗██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—████╗ ██║ ██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â•â•â•â–ˆâ–ˆâ–ˆâ–ˆâ•— ██║██╔â•â•â–ˆâ–ˆâ•—
+█████╗ ███████║██╔██╗ ██║ ██║ ██║██████╔â•â–ˆâ–ˆâ•‘ ██║██████╔╠█████╗ ██╔██╗ ██║██║ ██║
+██╔â•â•â• ██╔â•â•â–ˆâ–ˆâ•‘██║╚██╗██║ ██║ ██║██╔â•â•â–ˆâ–ˆâ•—██║ ██║██╔â•â•â•â• ██╔â•â•â• ██║╚██╗██║██║ ██║
+██║ ██║ ██║██║ ╚████║ ██████╔â•â–ˆâ–ˆâ•‘ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ███████╗██║ ╚████║██████╔â•
+â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•â•â•šâ•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+
+
+void function FanDropEndStartPointSetup( entity player )
+{
+ TeleportPlayerToEnt( player, GetEntByScriptName( "checkpointFanDropEnd" ) )
+ vector objectivePos = GetEntByScriptName( "objective_concourse_panel" ).GetOrigin()
+ TimeshiftSetObjectiveSilent( player, "#TIMESHIFT_OBJECTIVE_RETURN", objectivePos )
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function FanDropEndSkipped( entity player )
+{
+
+ CleanupEnts( "triggers_instadeath_humanroom" )
+ CleanupEnts( "triggers_quickdeath_humanroom" )
+ CleanupEnts( "trigger_quickdeath_checkpoint_humanroom" )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AA_FanDropEndThread( entity player )
+{
+ FlagWait( "DoneWithFanDropSequence" )
+
+
+ CleanupEnts( "rings_pristine" )
+ CleanupEnts( "triggers_instadeath_humanroom" )
+ CleanupEnts( "triggers_quickdeath_humanroom" )
+ CleanupEnts( "trigger_quickdeath_checkpoint_humanroom" )
+
+ Remote_CallFunction_Replay( player, "ServerCallback_ShowHologramTitles" )
+ thread MusicFanDropEnd( player)
+ thread StalkerDoorSequences( player )
+ thread DialogueLabCharlie( player )
+ thread DialogueTransitionHall( player )
+ delaythread ( 1 ) AndersonHologramSequence( player, "node_hologram_lab3", "StartAndersonHologram3" )
+
+ vector objectivePos = GetEntByScriptName( "objective_concourse_panel_breadcrumb_01" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+
+ wait 2
+
+ CheckPoint()
+
+
+ FlagWait( "exited_intel_room3" )
+
+ objectivePos = GetEntByScriptName( "objective_concourse_panel" ).GetOrigin()
+ TimeshiftUpdateObjective( player, objectivePos )
+
+ //-------------------------------
+ // Token overgrown/pristine wall Spectres
+ //-------------------------------
+ array< entity > propSpawners = GetEntArrayByScriptNameInInstance( "spectre_door_spawner", "spectre_spawner_return_overgrown" )
+ float delayMin = 0
+ float delayMax = 0.4
+ int maxToSpawn = 1
+ string flagToAbort = "transition_hallway_return_finished"
+ string flagToSetWhenSpectreSpawned = ""
+ bool requiresLookAt = true
+ thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, "", delayMin, delayMax, flagToSetWhenSpectreSpawned, requiresLookAt )
+
+ propSpawners = GetEntArrayByScriptNameInInstance( "spectre_door_spawner", "spectre_spawner_return_pristine" )
+ delayMin = 0
+ delayMax = 0.4
+ maxToSpawn = 1
+ flagToAbort = "transition_hallway_return_finished"
+ flagToSetWhenSpectreSpawned = ""
+ requiresLookAt = true
+ thread SpawnShowcaseGroupWhenInRange( player, propSpawners, maxToSpawn, flagToAbort, "", delayMin, delayMax, flagToSetWhenSpectreSpawned, requiresLookAt )
+
+
+ FlagWait( "transition_hallway_return_finished" )
+
+
+
+ //ScreenFadeToBlack( player, 1.5, 5 )
+ //wait 1.5
+ //player.SetInvulnerable()
+ //player.FreezeControlsOnServer()
+
+ thread TransitionSpoke1()
+}
+
+
+void function TransitionSpoke1()
+{
+ LevelTransitionStruct trans = SaveBoyleAudioLogs()
+ if ( level.timeZone == TIMEZONE_NIGHT )
+ trans.timeshiftMostRecentTimeline = 1
+ else
+ trans.timeshiftMostRecentTimeline = 0
+
+ PickStartPoint( "sp_hub_timeshift", "PRISTINE CAMPUS", trans )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function MusicFanDropEnd( entity player )
+{
+ FlagWait( "StartAndersonHologram3" )
+ FlagWait( "BrokeOutOfFanDropMusicLoop" )
+
+ //FlagWait( "AndersonHologram3Playing" )
+
+ //StopMusic()
+ PlayMusicThatCantBeStopped( "music_timeshift_30_andersonlog04")
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function StalkerDoorSequences( entity player )
+{
+
+ thread QuickSkit( player, GetEntByScriptName( "node_intel_room3_stalker_door_overgrown" ), "player_near_intel3_exit", GetEntByScriptName( "intel3_exit_overgrown_look_ent" ), GetEntByScriptName( "trig_approaching_lab3_exit_overgrown" ) )
+ thread QuickSkit( player, GetEntByScriptName( "node_intel_room3_stalker_door_pristine" ), "player_near_intel3_exit", GetEntByScriptName( "intel3_exit_pristine_look_ent" ), GetEntByScriptName( "trig_approaching_lab3_exit_pristine" ) )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueLabCharlie( entity player )
+{
+
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+ FlagWait( "DoneWithFanDropSequence" )
+
+ wait 1.5
+
+ FlagSet( "StartAndersonHologram3" )
+
+
+ FlagWaitAny( "AndersonHologram3Finished", "exited_intel_room3" )
+
+ wait 0.75
+
+ //That was Major Anderson's final recording.
+ waitthread PlayBTDialogue( "diag_sp_targeting_TS232_02_01_mcor_bt" )
+
+ while( true )
+ {
+ wait 0.25
+ if ( IsAudioLogPlaying( player ) )
+ continue
+
+ else
+ {
+ //Cooper, based on your recon of this facility, I may have a plan. Meet me outside. diag_sp_targeting_TS232_03_01_mcor_bt
+ waitthread PlayBTDialogue( "diag_sp_targeting_TS232_03_01_mcor_bt" )
+ Objective_Remind()
+ break
+ }
+ }
+
+ thread ObjectiveRemindUntilFlag( "transition_hallway_return_finished" )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DialogueTransitionHall( entity player )
+{
+
+
+/*
+██████╗ ██╗ █████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗███████╗
+██╔â•â•â–ˆâ–ˆâ•—██║██╔â•â•â–ˆâ–ˆâ•—██║ ██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â• ██║ ██║██╔â•â•â•â•â•
+██║ ██║██║███████║██║ ██║ ██║██║ ███╗██║ ██║█████╗
+██║ ██║██║██╔â•â•â–ˆâ–ˆâ•‘██║ ██║ ██║██║ ██║██║ ██║██╔â•â•â•
+██████╔â•â–ˆâ–ˆâ•‘██║ ██║███████╗╚██████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•—
+â•šâ•â•â•â•â•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â•â•
+*/
+
+
+ FlagWaitAny( "player_near_intel3_exit_overgrown", "player_near_intel3_exit_pristine" )
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ wait 1.5
+
+ //Security - PA Attention. Automated security personnel have now been deployed in all non-combatant sectors.
+ //Please display security credentials clearly to avoid accidental termination. Thank you.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_targeting_TS231_12_01_imc_facilityPA" )
+
+ wait 3
+
+ soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ //Security -PA Automated security personnel: Please target all non-IMC military subjects.
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_targeting_TS231_11_01_imc_facilityPA" )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—
+███████╗███████║███████║██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║
+â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██║ ██║
+███████║██║ ██║██║ ██║██║ ██║███████╗██████╔â•
+â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—
+███████╗███████║███████║██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║
+â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██║ ██║
+███████║██║ ██║██║ ██║██║ ██║███████╗██████╔â•
+â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—
+███████╗███████║███████║██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║
+â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██║ ██║
+███████║██║ ██║██║ ██║██║ ██║███████╗██████╔â•
+â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗
+██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â•â•â•â–ˆâ–ˆâ•”â•â•â–ˆâ–ˆâ•—
+███████╗███████║███████║██████╔â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•— ██║ ██║
+â•šâ•â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•‘██╔â•â•â–ˆâ–ˆâ•—██╔â•â•â• ██║ ██║
+███████║██║ ██║██║ ██║██║ ██║███████╗██████╔â•
+â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â•â•â•â•â•
+
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void function OnSpawnedPropDynamic( entity propDynamic )
+{
+ string scriptName = propDynamic.GetScriptName()
+
+ if ( scriptName == "flyer_lab" )
+ thread LabCreatureThink( propDynamic )
+
+ if ( scriptName == "labrats_idlers" )
+ thread LabRatIdlerThink( propDynamic )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function LabRatIdlerThink( entity propDynamic )
+{
+ entity node = CreateScriptRef( propDynamic.GetOrigin(), propDynamic.GetAngles() )
+ vector originOffset = PositionOffsetFromEnt( node, -15, 0, 0 )
+ node.SetOrigin( originOffset )
+ thread PlayAnimTeleport( propDynamic, propDynamic.kv.script_noteworthy, node )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function OnSpawnedLevelNPC( entity npc )
+{
+ string scriptName = npc.GetScriptName()
+
+ if ( scriptName == "lab_prowlers" )
+ {
+ thread LabCreatureThink( npc )
+
+ }
+
+ if ( scriptName == "soldiers_human_room_fight_01" )
+ AttackPlayer( npc )
+
+
+ //if ( scriptName == "doctor_science_intel_room_01" )
+ //thread LabAlphaDoctorScienceThink( npc )
+
+ if ( scriptName == "civilian_walker_evac_elevator_labs" )
+ thread LabAlphaScientistThink( npc )
+
+ if ( scriptName == "labA_dudes" )
+ thread LabAlphaScientistThink( npc )
+
+ if ( scriptName == "grunts_elevator" )
+ thread GruntElevatorThink( npc )
+
+ if ( scriptName == "prowlers_courtyard" )
+ thread ProwlersAmbientThink( npc )
+
+ if ( scriptName == "courtyard_responders" )
+ thread CourtyardSoldiersThink( npc )
+
+ if ( scriptName == "civilian_walker_courtyard" )
+ thread CourtyardSoldiersThink( npc )
+
+ if ( scriptName == "creature_surprised_dudes" )
+ thread CreatureSurprisedSoldiersThink( npc )
+
+ if ( scriptName == "elevator_turret_dudes" )
+ thread HallwayTurretDudesThink( npc )
+
+ if ( scriptName == "sphere_room_dudes" )
+ thread GruntsSphereRoomThink( npc )
+
+ if ( scriptName == "first_timeshift_soldiers" )
+ AttackPlayer( npc )
+
+
+
+}
+
+
+void function LabCreatureThink( entity creature )
+{
+ creature.EndSignal( "PlayerInsideCage" )
+ creature.EndSignal( "OnDeath" )
+ creature.EndSignal( "OnDestroy" )
+
+ thread AnimalCrueltyAcheivement( creature )
+ wait 1 //need to wait for player to spawn, otherwise it may early out
+
+ if ( creature.IsNPC() )
+ creature.SetNoTarget( true )
+
+ entity trigger
+ entity node
+ array <entity> linkedEnts = creature.GetLinkEntArray()
+ foreach ( entity ent in linkedEnts )
+ {
+ if ( ent.GetClassName() == "script_ref" )
+ {
+ node = ent
+ continue
+ }
+ if ( ent.GetClassName() == "trigger_multiple" )
+ {
+ trigger = ent
+ continue
+ }
+ }
+ //if ( creature.IsNPC() )
+ //Assert( IsValid( trigger ), "creature at " + creature.GetOrigin() + " is not linked to a trigger" )
+
+ Assert( IsValid( node ), "creature at " + creature.GetOrigin() + " is not linked to a node" )
+
+
+ string animIdle = ""
+ string animBreakout = ""
+ string animAggroLoop = ""
+
+ switch ( node.GetScriptName() )
+ {
+ case "variant01":
+ animIdle = "pr_timeshift_caged_sleeping_01"
+ animBreakout = "pr_timeshift_caged_sleeping_01_interrupt"
+ animAggroLoop = "pr_timeshift_caged_small_aggro_01"
+ break
+ case "variant02":
+ animIdle = "pr_timeshift_caged_sleeping_02"
+ animBreakout = "pr_timeshift_caged_sleeping_02_interrupt"
+ animAggroLoop = "pr_timeshift_caged_small_aggro_02"
+ break
+ case "variant03":
+ animIdle = "pr_timeshift_caged_sleeping_03"
+ animBreakout = "pr_timeshift_caged_sleeping_03_interrupt"
+ animAggroLoop = "pr_timeshift_caged_small_aggro_03"
+ break
+ case "variant04":
+ animIdle = "pr_timeshift_caged_laying_01"
+ animBreakout = "pr_timeshift_caged_laying_01_interrupt"
+ animAggroLoop = "pr_timeshift_caged_small_aggro_04"
+ break
+ case "variant05":
+ animIdle = "pr_timeshift_caged_sitting_01"
+ animBreakout = "pr_timeshift_caged_sitting_01_interrupt"
+ animAggroLoop = "pr_timeshift_caged_small_aggro_05"
+ break
+ case "variant06":
+ animIdle = "pr_timeshift_caged_standing_01"
+ animBreakout = "pr_timeshift_caged_standing_01_interrupt"
+ animAggroLoop = "pr_timeshift_caged_small_aggro_03"
+ break
+ case "variant07":
+ animIdle = "pr_timeshift_caged_long_14"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterA1":
+ animIdle = "pr_timeshift_caged_long_06"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterA2":
+ animIdle = "pr_timeshift_caged_long_07"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterA3":
+ animIdle = "pr_timeshift_caged_long_08"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterA4":
+ animIdle = "pr_timeshift_caged_long_09"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterA5":
+ animIdle = "pr_timeshift_caged_long_10"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterA6":
+ animIdle = "pr_timeshift_caged_long_11"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterA7":
+ animIdle = "pr_timeshift_caged_long_12"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterA8":
+ animIdle = "pr_timeshift_caged_long_13"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterB1":
+ animIdle = "pr_timeshift_caged_long_01"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterB2":
+ animIdle = "pr_timeshift_caged_long_02"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterB3":
+ animIdle = "pr_timeshift_caged_long_03"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterB4":
+ animIdle = "pr_timeshift_caged_long_04"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantCageTogeterB5":
+ animIdle = "pr_timeshift_caged_long_05"
+ animBreakout = ""
+ animAggroLoop = ""
+ break
+ case "variantLab01":
+ animIdle = "fl_timeshift_caged_tall_laying_01"
+ animBreakout = "fl_timeshift_caged_tall_interrupt_01"
+ animAggroLoop = "fl_timeshift_caged_tall_aggro_01"
+ break
+ case "variantLab02":
+ animIdle = "fl_timeshift_caged_tall_laying_02"
+ animBreakout = "fl_timeshift_caged_tall_interrupt_02"
+ animAggroLoop = "fl_timeshift_caged_tall_aggro_02"
+ break
+ case "variantLab03":
+ animIdle = "fl_timeshift_caged_tall_laying_03"
+ animBreakout = "fl_timeshift_caged_tall_interrupt_03"
+ animAggroLoop = "fl_timeshift_caged_tall_aggro_03"
+ break
+ case "variantLab04":
+ animIdle = "fl_timeshift_caged_tall_laying_04"
+ animBreakout = "fl_timeshift_caged_tall_interrupt_04"
+ animAggroLoop = "fl_timeshift_caged_tall_aggro_04"
+ break
+ case "variantCylinder01":
+ animIdle = "fl_timeshift_caged_cylinder_01"
+ animBreakout = ""
+ animAggroLoop = ""
+ thread PlayDeathAnimWhenShot( creature, "fl_timeshift_caged_cylinder_death_01" )
+ break
+ case "variantCylinder02":
+ animIdle = "fl_timeshift_caged_cylinder_02"
+ animBreakout = ""
+ animAggroLoop = ""
+ thread PlayDeathAnimWhenShot( creature, "fl_timeshift_caged_cylinder_death_01" )
+ break
+ default:
+ Assert( 0, "creatureNode at " + node.GetOrigin() + " has an unhandled script_name" )
+
+ }
+
+ //-------------------------------------------------------------
+ // Go back into AI if this thread ends (player inside cage)
+ //--------------------------------------------------------------
+
+ OnThreadEnd(
+ function() : ( creature )
+ {
+ if ( !IsAlive( creature ) )
+ return
+ creature.Anim_Stop()
+ }
+ )
+
+ //------------------------------------------
+ // Play chill idle
+ //------------------------------------------
+ thread PlayAnimTeleport( creature, animIdle, node )
+
+ array <entity> players = GetPlayerArray()
+ if ( players.len() <= 0 )
+ return
+ entity player = players[ 0 ]
+
+ if ( ( creature.IsNPC() ) && ( trigger != null ) )
+ thread LabProwlerPlayerInsideCage( creature, trigger, player )
+
+ while( !PlayerInRange( player.GetOrigin(), creature.GetOrigin(), 256 ) )
+ wait 0.2
+
+ wait ( RandomFloatRange( 0, 0.8 ) )
+
+ //------------------------------------------
+ // Player close, breakout into aggro idle
+ //------------------------------------------
+ if ( animBreakout != "" )
+ waitthread PlayAnim( creature, animBreakout, node )
+
+ if ( animAggroLoop != "" )
+ thread PlayAnim( creature, animAggroLoop, node )
+
+
+ WaitForever()
+}
+
+
+void function AnimalCrueltyAcheivement( creature )
+{
+ if ( Flag( "AcheivementUnlockedLabProwler") )
+ return
+ if ( !creature.IsNPC() )
+ return
+ if ( creature.GetClassName() != "npc_prowler" )
+ return
+
+ if ( Flag( "AcheivementUnlockedLabProwler") )
+ return
+
+ FlagEnd( "AcheivementUnlockedLabProwler" )
+
+
+ //To do: detect if killed by the player
+ //creature.WaitSignal( "OnDeath" )
+
+
+}
+void function PlayDeathAnimWhenShot( entity propCreature, string anim )
+{
+
+
+
+
+}
+
+void function LabProwlerPlayerInsideCage( entity prowler, entity trigger, entity player )
+{
+ prowler.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDeath" )
+
+ trigger.WaitSignal( "OnTrigger" )
+
+ prowler.Signal( "PlayerInsideCage" )
+
+}
+
+
+void function PlayMusicInTimezoneUntilFlag( string track, var timeZone, string flagToAbort )
+{
+ if ( Flag( flagToAbort ) )
+ return
+
+ FlagEnd( flagToAbort )
+
+ var otherTimeZone
+ if ( timeZone == TIMEZONE_NIGHT )
+ otherTimeZone = TIMEZONE_DAY
+ else
+ otherTimeZone = TIMEZONE_NIGHT
+
+ while( true )
+ {
+
+ waitthread WaittillPlayerSwitchesTimezone( timeZone )
+
+ StopMusic()
+ PlayMusic( track )
+
+ waitthread WaittillPlayerSwitchesTimezone( otherTimeZone )
+ }
+}
+
+
+
+
+void function DebugX( entity player )
+{
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ entity soundEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+
+ waitthread PlayTimeShiftDialogue( player, soundEnt, "diag_sp_wildlifeStudy_TS191_18_01_imc_grunt3" )
+} \ No newline at end of file
diff --git a/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_utility.nut b/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_utility.nut
new file mode 100644
index 000000000..29b2cfc3a
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/hubs/sp_timeshift_utility.nut
@@ -0,0 +1,7253 @@
+untyped
+
+global function IsAudioLogPlaying
+global function SaveBoyleAudioLogs
+global function InitBoyleAudioLogs
+global function SetFlagWhenPlayerWithinRangeOfEnt
+global function DeleteUnnecessaryFlyers
+global function GetPosInOtherTimeline
+global function ObjectiveRemindUntilFlag
+global function PlayAnimThenDelete
+global function LoudspeakerThread
+global function GivePropForAnim
+global function ElectricalScreenEffects
+global function GetTimelinePosition
+global function DisableNavmeshSeperatorTargetedByEnt
+global function DropshipSpawnAndRepeat
+global function DeleteNpcWhenOutOfSight
+global function TitanTimeshiftHint
+global function CreateShakeWhileFlagSet
+global function CreateShakeTimeshift
+global function RingsLocalExplosionNormal
+global function RingsLocalExplosionBig
+global function PlayerDropLand
+global function ProwlersAmbientThink
+global function PlayerConversationStopOnFlagImmediate
+global function FlagSetDelayed
+global function FlagClearDelayed
+global function PlayerConversationStopOnFlag
+global function HideCritTimeshift
+global function TitanRackSpawnersThink
+global function TitanRackDeploy
+global function SpawnAutoSecurityGroup
+global function SpawnPristineStalkersWithDopplegangers
+global function TitanTimeshiftLoadout
+global function AndersonHologramSequence
+global function GunshipSequence
+global function GetTimeshiftPlayer
+global function GetClosestGrunt
+global function EmitSoundAtPositionHack
+global function FreezeNPC
+global function UnFreezeNPC
+global function MakeCivilian
+global function DestroyNPCOnFlag
+global function AttackPlayer
+global function HideStuff
+global function ShowStuff
+global function SwapTimelinesScripted
+global function TS_WithinPlayerFOV
+global function SetFlagWhenPlayerLookingAtEnt
+global function PlayerInRange
+global function HideWeaponsAndAmmoTillFlag
+global function DataStab
+//global function MakeSpectreOwnedByPlayer
+global function SkyboxStart
+global function TimeshiftSetObjective
+global function TimeshiftSetObjectiveSilent
+global function TimeshiftUpdateObjective
+global function SPTimeshiftUtilityInit
+global function GetSpectreDoorSwitchByDummyName
+global function DropHangingOgre
+global function DropHangingOgreOnFlag
+global function DestroyArray
+global function LaserMeshDeactivateByInstanceName
+global function LaserMeshDestroyByInstanceName
+global function LaserMeshActivateByInstanceName
+global function SetCurrentObjectivePos
+global function MoveEntityToOppositeTimePeriod
+global function HackPlayLoopEffectOnEntity
+//global function CreateSoundRefHack
+global function DeleteFireHazards
+global function DestroyFuncBrushBreakable
+//global function SpawnWallSpectreGroupWhenInRange
+global function SpawnShowcaseGroupWhenInRange
+global function RemoveBlocker
+global function RestoreBlocker
+global function TempExplosion
+global function DestroyEntByScriptName
+global function SwapTimelines
+global function WaittillSomeDudesAreDead
+global function WaittillPlayerSwitchesTimezone
+global function TimeshiftPlayerThink
+global function TimeshiftHint
+global function DestroyInstancesByScriptInstanceName
+global function HACK_DisableTurret
+global function HACK_EnableTurret
+global function GetNpcByScriptName
+global function CreateLoudspeakerEnt
+global function GiveTimeshiftAbility
+global function SetFlagWhenBreakablesDestroyed
+global function DestroyIfValid
+global function GetEntityTimelinePosition
+global function SleepingSpectreFX
+global function CleanupAI
+global function CleanupEnts
+global function CreateBestLoudspeakerEnt
+global function GiveLowAmmo
+//global function DeleteWaponsWithScriptname
+global function QuickSkit
+global function KillMyInterdimensionalBrother
+global function RingsThink
+global function SetLectureHallLineDuration
+
+
+
+global struct EntityLevelStruct
+{
+ bool npcMarkedForCleanup
+}
+
+struct
+{
+ array< entity > flagSetEntities
+ array< entity > spectreDoorPanels
+ array< entity > spectreDoorTriggers,
+ array< entity > spawnProps
+ array< entity > spectreSpawnDoors
+ array< entity > loudspeakerEnts
+ vector currentObjectivePos
+ vector lastGoodTimeshiftPosOvergrown
+ vector lastGoodTimeshiftPosPristine
+ bool isDisplayingDamageText
+ float lectureHallTimeBeforePlayerInterrupts
+ array<entity> npcsPresent
+ array<entity> npcsPast
+ entity currentObjectiveEntity
+ entity titanCorpseOrg
+ entity stalkerCorpseOrg
+ array< entity > titanCorpsePieces
+ array< entity > stalkerCorpsePieces
+ array<string> deathPoses
+ bool isDisplayingTimeshiftHint
+ bool loudspeakerThreadRunning
+ array< entity > ambientDeletableFlyers
+ int[5] boyleAudioLogNumberAssignments = [ 0, 0, 0, 0, 0 ]
+ int boyleAudioLogsCollected = 0
+ bool debugAudioLogs = false
+ array< entity > audioLogModels
+
+} file
+
+//---------------------------
+// GLOBALS
+//---------------------------
+const FLYER_TIMESHIFT_HEALTH = 500
+const TIME_ZOFFSET_TOEXPLOSION_FROM_PRISTINE = -22144
+const TIME_ZOFFSET_TOEXPLOSION_FROM_OVERGROWN = -10624
+const TIMEZONE_DAY = 0
+const TIMEZONE_NIGHT = 1
+const TIMEZONE_ALL = 2
+const TIMEZONE_FROZEN = 3
+
+const BREAKABLE_TYPE_SATCHEL_DEBRIS_MEDIUM = 0
+const BREAKABLE_TYPE_SATCHEL_DEBRIS_MEDIUM_WIDE = 1
+const BREAKABLE_TYPE_AQUARIUM = 2
+
+const DIST_TO_NOT_CARE_ABOUT_AUDIOLOGS = 1500
+const MODEL_CIV01 = $"models/Humans/civilian/civilian_sci_v1.mdl"
+const MODEL_CIV02 = $"models/Humans/civilian/civilian_sci_v2.mdl"
+const MODEL_CIV03 = $"models/Humans/civilian/civilian_sci_v3.mdl"
+const MODEL_CIV04 = $"models/Humans/civilian/civilian_sci_v4.mdl"
+const MODEL_BUTTON = $"models/props/global_access_panel_button/global_access_panel_button_wall.mdl"
+const MODEL_BUTTON_LARGE = $"models/props/global_access_panel_button/global_access_panel_button_console.mdl"
+const IMC_CORPSE_MODEL_LMG = $"models/Humans/grunts/imc_grunt_lmg_corpse.mdl"
+const IMC_CORPSE_MODEL_RIFLE = $"models/Humans/grunts/imc_grunt_rifle_corpse.mdl"
+const IMC_CORPSE_MODEL_SHOTGUN = $"models/Humans/grunts/imc_grunt_shotgun_corpse.mdl"
+const IMC_CORPSE_MODEL_SMG = $"models/Humans/grunts/imc_grunt_smg_corpse.mdl"
+const IMC_CORPSE_MODEL_HEAVY = $"models/Humans/grunts/imc_grunt_lmg_corpse.mdl"
+const HOLOGRAM_KNIFE_MODEL = $"models/weapons/combat_knife/w_combat_knife.mdl"
+const ANDERSON_HOLOGRAM_MODEL = $"models/humans/heroes/mlt_hero_anderson.mdl"
+const ANDERSON_MODEL = $"models/humans/heroes/mlt_hero_anderson.mdl"
+const SARAH_HOLOGRAM_MODEL = $"models/humans/heroes/mlt_hero_jack.mdl"
+const ENEMY_HOLOGRAM_MODEL = $"models/humans/grunts/imc_grunt_rifle.mdl"
+const ANDERSON_PISTOL_MODEL = $"models/Weapons/b3wing/b3_wingman_ab_01.mdl"
+const HOLOGRAM_ENEMY_GUN_MODEL = $"models/Weapons/b3wing/b3_wingman_ab_01.mdl"
+const MODEL_IPAD = $"models/props/tablet/tablet.mdl"
+const MODEL_COFFEE = $"models/domestic/mug_coffee_white.mdl"
+const MARVIN_MODEL_OVERGROWN = $"models/robots/marvin/marvin_mossy.mdl"
+
+//---------------------------
+// FX
+//---------------------------
+const FX_DLIGHT_LIGHT_FLICKER = $"interior_Dlight_blue_MED"
+const FX_TIMESHIFT_ENTITY_MARKER = $"P_ts_entity"
+const FX_GREEN_BLINKIE = $"runway_light_green"
+const FX_FIRE_HYDRAULIC = $"P_fire_small_FULL"
+const FX_DOOR_SCANNER = $"scan_laser_beam_mdl" //scan_laser_beam_mdl_sm
+const FX_TIME_PORTAL = $"P_ts_portal"
+const FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM = $"xo_exp_death"
+const FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM_WIDE = $"xo_exp_death"
+const FX_FIRE_MEDIUM = $"P_fire_rooftop"
+const FX_FIRE_SMALL = $"P_fire_rooftop"
+const FX_FIRE_HUGE = $"P_fire_512"
+const FX_ELECTRICITY = $"P_elec_arc_LG_1"
+//const FX_SPARKS = $"P_sparks_omni_SM_cheap" //this effect stopped working a few weeks ago and no one knows why
+const FX_SPARKS = $"xo_sparks_large_trail_cheap"
+const FX_LASER = $"P_security_laser"
+const FX_RADIATION = $"env_ground_smoke_1024"
+const FX_GENERATOR_LOOP_ACTIVE = $"P_drone_cloak_beam"
+const FX_GENERATOR_LOOP_DORMANT = $"P_elec_arc_LG_1"
+const FX_IMPACT_TABLE_TIMESHIFT = "timeshift_impact"
+const FX_HOLOGRAM_FLASH_EFFECT = $"P_ar_holopilot_flash"
+const FX_HOLOGRAM_HEX_EFFECT = $"P_ar_holopilot_hextrail"
+const FX_HOLO_SCAN_ENVIRONMENT = $"P_ar_holopulse_CP"
+
+
+
+
+//---------------------------
+// SOUND
+//---------------------------
+const SOUND_HOLOGRAM_FLICKER = "Timeshift_Scr_AndersonHolo_FlickerIn"
+const SOUND_HYDRAULIC_EXPLOSION = "Goblin_Dropship_Explode"
+const SOUND_SCANNER_SPECTRE_DOOR = "SpectreDoorScanner" //LaserScanner.ScanSound_3P"
+const SOUND_SCANNER_SPECTRE_DOOR_UNLOCK = "SpectreDoorUnlock"
+const SOUND_SCANNER_SPECTRE_DOOR_FAIL = "SpectreDoorFail"
+const SOUND_TIME_PORTAL_LOOP = "Time_Vortex_Loop"
+const SOUND_BREAKABLE_SATCHEL_DEBRIS_MEDIUM = "Goblin_Dropship_Explode"
+const SOUND_BREAKABLE_SATCHEL_DEBRIS_MEDIUM_WIDE = "Goblin_Dropship_Explode"
+const SOUND_ELECTRICITY = "electricity_loop"
+const SOUND_FIRE_MEDIUM = "amb_colony_fire_medium"
+const SOUND_FIRE_HUGE = "amb_colony_fire_medium"
+const SOUND_SPARKS = "Timeshift_Emit_Sparks" //
+const SOUND_LASER_LOOP = "Timeshift_LaserMesh_Loop"
+const SOUND_LASER_DAMAGE = "Timeshift_LaserMesh_Damage"
+const SOUND_FAN_DAMAGE = "flesh_fanblade_damage_1p"
+const SOUND_LIGHT_FLICKER = "marvin_weld_short"
+
+//---------------------------
+// STRINGS
+//---------------------------
+const HINT_STRING_SPECTRE_REQUIRED = "Security Personnel Required"
+const HINT_STRING_SATCHEL_REQUIRED = "Satchel Charge Required"
+
+//----------------------------
+// MODELS
+//-----------------------------
+const POV_MODEL_TIMESHIFT = $"models/weapons/arms/pov_mlt_hero_jack_ts.mdl"
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function SPTimeshiftUtilityInit()
+{
+ FlyersShared_Init()
+ Temp_InitTimeshiftDialogue()
+
+ Riff_ForceSetSpawnAsTitan( eSpawnAsTitan.Never )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+ AddCallback_OnPlayerRespawned( PlayerSpawned )
+ AddDamageCallback( "player", OnPlayerDamage_TimeShift )
+
+
+ PrecacheModel( MODEL_CIV01 )
+ PrecacheModel( MODEL_CIV02 )
+ PrecacheModel( MODEL_CIV03 )
+ PrecacheModel( MODEL_CIV04 )
+ PrecacheModel( MODEL_BUTTON )
+ PrecacheModel( MODEL_BUTTON_LARGE )
+ PrecacheModel( MODEL_IPAD )
+ PrecacheModel( MODEL_COFFEE )
+ PrecacheModel( POV_MODEL_TIMESHIFT )
+ PrecacheModel( SARAH_HOLOGRAM_MODEL )
+ PrecacheModel( ANDERSON_HOLOGRAM_MODEL )
+ PrecacheModel( ENEMY_HOLOGRAM_MODEL )
+ PrecacheModel( ANDERSON_PISTOL_MODEL )
+ PrecacheModel( HOLOGRAM_ENEMY_GUN_MODEL )
+ PrecacheModel( IMC_CORPSE_MODEL_LMG )
+ PrecacheModel( IMC_CORPSE_MODEL_RIFLE )
+ PrecacheModel( IMC_CORPSE_MODEL_SHOTGUN )
+ PrecacheModel( IMC_CORPSE_MODEL_SMG )
+ PrecacheModel( IMC_CORPSE_MODEL_HEAVY )
+ PrecacheModel( HOLOGRAM_KNIFE_MODEL )
+ PrecacheModel( ANDERSON_MODEL )
+ PrecacheModel( MARVIN_MODEL_OVERGROWN )
+
+
+ PrecacheImpactEffectTable( FX_IMPACT_TABLE_TIMESHIFT )
+
+ PrecacheParticleSystem( FX_HOLOGRAM_FLASH_EFFECT )
+ PrecacheParticleSystem( FX_HOLOGRAM_HEX_EFFECT )
+ PrecacheParticleSystem( FX_HOLO_SCAN_ENVIRONMENT )
+ PrecacheParticleSystem( FX_DLIGHT_LIGHT_FLICKER )
+ PrecacheParticleSystem( FX_FIRE_HYDRAULIC )
+ PrecacheParticleSystem( FX_DOOR_SCANNER )
+ PrecacheParticleSystem( FX_TIME_PORTAL )
+ PrecacheParticleSystem( FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM )
+ PrecacheParticleSystem( FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM_WIDE )
+ PrecacheParticleSystem( FX_RADIATION )
+ PrecacheParticleSystem( FX_FIRE_MEDIUM )
+ PrecacheParticleSystem( FX_FIRE_SMALL )
+ PrecacheParticleSystem( FX_ELECTRICITY )
+ PrecacheParticleSystem( FX_SPARKS )
+ PrecacheParticleSystem( FX_LASER )
+ PrecacheParticleSystem( FX_TIMESHIFT_ENTITY_MARKER )
+ PrecacheParticleSystem( FX_GENERATOR_LOOP_ACTIVE )
+ PrecacheParticleSystem( FX_GENERATOR_LOOP_DORMANT )
+ PrecacheParticleSystem( FX_GREEN_BLINKIE )
+
+ //spawn callbacks
+ AddSpectreRackCallback( RackSpawnCallback )
+ AddSpawnCallback( "info_target", OnSpawnedInfoTarget )
+ AddSpawnCallback( "func_brush", OnSpawnedFuncBrush )
+ AddSpawnCallback( "trigger_multiple", OnSpawnedTrigger)
+ AddSpawnCallback( "trigger_once", OnSpawnedTrigger )
+ AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_quickdeath", OnSpawnedTriggerQuickdeath )
+
+
+
+ AddSpawnCallback( "npc_turret_sentry", OnSpawnedNPC )
+ AddSpawnCallback( "npc_drone", OnSpawnedNPC )
+ AddSpawnCallback( "npc_soldier", OnSpawnedNPC )
+ AddSpawnCallback( "npc_titan", OnSpawnedNPC )
+ AddSpawnCallback( "npc_spectre", OnSpawnedNPC )
+ AddSpawnCallback( "npc_stalker", OnSpawnedNPC )
+ AddSpawnCallback( "npc_stalker_zombie", OnSpawnedNPC )
+ AddSpawnCallback( "npc_stalker_zombie_mossy", OnSpawnedNPC )
+ AddSpawnCallback( "npc_stalker_crawling_mossy", OnSpawnedNPC )
+ AddSpawnCallback( "npc_prowler", OnSpawnedNPC )
+ AddSpawnCallback( "npc_marvin", OnSpawnedNPC )
+ AddSpawnCallback( "npc_frag_drone", OnSpawnedNPC )
+ AddSpawnCallback( "npc_super_spectre", OnSpawnedNPC )
+ //AddSpawnCallback( "env_fog_controller", OnSpawnedFogController )
+ AddSpawnCallback( "info_spawnpoint_marvin", OnSpawnedMarvinSpawner )
+ AddSpawnCallback( "prop_dynamic", OnSpawnedPropDynamic )
+ AddSpawnCallback( "prop_dynamic_lightweight", OnSpawnedPropDynamic )
+ AddSpawnCallbackEditorClass( "prop_dynamic", "script_switch", OnSpawnedScriptedSwitch )
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_weapon", WeaponPickupHack )
+
+
+ //death callbacks
+ //AddDeathCallback( "player", TS_PlayerDeath )
+ AddDeathCallback( "npc_soldier", TS_OnDeathNPC )
+ AddDeathCallback( "npc_spectre", TS_OnDeathNPC )
+ AddDeathCallback( "npc_prowler", TS_OnDeathNPC )
+ AddDeathCallback( "npc_titan", TS_OnDeathNPC )
+ AddDeathCallback( "npc_stalker", TS_OnDeathNPC )
+ AddDeathCallback( "npc_marvin", TS_OnDeathNPC )
+
+ AddCallback_OnTimeShiftAbilityUsed( OnTimeShiftAbilityUsed )
+ AddCallback_OnTimeShiftTitanAbilityUsed( OnTimeShiftAbilityUsed )
+ AddCallback_OnSatchelPlanted( OnSatchelPlanted )
+
+ AddDamageCallback( "func_brush", OnDamagedFuncBrush )
+
+ FlagInit( "AudioLogPlaying" )
+ FlagInit( "ShouldPlayGlobalLoudspeker" )
+ FlagInit( "ForceFlyerTakeoff" )
+ FlagInit( "AndersonHologram1Playing" )
+ FlagInit( "AndersonHologram2Playing" )
+ FlagInit( "AndersonHologram3Playing" )
+ FlagInit( "PlayerInterruptedLecture" )
+ FlagInit( "AndersonHologram1Finished" )
+ FlagInit( "AndersonHologram2Finished" )
+ FlagInit( "AndersonHologram3Finished" )
+ FlagInit( "DisplayTheDamageHint" )
+ FlagInit( "RingsShouldBeSpinning" )
+ FlagSet( "DisableDropships" )
+ FlagInit( "PlayerHasTimeTraveledInsideBT" )
+ FlagInit( "DoingCinematicTimeshift" )
+ FlagInit( "TurretsNearBunkerFenceActivated" )
+ FlagInit( "PlayerObtainedC4" )
+ FlagInit( "PlayerHasBioCreds")
+ FlagInit( "AtLeastOneBunkerTurretRestored" )
+ FlagInit( "bunker_battery_teleport" )
+ RegisterSignal( "BreakableDestroyed" )
+ RegisterSignal( "PropSpawnerActivate" )
+ RegisterSignal( "Frozen" )
+ RegisterSignal( "UnFrozen" )
+ RegisterSignal( "DisplayingSatchelHint" )
+ RegisterSignal( "DisplayingDamageHint" )
+ RegisterSignal( "PauseLasermesh" )
+ RegisterSignal( "AndersonTimeshifts" )
+ RegisterSignal( "AndersonEnemyShow" )
+ RegisterSignal( "AndersonHideGun" )
+ RegisterSignal( "AndersonEnemyShowKnife" )
+ RegisterSignal( "FlyerTakeoffOverride" )
+ RegisterSignal( "StopCoreEffects" )
+ RegisterSignal( "AudioLogDebugDraw" )
+ RegisterSignal( "StopAudioLog" )
+
+
+
+ file.isDisplayingDamageText = false
+ file.isDisplayingTimeshiftHint = false
+
+ level.allowTimeTravel <- false
+ //level.isTimeTraveling <- false
+ level.timeZone <- TIMEZONE_NIGHT
+ level.fogController <- null
+ //level.playerSpawn <- null
+
+ SetTimeshiftTimeOfDay_Night()
+
+ if ( file.debugAudioLogs )
+ thread AudioLogDebug()
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function AudioLogDebug()
+{
+ while ( true )
+ {
+ wait 1
+
+ printl( "*******************************************************" )
+ printt( "boyleAudioLogsCollected: ", file.boyleAudioLogsCollected )
+ printt( "boyleAudioLogNumberAssignments:" )
+ for ( int i = 0 ; i < file.boyleAudioLogNumberAssignments.len(); i++ )
+ {
+ printt( i, " = ", file.boyleAudioLogNumberAssignments[ i ] )
+ }
+
+ }
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function EntitiesDidLoad()
+{
+
+ array <entity> flyerSpawners = GetEntArrayByScriptName( "flyer_ambient" )
+ array <entity> ambientFlyers
+ entity flyer
+ foreach( spawner in flyerSpawners )
+ {
+ flyer = CreatePerchedFlyer( spawner.GetOrigin(), spawner.GetAngles() )
+ //flyer.s.health = FLYER_TIMESHIFT_HEALTH
+ if ( spawner.HasKey( "script_noteworthy") )
+ {
+ file.ambientDeletableFlyers.append( flyer )
+ }
+ ambientFlyers.append( flyer )
+
+ spawner.Destroy()
+ thread FlyerAmbientThink( flyer )
+ }
+
+
+
+ array <entity> spawners = GetSpawnerArrayByClassName( "npc_prowler" )
+ spawners.extend( GetSpawnerArrayByClassName( "npc_stalker" ) )
+ spawners.extend( GetSpawnerArrayByClassName( "npc_stalker_zombie" ) )
+ spawners.extend( GetSpawnerArrayByClassName( "npc_stalker_zombie_mossy" ) )
+ spawners.extend( GetSpawnerArrayByClassName( "npc_stalker_crawling_mossy" ) )
+ spawners.extend( GetSpawnerArrayByClassName( "npc_marvin" ) )
+ Assert( spawners.len() > 0 )
+ foreach( spawner in spawners )
+ {
+ if ( IsSpawner( spawner ) )
+ spawner.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID //setting this for all spawners because the error is retarded
+ }
+
+ thread PlayerIndorsStatus()
+
+ entity titanCorpseOrg = GetEntByScriptName( "titan_gibs_org" )
+ entity stalkerCorpseOrg = GetEntByScriptName( "stalker_gibs_org" )
+ Assert( IsValid( titanCorpseOrg ) )
+ Assert( IsValid( stalkerCorpseOrg ) )
+ thread CorpseSetup( titanCorpseOrg )
+ thread CorpseSetup( stalkerCorpseOrg )
+
+ array <string> deathPosesLocal
+
+ deathPosesLocal.append( "pt_timeshift_deathpose_back_01" )
+ deathPosesLocal.append( "pt_timeshift_deathpose_back_02" )
+ deathPosesLocal.append( "pt_timeshift_deathpose_back_03" )
+ deathPosesLocal.append( "pt_timeshift_deathpose_front_01" )
+ file.deathPoses = deathPosesLocal
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DeleteUnnecessaryFlyers()
+{
+ wait 1
+ foreach( flyer in file.ambientDeletableFlyers )
+ {
+ if ( IsValid( flyer ) )
+ flyer.Destroy()
+ }
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function CorpseSetup( entity corpseOrg )
+{
+ array< entity > linkedEnts = corpseOrg.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0 )
+ string scriptName = corpseOrg.GetScriptName()
+
+ foreach( entity ent in linkedEnts )
+ {
+ ent.SetParent( corpseOrg )
+ ent.Hide()
+ ent.NotSolid()
+
+ if ( scriptName == "titan_gibs_org" )
+ file.titanCorpsePieces.append( ent )
+ if ( scriptName == "stalker_gibs_org" )
+ file.stalkerCorpsePieces.append( ent )
+ }
+
+ if ( scriptName == "titan_gibs_org" )
+ file.titanCorpseOrg = corpseOrg
+ else if ( scriptName == "stalker_gibs_org" )
+ file.stalkerCorpseOrg = corpseOrg
+ else
+ Assert( 0, "Unhandled script_name: " + scriptName )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TimeshiftSetObjectiveSilent( entity player, string objectiveString, vector objectivePos = < 0, 0, 0 >, entity objectiveEntity = null, silent = false )
+{
+ TimeshiftSetObjective( player, objectiveString, objectivePos, objectiveEntity, true )
+}
+
+void function TimeshiftSetObjective( entity player, string objectiveString, vector objectivePos = < 0, 0, 0 >, entity objectiveEntity = null, silent = false )
+{
+ if ( objectiveEntity == null )
+ {
+ SetCurrentObjectivePos( objectivePos )
+ if ( silent )
+ Objective_SetSilent( objectiveString, objectivePos )
+ else
+ Objective_Set( objectiveString, objectivePos )
+ file.currentObjectiveEntity = null
+ }
+ else
+ {
+ if ( silent )
+ Objective_SetSilent( objectiveString, < 0, 0, 0 >, objectiveEntity )
+ else
+ Objective_Set( objectiveString, < 0, 0, 0 >, objectiveEntity )
+ file.currentObjectiveEntity = objectiveEntity
+ }
+
+ ObjectiveCompensate( player, objectiveEntity )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TimeshiftUpdateObjective( entity player, vector objectivePos, entity objectiveEntity = null )
+{
+ if ( objectiveEntity == null )
+ {
+ SetCurrentObjectivePos( objectivePos )
+ Objective_Update( objectivePos )
+ file.currentObjectiveEntity = null
+ }
+ else
+ {
+ Objective_Update( < 0, 0, 0 >, objectiveEntity )
+ file.currentObjectiveEntity = objectiveEntity
+ }
+
+ ObjectiveCompensate( player, objectiveEntity )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function SetCurrentObjectivePos( vector pos )
+{
+ Assert( pos.z != 0 )
+ file.currentObjectivePos = pos
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function ObjectiveCompensate( entity player, entity objectiveEntity = null )
+{
+ vector newPos
+
+ if ( !IsValid( objectiveEntity ) )
+ {
+ if ( GetTimelinePosition( file.currentObjectivePos ) == level.timeZone )
+ newPos = file.currentObjectivePos
+ else
+ newPos = GetPosInOtherTimeline( file.currentObjectivePos )
+
+ Objective_Update( newPos )
+ }
+ else
+ {
+ if ( GetEntityTimelinePosition( objectiveEntity ) == level.timeZone )
+ Objective_Update( < 0, 0, 0 >, objectiveEntity )
+ else
+ Objective_Update( GetPosInOtherTimeline( objectiveEntity.GetOrigin() ) )
+ }
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+vector function GetPosInOtherTimeline( vector pos )
+{
+ int zOffset
+ if ( GetTimelinePosition( pos ) == TIMEZONE_NIGHT )
+ zOffset = TIME_ZOFFSET
+ else
+ zOffset = TIME_ZOFFSET * -1
+
+ return Vector( pos.x, pos.y, pos.z + ( zOffset ) )
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+function DamagedBreakableHydraulic( func_brush, damageInfo )
+{
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ local damageAmount = DamageInfo_GetDamage( damageInfo )
+ local entName = func_brush.GetTargetName()
+
+ if ( damageSourceID != eDamageSourceId.mp_weapon_satchel )
+ {
+ Dev_PrintMessage( attacker, "#BLANK_TEXT", "#HINT_STRING_SATCHEL_REQUIRED", 3.0 )
+ return
+ }
+
+ Signal( func_brush, "BreakableDestroyed" )
+}
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+void function PlayerSpawned( entity player )
+{
+ //Need to decide whether we are in past/present based on level.struct
+ Remote_CallFunction_NonReplay( player, "ServerCallback_TimeFlipped", TIMEZONE_NIGHT )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function GiveTimeshiftAbility( entity __player )
+{
+ //give it to everyone, not just the player
+ foreach( entity player in GetPlayerArray() )
+ {
+ player.SetPlayerSettings( "pilot_solo_timeshift" )
+ if ( IsValid( player.GetOffhandWeapon( OFFHAND_SPECIAL ) ) )
+ player.TakeOffhandWeapon( OFFHAND_SPECIAL )
+ player.GiveOffhandWeapon( "mp_ability_timeshift", 1 )
+
+ entity viewModel = player.GetFirstPersonProxy()
+ viewModel.SetSkin( 1 )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_TimeDeviceAcquired" )
+ }
+ level.allowTimeTravel = true
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DataStab( entity player, entity ref )
+{
+ vector playerStartPos = player.GetOrigin()
+ vector playerStartAng = player.GetAngles()
+
+ vector origin = ref.GetOrigin()
+ vector angles = ref.GetAngles()
+
+
+ ref.SetAngles( ref.GetAngles() + Vector( 0, 0, 18 ) )
+
+ FirstPersonSequenceStruct sequenceStart
+ sequenceStart.blendTime = 1.0
+ sequenceStart.attachment = "ref"
+ sequenceStart.firstPersonAnim = "ptpov_data_core_leech_start"
+ sequenceStart.thirdPersonAnim = "pt_core_console_leech_start"
+ sequenceStart.viewConeFunction = ViewConeZero
+
+ FirstPersonSequenceStruct sequenceMid
+ sequenceMid.blendTime = 0.0
+ sequenceMid.attachment = "ref"
+ sequenceMid.firstPersonAnim = "ptpov_data_knife_console_leech_idle"
+ sequenceMid.thirdPersonAnim = "pt_data_knife_console_leech_idle"
+ sequenceMid.viewConeFunction = ViewConeTight
+
+
+ FirstPersonSequenceStruct sequenceEnd
+ sequenceEnd.blendTime = 0.0
+ sequenceEnd.attachment = "ref"
+ sequenceEnd.firstPersonAnim = "ptpov_core_scan_end"
+ sequenceEnd.thirdPersonAnim = "pt_core_scan_end"
+ sequenceEnd.viewConeFunction = ViewConeTight
+
+ player.DisableWeaponWithSlowHolster()
+ player.ContextAction_SetBusy()
+ //player.FreezeControlsOnServer()
+
+ //thread GiveDataknifeForDuration( player, 5 )
+
+ //entity fpProxy = player.GetFirstPersonProxy()
+ //int attachID = fpProxy.LookupAttachment( "KNIFE" )
+ //entity weaponModel = CreatePropDynamic( DATA_KNIFE_MODEL )
+ //weaponModel.SetParent( player.GetFirstPersonProxy(), "PROPGUN", false, 0.0 )
+
+
+ entity viewModel = player.GetFirstPersonProxy()
+ viewModel.Hide()
+ Remote_CallFunction_NonReplay( player, "ServerCallback_StopGloveGlow" )
+
+ thread CoreHudHighlight( player )
+
+
+ //delaythread ( 1 ) KnifePopOut( weaponModel )
+ waitthread FirstPersonSequence( sequenceStart, player, ref )
+
+
+ //EmitSoundOnEntity( player, "dataknife_loopable_beep" )
+
+
+ FlagEnd( "PlayerAtLevelEnd" )
+
+ OnThreadEnd(
+ function() : ( player, viewModel )
+ {
+ if ( !IsValid( player ) )
+ return
+ player.Anim_Stop()
+ player.ClearParent()
+ ClearPlayerAnimViewEntity( player )
+ if ( player.ContextAction_IsBusy() )
+ player.ContextAction_ClearBusy()
+ player.EnableWeapon()
+
+ if ( IsValid( viewModel ) )
+ viewModel.Show()
+ }
+ )
+
+ thread FirstPersonSequence( sequenceMid, player, ref )
+ wait 0.5
+ //EmitSoundOnEntity( player, "Player.Hitbeep_headshot.Kill.Human_3P_vs_1P" )
+ wait 0.5
+ //EmitSoundOnEntity( player, "dataknife_ring1" )
+ wait 1
+ //EmitSoundOnEntity( player, "dataknife_ring2" )
+ wait 1
+ //EmitSoundOnEntity( player, "dataknife_ring1" )
+ wait 15.5
+ //EmitSoundOnEntity( player, "dataknife_complete" )
+ //EmitSoundOnEntity( player, "Player.Hitbeep_headshot.Kill.Human_3P_vs_1P" )
+ waitthread FirstPersonSequence( sequenceEnd, player, ref )
+
+ //StopSoundOnEntity( player, "dataknife_loopable_beep" )
+
+
+
+
+}
+
+//////////////////////////////////////////////////////////////////////
+void function CoreHudHighlight( entity player )
+{
+ entity core_dummy = GetEntByScriptName( "core_dummy" )
+
+ //core_dummy.Show()
+ //SetTeam( core_dummy, TEAM_IMC )
+ //core_dummy.Highlight_ShowInside( 1.0 )
+ //core_dummy.Highlight_ShowOutline( 1.0 )
+ //Highlight_SetEnemyHighlight( core_dummy, "enemy_sonar" )
+
+
+
+
+
+}
+//////////////////////////////////////////////////////////////////////
+function KnifePopOut( entity knife )
+{
+ knife.Anim_Play( "data_knife_console_leech_start" )
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+function GiveDataknifeForDuration( entity player, float time )
+{
+
+ entity fpProxy = player.GetFirstPersonProxy()
+ int attachID = fpProxy.LookupAttachment( "KNIFE" )
+ entity weaponModel = CreatePropDynamic( DATA_KNIFE_MODEL, fpProxy.GetAttachmentOrigin( attachID ), fpProxy.GetAttachmentAngles( attachID ) )
+ weaponModel.SetParent( player.GetFirstPersonProxy(), "KNIFE", false, 0.0 )
+
+ OnThreadEnd(
+ function() : ( weaponModel )
+ {
+ if ( IsValid( weaponModel ) )
+ weaponModel.Destroy()
+ }
+ )
+
+ player.EndSignal( "OnDeath" )
+
+ wait time
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function OnSpawnedInfoTarget( entity info_target )
+{
+ string entName = info_target.GetTargetName()
+ string scriptName = info_target.GetScriptName()
+
+ if ( scriptName == "loudspeaker_ent" )
+ {
+ file.loudspeakerEnts.append( info_target )
+ info_target.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ }
+
+ if ( ( entName.find( "flagSetEntity" ) != null ) || ( entName.find( "FlagSetEntity" ) != null ) )
+ {
+ file.flagSetEntities.append( info_target )
+ string flagToSet = info_target.GetScriptName()
+ FlagInit( flagToSet )
+ }
+
+ //breakables
+ /*
+ if ( entName.find( "breakable" ) != null )
+ thread BreakableSetup( info_target )
+ */
+
+ //generic sparks
+ if ( scriptName == "sparks" )
+ thread GenericSparks( info_target )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function OnSpawnedFuncBrush( entity func_brush )
+{
+
+
+
+
+}
+
+/*
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TempDoorThink( entity info_target )
+{
+ entity doorController = info_target
+ vector origin = doorController.GetOrigin()
+ array< entity > linkedEnts = info_target.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0, "Door controller at " + info_target.GetOrigin() + " has no linked ents" )
+ string classname
+ array< entity > doors
+ foreach( entity ent in linkedEnts )
+ {
+ classname = ent.GetClassName()
+ if ( classname == "func_brush" )
+ doors.append( ent )
+ }
+ Assert( doors.len() > 0 )
+
+ string entName
+
+ foreach( door in doors )
+ {
+ linkedEnts = door.GetLinkEntArray()
+ foreach( ent in linkedEnts )
+ {
+ entName = ent.GetTargetName()
+ if ( entName.find( "start" ) != null )
+ door.s.startEnt <- ent
+ if ( entName.find( "end" ) != null )
+ door.s.endEnt <- ent
+ }
+ Assert( IsValid( door.s.startEnt ) )
+ Assert( IsValid( door.s.endEnt ) )
+
+ entity mover = TSCreateScriptMoverLight( expect entity( door.s.startEnt ), door.s.startEnt.GetOrigin(), door.s.startEnt.GetAngles() )
+ door.s.mover <- mover
+ door.SetParent( mover )
+ door.s.openPos <- door.s.endEnt.GetOrigin()
+ }
+
+ wait 1
+
+ entity flagEnt = GetClosest( file.flagSetEntities, doorController.GetOrigin() )
+ Assert( IsValid( flagEnt ) )
+ Assert( Distance( flagEnt.GetOrigin(), doorController.GetOrigin() ) < 50, "No flag entity within 50 units of doorController at " + doorController.GetOrigin() )
+ string flagToWaitFor = flagEnt.GetScriptName()
+
+ FlagWait( flagToWaitFor )
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_stop" )
+ EmitSoundOnEntity( doors[ 0 ], "door_open_loop" )
+ foreach( door in doors )
+ door.s.mover.NonPhysicsMoveTo( door.s.openPos, 2, 0.0, 0.0 )
+
+ wait 2
+
+ StopSoundOnEntity( doors[ 0 ], "door_open_loop" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_stop" )
+
+}
+*/
+/////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+void function OnSpawnedFogController( entity fogController )
+{
+ if ( GetBugReproNum() != 007 )
+ {
+ fogController.Destroy()
+ return
+ }
+
+ level.fogController = fogController
+ ChangeFog( TIMEZONE_NIGHT )
+}
+*/
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function OnSpawnedPropDynamic( entity propDynamic )
+{
+ int contextId = 0
+ string entName = propDynamic.GetTargetName()
+ string scriptName = propDynamic.GetScriptName()
+
+ if ( scriptName == "button_overgrown_large" )
+ thread ButtonOvergrownThink( propDynamic, "large" )
+
+ if ( scriptName == "button_overgrown" )
+ thread ButtonOvergrownThink( propDynamic, "small" )
+
+ if ( scriptName == "spectre_door_spawner" )
+ thread SpectreDoorSpawnerThink( propDynamic )
+
+ if ( scriptName == "door_out_of_order" )
+ thread DoorOutOfOrderThink( propDynamic )
+
+
+ if ( scriptName == "light_flicker" )
+ thread LightFlickerThink( propDynamic )
+
+ //----------------------------------------------------
+ // Objective highlighting needs to be done onSpawn
+ //----------------------------------------------------
+ if (
+ ( scriptName.find( "helmet_dogtag" ) != null ) ||
+ ( scriptName.find( "core_dummy" ) != null ) ||
+ ( scriptName.find( "anderson_first_half" ) != null )
+
+ )
+ {
+ Highlight_ClearEnemyHighlight( propDynamic )
+ propDynamic.Highlight_SetFunctions( contextId, 0, true, HIGHLIGHT_OUTLINE_INTERACT_BUTTON, 1, 0, false )
+ propDynamic.Highlight_SetParam( contextId, 0, HIGHLIGHT_COLOR_INTERACT )
+ propDynamic.Highlight_SetCurrentContext( contextId )
+ propDynamic.Highlight_ShowInside( 0 )
+ propDynamic.Highlight_ShowOutline( 0 )
+
+ Objective_InitEntity( propDynamic )
+ }
+
+ if ( scriptName == "audio_log_model" )
+ {
+ propDynamic.Highlight_SetFunctions( contextId, 0, true, HIGHLIGHT_OUTLINE_INTERACT_BUTTON, 1, 0, false )
+ propDynamic.Highlight_SetParam( contextId, 0, HIGHLIGHT_COLOR_INTERACT )
+ propDynamic.Highlight_SetCurrentContext( contextId )
+ propDynamic.Highlight_ShowInside( 0 )
+ propDynamic.Highlight_ShowOutline( 0 )
+ thread AudioLogModelThink( propDynamic )
+ }
+
+ /*
+ asset modelName = propDynamic.GetModelName()
+ if (
+ ( modelName == SARAH_HOLOGRAM_MODEL ) ||
+ ( modelName == ANDERSON_HOLOGRAM_MODEL ) ||
+ ( modelName == ENEMY_HOLOGRAM_MODEL )
+ )
+ {
+ propDynamic.SetSkin( 1 )
+ }
+ */
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function SpectreDoorSpawnerThink( entity spawnProp )
+{
+ asset modelName = spawnProp.GetModelName()
+ file.spawnProps.append( spawnProp )
+ entity spawnerRack
+ entity spawnerSpectre
+ array< entity > linkedEnts = spawnProp.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0 )
+ string editorClassname
+ string classname
+
+ foreach( entity ent in linkedEnts )
+ {
+ editorClassname = GetEditorClass( ent )
+ classname = ent.GetClassName()
+
+ if ( editorClassname == "npc_spectre_rack_wall" )
+ spawnerRack = ent
+ if ( classname == "spawner" )
+ spawnerSpectre = ent
+ }
+
+ Assert( IsValid( spawnerRack ) )
+ Assert( IsValid( spawnerSpectre ) )
+
+ entity mover
+
+ vector origin = spawnProp.GetOrigin()
+ vector angles = spawnProp.GetAngles()
+ vector originOffset
+ vector reverseOriginOffset
+
+ string sound
+ float doorOpenTime
+ float zHeight
+ entity ref = CreateEntity( "info_target" )
+ ref.SetOrigin( origin )
+ ref.SetAngles( angles )
+ DispatchSpawn( ref )
+
+ var doorOpenDelayTime
+ float spawnDelayTime
+ if ( spawnProp.HasKey( "script_delay" ) )
+ doorOpenDelayTime = spawnProp.kv.script_delay
+ else
+ doorOpenDelayTime = 0
+
+ mover = TSCreateScriptMoverLight( ref, origin, angles )
+ spawnProp.SetParent( mover )
+
+ switch( modelName )
+ {
+
+ case $"models/levels_terrain/sp_timeshift/door_custom_timeshift_lobby_01.mdl":
+ file.spectreSpawnDoors.append( spawnProp )
+ originOffset = PositionOffsetFromEnt( spawnProp, 0, 2, 0 )
+ reverseOriginOffset = PositionOffsetFromEnt( spawnProp, 0, -2, 0 )
+ sound = "Timeshift_Scr_StalkerPodOpen"
+ doorOpenTime = 0.5
+ spawnDelayTime = 0.1
+ zHeight = 72
+ break
+ case $"models/levels_terrain/sp_timeshift/door_custom_timeshift_concourse_01.mdl":
+ file.spectreSpawnDoors.append( spawnProp )
+ originOffset = PositionOffsetFromEnt( spawnProp, 4, 0, 0 )
+ reverseOriginOffset = PositionOffsetFromEnt( spawnProp, -4, 0, 0 )
+ sound = "Timeshift_Scr_StalkerPodOpen"
+ doorOpenTime = 2
+ spawnDelayTime = 0.1
+ zHeight = 85
+ break
+ case $"models/timeshift/timeshift_column_panel_09_destroyed.mdl":
+ case $"models/timeshift/timeshift_column_panel_09.mdl":
+ case $"models/timeshift/timeshift_column_panel_10_destroyed.mdl":
+ case $"models/timeshift/timeshift_column_panel_10.mdl":
+ file.spectreSpawnDoors.append( spawnProp )
+ originOffset = PositionOffsetFromEnt( spawnProp, 0, 2, 0 )
+ reverseOriginOffset = PositionOffsetFromEnt( spawnProp, 0, -2, 0 )
+ sound = "Timeshift_Scr_StalkerPodOpen"
+ doorOpenTime = 0.5
+ spawnDelayTime = 0.1
+ zHeight = 88
+ break
+ default:
+ Assert( 0, "Unhandled spectre door type at " + spawnProp.GetOrigin() )
+ break
+ }
+
+ while( true )
+ {
+ spawnProp.WaitSignal( "PropSpawnerActivate" )
+
+ wait doorOpenDelayTime
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, sound )
+ mover.NonPhysicsMoveTo( originOffset, 0.5, 0.0, 0.0 )
+ wait 0.5
+ mover.NonPhysicsMoveTo( spawnProp.GetOrigin() + Vector( 0, 0, zHeight ), doorOpenTime, 0.0, 0.0 )
+ delaythread ( spawnDelayTime ) SpawnFromStalkerRack( spawnerRack )
+
+ wait doorOpenTime + 3
+
+ mover.NonPhysicsMoveTo( spawnProp.GetOrigin() + Vector( 0, 0, -zHeight ), doorOpenTime, 0.0, 0.0 )
+ //EmitSoundAtPosition( TEAM_UNASSIGNED, origin, sound )
+ EmitSoundOnEntity( mover, "Timeshift_Scr_StalkerPodClose" )
+ wait doorOpenTime
+ mover.NonPhysicsMoveTo( reverseOriginOffset, 0.5, 0.0, 0.0 )
+
+ }
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DoorControlPanelSpectreDisable( entity doorSwitch )
+{
+ doorSwitch.s.enabled = false
+ doorSwitch.s.hintTrigger.kv.enabled = 0
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function DoorControlPanelSpectreEnable( entity doorSwitch )
+{
+ doorSwitch.s.enabled = true
+ doorSwitch.s.hintTrigger.kv.enabled = 1
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+bool function IsDoorHacking( playerSpectre )
+{
+ if ( !( "doorHacking" in playerSpectre.s ) )
+ return false
+ if ( playerSpectre.s.doorHacking )
+ return true
+ return false
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function DoorControlPanelSpectreTryToHack( playerSpectre, entity doorSwitch )
+{
+ playerSpectre.EndSignal( "OnDeath" )
+
+ if ( !( "doorHacking" in playerSpectre.s ) )
+ playerSpectre.s.doorHacking <- null
+ playerSpectre.s.doorHacking = true
+
+ //playerSpectre.SetTouchTriggers( false )
+ playerSpectre.DisableBehavior( "Follow" )
+ playerSpectre.EnableBehavior( "Assault" )
+ playerSpectre.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_USE_SHOOTING_COVER )
+
+ var animEnt = doorSwitch.s.animEnt
+ animEnt.SetAngles( animEnt.GetAngles() + Vector( 0, 0, 0 ) )
+ waitthread RunToAndPlayAnim( playerSpectre, "sp_casual_idle", animEnt.GetOrigin(), false, animEnt.GetAngles() )
+ thread PlayAnim( playerSpectre, "sp_casual_idle", animEnt.GetOrigin(), animEnt.GetAngles() )
+
+ bool scanSuccess = true
+ waitthread DoorControlPanelSpectreScan( doorSwitch, scanSuccess )
+
+ //if we make it this far, unlock the door
+ doorSwitch.s.unlocked = true
+}
+
+*/
+
+
+void function OnSpawnedTriggerQuickdeath( entity trigger )
+{
+
+ vector triggerOrigin = trigger.GetOrigin()
+ var timelinePositionTrigger = GetTimelinePosition( triggerOrigin )
+ if ( timelinePositionTrigger == TIMEZONE_FROZEN )
+ return
+
+ var timelinePositionPlayer
+ trigger.EndSignal( "OnDestroy" )
+ vector playerRespawnOrigin
+ var timelinePositionRespawnOrg
+ entity player
+ var result
+
+
+ while ( IsValid( trigger) )
+ {
+ player = null
+ result = trigger.WaitSignal( "OnTrigger" )
+
+ if ( !IsValid( result.activator ) )
+ continue
+
+ if ( !result.activator.IsPlayer() )
+ continue
+
+ player = expect entity( result.activator )
+ player.WaitSignal( "QuickDeathPlayerTeleported" )
+
+ timelinePositionPlayer = GetEntityTimelinePosition( player )
+ playerRespawnOrigin = player.GetOrigin()
+ timelinePositionRespawnOrg = GetTimelinePosition( playerRespawnOrigin )
+
+
+ // player respawn origin matches the timeZone he's supposed to be in
+ if ( timelinePositionRespawnOrg == level.timeZone )
+ continue
+
+ player.FreezeControlsOnServer()
+ if ( ( level.timeZone == TIMEZONE_NIGHT ) && ( timelinePositionRespawnOrg == TIMEZONE_DAY ) )
+ {
+ player.SetOrigin( playerRespawnOrigin + Vector( 0, 0, TIME_ZOFFSET * -1 ) )
+ }
+ else
+ {
+ player.SetOrigin( playerRespawnOrigin + Vector( 0, 0, TIME_ZOFFSET ) )
+ }
+ player.UnfreezeControlsOnServer()
+
+
+ /*
+ if ( timelinePositionPlayer == timelinePositionTrigger )
+ continue
+
+
+ if ( timelinePositionTrigger == TIMEZONE_DAY )
+ player.SetOrigin( playerRespawnOrigin + Vector( 0, 0, TIME_ZOFFSET ) ) //player hit a pristine death trig but respawned in overgrown
+ else
+ player.SetOrigin( playerRespawnOrigin + Vector( 0, 0, ( TIME_ZOFFSET * -1 ) ) ) //player hit a overgrown death trig but respawned in pristine
+ */
+ }
+
+
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function OnSpawnedTrigger( entity trigger )
+{
+ local entName = trigger.GetTargetName()
+ string scriptName = trigger.GetScriptName()
+
+
+ //laser mesh
+ if ( scriptName == "trig_propspawner" )
+ thread TriggerShowcaseSpawnInit( trigger )
+
+
+ //spectre wall spawner
+ //if ( scriptName.find( "trig_spectre_wall_spawner" ) != null )
+ //thread TriggerSpectreWallSpawnerThink( trigger )
+
+ //laser mesh
+ if ( scriptName == "laser_mesh" )
+ thread TriggerPushbackDamageThink( trigger )
+
+ //laser mesh
+ if ( scriptName == "spinning_fan" )
+ thread TriggerPushbackDamageThink( trigger )
+
+ //Dudes spawning in elevators
+ if ( scriptName == "trigger_elevator_npc" )
+ thread trigger_elevator_npc_think( trigger )
+
+ /*
+ if ( scriptName == "trigger_time_hint" )
+ thread TriggerTimehintThink( trigger )
+ */
+
+
+ //Spectre activated door panel triggers
+ if ( scriptName.find( "trigger_spectre_door_control" ) != null )
+ file.spectreDoorTriggers.append( trigger )
+
+
+ //HACK: need to switch over to script_name for these ones
+
+
+ //Spectre activated door panel triggers
+ if ( entName.find( "trigger_spectre_door_control" ) != null )
+ file.spectreDoorTriggers.append( trigger )
+
+
+ //Hazard triggers
+ if ( entName.find( "trigger_hazard" ) != null )
+ thread TriggerHazardThink( trigger )
+
+
+
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function trigger_elevator_npc_think( entity trigger )
+{
+ trigger.EndSignal( "OnDestroy" )
+
+ vector soundOrigin
+ entity door
+ entity elevatorLightEntity
+ //entity button
+ string classname
+ string editorClassname
+
+ array< entity > linkedEnts = trigger.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0 )
+ foreach( entity ent in linkedEnts )
+ {
+ classname = ent.GetClassName()
+ editorClassname = GetEditorClass( ent )
+
+ if ( classname == "info_target" )
+ {
+ elevatorLightEntity = ent
+ continue
+ }
+ if ( editorClassname == "script_door" )
+ {
+ door = ent
+ continue
+ }
+ /*if ( editorClassname == "script_switch" )
+ {
+ button = ent
+ continue
+ }
+ */
+ }
+ Assert( IsValid( door ) )
+ Assert( IsValid( elevatorLightEntity ) )
+ //Assert( IsValid( button ) )
+
+ string flagToOpenDoor = expect string( door.kv.script_flag )
+ Assert( flagToOpenDoor != "" )
+ soundOrigin = elevatorLightEntity.GetOrigin()
+ //elevatorLightEntity.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+
+ //---------------------------------------
+ // Guys spawn inside elevator, bell dings
+ //---------------------------------------
+ trigger.WaitSignal( "OnTrigger" )
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, soundOrigin, "Timeshift_ElevatorBell" )
+ entity fxHandle = PlayFX( FX_GREEN_BLINKIE, elevatorLightEntity.GetOrigin() )
+
+ OnThreadEnd(
+ function() : ( fxHandle )
+ {
+ if ( !IsValid( fxHandle) )
+ return
+ fxHandle.Fire( "Stop" )
+ fxHandle.Fire( "DestroyImmediately" )
+ }
+ )
+
+ wait 1.25
+
+ //--------------
+ // Door opens
+ //--------------
+ FlagSet( flagToOpenDoor )
+
+ wait 5
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TriggerPushbackDamageThink( entity trigger )
+{
+ trigger.EndSignal( "OnDestroy" )
+ trigger.EndSignal( "PauseLasermesh" )
+
+ string scriptName = trigger.GetScriptName()
+
+ vector triggerOrigin = trigger.GetOrigin()
+ vector soundOrigin = triggerOrigin
+
+ array< entity > linkedEnts = trigger.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0, "Laser mesh trigger at " + trigger.GetOrigin() + " not linked to anything" )
+ entity centerEnt
+ entity blockerBrush
+ foreach( entity ent in linkedEnts )
+ {
+ if ( ent.GetClassName() == "info_target" )
+ centerEnt = ent
+ if ( ent.GetClassName() == "func_brush" )
+ blockerBrush = ent
+ }
+
+ Assert( IsValid( centerEnt ) )
+ Assert( IsValid( blockerBrush ), "trigger at " + trigger.GetOrigin() + " does not link to a valid blockerbrush" )
+
+ //In case this was deactivated and we are re-activating it now
+ blockerBrush.Solid()
+
+
+ vector centerEntOrigin = centerEnt.GetOrigin()
+
+
+ //DebugDrawLine( topCorner, topCornerConnect, 255, 255, 0, true, 60.0 )
+ //DebugDrawSphere( topCorner, 10.0, 255, 200, 0, true, 60.0 )
+ //DebugDrawSphere( topCornerConnect, 10.0, 0, 255, 0, true, 60.0 )
+ //DebugDrawSphere( botCorner, 10.0, 255, 200, 0, true, 60.0 )
+ //DebugDrawBox( triggerOrigin, triggerMins, triggerMaxs, 255, 255, 0, 1, 60.0 )
+
+ string soundLoop
+ string soundDeactivate
+ string soundDamage
+
+ int damageID
+
+ switch ( scriptName )
+ {
+ case "laser_mesh":
+ //soundLoop = SOUND_LASER_LOOP //doing this on the client
+ soundDamage = SOUND_LASER_DAMAGE
+ damageID = eDamageSourceId.lasergrid
+ break
+ case "spinning_fan":
+ soundDamage = SOUND_FAN_DAMAGE
+ damageID = eDamageSourceId.burn
+ break
+ }
+
+ int statusEffectHandle
+ entity maxsCornerEnt
+
+ //-------------------
+ // setup laser mesh
+ //-------------------
+ if ( scriptName == "laser_mesh" )
+ {
+ // Get the trigger bounds
+ vector triggerMins = trigger.GetBoundingMins()
+ vector triggerMaxs = trigger.GetBoundingMaxs()
+ vector topCorner = PositionOffsetFromEnt( trigger, triggerMaxs.x, triggerMaxs.y, triggerMaxs.z )
+ vector topCornerConnect = PositionOffsetFromEnt( trigger, triggerMins.x, triggerMins.y, triggerMaxs.z )
+ vector botCorner = PositionOffsetFromEnt( trigger, triggerMaxs.x, triggerMaxs.y, triggerMins.z )
+
+ maxsCornerEnt = CreateEntity( "info_target" )
+ maxsCornerEnt.SetOrigin( trigger.GetOrigin() + triggerMaxs )
+ entity minsCornerEnt = CreateEntity( "info_target" )
+ minsCornerEnt.SetOrigin( trigger.GetOrigin() + triggerMins )
+ maxsCornerEnt.LinkToEnt( minsCornerEnt )
+
+ maxsCornerEnt.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ minsCornerEnt.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ maxsCornerEnt.EnableNetworkedEntityLinks()
+ DispatchSpawn( maxsCornerEnt )
+ DispatchSpawn( minsCornerEnt )
+
+ statusEffectHandle = StatusEffect_AddEndless( maxsCornerEnt, eStatusEffect.laser_mesh, 1.0 )
+ }
+
+
+ if ( soundLoop != "" )
+ thread EmitSoundAtPositionHack( TEAM_UNASSIGNED, soundOrigin, soundLoop )
+
+ var scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN
+ entity player
+ var result
+ vector playerOriginXYonly
+ vector triggerOriginXYonly = Vector( centerEntOrigin.x, centerEntOrigin.y, 0 )
+ vector vecToEnt
+
+ //---------------------------
+ // Cleanup when destroyed
+ //---------------------------
+ OnThreadEnd(
+ function() : ( trigger, maxsCornerEnt, soundOrigin, blockerBrush, soundLoop, soundDeactivate, statusEffectHandle )
+ {
+ //------------------------------------------------
+ //Don't destroy blocker brush if we are just pausing
+ //------------------------------------------------
+ if ( IsValid( trigger ) )
+ {
+ blockerBrush.NotSolid()
+ blockerBrush.Hide()
+ blockerBrush.MakeInvisible()
+ }
+ else
+ blockerBrush.Destroy()
+
+ if ( IsValid( maxsCornerEnt ) )
+ StatusEffect_Stop( maxsCornerEnt, statusEffectHandle )
+
+ //--------------------------------------------------------------------------------
+ // Stop and destroy all sounds and particles regardless of pausing or destroying
+ //--------------------------------------------------------------------------------
+ if ( soundLoop != "" )
+ StopSoundAtPosition( soundOrigin, soundLoop )
+
+ if ( soundDeactivate != "" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, soundOrigin, soundDeactivate )
+ }
+ )
+
+
+ //---------------------------
+ // Wait to do damage
+ //---------------------------
+ while ( IsValid( trigger) )
+ {
+ player = null
+ result = trigger.WaitSignal( "OnStartTouch" )
+
+ //if ( level.isTimeTraveling )
+ // continue
+
+ if ( !IsValid( result.activator ) )
+ continue
+
+ if ( result.activator.IsNPC() )
+ {
+ thread LaserMeshDamageNPC( expect entity( result.activator ) )
+ continue
+ }
+ if ( !result.activator.IsPlayer() )
+ continue
+
+ //Damage and pushback player
+ player = expect entity( result.activator )
+
+ if ( player.s.isTimeTraveling )
+ continue
+
+ player.TakeDamage( 50, svGlobal.worldspawn, svGlobal.worldspawn, { origin = player.GetOrigin(), scriptType = scriptTypeMask, damageSourceId = damageID } )
+ CreateShakeRumbleOnly( player.GetOrigin(), 10, 105, 1 )
+
+ playerOriginXYonly = player.GetOrigin()
+ playerOriginXYonly = Vector( playerOriginXYonly.x, playerOriginXYonly.y, 0 )
+
+ vecToEnt = ( playerOriginXYonly - triggerOriginXYonly )
+ vecToEnt = Normalize( vecToEnt )
+
+ printt( "vecToEnt: ", vecToEnt )
+ player.SetVelocity( vecToEnt * 1000 )
+ printt( "Velocity: ", vecToEnt * 1000 )
+
+ EmitSoundOnEntity( player, soundDamage )
+
+
+ //DebugDrawSphere( playerOriginXYonly, 16, 255, 0, 0, true, 3 )
+ //DebugDrawSphere( triggerOriginXYonly, 16, 255, 0, 0, true, 3 )
+
+ //SetVelocity( Normalize( triigerOrigin - playerOrigin ) * 1000 )
+
+ }
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function LaserMeshDamageNPC( entity npc )
+{
+ if ( !IsAlive( npc ) )
+ return
+
+ if ( npc.GetClassName() == "npc_marvin" )
+ return
+
+ if ( npc.GetClassName() == "npc_stalker" )
+ return
+
+ npc.Gib( < 0, 0, 100> )
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function LaserMeshDeactivateByInstanceName( string instanceName )
+{
+ array< entity > triggers = GetEntArrayByScriptNameInInstance( "laser_mesh", instanceName )
+ foreach( trigger in triggers )
+ {
+ if( IsValid( trigger ) )
+ trigger.Signal( "PauseLasermesh" )
+
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function LaserMeshDestroyByInstanceName( string instanceName )
+{
+ array< entity > triggers = GetEntArrayByScriptNameInInstance( "laser_mesh", instanceName )
+ foreach( trigger in triggers )
+ {
+ if( IsValid( trigger ) )
+ trigger.Destroy()
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function LaserMeshActivateByInstanceName( string instanceName )
+{
+ array< entity > lasermeshTriggers = GetEntArrayByScriptNameInInstance( "laser_mesh", instanceName )
+ foreach( trigger in lasermeshTriggers )
+ {
+ if( IsValid( trigger ) )
+ thread TriggerPushbackDamageThink( trigger )
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+var function CreateLaserMeshBeam( startPos, endPos )
+{
+ entity cpoint = CreateEntity( "info_placement_helper" )
+ cpoint.SetOrigin( endPos )
+ SetTargetName( cpoint, UniqueString( "controlpoint" ) )
+ DispatchSpawn( cpoint )
+
+ entity serverEffect = CreateEntity( "info_particle_system" )
+ serverEffect.SetOrigin( startPos )
+ serverEffect.SetValueForEffectNameKey( FX_LASER )
+ serverEffect.kv.start_active = 1
+ serverEffect.SetControlPointEnt( 1, cpoint )
+
+ DispatchSpawn( serverEffect )
+
+ return serverEffect
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function TriggerSpectreWallSpawnerThink( entity trigger )
+{
+ trigger.EndSignal( "OnDeath" )
+
+ entity spectreSpawnDoor
+ array< entity > linkedEnts = trigger.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0, "Trigger at " + trigger.GetOrigin() + " is not linked to any spectre spawn doors" )
+ entity spectreRack
+ //entity spawner
+
+ vector hackRackOrigin
+
+ foreach( entity ent in linkedEnts )
+ {
+ spectreSpawnDoor = ent
+ Assert( spectreSpawnDoor.GetClassName() == "prop_dynamic" )
+ spectreRack = spectreSpawnDoor.GetLinkEnt()
+ Assert( IsValid( spectreRack ), "Spectre door at " + spectreSpawnDoor.GetOrigin() + " with angles: " + spectreSpawnDoor.GetAngles() + " needs to target a valid spectre rack." )
+ //spawner = spectreRack.GetLinkEnt()
+ //Assert( IsValid( spawner ), "spectreRack at " + spectreRack.GetOrigin() + " needs to target a valid spectre spawner." )
+
+ //push racks back a bit so they don't clip thru doors...too difficult to do all in LevelEd...another reason we need instance_names!
+ hackRackOrigin = PositionOffsetFromEnt( spectreRack, -8, 0, 0 )
+ spectreRack.SetOrigin( hackRackOrigin )
+
+ thread PropSpawnerThink( trigger, spectreSpawnDoor, spectreRack )
+ }
+
+}
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+int function GetPlayerSatchelCount( var player )
+{
+
+ return 0
+
+ /*
+ int numSatchels
+
+ local weapon = player.GetOffhandWeapon( 0 )
+ if ( !IsValid( weapon ) )
+
+ if ( weapon )
+ local clipCount = player.GetWeaponAmmoMaxLoaded( weapon )
+
+
+ return numSatchels
+
+ */
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SwapTimelines( entity player, var timeZone )
+{
+ //needs to be threaded off since need to do a
+ //WaitEndFrame()...player will take damage from random hazard triggers otherwise
+ thread SwapTimelinesThread( player, timeZone )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DeviceUsedInFrozenWorld( entity player )
+{
+ const float EFFECT_DURATION_TOTAL = 0.5
+ const float EFFECT_DURATION_EASE_OUT = 0.5
+ StatusEffect_AddTimed( player, eStatusEffect.timeshift_visual_effect, 1.0, EFFECT_DURATION_TOTAL, EFFECT_DURATION_EASE_OUT )
+
+ //StopSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Past" )
+ //EmitSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Present" )
+ StopSoundOnEntity( player, "Timeshift_Scr_BrokenDeviceUse" )
+ EmitSoundOnEntity( player, "Timeshift_Scr_BrokenDeviceUse" )
+
+ //entity timeShiftOffhand = player.GetFirstPersonProxy()
+ //timeShiftOffhand.SetSkin( 0 )
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SwapTimelinesThread( entity player, var timeZoneDestination )
+{
+ if ( !CanTimeShift( player ) )
+ return
+
+ player.s.isTimeTraveling = true
+
+ // no idea why respawn did this, but removing it doesn't seem to break anything
+ //entity player = GetPlayerArray()[0]
+ //if ( !player )
+ // return
+
+ player.ClearTraverse()
+
+ player.EndSignal( "OnDeath" )
+
+ var skyCam
+ vector playerPos = player.GetOrigin()
+ vector newPlayerPos
+ int timeOffset
+
+ player.SetCloakReactEndTime( Time() )
+
+
+ if ( timeZoneDestination == TIMEZONE_NIGHT )
+ {
+ //--------------------------
+ // switch to night/overgrown
+ //--------------------------
+ SetGlobalNetBool( "PlayerInOvergrownTimeline", true )
+ skyCam = GetEnt( "skybox_cam_night" )
+
+ //level.timeZone = TIMEZONE_NIGHT
+ player.s.timeline = TIMEZONE_NIGHT
+
+ timeOffset = TIME_ZOFFSET * -1
+
+ // make this not use global stuff
+ //FreezeNpcs( TIMEZONE_DAY )
+ if ( GetPlayersInTimeline( TIMEZONE_DAY ).len() == 0 )
+ FreezeNpcs( TIMEZONE_DAY )
+
+ player.Signal( "OnTimeFlippedTimezoneNight" )
+ SetTimeshiftTimeOfDay_Night()
+ if ( Flag( "PlayerPickedUpTimeshiftDevice" ) )
+ SetTimeshiftArmDeviceSkin( 1 )
+ if ( Flag( "HidePlayerWeaponsDuringShifts") )
+ player.EnableWeapon()
+ }
+
+ else if ( timeZoneDestination == TIMEZONE_DAY )
+ {
+ //------------------------
+ //switch to day/pristine
+ //------------------------
+ SetGlobalNetBool( "PlayerInOvergrownTimeline", false )
+ skyCam = GetEnt( "skybox_cam_day" )
+
+ //level.timeZone = TIMEZONE_DAY
+ player.s.timeline = TIMEZONE_DAY
+
+ timeOffset = TIME_ZOFFSET
+ FreezeNpcs( TIMEZONE_NIGHT )
+
+ // make this not use global stuff
+ //FreezeNpcs( TIMEZONE_NIGHT )
+ if ( GetPlayersInTimeline( TIMEZONE_NIGHT ).len() == 0 )
+ FreezeNpcs( TIMEZONE_NIGHT )
+
+ player.Signal( "OnTimeFlippedTimezoneDay" )
+
+ SetTimeshiftTimeOfDay_Day()
+ if ( Flag( "PlayerPickedUpTimeshiftDevice" ) )
+ SetTimeshiftArmDeviceSkin( 0 )
+ if ( Flag( "HidePlayerWeaponsDuringShifts") )
+ player.DisableWeapon()
+ }
+ else
+ SetGlobalNetBool( "PlayerInOvergrownTimeline", false )
+
+
+ SatchelManagement( player, timeZoneDestination )
+
+ if ( ( player.IsTitan() ) && ( !Flag( "PlayerHasTimeTraveledInsideBT" ) ) )
+ {
+ FlagSet( "PlayerHasTimeTraveledInsideBT" )
+ }
+
+ TimeshiftUntrackLockedTargets( player )
+
+ vector positionOffset = Vector( 0, 0, timeOffset );
+ newPlayerPos = playerPos + positionOffset;
+
+ if ( PlayerPosInSolid( player, newPlayerPos ) )
+ {
+ if ( timeZoneDestination == TIMEZONE_DAY )
+ {
+ printt( "***WARNING: Destination TimeShiftPos " + newPlayerPos + " is in solid. Using file.lastGoodTimeshiftPosPristine instead" )
+ //newPlayerPos = file.lastGoodTimeshiftPosPristine
+ // use player script var instead
+ // this won't compile if we don't manually do type safety stuff here
+ var pos = player.s.lastGoodTimeshiftPosPristine
+ newPlayerPos = expect vector( pos )
+ }
+ else if ( timeZoneDestination == TIMEZONE_NIGHT )
+ {
+ printt( "***WARNING: Destination TimeShiftPos " + newPlayerPos + " is in solid. Using file.lastGoodTimeshiftPosOvergrown instead" )
+ //newPlayerPos = file.lastGoodTimeshiftPosOvergrown
+ // use player script var instead
+ // this won't compile if we don't manually do type safety stuff here
+ var pos = player.s.lastGoodTimeshiftPosOvergrown
+ newPlayerPos = expect vector( pos )
+ }
+ }
+ else
+ {
+ //Not in solid, just use the offset
+ newPlayerPos = Vector( playerPos.x, playerPos.y, playerPos.z + timeOffset )
+ }
+
+ thread SwapTimelineEffectsAndSound( player, timeZoneDestination )
+
+ const float EFFECT_DURATION_TOTAL = 0.5
+ const float EFFECT_DURATION_EASE_OUT = 0.5
+ StatusEffect_AddTimed( player, eStatusEffect.timeshift_visual_effect, 1.0, EFFECT_DURATION_TOTAL, EFFECT_DURATION_EASE_OUT )
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_TimeFlipped", player.s.timeline )
+ player.SetSkyCamera( skyCam )
+ if ( !IsAlive( player ) )
+ return
+ MakeInvincible( player )
+ WaitEndFrame() //player will take damage from random hazard triggers otherwise
+
+ if ( Flag( "DoingCinematicTimeshift" ) )
+ player.SetAbsOrigin( newPlayerPos )
+ else
+ player.SetOrigin( newPlayerPos )
+
+ EmitAISoundWithOwner( player, SOUND_PLAYER, 0, newPlayerPos, 150, 0.2 )
+
+ SetRapidShiftOffset( -positionOffset );
+ ClearInvincible( player )
+
+ if ( timeZoneDestination == TIMEZONE_DAY )
+ GruntChatter_TryEnemyTimeShifted( player )
+
+ ObjectiveCompensate( player, file.currentObjectiveEntity )
+ player.s.isTimeTraveling = false
+
+ //thread SonarColorCorrection( player, 0.5, null )
+}
+
+
+
+void function TimeshiftUntrackLockedTargets( entity player )
+{
+ if ( !IsValid( player ) )
+ return
+
+ entity trackingWeapon
+ entity offhand
+
+ if ( !player.IsTitan() )
+ return
+
+
+ offhand = player.GetOffhandWeapon( OFFHAND_RIGHT )
+ if ( !IsValid( offhand ) )
+ return
+ if ( offhand.GetWeaponClassName() == "mp_titanweapon_tracker_rockets" )
+ trackingWeapon = offhand
+
+
+ if ( !IsValid( trackingWeapon ) )
+ return
+
+ trackingWeapon.SmartAmmo_Clear( true, true )
+
+ /*
+ var allTargets = trackingWeapon.SmartAmmo_GetTargets()
+ foreach ( target in allTargets )
+ {
+ if ( !IsValid( target.ent ) )
+ continue
+ trackingWeapon.SmartAmmo_UntrackEntity( target.ent )
+
+ }
+ */
+
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function PlayerIndorsStatus()
+{
+ while( GetPlayerArray().len() == 0 )
+ wait 0.1
+
+ entity player = GetPlayerArray()[ 0 ]
+ Assert( IsValid( player ) )
+ player.EndSignal( "OnDeath" )
+
+
+ if ( GetMapName() == "sp_timeshift_spoke02" )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerIndoorsChanged", 1 )
+ return
+ }
+
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerIndoorsChanged", 0 )
+
+ while( true )
+ {
+ wait 0.1
+
+ FlagWait( "player_is_indoors" )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerIndoorsChanged", 1 )
+
+ FlagWaitClear( "player_is_indoors" )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerIndoorsChanged", 0 )
+
+ }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SatchelManagement( entity player, var timeZone )
+{
+ if ( !IsValid( player ) )
+ return
+
+ array<entity> traps = GetScriptManagedEntArray( player.s.activeTrapArrayId )
+ foreach ( index, satchel in traps )
+ {
+ if ( !IsValid( satchel ) )
+ continue
+ if ( GetEntityTimelinePosition( satchel ) == timeZone )
+ DisableSatchel( satchel, false )
+ else
+ DisableSatchel( satchel, true )
+ }
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DisableSatchel( entity satchel, bool isDisabled )
+{
+ satchel.e.isDisabled = isDisabled
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+bool function CanTimeShift( entity player )
+{
+ if ( Flag( "DoingCinematicTimeshift" ) )
+ return true
+
+ if ( player.GetParent() )
+ {
+ printl( "Can't teleport...parented")
+ return false
+ }
+
+ /*
+ if ( player.IsTraversing() )
+ {
+ printl( "Can't teleport...traversing")
+ return false
+ }
+ */
+ /*
+ if ( ( player.IsTitan() ) && ( level.titanCanTimeTravel == false ) )
+ {
+ printl( "Can't teleport...Titan")
+ return false
+ }
+ */
+
+ if ( player.s.isTimeTraveling )
+ return false
+
+ return true
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+function OnTimeShiftAbilityUsed( player )
+{
+ if ( level.allowTimeTravel == false )
+ {
+ if ( Flag( "SwappedToFrozenWorld" ) )
+ thread DeviceUsedInFrozenWorld( expect entity( player ) )
+ return
+
+ }
+
+ //if ( level.timeZone == TIMEZONE_DAY )
+ if ( player.s.timeline == TIMEZONE_DAY )
+ SwapTimelines( expect entity( player ), TIMEZONE_NIGHT )
+ else
+ SwapTimelines( expect entity( player ), TIMEZONE_DAY )
+}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+function OnSatchelPlanted( player, collisionParams )
+{
+ expect entity( player )
+ if ( !IsValid( player ) )
+ return
+
+ expect table( collisionParams )
+
+ vector plantAngles = VectorToAngles( collisionParams.normal )
+ vector plantPosition = expect vector( collisionParams.pos )
+
+ //thread SatchelHint( player )
+
+ //----------------------------------------------------------------
+ //create a duplicate satchel in the present if planted in the past
+ //----------------------------------------------------------------
+ if ( GetTimelinePosition( plantPosition ) == TIMEZONE_NIGHT )
+ return
+
+ if ( collisionParams.hitEnt != null ) //forget it if it's attached to an AI or anything moving
+ return
+
+ //TO DO - plant a dupe satchel here
+ //bool result = PlantStickyEntity( weapon, collisionParams )
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+function SatchelHint( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.Signal( "DisplayingSatchelHint" )
+ player.EndSignal( "DisplayingSatchelHint" )
+
+ array<entity> traps
+ entity playerCurrentWeapon
+ string playerCurrentWeaponClassname
+ bool isDisplayingHint = false
+
+ entity closestSatchel
+ entity offHandOrdinance
+ while ( true )
+ {
+ wait 1.5
+
+ if ( isDisplayingHint == true )
+ {
+ isDisplayingHint = false
+ ClearOnscreenHint( player )
+ }
+
+ offHandOrdinance = player.GetOffhandWeapon( 0 )
+ if ( !offHandOrdinance )
+ break
+ if ( offHandOrdinance.GetWeaponClassName() != "mp_weapon_satchel" )
+ break
+
+ traps = GetScriptManagedEntArray( player.s.activeTrapArrayId )
+ if ( traps.len() < 1 )
+ break
+
+ playerCurrentWeapon = player.GetActiveWeapon()
+
+ // No current weapon selected
+ if ( !IsValid( playerCurrentWeapon ) )
+ continue
+
+ playerCurrentWeaponClassname = playerCurrentWeapon.GetWeaponClassName()
+
+ //Player is equipped with the clacker, clear message
+ if ( playerCurrentWeaponClassname == "mp_weapon_satchel" )
+ continue
+
+ closestSatchel = GetClosest( traps, player.GetOrigin() )
+ if ( !IsValid( closestSatchel ) )
+ continue
+
+ if ( DistanceSqr( player.GetOrigin(), closestSatchel.GetOrigin() ) > ( 1024 * 1024 ) )
+ continue
+
+
+ //player is not equipped with the clacker, display message
+ else
+ {
+ if ( file.isDisplayingTimeshiftHint)
+ continue
+ if ( file.isDisplayingDamageText )
+ continue
+
+ DisplayOnscreenHint( player, "satchel_hint_tap_twice", 3.0 )
+ isDisplayingHint = true
+ continue
+ }
+
+ }
+
+ ClearOnscreenHint( player )
+
+
+}
+
+*/
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+bool function PlayerHasSatchels( entity player )
+{
+ if ( !IsValid( player ) )
+ return false
+
+ entity weapon = player.GetOffhandWeapon( 0 )
+ if ( !IsValid( weapon ) )
+ return false
+
+ int satchelCount = player.GetWeaponAmmoMaxLoaded( weapon )
+ if ( satchelCount == 0 )
+ return false
+
+ return true
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+function FreezeNpcs( timeZone )
+{
+ array<entity> npcsToFreeze
+ array<entity> npcsToUnFreeze
+ ArrayRemoveDead( file.npcsPresent )
+ ArrayRemoveDead( file.npcsPast )
+
+ if ( timeZone == TIMEZONE_NIGHT )
+ {
+ npcsToFreeze = file.npcsPresent
+ npcsToUnFreeze = file.npcsPast
+ }
+
+
+ else if ( timeZone == TIMEZONE_DAY )
+ {
+ npcsToFreeze = file.npcsPast
+ npcsToUnFreeze = file.npcsPresent
+ }
+
+
+ foreach( npc in npcsToFreeze )
+ FreezeNPC( npc )
+
+ foreach( npc in npcsToUnFreeze )
+ UnFreezeNPC( npc )
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function FreezeNPC( entity npc )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ if ( IsFreezeProof( npc ) )
+ return
+
+ if ( IsFrozen( npc ) == true )
+ return
+
+ npc.Freeze()
+ npc.Signal( "Frozen" )
+
+ if ( !( "isFrozen" in npc.s ) )
+ npc.s.isFrozen <- null
+ npc.s.isFrozen = true
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function UnFreezeNPC( entity npc )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ if ( IsFrozen( npc ) == false )
+ return
+
+ npc.Unfreeze()
+ npc.Signal( "UnFrozen" )
+ if ( !( "isFrozen" in npc.s ) )
+ npc.s.isFrozen <- null
+ npc.s.isFrozen = false
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function IsFrozen( npc )
+{
+ if ( !( "isFrozen" in npc.s ) )
+ return false
+
+ return npc.s.isFrozen
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+bool function IsFreezeProof( entity npc )
+{
+ if ( !IsValid( npc ) )
+ return false
+ if ( "dontAllowFreeze" in npc.s )
+ return true
+
+ return false
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DontAllowFreeze( entity npc, bool state )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ if ( state == true )
+ {
+ if ( !( "dontAllowFreeze" in npc.s ) )
+ npc.s.dontAllowFreeze <- null
+ npc.s.dontAllowFreeze = true
+ }
+ else
+ {
+ if ( "dontAllowFreeze" in npc.s )
+ delete npc.s.dontAllowFreeze
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function OnSpawnedNPC( entity npc )
+{
+ string classname = npc.GetClassName()
+ string editorClassname = GetEditorClass( npc )
+
+ if ( editorClassname == "npc_marvin_drone" )
+ return
+
+ var timeZone = GetEntityTimelinePosition( npc )
+ expect int(timeZone)
+
+ int difficulty = GetSpDifficulty()
+
+
+ if ( timeZone == TIMEZONE_NIGHT )
+ file.npcsPresent.append( npc )
+ else if ( timeZone == TIMEZONE_DAY )
+ file.npcsPast.append( npc )
+ else
+ {
+ //Frozen world - do nothing
+ }
+
+ //If I just spawned in the opposite time zone as the player, freeze me
+ //if ( ( level.timeZone != timeZone ) && ( timeZone != TIMEZONE_FROZEN ) )
+ if ( GetPlayersInTimeline( timeZone ).len() == 0 && timeZone != TIMEZONE_FROZEN )
+ FreezeNPC( npc )
+
+ if ( timeZone == TIMEZONE_FROZEN )
+ return
+
+ //Do blue afterglow if we have the device now
+ if ( level.allowTimeTravel )
+ thread TimeshiftAfterglowThink( npc, timeZone )
+
+
+
+
+ int maxHealth = npc.GetMaxHealth()
+ switch( classname )
+ {
+ case "npc_marvin":
+ if ( timeZone == TIMEZONE_NIGHT )
+ {
+ npc.SetModel( MARVIN_MODEL_OVERGROWN )
+ npc.SetSkin( 1 ) //mossy
+ }
+ break
+ case "npc_stalker":
+ case "npc_stalker_zombie":
+ case "npc_stalker_zombie_mossy":
+ case "npc_stalker_crawling_mossy":
+ thread StalkerThink( npc, classname )
+
+ break
+ case "npc_drone":
+ break
+ case "npc_turret_sentry":
+ if ( difficulty < DIFFICULTY_MASTER )
+ npc.kv.AccuracyMultiplier = 4
+ else
+ npc.kv.AccuracyMultiplier = 3
+ break
+ case "npc_titan":
+ thread TitanEnemyThink( npc )
+ break
+ }
+
+ string scriptName = npc.GetScriptName()
+ //--------------------------
+ // Civilians
+ //--------------------------
+ if ( scriptName.find( "civilian_walker_" ) != null )
+ thread CivilianWalkerThink( npc )
+ if ( scriptName.find( "civilian_actor_" ) != null )
+ thread CivilianActorThink( npc )
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TitanEnemyThink( entity npc )
+{
+ if ( npc.GetTeam() != TEAM_IMC )
+ return
+
+ //npc.WaitSignal( "WeakTitanHealthInitialized" )
+ //DeregisterBossTitan( npc )
+ //npc.SetHealth( 100 )
+
+ var timeZone = GetEntityTimelinePosition( npc )
+
+ if ( timeZone == TIMEZONE_DAY )
+ thread TitanEnemyThinkPristine( npc )
+ else
+ thread TitanEnemyThinkOvergrown( npc )
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TitanEnemyThinkOvergrown( npc )
+{
+ npc.WaitSignal( "WeakTitanHealthInitialized" )
+ npc.TakeDamage( npc.GetMaxHealth()/2, null, null, { damageSourceId=damagedef_suicide } )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TitanEnemyThinkPristine( npc )
+{
+ npc.WaitSignal( "WeakTitanHealthInitialized" )
+ printt( "Titan health: ", npc.GetHealth() )
+ npc.SetMaxHealth( 10000 )
+ npc.SetHealth( 10000 )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function StalkerThink( entity npc, string classname )
+{
+ npc.SetSkin( 1 ) //mossy
+ TakeAllWeapons( npc )
+
+ if ( ( GetMapName() == "sp_hub_timeshift" ) && ( !Flag( "player_back_in_amenities_lobby") ) )
+ return
+
+ if ( ( GetMapName() == "sp_timeshift_spoke02" ) && ( !Flag( "StartAndersonHologram1" ) ) )
+ return
+
+ npc.GiveWeapon( "mp_weapon_mgl" )
+
+ npc.EndSignal( "OnDeath" )
+ while( true )
+ {
+ npc.kv.allowshoot = 0
+ wait RandomFloatRange( 4, 6 )
+ npc.kv.allowshoot = 1
+ wait RandomFloatRange( 3, 5 )
+ }
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TimeshiftAfterglowThink( entity npc, var timeZone )
+{
+
+ if( !IsValid( npc ) )
+ return
+
+ npc.EndSignal( "OnDestroy" )
+ npc.EndSignal( "OnDeath" )
+
+ //don't do glows for friendlies
+ int team = npc.GetTeam()
+ if ( team == TEAM_MILITIA )
+ return
+
+ if ( team == TEAM_UNASSIGNED )
+ return
+
+ //don't do glow for certain scripted things
+ string scriptName = npc.GetScriptName()
+ if ( ( IsValid( scriptName ) ) && ( scriptName == "lab_prowlers" ) )
+ return
+
+ if ( ( IsValid( scriptName ) ) && ( scriptName == "bio_pod_body" ) )
+ return
+
+
+
+ string classname = npc.GetClassName()
+ string glowSound = "Timeshift_Scr_TimeResidue_Generic"
+ int zOffset
+ vector fxAfterglowAdditionalOffset
+
+ switch( classname )
+ {
+ case "npc_marvin":
+ return
+ case "npc_soldier":
+ glowSound = "Timeshift_Scr_TimeResidue_Grunt"
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 32 )
+ break
+ case "npc_spectre":
+ case "npc_stalker_zombie":
+ case "npc_stalker_zombie_mossy":
+ case "npc_stalker_crawling_mossy":
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 32 )
+ glowSound = "Timeshift_Scr_TimeResidue_ZombieStalker"
+ break
+ case "npc_stalker":
+ glowSound = "Timeshift_Scr_TimeResidue_Stalker"
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 32 )
+ break
+ case "npc_prowler":
+ glowSound = "Timeshift_Scr_TimeResidue_Prowler"
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 32 )
+ break
+ case "npc_drone":
+ glowSound = "Timeshift_Scr_TimeResidue_Drone"
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 0 )
+ break
+ case "npc_titan":
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 80 )
+ break
+ case "npc_super_spectre":
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 64 )
+ break
+ case "npc_frag_drone":
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 32 )
+ break
+ case "npc_turret_sentry":
+ fxAfterglowAdditionalOffset = Vector( 0, 0, 16 )
+ break
+ default:
+ Assert( 0, "Unhandled npc: " + classname )
+ break
+ }
+
+
+ if ( GetEditorClass( npc ) == "npc_specialist_imc" )
+ glowSound = "Timeshift_Scr_TimeResidue_Generic"
+
+ if ( timeZone == TIMEZONE_NIGHT )
+ zOffset = TIME_ZOFFSET
+ else
+ zOffset = TIME_ZOFFSET * -1
+
+ vector fxAfterglowPos
+ vector fxAfterglowAng
+
+ entity fx
+ while( true )
+ {
+ npc.WaitSignal( "Frozen" )
+ fxAfterglowPos = npc.GetOrigin()
+ fxAfterglowPos = fxAfterglowPos + fxAfterglowAdditionalOffset
+ fxAfterglowPos = Vector( fxAfterglowPos.x, fxAfterglowPos.y, fxAfterglowPos.z + zOffset )
+ fxAfterglowAng = npc.GetAngles()
+ fx = PlayFX( FX_TIMESHIFT_ENTITY_MARKER, fxAfterglowPos, fxAfterglowAng )
+ if ( glowSound != "" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, fxAfterglowPos, glowSound )
+
+ OnThreadEnd(
+ function() : ( fx, glowSound, fxAfterglowPos )
+ {
+ DestroyFxIfValid( fx )
+ if ( glowSound != "" )
+ StopSoundAtPosition( fxAfterglowPos, glowSound )
+ }
+ )
+
+ npc.WaitSignal( "UnFrozen" )
+ DestroyFxIfValid( fx )
+ if ( glowSound != "" )
+ StopSoundAtPosition( fxAfterglowPos, glowSound )
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DestroyFxIfValid( entity fx )
+{
+ if ( !IsValid( fx) )
+ return
+ fx.Fire( "Stop" )
+ fx.Fire( "DestroyImmediately" )
+ fx.Destroy()
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TS_OnDeathNPC( entity npc, var damageInfo )
+{
+ thread TS_OnDeathNPCThread( npc, damageInfo )
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TS_OnDeathNPCThread( entity npc, var damageInfo )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ string editorClassname = GetEditorClass( npc )
+
+ if ( GetEntityTimelinePosition( npc ) == TIMEZONE_NIGHT )
+ {
+ if ( editorClassname == "npc_marvin" )
+ npc.SetSkin( 1 ) //bug where Marvins revert to base skin when killed
+
+ //TryTimeshiftGibDeath( npc, damageInfo )
+
+ //get out of here because we don't care about npcs in the present
+ return
+ }
+
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+
+ if ( !damageType )
+ return
+
+ printt( "explosive: " + ( damageType & DF_EXPLOSION ) )
+ printt( "bullet: " + ( damageType & DF_BULLET ) )
+ printt( "gib: " + ( damageType & DF_GIB ) )
+
+ if ( damageType & DF_GIB )
+ {
+ return
+ }
+
+ //if ( TryTimeshiftGibDeath( npc, damageInfo ) )
+ //return
+
+
+ //vector deathPos = npc.GetOrigin()
+ vector deathPos = npc.GetOrigin() + Vector( 0, 0, ( TIME_ZOFFSET * -1 ) )
+ //vector deathPos = npc.GetOrigin()
+
+
+
+
+ asset deathModel
+ entity corpseOrg
+ bool usesSingleDeathModel = true
+ int additionalZoffset = 0
+ if ( editorClassname == "" )
+ editorClassname = npc.GetClassName()
+
+ switch( editorClassname )
+ {
+ case "npc_prowler":
+ break
+ case "npc_marvin":
+ deathModel = MARVIN_MODEL_OVERGROWN
+ break
+ case "npc_stalker":
+ corpseOrg = file.stalkerCorpseOrg
+ usesSingleDeathModel = false
+ additionalZoffset = 16
+ break
+ case "npc_titan_ogre_minigun":
+ case "npc_titan":
+ corpseOrg = file.titanCorpseOrg
+ usesSingleDeathModel = false
+ additionalZoffset = 64
+ break
+ case "npc_soldier_imc_shotgun":
+ deathModel = IMC_CORPSE_MODEL_SHOTGUN
+ break
+ case "npc_soldier_imc_rifle":
+ case "npc_soldier":
+ deathModel = IMC_CORPSE_MODEL_RIFLE
+ break
+ case "npc_shield_captain_imc":
+ deathModel = IMC_CORPSE_MODEL_HEAVY
+ break
+ case "npc_soldier_imc_smg":
+ deathModel = IMC_CORPSE_MODEL_SMG
+ break
+ }
+
+
+ /*
+ int xAngle = 90
+ if ( CoinFlip() )
+ xAngle = -90
+ vector deathAng = Vector( xAngle, RandomFloatRange( 0, 360 ), RandomFloatRange( 0, 360 ) )
+ */
+
+ //randomly spin the body/gib collection on it's y axis
+ vector deathAng = Vector( 0, RandomFloatRange( 0, 360 ), 0 )
+
+ if ( ( deathModel == $"" ) && ( usesSingleDeathModel ) )
+ {
+ printt( "Unhandled corpse type: '" + editorClassname + "'")
+ return
+ }
+
+
+ array<entity> corpseModels
+
+ if ( usesSingleDeathModel )
+ {
+ entity corpse = CreatePropDynamic( deathModel, deathPos + Vector( 0, 0, 5), deathAng, 6 ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+ corpse.Hide()
+ if ( deathModel == MARVIN_MODEL_OVERGROWN )
+ corpse.SetSkin( 1 ) //mossy
+ corpseModels.append( corpse )
+ string anim = GetrandomDeathPose( deathModel )
+ vector newOrg = HackGetDeltaToRef( corpse.GetOrigin(), corpse.GetAngles(), corpse, anim )
+ thread PlayAnimTeleport( corpse, anim, newOrg, corpse.GetAngles() )
+ }
+ else
+ {
+
+ corpseOrg.SetOrigin( deathPos )
+ corpseOrg.SetAngles( deathAng )
+ array<entity> bodyPartArray = file.titanCorpsePieces
+ if ( editorClassname == "npc_stalker" )
+ bodyPartArray = file.stalkerCorpsePieces
+
+ foreach( bodypart in bodyPartArray )
+ {
+ asset modelName = bodypart.GetModelName()
+ vector angles = bodypart.GetAngles()
+ vector origin = bodypart.GetOrigin()
+ entity clonedModel = CreatePropPhysics( modelName, origin + Vector( 0, 0, 20 + additionalZoffset ), angles ) // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+ clonedModel.Hide()
+ corpseModels.append( clonedModel )
+ }
+
+
+ }
+
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_NIGHT )
+
+ foreach( corpse in corpseModels )
+ {
+ corpse.Show()
+
+ if ( usesSingleDeathModel )
+ corpse.BecomeRagdoll( Vector( 0,0,0 ), false )
+ }
+
+}
+
+/*
+ array<string> idles = [ "pt_S2S_crew_A_idle",
+ "pt_S2S_crew_B_idle",
+ "pt_S2S_crew_C_idle",
+ "pt_S2S_crew_D_idle" ]
+*/
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//HACK: need a better global way to spawn marvins using levelEd ents
+void function OnSpawnedMarvinSpawner( entity spawner )
+{
+ SpawnMarvin( spawner )
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+bool function TryTimeshiftGibDeath( entity npc, var damageInfo )
+{
+ if ( !IsValid( npc ) )
+ return false
+
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( !IsValid( damageSourceId ) )
+ return false
+ if ( damageSourceId != eDamageSourceId.mp_weapon_satchel )
+ return false
+
+
+ //string editorClassname = GetEditorClass( npc )
+ string classname = npc.GetClassName()
+
+ if ( !IsValidGibTarget( classname ) )
+ return false
+
+
+ vector npcOrigin = npc.GetOrigin()
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+
+ if ( !IsValid( npcOrigin ) )
+ return false
+
+ if ( !IsValid( damageOrigin ) )
+ return false
+
+ float minDist = 100
+ if ( Distance( npcOrigin, damageOrigin ) > minDist )
+ return false
+
+ EmitSoundAtPosition( TEAM_ANY, npcOrigin, "death.pinkmist" )
+ npc.Dissolve( ENTITY_DISSOLVE_PINKMIST, Vector( 0, 0, 0 ), 500 )
+
+ return true
+
+}
+
+bool function IsValidGibTarget( string classname )
+{
+ if ( classname == "npc_prowler" )
+ return true
+
+ if ( classname == "npc_soldier" )
+ return true
+
+ return false
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//HACK: need a better global way to spawn marvins using levelEd ents
+function SpawnMarvin( spawner )
+{
+ local origin = spawner.GetOrigin()
+ local angles = spawner.GetAngles()
+ entity npc_marvin = CreateEntity( "npc_marvin" )
+ SetTargetName( npc_marvin, UniqueString( "mp_random_marvin") )
+ npc_marvin.SetOrigin( origin )
+ npc_marvin.SetAngles( angles )
+ //npc_marvin.kv.rendercolor = "255 255 255"
+ npc_marvin.kv.health = -1
+ npc_marvin.kv.max_health = -1
+ npc_marvin.kv.spawnflags = 516 // Fall to ground, Fade Corpse
+ //npc_marvin.kv.FieldOfView = 0.5
+ //npc_marvin.kv.FieldOfViewAlert = 0.2
+ npc_marvin.kv.AccuracyMultiplier = 1.0
+ npc_marvin.kv.physdamagescale = 1.0
+ npc_marvin.kv.WeaponProficiency = eWeaponProficiency.GOOD
+
+ npc_marvin.s.bodytype <- MARVIN_TYPE_WORKER
+ npc_marvin.SetValueForModelKey( $"models/robots/marvin/marvin.mdl" )
+
+ DispatchSpawn( npc_marvin )
+
+ SetTeam( npc_marvin, TEAM_UNASSIGNED )
+
+ return npc_marvin
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+bool function PlayerInRange( vector pos1, vector pos2, float minRange )
+{
+
+ if ( Distance( pos1 , pos2 ) > minRange )
+ return false
+
+ return true
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+entity function GetSpectreDoorSwitchByDummyName( string dummyName )
+{
+ entity dummy = GetEntByScriptName( dummyName )
+ entity doorSwitch = GetClosest( file.spectreDoorPanels, dummy.GetOrigin() )
+
+ Assert( IsValid( doorSwitch ) )
+ Assert( Distance( doorSwitch.GetOrigin(), dummy.GetOrigin() ) < 50, "No doorSwitch within 50 units of dummy: " + dummyName )
+
+ return doorSwitch
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+void function TimeVortexThink( entity trigger )
+{
+ trigger.EndSignal( "OnDestroy" )
+
+ local result
+ entity player
+ entity fxEnt = trigger.GetLinkEnt()
+ Assert( IsValid( fxEnt ), "Time vortex trig at " + trigger.GetOrigin() + " isn't linked to anything")
+
+ var destinationTimeline = GetOppositeTimeline( GetEntityTimelinePosition( fxEnt ) )
+
+ //----------------
+ // Play looping fx
+ //----------------
+ entity FX = CreateEntity( "info_particle_system" )
+ FX.SetValueForEffectNameKey( FX_TIME_PORTAL )
+ FX.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ FX.kv.start_active = 1
+ FX.SetOrigin( fxEnt.GetOrigin() )
+ FX.SetAngles( fxEnt.GetAngles() + Vector( 90, 0, 0 ) )
+ DispatchSpawn( FX )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, fxEnt.GetOrigin(), SOUND_TIME_PORTAL_LOOP )
+
+
+ OnThreadEnd(
+ function() : ( FX, fxEnt )
+ {
+ StopSoundAtPosition( fxEnt.GetOrigin(), SOUND_TIME_PORTAL_LOOP )
+ DestroyFxIfValid( FX )
+ fxEnt.Destroy()
+ }
+ )
+
+
+ //----------------
+ // Think...
+ //----------------
+ while( 1 )
+ {
+ player = null
+ wait 0.1
+ result = trigger.WaitSignal( "OnTrigger" )
+
+ if ( !IsValid( result.activator ) )
+ continue
+ if ( result.activator.IsTitan() )
+ continue
+ if ( !result.activator.IsPlayer() )
+ continue
+
+ player = expect entity( result.activator )
+ player.EndSignal( "OnDeath" )
+ thread TimeVortexSoundThread( player )
+
+
+ SwapTimelines( player, destinationTimeline )
+
+ //thread TimeShiftSwapEffect( player )
+ wait 4
+
+ EmitSoundOnEntity( player, "Pilot_Time_Vortex_Deactivate" )
+
+ wait 1
+ float fadeTime = 0.3
+ float holdTime = 0
+ //(player, r, g, b, a, fadeTime, fadeHold, FFADE_ flags)
+ wait 0.75
+ ScreenFade( player, 255, 255, 255, 254, fadeTime, holdTime, FFADE_OUT | FFADE_PURGE )
+ wait 0.25
+
+ //HACK: can't time shift if player in the middle of hacking a panel
+ while ( !CanTimeShift( player ) )
+ wait 0.1
+
+ StopSoundOnEntity( player, "Pilot_Time_Vortex_Loop" )
+ SwapTimelines( player, GetOppositeTimeline( destinationTimeline ) )
+ wait 1
+ }
+
+}
+
+*/
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+void function TimeVortexSoundThread( player )
+{
+ player.EndSignal( "OnDeath" )
+
+ //need to delay 0.1 otherwise won't hear the sound when he is teleported
+ wait 0.1
+ EmitSoundOnEntity( player, "Pilot_Time_Vortex_Activate" )
+ EmitSoundOnEntity( player, "Pilot_Time_Vortex_Loop" )
+
+}
+
+*/
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SwapTimelineEffectsAndSound( entity player, timeZone )
+{
+
+
+
+
+ //---------------------------------
+ // Player has wrist mounted device
+ //---------------------------------
+ if ( Flag( "PlayerPickedUpTimeshiftDevice" ) )
+ {
+ //( amplitude frequency duration
+ CreateAirShake( player.GetOrigin(), 10, 50, 0.2, 10000 )
+
+ if ( player.IsTitan() )
+ {
+ float fadeTime = 0.1
+ float holdTime = 0.03
+ ScreenFade( player, 255, 255, 255, 254, fadeTime, holdTime, FFADE_IN | FFADE_PURGE )
+ CreateAirShake( player.GetOrigin(), 10, 50, 1, 20000 )
+ }
+
+
+ if ( timeZone == TIMEZONE_NIGHT )
+ {
+ //--------------------------
+ // switch to night/overgrown
+ //--------------------------
+ StopSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Past" )
+ EmitSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Present" )
+ }
+ else
+ {
+ //--------------------------
+ // switch to day/pristine
+ //--------------------------
+ StopSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Present" )
+ EmitSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Past" )
+
+ }
+
+ }
+ //---------------------------------
+ // Scripted timeshift, no device
+ //---------------------------------
+ else
+ {
+ float fadeTime = 0.1
+ float holdTime = 0.05
+ ScreenFade( player, 255, 255, 255, 254, fadeTime, holdTime, FFADE_IN | FFADE_PURGE )
+
+ //--------------
+ // FX
+ //--------------
+ EmitSoundOnEntity( player, "Timeshift_Scr_InvoluntaryShift" )
+ }
+
+
+
+
+
+ //-----------------------------------
+ // Impact table on ground
+ //-----------------------------------
+ TraceResults results = TraceLine( player.GetOrigin() + Vector( 0, 0, 32 ), player.GetOrigin() + Vector( 0, 0, -200 ), [ player ], TRACE_MASK_NPCSOLID_BRUSHONLY | TRACE_MASK_WATER, TRACE_COLLISION_GROUP_NONE )
+ if ( !results.startSolid && !results.allSolid )
+ PlayImpactFXTable( GetPosInOtherTimeline( results.endPos ), player, FX_IMPACT_TABLE_TIMESHIFT )
+
+ //-----------------------------------
+ // FX for viewmodel device
+ //-----------------------------------
+ if ( !Flag( "PlayerPickedUpTimeshiftDevice" ) )
+ return
+
+ entity timeShiftOffhand
+
+
+
+ //-------------------------------------
+ // Skin change for cinematic device equip only
+ //---------------------------------------
+ if ( !Flag( "DoingCinematicTimeshift" ) )
+ return
+
+ timeShiftOffhand = player.GetFirstPersonProxy()
+
+ if ( !IsValid( timeShiftOffhand) )
+ return
+
+ if ( timeZone == TIMEZONE_DAY )
+ {
+ timeShiftOffhand.SetSkin( 0 ) //orange 0
+ }
+
+ else if ( timeZone == TIMEZONE_NIGHT )
+ {
+ timeShiftOffhand.SetSkin( 1 ) //blue 1
+ }
+
+
+ //{ "hero_mil_jack_gauntlet_timeshift_skn_01"} // present amber
+ //{ "hero_mil_jack_gauntlet_timeshift_skn_01_v1"} // past blue
+ //{ "hero_mil_jack_gauntlet_timeshift_skn_01_v2"} // error red
+ //{ "hero_mil_jack_gauntlet_timeshift_skn_01_v3"} // off
+
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+var function GetOppositeTimeline( timeline )
+{
+ var destinationTimeline
+ if ( timeline == TIMEZONE_NIGHT )
+ destinationTimeline = TIMEZONE_DAY
+ else
+ destinationTimeline = TIMEZONE_NIGHT
+
+ return destinationTimeline
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+var function GetEntityTimelinePosition( ent )
+{
+ //What time period does this ent live in?
+ local z = ent.GetOrigin().z
+ if ( z < -6000 )
+ return TIMEZONE_FROZEN
+ else if ( z < 5300 )
+ return TIMEZONE_NIGHT
+ else
+ return TIMEZONE_DAY
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+var function GetTimelinePosition( vector pos )
+{
+ //What time period does this pos live in?
+ local z = pos.z
+ if ( z < -6000 )
+ return TIMEZONE_FROZEN
+ else if ( z < 5300 )
+ return TIMEZONE_NIGHT
+ else
+ return TIMEZONE_DAY
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DebugDrawOnEnt( ent )
+{
+ expect entity( ent )
+
+ while ( true )
+ {
+ DebugDrawSphere( ent.GetOrigin(), 16, 255, 255, 255, true, 0.22 )
+ wait 0.2
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+void function WaitTillPlayerTouchesTrigger( trigger )
+{
+ local result
+ while ( 1 )
+ {
+ result = trigger.WaitSignal( "OnStartTouch" )
+
+ if ( !IsValid( result.activator ) )
+ continue
+ if ( !result.activator.IsPlayer() )
+ continue
+ break
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function SetFlagWhenBreakablesDestroyed( string flagToSet, string scriptName, string instanceName )
+{
+ array < entity > breakableFuncBrushes = GetEntArrayByScriptNameInInstance( scriptName, instanceName )
+ foreach ( funcBrush in breakableFuncBrushes )
+ {
+ Assert( !( "flagToSetWhenDestroyed" in funcBrush.s ), "func brush '" + scriptName + "' instanceName '" + instanceName + "' has flag: " + flagToSet)
+ funcBrush.s.flagToSetWhenDestroyed <- flagToSet
+ }
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function OnDamagedFuncBrush( entity funcBrush, var damageInfo )
+{
+
+ //----------------------
+ // Return if not player
+ //----------------------
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+
+ if ( !damageSourceID )
+ return
+
+ if ( !damageAmount )
+ return
+
+ if ( !attacker )
+ return
+
+ if ( !attacker.IsPlayer() )
+ return
+
+ //-----------------------------------
+ // Do damage if correct damage type
+ //-----------------------------------
+ var health = funcBrush.s.health
+ var breakableType = funcBrush.s.breakableType
+ switch( breakableType )
+ {
+ case BREAKABLE_TYPE_AQUARIUM:
+ case BREAKABLE_TYPE_SATCHEL_DEBRIS_MEDIUM_WIDE:
+ case BREAKABLE_TYPE_SATCHEL_DEBRIS_MEDIUM:
+ DamageSatchelDebris( funcBrush, damageSourceID, damageAmount )
+ break
+ default:
+ Assert( 0, "Unhandled breakableType ' " + breakableType + "' at " + funcBrush.GetOrigin() )
+ }
+
+
+ if ( funcBrush.s.health > 0 )
+ return
+
+ DestroyFuncBrushBreakable( funcBrush )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DestroyFuncBrushBreakable( funcBrush )
+{
+ expect entity( funcBrush )
+
+ if ( !IsValid( funcBrush ) )
+ return
+
+ //----------------------
+ // Debris blows up
+ //----------------------
+ Signal( funcBrush, "BreakableDestroyed" )
+
+ entity hintTrigger
+ if ( ( "hintTrigger" in funcBrush.s ) )
+ hintTrigger = expect entity( funcBrush.s.hintTrigger )
+
+ entity fxEnt = expect entity( funcBrush.s.fxEnt )
+ asset explosionFx = expect asset( funcBrush.s.fxExplode )
+ var explosionSound = funcBrush.s.soundExplode
+ vector origin = fxEnt.GetOrigin()
+ vector angles = fxEnt.GetAngles()
+
+ PlayFX( explosionFx, origin, angles )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, explosionSound )
+
+ //--------------------------------------
+ // Swap out damaged/intact if applicable
+ //-------------------------------------
+ if ( ( "brokenState" in funcBrush.s ) && ( funcBrush.s.brokenState != null ) )
+ funcBrush.s.brokenState.Show()
+
+ //--------------------------------------
+ // Delete intact version
+ //-------------------------------------
+ funcBrush.Destroy()
+
+ if ( "flagToSetWhenDestroyed" in funcBrush.s )
+ {
+ FlagSet( expect string( funcBrush.s.flagToSetWhenDestroyed ) )
+ }
+
+ //----------------------
+ // Cleanup
+ //----------------------
+ if ( IsValid( hintTrigger ) )
+ hintTrigger.Destroy()
+ fxEnt.Destroy()
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DamageSatchelDebris( funcBrush, damageSourceID, damageAmount )
+{
+ if ( damageSourceID != eDamageSourceId.mp_weapon_satchel )
+ return
+ funcBrush.s.health = funcBrush.s.health - damageAmount
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TriggerHazardThink( entity trigger )
+{
+ trigger.EndSignal( "OnDeath" )
+ trigger.EndSignal( "OnDestroy" )
+
+ int damageAmt
+ float interval
+ string scriptName = trigger.GetScriptName()
+ asset fx
+ var sound
+ local scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN
+
+ if ( scriptName.find( "fireSmall" ) != null )
+ scriptName = "fireSmall"
+
+ if ( scriptName.find( "fireHuge" ) != null )
+ scriptName = "fireHuge"
+
+ if ( scriptName.find( "fireMedium" ) != null )
+ scriptName = "fireMedium"
+
+ if ( scriptName.find( "radiation" ) != null )
+ scriptName = "radiation"
+
+ string soundDamage = ""
+ int damageID = eDamageSourceId.burn
+
+ switch( scriptName )
+ {
+ case "electricity":
+ fx = FX_ELECTRICITY
+ sound = SOUND_ELECTRICITY
+ scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN
+ damageAmt = 25
+ interval = 0.75
+ soundDamage = "flesh_electrical_damage_1p"
+ damageID = eDamageSourceId.electric_conduit
+ break
+ case "fireMedium":
+ fx = FX_FIRE_MEDIUM
+ sound = SOUND_FIRE_MEDIUM
+ scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN
+ damageAmt = 25
+ interval = 1
+ soundDamage = "flesh_fire_damage_1p"
+ damageID = eDamageSourceId.burn
+ break
+ case "fireSmall":
+ fx = FX_FIRE_SMALL
+ sound = SOUND_FIRE_MEDIUM
+ scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN
+ damageAmt = 25
+ interval = 1
+ soundDamage = "flesh_fire_damage_1p"
+ damageID = eDamageSourceId.burn
+ break
+ case "fireHuge":
+ fx = FX_FIRE_HUGE
+ sound = SOUND_FIRE_HUGE
+ scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN
+ damageAmt = 25
+ interval = 1
+ soundDamage = "flesh_fire_damage_1p"
+ damageID = eDamageSourceId.burn
+ break
+ case "radiation":
+ case "trigger_concourse_radiation":
+ fx = FX_RADIATION
+ scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN
+ damageAmt = 100
+ interval = 1
+ soundDamage = "flesh_fire_damage_1p"
+ damageID = eDamageSourceId.burn
+ break
+ default:
+ Assert( 0, "Unhandled hazard type ' " + scriptName + "' at " + trigger.GetOrigin() )
+ }
+
+ array< entity > linkedEnts = trigger.GetLinkEntArray()
+ array< var > fxHandles
+ //Assert( linkedEnts.len() > 0, "Hazard trigger at " + trigger.GetOrigin() + " has no linked ents to play fx on" )
+
+ //-------------------
+ // Hazard sound/FX
+ //--------------------
+ foreach( ent in linkedEnts )
+ thread HackPlayLoopEffectOnEntity( fx, ent )
+
+ if ( sound )
+ thread EmitSoundAtPositionHack( TEAM_UNASSIGNED, trigger.GetOrigin(), sound )
+
+ OnThreadEnd(
+ function() : ( trigger, linkedEnts )
+ {
+ if ( IsValid( trigger ) )
+ trigger.Destroy()
+ foreach( ent in linkedEnts )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ }
+ )
+ //-------------------
+ // Damage player
+ //--------------------
+
+ var player
+ local result
+ vector origin
+ while ( IsValid( trigger) )
+ {
+
+ wait 0.1
+
+ player = null
+ result = trigger.WaitSignal( "OnTrigger" )
+
+ //if ( level.isTimeTraveling )
+ // continue
+
+ if ( !IsValid( result.activator ) )
+ continue
+ if ( result.activator.IsPlayer() )
+ {
+ player = result.activator
+
+ if ( player.s.isTimeTraveling )
+ continue
+
+ while ( trigger.IsTouching( player ) )
+ {
+ player.TakeDamage( damageAmt, svGlobal.worldspawn, svGlobal.worldspawn, { origin = player.GetOrigin(), scriptType = scriptTypeMask, damageSourceId = damageID } )
+ CreateShakeRumbleOnly( expect entity( player ).GetOrigin(), 10, 105, interval )
+ if ( soundDamage != "" )
+ EmitSoundOnEntity( player, soundDamage )
+ printt( "Player taking damage: " + damageAmt )
+ wait interval
+ }
+ }
+ }
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function EmitSoundAtPositionHack( int team, vector origin, var sound )
+{
+ //Need to delay 0.2 before playing at level start, Baker/Barb know this is a big but said will fix next game
+ wait 0.2
+ EmitSoundAtPosition( team, origin, sound )
+
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// HACK - need better way to play fx on server
+void function HackPlayLoopEffectOnEntity( asset fxName, entity ent )
+{
+ ent.EndSignal( "OnDeath" )
+ ent.EndSignal( "OnDestroy" )
+ entity fxHandle
+ float waitTime
+
+
+
+ fxHandle = CreateEntity( "info_particle_system" )
+ fxHandle.SetValueForEffectNameKey( fxName )
+ fxHandle.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ fxHandle.kv.start_active = 1
+ fxHandle.SetOrigin( ent.GetOrigin() )
+ fxHandle.SetAngles( ent.GetAngles() )
+ DispatchSpawn( fxHandle )
+
+
+ OnThreadEnd(
+ function() : ( fxHandle )
+ {
+ DestroyFxIfValid( fxHandle )
+ }
+ )
+
+
+ WaitForever()
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DropHangingOgre( entity titan, entity player )
+{
+ thread PlayAnim( titan.s.rack, "tr_AI_titanrack_bootup_heavy" )
+ PlayAnimTeleport( titan, "ht_AI_titanrack_bootup_heavy", titan.s.rack )
+ wait 0.5
+ Embark_Allow( player )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DropHangingOgreOnFlag( entity titan, string flagToDrop, entity player )
+{
+ Assert( IsValid( titan ) )
+ FlagWait( flagToDrop )
+ thread DropHangingOgre( titan, player )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function GenericSparks( entity ent )
+{
+ vector origin = ent.GetOrigin()
+ vector angles = ent.GetAngles()
+
+ entity fxHandle
+ wait ( RandomFloatRange( 0.1, 2.5 ) )
+
+ while( IsValid( ent ) )
+ {
+ if ( GetBugReproNum() == 007 )
+ {
+ //fails silently to play fx/sound when it's an info_target
+ fxHandle = PlayLoopFXOnEntity( FX_SPARKS, ent )
+ EmitSoundOnEntity( ent, SOUND_SPARKS )
+ }
+ else
+ {
+ fxHandle = PlayFX( FX_SPARKS, origin, angles )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, SOUND_SPARKS )
+ }
+
+ wait ( RandomFloatRange( 1.7, 5.2 ) )
+
+ if ( IsValid( fxHandle ) )
+ {
+ DestroyFxIfValid( fxHandle )
+ }
+ wait ( RandomFloatRange( 0.1, 0.4 ) )
+ }
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function ZombieSpectreSpawnAndDie( entity npc )
+{
+ npc.EndSignal( "OnDeath" )
+
+ wait ( RandomFloatRange( 0.2, 1.75 ) )
+
+ npc.Die()
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function RackSpawnCallback( entity npc, entity activator )
+{
+ string scriptName = npc.GetScriptName()
+
+ if ( scriptName == "rusted_spectre" )
+ thread ZombieSpectreSpawnAndDie( npc )
+
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DestroyArray( array entities )
+{
+ foreach( ent in entities )
+ {
+ if( IsValid( ent ) )
+ ent.Destroy()
+ }
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function MoveEntityToOppositeTimePeriod( entity ent )
+{
+ int zOffset = TIME_ZOFFSET
+ if ( GetEntityTimelinePosition( ent ) == TIMEZONE_DAY )
+ zOffset = TIME_ZOFFSET * -1
+
+ ent.SetOrigin( ent.GetOrigin() + Vector( 0, 0, TIME_ZOFFSET ) )
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DeleteFireHazards( string scriptName )
+{
+array< entity > fireTriggers = GetEntArrayByScriptName( scriptName )
+ entity fxEnt
+ foreach( trigger in fireTriggers )
+ {
+ fxEnt = trigger.GetLinkEnt()
+ fxEnt.Destroy()
+ trigger.Destroy()
+ }
+
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function SpawnWallSpectreGroupWhenInRange( entity player, array< entity > propSpawners, int maxToSpawn, string flagToAbort = "", string flagToSetWhenDone = "", float delayMin = 3, float delayMax = 5, string flagToSet = "", bool requireLookAt = false )
+{
+ Assert( propSpawners.len() >= maxToSpawn, "Max to spawn( " + maxToSpawn + ") is greater than number of propSpawners (" + propSpawners.len() + ") " )
+ int spectresSpawned = 0
+ entity spawner
+
+ var spawnerTimezone = GetEntityTimelinePosition( propSpawners[ 0 ] )
+
+ while( spectresSpawned < maxToSpawn )
+ {
+ if ( ( flagToAbort != "") && ( Flag( flagToAbort ) ) )
+ break
+
+
+ // Don't spawn if player not in the same timezone
+ if ( spawnerTimezone != GetEntityTimelinePosition( player ) )
+ {
+ wait 0.1
+ continue
+
+ }
+
+ spawner = GetBestPropSpawnerFromGroup( propSpawners, player, requireLookAt )
+ if ( !IsValid( spawner ) )
+ {
+ wait 0.1
+ continue
+ }
+
+ spawner.Signal( "PropSpawnerActivate" )
+ spectresSpawned++
+
+ if ( ( flagToSet != "" ) && ( !Flag( flagToSet ) ) )
+ FlagSet( flagToSet )
+
+ propSpawners.fastremovebyvalue( spawner )
+ if ( ( spectresSpawned == maxToSpawn ) && ( flagToSetWhenDone != "" ) )
+ {
+ FlagSet( flagToSetWhenDone )
+ break
+ }
+
+ wait( RandomFloatRange( delayMin, delayMax ) )
+ }
+}
+*/
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SpawnShowcaseGroupWhenInRange( entity player, array< entity > propSpawners, int maxToSpawn, string flagToAbort = "", string flagToSetWhenDone = "", float delayMin = 3, float delayMax = 5, string flagToSet = "", bool requireLookAt = false )
+{
+ Assert( propSpawners.len() >= maxToSpawn, "Max to spawn( " + maxToSpawn + ") is greater than number of propSpawners (" + propSpawners.len() + ") " )
+ int dudesSpawned = 0
+ entity spawnProp
+
+ var spawnerTimezone = GetEntityTimelinePosition( propSpawners[ 0 ] )
+
+ while( dudesSpawned < maxToSpawn )
+ {
+ if ( ( flagToAbort != "") && ( Flag( flagToAbort ) ) )
+ break
+
+
+ // Don't spawn if player not in the same timezone
+ if ( spawnerTimezone != GetEntityTimelinePosition( player ) )
+ {
+ wait 0.1
+ continue
+
+ }
+
+ spawnProp = GetBestPropSpawnerFromGroup( propSpawners, player, requireLookAt )
+ if ( !IsValid( spawnProp ) )
+ {
+ wait 0.1
+ continue
+ }
+
+ if ( IsSpectreRackDoorSpawner( spawnProp ) )
+ spawnProp.Signal( "PropSpawnerActivate" )
+ else
+ thread ShowcaseSpawn( spawnProp )
+
+ dudesSpawned++
+
+ if ( ( flagToSet != "" ) && ( !Flag( flagToSet ) ) )
+ FlagSet( flagToSet )
+
+ propSpawners.fastremovebyvalue( spawnProp )
+ if ( ( dudesSpawned == maxToSpawn ) && ( flagToSetWhenDone != "" ) )
+ {
+ FlagSet( flagToSetWhenDone )
+ break
+ }
+
+ wait( RandomFloatRange( delayMin, delayMax ) )
+ }
+
+ if ( FlagExists( flagToSetWhenDone ) )
+ FlagSet( flagToSetWhenDone )
+
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+entity function GetBestPropSpawnerFromGroup( array< entity > propSpawners, entity player, bool requireLookAt = false )
+{
+ entity bestSpawnerProp
+
+ array< entity > propSpawnersOrderedClosest = ArrayClosest( propSpawners, player.GetOrigin() )
+
+ float minDistSqr = 128 * 128
+ bool doTrace = true
+ float degrees = 90
+
+ foreach( spawnerProp in propSpawnersOrderedClosest )
+ {
+ if ( DistanceSqr( player.GetOrigin(), spawnerProp.GetOrigin() ) < minDistSqr )
+ continue
+
+ if ( TS_WithinPlayerFOV( spawnerProp.GetOrigin() + Vector( 0, 0, 16) ) )
+ {
+ bestSpawnerProp = spawnerProp
+ break
+ }
+ }
+
+ if ( ( !IsValid( bestSpawnerProp ) ) && ( requireLookAt == false ) )
+ bestSpawnerProp = GetClosest( propSpawners, player.GetOrigin() )
+
+ return bestSpawnerProp
+}
+
+//////////////////////////////////////////////////////////////
+// HACK: Move to utility?
+bool function TS_WithinPlayerFOV( targetPos, MinDot = 0.8 )
+{
+ expect vector( targetPos )
+ entity player = GetPlayerArray()[0]
+ if ( !player )
+ return false
+
+ float dot = VectorDot_PlayerToOrigin( player, targetPos )
+ if ( dot < MinDot )
+ return false
+
+ return true
+}
+//////////////////////////////////////////////////////////////
+void function RemoveBlocker( string scriptName )
+{
+ array <entity> blockers = GetEntArrayByScriptName( scriptName )
+ Assert( blockers.len() > 0, "No blockers found with scriptName " + scriptName )
+ foreach( blocker in blockers )
+ {
+ blocker.Hide() //Does this even do anything anymore?
+ blocker.MakeInvisible()
+ blocker.NotSolid()
+ }
+
+}
+//////////////////////////////////////////////////////////////
+void function RestoreBlocker( string scriptName )
+{
+ array <entity> blockers = GetEntArrayByScriptName( scriptName )
+ Assert( blockers.len() > 0, "No blockers found with scriptName " + scriptName )
+ foreach( blocker in blockers )
+ {
+ blocker.Show() //Does this even do anything anymore?
+ blocker.MakeVisible()
+ blocker.Solid()
+ }
+}
+
+//////////////////////////////////////////////////////////////
+void function TempExplosion( vector origin )
+{
+ CreateAirShake( origin, 10, 105, 1.25 )
+ PlayFX( FX_BREAKABLE_SATCHEL_DEBRIS_MEDIUM, origin )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, SOUND_BREAKABLE_SATCHEL_DEBRIS_MEDIUM )
+}
+//////////////////////////////////////////////////////////////
+void function DestroyEntByScriptName( string scriptName )
+{
+ entity ent = GetEntByScriptName( scriptName )
+ Assert( IsValid( ent ) )
+ ent.Destroy()
+
+}
+
+//////////////////////////////////////////////////////////////
+void function DestroyInstancesByScriptInstanceName( string scriptName, string instanceName )
+{
+ array< entity > ents = GetEntArrayByScriptNameInInstance( scriptName, instanceName )
+ Assert( ents.len() > 0, "No entities with script_name/instance_name combo: " + scriptName + " / " + instanceName )
+ foreach( ent in ents )
+ ent.Destroy()
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function WaittillSomeDudesAreDead( string scriptName, int numberToWaitToBeDead, string flagToSet = "" )
+{
+ array< entity > entities = GetEntArrayByScriptName( scriptName )
+ Assert( entities.len() > 0 )
+ array< entity > dudes
+ foreach( ent in entities )
+ {
+ if ( ent.IsNPC() )
+ dudes.append( ent )
+ }
+ if ( dudes.len() == 0 )
+ {
+ printt( "Warning: WaittillSomeDudesAreDead returning. No npcs exist for scriptName " + scriptName )
+ return
+ }
+ Assert( dudes.len() >= numberToWaitToBeDead )
+ int dudesThatHaveDied = 0
+
+ while( dudesThatHaveDied < numberToWaitToBeDead )
+ {
+ wait 0.1
+ foreach( dude in dudes )
+ {
+ if ( !IsAlive( dude ) )
+ {
+ dudesThatHaveDied++
+ dudes.fastremovebyvalue( dude )
+ }
+ }
+ }
+
+ if ( flagToSet != "" )
+ FlagSet( flagToSet )
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function WaittillPlayerSwitchesTimezone( var timeZoneToWaitFor )
+{
+
+ if ( level.timeZone == timeZoneToWaitFor )
+ return
+
+ while( level.timeZone != timeZoneToWaitFor )
+ wait 0.1
+
+ wait 0.1
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TimeshiftPlayerThink( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ vector posInOtherTimeline
+ vector playerOrigin
+ var timeZoneCurrent
+ float minDist = 32
+ float minDistSqr = minDist * minDist
+ vector idealPosInOvergrown
+ vector idealPosInPristine
+
+ wait 0.1
+
+ while( true )
+ {
+ WaitFrame()
+
+ playerOrigin = player.GetOrigin()
+ timeZoneCurrent = GetTimelinePosition( playerOrigin )
+ if ( timeZoneCurrent == TIMEZONE_FROZEN )
+ return
+
+ posInOtherTimeline = GetPosInOtherTimeline( playerOrigin )
+
+ if ( timeZoneCurrent == TIMEZONE_NIGHT )
+ {
+ idealPosInOvergrown = playerOrigin
+ idealPosInPristine = posInOtherTimeline
+ }
+ else if ( timeZoneCurrent == TIMEZONE_DAY )
+ {
+ idealPosInOvergrown = posInOtherTimeline
+ idealPosInPristine = playerOrigin
+ }
+ else
+ printl( "Player in frozen world, no need to run the TimeshiftPlayerThinkThread anymore" )
+
+ //-------------------------------------------------------------
+ // get last good pos in current timeline (player's pos, usually)
+ //-------------------------------------------------------------
+ if ( !PlayerPosInSolid( player, playerOrigin ) )
+ {
+ if ( timeZoneCurrent == TIMEZONE_NIGHT )
+ //file.lastGoodTimeshiftPosOvergrown = playerOrigin
+ player.s.lastGoodTimeshiftPosOvergrown = playerOrigin
+ else if ( timeZoneCurrent == TIMEZONE_DAY )
+ //file.lastGoodTimeshiftPosPristine = playerOrigin
+ player.s.lastGoodTimeshiftPosPristine = playerOrigin
+ else
+ printl( "Player in frozen world, no need to run the TimeshiftPlayerThinkThread anymore" )
+ }
+ else
+ {
+ if ( GetBugReproNum() == 88 )
+ printt( "Player is stuck in solid in home timeline: " + playerOrigin )
+ }
+
+ //--------------------------------------
+ // get last good pos in other timeline
+ //--------------------------------------
+ if ( !PlayerPosInSolid( player, posInOtherTimeline ) )
+ {
+ if ( timeZoneCurrent == TIMEZONE_NIGHT )
+ {
+ //file.lastGoodTimeshiftPosPristine = posInOtherTimeline
+ player.s.lastGoodTimeshiftPosPristine = posInOtherTimeline
+
+ }
+ else if ( timeZoneCurrent == TIMEZONE_DAY )
+ {
+ //file.lastGoodTimeshiftPosOvergrown = posInOtherTimeline
+ player.s.lastGoodTimeshiftPosOvergrown = posInOtherTimeline
+ }
+ else
+ printl( "Player in frozen world, no need to run the TimeshiftPlayerThinkThread anymore" )
+ }
+ else
+ {
+ if ( GetBugReproNum() == 88 )
+ {
+ printt( "******WARNING: player would be in solid in other timeline: " + posInOtherTimeline )
+ thread DebugDrawBadCollisionBox( posInOtherTimeline )
+ }
+
+
+ }
+
+ //-------------------------------------------
+ // Assert if vectors are in the wrong timeline
+ //-------------------------------------------
+ //Assert ( GetTimelinePosition( file.lastGoodTimeshiftPosPristine ) == TIMEZONE_DAY, "lastGoodTimeshiftPosPristine ( " + file.lastGoodTimeshiftPosPristine + " ) is not in the proper timeZone" )
+ //Assert ( GetTimelinePosition( file.lastGoodTimeshiftPosOvergrown ) == TIMEZONE_NIGHT, "lastGoodTimeshiftPosPristine ( " + file.lastGoodTimeshiftPosPristine + " ) is not in the proper timeZone" )
+ Assert ( GetTimelinePosition( player.s.lastGoodTimeshiftPosPristine ) == TIMEZONE_DAY, "lastGoodTimeshiftPosPristine ( " + player.s.lastGoodTimeshiftPosPristine + " ) is not in the proper timeZone" )
+ Assert ( GetTimelinePosition( player.s.lastGoodTimeshiftPosOvergrown ) == TIMEZONE_NIGHT, "lastGoodTimeshiftPosPristine ( " + player.s.lastGoodTimeshiftPosPristine + " ) is not in the proper timeZone" )
+
+ //---------------------------------------------------------------
+ // Check if disparity between ideal pos and proposed is too huge
+ //----------------------------------------------------------------
+ //if ( ( DistanceSqr( file.lastGoodTimeshiftPosOvergrown, idealPosInOvergrown ) > minDistSqr ) && ( GetBugReproNum() == 88 ) )
+ if ( ( DistanceSqr( player.s.lastGoodTimeshiftPosOvergrown, idealPosInOvergrown ) > minDistSqr ) && ( GetBugReproNum() == 88 ) )
+ {
+ printl( "*****************WARNING:")
+ //printt( "lastGoodTimeshiftPosOvergrown( " + file.lastGoodTimeshiftPosOvergrown + " > " + minDist + " from ( " + idealPosInOvergrown + ")" )
+ printt( "lastGoodTimeshiftPosOvergrown( " + player.s.lastGoodTimeshiftPosOvergrown + " > " + minDist + " from ( " + idealPosInOvergrown + ")" )
+ //thread DebugDrawBadTeleportBoxes( file.lastGoodTimeshiftPosOvergrown, idealPosInOvergrown )
+ var pos = player.s.lastGoodTimeshiftPosOvergrown
+ thread DebugDrawBadTeleportBoxes( expect vector( pos ), idealPosInOvergrown )
+ }
+ //if ( ( DistanceSqr( file.lastGoodTimeshiftPosPristine, idealPosInPristine ) > minDistSqr ) && ( GetBugReproNum() == 88 ) )
+ if ( ( DistanceSqr( player.s.lastGoodTimeshiftPosPristine, idealPosInPristine ) > minDistSqr ) && ( GetBugReproNum() == 88 ) )
+ {
+ printl( "*****************WARNING:")
+ //printt( "lastGoodTimeshiftPosPristine( " + file.lastGoodTimeshiftPosPristine + " > " + minDist + " from ( " + idealPosInPristine + ")" )
+ printt( "lastGoodTimeshiftPosPristine( " + player.s.lastGoodTimeshiftPosPristine + " > " + minDist + " from ( " + idealPosInPristine + ")" )
+ //thread DebugDrawBadTeleportBoxes( file.lastGoodTimeshiftPosPristine, idealPosInPristine )
+ var pos = player.s.lastGoodTimeshiftPosPristine
+ thread DebugDrawBadTeleportBoxes( expect vector( pos ), idealPosInPristine )
+ }
+ //Assert( DistanceSqr( file.lastGoodTimeshiftPosOvergrown, idealPosInOvergrown ) < minDistSqr, "lastGoodTimeshiftPosOvergrown( " + file.lastGoodTimeshiftPosOvergrown + " > " + minDist + " from ( " + idealPosInOvergrown + ")" )
+ //Assert( DistanceSqr( file.lastGoodTimeshiftPosPristine, idealPosInPristine ) < minDistSqr, "lastGoodTimeshiftPosPristine( " + file.lastGoodTimeshiftPosPristine + " > " + minDist + " from ( " + idealPosInPristine + ")" )
+
+ }
+}
+
+
+void function DebugDrawBadTeleportBoxes( vector badPos, vector goodPos )
+{
+
+ vector boxSize1 = Vector(-16,-16,0)
+ vector boxSize2 = Vector(16,16,72)
+ vector zOffset = Vector( 0, 0, 36)
+
+ while( true )
+ {
+ wait 0.15
+ //r, g, b, a, drwTime
+ DebugDrawBox( badPos, boxSize1, boxSize2, 255, 0, 0, 1, 0.15 )
+ DebugDrawBox( goodPos, boxSize1, boxSize2, 0, 255, 0, 1, 0.15 )
+ DebugDrawLine( badPos + zOffset, goodPos + zOffset, 255, 0, 0, true, 0.15 )
+ DebugDrawText( badPos + zOffset, "Bad TimeTravel Teleports", true, 0.15 )
+ }
+
+}
+
+
+void function DebugDrawAudioLogNumber( entity audioLogModel, string number )
+{
+ audioLogModel.Signal( "AudioLogDebugDraw" )
+ audioLogModel.EndSignal( "AudioLogDebugDraw" )
+
+ vector pos = audioLogModel.GetOrigin()
+ while( true )
+ {
+ wait 0.15
+ DebugDrawText( pos, number, true, 0.15 )
+ }
+
+
+}
+void function DebugDrawBadCollisionBox( vector badPos )
+{
+
+ vector boxSize1 = Vector(-16,-16,0)
+ vector boxSize2 = Vector(16,16,72)
+ vector zOffset = Vector( 0, 0, 36)
+
+ while( true )
+ {
+ wait 0.15
+ //r, g, b, a, drwTime
+ DebugDrawBox( badPos, boxSize1, boxSize2, 255, 255, 0, 1, 0.15 )
+ DebugDrawText( badPos + zOffset, "Timeshift into solid", true, 0.15 )
+ }
+
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////
+void function OnPlayerDamage_TimeShift( entity player, var damageInfo )
+{
+ if ( level.allowTimeTravel == false )
+ return
+
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ local damageAmount = DamageInfo_GetDamage( damageInfo )
+
+ if ( !IsValid( attacker ) )
+ return
+
+ if ( !attacker.IsNPC() )
+ return
+
+ int playerHealth = player.GetHealth()
+ int playerMaxHealth = player.GetMaxHealth()
+
+ if ( !Flag( "DisplayTheDamageHint" ) )
+ return
+
+ //if ( playerHealth > ( playerMaxHealth - 50 ) )
+ //return
+
+ //Show damage hint if we are at 60% or lower
+ printt( "Player health: ", playerHealth / playerMaxHealth.tofloat() )
+ if ( playerHealth / playerMaxHealth.tofloat() <= 0.90 )
+ thread DamageHintTillTimetravel( player )
+
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+function DamageHintTillTimetravel( entity player )
+{
+
+ if ( file.isDisplayingDamageText )
+ return
+
+ file.isDisplayingDamageText = true
+ file.isDisplayingTimeshiftHint = true
+
+ player.EndSignal( "OnDeath" )
+ player.Signal( "DisplayingDamageHint" )
+ player.EndSignal( "DisplayingDamageHint" )
+ EndSignal( player, "OnTimeFlippedTimezoneNight" )
+ EndSignal( player, "OnTimeFlippedTimezoneDay" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ ClearOnscreenHint( player )
+ file.isDisplayingDamageText = false
+ file.isDisplayingTimeshiftHint = false
+ }
+ )
+
+ thread DisplayOnscreenHint( player, "timeshift_hint_combat", 3.0 )
+ wait 3
+
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+void function TriggerTimehintThink( entity trigger )
+{
+ wait 1
+
+ trigger.EndSignal( "OnDestroy" )
+ string flagToAbort = trigger.GetTargetName()
+ Assert( IsValid( flagToAbort ) )
+ if ( Flag( flagToAbort ) )
+ return
+ FlagEnd( flagToAbort )
+
+ var timeZoneToShowHint = GetEntityTimelinePosition( trigger )
+
+
+ local result
+ entity player
+
+ while( true )
+ {
+ result = trigger.WaitSignal( "OnTrigger" )
+
+ if ( !IsValid( result.activator ) )
+ continue
+ if ( !result.activator.IsPlayer() )
+ continue
+ if ( result.activator.IsTitan() )
+ continue
+
+ player = expect entity( result.activator )
+
+ break
+ }
+
+ thread TimeshiftHint( player, timeZoneToShowHint, flagToAbort, "#BLANK_TEXT", trigger )
+
+}
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function TimeshiftHint( entity player, var timeZoneToShowHint, string flagToAbort, string message, entity trigger = null )
+{
+ player.EndSignal( "OnDeath" )
+ if( !IsValid( player ) )
+ return
+
+ if ( IsValid( trigger ) )
+ trigger.EndSignal( "OnDestroy")
+
+ Assert( FlagExists( flagToAbort ) )
+ if ( Flag( flagToAbort ) )
+ return
+ FlagEnd( flagToAbort )
+
+ //--------------------------------
+ // Display in any timezone?
+ //--------------------------------
+ bool hintIsTimezoneSpecific = true
+ if ( timeZoneToShowHint == TIMEZONE_ALL )
+ hintIsTimezoneSpecific = false
+
+ //--------------------------------`
+ // Custom hint message or default?
+ //--------------------------------
+ string hintMessage
+ if ( message != "" )
+ hintMessage = message
+ else
+ {
+ if ( timeZoneToShowHint == TIMEZONE_DAY )
+ hintMessage = "timeshift_hint_present"
+ else if ( timeZoneToShowHint == TIMEZONE_NIGHT )
+ hintMessage = "timeshift_hint_past"
+ else if ( timeZoneToShowHint == TIMEZONE_ALL )
+ hintMessage = "timeshift_hint_default"
+ }
+
+ //-----------
+ // Cleanup
+ //-----------
+ /*
+ OnThreadEnd(
+ function() : ( trigger )
+ {
+ ClearOnscreenHint( player )
+ if ( IsValid( trigger ) )
+ trigger.Destroy()
+ }
+ )
+ */
+
+ //----------------------------
+ // Hint message display logic
+ //----------------------------
+
+ var hintTimezone = GetOppositeTimeline( timeZoneToShowHint )
+
+ bool isDisplayingMsg = false
+ file.isDisplayingTimeshiftHint = false
+
+ float ticker = 0.0
+ float displayTime = 5.0
+ float increment = 0.25
+
+ //---------------------------------------------
+ // Don't be condescending, before displaying
+ //------------------------------------------------
+ wait 2
+
+ while( true )
+ {
+ wait increment
+
+ //----------------------------------------------------
+ // No message if player not touching (optional) trigger
+ //----------------------------------------------------
+ if ( IsValid( trigger ) )
+ {
+ if ( !trigger.IsTouching( player ) )
+ {
+ ClearOnscreenHint( player )
+ continue
+ }
+
+ }
+
+ //----------------------------------------------------
+ // No message if player has swapped to correct timeline
+ //----------------------------------------------------
+ if ( ( GetEntityTimelinePosition( player ) != timeZoneToShowHint ) && ( hintIsTimezoneSpecific ) )
+ {
+ ClearOnscreenHint( player )
+ continue
+ }
+
+ //----------------------------------------------------
+ // Clear msg and wait if we've displayed it long enough
+ //----------------------------------------------------
+ if ( ticker >= displayTime )
+ {
+ ClearOnscreenHint( player )
+ isDisplayingMsg = false
+ file.isDisplayingTimeshiftHint = false
+ wait 5
+ }
+
+ //----------------------------------------------------
+ // Increment ticker if we are already displaying message
+ //----------------------------------------------------
+ if ( isDisplayingMsg )
+ {
+ ticker = ticker + increment
+ continue
+ }
+ //------------------------------------
+ // Display the hint, reset the ticker
+ //------------------------------------
+ else
+ {
+ ticker = 0.0
+ DisplayOnscreenHint( player, hintMessage, displayTime )
+ isDisplayingMsg = true
+ file.isDisplayingTimeshiftHint = true
+ }
+
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+function HACK_DisableTurret( turret, shield = false )
+{
+ //turret.EndSignal( "OnDestroy" )
+
+
+ turret.EnableNPCFlag( NPC_IGNORE_ALL )
+ turret.SetNoTarget( true )
+ //turret.EnableNPCFlag( NPC_DISABLE_SENSING )
+ turret.Anim_ScriptedPlay( "undeploy" )
+ wait 1.0
+ turret.DisableTurret()
+ turret.UnsetUsable()
+ turret.SetTitle( "" )
+
+ if ( !shield )
+ turret.Signal( "TurretShieldWallRelease" )
+ //turret.Anim_Stop()
+}
+
+function HACK_EnableTurret( turret )
+{
+ //turret.EndSignal( "OnDestroy" )
+
+ turret.Anim_ScriptedPlay( "deploy" )
+ wait 1.0
+ turret.EnableTurret()
+ turret.kv.AccuracyMultiplier = 100
+
+ //wait 1.0
+ //turret.Anim_Stop()
+ turret.DisableNPCFlag( NPC_IGNORE_ALL )
+ turret.SetNoTarget( false )
+ //turret.DisableNPCFlag( NPC_DISABLE_SENSING )
+}
+
+void function OnSpawnedScriptedSwitch( entity button )
+{
+ array< entity > linkedEnts = button.GetLinkEntArray()
+ array< entity > turrets
+
+ foreach( entity ent in linkedEnts )
+ {
+ if ( ent.GetClassName() == "npc_turret_sentry")
+ turrets.append( ent )
+ }
+
+ if ( turrets.len() > 0 )
+ thread TurretButtonThink( button, turrets )
+}
+//////////////////////////////////////////////////////////////////////////////////////////
+void function TurretButtonThink( entity button, array< entity > turrets )
+{
+
+ foreach( turret in turrets )
+ thread HACK_DisableTurret( turret )
+
+ var player //hack: have to use "var" when waiting on a usable signal or trigger
+
+ string flagRequired = expect string( button.kv.scr_flagRequired )
+ if ( flagRequired != "" )
+ FlagWait( flagRequired )
+
+ button.SetUsePrompts( "#TIMESHIFT_HINT_TURRET_USE" , "#TIMESHIFT_HINT_TURRET_USE" )
+
+ while( true )
+ {
+ button.WaitSignal( "OnActivate" )
+ break
+ }
+
+ if ( !Flag( "AtLeastOneBunkerTurretRestored" ) )
+ FlagSet( "AtLeastOneBunkerTurretRestored" )
+
+
+ string scriptName = button.GetScriptName()
+ if ( ( IsValid( scriptName ) ) && ( scriptName == "button_bunker_turrets_fence" ) )
+ FlagSet( "TurretsNearBunkerFenceActivated" )
+
+ foreach( turret in turrets )
+ {
+ thread HACK_EnableTurret( turret )
+ }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+entity function GetNpcByScriptName( string scriptName )
+{
+
+ array< entity > ents = GetEntArrayByScriptName( scriptName )
+ array< entity > npcs
+ foreach( ent in ents )
+ {
+ if ( ent.IsNPC() )
+ npcs.append( ent )
+ }
+
+ Assert( npcs.len() > 0, "No NPCs found with script name: " + scriptName )
+ Assert( npcs.len() < 2, "Multiple NPCs ( " + npcs.len() + " ) found with scriptName: " + scriptName )
+
+ return npcs[ 0 ]
+}
+
+
+entity function CreateLoudspeakerEnt( vector origin )
+{
+ entity loudspeakerEnt = CreateEntity( "info_target" )
+ loudspeakerEnt.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( loudspeakerEnt )
+ loudspeakerEnt.SetOrigin( origin )
+
+ return loudspeakerEnt
+}
+
+/*
+void function BatteryMoveSounds( entity leftBattery, entity rightBattery )
+{
+ entity soundEnt = leftBattery
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" )
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+ wait 1.5
+ StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+ soundEnt = rightBattery
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" )
+}
+*/
+
+
+/*
+void function BrokenBatteryMoveSounds( entity leftBattery )
+{
+ FlagEnd( "bunker_move_battery" )
+
+ entity soundEnt = leftBattery
+
+ while( !Flag( "bunker_move_battery" ) )
+ {
+
+
+ OnThreadEnd(
+ function() : ( soundEnt )
+ {
+ StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+ }
+ )
+
+ FlagWait( "broken_battery_moved_to_node0" )
+ StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" )
+ wait 1
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+
+ FlagWait( "broken_battery_moved_to_node1" )
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" )
+
+ FlagWait( "broken_battery_moved_to_node2" )
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" )
+ StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+ wait 0.75
+
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+ FlagWait( "broken_battery_moved_to_node3" )
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_stop" )
+ StopSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+ wait 0.75
+
+ EmitSoundOnEntity( soundEnt, "timeshift_battery_conduit_move_loop" )
+
+ }
+}
+
+*/
+
+
+/*
+
+void function FuelPanelSounds( vector origin, string flagToStart, string flagToStop )
+{
+ FlagWait( flagToStart )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_stop" )
+
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_open_loop" )
+
+ FlagWait( flagToStop )
+ StopSoundAtPosition( origin, "door_open_loop")
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "door_stop" )
+}
+
+*/
+
+
+void function DestroyIfValid( string scriptName )
+{
+ array< entity > ents = GetEntArrayByScriptName( scriptName )
+ foreach( ent in ents )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+function SkyboxStart()
+{
+ wait 1.5
+ local skyCam = GetEnt( "skybox_cam_night" )
+ foreach ( player in GetPlayerArray() )
+ {
+ player.SetSkyCamera( skyCam )
+ }
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SleepingSpectreFX( entity spectreModel, string signalToAbort )
+{
+ spectreModel.EndSignal( "OnDestroy" )
+ spectreModel.EndSignal( signalToAbort )
+
+ entity fxEyeSparksLeft
+ entity fxEyeSparksRight
+
+ array< entity > fxArray
+ fxArray.append( fxEyeSparksLeft )
+ fxArray.append( fxEyeSparksRight )
+
+ //fxFullBodyEffect = PlayFXOnEntity( <whatever>, spectreModel, "TAG" )
+
+ while( true )
+ {
+
+ OnThreadEnd(
+ function() : ( fxArray )
+ {
+ foreach( fx in fxArray )
+ DestroyFxIfValid( fx )
+ }
+ )
+
+// ModelFX_EnableGroup( spectreModel, "friend_lights" )
+
+ wait( RandomFloatRange( 2, 3.75 ) )
+
+// ModelFX_DisableGroup( spectreModel, "friend_lights" )
+
+ fxEyeSparksLeft = PlayFXOnEntity( FX_SPARKS, spectreModel, "vent_left_out" )
+ fxEyeSparksRight = PlayFXOnEntity( FX_SPARKS, spectreModel, "vent_right_out" )
+ EmitSoundOnEntity( spectreModel, SOUND_SPARKS )
+ wait( RandomFloatRange( 0.2, 0.25 ) )
+ fxEyeSparksLeft.Fire( "Stop" )
+ fxEyeSparksLeft.Fire( "DestroyImmediately" )
+ fxEyeSparksLeft.Destroy()
+ fxEyeSparksRight.Fire( "Stop" )
+ fxEyeSparksRight.Fire( "DestroyImmediately" )
+ fxEyeSparksRight.Destroy()
+
+
+ fxEyeSparksLeft = PlayFXOnEntity( FX_SPARKS, spectreModel, "vent_left_out" )
+ fxEyeSparksRight = PlayFXOnEntity( FX_SPARKS, spectreModel, "vent_right_out" )
+ EmitSoundOnEntity( spectreModel, SOUND_SPARKS )
+ wait( RandomFloatRange( 0.3, 0.5 ) )
+ fxEyeSparksLeft.Fire( "Stop" )
+ fxEyeSparksLeft.Fire( "DestroyImmediately" )
+ fxEyeSparksLeft.Destroy()
+ fxEyeSparksRight.Fire( "Stop" )
+ fxEyeSparksRight.Fire( "DestroyImmediately" )
+ fxEyeSparksRight.Destroy()
+
+ wait( RandomFloatRange( 0.1, 0.25 ) )
+
+
+ }
+}
+
+///////////////////////////////////////////////////////////////
+void function CleanupEnts( string scriptName )
+{
+ array<entity> entArray = GetEntArrayByScriptName( scriptName )
+ foreach( ent in entArray )
+ {
+ if( IsValid( ent ) )
+ ent.Destroy()
+ }
+
+}
+
+///////////////////////////////////////////////////////////////
+void function CleanupAI( entity player, string triggerScriptName = "", immediate = false )
+{
+ if ( !IsValid( player ) )
+ return
+
+ array<entity> npcArray = GetNPCArray()
+
+ if ( npcArray.len() == 0 )
+ return
+
+ //--------------------------------------------------
+ // Optional trigger to specify which npcs to select
+ //--------------------------------------------------
+ if ( triggerScriptName != "" )
+ {
+
+ entity trigger = GetEntByScriptName( triggerScriptName )
+ vector triggerMins = trigger.GetBoundingMins()
+ vector triggerMaxs = trigger.GetBoundingMaxs()
+ bool entIsInsideTrigger
+
+ for ( int i = npcArray.len() - 1; i >= 0; i-- ) //loop backward through array since we are removing entries
+ {
+
+
+ //HACK: Titans and marvins aren't registering IsTouching
+ if ( ( triggerScriptName == "trigger_ai_pristine" ) && ( GetEntityTimelinePosition( npcArray[ i ] ) == TIMEZONE_DAY ) )
+ {
+ if ( !trigger.IsTouching( npcArray[ i ] ) )
+ {
+ printl( "Ent " + ( npcArray[ i ] ).GetClassName() + " at " + npcArray[ i ].GetOrigin() + " is within trigger " + triggerScriptName + " but IsTouching returns false" )
+ continue
+ }
+ }
+
+ if ( !trigger.IsTouching( npcArray[ i ] ) )
+ {
+ //Remove the npc
+ npcArray.fastremove( i )
+ }
+ }
+ }
+
+
+ if ( npcArray.len() == 0 )
+ return
+
+ if ( immediate )
+ {
+ foreach( npc in npcArray )
+ {
+ if ( ( IsAlive( npc ) ) && ( npc.GetTeam() != TEAM_MILITIA ) )
+ npc.Destroy()
+ }
+
+ return
+ }
+
+ //--------------------------------------------------
+ // Delete all NPCs when out of sight
+ //--------------------------------------------------
+ while ( npcArray.len() > 0 )
+ {
+ ArrayRemoveDead( npcArray )
+ for ( int i = npcArray.len() - 1; i >= 0; i-- ) //loop backward through array since we are removing entries
+ {
+ if ( npcArray[ i ].GetTeam() != TEAM_IMC )
+ {
+ npcArray.fastremove( i )
+ continue
+ }
+ thread DeleteNpcWhenOutOfSight( npcArray[ i ], player )
+ npcArray.fastremove( i )
+ }
+ wait 0.1
+ }
+}
+
+///////////////////////////////////////////////////////////////
+void function DeleteNpcWhenOutOfSight( entity npc, entity player )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( !npc.IsNPC() )
+ return
+
+ npc.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+
+ //don't run this func twice on the same npc)
+ if ( npc.l.npcMarkedForCleanup == true )
+ return
+
+ npc.l.npcMarkedForCleanup = true
+
+ float minDistSqr = 1024 * 1024
+ bool doTrace = true
+ float degrees = 90
+
+ while( IsAlive( npc ) )
+ {
+ WaitFrame()
+
+ if ( DistanceSqr( player.GetOrigin(), npc.GetOrigin() ) < minDistSqr )
+ continue
+
+ if ( PlayerCanSee( player, npc, doTrace, degrees ) )
+ continue
+
+ npc.Destroy()
+ break
+ }
+
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+void function TimeFlux( entity player, string triggerNameToDoFlux, string flagToAbort = "", int maxSwaps = -1, float minFluxTime = 0.75, maxFluxTime = 2 )
+{
+ if ( !IsValid( player ) )
+ return
+ if ( ( flagToAbort != "" ) && ( Flag( flagToAbort ) ) )
+ return
+
+ entity triggerToDoFluxIn = GetEntByScriptName( triggerNameToDoFlux )
+ entity lookTarget = triggerToDoFluxIn.GetLinkEnt()
+ var homeTimeZone = GetEntityTimelinePosition( triggerToDoFluxIn )
+ var destTimeZone = GetOppositeTimeline( homeTimeZone )
+ float fluxTime
+ int swapCount = 0
+
+ float fadeTime = 0.3
+ float holdFadeTime = 0
+
+
+ player.EndSignal( "OnDeath" )
+
+ if ( flagToAbort != "" )
+ FlagEnd( flagToAbort )
+
+
+ OnThreadEnd(
+ function() : ( player, homeTimeZone )
+ {
+ if ( !IsValid( player ) )
+ return
+ if ( GetEntityTimelinePosition( player ) != homeTimeZone )
+ thread SwapTimelinesScripted( player, homeTimeZone )
+ }
+ )
+
+
+
+ while( true )
+ {
+ wait RandomFloatRange( 0.2, 0.5 )
+
+ //--------------------------------------------------
+ // Player in home timezone with all requirements met
+ //--------------------------------------------------
+ if ( ( GetEntityTimelinePosition( player ) == homeTimeZone ) && ( TS_WithinPlayerFOV( lookTarget.GetOrigin(), 0.7 ) ) && ( triggerToDoFluxIn.IsTouching( player ) ) )
+ {
+ thread SwapTimelinesScripted( player, destTimeZone )
+ fluxTime = RandomFloatRange( minFluxTime, maxFluxTime )
+ wait fluxTime
+
+ }
+
+ //--------------------------------------------------
+ // Player in dest timezone...swap back to home timezone
+ //--------------------------------------------------
+ if ( GetEntityTimelinePosition( player ) == destTimeZone )
+ {
+ thread SwapTimelinesScripted( player, homeTimeZone )
+ fluxTime = RandomFloatRange( minFluxTime, maxFluxTime )
+ wait fluxTime
+ swapCount++
+ }
+
+ //---------------------------------------
+ // Early out if we've done enough swaps
+ //----------------------------------------
+ if ( ( maxSwaps != -1 ) && ( swapCount >= maxSwaps ) )
+ break
+
+ }
+
+}
+
+*/
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SwapTimelinesScripted( entity player, var timeZone )
+{
+ Remote_CallFunction_NonReplay( player, "ServerCallback_ScriptedTimeshiftStart", timeZone )
+
+ //-------------------
+ // crescendo sound
+ //-------------------
+ if ( timeZone == TIMEZONE_DAY )
+ EmitSoundOnEntity( player, "Timeshift_Scr_InvoluntaryShift2Past_Start" )
+ else
+ EmitSoundOnEntity( player, "Timeshift_Scr_InvoluntaryShift2Present_Start" )
+
+ wait 0.55
+ //-------------------
+ // Scripted timeshift FX
+ //-------------------
+
+ //Mostly done on the client
+
+ wait 0.8
+ //( amplitude frequency duration
+ CreateAirShake( player.GetOrigin(), 10, 50, 1.4, 20000 )
+ wait 0.4
+
+ //-------------------
+ // Swap timelines
+ //-------------------
+ SwapTimelines( player, timeZone )
+
+
+ //phaseshift_postfx_forceOn 1
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+
+/*
+void function MakeSpectreOwnedByPlayer( entity spectre, entity player )
+{
+ spectre.EnableNPCFlag( NPC_IGNORE_ALL )
+ spectre.SetNoTarget( true )
+ //spectre.SetOwner( player )
+ //spectre.SetOwnerPlayer( player )
+ //spectre.SetBossPlayer( player )
+ SetTeam( spectre, TEAM_MILITIA )
+
+ spectre.SetTitle( "" )
+ SetTargetName( spectre, "" )
+
+
+ int followBehavior = GetDefaultNPCFollowBehavior( spectre )
+ spectre.InitFollowBehavior( player, followBehavior )
+ spectre.DisableBehavior( "Assault" )
+ spectre.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_USE_SHOOTING_COVER )
+ spectre.EnableBehavior( "Follow" )
+
+}
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+entity function CreateBestLoudspeakerEnt( entity player, var timeZone, entity existingEnt = null )
+{
+ entity soundEnt = existingEnt
+ if ( existingEnt == null)
+ soundEnt = CreateLoudspeakerEnt( player.GetOrigin() + Vector( 0, 0, 100 ) )
+ if ( GetEntityTimelinePosition( soundEnt ) != timeZone )
+ soundEnt.SetOrigin( GetPosInOtherTimeline( soundEnt.GetOrigin() ) )
+
+ //randomize position
+ vector baseOrigin = soundEnt.GetOrigin()
+ soundEnt.SetOrigin( baseOrigin + Vector( RandomIntRange( 0, 150 ), RandomIntRange( 0, 150 ), 0 ) )
+
+ return soundEnt
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function HideWeaponsAndAmmoTillFlag( string scriptName, string flagToRestore )
+{
+ array <entity> weapons = GetWeaponArray( true )
+ foreach ( weapon in weapons )
+ {
+ if ( ( weapon.HasKey( "script_name" ) ) && ( weapon.kv.script_name == scriptName ) )
+ {
+ weapon.Hide()
+ weapon.MakeInvisible()
+ weapon.UnsetUsable()
+ thread ShowEntityOnFlag( weapon, flagToRestore )
+ }
+
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function DeleteWaponsWithScriptname( string scriptName )
+{
+ array <entity> weapons = GetWeaponArray( true )
+ foreach ( weapon in weapons )
+ {
+ if ( ( weapon.HasKey( "script_name" ) ) && ( weapon.kv.script_name == scriptName ) )
+ weapon.Destroy()
+
+ }
+}
+*/
+/////////////////////////////////////////////////////////////////////////////////////
+void function ShowEntityOnFlag( entity weapon, string flagToRestore )
+{
+ FlagWait( flagToRestore )
+ weapon.Show()
+ weapon.MakeVisible()
+ weapon.SetUsable()
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+void function SetFlagWhenPlayerLookingAtEnt( entity player, string flagToSet, entity ent, entity trigger = null )
+{
+ if ( Flag( flagToSet ) )
+ return
+ FlagEnd( flagToSet )
+
+ //WaitTillLookingAt( entity player, entity ent, bool doTrace, float degrees, float minDist = 0, float timeOut = 0, entity trigger = null )
+ waitthread WaitTillLookingAt( player, ent, true, 45, 0, 0 )
+ FlagSet( flagToSet )
+}
+
+
+void function DoorOutOfOrderThink( entity propDynamic )
+{
+ thread PlayAnim( propDynamic, "stutter01", propDynamic.GetOrigin(), propDynamic.GetAngles() )
+}
+
+
+
+entity function TSCreateScriptMoverLight( entity owner = null, origin = null, angles = null, solidType = 0 )
+{
+ if ( owner == null )
+ {
+ entity script_mover = CreateEntity( "script_mover_lightweight" )
+ script_mover.kv.solid = solidType
+ script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" )
+ script_mover.kv.SpawnAsPhysicsMover = 0
+ if ( origin )
+ script_mover.SetOrigin( origin)
+ if ( angles )
+ script_mover.SetAngles( angles )
+
+ DispatchSpawn( script_mover )
+ return script_mover
+ }
+
+ entity script_mover = CreateEntity( "script_mover_lightweight" )
+ script_mover.kv.solid = solidType
+ script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" )
+ script_mover.kv.SpawnAsPhysicsMover = 0
+ script_mover.SetOrigin( owner.GetOrigin() )
+ script_mover.SetAngles( owner.GetAngles() )
+ DispatchSpawn( script_mover )
+ script_mover.Hide()
+
+ script_mover.SetOwner( owner )
+ return script_mover
+}
+
+void function GiveLowAmmo( entity player )
+{
+ //give player one clip of ammo
+ int clipBulletCapacity
+ int currentAmmo
+
+ entity weapon = player.GetMainWeapons()[ 0 ]
+ clipBulletCapacity = player.GetWeaponAmmoMaxLoaded( weapon )
+ currentAmmo = player.GetActiveWeaponPrimaryAmmoLoaded()
+
+ //hack - just refill the current clips if ammo is picked up
+ weapon.SetWeaponPrimaryClipCount( clipBulletCapacity )
+ player.SetActiveWeaponPrimaryAmmoTotal( 1 )
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+void function CivilianSkitThink( entity civilian, entity player, string flagToReact = "" )
+{
+ /*
+ //---------------------------------------------
+ // Does civilian have props or escape nodes?
+ //---------------------------------------------
+ array< entity > linkedEnts = civilian.GetLinkEntArray()
+ string classname
+ entity escapeOrg
+ entity animProp
+ foreach( entity ent in linkedEnts )
+ {
+ classname = ent.GetClassName()
+ if ( classname == "prop_dynamic" )
+ animProp = ent
+ if ( classname == "info_move_target" )
+ escapeOrg = ent
+ }
+
+ */
+
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function CivilianWalkerThink( entity civilian )
+{
+ if ( !civilian.HasKey( "script_noteworthy" ) )
+ return
+
+ string walkAnim = expect string( civilian.kv.script_noteworthy )
+ Assert( IsValid( walkAnim ), "Ent at " + civilian.GetOrigin() + " needs a walk anim name in its script_noteworthy" )
+
+ thread GivePropForAnim( civilian, walkAnim )
+
+ /*
+ entity spawner = civilian.spawner
+ Assert( IsValid ( spawner ) )
+ asset model = civilian.spawner.GetSpawnerModelName()
+ */
+
+ civilian.SetModel( GetRandomCivilianModel() )
+ civilian.SetMoveAnim( walkAnim )
+ MakeCivilian( civilian )
+
+
+
+ array< entity > linkedEnts = civilian.GetLinkEntArray()
+ string editorClassname
+ entity destinationOrg
+ foreach( entity ent in linkedEnts )
+ {
+ editorClassname = GetEditorClass( ent )
+ if ( editorClassname == "info_move_target" )
+ destinationOrg = ent
+
+ }
+ Assert( IsValid( destinationOrg ) )
+ civilian.AssaultPoint( destinationOrg.GetOrigin() )
+}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function CivilianActorThink( entity civilian )
+{
+ civilian.SetModel( GetRandomCivilianModel() )
+ MakeCivilian( civilian )
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function QuickSkit( entity player, entity skitNode, string failsafeFlagToStart = "", entity lookAtEnt = null, entity lookAtTrigger = null )
+{
+ array< entity > linkedEnts = skitNode.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0, "skitNode at " + skitNode.GetOrigin() + " has no linked ents")
+ vector origin = skitNode.GetOrigin()
+ vector angles = skitNode.GetAngles()
+ skitNode.Destroy()
+ bool isLooping = false
+ bool showIdle = false
+ bool isSpawnSkit = false
+ bool isDeleteSkit = false
+
+
+ string scriptName
+ if ( ( skitNode.HasKey( "script_noteworthy") ) && ( skitNode.kv.script_noteworthy == "looping" ) )
+ isLooping = true
+ if ( ( skitNode.HasKey( "script_noteworthy") ) && ( skitNode.kv.script_noteworthy == "show_idle" ) )
+ showIdle = true
+ if ( ( skitNode.HasKey( "script_noteworthy") ) && ( skitNode.kv.script_noteworthy == "delete" ) )
+ isDeleteSkit = true
+
+ entity actor
+ array <entity> skitActors
+ bool isDoorSkit = false
+ asset modelName
+
+ foreach( entity ent in linkedEnts )
+ {
+ modelName = ent.GetModelName()
+ if ( modelName == $"models/door/door_imc_interior_03_128_animated.mdl" )
+ isDoorSkit = true
+
+ }
+
+
+ foreach( entity ent in linkedEnts )
+ {
+
+ if( IsSpawner( ent ) )
+ {
+ actor = ent.SpawnEntity()
+ actor.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+ DispatchSpawn( actor )
+ isSpawnSkit = true
+ actor.EnableNPCFlag( NPC_DISABLE_SENSING )
+ actor.kv.alwaysAlert = 0
+ actor.EnableNPCFlag( NPC_IGNORE_ALL )
+ actor.SetNoTarget( true )
+ actor.EnableNPCFlag( NPC_NO_MOVING_PLATFORM_DEATH )
+ }
+ else
+ actor = ent
+
+
+ actor.EndSignal( "OnDestroy" )
+ actor.EndSignal( "OnDeath" )
+
+ actor.s.anim <- expect string( actor.kv.script_noteworthy )
+ Assert( IsValid( actor.s.anim ), "Ent at " + actor.GetOrigin() + " needs an anim name in its script_noteworthy" )
+ Assert( actor.s.anim != "", "Ent at " + actor.GetOrigin() + " needs an anim name in its script_noteworthy" )
+
+ if ( isDoorSkit == true )
+ DontAllowFreeze( actor, true )
+
+
+ if ( ( actor.GetScriptName().find( "civilian_" ) != null ) && ( isSpawnSkit == true ) )
+ {
+ actor.SetMoveAnim( GetRandomCivilianRunAnim() )
+ MakeCivilian( actor )
+ isDeleteSkit = true
+ }
+
+ if ( !isLooping )
+ {
+ actor.s.animIdle <- actor.s.anim + "_idle"
+ thread PlayAnimTeleport( actor, actor.s.animIdle, origin, angles )
+ }
+ else if ( ( isLooping ) && ( actor.IsNPC() ) )
+ actor.EnableNPCFlag( NPC_DISABLE_SENSING )
+
+ if ( !showIdle )
+ actor.Hide()
+
+ skitActors.append( actor )
+ }
+
+ if ( lookAtEnt )
+ waitthread WaitTillLookingAt( player, lookAtEnt, true, 30, 0, 0, lookAtTrigger, failsafeFlagToStart )
+ else if ( failsafeFlagToStart != "" )
+ FlagWait( failsafeFlagToStart )
+
+
+ float animLength
+
+
+ foreach( entity actor in skitActors )
+ {
+
+
+ modelName = actor.GetModelName()
+ if ( modelName == $"models/door/door_imc_interior_03_128_animated.mdl" )
+ {
+ //actor.NotSolid()
+ //actor.kv.solid = 0
+ // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+
+ animLength = actor.GetSequenceDuration( actor.s.anim )
+ delaythread ( animLength ) DisableNavmeshSeperatorTargetedByEnt( actor )
+ //ToggleNPCPathsForEntity( actor, true )
+ DisableNavmeshSeperatorTargetedByEnt( actor )
+ }
+
+ actor.Show()
+ if ( ( isSpawnSkit ) && ( isDeleteSkit ) )
+ thread PlayAnimThenDelete( actor, expect string( actor.s.anim ), origin, angles )
+ else if ( isSpawnSkit )
+ {
+ thread PlayAnim( actor, expect string( actor.s.anim ), origin, angles )
+ animLength = actor.GetSequenceDuration( actor.s.anim )
+ if ( actor.IsNPC() )
+ {
+ actor.DisableNPCFlag( NPC_DISABLE_SENSING )
+ actor.DisableNPCFlag( NPC_IGNORE_ALL )
+ actor.SetNoTarget( false )
+
+ }
+
+ if ( isDoorSkit == true )
+ thread AllowFreezeWhenAnimDone( actor, animLength )
+ }
+ else if ( isLooping )
+ thread PlayAnim( actor, expect string( actor.s.anim ), origin, angles )
+ else
+ thread PlayAnimThenDelete( actor, expect string( actor.s.anim ), origin, angles )
+ }
+
+}
+
+
+void function AllowFreezeWhenAnimDone( entity npc, float animLength )
+{
+ if ( !IsValid( npc ) )
+ return
+ npc.EndSignal( "OnDeath" )
+
+ wait animLength
+
+ DontAllowFreeze( npc, false )
+
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+string function GetRandomCivilianRunAnim()
+{
+ array<string> anims
+ //anims.append( "pt_civ_walk_swagger" )
+ anims.append( "pt_civ_fleeing_run_flail" )
+ anims.append( "pt_civ_fleeing_run_hunched" )
+ //anims.append( "pt_civ_fleeing_run_lookback" )
+ //anims.append( "pt_civ_fleeing_run_pause" )
+ //anims.append( "pt_civ_fleeing_run_spin" )
+ anims.randomize()
+ return anims[ 0 ]
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void function MakeCivilian( entity npc )
+{
+
+ TakeAllWeapons( npc )
+ npc.EnableNPCFlag( NPC_DISABLE_SENSING )
+ npc.kv.alwaysAlert = 0
+ npc.EnableNPCFlag( NPC_IGNORE_ALL )
+ npc.EnableNPCMoveFlag( NPCMF_DISABLE_MOVE_TRANSITIONS )
+ SetTeam( npc, TEAM_UNASSIGNED )
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+asset function GetRandomCivilianModel()
+{
+ array<asset> models
+ models.append( MODEL_CIV01 )
+ models.append( MODEL_CIV02 )
+ models.append( MODEL_CIV03 )
+ models.append( MODEL_CIV04 )
+ models.randomize()
+ return models[ 0 ]
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function PlayAnimThenDelete( entity actor, string anim, origin, angles )
+{
+ actor.EndSignal( "OnDeath" )
+ actor.EndSignal( "OnDestroy" )
+ entity node = actor.GetLinkEnt()
+ if ( ( actor.IsNPC ) && ( IsValid( node ) ) )
+ actor.AssaultPoint( node.GetOrigin() )
+
+ float animLength = actor.GetSequenceDuration( anim )
+ thread PlayAnimTeleport( actor, anim, origin, angles )
+ wait animLength
+
+ if ( ( actor.IsNPC ) && ( IsValid( node ) ) )
+ actor.WaitSignal( "OnFinishedAssault" )
+
+ if ( IsValid( actor ) )
+ actor.Destroy()
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TriggerShowcaseSpawnInit( entity trigger )
+{
+ bool requiresLookAt = false
+ if ( ( trigger.HasKey( "script_noteworthy") ) && ( trigger.kv.script_noteworthy == "lookat" ) )
+ requiresLookAt = true
+
+ array< entity > linkedEnts = trigger.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0, "Showcase spawn trigger at " + trigger.GetOrigin() + " has no linked ents" )
+
+ foreach( ent in linkedEnts )
+ {
+ Assert( ent.GetClassName() == "prop_dynamic", "Showcase spawn trigger at " + trigger.GetOrigin() + " Should only link to prop_dynamics " + ( ent.GetClassName() ) )
+ thread SpawnerShowcaseSpawnerThink( trigger, ent, requiresLookAt )
+ }
+
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SpawnerShowcaseSpawnerThink( entity trigger, entity spawnProp, bool requiresLookAt )
+{
+ FlagWait( "PlayerDidSpawn" )
+ entity player = GetPlayerArray()[ 0 ]
+ if ( !IsValid( player ) )
+ return
+ player.EndSignal( "OnDeath" )
+ trigger.EndSignal( "OnDestroy" )
+
+ //---------------------------------------------
+ // Get spawner and associated animated prop
+ //---------------------------------------------
+ entity spawner = spawnProp.GetLinkEnt()
+ Assert( IsSpawner( spawner ), "Entity at " + spawner.GetOrigin() + " is not a spawner" )
+ var spawnerKeyValues = spawner.GetSpawnEntityKeyValues()
+ expect table( spawnerKeyValues )
+ int spawnCount = 1
+ float spawnDelayTime = 0.0
+
+ if ( "script_delay" in spawnerKeyValues )
+ spawnDelayTime = float( spawnerKeyValues.script_delay )
+
+ if ( "spawn_count" in spawnerKeyValues ) //spawn_count not implemented yet, but will add later
+ spawnCount = int( spawnerKeyValues.script_delay )
+
+ string spawnModel = expect string( spawnerKeyValues.model )
+
+ int npcsSpawned = 0
+
+
+ //--------------------------
+ // Wait for trigger to be hit
+ //--------------------------
+
+ entity npc
+
+ while ( GetTriggerEnabled( trigger ) == false )
+ wait 0.1
+
+ while( true )
+ {
+ wait 0.1
+ trigger.WaitSignal( "OnTrigger" )
+
+ wait spawnDelayTime
+
+ if ( requiresLookAt )
+ {
+ //player, entity, doTrace, degrees minDist, timeOut, trigger
+ waitthread WaitTillLookingAt( player, spawnProp, true, 45, 0, 0, trigger )
+ }
+
+ thread ShowcaseSpawn( spawnProp )
+
+ npcsSpawned++
+ if ( npcsSpawned == spawnCount )
+ break
+
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+void function ShowcaseSpawn( entity spawnProp )
+{
+ vector origin = spawnProp.GetOrigin()
+ vector angles = spawnProp.GetAngles()
+ string spawnAnimNPC = ""
+ string spawnAnimProp = ""
+ entity spawner = spawnProp.GetLinkEnt()
+ Assert( IsSpawner( spawner ), "Entity at " + spawner.GetOrigin() + " is not a spawner" )
+ var spawnerKeyValues = spawner.GetSpawnEntityKeyValues()
+ expect table( spawnerKeyValues )
+ string spawnModel = expect string( spawnerKeyValues.model ).tolower()
+
+ switch( spawnProp.GetModelName() )
+ {
+ //--------------------------
+ // Floor panel prop
+ //--------------------------
+ case $"models/props/floor_panel_animated.mdl":
+ case $"models/props/floor_vent_d_animated.mdl":
+ if ( spawnModel == "models/creatures/prowler/r2_prowler.mdl" )
+ {
+ //spawnAnimProp = "floor_timeshift_prowler_spawn_01"
+ //spawnAnimNPC = "pr_timeshift_floor_spawn_01"
+ spawnAnimProp = "floor_timeshift_prowler_spawn_01_long"
+ spawnAnimNPC = "pr_timeshift_floor_spawn_01_long"
+
+ }
+ else if ( spawnModel == "models/robots/stalker/robot_stalker_mossy.mdl" )
+ {
+ if ( CoinFlip() )
+ {
+ //spawnAnimProp = "floor_breakout_spawn_floorpanel"
+ //spawnAnimNPC = "st_breakout_spawn_floorpanel"
+ spawnAnimProp = "floor_breakout_spawn_floorpanel_long"
+ spawnAnimNPC = "st_breakout_spawn_floorpanel_long"
+ }
+ else
+ {
+ //spawnAnimProp = "floor_breakout_spawn_floordeath"
+ //spawnAnimNPC = "st_breakout_spawn_floordeath"
+ spawnAnimProp = "floor_breakout_spawn_floordeath_long"
+ spawnAnimNPC = "st_breakout_spawn_floordeath_long"
+ }
+ }
+ else
+ Assert( 0, "Unhandled spawn prop: " + spawnProp.GetModelName() )
+ break
+ //--------------------------
+ // Wall panel prop
+ //--------------------------
+ case $"models/props/wall_vent_animated.mdl":
+ if ( spawnModel == "models/creatures/prowler/r2_prowler.mdl" )
+ {
+ if ( ( spawnProp.HasKey( "script_noteworthy") ) && ( spawnProp.kv.script_noteworthy == "ground_vent" ) )
+ {
+ //spawnAnimProp = "vent_low_timeshift_prowler_spawn_01"
+ //spawnAnimNPC = "pr_timeshift_vent_low_spawn_01"
+ spawnAnimProp = "vent_low_timeshift_prowler_spawn_01_long"
+ spawnAnimNPC = "pr_timeshift_vent_low_spawn_01_long"
+ }
+ else
+ {
+ //spawnAnimProp = "vent_timeshift_prowler_spawn_01"
+ //spawnAnimNPC = "pr_timeshift_vent_spawn_01"
+ spawnAnimProp = "vent_timeshift_prowler_spawn_01_long"
+ spawnAnimNPC = "pr_timeshift_vent_spawn_01_long"
+ }
+
+ }
+ else if ( spawnModel == "models/robots/stalker/robot_stalker_mossy.mdl" )
+ {
+ //spawnAnimProp = "vent_vent_spawn_core"
+ //spawnAnimNPC = "st_vent_spawn_core"
+ spawnAnimProp = "vent_vent_spawn_damaged_longintro"
+ spawnAnimNPC = "st_vent_spawn_damaged_longintro"
+ }
+ else
+ {
+ Assert( 0, "Unhandled spawnModel: " + spawnModel )
+ }
+ break
+ default:
+ Assert( 0, "Unhandled spawn prop: " + spawnProp.GetModelName() )
+
+ }
+
+ //------------------
+ //Spawn the npc
+ //------------------
+ entity npc = spawner.SpawnEntity()
+ npc.Hide()
+ npc.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+ DispatchSpawn( npc )
+
+ //----------------------------------
+ // Animate the spawned npc and prop
+ //----------------------------------
+ thread PlayAnim( npc, spawnAnimNPC, origin, angles )
+ npc.Show()
+ thread PlayAnim( spawnProp, spawnAnimProp, origin, angles )
+
+}
+
+///////////////////////////////////////////////////////////////////
+
+bool function IsSpectreRackDoorSpawner( entity spawnProp )
+{
+ array validModels = [
+ $"models/timeshift/timeshift_column_panel_09_destroyed.mdl",
+ $"models/timeshift/timeshift_column_panel_10_destroyed.mdl",
+ $"models/timeshift/timeshift_column_panel_09.mdl",
+ $"models/timeshift/timeshift_column_panel_10.mdl"
+ ]
+
+ if ( validModels.contains( spawnProp.GetModelName() ) )
+ return true
+
+ return false
+}
+
+///////////////////////////////////////////////////////////////////
+void function HideStuff( string scriptName )
+{
+ array <entity> stuff = GetEntArrayByScriptName( scriptName )
+ foreach( thing in stuff )
+ {
+ thing.Hide()
+ thing.MakeInvisible()
+ thing.NotSolid()
+ }
+
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function ShowStuff( string scriptName )
+{
+ array <entity> stuff = GetEntArrayByScriptName( scriptName )
+ foreach( thing in stuff )
+ {
+ thing.Show()
+ thing.Solid()
+ thing.MakeVisible()
+ }
+
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function AttackPlayer( entity npc )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ array<entity> players = GetPlayerArray()
+ if ( players.len() <= 0 )
+ return
+
+ npc.SetEnemy( players[ 0 ] )
+ printt( "Attacking player..." )
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+void function ChangeIMCCorpse( entity model, string corpseType )
+{
+ //disable all bodygroups
+ int stateIndex = 1 // 0 = show, 1 = hide
+ model.SetBodygroup( model.FindBodyGroup( "imc_corpse_rifle" ), 1 )
+ model.SetBodygroup( model.FindBodyGroup( "imc_corpse_shotgun" ), 1 )
+ model.SetBodygroup( model.FindBodyGroup( "imc_corpse_smg" ), 1 )
+ model.SetBodygroup( model.FindBodyGroup( "imc_corpse_lmg" ), 1 )
+
+ //enable the right one
+ int bodyGroupIndex = model.FindBodyGroup( corpseType )
+ stateIndex = 0 // 0 = show, 1 = hide
+ model.SetBodygroup( bodyGroupIndex, stateIndex )
+}
+*/
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DestroyNPCOnFlag( entity npc, string flagToDestroy )
+{
+ npc.EndSignal( "OnDeath" )
+ FlagEnd( flagToDestroy )
+
+ OnThreadEnd(
+ function() : ( npc )
+ {
+ if ( IsAlive( npc ) )
+ npc.Destroy()
+ }
+ )
+ WaitForever()
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+entity function GetClosestGrunt( entity player, var timeZone, string scriptName = "" )
+{
+ if ( !IsValid( player ) )
+ return
+ player.EndSignal( "OnDeath" )
+
+ vector playerOrigin = player.GetOrigin()
+ if ( level.timeZone != timeZone )
+ playerOrigin = GetPosInOtherTimeline( player.GetOrigin() )
+
+ array <entity> npcs = GetNPCArray()
+ array <entity> grunts
+ string classname
+ foreach( npc in npcs )
+ {
+ if ( !IsValid( npc ) )
+ continue
+ if ( !npc.IsNPC() )
+ continue
+ if ( !IsAlive( npc ) )
+ continue
+ if ( ( scriptName != "" ) && ( npc.GetScriptName() != scriptName ) )
+ continue
+ if ( !npc.IsHuman() )
+ continue
+
+ grunts.append( npc )
+ }
+
+ ArrayRemoveDead( grunts )
+ if ( grunts.len() == 0 )
+ return null
+
+ entity closestDude = GetClosest( grunts, playerOrigin )
+
+ if ( IsValid( closestDude ) )
+ return closestDude
+ else
+ return null
+
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+entity function GetTimeshiftPlayer()
+{
+ array<entity> players = GetPlayerArray()
+ if ( players.len() <= 0 )
+ return null
+ entity player = players[ 0 ]
+ return player
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function GunshipSequence( string instanceName, entity player, string nodeName, string whichSequence, string flagToStart )
+{
+
+ entity gunship = GetEntByScriptNameInInstance( "gunship_artifact_hauler", instanceName )
+ entity artifact = GetEntByScriptNameInInstance( "artifact_cargo", instanceName )
+ entity harness = GetEntByScriptNameInInstance( "artifact_cargo_containment", instanceName )
+ array <entity> ropeLinks = GetEntArrayByScriptNameInInstance( "harness_rope_points", instanceName )
+
+ foreach( ropeAttachEnt in ropeLinks )
+ ropeAttachEnt.SetParent( harness )
+
+ gunship.SetFadeDistance( 1000000 )
+ artifact.SetFadeDistance( 1000000 )
+ harness.SetFadeDistance( 1000000 )
+
+ entity animEnt = GetEntByScriptName( nodeName )
+
+
+ artifact.SetParent( gunship, "", true )
+ harness.SetParent( gunship, "", true )
+
+
+ string animIdle = ""
+ string animLeave = ""
+
+ if ( whichSequence == "rings" )
+ {
+ artifact.Destroy()
+ animIdle = "gunship_timeshift_fly_from_rings_idle"
+ animLeave = "gunship_timeshift_fly_from_rings"
+
+ }
+ else if ( whichSequence == "pad" )
+ {
+ animIdle = "st_AngelCity_IMC_Win_Idle"
+ //EmitSoundOnEntity( gunship, "Timeshift_Scr_DropshipTowingCore_Hover" )
+ //animLeave = "st_AngelCity_IMC_Win_Leave"
+ }
+ else
+ Assert( 0, "Invalid sequence: " + whichSequence )
+
+
+
+ entity gunshipAttachEnt = CreateScriptRef( gunship.GetOrigin(), gunship.GetAngles() )
+ gunshipAttachEnt.SetParent( gunship )
+ local subdivisions = 15 // 25
+ local slack = 25 // 25
+
+ wait 0.1
+
+ if ( whichSequence == "pad" )
+ animEnt.SetOrigin( animEnt.GetOrigin() + Vector( 0, 0, -400 ) )
+
+ thread PlayAnimTeleport( gunship, animIdle, animEnt )
+
+
+ wait 0.1
+
+ if ( whichSequence == "pad" )
+ {
+ foreach( ropeAttachEnt in ropeLinks )
+ {
+
+ string startpointName = UniqueString( "rope_startpoint" )
+ string endpointName = UniqueString( "rope_endpoint" )
+
+ entity rope_start = CreateEntity( "move_rope" )
+ SetTargetName( rope_start, startpointName )
+ rope_start.kv.NextKey = endpointName
+ rope_start.kv.MoveSpeed = 32
+ rope_start.kv.Slack = slack
+ rope_start.kv.Subdiv = subdivisions
+ rope_start.kv.Width = "3"
+ rope_start.kv.TextureScale = "1"
+ rope_start.kv.RopeMaterial = "cable/cable.vmt"
+ rope_start.kv.PositionInterpolator = 2
+ rope_start.SetOrigin( gunshipAttachEnt.GetOrigin() )
+
+ entity rope_end = CreateEntity( "keyframe_rope" )
+ SetTargetName( rope_end, endpointName )
+ rope_end.kv.MoveSpeed = 32
+ rope_end.kv.Slack = slack
+ rope_end.kv.Subdiv = subdivisions
+ rope_end.kv.Width = "3"
+ rope_end.kv.TextureScale = "1"
+ rope_end.kv.RopeMaterial = "cable/cable.vmt"
+ rope_end.SetOrigin( ropeAttachEnt.GetOrigin() )
+
+
+ DispatchSpawn( rope_start )
+ DispatchSpawn( rope_end )
+
+ rope_start.SetParent( gunship )
+ rope_end.SetParent( harness )
+ }
+ //
+
+ }
+ else
+ {
+ harness.Destroy()
+
+ }
+
+
+
+ wait 0.1
+
+ FlagWait( flagToStart )
+
+ if ( whichSequence == "rings" )
+ {
+ EmitSoundAtPosition( TEAM_UNASSIGNED, GetEntByScriptName( "lookent_rings" ).GetOrigin(), "timeshift_scr_dropshipcoreflyover_long_muffled" )
+ waitthread PlayAnim( gunship, animLeave, animEnt )
+
+ }
+
+ else if ( whichSequence == "pad" )
+ {
+
+ int attachID = gunship.LookupAttachment( "REF" )
+ vector attachOrg = gunship.GetAttachmentOrigin( attachID )
+ vector attachAng = gunship.GetAttachmentAngles( attachID )
+ entity mover = CreateScriptMover( attachOrg, attachAng )
+ gunship.SetParent( mover, "", true )
+ float duration = 30
+ //StopSoundOnEntity( gunship, "Timeshift_Scr_DropshipTowingCore_Hover" )
+ EmitSoundOnEntity( gunship, "Timeshift_Scr_DropshipTowingCore_Takeoff" )
+
+ mover.NonPhysicsMoveTo( gunship.GetOrigin() + Vector( 0, 0, 3200 ), duration, 0, 0 ) //, duration*0.4, duration*0.4 )
+ wait duration
+ mover.Destroy()
+ }
+
+
+}
+
+/////////////////////////////////////////////////////////////////////////////
+void function AndersonHologramSequence( entity player, string nodeName, string flagToStart )
+{
+ //Double the geo, double the fun
+ entity node = GetEntByScriptName( nodeName )
+ entity node2 = CreateScriptRef( node.GetOrigin() + Vector( 0, 0, TIME_ZOFFSET ), node.GetAngles() )
+ node2.kv.script_name = nodeName
+
+ thread AndersonHologramSequenceThread( player, node, flagToStart )
+ thread AndersonHologramSequenceThread( player, node2, flagToStart )
+}
+
+/////////////////////////////////////////////////////////////////////////////
+void function AndersonHologramSequenceThread( entity player, entity node, string flagToStart )
+{
+ player.EndSignal( "OnDeath" )
+ vector origin = node.GetOrigin()
+ vector angles = node.GetAngles()
+ entity anderson = CreatePropDynamic( ANDERSON_HOLOGRAM_MODEL, origin, angles, 0 ) // 0 = no collision
+ anderson.kv.script_name = "anderson_holo"
+ entity andersonWeapon = CreatePropDynamic( ANDERSON_PISTOL_MODEL, origin, angles, 0 ) // 0 = no collision
+ andersonWeapon.SetSkin( 1 )
+ anderson.SetSkin( 1 )
+ string nodeName = node.GetScriptName()
+ Assert( nodeName != "" )
+
+ bool isOvergrownHolo = false
+ if ( GetEntityTimelinePosition( node ) == TIMEZONE_NIGHT )
+ isOvergrownHolo = true
+
+ anderson.Hide()
+ andersonWeapon.Hide()
+
+ //string attachment = "PROPGUN"
+ //int attachIndex = anderson.LookupAttachment( attachment )
+ //vector attachOrigin = anderson.GetAttachmentOrigin( attachIndex )
+ andersonWeapon.SetParent( anderson, "PROPGUN", false, 0.0 )
+
+ string animAnderson
+ string animAndersonIdle
+ string animEnemy
+ string animEnemyIdle
+ entity andersonEnemy
+ entity enemyKnife
+ entity enemyGun
+ string flagToSetWhenDone
+ string flagToSetWhenPlaying
+
+ switch( nodeName )
+ {
+ case "node_hologram_lab1":
+ animAndersonIdle = "anderson_ghost_A_scene_idle"
+ animAnderson = "anderson_ghost_A_scene"
+ flagToSetWhenDone = "AndersonHologram1Finished"
+ flagToSetWhenPlaying = "AndersonHologram1Playing"
+ break
+ case "node_hologram_lab2":
+ animAndersonIdle = "anderson_ghost_B_scene_idle"
+ animAnderson = "anderson_ghost_B_scene"
+ flagToSetWhenDone = "AndersonHologram2Finished"
+ flagToSetWhenPlaying = "AndersonHologram2Playing"
+ break
+ case "node_hologram_lab3":
+ animAndersonIdle = "anderson_ghost_C_scene_idle"
+ animAnderson = "anderson_ghost_C_scene"
+ flagToSetWhenPlaying = "AndersonHologram3Playing"
+ andersonEnemy = CreatePropDynamic( ENEMY_HOLOGRAM_MODEL, origin, angles, 0 ) // 0 = no collision
+ andersonEnemy.SetSkin( 1 )
+ enemyKnife = CreatePropDynamic( HOLOGRAM_KNIFE_MODEL, origin, angles, 0 ) // 0 = no collision
+ enemyKnife.SetSkin( 1 )
+ enemyGun = CreatePropDynamic( HOLOGRAM_ENEMY_GUN_MODEL, origin, angles, 0 ) // 0 = no collision
+ enemyGun.SetSkin( 1 )
+ enemyGun.SetParent( andersonEnemy, "PROPGUN", false, 0.0 )
+ enemyKnife.SetParent( andersonEnemy, "KNIFE", false, 0.0 )
+ andersonEnemy.Hide()
+ andersonEnemy.MakeInvisible()
+ enemyKnife.Hide()
+ enemyKnife.MakeInvisible()
+ animEnemy = "pt_ghost_C_scene"
+ animEnemyIdle = "pt_ghost_C_scene_idle"
+ flagToSetWhenDone = "AndersonHologram3Finished"
+ thread AndersonHologram3Think( anderson, andersonWeapon )
+ thread AndersonEnemyHologram3Think( andersonEnemy, enemyKnife, enemyGun )
+ break
+ default:
+ Assert( 0, "Unhandled nodeName: " + nodeName )
+ }
+
+ if ( IsValid( andersonEnemy) )
+ {
+ Assert( animEnemyIdle != "" )
+ thread PlayAnimTeleport( andersonEnemy, animEnemyIdle, node )
+ }
+ Assert( animAndersonIdle != "" )
+ thread PlayAnimTeleport( anderson, animAndersonIdle, node )
+
+
+ FlagWait( flagToStart )
+
+
+
+
+
+
+
+
+ //only display HUD stuff once
+ if ( isOvergrownHolo )
+ thread DecodingLogsScreenPrint( player )
+
+ anderson.Show()
+ //spawn effects
+ int attachIndex = anderson.LookupAttachment( "CHESTFOCUS" )
+ StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_FLASH_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex )
+ StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_HEX_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex )
+
+ /*
+ int scanEffectIndex = GetParticleSystemIndex( FX_HOLO_SCAN_ENVIRONMENT )
+ int particleIndex = StartParticleEffectInWorldWithHandle( scanEffectIndex, anderson.GetOrigin(), <0,0,0> )
+ EffectSetControlPointVector( particleIndex, 1, <2.5,50,0> )
+ */
+
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Show()
+ StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_FLASH_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex )
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.25
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.5
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.25
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.01
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.25
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.03
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.2
+
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.3
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.2
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.05
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.5
+
+ anderson.Show()
+ StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_FLASH_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex )
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.2
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.2
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Show()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+ anderson.Hide()
+ EmitSoundOnEntity( anderson, SOUND_HOLOGRAM_FLICKER )
+ wait 0.1
+
+
+ FlagSet( flagToSetWhenPlaying )
+
+ EmitSoundOnEntity( anderson, "PathHologram_Materialized_3P" )
+ //EmitSoundOnEntity( anderson, "PathHologram_Sustain_Loop_3P" )
+
+ if ( IsValid( andersonEnemy) )
+ {
+ Assert( animEnemy != "" )
+ thread PlayAnim( andersonEnemy, animEnemy, node )
+ int attachIndexEnemy = andersonEnemy.LookupAttachment( "CHESTFOCUS" )
+ StartParticleEffectOnEntity( andersonEnemy, GetParticleSystemIndex( FX_HOLOGRAM_HEX_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndexEnemy )
+ }
+ anderson.Show()
+ andersonWeapon.Show()
+ Assert( animAnderson != "" )
+ StartParticleEffectOnEntity( anderson, GetParticleSystemIndex( FX_HOLOGRAM_FLASH_EFFECT ), FX_PATTACH_POINT_FOLLOW, attachIndex )
+ waitthread PlayAnim( anderson, animAnderson, node )
+
+ vector andersonEndPos = anderson.GetAttachmentOrigin( attachIndex )
+
+ //hologram 3 we need to manually deal with Anderson disappearing at the end...all others he can just blink out
+ if ( nodeName != "node_hologram_lab3" )
+ {
+ EmitSoundAtPosition( TEAM_UNASSIGNED, andersonEndPos, "AndersonHologram_Deactivate" )
+ PlayFX( FX_HOLOGRAM_FLASH_EFFECT, andersonEndPos )
+ }
+
+ FlagSet( flagToSetWhenDone )
+ anderson.Destroy()
+ if ( IsValid( andersonEnemy) )
+ {
+ int attachIndexEnemy = andersonEnemy.LookupAttachment( "CHESTFOCUS" )
+ vector andersonEnemyEndPos = andersonEnemy.GetAttachmentOrigin( attachIndexEnemy )
+ PlayFX( FX_HOLOGRAM_FLASH_EFFECT, andersonEnemyEndPos )
+ andersonEnemy.Destroy()
+ }
+
+
+
+ if ( isOvergrownHolo )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_ClearScanningHudElem" )
+
+
+}
+//////////////////////////////////////////////////////////////////
+void function AndersonHologram3Think( entity anderson, entity andersonGun )
+{
+ anderson.EndSignal( "OnDestroy" )
+
+ anderson.WaitSignal( "AndersonHideGun" )
+ andersonGun.Destroy()
+
+ anderson.WaitSignal( "AndersonTimeshifts" )
+
+ anderson.Dissolve( ENTITY_DISSOLVE_CHAR, Vector( 0, 0, 0 ), 0 )
+
+}
+//////////////////////////////////////////////////////////////////
+void function AndersonEnemyHologram3Think( entity andersonEnemy, entity knife, entity andersonEnemyGun )
+{
+ andersonEnemy.EndSignal( "OnDestroy" )
+
+ andersonEnemy.WaitSignal( "AndersonEnemyShow" )
+ andersonEnemy.Show()
+ andersonEnemy.MakeVisible()
+
+ andersonEnemy.WaitSignal( "AndersonEnemyShowKnife" )
+ knife.Show()
+ knife.MakeVisible()
+}
+
+
+/*
+//////////////////////////////////////////////////////////////////
+void function TitanTimeshiftLoadoutOLD( entity player )
+{
+ wait 1
+ Assert( IsValid( player ) )
+ entity bt = player.GetPetTitan()
+ Assert( IsValid( bt ) )
+ //TitanLoadoutDef loadout = bt.ai.titanSpawnLoadout
+ //loadout.special = "mp_titanability_timeshift"
+ entity weapon = bt.GetOffhandWeapon( OFFHAND_SPECIAL )
+ bt.TakeWeapon( weapon.GetWeaponClassName() )
+ //entity offhand2weapon = bt.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ //bt.TakeWeapon( offhand2weapon.GetWeaponClassName() )
+ bt.GiveOffhandWeapon( "mp_titanability_timeshift", OFFHAND_SPECIAL, [] )
+ LockOffhandSlot( player, OFFHAND_SPECIAL )
+}
+
+*/
+
+
+
+//////////////////////////////////////////////////////////////////
+void function TitanTimeshiftLoadout( entity player )
+{
+ entity weapon = player.GetOffhandWeapon( OFFHAND_SPECIAL )
+ if ( IsValid( weapon ) )
+ {
+ string weaponName = weapon.GetWeaponClassName()
+ if ( weaponName == "mp_titanability_timeshift" )
+ return
+
+ player.TakeWeapon( weaponName )
+ }
+
+ player.GiveOffhandWeapon( "mp_titanability_timeshift", OFFHAND_SPECIAL, [] )
+// LockOffhandSlot( player, OFFHAND_SPECIAL )
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function KillMyInterdimensionalBrother( entity baseNpc, entity brotherNpc = null )
+{
+ if ( !IsValid( baseNpc ) )
+ return
+
+ if ( baseNpc.IsTitan() )
+ baseNpc.WaitSignal( "OnDeath" ) //may change to Doomed if not weak
+ else
+ baseNpc.WaitSignal( "OnDeath" )
+
+ entity brother = brotherNpc
+
+ if ( !IsValid( brother ) )
+ return
+ if ( !IsAlive( brother ) )
+ return
+ if ( !brother.IsNPC() )
+ return
+
+ vector origin = brother.GetOrigin()
+
+ TakeAllWeapons( brother )
+ UnFreezeNPC( brother )
+
+
+ if( level.timeZone == TIMEZONE_NIGHT )
+ brother.TakeDamage( brother.GetMaxHealth() + 1, null, null, { damageSourceId=damagedef_suicide } ) //Kill npc manually if player can see him
+ else
+ brother.Destroy() //otherwise delete as if he never existed
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SpawnPristineStalkersWithDopplegangers( string scriptName )
+{
+ array <entity> spawners = GetEntArrayByScriptName( scriptName )
+ Assert( spawners.len() > 0 )
+
+
+ foreach( spawner in spawners )
+ {
+ Assert( IsSpawner( spawner ) )
+ entity baseStalker = spawner.SpawnEntity()
+ DispatchSpawn( baseStalker )
+ baseStalker.kv.alwaysAlert = 1
+
+
+ entity doppleganger = CreateZombieStalkerMossy( TEAM_IMC, baseStalker.GetOrigin() + Vector( 0, 0, ( TIME_ZOFFSET * -1 ) ), baseStalker.GetAngles() )
+ DispatchSpawn( doppleganger )
+ SetSquad( doppleganger, "bridge_room_overgrown" )
+ doppleganger.kv.alwaysAlert = 1
+ thread KillMyInterdimensionalBrother( baseStalker, doppleganger )
+ }
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function SpawnAutoSecurityGroup( string scriptName )
+{
+ array <entity> spawners = GetEntArrayByScriptName( scriptName )
+ Assert( spawners.len() > 0 )
+ array <entity> pristineRobots
+ array <entity> overgrownRobots
+
+ foreach( spawner in spawners )
+ {
+ Assert( IsSpawner( spawner ) )
+ entity npc = spawner.SpawnEntity()
+ DispatchSpawn( npc )
+ if ( GetEntityTimelinePosition( npc ) == TIMEZONE_DAY )
+ pristineRobots.append( npc )
+ else
+ overgrownRobots.append( npc )
+
+ }
+
+ Assert( overgrownRobots.len() == pristineRobots.len(), "Arrays of pristine and overgrown robots are not equal: " + scriptName )
+
+ int i = 0
+ foreach( robot in pristineRobots )
+ {
+ thread KillMyInterdimensionalBrother( robot, overgrownRobots[ i ] )
+ i++
+ }
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TitanRackSpawnersThink( string scriptName )
+{
+ array <entity> spawners = GetEntArrayByScriptName( scriptName )
+ Assert( spawners.len() > 0 )
+ entity pristineTitan
+ entity overgrownTitan
+
+ foreach( spawner in spawners )
+ {
+ Assert( IsSpawner( spawner ) )
+
+ array <entity> linkedEnts = spawner.GetLinkEntArray()
+ entity rack
+ foreach( ent in linkedEnts )
+ {
+ if ( ent.GetClassName() == "info_target" )
+ rack = ent
+ }
+ Assert( IsValid( rack ) )
+ entity npc = spawner.SpawnEntity()
+ npc.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+ DispatchSpawn( npc )
+ npc.EnableNPCFlag( NPC_IGNORE_ALL )
+ npc.SetNoTarget( true )
+ npc.SetTitle( "" )
+ npc.SetValidHealthBarTarget( false )
+ DisableTitanRodeo( npc )
+ HideName( npc )
+ //thread PlayAnimTeleport( npc, "bt_TDay_drop_titan1", rack )
+ thread PlayAnimTeleport( npc, "at_titanrack_bootup_idle", rack ) //bt_TDay_drop_titan1
+ //thread PlayAnim( rack, "tr_titanrack_bootup_idle" )
+
+ npc.s.rack <- rack
+
+ if ( GetEntityTimelinePosition( npc ) == TIMEZONE_DAY )
+ {
+ pristineTitan = npc
+ thread TitanRackHealthThink( pristineTitan, TIMEZONE_DAY )
+ }
+ else
+ {
+ overgrownTitan = npc
+ thread TitanRackHealthThink( overgrownTitan, TIMEZONE_DAY )
+ }
+ }
+
+ thread KillMyInterdimensionalBrother( pristineTitan, overgrownTitan )
+}
+
+
+void function TitanRackHealthThink( entity titan, var timeZone )
+{
+ titan.EndSignal( "OnDeath" )
+ if ( timeZone == TIMEZONE_DAY )
+ FlagWait( "player_inside_bridge_room_pristine" )
+ else
+ FlagWait( "player_inside_bridge_room_overgrown" )
+
+ titan.SetMaxHealth( 1000 )
+ titan.SetHealth( 1000 )
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void function TitanRackDeploy( string scriptName, var timeZone )
+{
+ array <entity> ents = GetEntArrayByScriptName( scriptName )
+ foreach( ent in ents )
+ {
+ if ( !IsValid( ent ) )
+ continue
+ if ( ent.IsNPC() )
+ thread TitanRackDeployThread( ent, timeZone )
+ }
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function TitanRackDeployThread( entity titan, var timeZone )
+{
+ if( !IsValid( titan ) )
+ return
+ if( !IsAlive( titan ) )
+ return
+
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ if ( ( GetEntityTimelinePosition( titan ) == TIMEZONE_DAY ) && ( timeZone == TIMEZONE_NIGHT ) )
+ return
+ if ( ( GetEntityTimelinePosition( titan ) == TIMEZONE_NIGHT ) && ( timeZone == TIMEZONE_DAY ) )
+ return
+
+ float animLength = titan.GetSequenceDuration( "at_titanrack_bootup" )
+ entity rack = titan.GetLinkEnt()
+ Assert( IsValid( rack ) )
+ thread PlayAnim( titan, "at_titanrack_bootup", rack )
+ thread PlayAnim( rack, "tr_titanrack_bootup" )
+ titan.SetNoTarget( false )
+
+ wait animLength
+
+ titan.DisableNPCFlag( NPC_IGNORE_ALL )
+}
+
+
+void function HideCritTimeshift( entity ent )
+{
+ int bodyGroupIndex = ent.FindBodyGroup( "hitpoints" )
+
+ if ( bodyGroupIndex == -1 )
+ {
+ return
+ }
+
+ ent.SetBodygroup( bodyGroupIndex, 1 )
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function RingsThink()
+{
+ entity rings = GetEntByScriptName( "rings_pristine" )
+ if ( !IsValid( rings ) )
+ return
+
+ rings.EndSignal( "OnDestroy" )
+ thread PlayAnim( rings, "idle" )
+
+ FlagWait( "RingsShouldBeSpinning" )
+
+ string spinAnim = "animated_slow"
+ if ( Flag( "player_back_in_amenities_lobby" ) )
+ spinAnim = "animated"
+
+ //rings.Anim_Stop()
+ thread PlayAnim( rings, spinAnim )
+
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function PlayerConversationStopOnFlagImmediate( string name, entity player, string flagToAbort )
+{
+ Assert( flagToAbort != "" )
+ waitthread PlayerConversationStopOnFlag( name, player, flagToAbort, true )
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function PlayerConversationStopOnFlag( string name, entity player, string flagToAbort = "", bool immediate = false )
+{
+ if ( flagToAbort != "" )
+ {
+ if ( Flag( flagToAbort ) )
+ return
+
+ FlagEnd( flagToAbort )
+ }
+
+ OnThreadEnd(
+ function() : ( player, immediate, flagToAbort )
+ {
+ if ( flagToAbort == "" )
+ return
+ if ( !IsValid( player ) )
+ return
+ if ( immediate == false )
+ StopConversation( player )
+ else if ( immediate == true )
+ StopConversationNow( player )
+ }
+ )
+
+ thread PlayerConversation( name, player )
+ WaitSignal( player, "ConversationEnded" )
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function FlagSetDelayed( string flagToSet, float delay )
+{
+ thread FlagSetDelayedThread( flagToSet, delay )
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function FlagSetDelayedThread( string flagToSet, float delay )
+{
+ wait delay
+ FlagSet( flagToSet )
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function FlagClearDelayed( string flagToClear, float delay )
+{
+ thread FlagClearDelayedThread( flagToClear, delay )
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function FlagClearDelayedThread( string flagToClear, float delay )
+{
+ wait delay
+ FlagClear( flagToClear )
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DecodingLogsScreenPrint( entity player )
+{
+ Remote_CallFunction_NonReplay( player, "ServerCallback_ShowHoloDecoding" )
+
+}
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function AudioLogModelThink( entity audioLogModel )
+{
+ wait 0.25
+
+ file.audioLogModels.append( audioLogModel )
+ audioLogModel.SetFadeDistance( 2048 )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, audioLogModel.GetOrigin(), "LSTAR_Reloading_Beep_low" )
+ audioLogModel.Highlight_ShowInside( 1.0 )
+ audioLogModel.Highlight_ShowOutline( 1.0 )
+
+ string instanceName = audioLogModel.GetInstanceName()
+ string audioLogAlias
+ int logIndex
+ bool isTempAudioLog = false
+ bool isBoyleAudioLog = false
+ int boyleAudioLogNumber = 0
+ int boyleLogInstanceNumber = 0
+
+ Assert( instanceName != "", "Audio log has no instance name: " + audioLogModel.GetOrigin() )
+
+ if ( instanceName == "audiolog_lobby_overgrown" )
+ {
+ // Audio Log - Scientist 2 The trial-run of the Sculptor Core will continue as planned, but you have to get security to evacuate all Tier 1 personnel.
+ // General Marder and his key team members are transferring to remote observation.
+ audioLogAlias = "diag_sp_anderson_TS171_02_01_imc_scientist2"
+ logIndex = 1
+ }
+ else if ( instanceName == "audiolog_security_overgrown" )
+ {
+ // Dr. Alexander Darren log fourteen point six. The intruder has some kind of advanced tech and is slaughtering our response teams.
+ //Tyler in Wildlife Research said 2 teams were taken out at the elevator banks in a matter of seconds...by one guy!
+ audioLogAlias = "diag_sp_anderson_TS171_01_01_imc_scientist1"
+ logIndex = 2
+ }
+ else if ( instanceName == "audiolog_lecture_overgrown" )
+ {
+
+ bool doLongSpeechAtLectern = false
+
+ if ( doLongSpeechAtLectern == true )
+ {
+ // Full ted talk, 1-28
+ instanceName = "audiolog_ted_talk"
+ audioLogAlias = "tedTalk01"
+ logIndex = 9
+ }
+ else
+ {
+ //General Marder
+ //But lest we lose sight of the bigger picture - remember those losses are ultimately
+ //replaceable by the inexorable march of human civilization.
+ audioLogAlias = "lectureLogA"
+ logIndex = 3
+ }
+
+ }
+ else if ( instanceName == "audiolog_upper_hub1_overgrown" )
+ {
+
+ //Audio log 4 This is Dr. Colby Marvin. I don't know how to explain it, but a Vanguard-Class Titan just appeared out of nowhere. The test is still underway. It will be completed.
+ audioLogAlias = "diag_sp_ambScience_TS551_09_01_imc_sci"
+ logIndex = 4
+
+ }
+ else if ( instanceName == "audiolog_upper_hub2_overgrown" )
+ {
+ //Audio log 5 Dr. Altamirano log seven point six. General Marder is gone. He's making us stay to complete the test. I don't trust this thing. The Ark is unstable.
+ audioLogAlias = "diag_sp_ambScience_TS551_10_01_imc_sci"
+ logIndex = 5
+
+ }
+ else if ( IsAudioLogBoyle( instanceName ) )
+ {
+ isBoyleAudioLog = true
+ logIndex = 6
+ boyleLogInstanceNumber = GetBoyleLogInstanceNumber( instanceName )
+
+ if ( file.debugAudioLogs )
+ thread DebugDrawAudioLogNumber( audioLogModel, file.boyleAudioLogNumberAssignments[ boyleLogInstanceNumber ].tostring() )
+ }
+
+ else if ( instanceName == "audiolog_humanroom" )
+ {
+
+ //Marders Log 21b - Human specimen 3 point 4. The experiments on the IMS Odyssey's colonists are underway.
+ //Soon we will discover the long lasting effects the Ark has on organic matter and brain function.
+ audioLogAlias = "diag_sp_audioLog_TS132_01_01_imc_genMarder"
+ logIndex = 7
+
+ }
+ else if ( instanceName == "audiolog_humanroom_tower" )
+ {
+ //Audio log 7 Dr. Ehrenberg log eleven point four. Further research still leaves questions about the Fold Weapon and its intended purpose.
+ //I don't think we're using it right and that may cause a problem. Marder thinks it's worth it. Well I'm going on record - this is a bad idea.
+ audioLogAlias = "diag_sp_ambScience_TS551_12_01_imc_sci"
+ logIndex = 8
+ }
+
+ else if ( instanceName == "audiolog_ted_talk" )
+ {
+ // Full ted talk, 1-28
+ audioLogAlias = "tedTalk01"
+ logIndex = 9
+ }
+
+
+ else
+ Assert( 0, "Unhandled audio log instance name: " + instanceName )
+
+
+ audioLogModel.SetUsable()
+ audioLogModel.SetUsableByGroup( "pilot" )
+ audioLogModel.SetUsePrompts( "#TIMESHIFT_HINT_AUDIO_LOG" , "#TIMESHIFT_HINT_AUDIO_LOG_PC" )
+ local playerActivator
+ while( true )
+ {
+ playerActivator = audioLogModel.WaitSignal( "OnPlayerUse" ).player
+ if ( IsValid( playerActivator ) && playerActivator.IsPlayer() )
+ break
+ }
+
+ FlagSet( "AudioLogPlaying" )
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, audioLogModel.GetOrigin(), "LSTAR_Reloading_Beep_low" )
+ audioLogModel.UnsetUsable()
+ audioLogModel.Highlight_HideInside( 0 )
+ audioLogModel.Highlight_HideOutline( 0 )
+
+ wait 0.5
+
+ entity player = GetPlayerArray()[ 0 ]
+ if ( !IsValid( player ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+
+
+ //Remote_CallFunction_NonReplay( player, "ServerCallback_ShowHoloDecoding", logIndex )
+
+ //wait 2
+
+ thread StopAudioLogWhenPlayerFarAway( audioLogModel )
+
+ audioLogModel.EndSignal( "StopAudioLog" )
+
+ OnThreadEnd(
+ function() : ( player, audioLogModel )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_ClearScanningHudElem" )
+ FlagClear( "AudioLogPlaying" )
+ if ( IsValid( audioLogModel ) )
+ {
+ audioLogModel.Signal( "StopAudioLog" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, audioLogModel.GetOrigin(), "LSTAR_Reloading_Beep_low" )
+ delaythread( 2 ) AudioLogModelThink( audioLogModel )
+ }
+
+
+ }
+ )
+
+
+ if ( isBoyleAudioLog )
+ {
+ boyleAudioLogNumber = GetBoyleLogNumber( boyleLogInstanceNumber, audioLogModel )
+ file.boyleAudioLogNumberAssignments[ boyleLogInstanceNumber ] = boyleAudioLogNumber
+ }
+ else
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, audioLogAlias )
+
+
+
+
+ //------------------------------------------------
+ // If this is the lecture hall, play the tail lines
+ //-------------------------------------------------
+ if ( instanceName == "audiolog_lecture_overgrown" )
+ {
+ if ( !Flag( "PlayerInterruptedLecture") )
+ {
+
+ //General Marder By decisively neutralizing the Militia forces,
+ //we will in fact, safeguard the existence of the human race, extending its reach and power towards a prosperous, and bright future.
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "lectureLogB" )
+ }
+ else
+ {
+ entity soundDummy = CreateLoudspeakerEnt( audioLogModel.GetOrigin() )
+
+ //General Marder By decisively neutralizing the Militia forces,
+ //we will in fact, safeguard the existence of the human race, extending its reach and power towards a prosperous, and bright future.
+ thread PlayTimeShiftDialogue( player, soundDummy, "lectureLogB" )
+ wait file.lectureHallTimeBeforePlayerInterrupts
+
+ //General Marder Yes, test Pilot? May I help you?
+ soundDummy.Destroy()
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "lectureLogC" )
+
+ }
+ }
+
+ //------------------------------------------------
+ // If this is a Boyle log, play all the aliases for the log number
+ //-------------------------------------------------
+ else if ( isBoyleAudioLog )
+ {
+ array <string> aliases
+ if ( boyleAudioLogNumber == 1 )
+ {
+ //---------------------------------------
+ // Boyle audio logs: 1
+ //---------------------------------------
+ //Dr. Jefferson Boyle - Log one. Looks like they went forward with the Ark test despite my warnings to postpone, but what Marder wants - Marder gets.
+ aliases.append( "diag_sp_BoyleLog1_TS701_01_01_imc_boyle" )
+
+ //I don’t know how I survived, but I did...for now. I don’t know how I survived, but I did...for now.
+ aliases.append( "diag_sp_BoyleLog1_TS701_02_01_imc_boyle" )
+
+ //I’ve tried all exits but I’m trapped; damn place is locked down good. All I have is Hope. That’s what I get for picking a lab underground... What can say? I like archaeology.
+ aliases.append( "diag_sp_BoyleLog1_TS701_03_01_imc_boyle" )
+ }
+ else if ( boyleAudioLogNumber == 2 )
+ {
+
+ //---------------------------------------
+ // Boyle audio logs: 2
+ //---------------------------------------
+ //Dr. Jefferson Boyle Log 2 Dr. Jefferson Boyle - Log two. I found myself a standard IMC survival kit, which provides me with enough flavorless rations to keep me alive for a few days. Dr. Jefferson Boyle. Log two. I found myself a standard IMC survival kit, which provides me with enough flavorless rations to keep me alive for a few days.
+ aliases.append( "diag_sp_BoyleLog2_TS701_04_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 2 I’m hoping that’s all I need otherwise I’m going to have to get creative. I hate getting creative... I’m hoping that’s all I need otherwise I’m going to have to get creative. I hate getting creative...
+ aliases.append( "diag_sp_BoyleLog2_TS701_05_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 2 I hate getting creative...
+ //aliases.append( "diag_sp_BoyleLog2_TS701_06_01_imc_boyle" )
+
+ }
+ else if ( boyleAudioLogNumber == 3 )
+ {
+
+ //---------------------------------------
+ // Boyle audio logs: 3
+ //---------------------------------------
+ //Dr. Jefferson Boyle Log 3 Dr. Jefferson Boyle - Log three. I had to get creative. Failed experiments on Typhon’s indigenous wildlife are unfortunately next door...in other words, I cooked a prowler. Dr. Jefferson Boyle. Log three. I had to get creative. Failed experiments on Typhon’s indigenous wildlife are unfortunately next door...in other words, I cooked a prowler.
+ aliases.append( "diag_sp_BoyleLog3_TS701_07_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 3 It tasted like chicken if chicken was a weird dinosaur-like creature injected with IMC meds and steroids. It tasted like chicken if chicken was a weird dinosaur-like creature injected with IMC meds and steroids.
+ aliases.append( "diag_sp_BoyleLog3_TS701_08_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 3 On a lighter note, I think I found a way to upload these logs to what's left of the IMC network here. Maybe someone's monitoring...here's to Hope. On a lighter note, I think I found a way to upload these logs to what's left of the IMC network here. Maybe someone's monitoring...here's to Hope.
+ aliases.append( "diag_sp_BoyleLog3_TS701_09_01_imc_boyle" )
+
+ }
+ else if ( boyleAudioLogNumber == 4 )
+ {
+ //---------------------------------------
+ // Boyle audio logs: 4
+ //---------------------------------------
+ //Dr. Jefferson Boyle Log 4 Dr. Jefferson Boyle - Log four. All right, my logs are on the network but unfortunately, I am out of Prowler so I'm pretty disappointed. Dr. Jefferson Boyle. Log four. All right, my logs are on the network but unfortunately, I am out of Prowler so I'm pretty disappointed.
+ aliases.append( "diag_sp_BoyleLog4_TS701_10_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 4 I’ve moved on to the vines and plants growing throughout this facility. I’ve moved on to the vines and plants growing throughout this facility.
+ aliases.append( "diag_sp_BoyleLog4_TS701_11_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 4 It’s raining non-stop so I have water, at least I think it’s water. It’s raining non-stop so I have water, at least I think it’s water.
+ aliases.append( "diag_sp_BoyleLog4_TS701_12_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 4 Yeah, it's water. It's definitely water....I think. Um, I gotta go run some tests... Yeah, it's water. It's definitely water...I think. Um, I got to go run some tests...
+ aliases.append( "diag_sp_BoyleLog4_TS701_13_01_imc_boyle" )
+
+ }
+ else if ( boyleAudioLogNumber == 5 )
+ {
+ //---------------------------------------
+ // Boyle audio logs: 5
+ //---------------------------------------
+
+ //Dr. Jefferson Boyle Log 5 Dr. Jefferson Boyle - Log five. Okay, good news, tests came back negative...it is water. Bad news... Literally everything else. Nothing's good. Dr. Jefferson Boyle. Log five. Okay, good news, tests came back negative...it is water. Bad news... Literally everything else. Nothing's good.
+ aliases.append( "diag_sp_BoyleLog5_TS701_14_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 5 I think it's time I get out of this place. I've managed to breach a hole in the wall, it leads up to the main campus. I think it's time I get out of this place. I've managed to breach a hole in the wall, it leads up to the main campus.
+ aliases.append( "diag_sp_BoyleLog5_TS701_15_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 5 I'm about 100 meters underground. I have no idea how long it'll take me to get to the top because I'm horrible at math, but I am an archaeologist; I've done some climbing before. I'm about 100 meters underground. I have no idea how long it'll take me to get to the top because I'm horrible at math, but I am an archaeologist; I've done some climbing before.
+ aliases.append( "diag_sp_BoyleLog5_TS701_16_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 5 I just don't know what I'll find when I get up there but I have no choice. Okay, I have a choice but right now, it's not to die. I just don't know what I'll find when I get up there but I have no choice. Okay, I have a choice but right now, it's not to die.
+ aliases.append( "diag_sp_BoyleLog5_TS701_17_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 5 So, that's it. This is my final entry. Wish me luck. So, that's it. This is my final entry. Wish me luck.
+ aliases.append( "diag_sp_BoyleLog5_TS701_18_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 5 See you soon, Hope. See you soon, Hope.
+ aliases.append( "diag_sp_BoyleLog5_TS701_19_01_imc_boyle" )
+
+ //Dr. Jefferson Boyle Log 5 Dr. Jefferson Boyle - Signing Off. Err.. over and out. Is that how you say it? Whatever... bye. Dr. Jefferson Boyle. Signing Off. Err.. over and out. Is that how you say it? Whatever... bye.
+ aliases.append( "diag_sp_BoyleLog5_TS701_20_01_imc_boyle" )
+
+ }
+ else
+ Assert( 0, "Invalid Boyle audio log number " + boyleAudioLogNumber )
+
+ foreach( alias in aliases )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, alias )
+ }
+
+
+
+
+ //-------------------------------------------------------------
+ // If this is the full ted talk, play the whole damn thing
+ //--------------------------------------------------------------
+ else if ( instanceName == "audiolog_ted_talk" )
+ {
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk02" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk03" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk04" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk05" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk06" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk07" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk08" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk09" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk10" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk11" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk12" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk13" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk14" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk15" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk16" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk17" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk18" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk19" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk20" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk21" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk22" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk23" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk24" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk25" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk26" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk27" )
+ waitthread PlayTimeShiftDialogue( player, audioLogModel, "tedTalk28" )
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+void function StopAudioLogWhenPlayerFarAway( entity audioLogModel )
+{
+ if ( !IsValid( audioLogModel ) )
+ return
+
+ audioLogModel.EndSignal( "StopAudioLog" )
+
+ array<entity> players = GetPlayerArray()
+ if ( players.len() == 0 )
+ return
+
+ entity player = players[ 0 ]
+ if ( !IsValid( player ) )
+ return
+
+ var timeZoneAudioLog = GetEntityTimelinePosition( audioLogModel )
+ player.EndSignal( "OnDeath" )
+
+ while( true )
+ {
+ wait 0.25
+ if ( timeZoneAudioLog != level.timeZone ) //don't do distance check if we are in other timezone
+ continue
+ if ( !PlayerInRange( player.GetOrigin(), audioLogModel.GetOrigin(), DIST_TO_NOT_CARE_ABOUT_AUDIOLOGS ) )
+ break
+ }
+
+
+ audioLogModel.Signal( "StopAudioLog" )
+}
+///////////////////////////////////////////////////////////////////////////
+bool function IsAudioLogBoyle( string instanceName )
+{
+ if ( instanceName.find( "audiolog_boyle" ) == null )
+ return false
+ return true
+
+}
+///////////////////////////////////////////////////////////////////////////
+int function GetBoyleLogInstanceNumber( string instanceName )
+{
+ int instanceNumber = -1
+
+ if ( instanceName == "audiolog_boyle_0" )
+ instanceNumber = 0
+ else if ( instanceName == "audiolog_boyle_1" )
+ instanceNumber = 1
+ else if ( instanceName == "audiolog_boyle_2" )
+ instanceNumber = 2
+ else if ( instanceName == "audiolog_boyle_3" )
+ instanceNumber = 3
+ else if ( instanceName == "audiolog_boyle_4" )
+ instanceNumber = 4
+ else
+ Assert( 0, "Unhandled instance name " + instanceName )
+
+ Assert( instanceNumber > -1 )
+
+ return instanceNumber
+}
+///////////////////////////////////////////////////////////////////////////
+int function GetBoyleLogNumber( int instanceNumber, entity audioLogModel )
+{
+ int logNumber = file.boyleAudioLogNumberAssignments[ instanceNumber ]
+ if ( logNumber == 0 )
+ logNumber = GetNextBoyleLogNumber( audioLogModel )
+
+ return logNumber
+
+}
+
+///////////////////////////////////////////////////////////////////////////
+int function GetNextBoyleLogNumber( entity audioLogModel )
+{
+ int logNumber = file.boyleAudioLogsCollected + 1
+ Assert( logNumber > 0 && logNumber < 6, "Boyle log at " + audioLogModel.GetOrigin() + " is trying to get assigned a number greater than 5: " + logNumber )
+ file.boyleAudioLogsCollected++
+
+ return logNumber
+}
+///////////////////////////////////////////////////////////////////////////
+void function TempAudioLogDevMsg( entity player )
+{
+ Dev_PrintMessage( player, "#BLANK_TEXT", "#AUDIOLLOG_TEMPTEXT_TIMESHIFT", 3.0 )
+}
+
+////////////////////////////////////////////////////////////////////////////
+void function InitBoyleAudioLogs()
+{
+ LevelTransitionStruct ornull trans = GetLevelTransitionStruct()
+ if ( trans == null )
+ return
+
+ expect LevelTransitionStruct( trans )
+ file.boyleAudioLogsCollected = trans.boyleAudioLogsCollected
+
+ for ( int i = 0 ; i < file.boyleAudioLogNumberAssignments.len(); i++ )
+ {
+ file.boyleAudioLogNumberAssignments[ i ] = trans.boyleAudioLogNumberAssignments[ i ]
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+LevelTransitionStruct function SaveBoyleAudioLogs()
+{
+ LevelTransitionStruct trans
+ trans.boyleAudioLogsCollected = file.boyleAudioLogsCollected
+
+ for ( int i = 0 ; i < trans.boyleAudioLogNumberAssignments.len(); i++ )
+ {
+ trans.boyleAudioLogNumberAssignments[ i ] = file.boyleAudioLogNumberAssignments[ i ]
+ }
+
+ return trans
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+void function SetLectureHallLineDuration( float duration )
+{
+ file. lectureHallTimeBeforePlayerInterrupts = duration
+}
+///////////////////////////////////////////////////////////////////////////
+
+void function ProwlersAmbientThink( entity npc )
+{
+ npc.EnableNPCFlag( NPC_IGNORE_ALL )
+ npc.SetNoTarget( true )
+}
+///////////////////////////////////////////////////////////////////////////
+
+void function LightFlickerThink( entity lightModelOff )
+{
+ entity lightModelOn = lightModelOff.GetLinkEnt()
+ Assert( IsValid( lightModelOn ), "Light model at " + lightModelOff.GetOrigin() + " needs to target a lit version of the same model" )
+
+ lightModelOn.Hide()
+ entity fx
+
+ while( true )
+ {
+
+ wait RandomFloatRange( 0.5, 0.6 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+ EmitSoundOnEntity( lightModelOn, SOUND_LIGHT_FLICKER )
+
+ wait RandomFloatRange( 0.3, 0.4 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+ wait RandomFloatRange( 0.5, 0.6 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+
+ wait RandomFloatRange( 0.5, 0.6 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+ wait RandomFloatRange( 0.5, 0.6 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+
+
+ wait RandomFloatRange( 0.01, 0.02 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+ wait RandomFloatRange( 0.01, 0.02 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+ EmitSoundOnEntity( lightModelOn, SOUND_LIGHT_FLICKER )
+
+ wait RandomFloatRange( 0.02, 0.03 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+ wait RandomFloatRange( 1, 1.1 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+
+
+ wait RandomFloatRange( 0.2, 0.3 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+ wait RandomFloatRange( 1, 1.1 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+ EmitSoundOnEntity( lightModelOn, SOUND_LIGHT_FLICKER )
+
+ wait RandomFloatRange( 0.2, 0.3 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+ wait RandomFloatRange( 0.2, 0.3 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+
+
+
+ wait RandomFloatRange( 0.01, 0.02 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+ wait RandomFloatRange( 0.01, 0.02 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+ EmitSoundOnEntity( lightModelOn, SOUND_LIGHT_FLICKER )
+
+ wait RandomFloatRange( 0.01, 0.02 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+ wait RandomFloatRange( 0.01, 0.02 )
+ lightModelOn.Show()
+ lightModelOff.Hide()
+ fx = PlayFXOnEntity( FX_DLIGHT_LIGHT_FLICKER, lightModelOn )
+
+
+ wait RandomFloatRange( 0.02, 0.03 )
+ lightModelOff.Show()
+ lightModelOn.Hide()
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+
+
+ }
+
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+void function PlayerDropLand( entity player, entity node, bool doBlur = false )
+{
+ entity moverNode = CreateScriptMover( node.GetOrigin(), node.GetAngles() )
+
+ float targetZpos = node.GetOrigin().z + 16
+
+ while( player.GetOrigin().z > targetZpos )
+ WaitFrame()
+
+ player.UnfreezeControlsOnServer()
+ player.ClearInvulnerable()
+
+ string anim3rd= "pt_timeshift_fall_land"
+ string animPOV = "ptpov_timeshift_fall_land"
+
+ player.SetAnimNearZ( 3 )
+
+ if ( doBlur )
+ Remote_CallFunction_Replay( player, "ServerCallback_FanDropBlur" )
+
+ //PlayFPSAnimTeleportShowProxy( player, anim3rd, anim1st ref = null optionalTag, animView = null, float initialTime = 0.0 )
+ waitthread PlayFPSAnimTeleportShowProxy( player, anim3rd, animPOV, moverNode, "REF", ViewConeTight )
+ //wait 2
+ player.ClearAnimNearZ()
+ player.ClearParent()
+ moverNode.Destroy()
+ player.UnforceStand()
+ player.Anim_Stop()
+ ClearPlayerAnimViewEntity( player )
+ player.ClearParent()
+ player.EnableWeaponWithSlowDeploy()
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function RingsLocalExplosionNormal( entity rings )
+{
+ if ( level.timeZone == TIMEZONE_NIGHT )
+ return
+ vector origin = rings.GetOrigin()
+ //CreateShake( org, amplitude = 16, frequency = 150, duration = 1.5, radius = 2048 )
+ thread CreateAirShake( origin, 4, 10, 1, 32000 )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function RingsLocalExplosionBig( entity rings )
+{
+ if ( level.timeZone == TIMEZONE_NIGHT )
+ return
+
+ vector origin = rings.GetOrigin()
+ //CreateShake( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
+ thread CreateAirShake( origin, 4, 10, 1, 32000 )
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+void function CreateShakeTimeshift( float amplitude, float frequency, float duration )
+{
+ array<entity> players = GetPlayerArray()
+ if ( players.len() == 0 )
+ return
+
+ entity player = players[ 0 ]
+ if ( !IsValid( player ) )
+ return
+
+ CreateAirShake( player.GetOrigin(), amplitude, frequency, duration )
+ //Remote_CallFunction_Replay( player, "ServerCallback_ScreenShake", amplitude, frequency, duration )
+}
+/////////////////////////////////////////////////////////////////////////////////////////
+void function CreateShakeWhileFlagSet( float amplitude, float frequency, float duration, string flagToShake, string flagToAbort )
+{
+ array<entity> players = GetPlayerArray()
+ if ( players.len() == 0 )
+ return
+
+ entity player = players[ 0 ]
+ if ( !IsValid( player ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+ FlagEnd( flagToAbort )
+
+ while ( true )
+ {
+ wait 0.1
+ if ( Flag( flagToShake ) )
+ {
+ CreateAirShake( player.GetOrigin(), amplitude, frequency, duration )
+ //Remote_CallFunction_Replay( player, "ServerCallback_ScreenShake", amplitude, frequency, duration )
+ wait duration / 4
+ }
+ }
+
+}
+
+void function TitanTimeshiftHint( entity player )
+{
+ Remote_CallFunction_Replay( player, "ServerCallback_ShowTitanTimeshiftHint" )
+}
+
+/////////////////////////////////////////////////////////////////////////////
+string function GetrandomDeathPose( asset deathModel )
+{
+ if ( deathModel == MARVIN_MODEL_OVERGROWN )
+ return "mv_timeshift_death_pose_01"
+
+ array <string> deathPosesLocal = file.deathPoses
+ deathPosesLocal.randomize()
+ return deathPosesLocal[ 0 ]
+}
+
+/////////////////////////////////////////////////////////////////////////////
+void function FlyerAmbientThink( entity flyer )
+{
+ if ( GetMapName() == "sp_hub_timeshift" )
+ return
+
+ flyer.EndSignal( "OnDeath" )
+ flyer.EndSignal( "OnDestroy" )
+
+ FlagWait( "ForceFlyerTakeoff" )
+ flyer.Signal( "FlyerStopThink" )
+ //flyer.WaitSignal( "FlyerTakeoffOverride" )
+ wait RandomFloatRange( 0, 6 )
+ thread FlyerTakeOff( flyer )
+}
+
+void function DropshipSpawnAndRepeat( entity dropship )
+{
+ dropship.EndSignal( "OnDeath" )
+ dropship.EndSignal( "OnDestroy" )
+ dropship.EndSignal( "OnAnimationInterrupted" )
+ entity animNode = dropship.GetLinkEnt()
+ Assert( IsValid( animNode ) )
+ string anim = expect string( animNode.kv.leveled_animation )
+ float animLength = dropship.GetSequenceDuration( anim )
+ Assert( IsValid( anim ) )
+
+ bool repeat = true
+ if ( dropship.GetScriptName() == "dropships_skybridge" )
+ repeat = false
+
+ OnThreadEnd(
+ function() : ( dropship )
+ {
+ if ( IsValid( dropship ) )
+ dropship.Destroy()
+ }
+ )
+
+ while( true )
+ {
+
+ wait animLength
+
+ if ( repeat == false )
+ break
+
+ }
+
+}
+
+
+void function DisableNavmeshSeperatorTargetedByEnt( entity doorModel )
+{
+ array <entity> linkedEnts = doorModel.GetLinkEntArray()
+ Assert( linkedEnts.len() > 0 )
+ string classname
+ entity navmeshBrush
+
+ foreach( entity ent in linkedEnts )
+ {
+ classname = GetEditorClass( ent )
+ if ( classname == "func_brush_navmesh_separator" )
+ {
+ navmeshBrush = ent
+ break
+ }
+ }
+
+ Assert( IsValid( navmeshBrush ), "Entity at " + doorModel.GetOrigin() + " isn't targeting a func_brush_navmesh_separator" )
+
+ //navmeshBrush.Hide()
+ navmeshBrush.NotSolid()
+ ToggleNPCPathsForEntity( navmeshBrush, true )
+
+
+}
+
+
+
+
+void function ElectricalScreenEffects( entity player, string enabledFlag = "" )
+{
+ EndSignal( player, "OnDeath" )
+ EndSignal( player, "StopCoreEffects" )
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid ( player ) )
+ StopSoundOnEntity( player, EMP_IMPARED_SOUND )
+ }
+ )
+
+ if ( enabledFlag != "" )
+ {
+ if ( !Flag( enabledFlag ) )
+ FlagWait( enabledFlag )
+ }
+
+ array<entity> ents = GetEntArrayByScriptName( "BeaconScreenEffect" )
+ array<vector> start
+ array<vector> end
+ array<float> radius
+ foreach( entity ent in ents )
+ {
+ start.append( ent.GetOrigin() )
+ end.append( ent.GetLinkEnt().GetOrigin() )
+ radius.append( float( ent.kv.radius ) )
+ }
+
+ bool soundPlaying
+ float maxAmount
+ vector p
+ while( true )
+ {
+ maxAmount = 0
+ p = player.GetOrigin()
+ for ( int i = 0 ; i < ents.len() ; i++ )
+ {
+ float d = GetDistanceFromLineSegment( start[i], end[i], p )
+ float amount = GraphCapped( d, 0.0, radius[i], 1.0, 0.0 )
+ maxAmount = max( amount, maxAmount )
+ }
+
+ if ( maxAmount > 0 )
+ {
+ StatusEffect_AddTimed( player, eStatusEffect.emp, maxAmount, 0.25, 0.05 )
+ if ( !soundPlaying )
+ {
+ EmitSoundOnEntity( player, EMP_IMPARED_SOUND )
+ soundPlaying = true
+ }
+ }
+ else if ( soundPlaying )
+ {
+ StopSoundOnEntity( player, EMP_IMPARED_SOUND )
+ soundPlaying = false
+ }
+
+ wait 0.1
+ }
+}
+
+
+void function GivePropForAnim( entity npc, string anim )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ string tagName
+ asset model
+
+ if ( anim == "pt_lecture_student_1_idle" )
+ {
+ tagName = "L_HAND"
+ model = MODEL_IPAD
+ }
+
+ else if ( anim == "pt_lecture_student_2_idle" )
+ {
+ tagName = "R_HAND"
+ model = MODEL_COFFEE
+ }
+
+ else if ( anim == "pt_lecture_student_5_idle" )
+ {
+ tagName = "L_HAND"
+ model = MODEL_IPAD
+ }
+ else if ( anim == "pt_civ_walk_tablet" )
+ {
+ tagName = "R_HAND"
+ model = MODEL_IPAD
+ }
+
+ else if ( anim == "pt_civ_walk_tablet_reading" )
+ {
+ tagName = "R_HAND"
+ model = MODEL_IPAD
+ }
+
+ else if ( anim == "pt_civ_walk_drink" )
+ {
+ tagName = "R_HAND"
+ model = MODEL_COFFEE
+ }
+
+ else
+ return
+
+ entity prop = CreatePropDynamic( model )
+ prop.SetParent( npc, tagName, false )
+}
+
+
+
+void function LoudspeakerThread( entity player )
+{
+
+ //if ( GetBugReproNum() != 202020 )
+ //return
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( file.loudspeakerThreadRunning )
+ return
+
+ FlagSet( "ShouldPlayGlobalLoudspeker" )
+
+ file.loudspeakerThreadRunning = true
+
+ player.EndSignal( "OnDeath" )
+
+ wait 3
+
+ array <string> arrayLoudspeakerLines
+ arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_01_01_imc_sci" )
+ arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_02_01_imc_sci" )
+ arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_03_01_imc_sci" )
+ arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_04_01_imc_sci" )
+ arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_05_01_imc_sci" )
+ arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_06_01_imc_sci" )
+ arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_07_01_imc_sci" )
+ arrayLoudspeakerLines.append( "diag_sp_ambScience_TS551_08_01_imc_sci" )
+
+ int numberOfLoudspeakerLines = arrayLoudspeakerLines.len() -1
+
+ entity loudspeakerEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY )
+ int loudspeakerLineCount = 0
+ //bool playSpectreLine = false
+
+
+ while( true )
+ {
+ waitthread WaittillPlayerSwitchesTimezone( TIMEZONE_DAY )
+
+ if ( !Flag( "ShouldPlayGlobalLoudspeker" ) )
+ {
+ FlagWait( "ShouldPlayGlobalLoudspeker" )
+ continue
+ }
+
+ loudspeakerEnt = CreateBestLoudspeakerEnt( player, TIMEZONE_DAY, loudspeakerEnt )
+
+ waitthread PlayTimeShiftDialogue( player, loudspeakerEnt, "Timeshift_Scr_AnnouncementChime" )
+
+ //if ( playSpectreLine )
+ //waitthread PlayTimeShiftDialogue( player, loudspeakerEnt, "Timeshift_Scr_SpectreAnnouncement" )
+
+ waitthread PlayTimeShiftDialogue( player, loudspeakerEnt, arrayLoudspeakerLines[ loudspeakerLineCount ] )
+ loudspeakerLineCount++
+ if ( loudspeakerLineCount > numberOfLoudspeakerLines )
+ loudspeakerLineCount = 0
+
+
+ wait RandomFloatRange( 60, 70 )
+
+ /*
+ if ( playSpectreLine == false )
+ playSpectreLine = true
+ else if ( playSpectreLine == true )
+ playSpectreLine = false
+ */
+ }
+}
+
+
+void function ButtonOvergrownThink( entity propDynamic, string whichButton )
+{
+ asset swapModelName
+ entity swapModel
+
+ if ( whichButton == "small" )
+ swapModelName = MODEL_BUTTON
+ else
+ swapModelName = MODEL_BUTTON_LARGE
+
+ swapModel = CreatePropDynamic( swapModelName, propDynamic.GetOrigin(), propDynamic.GetAngles() )
+
+ while( true )
+ {
+ swapModel.Hide()
+ propDynamic.Show()
+
+ wait RandomFloatRange( 2, 3 )
+
+ swapModel.Show()
+ propDynamic.Hide()
+
+ wait 0.2
+
+ swapModel.Hide()
+ propDynamic.Show()
+
+ wait 0.1
+
+ swapModel.Show()
+ propDynamic.Hide()
+
+ wait 0.2
+
+ swapModel.Hide()
+ propDynamic.Show()
+
+ wait 0.05
+
+ swapModel.Show()
+ propDynamic.Hide()
+
+ wait 0.05
+
+ swapModel.Hide()
+ propDynamic.Show()
+
+ wait 0.05
+
+ swapModel.Show()
+ propDynamic.Hide()
+
+ wait 0.4
+
+ swapModel.Hide()
+ propDynamic.Show()
+
+ wait 0.1
+
+ swapModel.Show()
+ propDynamic.Hide()
+
+ wait 0.2
+
+ swapModel.Hide()
+ propDynamic.Show()
+
+ wait 0.05
+
+ swapModel.Show()
+ propDynamic.Hide()
+
+ }
+}
+
+
+entity function CreateTimeshiftCinematicFlyer( entity flyerModel, entity victim = null )
+{
+ entity newFlyer = CreateServerFlyer( flyerModel.GetOrigin(), flyerModel.GetAngles(), 100 )
+ flyerModel.Destroy()
+ return newFlyer
+
+}
+
+///////////////////////////////////////////////////////////////////
+void function ObjectiveRemindUntilFlag( string flagToAbort )
+{
+ Assert( IsNewThread(), "Must be threaded off" )
+
+ if ( Flag( flagToAbort ) )
+ return
+ FlagEnd( flagToAbort )
+
+ while( true )
+ {
+ wait RandomFloatRange( 45, 50 )
+ if ( Flag( flagToAbort ) )
+ break
+ Objective_Remind()
+
+ }
+
+
+}
+///////////////////////////////////////////////////////////////////
+void function SetFlagWhenPlayerWithinRangeOfEnt( entity player, entity ent, float minDist, string flagToSet )
+{
+ if ( Flag( flagToSet ) )
+ return
+
+ if ( !IsValid( ent ) )
+ return
+
+ if ( !IsValid( player ) )
+ return
+
+ FlagEnd( flagToSet )
+ ent.EndSignal( "OnDestroy" )
+ ent.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDeath" )
+
+ while( true )
+ {
+ wait 0.25
+ if ( Distance( player.GetOrigin(), ent.GetOrigin() ) < minDist )
+ {
+ FlagSet( flagToSet )
+ break
+ }
+
+ }
+
+}
+///////////////////////////////////////////////////////////////////
+
+bool function IsAudioLogPlaying( entity player )
+{
+ if ( !IsValid( player ) )
+ return false
+
+ if ( !Flag( "AudioLogPlaying" ) )
+ return false
+
+ if ( !PlayerInRangeOfAnyLaptopWhatsoever( player ) )
+ return false
+
+ return true
+}
+///////////////////////////////////////////////////////////////////
+bool function PlayerInRangeOfAnyLaptopWhatsoever( entity player )
+{
+ //even if an audio log is "playing", we don't care if player is more than X units away from any laptop
+ foreach( model in file.audioLogModels )
+ {
+ if ( PlayerInRange( player.GetOrigin(), model.GetOrigin(), DIST_TO_NOT_CARE_ABOUT_AUDIOLOGS ) )
+ return true
+ }
+
+ return false
+} \ No newline at end of file
diff --git a/Northstar.Coop/scripts/vscripts/sp/sh_coop_sp_utils.gnut b/Northstar.Coop/scripts/vscripts/sp/sh_coop_sp_utils.gnut
new file mode 100644
index 000000000..62f603e9a
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/sh_coop_sp_utils.gnut
@@ -0,0 +1,34 @@
+untyped
+
+global function ClCoopSpUtils_Init
+global function AreAllPlayersDead
+
+global function IsPlayingTimeshiftLevel
+
+void function ClCoopSpUtils_Init()
+{
+
+}
+
+bool function AreAllPlayersDead()
+{
+ foreach ( entity player in GetPlayerArray() )
+ if ( IsAlive( player ) )
+ return false
+
+ return true
+}
+
+// TIMESHIFT STUFF
+
+bool function IsPlayingTimeshiftLevel()
+{
+ bool allowed = false
+ try
+ {
+ allowed = expect bool( level.allowTimeTravel )
+ }
+ catch ( exception ) {}
+
+ return GetMapName().find( "timeshift" ) != null || allowed
+}
diff --git a/Northstar.Coop/scripts/vscripts/sp/sh_sp_objective.gnut b/Northstar.Coop/scripts/vscripts/sp/sh_sp_objective.gnut
new file mode 100644
index 000000000..d98d4a6e1
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/sh_sp_objective.gnut
@@ -0,0 +1,537 @@
+global function SPObjectiveInit
+
+const OBJECTIVE_DISPLAY_TIME = 8.0
+const FADE_IN_TIME = 0.2
+const FADE_OUT_TIME = 0.5
+const OBJECTIVE_DISPLAY_TOTAL_TIME = FADE_IN_TIME + OBJECTIVE_DISPLAY_TIME + FADE_OUT_TIME
+
+#if SERVER
+ global function Objective_Set
+ global function Objective_SetSilent
+ global function Objective_Set_WithAltHighlight
+ global function Objective_SetSilent_WithAltHighlight
+ global function Objective_AddHighlightEntity
+ global function Objective_Clear
+ global function Objective_Hide
+ global function Objective_Update
+ global function Objective_InitEntity
+ global function Objective_Remind
+ global function Objective_AddKilometers
+ global function Objective_StaticModelHighlightOverrideEntity // if set it will highlight this entity instead, and also hide/show the entity when highlighting. This helps us get around not being able to do model highlight on a static model
+ global function Objective_StaticModelHighlightOverrideEntityArray
+ global function Objective_GetMarkerEntity // Dont use this if you're not working on S2S
+ global function Objective_SetFastball// Don't use this unless you grab the marker ent and clear it's parent after you're done
+ global function Objective_WayPointEneable
+ global function Objective_LastShownTime
+ global function Objective_SuppressCloseMenuShowsObjective
+
+ struct
+ {
+ array<entity> objectiveEntities
+ entity objectiveMarkerEnt
+ array<entity> objectiveHighlightEntArray
+ array<entity> objectiveAltHighlightEntArray
+ bool showingObjective
+ float objectiveLastShownTime
+ bool suppressCloseMenuShowsObjective
+ bool objectiveAltHighlight = false
+ } file
+#endif
+
+#if CLIENT
+ global function ShowObjectiveLineChanged
+ global function ObjectiveStringChanged
+ global function ShowObjectiveChanged
+
+ const OBJECTIVE_MARKER_MODEL = $"models/dev/editor_ref.mdl"
+ const BLING_DURATION = 0.2
+
+ struct
+ {
+ bool onTimeshiftLevel
+ var objectiveRUI
+ } file
+#endif
+
+void function SPObjectiveInit()
+{
+ #if SERVER
+ RegisterSignal( "ShowingObjective" )
+ RegisterSignal( "HidingObjective" )
+ AddClientCommandCallback( "ToggleObjective", ClientCommandToggleObjective )
+ AddClientCommandCallback( "ShowObjective", ClientCommandShowObjective )
+ #endif
+
+ #if CLIENT
+ PrecacheModel( OBJECTIVE_MARKER_MODEL )
+ RegisterSignal( "ShowingObjectiveRUI" )
+
+ file.onTimeshiftLevel = GetMapName().find( "sp_timeshift" ) == 0
+ if ( file.onTimeshiftLevel )
+ thread TimeshiftClientMarkerPositionThink()
+ #endif
+}
+
+#if SERVER
+ void function Objective_Remind()
+ {
+ foreach( entity player in GetPlayerArray() )
+ ClientCommand( player, "ShowObjective" )
+ }
+
+ void function Objective_InitEntity( entity ent )
+ {
+ if ( ent.GetClassName() == "func_brush" )
+ return
+
+ // Add the entity to a valid list of highlightable objective entities
+ // (allows highlighting to be enabled without errors before everyone updates their script to call this function)
+ file.objectiveEntities.append( ent )
+
+ // Make the entity always render so the highlighting wont stop working
+ ent.DisableHibernation()
+ ent.EnableRenderAlways()
+ ent.kv.fadedist = 99999
+
+ // Highlight and unhighlight to precache the highlight ability on this entity (required by code)
+ Highlight_SetNeutralHighlight( ent, OBJECTIVE_HIGHLIGHT )
+ Highlight_ClearNeutralHighlight( ent )
+ }
+
+ void function Objective_Set_WithAltHighlight( string objective, vector position = < 0, 0, 0 >, entity ent = null )
+ {
+ file.objectiveAltHighlight = true
+ _ObjectiveSet( objective, position, ent, false )
+ }
+
+ void function Objective_SetSilent_WithAltHighlight( string objective, vector position = < 0, 0, 0 >, entity ent = null )
+ {
+ file.objectiveAltHighlight = true
+ _ObjectiveSet( objective, position, ent, true )
+ }
+
+ void function Objective_Set( string objective, vector position = < 0, 0, 0 >, entity ent = null )
+ {
+ file.objectiveAltHighlight = false
+ _ObjectiveSet( objective, position, ent, false )
+ }
+
+ void function Objective_SetSilent( string objective, vector position = < 0, 0, 0 >, entity ent = null )
+ {
+ file.objectiveAltHighlight = false
+ _ObjectiveSet( objective, position, ent, true )
+ }
+
+ void function _ObjectiveSet( string objective, vector position, entity ent, bool silent )
+ {
+ UpdateMarkerEnt( position, ent )
+ SetGlobalNetInt( "objectiveStringIndex", GetObjectiveStringID( objective ) )
+
+ ClearObjectiveHighlight()
+ file.objectiveHighlightEntArray = [ ent ]
+ file.objectiveAltHighlightEntArray = []
+ SetGlobalNetBool( "hilightingObjective", IsValid( ent ) )
+ SetGlobalNetFloat( "additionalKilometers", 0.0 )
+
+ if ( !silent )
+ thread ShowNewObjective()
+
+ int stringIndex = GetGlobalNetInt( "objectiveStringIndex" )
+ foreach( entity player in GetPlayerArray() )
+ Remote_CallFunction_UI( player, "ServerCallback_UI_ObjectiveUpdated", stringIndex )
+ }
+
+ void function ShowNewObjective()
+ {
+ wait 0.1 // give time to sent position and entity to client
+ foreach( entity player in GetPlayerArray() )
+ thread ShowObjectiveForDuration( player, OBJECTIVE_DISPLAY_TOTAL_TIME )
+ }
+
+ void function Objective_AddKilometers( float kilometers )
+ {
+ Assert( kilometers >= 0.0 && kilometers <= 128.0 )
+ SetGlobalNetFloat( "additionalKilometers", kilometers )
+ }
+
+ void function Objective_AddHighlightEntity( entity ent )
+ {
+ file.objectiveHighlightEntArray.append( ent )
+ }
+
+ void function Objective_StaticModelHighlightOverrideEntity( entity ent )
+ {
+ ent.kv.rendermode = 3
+ ent.kv.renderamt = 1
+ ent.Hide()
+ file.objectiveAltHighlightEntArray.append( ent )
+ }
+
+ void function Objective_StaticModelHighlightOverrideEntityArray( array<entity> ents )
+ {
+ foreach ( ent in ents )
+ Objective_StaticModelHighlightOverrideEntity( ent )
+ }
+
+ void function Objective_Clear()
+ {
+ file.objectiveAltHighlight = false
+ ClearObjectiveHighlight()
+ file.objectiveHighlightEntArray = []
+ file.objectiveAltHighlightEntArray = []
+ SetGlobalNetBool( "hilightingObjective", false )
+ SetGlobalNetInt( "objectiveStringIndex", 0 )
+ SetGlobalNetFloat( "additionalKilometers", 0.0 )
+
+ foreach( entity player in GetPlayerArray() )
+ {
+ Objective_Hide( player )
+ Remote_CallFunction_UI( player, "ServerCallback_UI_ObjectiveUpdated", -1 ) //-1 is no objective
+ }
+ }
+
+ void function Objective_Update( vector position, entity ent = null )
+ {
+ UpdateMarkerEnt( position, ent )
+ ClearObjectiveHighlight()
+ file.objectiveHighlightEntArray = [ ent ]
+ file.objectiveAltHighlightEntArray = []
+ SetGlobalNetBool( "hilightingObjective", IsValid( ent ) )
+ }
+
+ void function UpdateMarkerEnt( vector position, entity ent )
+ {
+ vector newPos = position
+ bool showLine = IsValid( ent ) || (position != <0,0,0>)
+
+ SetGlobalNetBool( "showObjectiveLine", showLine )
+
+ if ( IsValid( ent ) )
+ {
+ newPos = ent.GetOrigin() + position
+ }
+
+ // Create marker ent every time
+ RefreshMarkerEnt( newPos )
+
+ if ( IsValid( ent ) )
+ {
+ if ( ent.GetClassName() != "info_target" )
+ file.objectiveMarkerEnt.SetParent( ent, "", true )
+ else
+ CodeWarning( "Tried to set objective entity to an info_target. This isn't supported because they aren't sent to the client. Try using a script_mover_lightweight or prop_dynamic." )
+ }
+ }
+
+ void function RefreshMarkerEnt( vector position )
+ {
+ if ( !IsValid( file.objectiveMarkerEnt ) )
+ {
+ file.objectiveMarkerEnt = CreateEntity( "info_target" )
+ file.objectiveMarkerEnt.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( file.objectiveMarkerEnt )
+
+ SetGlobalNetEnt( "objectiveMarkerEntity", file.objectiveMarkerEnt )
+ }
+
+ file.objectiveMarkerEnt.ClearParent()
+ file.objectiveMarkerEnt.SetOrigin( position )
+ }
+
+ void function ShowObjectiveForDuration( entity player, float duration )
+ {
+ EndSignal( player, "OnDeath" )
+ EndSignal( player, "OnDestroy" )
+
+ // Make sure thread only runs once
+ Signal( player, "ShowingObjective" )
+ EndSignal( player, "ShowingObjective" )
+ EndSignal( player, "HidingObjective" )
+
+ player.SetPlayerNetBool( "showObjective", true )
+ SetObjectiveHighlight()
+ file.objectiveLastShownTime = Time()
+
+ wait duration
+ Objective_Hide( player )
+ }
+
+ void function Objective_Hide( entity player )
+ {
+ player.SetPlayerNetBool( "showObjective", false )
+ ClearObjectiveHighlight()
+ Signal( player, "HidingObjective" )
+ }
+
+ float function Objective_LastShownTime()
+ {
+ return file.objectiveLastShownTime
+ }
+
+ bool function ClientCommandToggleObjective( entity player, array<string> args )
+ {
+ bool isShowing = player.GetPlayerNetBool( "showObjective" )
+
+ if ( isShowing )
+ thread Objective_Hide( player )
+ else
+ thread ShowObjectiveForDuration( player, OBJECTIVE_DISPLAY_TOTAL_TIME )
+
+ return true
+ }
+
+ bool function ClientCommandShowObjective( entity player, array<string> args )
+ {
+ if ( args.len() > 0 )
+ {
+ if ( args[0] == "closedSPMenu" && file.suppressCloseMenuShowsObjective )
+ return true
+ }
+
+ if ( IsAlive( player ) )
+ thread ShowObjectiveForDuration( player, OBJECTIVE_DISPLAY_TOTAL_TIME )
+ return true
+ }
+
+ void function Objective_SuppressCloseMenuShowsObjective( bool enabled )
+ {
+ file.suppressCloseMenuShowsObjective = enabled
+ }
+
+ void function SetObjectiveHighlight()
+ {
+ // Alt highlight ent?
+ if ( file.objectiveAltHighlightEntArray.len() )
+ {
+ foreach ( ent in file.objectiveAltHighlightEntArray )
+ {
+ if ( !IsValid( ent ) )
+ continue
+ Assert( file.objectiveEntities.contains( ent ), "Tried to use Objective_StaticModelHighlightOverrideEntity on entity that didn't call Objective_InitEntity" )
+ ent.Show()
+ Highlight_SetNeutralHighlight( ent, OBJECTIVE_HIGHLIGHT )
+ }
+ }
+ else if ( file.objectiveHighlightEntArray.len() )
+ {
+ foreach ( ent in file.objectiveHighlightEntArray )
+ {
+ if ( !IsValid( ent ) )
+ continue
+
+ if ( file.objectiveEntities.contains( ent ) )
+ Highlight_SetNeutralHighlight( ent, OBJECTIVE_HIGHLIGHT )
+ }
+ }
+ }
+
+ void function ClearObjectiveHighlight()
+ {
+ // Alt highlight ent?
+ if ( file.objectiveAltHighlightEntArray.len() )
+ {
+ foreach ( ent in file.objectiveAltHighlightEntArray )
+ {
+ if ( !IsValid( ent ) )
+ continue
+ Assert( file.objectiveEntities.contains( ent ), "Tried to use Objective_StaticModelHighlightOverrideEntity on entity that didn't call Objective_InitEntity" )
+ ClearObjectiveHighlight_Internal( ent )
+ ent.Hide()
+ }
+ }
+ else if ( file.objectiveHighlightEntArray.len() )
+ {
+ foreach ( ent in file.objectiveHighlightEntArray )
+ {
+ if ( !IsValid( ent ) )
+ continue
+ if ( file.objectiveEntities.contains( ent ) )
+ ClearObjectiveHighlight_Internal( ent )
+ }
+ }
+ }
+
+ void function ClearObjectiveHighlight_Internal( entity ent )
+ {
+ if ( !file.objectiveAltHighlight )
+ Highlight_ClearNeutralHighlight( ent )
+ else
+ Highlight_SetNeutralHighlight( ent, OBJECTIVE_HIGHLIGHT_ALT )
+ }
+
+ entity function Objective_GetMarkerEntity()
+ {
+ return file.objectiveMarkerEnt
+ }
+
+ entity function Objective_WayPointEneable( bool enable )
+ {
+ SetGlobalNetBool( "objectiveMarkerIsWayPoint", enable )
+ }
+
+ // Don't use this unless you grab the marker ent and clear it's parent after you're done
+ void function Objective_SetFastball( entity bt )
+ {
+ entity marker = Objective_GetMarkerEntity()
+ string tag = "HAND_R"
+ int attachID = bt.LookupAttachment( tag )
+ vector angles = bt.GetAttachmentAngles( attachID )
+ vector offset = AnglesToRight( angles ) * 20
+ marker.SetOrigin( bt.GetAttachmentOrigin( attachID ) + offset )
+ marker.SetParent( bt, tag, true, 0 )
+ }
+
+
+#endif
+
+
+
+
+
+
+
+
+
+
+#if CLIENT
+ void function ObjectiveStringChanged( entity player, int oldString, int newString, bool actuallyChanged )
+ {
+ if ( file.objectiveRUI == null )
+ return
+
+ if ( newString == 0 )
+ return
+
+ string objectiveText = GetObjectiveStringFromID( newString )
+ RuiSetString( file.objectiveRUI, "objectiveText", objectiveText )
+ }
+
+ void function ShowObjectiveLineChanged( entity player, bool old, bool new, bool actuallyChanged )
+ {
+ if ( file.objectiveRUI == null )
+ return
+
+ if ( !new )
+ RuiSetBool( file.objectiveRUI, "showLine", false )
+ else
+ RuiSetBool( file.objectiveRUI, "showLine", true )
+
+ if ( GetGlobalNetBool( "hilightingObjective" ) == true || !new )
+ RuiSetBool( file.objectiveRUI, "showMarker", false )
+ else
+ RuiSetBool( file.objectiveRUI, "showMarker", true )
+ }
+
+ void function ShowObjectiveChanged( entity player, bool wasShowing, bool isShowing, bool actuallyChanged )
+ {
+ if ( !actuallyChanged )
+ return
+
+ if ( isShowing )
+ thread ShowObjective( true )
+ else
+ HideObjective()
+ }
+
+ void function ShowObjective( bool newObjective )
+ {
+ // Make sure only one RUI is displayed at a time
+ Signal( clGlobal.levelEnt, "ShowingObjectiveRUI" )
+ EndSignal( clGlobal.levelEnt, "ShowingObjectiveRUI" )
+
+ entity marker = GetGlobalNetEnt( "objectiveMarkerEntity" )
+ if ( !IsValid( marker ) )
+ return
+
+ OnThreadEnd(
+ function() : ( )
+ {
+ HideObjective()
+ }
+ )
+
+ int stringIndex = GetGlobalNetInt( "objectiveStringIndex" )
+ if ( stringIndex == 0 )
+ return
+
+ string titleText = newObjective ? "#OBJECTIVE_UPDATED" : "#OBJECTIVE_REMIND"
+ bool isWayPoint = GetGlobalNetBool( "objectiveMarkerIsWayPoint" )
+ if( isWayPoint )
+ titleText = "#OBJECTIVE_WAYPOINT"
+
+ string objectiveText = GetObjectiveStringFromID( stringIndex )
+ float additionalKilometers = GetGlobalNetFloat( "additionalKilometers" )
+
+ HideObjective()
+ file.objectiveRUI = CreatePermanentCockpitRui( $"ui/sp_objective.rpak", 0 )
+
+ RuiSetGameTime( file.objectiveRUI, "startTime", Time() )
+ RuiSetGameTime( file.objectiveRUI, "endTime", Time() + OBJECTIVE_DISPLAY_TIME )
+ RuiSetFloat( file.objectiveRUI, "fadeInDuration", FADE_IN_TIME )
+ RuiSetFloat( file.objectiveRUI, "fadeOutDuration", FADE_OUT_TIME )
+ RuiSetFloat( file.objectiveRUI, "blingDuration", newObjective ? BLING_DURATION : 0.0 )
+ RuiSetString( file.objectiveRUI, "objectiveTitleText", titleText )
+ RuiSetString( file.objectiveRUI, "objectiveText", objectiveText )
+ RuiSetBool( file.objectiveRUI, "showButtonHint", newObjective )
+
+ // handle objective position in code on timeshift levels
+ if ( !file.onTimeshiftLevel )
+ RuiTrackFloat3( file.objectiveRUI, "pos", marker, RUI_TRACK_ABSORIGIN_FOLLOW )
+
+ RuiSetFloat( file.objectiveRUI, "additionalKilometers", additionalKilometers )
+
+ if ( !GetGlobalNetBool( "showObjectiveLine" ) )
+ RuiSetBool( file.objectiveRUI, "showLine", false )
+
+ if ( GetGlobalNetBool( "hilightingObjective" ) == true || !GetGlobalNetBool( "showObjectiveLine" ) )
+ RuiSetBool( file.objectiveRUI, "showMarker", false )
+
+ EmitSoundOnEntity( GetLocalClientPlayer(), "ui_holotutorial_analyzingfinish" )
+
+ wait OBJECTIVE_DISPLAY_TOTAL_TIME
+ }
+
+ void function HideObjective()
+ {
+ if ( file.objectiveRUI != null )
+ {
+ RuiDestroyIfAlive( file.objectiveRUI )
+ file.objectiveRUI = null
+ }
+ }
+
+ void function TimeshiftClientMarkerPositionThink()
+ {
+ while ( true )
+ {
+ WaitFrame()
+
+ if ( IsValid( file.objectiveRUI ) )
+ {
+ // marker is how the server tells us the objective pos
+ entity marker = GetGlobalNetEnt( "objectiveMarkerEntity" )
+ if ( !IsValid( marker ) )
+ continue // unsure if this can even ever be hit
+
+ // get our current timeline and get zoffset for it
+ int zOffset = 0
+ if ( marker.GetOrigin().z < -6000 ) // frozen time
+ {
+ RuiTrackFloat3( file.objectiveRUI, "pos", marker, RUI_TRACK_ABSORIGIN_FOLLOW )
+ break // once we hit frozen time, all objectives from now will be on the same timeline as players so we can use normal objective pos logic
+ }
+ else if ( marker.GetOrigin().z < 5300 && GetLocalClientPlayer().GetOrigin().z > 5300 ) // marker in night, player in day
+ zOffset = TIME_ZOFFSET
+ else if ( marker.GetOrigin().z > 5300 && GetLocalClientPlayer().GetOrigin().z < 5300 ) // marker in day, player in night
+ zOffset = TIME_ZOFFSET * -1
+
+ // set objective pos
+ vector pos = marker.GetOrigin() + Vector( 0, 0, zOffset )
+ //RuiSetString( file.objectiveRUI, "objectiveTitleText", "marker: " + marker.GetOrigin() + ", player: " + GetLocalClientPlayer().GetOrigin() + ", offset: " + zOffset )
+ RuiSetFloat3( file.objectiveRUI, "pos", pos )
+ }
+ }
+ }
+
+#endif
+
diff --git a/Northstar.Coop/scripts/vscripts/sp/sp_training.nut b/Northstar.Coop/scripts/vscripts/sp/sp_training.nut
new file mode 100644
index 000000000..21a67a40a
--- /dev/null
+++ b/Northstar.Coop/scripts/vscripts/sp/sp_training.nut
@@ -0,0 +1,7554 @@
+global function CodeCallback_MapInit
+
+#if DEV
+global function skyboxchange
+global function GetOGPilot
+global function wallruntest
+global function Record_ZenGarden_Wallrun
+global function Record_ZenGarden_Slide
+global function Record_ZenGarden_DoubleJump
+global function shutdownscreentest
+global function SendTrainingGauntletStats
+global function FancyTeleport_EffectsAndSound
+global function Training_WeaponRacks_SetSolidity
+global function setfakeinstalldone
+
+global function Training_EnvArtColorCorrection_SetEnabled
+global function SimpleScreenShake
+global function GetSkitGuyInfo_ByName
+global function NudgeSkitGuy
+#endif
+
+const MARVIN_MODEL = $"models/robots/marvin/marvin.mdl"
+const asset FX_POD_LASER = $"P_pod_scan_laser_FP"
+const asset FX_POD_GLOWLIGHT = $"P_pod_door_glow_FP"
+const asset FX_POD_SCREEN_IN = $"P_pod_screen_lasers_IN"
+const asset FX_POD_SCREEN_OUT = $"P_pod_screen_lasers_OUT"
+const asset FX_POD_DLIGHT_CONSOLE1 = $"P_pod_Dlight_console1"
+const asset FX_POD_DLIGHT_CONSOLE2 = $"P_pod_Dlight_console2"
+//const asset FX_POD_DLIGHT_BACKLIGHT_SIDE = $"P_pod_Dlight_backlight_side"
+//const asset FX_POD_DLIGHT_BACKLIGHT_TOP = $"P_pod_Dlight_backlight_top"
+const asset FX_FANCY_TELEPORT_ENV_PULSE = $"P_ar_holopulse_CP"
+const asset FX_COCKPIT_LIGHT = $"xo_cockpit_dlight"
+const asset OG_PILOT_HELMET_MODEL = $"models/Humans/heroes/mlt_hero_anderson_helmet.mdl"
+const asset ANDERSON_PILOT_MODEL = $"models/humans/heroes/mlt_hero_anderson.mdl"
+const asset PILOT_MODEL_BAY1 = $"models/humans/pilots/sp_medium_geist_f.mdl"
+const asset PILOT_MODEL_BAY2 = $"models/humans/pilots/sp_medium_reaper_m.mdl"
+const asset BUDDY_MODEL_POSED_NO_ANIMS = $"models/Titans/buddy/BT_posed.mdl"
+const asset SAFETY_BATON_MODEL = $"models/industrial/safety_baton.mdl"
+
+const asset OG_PILOT_MODEL = $"models/humans/heroes/mlt_hero_lastimosa.mdl"
+const int OG_PILOT_MODEL_HEAD_IDX_BARE = 0
+const int OG_PILOT_MODEL_HEAD_IDX_HELMET = 2
+const int OG_PILOT_MODEL_DECAL_IDX = 0
+const int OG_PILOT_MODEL_DECAL_IDX_BARE = 1
+
+const string OG_WEAPON = "mp_weapon_rspn101"
+
+const string ANIM_OG_STANDING_IDLE = "OG_stand_upright_idle"
+const string ANIM_OG_STANDING_TALK = "OG_stand_upright_talk"
+const string ANIM_OG_SITTING_IDLE = "OG_sit_high_idle"
+const string ANIM_OG_SITTING_TALK = "OG_sit_high_talk"
+const string ANIM_OG_LEANING_IDLE = "OG_stand_lean_idle"
+const string ANIM_OG_LEANING_TALK = "OG_stand_lean_talk"
+
+const int SCRIPTED_PATH_WALK = 0
+const int SCRIPTED_PATH_RUN = 1
+
+const int MAX_RECREATED_OLD_WEAPONS = 16
+
+const float TITANFALL_NAG_DURATION = 3.0 // extra time compensation for nag line playing when titanfall started
+
+const string TRAINING_PLAYER_SETTINGS = "pilot_solo_training"
+
+struct TrainingPod_dLightMapping
+{
+ string scriptAlias
+ asset fxName
+ string attachName
+ entity fxHandle
+}
+
+struct TrainingPod_LaserEmitter
+{
+ entity ent
+ string attachName
+ vector ogAng
+ bool sweepDone = false
+ entity fxHandle
+}
+
+struct TrainingPod_GlowLightRow
+{
+ array<string> fxSpotsL
+ array<string> fxSpotsR
+}
+
+struct LoudspeakerVO_Info
+{
+ string scriptAlias
+ string soundAlias
+ float duration
+}
+
+struct FiringRangeTarget
+{
+ entity ent
+ entity angleRefEnt
+ entity mover
+ bool wasDamaged
+ vector ogAngles
+}
+
+struct SkitGuyInfo
+{
+ int id
+ string name
+ string skitAnim
+ entity guy
+ entity skitRef
+}
+
+struct HangarTitanGroup
+{
+ entity ref
+
+ entity titan
+ entity rack
+ entity marvin
+ entity pilot
+
+ int titanSkin = -1
+
+ string titanAnim
+ string rackAnim
+ string marvinAnim
+ string pilotAnim
+
+ asset pilotModel
+
+ vector rack_ogPos
+ vector rack_ogAng
+
+ float sequenceDuration
+ float animInitialTime = 0.0
+
+ bool isInited = false
+}
+
+struct TrainingGauntletStats
+{
+ bool didBeatRequiredTime = false
+ int numRunsBeforeBeatRequiredTime = 0
+ int numChallengeRuns = 0
+ int numRestarts = 0
+ float bestTime = -1.0
+ int recommendedDifficulty = 0
+}
+
+struct
+{
+ #if DEV
+ bool fakeInstallDone = false
+ #endif
+
+ bool gauntletMode = false
+
+ entity player
+ int playerInputType
+
+ entity ogPilot
+ entity ogTwin
+ entity anderson
+ entity titanTwin
+ entity ogHelmet
+ entity playerAnimWeapon
+
+ entity ogPathMover
+
+ entity animref_hangar
+ entity animref_leaveGauntlet
+
+ entity trainingPod
+ array<TrainingPod_GlowLightRow> trainingPodGlowLightRows
+ array<entity> trainingPodGlowLightFXHandles
+ array<TrainingPod_dLightMapping> trainingPodDLightMappings
+ array<TrainingPod_LaserEmitter> trainingPodLaserEmitters
+
+ float postWallrunVOEndTime = -1
+
+ float titanfallNagStartTime = -1
+ vector playerTitanCallInPos
+
+ entity envArt_colorCorrectionEnt
+ entity skycam_default
+ entity skycam_glitch
+
+ table<string,LoudspeakerVO_Info> loudspeakerVO = {}
+ entity loudspeaker
+
+ array<FiringRangeTarget> firingRangeTargets = []
+
+ //table<string,SkitGuyInfo> skitguys = {}
+ array<SkitGuyInfo> skitguys = []
+
+ TrainingGauntletStats trainingGauntletStats
+
+ array<entity> scriptCreatedWeaponPickups = []
+ bool weaponPickupsHaveAmmo = false
+
+ bool displayWeaponHUD = true
+} file
+
+void function CodeCallback_MapInit()
+{
+ FlagSet( "FlightPath_TitanDrop" )
+
+ PrecacheParticleSystem( FX_POD_LASER )
+ PrecacheParticleSystem( FX_POD_GLOWLIGHT )
+ PrecacheParticleSystem( FX_POD_SCREEN_IN )
+ PrecacheParticleSystem( FX_POD_SCREEN_OUT )
+ PrecacheParticleSystem( FX_POD_DLIGHT_CONSOLE1 )
+ PrecacheParticleSystem( FX_POD_DLIGHT_CONSOLE2 )
+ //PrecacheParticleSystem( FX_POD_DLIGHT_BACKLIGHT_SIDE )
+ //PrecacheParticleSystem( FX_POD_DLIGHT_BACKLIGHT_TOP )
+ PrecacheParticleSystem( FX_FANCY_TELEPORT_ENV_PULSE )
+ PrecacheParticleSystem( FX_COCKPIT_LIGHT )
+
+ PrecacheModel( OG_PILOT_HELMET_MODEL )
+ PrecacheModel( OG_PILOT_MODEL )
+ PrecacheModel( ANDERSON_PILOT_MODEL )
+ PrecacheModel( PILOT_MODEL_BAY1 )
+ PrecacheModel( PILOT_MODEL_BAY2 )
+ PrecacheModel( BUDDY_MODEL_POSED_NO_ANIMS )
+ PrecacheModel( SAFETY_BATON_MODEL )
+ PrecacheModel( MARVIN_MODEL )
+ PrecacheModel( DATA_KNIFE_MODEL )
+
+ LoudspeakerVO_Setup()
+ Training_SharedInit()
+
+ RegisterSignal( "ButtonPressedJump" )
+ RegisterSignal( "ButtonPressedAttack" )
+ RegisterSignal( "PodIntro_OG_StartPodAnim" )
+ RegisterSignal( "PodInteriorSequenceDone" )
+ RegisterSignal( "FancyTeleportStart" )
+ RegisterSignal( "TargetRotate" )
+ RegisterSignal( "TargetDamaged" )
+ RegisterSignal( "Target_WaitForDamage_Start" )
+ RegisterSignal( "StopRepeatingGhostRecorder" )
+ RegisterSignal( "FiringRange_StopResettingTargets" )
+ RegisterSignal( "Gauntlet_StopTeleportingPlayerAtFinishLine" )
+ RegisterSignal( "FirstRun_OG_Creates_Ghost" )
+ RegisterSignal( "GauntletChallenge_FirstGhostAppear" )
+ RegisterSignal( "PlayerMadeSelection" )
+ RegisterSignal( "TrainingPod_BeginInteriorShutdown" )
+ RegisterSignal( "NPC_NewCommand" )
+ RegisterSignal( "LoudspeakerVO_Stop" )
+ RegisterSignal( "glitch_start" )
+
+ FlagInit( "PlayerPressedUse" )
+ FlagInit( "PlayerReloaded" )
+ FlagInit( "PodIntro_PodDoorsClosed" )
+ FlagInit( "PlayerLookedAtTopTarget" )
+ FlagInit( "PlayerLookedAtBottomTarget" )
+ FlagInit( "PodIntro_InteriorBootSequence_Starting" )
+ FlagInit( "OG_WhyWeFight_VO_Done" )
+ FlagInit( "FiringRange_Approach_OG_Sequence_Done" )
+ FlagInit( "ReloadTraining_PlayerPressedReload" )
+ FlagInit( "PlayerSprinted" )
+ FlagInit( "PlayerADSed" )
+ FlagInit( "FiringRangeWeaponSwapped" )
+ FlagInit( "FiringRange_AllTargetsKilled" )
+ FlagInit( "OG_MovedTo_GauntletEntrance" )
+ FlagInit( "Gauntlet_FirstRun_All_VO_Finished" )
+ FlagInit( "Gauntlet_FirstRun_Done" )
+ FlagInit( "ChallengeIntro_VO_Done" )
+ FlagInit( "Gauntlet_PlayingFeedbackVO" )
+ FlagInit( "PlayerUsedConversationInterface" )
+ FlagInit( "GauntletExitConvo_FinishedResponse" )
+ FlagInit( "TitanfallIntroConvo_FinishedResponse" )
+ FlagInit( "PlayerConfirmedGauntletExit" )
+ FlagInit( "PlayerLeavingGauntlet" )
+ FlagInit( "PlayerStartedTitanfall" )
+ FlagInit( "Titanfall_OG_FallingIn_VO_Start" )
+ FlagInit( "TitanfallGlitchStart" )
+ FlagInit( "PlayerWorldChangeThread" )
+ FlagInit( "Glitch_WorldChanging_Zen" )
+ FlagInit( "Glitch_WorldChanging_NonZen" )
+ FlagInit( "PodOutroStarted" )
+ FlagInit( "SimPodShutdown_LoudspeakerVO_Done" )
+ FlagInit( "MeetOG_StartScene" )
+ FlagInit( "CadillacMoment_MeetOG_Done" )
+ FlagInit( "CadillacMoment_MeetOG_StartFadeOut" )
+ FlagInit( "MeetOG_VO_Done" )
+
+ FlagClear( "AutomaticCheckpointsEnabled" )
+
+ AddClientCommandCallback( "Training_SetInputType", ClientCommand_Training_SetInputType )
+ AddClientCommandCallback( "Training_PlayerPressedUse", ClientCommand_Training_PlayerPressedUse )
+ AddClientCommandCallback( "Training_PlayerReloaded", ClientCommand_Training_PlayerReloaded )
+ AddClientCommandCallback( "topTarget", ClientCommand_LookTarget_Top )
+ AddClientCommandCallback( "bottomTarget", ClientCommand_LookTarget_Bottom )
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+ AddPlayerDidLoad( Training_PlayerDidLoad )
+ AddCallback_OnLoadSaveGame( Training_OnLoadSaveGame )
+
+ AddDamageCallback( "func_brush", Training_FuncBrush_OnDamaged )
+
+ TimerInit( "firingRangeNag", 15.0 )
+ TimerInit( "installWaitComment", 60.0 )
+
+ AddStartPoint( "Pod Intro", Training_PodIntro, null, Training_Skipped_PodIntro )
+ AddStartPoint( "Basic Movement", Training_BasicMovement, Training_Setup_BasicMovement, Training_Skipped_BasicMovement )
+ AddStartPoint( "Zen Garden", Training_ZenGarden, Training_Setup_ZenGarden, Training_Skipped_ZenGarden )
+ AddStartPoint( "Firing Range", Training_FiringRange, Training_Setup_FiringRange, Training_Skipped_FiringRange )
+ AddStartPoint( "Gauntlet", Training_Gauntlet, Training_Setup_Gauntlet, Training_Skipped_Gauntlet )
+ AddStartPoint( "Gauntlet Challenge", Training_GauntletChallenge, Training_Setup_GauntletChallenge, Training_Skipped_GauntletChallenge )
+ AddStartPoint( "Titanfall", Training_Titanfall, Training_Setup_Titanfall, Training_Skipped_Titanfall )
+ AddStartPoint( "Pod Outro", Training_PodOutro, Training_Setup_PodOutro, Training_Skipped_PodOutro )
+ AddStartPoint( "Meet OG", Training_MeetOG, Training_Setup_MeetOG, Training_Skipped_MeetOG )
+ AddStartPoint( "Gauntlet Mode", Training_GauntletModeStart, Training_Setup_GauntletMode, null )
+
+ #if DEV
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_FIRSTRUN", TrainingGauntlet_RecordGhostStart_FirstRun, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_WIP", TrainingGauntlet_RecordGhostStart_Challenge_WIP, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_01", TrainingGauntlet_RecordGhostStart_Challenge_01, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_02", TrainingGauntlet_RecordGhostStart_Challenge_02, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_03", TrainingGauntlet_RecordGhostStart_Challenge_03, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_04", TrainingGauntlet_RecordGhostStart_Challenge_04, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_05", TrainingGauntlet_RecordGhostStart_Challenge_05, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_06", TrainingGauntlet_RecordGhostStart_Challenge_06, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_07", TrainingGauntlet_RecordGhostStart_Challenge_07, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_08", TrainingGauntlet_RecordGhostStart_Challenge_08, null, null )
+ AddStartPoint( "DEV_GHOSTREC_GAUNTLET_CHAL_09", TrainingGauntlet_RecordGhostStart_Challenge_09, null, null )
+
+ AddStartPoint( "Pod Intro DEV", DEV_PodIntro, null, null )
+ AddStartPoint( "Pod Outro DEV", DEV_PodOutro, null, null )
+ #endif
+}
+
+
+void function EntitiesDidLoad()
+{
+ QuickDeathTrigger_SetIsPunitive( false )
+
+ SetupTrainingPod()
+ TrainingPod_GlowLightsArraySetup()
+
+ FiringRangeTargets_Init()
+
+ file.animref_hangar = GetEntByScriptName( "animref_hangar" )
+ file.animref_hangar.DisableHibernation()
+
+ file.skycam_default = GetEnt( "skybox_cam_level" )
+ file.skycam_glitch = GetEnt( "skybox_cam_glitch" )
+}
+
+
+void function Training_PlayerDidLoad( entity player )
+{
+ player.SetNoTarget( true )
+ SetGlobalForcedDialogueOnly( true )
+
+ AddButtonPressedPlayerInputCallback( player, IN_JUMP, Training_ButtonPressedJump )
+ AddButtonPressedPlayerInputCallback( player, IN_ATTACK, Training_ButtonPressedAttack )
+
+ file.envArt_colorCorrectionEnt = GetEnt( "color_correction_1" )
+
+ file.player = player
+
+ EnableDemigod( player )
+
+ player.ForceMPAimassist() // TODO doublecheck this
+
+ player.SetSkyCamera( file.skycam_default )
+
+ Training_WeaponPickups_Init( player )
+ player.PreventWeaponDestroyNoAmmo() // makes it so when player swaps empty weapon for another pickup, doesn't destroy empty weapon
+
+ thread Training_PlayerQuickdeathSFX( player )
+
+ DisableFriendlyHighlight()
+}
+
+
+void function Training_OnLoadSaveGame( entity player )
+{
+ thread Training_OnLoadSaveGame_Think( player )
+}
+
+void function Training_OnLoadSaveGame_Think( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ wait 0.5 // HACK have to wait otherwise it doesn't work
+ SetWeaponHUDEnabled( player, file.displayWeaponHUD )
+}
+
+
+void function Training_ButtonPressedJump( entity player )
+{
+ player.Signal( "ButtonPressedJump" )
+}
+
+void function Training_ButtonPressedAttack( entity player )
+{
+ player.Signal( "ButtonPressedAttack" )
+}
+
+void function Training_FuncBrush_OnDamaged( entity ent, var damageInfo )
+{
+ if( !IsValid( ent ) )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( ent.GetScriptName() == "firingrange_target" )
+ {
+ if ( IsValid( attacker ) && attacker.IsPlayer() )
+ {
+ table<string,vector> resultTable = {}
+ resultTable["damagePos"] <- DamageInfo_GetDamagePosition( damageInfo )
+ ent.Signal( "TargetDamaged", resultTable )
+ }
+ }
+}
+
+
+// ==============================
+// ========= POD INTRO ==========
+// ==============================
+void function Training_Skipped_PodIntro( entity player )
+{
+ player.SetExtraWeaponMods( [ "low_ammo_disable" ] )
+ SetWeaponHUDEnabled( player, false )
+}
+
+#if DEV
+// bare bones start in pod
+void function DEV_PodIntro( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ TakeAllWeapons( player )
+
+ player.SetExtraWeaponMods( [ "low_ammo_disable" ] )
+ SetWeaponHUDEnabled( player, false )
+
+ Training_EnvArtColorCorrection_SetEnabled( false )
+ SetDoF_Hangar( player )
+
+ thread PodIntro_BackgroundSkits( player )
+
+ entity pod = file.trainingPod
+
+ TrainingPod_PlayerSequence_DoorsOpenIdle( player, false )
+
+ // anim starts at a slightly different spot
+ player.SetOrigin( < 10564, -10235, -6056.9 > )
+ player.SetAngles( < -6, 90, 0 > )
+
+ WaitForever()
+}
+#endif //DEV
+
+
+void function Training_PodIntro( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ player.SetExtraWeaponMods( [ "low_ammo_disable" ] )
+ SetWeaponHUDEnabled( player, false )
+
+ Training_EnvArtColorCorrection_SetEnabled( false )
+
+ entity pod = file.trainingPod
+
+ OnThreadEnd(
+ function() : ( player, pod )
+ {
+ if ( IsValid( player ) )
+ {
+ player.Anim_Stop()
+ ClearPlayerAnimViewEntity( player )
+ player.ClearParent()
+ player.UnforceStand()
+
+ Training_EnvArtColorCorrection_SetEnabled( true )
+ }
+
+ if ( IsValid ( pod ) )
+ {
+ pod.Anim_Stop()
+
+ thread TrainingPod_ResetLaserEmitterRotation( pod )
+ thread TrainingPod_KillLasers( pod )
+ thread TrainingPod_KillGlowFX( pod )
+ TrainingPod_KillInteriorDLights()
+ }
+ }
+ )
+
+ // NORMAL LEVEL START
+ SetDoF_Hangar( player )
+
+ ShowIntroScreen( player )
+
+ thread PodIntro_MeetOG( player )
+
+ TrainingPod_PlayerSequence_DoorsOpenIdle( player )
+
+ FlagWait( "IntroScreenFading" )
+ wait 1.2
+ Remote_CallFunction_Replay( player, "ScriptCallback_LevelIntroText" )
+ wait 4.2 // matches fade time in sp_introscreen data
+
+ thread PodIntro_BackgroundSkits( player )
+
+
+ player.Signal( "PodIntro_OG_StartPodAnim" )
+
+ // time for OG to animate before starting viewmodel anim
+ wait 11.8
+
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.blendTime = 0.25
+ playerSequence.attachment = "REF"
+ playerSequence.firstPersonAnim = "ptpov_trainingpod_doors_close"
+ playerSequence.firstPersonAnimIdle = "ptpov_trainingpod_idle"
+ playerSequence.thirdPersonAnim = "pt_trainingpod_doors_close"
+ playerSequence.thirdPersonAnimIdle = "pt_trainingpod_idle"
+ playerSequence.viewConeFunction = TrainingPod_ViewConeLock_SemiStrict
+ playerSequence.renderWithViewModels = true
+
+ FirstPersonSequenceStruct podSequence
+ podSequence.blendTime = 0.25
+ podSequence.thirdPersonAnim = "trainingpod_doors_close"
+ podSequence.thirdPersonAnimIdle = "trainingpod_doors_close_idle"
+ podSequence.renderWithViewModels = true
+
+ entity viewmodel = player.GetFirstPersonProxy()
+
+ if ( !HasAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut" ) )
+ AddAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut", PlaySound_SimPod_DoorShut )
+
+ // HACK this should be based on an anim event
+ thread TrainingPod_KillInteriorDLights_Delayed( player, 2.65 )
+
+ thread FirstPersonSequence( podSequence, pod )
+ waitthread FirstPersonSequence( playerSequence, player, pod )
+
+ FlagSet( "PodIntro_PodDoorsClosed" )
+
+ TrainingPod_ViewConeLock_PodClosed( player )
+
+ waitthread LookTraining( player )
+
+ // "Let's see how much you remember from last time."
+ waitthread PlayDialogue( "og_how_much_you_remember", player )
+
+ // "Setting the neural link."
+ waitthread PlayDialogue( "og_neural_link", player )
+
+ // "Not quite the same as a Titan link, but it's similar."
+ waitthread PlayDialogue( "og_neural_link_2", player )
+
+ thread TrainingPod_Interior_BootSequence( player )
+
+ // "To learn new skills, we need to be in the right state of mind."
+ thread PlayDialogue( "og_simulation_starting", player, 2.5 )
+
+ player.WaitSignal( "PodInteriorSequenceDone" )
+ printt( "POD SEQUENCE DONE" )
+
+ wait 2.0 // timed to match the screen effect white screen flash
+
+ SetDoF_Default( player )
+}
+
+void function PlaySound_SimPod_DoorShut( entity playerFirstPersonProxy ) //Hack, needed for wargames but has unfortunate side effect for Training.
+{
+ entity player = playerFirstPersonProxy.GetOwner()
+ if ( !IsValid( player ) )
+ return
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "NPE_Scr_SimPod_DoorShut" )
+
+}
+
+void function PodIntro_MeetOG( entity player )
+{
+ entity animref = file.animref_hangar
+ vector animrefOrigin = animref.GetOrigin()
+ vector animrefAngles = animref.GetAngles()
+ vector btSpawnOrg = <0,0,0> // to avoid red text errors about BT spawning in solid
+
+ entity animEnt = CreateScriptMover( animrefOrigin, animrefAngles )
+
+ // Spawn scene actors
+ entity og = Training_SpawnOGPilot( animref )
+ Training_OGPilot_SetHelmetOn( og, false )
+ AddAnimEvent( file.ogPilot, "pod_slap", PodIntro_OG_Slaps_Pod )
+
+ TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap()
+ entity bt = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, btSpawnOrg, animrefAngles )
+ SetSpawnOption_AISettings( bt, "npc_titan_buddy" )
+ bt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+ DispatchSpawn( bt )
+ FreeAutoTitan( bt ) // HACK this disables the worldspace BT locator icon
+ TakeAllWeapons( bt )
+
+ array<entity> actors = [ bt, og ]
+
+ asset btWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( "mp_titanweapon_xo16_shorty", "playermodel" )
+ Assert( btWeaponModel != $"" )
+ entity btWeapon = CreatePropDynamic( btWeaponModel )
+ btWeapon.SetParent( bt, "PROPGUN" )
+
+ asset ogWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( OG_WEAPON, "playermodel" )
+ Assert( ogWeaponModel != $"" )
+ entity ogWeapon = CreatePropDynamic( ogWeaponModel )
+ ogWeapon.SetParent( og, "PROPGUN" )
+
+ entity ogHelmet = CreatePropDynamic( OG_PILOT_HELMET_MODEL )
+ ogHelmet.DisableHibernation()
+
+ array<entity> props = [ btWeapon, ogWeapon, ogHelmet ]
+
+ OnThreadEnd(
+ function() : ( actors, props, animEnt )
+ {
+ if ( IsValid( file.ogPilot ) )
+ DeleteAnimEvent( file.ogPilot, "pod_slap" )
+
+ foreach ( weapon in props )
+ {
+ if ( IsValid( weapon ) )
+ {
+ weapon.ClearParent()
+ weapon.Destroy()
+ }
+ }
+
+ foreach ( actor in actors )
+ {
+ if ( !IsValid( actor ) )
+ continue
+
+ actor.ClearParent()
+
+ if ( IsInvincible( actor ) )
+ ClearInvincible( actor )
+
+ if ( !actor.IsPlayer() )
+ actor.Destroy()
+ }
+
+ if ( IsValid( animEnt ) )
+ animEnt.Destroy()
+ }
+ )
+
+ foreach ( guy in actors )
+ {
+ MakeInvincible( guy )
+ guy.SetEfficientMode( true )
+ Highlight_ClearFriendlyHighlight( guy )
+ }
+
+ string anim_og_idle = "pt_OG_training_rail_sit_idle"
+ string anim_og_helmet_idle = "helmet_intro_scene_OG_idle"
+ string anim_bt_idle = "BT_intro_scene_OG_idle"
+
+ string anim_og = "pt_pod_setup_OG"
+ string anim_bt = "BT_pod_setup_OG"
+
+ thread PlayAnimTeleport( og, anim_og_idle, animEnt )
+ thread PlayAnimTeleport( ogHelmet, anim_og_helmet_idle, animEnt )
+ thread PlayAnimTeleport( bt, anim_bt_idle, animEnt )
+
+ player.WaitSignal( "PodIntro_OG_StartPodAnim" )
+
+ if ( !IsValid( og ) )
+ return
+
+ og.Anim_Stop()
+ thread PlayAnim( og, anim_og, animEnt )
+
+ bt.Anim_Stop()
+ thread PlayAnim( bt, anim_bt, animEnt )
+
+ float animDuration = og.GetSequenceDuration( anim_og )
+ wait animDuration - 0.1
+
+ if ( !IsValid( og ) )
+ return
+
+ og.Anim_Stop()
+ thread PlayAnimTeleport( og, anim_og_idle, animEnt )
+
+ bt.Anim_Stop()
+ thread PlayAnimTeleport( bt, anim_bt_idle, animEnt )
+
+ // wait for look training to get started before killing the scene
+ FlagWaitAny( "PlayerLookedAtTopTarget", "PlayerLookedAtBottomTarget" )
+}
+
+void function PodIntro_OG_Slaps_Pod( entity ogPilot )
+{
+ array<entity> players = GetPlayerArray()
+ if ( !players.len() )
+ return
+
+ entity player = players[0]
+ if ( !IsValid( player ) )
+ return
+
+ float shakeDuration = 0.45
+ float shakeAmplitude = 0.14
+ float screenBlurFrac = 0
+ SimpleScreenShake( player, shakeDuration, shakeAmplitude, screenBlurFrac )
+}
+
+void function LookTraining( entity player )
+{
+ thread LookTraining_StartNag( player, "PlayerLookedAtTopTarget", "PlayerLookedAtBottomTarget" )
+
+ Remote_CallFunction_Replay( player, "ScriptCallback_ShowInvertCrosshair", true )
+
+ // LOOKAT SECTION
+ Remote_CallFunction_Replay( player, "ScriptCallback_SetupLookTargets" )
+ wait 0.5
+ Remote_CallFunction_Replay( player, "ScriptCallback_LookTargets_WaitForLookat" )
+
+ DialogueGroup invertConfirmVO = GetDialogueGroup( "ogInvertConfirm" )
+
+ int numInverts = 0
+ int maxInverts = 2
+ //while ( numInverts < maxInverts )
+ while ( 1 )
+ {
+ Remote_CallFunction_Replay( player, "ScriptCallback_SetupLookTargets" )
+ wait 0.5
+ Remote_CallFunction_Replay( player, "ScriptCallback_LookTargets_WaitForLookat" )
+
+ string hintAlias = "invert_look_at_lights"
+ if ( numInverts > 0 )
+ hintAlias = "invert_look_at_lights_again"
+
+ // only play this VO once
+ if ( numInverts == 1 )
+ {
+ // "You sure?"
+ thread PlayDialogue( "og_invert_confirm_3", player )
+ }
+
+ DisplayOnscreenHint( player, hintAlias )
+
+ printt( "Waiting for player to look at either target" )
+
+ FlagWaitAny( "PlayerLookedAtTopTarget", "PlayerLookedAtBottomTarget" )
+
+ printt( "Player looked at one of the targets" )
+
+ hintAlias = "invert_look_at_lights_1_left"
+ if ( numInverts > 0 )
+ hintAlias = "invert_look_at_lights_again_1_left"
+
+ DisplayOnscreenHint( player, hintAlias )
+
+ FlagWait( "PlayerLookedAtTopTarget" )
+ FlagWait( "PlayerLookedAtBottomTarget" )
+
+ printt( "Player looked at both targets" )
+
+ hintAlias = "invert_look_at_lights_0_left"
+ if ( numInverts > 0 )
+ hintAlias = "invert_look_at_lights_again_0_left"
+
+ DisplayOnscreenHint( player, hintAlias )
+
+ printt( "invert waiting for OG dialogue" )
+
+ // "Does that feel right to you?"
+ // "How about now? Feel alright?"
+ string askAlias = DialogueGroup_GetNextLine( invertConfirmVO )
+ waitthread PlayDialogue( askAlias, player )
+
+ printt( "invert OG dialogue done" )
+
+ ClearOnscreenHint( player )
+
+ string invertConvar = file.playerInputType == 0 ? INVERT_CONVAR_GAMEPAD : INVERT_CONVAR_MOUSE
+ printt( "invertConvar:", invertConvar )
+ bool invertSettingBeforeMenu = GetConVarBool( invertConvar )
+
+ printt( "Opening invert look dialog" )
+
+ // THIS PAUSES THE GAME UNTIL MENU IS CLOSED
+ Remote_CallFunction_UI( player, "ScriptCallback_OpenInvertLookDialog" )
+ wait 0.5 // let the game come back after menu is closed
+
+ // if player didn't change setting, don't repeat
+ invertConvar = file.playerInputType == 0 ? INVERT_CONVAR_GAMEPAD : INVERT_CONVAR_MOUSE
+ printt( "invertConvar:", invertConvar )
+ if ( GetConVarBool( invertConvar ) == invertSettingBeforeMenu )
+ {
+ printt( "player didn't change setting, not repeating" )
+ break
+ }
+
+ printt( "invert- player changed setting, repeating to confirm" )
+
+ numInverts++
+
+ // kill lights and reset flags
+ Remote_CallFunction_Replay( player, "ScriptCallback_LookTargets_KillLights" )
+
+ FlagClear( "PlayerLookedAtTopTarget" )
+ FlagClear( "PlayerLookedAtBottomTarget" )
+ }
+
+ ClearOnscreenHint( player )
+ Remote_CallFunction_Replay( player, "ScriptCallback_ShowInvertCrosshair", false )
+
+ // "Alright, we're good to go."
+ waitthread PlayDialogue( "og_invert_complete", player )
+
+ TrainingPod_ViewConeLock_PodClosed( player )
+
+ TrainingPod_ViewConeLock_SemiStrict( player ) // recenter player view
+ Remote_CallFunction_Replay( player, "ScriptCallback_LookTargets_KillLights" )
+}
+
+void function LookTraining_StartNag( entity player, string flag1, string flag2 )
+{
+ EndSignal( player, "OnDestroy" )
+
+ if ( Flag( flag1 ) || Flag( flag2 ) )
+ return
+
+ FlagEnd( flag1 )
+ FlagEnd( flag2 )
+
+ float nagInterval = 15.0
+ float nextNagTime = Time() + nagInterval
+
+ while( 1 )
+ {
+ wait 1
+
+ if ( Time() < nextNagTime )
+ continue
+
+ // "We have to calibrate the pod. It won't boot up until you look at both of those lights."
+ waitthread PlayDialogue( "og_pod_calibrate", player )
+
+ nextNagTime = Time() + nagInterval
+ }
+}
+
+void function TrainingPod_PlayerSequence_DoorsOpenIdle( entity player, bool doPlayerAnim = true )
+{
+ entity pod = file.trainingPod
+
+ // Have to do this first so the anim starts centered on the ref attachment angles
+ string podAttach = "REF"
+ int attachID = pod.LookupAttachment( podAttach )
+ vector podRefOrg = pod.GetAttachmentOrigin( attachID )
+ vector podRefAng = pod.GetAttachmentAngles( attachID )
+ player.SetOrigin( podRefOrg )
+ player.SetAngles( podRefAng )
+ player.ForceStand()
+
+ player.DisableWeapon()
+
+ // default start anim starts open
+ void functionref( entity ) viewConeFunction_start = TrainingPod_ViewConeLock_PodOpen
+ string podAnim_start = "trainingpod_doors_open_idle"
+
+ // start open idle
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.blendTime = 0.0
+ playerSequence.attachment = podAttach
+ playerSequence.firstPersonAnimIdle = "ptpov_trainingpod_idle"
+ playerSequence.thirdPersonAnimIdle = "pt_trainingpod_idle"
+ playerSequence.viewConeFunction = viewConeFunction_start
+ playerSequence.renderWithViewModels = true
+
+ FirstPersonSequenceStruct podSequence
+ podSequence.blendTime = 0.0
+ podSequence.thirdPersonAnimIdle = podAnim_start
+ podSequence.renderWithViewModels = true
+
+ thread FirstPersonSequence( podSequence, pod )
+
+ if ( doPlayerAnim )
+ thread FirstPersonSequence( playerSequence, player, pod )
+
+ TrainingPod_TurnOnInteriorDLight( "console1" )
+ TrainingPod_TurnOnInteriorDLight( "console2" )
+ //TrainingPod_TurnOnInteriorDLight( "backlight_side_L" )
+ //TrainingPod_TurnOnInteriorDLight( "backlight_side_R" )
+}
+
+
+
+// ------ POD INTRO BACKGROUND SKITS ------
+void function PodIntro_BackgroundSkits( entity player )
+{
+ string endFlag = "PodIntro_PodDoorsClosed"
+ FlagEnd( endFlag )
+
+ thread PodIntro_LoudspeakerVO( player, "PodIntro_InteriorBootSequence_Starting" )
+
+ thread PodIntro_TitanRacks( endFlag )
+
+ thread PodIntro_Background_WalkingGuys()
+
+ // ---- ANIMATING SKIT GUYS ---
+ // script NudgeSkitGuy( "back_console_supervisor", 0, 5, 0 )
+
+ // guy sitting at console in back center of room
+ SkitGuyInfo backConsoleGuy1 = SpawnSkitGuy( "back_console_sitting", "pt_console_idle", < 10521.1, -9679.33, -6044.1 >, < 0, 88.3541, 0 >, TEAM_MILITIA, "npc_soldier" )
+ SkitGuy_PlayAnim( backConsoleGuy1 )
+ SkitGuyInfo backConsoleGuy2 = SpawnSkitGuy( "back_console_supervisor", "pt_bored_interface_leanback", < 10527.6, -9656.36, -6043.97 >, < 0, -26.564, 0 >, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_g2" )
+ SkitGuy_PlayAnim( backConsoleGuy2 )
+
+ // extra marvins working on titans 1 and 2
+ SkitGuyInfo titanBay1_marvin = SpawnSkitGuy( "bay1_marvin", "mv_idle_weld", < 10711.2, -9781.04, -6080.65 >, < 0, 153.715, 0 > )
+ SkitGuy_PlayAnim( titanBay1_marvin, 3.0 ) // don't play same anim at same time
+ SkitGuyInfo titanBay2_marvin = SpawnSkitGuy( "bay2_marvin", "mv_idle_weld", < 10316.2, -9730.23, -6079.97 >, < 0, -69.965, 0 > )
+ SkitGuy_PlayAnim( titanBay2_marvin )
+
+ // guys looking at console in back left
+ SkitGuyInfo leftConsoleGuy1 = SpawnSkitGuy( "console_lean", "pt_bored_interface_leanin", < 10257.6, -9846.63, -6079.97 >, < 0, 34.5175, 0 >, TEAM_MILITIA, "npc_soldier" )
+ SkitGuy_PlayAnim( leftConsoleGuy1 )
+ SkitGuyInfo leftConsoleGuy2 = SpawnSkitGuy( "console_supervisor", "pt_bored_interface_leanback", < 10260.5, -9874.78, -6079.97 >, < 0, -42.534, 0 >, TEAM_MILITIA, "npc_soldier", "mp_weapon_mastiff" )
+ SkitGuy_PlayAnim( leftConsoleGuy2 )
+
+ OnThreadEnd(
+ function() : ()
+ {
+ DeleteAllSkitGuys()
+ }
+ )
+
+ WaitForever()
+}
+
+void function PodIntro_LoudspeakerVO( entity player, string endFlag )
+{
+ EndSignal( player, "OnDestroy" )
+
+ array<string> aliases
+ // "Inbound to Planet Typhon. Subspace rendezvous in approximately 20 minutes."
+ aliases.append( "intro_0" )
+ // "Reminder to dock personnel: Titan ordnance is a Type 3 Hazardous Material."
+ aliases.append( "intro_2" )
+ // "Running Lifeboat diagnostic test two point one. All Mark Eight lifeboats are in the green."
+ aliases.append( "intro_4" )
+ // "3rd Militia Grenadiers - prep dropship MacAllan 17."
+ aliases.append( "intro_5" )
+ // "Captain Cole to communications."
+ aliases.append( "intro_3" )
+ // "Major Anderson, please report to the briefing room."
+ aliases.append( "intro_1" )
+
+ thread LoopLoudspeakerVO( aliases, endFlag, 1.0, 2.0 )
+}
+
+void function PodIntro_Background_WalkingGuys()
+{
+ // group of guys walking left to right
+ array<Point> path1 = []
+ ScriptedPath_AddPoint( path1, < 10327.9, -10000.28, -6079.97 >, < 0, 0.918, 0 > )
+ ScriptedPath_AddPoint( path1, < 10373.5, -9987.14, -6079.97 >, < 0, 0.093, 0 > )
+ ScriptedPath_AddPoint( path1, < 10688.3, -9973.76, -6079.97 >, < 0, 0, 0 > )
+ ScriptedPath_AddPoint( path1, < 10856.4, -9973.58, -6079.97 >, < 0, 0, 0 > )
+ SkitGuyInfo walkerInfo_1 = SpawnSkitGuy( "walker_1", "", path1[0].origin, path1[0].angles, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_hemlok_smg" )
+ thread ScriptedPath_Walk( walkerInfo_1, path1, 0.75 )
+
+ array<Point> path2 = []
+ ScriptedPath_AddPoint( path2, < 10248.6, -9925.06, -6079.97 >, < 0, 6.36392, 0 > )
+ ScriptedPath_AddPoint( path2, < 10373.5, -9939.14, -6079.97 >, < 0, 0.093, 0 > )
+ ScriptedPath_AddPoint( path2, < 10688.3, -9973.76, -6079.97 >, < 0, 0, 0 > )
+ ScriptedPath_AddPoint( path2, < 10856.4, -9973.58, -6079.97 >, < 0, 0, 0 > )
+ SkitGuyInfo walkerInfo_2 = SpawnSkitGuy( "walker_2", "", path2[0].origin, path2[0].angles, TEAM_MILITIA, "npc_soldier", "mp_weapon_hemlok" )
+ thread ScriptedPath_Walk( walkerInfo_2, path2, 0.85 )
+}
+
+void function PodIntro_TitanRacks( string endFlag )
+{
+ FlagEnd( endFlag )
+
+ array<HangarTitanGroup> titanGroups
+
+ HangarTitanGroup bay1
+ bay1.ref = GetEntByScriptName( "animref_drop_hangar_titan_1" )
+ bay1.titan = GetEntByScriptName( "hangar_titan_1" )
+ bay1.rack = GetEntByScriptName( "hangar_titan_rack_1" )
+ bay1.titanAnim = "bt_TDay_drop_titan4"
+ bay1.rackAnim = "rack_TDay_drop_rack4"
+ bay1.marvinAnim = "mv_TDay_drop_marvin4"
+ HangarTitanGroup_Init( bay1 )
+ titanGroups.append( bay1 )
+
+ // --- BAY 2 ---
+ HangarTitanGroup bay2
+ bay2.ref = GetEntByScriptName( "animref_drop_hangar_titan_2" )
+ bay2.titan = GetEntByScriptName( "hangar_titan_2" )
+ bay2.rack = GetEntByScriptName( "hangar_titan_rack_2" )
+ bay2.titanAnim = "bt_TDay_drop_titan3"
+ bay2.rackAnim = "rack_TDay_drop_rack3"
+ bay2.marvinAnim = "mv_TDay_drop_marvin3"
+ HangarTitanGroup_Init( bay2 )
+ titanGroups.append( bay2 )
+
+ float wait_earlyEnd = 27.0
+ thread HangarTitanGroup_Animate( bay1, endFlag, wait_earlyEnd )
+ thread HangarTitanGroup_Animate( bay2, endFlag, wait_earlyEnd )
+
+ wait wait_earlyEnd
+ thread PodIntro_TitanRacks( endFlag )
+}
+
+
+
+// ===================================
+// ========= BASIC MOVEMENT ==========
+// ===================================
+void function Training_Setup_BasicMovement( entity player )
+{
+ player.DisableWeapon()
+}
+
+void function Training_Skipped_BasicMovement( entity player )
+{
+ player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, [ "disable_doublejump", "disable_wallrun" ] )
+
+ Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" )
+}
+
+void function Training_BasicMovement( entity player )
+{
+ entity standNearJumpRef = GetEntByScriptName( "basic_movement_og_stand_near_jump" )
+ standNearJumpRef.SetOrigin( OriginToGround( standNearJumpRef.GetOrigin() + <0,0,0.5> ) ) // HACK HACK the ref node is a tiny bit in the geo which causes OG to dip when blending between anims
+ entity ogStart = GetEntByScriptName( "basic_movement_og_start" )
+ entity og = Training_SpawnOGPilot( ogStart )
+
+ player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, [ "disable_doublejump", "disable_wallrun" ] )
+ player.ForceAutoSprintOff()
+
+ entity playerStart = GetEntByScriptName( "startpoint_basic_movement" )
+ waitthread PlayerAndOGTeleport_Fancy( player, playerStart.GetOrigin(), "basic_movement_og_start", playerStart.GetAngles() )
+ Training_OG_Idles_Sitting( ogStart, "OG_base_move_A_idle" )
+
+ thread BasicMovement_DelayedWeaponDeploy( player, 1.5 )
+ Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" )
+
+ // "Ah. Much better."
+ waitthread Training_OG_ScriptedAnim( ogStart, "OG_base_move_A" )
+ thread Training_OG_Idles_Sitting( ogStart, "OG_base_move_B_idle" )
+
+ CheckPoint_Silent()
+
+ thread OnscreenHint_DisplayUntilFlag( player, "move_hint", "BasicMovement_PlayerMovedForward", 10.0 )
+
+ FlagWait( "BasicMovement_PlayerMovedForward" )
+
+ if ( !Flag( "BasicMovement_PlayerJumped" ) )
+ {
+ // "Technically, I'm not supposed to be training you. But in you, I see potential.
+ waitthread Training_OG_ScriptedAnim( ogStart, "OG_base_move_B1")
+ }
+
+ if ( !Flag( "BasicMovement_PlayerJumped" ) )
+ {
+ // "Besides, we're at war. Who's got time for classes, eh?"
+ waitthread Training_OG_ScriptedAnim( ogStart, "OG_base_move_B2")
+ thread Training_OG_Idles_Sitting( ogStart )
+ }
+
+ if ( !Flag( "BasicMovement_PlayerJumped" ) && player.IsOnGround() )
+ {
+ DisplayOnscreenHint( player, "jump_hint" )
+
+ // "Here you go, up and over."
+ waitthread Training_OG_ScriptedAnim( ogStart, "OG_base_move_C" )
+ waitthread Training_OG_Moves( standNearJumpRef )
+ }
+
+ if ( !Flag( "BasicMovement_PlayerJumped" ) )
+ {
+ // "Cmon, schedule's tight today."
+ // "Here you go, up and over."
+ array<string> jumpNags = [ "og_jump_nag_2", "og_jump_nag" ]
+
+ thread Training_OG_NagPlayerUntilFlag( player, jumpNags, 20.0, standNearJumpRef, "BasicMovement_PlayerJumped" )
+ }
+
+ FlagWait( "BasicMovement_PlayerJumped" )
+
+ ClearOnscreenHint( player )
+
+ waitthread BasicMovement_Sprint( player )
+}
+
+void function BasicMovement_Sprint( entity player )
+{
+ thread BasicMovement_Sprint_OG_Moves( player )
+
+ //player.ForceAutoSprintOn()
+
+ FlagClear( "PlayerSprinted" )
+ thread BasicMovement_PlayerSprintDetection( player )
+
+ // "Let's pick up the pace. Enabling jumpkit assist."
+ float endVOTime = Time() + 3.5
+ thread PlayDialogue( "og_autosprint_on", file.ogPilot )
+
+ wait 2.5 // let the VO play a bit before showing the hint
+
+ player.UnforceAutoSprint()
+ thread BasicMovement_SprintHint_Think( player )
+
+ wait endVOTime - Time()
+
+ // HACK- Create a fake VO speaker where OG will eventually move to/from so the lines emit in worldspace for audio
+ entity ref = GetEntByScriptName( "basic_movement_og_mid_hallway" )
+ entity tempSpeaker = CreateScriptMover( ref.GetOrigin(), <0,0,0> )
+ thread HACK_MoveTempSpeaker_WithOGPathMover( player, tempSpeaker )
+
+ // "Jumpkits operate on the principle of relaxed stability."
+ EmitSoundOnEntity( tempSpeaker, "diag_sp_addtional_TR411_53_mcor_og" ) // do this instead of PlayDialogue because it will follow the ent around
+ wait 3.4 // HACK
+
+ // "Once your jumpkit calibrates to your movement style, enhanced mobility becomes second nature."
+ EmitSoundOnEntity( tempSpeaker, "diag_sp_movement_TR121_08_01_mcor_og" )
+ wait 6.0 // HACK
+
+ tempSpeaker.Destroy()
+}
+
+void function HACK_MoveTempSpeaker_WithOGPathMover( entity player, entity tempSpeaker )
+{
+ EndSignal( player, "OnDestroy" )
+ EndSignal( tempSpeaker, "OnDestroy" )
+
+ vector prevOrg = < -1,-1,-1 >
+
+ while ( 1 )
+ {
+ WaitFrame()
+
+ vector newOrg = < -1,-1,-1 >
+
+ // if we are doing a path move, use the pathMover origin- otherwise use OG's origin
+ if ( IsValid( file.ogPathMover ) )
+ newOrg = file.ogPathMover.GetOrigin()
+ else if ( IsValid( file.ogPilot ) )
+ file.ogPilot.GetOrigin()
+
+ if ( newOrg != < -1,-1,-1 > && newOrg != prevOrg )
+ {
+ tempSpeaker.SetOrigin( newOrg )
+ prevOrg = newOrg
+ }
+ }
+}
+
+void function BasicMovement_SprintHint_Think( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ float autosprintHintTime = 10.0
+
+ // player with autosprint on will already be sprinting
+ if ( GetAutosprintEnabled() )
+ {
+ thread DisplayOnscreenHint( player, "autosprint_hint", autosprintHintTime )
+ return
+ }
+
+ // Below this point assume that autosprint is NOT enabled
+ if ( !Flag( "PlayerSprinted" ) )
+ {
+ thread OnscreenHint_DisplayUntilFlag( player, "sprint_button_hint", "PlayerSprinted", 0.0, true )
+ FlagWait( "PlayerSprinted" )
+
+ wait 1.0
+ }
+
+ DisplayOnscreenHint( player, "autosprint_available", autosprintHintTime )
+}
+
+void function BasicMovement_Sprint_OG_Moves( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ entity ogMidHallwayRef = GetEntByScriptName( "basic_movement_og_mid_hallway" )
+ waitthread Training_OG_Moves_ToSitting( ogMidHallwayRef, "", 0.8 )
+
+ FlagWait( "BasicMovement_PlayerReachedMidHallway" )
+
+ entity og_zenGarden_start = GetEntByScriptName( "basic_movement_og_zen_start" )
+ thread Training_OG_Moves( og_zenGarden_start, "OG_Beautiful_idle" )
+}
+
+void function BasicMovement_PlayerSprintDetection( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+ FlagEnd( "BasicMovement_PlayerReachedOpenArea" )
+
+ while ( 1 )
+ {
+ WaitFrame()
+
+ if ( player.IsSprinting() )
+ break
+ }
+
+ FlagSet( "PlayerSprinted" )
+}
+
+void function BasicMovement_DelayedWeaponDeploy( entity player, float delay )
+{
+ player.EndSignal( "OnDestroy" )
+
+ wait delay
+ player.EnableWeaponWithSlowDeploy()
+ thread TakeAmmoFromPlayerASAP( player )
+}
+
+
+// =================================================
+// ========= BASIC MOVEMENT 2: ZEN GARDEN ==========
+// =================================================
+void function Training_Setup_ZenGarden( entity player )
+{
+ entity ogStart = GetEntByScriptName( "basic_movement_og_zen_start" )
+ entity og = Training_SpawnOGPilot( ogStart )
+ Training_OG_Idles( ogStart, "OG_Beautiful_idle" )
+
+ thread TakeAmmoFromPlayerASAP( player )
+
+ TeleportPlayerAndBT( "startpoint_zen_garden" )
+}
+
+void function Training_Skipped_ZenGarden( entity player )
+{
+ player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, [] )
+
+ OpenZenGardenExitDoor()
+}
+
+void function Training_ZenGarden( entity player )
+{
+ entity og = GetOGPilot()
+ Assert( IsValid( og ) )
+
+ entity ref_hillclimbFinish = GetEntByScriptName( "zengarden_hillclimb_finish_idle" )
+ entity ref_exitSpot = GetEntByScriptName( "zengarden_og_exit_ref" )
+
+ OpenZenGardenExitDoor()
+
+ FlagWait( "BasicMovement_PlayerReachedOpenArea" )
+
+ player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, ["disable_doublejump"] )
+
+ CheckPoint_Silent()
+
+ thread ZenGarden_WhyWeFight_VO( player, og )
+
+ waitthread Training_ZenGarden_Wallrun( player )
+
+ waitthread Training_ZenGarden_Crouch( player )
+
+ player.SetPlayerSettingsWithMods( TRAINING_PLAYER_SETTINGS, [] )
+
+ CheckPoint_Silent()
+
+ waitthread Training_ZenGarden_DoubleJump( player )
+
+ waitthread Training_ZenGarden_HillClimb( player, ref_hillclimbFinish, ref_exitSpot )
+
+ if ( IsValid( level ) )
+ Signal( level, "StopRepeatingGhostRecorder" )
+}
+
+// ZEN GARDEN SHARED BETWEEN PATHS
+void function ZenGarden_WhyWeFight_VO( entity player, entity og )
+{
+ entity ogStart = GetEntByScriptName( "basic_movement_og_zen_start" )
+ entity firstRockRef = GetEntByScriptName( "zengarden_og_rock1_ref" )
+ entity pathRef = GetEntByScriptName( "zengarden_og_path_ref" )
+ entity treeRef = GetEntByScriptName( "zengarden_og_tree_ref" )
+
+ string stopFlag = "ZenGarden_PlayerReachedWallrunStart"
+ string setFlag = "OG_WhyWeFight_VO_Done"
+
+ if ( !Flag( stopFlag ) )
+ {
+ // "Beautiful, isn't it?"
+ waitthread Training_OG_ScriptedAnim( ogStart, "OG_Beautiful" )
+ }
+
+ if ( !Flag( stopFlag ) )
+ {
+ waitthread Training_OG_Moves( firstRockRef, "", 0.25 )
+ }
+
+ if ( !Flag( stopFlag ) )
+ {
+ // "It's inspired by my home planet of Harmony."
+ waitthread Training_OG_ScriptedAnim( firstRockRef, "OG_harmony_A" )
+ }
+
+ if ( !Flag( stopFlag ) )
+ {
+ // "This is where I grew up."
+ waitthread Training_OG_ScriptedAnim( firstRockRef, "OG_harmony_B" )
+ }
+
+ if ( !Flag( stopFlag ) && !Flag( "ZenGarden_PlayerReachedFirstRock" ) )
+ {
+ waitthread Training_OG_Moves( pathRef )
+ }
+
+ FlagWaitAny( stopFlag, "ZenGarden_PlayerReachedFirstRock" )
+
+ if ( !Flag( stopFlag ) )
+ {
+ waitthread Training_OG_Moves_ToSitting( treeRef, "", 0.5 )
+ }
+
+ if ( !Flag( stopFlag ) )
+ {
+ // "This is what we're fighting for, Cooper."
+ waitthread Training_OG_ScriptedAnim( treeRef, "OG_freedom_A" )
+ }
+
+ if ( !Flag( stopFlag ) )
+ {
+ // "A world that's not metal and smoke."
+ waitthread Training_OG_ScriptedAnim( treeRef, "OG_freedom_B" )
+ }
+
+ if ( !Flag( stopFlag ) )
+ {
+ // "The freedom to live in peace and prosperity."
+ waitthread Training_OG_ScriptedAnim( treeRef, "OG_freedom_C" )
+ }
+
+ FlagSet( setFlag )
+}
+
+void function Training_ZenGarden_Wallrun( entity player )
+{
+ entity wallrunRef = GetEntByScriptName( "basic_movement_og_zen_wallrun_idle" )
+ entity recorderRef = GetEntByScriptName( "basic_movement_wallrun_start_ref" )
+
+ FlagWaitAny( "ZenGarden_PlayerReachedWallrunStart", "OG_WhyWeFight_VO_Done" )
+
+ FlagWait( "OG_WhyWeFight_VO_Done" )
+
+ waitthread Training_OG_Moves_ToSitting( wallrunRef, "OG_primed_idle", 0.5 )
+
+ FlagWait( "ZenGarden_PlayerReachedWallrunStart" )
+
+ thread GhostRecorder_RepeatUntilFlag( player, "ZenGarden_PlayerFinishedWallrun", recorderRef, $"anim_recording/training_record_zengarden_wallrun.rpak" )
+
+ if ( !Flag( "ZenGarden_PlayerFinishedWallrun" ) )
+ thread OnscreenHint_DisplayUntilFlag( player, "wallrun_hint", "ZenGarden_PlayerFinishedWallrun" )
+
+ wait 1.0 // wait to see if player starts wallrunning right away
+
+ if ( !Flag( "ZenGarden_PlayerFinishedWallrun" ) && !Flag( "ZenGarden_PlayerTouchingWallrunPanel" ) )
+ {
+ // "Let's make sure your jump kit is primed. Basic wallrun here, give it a try."
+ waitthread Training_OG_ScriptedAnim( wallrunRef, "OG_primed" )
+ thread Training_OG_Idles_Sitting( wallrunRef, "OG_primed_idle" )
+ }
+
+ if ( !Flag( "ZenGarden_PlayerFinishedWallrun" ) && !Flag( "ZenGarden_PlayerTouchingWallrunPanel" ) )
+ wait 1.0
+
+ if ( !Flag( "ZenGarden_PlayerFinishedWallrun" ) && !Flag( "ZenGarden_PlayerTouchingWallrunPanel" ) )
+ {
+ // "Same routine as last time- watch the ghost pilot, and try to follow along."
+ thread PlayDialogue( "og_wallrun_follow_ghost", file.ogPilot )
+ waitthread Training_OG_ScriptedAnim( wallrunRef, "OG_primed_generic" )
+ thread Training_OG_Idles_Sitting( wallrunRef, "OG_primed_idle" )
+ }
+
+ FlagWait( "ZenGarden_PlayerFinishedWallrun" )
+
+ if ( !Flag( "ZenGarden_PlayerReachedCrouchArea" ) )
+ {
+ // "Good! Now you're moving."
+ file.postWallrunVOEndTime = Time() + 3.0
+ thread PlayDialogue( "og_wallrun_done", player )
+ wait 0.3 // min wait after
+ }
+}
+
+void function Training_ZenGarden_Crouch( entity player )
+{
+ entity ogIdle_crouchSpot = GetEntByScriptName( "zengarden_og_slide_idle")
+ entity ref_slideStart = GetEntByScriptName( "zengarden_slide_ref" )
+
+ if ( Flag( "ZenGarden_PlayerCrouched" ) )
+ return
+
+ waitthread Training_OG_Moves_ToSitting( ogIdle_crouchSpot, "OG_low_idle", 0.8 )
+ thread GhostRecorder_RepeatUntilFlag( player, "ZenGarden_PlayerCrouched", ref_slideStart, $"anim_recording/training_record_zengarden_slide.rpak", 1.0 )
+
+ FlagWait( "ZenGarden_PlayerReachedCrouchArea" )
+
+ DisplayOnscreenHint( player, "crouch_hint", 5.0 )
+
+ while ( file.postWallrunVOEndTime > 0 && file.postWallrunVOEndTime - Time() > 0 )
+ wait 0.1
+
+ if ( Flag( "ZenGarden_PlayerCrouched" ) )
+ return
+
+ // "Under here. Stay low."
+ waitthread Training_OG_ScriptedAnim( ogIdle_crouchSpot, "OG_low" )
+ thread Training_OG_Idles_Sitting( ogIdle_crouchSpot, "OG_low_idle", true )
+
+ thread Training_ZenGarden_CrouchHint_WithNags( player, 10.0, ogIdle_crouchSpot, "OG_low_idle" )
+
+ FlagWait( "ZenGarden_PlayerCrouched" )
+
+ ClearOnscreenHint( player )
+}
+
+void function Training_ZenGarden_CrouchHint_WithNags( entity player, float nagInterval, entity idleRef, string ogIdleAnim )
+{
+ player.EndSignal( "OnDestroy" )
+
+ string endFlag = "ZenGarden_PlayerCrouched"
+ string activeFlag = "ZenGarden_PlayerAtCrouchStart"
+
+ // "Crouch underneath, and we'll keep moving."
+ // "You need to get low here."
+ array<string> nags = [ "og_crouch_nag_1", "og_crouch_nag_2" ]
+
+ int nagIdx = 0
+ float nextNagTime = Time() + nagInterval
+
+ bool showingHint = false
+
+ while ( !Flag( endFlag ) )
+ {
+ FlagWait( activeFlag )
+
+ while ( Flag( activeFlag ) )
+ {
+ wait 0.1
+
+ if ( player.IsCrouched() )
+ {
+ if ( showingHint )
+ {
+ showingHint = false
+ ClearOnscreenHint( player )
+ }
+
+ continue
+ }
+
+ if ( !showingHint )
+ {
+ DisplayOnscreenHint( player, "crouch_hint" )
+ showingHint = true
+ }
+
+ if ( Time() - nextNagTime >= nagInterval )
+ {
+ thread Training_OG_Talks_Sitting( nags[nagIdx], idleRef, "", ogIdleAnim )
+ nextNagTime = Time() + nagInterval
+
+ nagIdx++
+ if ( nagIdx >= nags.len() )
+ nagIdx = 0
+ }
+ }
+
+ if ( showingHint )
+ {
+ showingHint = false
+ ClearOnscreenHint( player )
+ }
+ }
+}
+
+void function Training_ZenGarden_DoubleJump( entity player )
+{
+ entity pathRef = GetEntByScriptName( "zengarden_og_postslide_idle" )
+ entity ogIdleRef = GetEntByScriptName( "zengarden_og_doublejump_idle" )
+ entity recordedAnimRef = GetEntByScriptName( "zengarden_doublejump_ref" )
+
+ waitthread Training_OG_Moves( pathRef, "", 0.5 )
+
+ // "Simple double jump. Follow the ghost."
+ waitthread Training_OG_Talks( "og_doublejump_hint", pathRef )
+
+ thread GhostRecorder_RepeatUntilFlag( player, "ZenGarden_PlayerDoubleJumped", recordedAnimRef, $"anim_recording/training_record_zengarden_doublejump.rpak", 1.0 )
+
+ waitthread Training_OG_Moves_ToSitting( ogIdleRef, "OG_doublejump_idle", 0.5 )
+
+ // "We've retaken over a quarter of Frontier space since the Battle of Demeter. The Militia's better organized now. More people join everyday to fight the IMC. People like you."
+ waitthread Training_OG_ScriptedAnim( ogIdleRef, "OG_doublejump_A" )
+ Training_OG_Idles( ogIdleRef, "OG_doublejump_B_idle" )
+
+ FlagWait( "ZenGarden_PlayerReachedDoubleJumpArea" )
+
+ if ( !Flag( "ZenGarden_PlayerDoubleJumped" ) )
+ {
+ DisplayOnscreenHint( player, "doublejump_hint", 5.0 )
+ thread OnscreenHint_NagUntilFlag( player, "doublejump_hint", "ZenGarden_PlayerDoubleJumped", 10.0, 5.0 )
+
+ wait 0.8
+ }
+
+ FlagWait( "ZenGarden_PlayerDoubleJumped" )
+}
+
+void function Training_ZenGarden_HillClimb( entity player, entity ref_hillclimbFinish, entity ref_exitSpot )
+{
+ // "We used to just run and hide from them. But now we chase them."
+ string nextAnim = "OG_doublejump_B"
+
+ if ( !Flag( "ZenGarden_PlayerClimbedHill" ) )
+ {
+ waitthread Training_OG_Moves( ref_hillclimbFinish, "", 0.25 )
+ //waitthread Training_OG_Talks( nextLine, ref_hillclimbFinish )
+ waitthread Training_OG_ScriptedAnim( ref_hillclimbFinish, nextAnim )
+ FlagWait( "ZenGarden_PlayerClimbedHill" )
+ waitthread Training_OG_Moves( ref_exitSpot, ANIM_OG_LEANING_IDLE, 0.5 )
+ }
+ else if ( !Flag( "ZenGarden_PlayerReachedExitArea" ) )
+ {
+ waitthread Training_OG_Moves( ref_exitSpot, ANIM_OG_LEANING_IDLE, 0.5 )
+
+ if ( !Flag( "ZenGarden_PlayerReachedExitArea" ) )
+ waitthread Training_OG_ScriptedAnim( ref_exitSpot, nextAnim )
+
+ if ( !Flag( "ZenGarden_PlayerReachedExitArea" ) )
+ waitthread Training_OG_Moves( ref_exitSpot, ANIM_OG_LEANING_IDLE, 0.5 )
+ }
+
+ FlagWait( "ZenGarden_PlayerReachedExitArea" )
+}
+
+
+// =================================
+// ========= FIRING RANGE ==========
+// =================================
+void function Training_Setup_FiringRange( entity player )
+{
+ entity ogStart = GetEntByScriptName( "firingrange_og_needguns_start_sitting" )
+ entity og = Training_SpawnOGPilot( ogStart )
+ Training_OG_Idles_Sitting( ogStart )
+
+ thread TakeAmmoFromPlayerASAP( player )
+
+ TeleportPlayerAndBT( "startpoint_firing_range" )
+}
+
+void function Training_Skipped_FiringRange( entity player )
+{
+ CloseZenGardenExitDoor()
+ OpenGauntletDoor()
+
+ FlagSet( "ineedguns" )
+ Training_WeaponRacks_SetSolidity( true )
+
+ player.SetExtraWeaponMods( [ "" ] ) // turns off low_ammo_disable
+ SetWeaponHUDEnabled( player, true )
+
+ Training_SetWeaponPickupsFullAmmo()
+}
+
+void function Training_FiringRange( entity player )
+{
+ entity ref_og_needGunsStart = GetEntByScriptName( "firingrange_og_needguns_start_sitting" )
+ entity ref_og_firingRangeSpot = GetEntByScriptName( "firingrange_og_spot" )
+ entity ref_og_firingRangeAttractSpot = GetEntByScriptName( "firingrange_og_attract_spot" )
+
+ float ogMoveTime = -1
+
+ Training_WeaponRacks_SetSolidity( false )
+ Training_SetWeaponPickupsEmptyAmmo()
+
+ thread FiringRange_CloseZenGardenDoor_WhenPlayerReachesRange( player )
+
+ waitthread Training_OG_Moves_ToSitting( ref_og_needGunsStart, "OG_Weapons_idle", ogMoveTime )
+
+ FlagWait( "PlayerApproachingFiringRange" )
+
+ thread FiringRange_DetectWeaponSwitch( player )
+
+ waitthread FiringRange_ApproachAndEntry( player, ref_og_needGunsStart, ref_og_firingRangeAttractSpot, ref_og_firingRangeSpot )
+
+ waitthread FiringRange_TrainReload( player, ref_og_firingRangeSpot )
+
+ Training_WeaponRacks_SetSolidity( true )
+ thread FiringRange_InfiniteAmmo_WhenNearRange( player, "PodOutroStarted" )
+ thread FiringRange_ResetTargets_Think( player )
+
+ thread Training_OG_Idles( ref_og_firingRangeSpot, "OG_firingrange_idle" )
+
+ waitthread FiringRange_TrainADS( player, ref_og_firingRangeSpot )
+
+ waitthread FiringRange_PlayerMustDamageAllTargets( player, ref_og_firingRangeSpot, true, false )
+
+ Training_SetWeaponPickupsFullAmmo()
+
+ if ( !Flag( "FiringRangeWeaponSwapped" ) )
+ {
+ waitthread FiringRange_TrainWeaponSwap( player, ref_og_firingRangeSpot )
+
+ FlagWait( "PlayerNearFiringRange" ) // if player moved away to get a weapon, wait for them to come back
+ wait 0.25 // extra wait for player to see the targets reset
+
+ waitthread FiringRange_PlayerMustDamageAllTargets( player, ref_og_firingRangeSpot, false )
+ }
+
+ OpenGauntletDoor()
+
+ CheckPoint_Silent()
+
+ // "Good. Practice more if you want, then head to the Gauntlet."
+ waitthread Training_OG_ScriptedAnim( ref_og_firingRangeSpot, "OG_firingrange_ending" )
+
+ array<string> moveToGauntletNags = [ "og_moving_to_gauntlet_nag_1", "og_moving_to_gauntlet_nag_2", "og_moving_to_gauntlet_nag_3" ]
+
+ if ( Flag( "PlayerNearFiringRange" ) )
+ {
+ entity ref_og_midway2Gauntlet = GetEntByScriptName( "og_between_firingrange_and_gauntlet" )
+ thread Training_OG_NagPlayerUntilFlag_Sitting( player, moveToGauntletNags, 45.0, ref_og_midway2Gauntlet, "OG_MovedTo_GauntletEntrance" )
+
+ waitthread Training_OG_Moves_ToSitting( ref_og_midway2Gauntlet )
+
+ FlagWaitClear( "PlayerNearFiringRange" )
+ }
+
+ FlagSet( "OG_MovedTo_GauntletEntrance" )
+
+ entity ref_og_gauntletEntrance = GetEntByScriptName( "og_gauntlet_entrance_attract_spot" )
+ thread Training_OG_NagPlayerUntilFlag( player, moveToGauntletNags, 45.0, ref_og_gauntletEntrance, "PlayerInGauntletEntryway" )
+
+ waitthread Training_OG_Moves( ref_og_gauntletEntrance )
+}
+
+void function FiringRange_CloseZenGardenDoor_WhenPlayerReachesRange( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ vector doorFarEdge = < -6368, -952, 48 > // HACK this is the bottom edge of the door farthest from the firing range
+
+ while ( 1 )
+ {
+ WaitFrame()
+
+ if ( !Flag( "PlayerNearFiringRange" ) )
+ continue
+
+ if ( PlayerCanSeePos( player, doorFarEdge, true, 90 ) )
+ continue
+
+ break
+ }
+
+ CloseZenGardenExitDoor()
+}
+
+void function FiringRange_ApproachAndEntry( entity player, entity ref_og_needGunsStart, entity ref_og_firingRangeAttractSpot, entity ref_og_firingRangeSpot )
+{
+ thread FiringRange_Approach_OG_Sequence( player, ref_og_needGunsStart )
+
+ FlagWait( "FiringRange_Approach_OG_Sequence_Done" )
+
+ if ( !Flag( "PlayerNearFiringRange" ) )
+ {
+ // "Time to hit the range."
+ waitthread Training_OG_ScriptedAnim( ref_og_needGunsStart, "OG_Weapons_D" )
+ }
+
+ if ( !Flag( "PlayerNearFiringRange" ) )
+ {
+ // "I'm over here at the range, Cooper."
+ thread Training_OG_NagPlayerUntilFlag( player, [ "og_firingrange_attract_nag" ], 40.0, ref_og_firingRangeAttractSpot, "PlayerNearFiringRange" )
+
+ waitthread Training_OG_Moves( ref_og_firingRangeAttractSpot )
+ }
+
+ FlagWait( "PlayerNearFiringRange" )
+
+ waitthread Training_OG_Moves( ref_og_firingRangeSpot, "OG_firingrange_idle" )
+}
+
+void function FiringRange_Approach_OG_Sequence( entity player, entity ref_og_needGunsStart )
+{
+ EndSignal( player, "OnDestroy" )
+
+ if ( !Flag( "PlayerNearFiringRange" ) )
+ Training_OG_Idles_Sitting( ref_og_needGunsStart )
+
+ if ( Flag( "PlayerNearFiringRange" ) )
+ {
+ // player is rushing forward
+ FlagSet( "ineedguns" )
+ thread FiringRange_INeedGuns_SFX( player, 0.0 )
+ }
+ else
+ {
+ FlagSetDelayed( "ineedguns", 2.0 )
+ thread FiringRange_INeedGuns_SFX( player, 2.0 )
+ }
+
+ if ( !Flag( "PlayerNearFiringRange" ) )
+ {
+ // "In combat, things never go as you expect."
+ waitthread Training_OG_ScriptedAnim( ref_og_needGunsStart, "OG_Weapons_A" )
+ }
+
+ if ( !Flag( "PlayerNearFiringRange" ) )
+ {
+ // "You must be ready to use any weapon you can find on the field."
+ waitthread Training_OG_ScriptedAnim( ref_og_needGunsStart, "OG_Weapons_B" )
+ }
+
+ if ( !Flag( "PlayerNearFiringRange" ) )
+ {
+ // "These are just a few of the weapons I've come across out there."
+ waitthread Training_OG_ScriptedAnim( ref_og_needGunsStart, "OG_Weapons_C" )
+ }
+
+ FlagSet( "FiringRange_Approach_OG_Sequence_Done" )
+}
+
+void function FiringRange_INeedGuns_SFX( entity player, float delayTime )
+{
+ EndSignal( player, "OnDestroy" )
+
+
+ entity centerEmitter = GetEntByScriptName( "ref_firingrange_rack_sfx_center" )
+ entity backCenterEmitter = GetEntByScriptName( "ref_firingrange_rack_sfx_backcenter" )
+ entity leftEmitter = GetEntByScriptName( "ref_firingrange_rack_sfx_left" )
+ entity rightEmitter = GetEntByScriptName( "ref_firingrange_rack_sfx_right" )
+
+ entity moverCenter = CreateScriptMover( centerEmitter.GetOrigin(), centerEmitter.GetAngles() )
+ entity moverBackCenter = CreateScriptMover( backCenterEmitter.GetOrigin(), backCenterEmitter.GetAngles() )
+ entity moverLeft = CreateScriptMover( leftEmitter.GetOrigin(), leftEmitter.GetAngles() )
+ entity moverRight = CreateScriptMover( rightEmitter.GetOrigin(), rightEmitter.GetAngles() )
+ array<entity> movers = [ moverCenter, moverBackCenter, moverLeft, moverRight ]
+
+ OnThreadEnd(
+ function() : ( movers )
+ {
+ foreach ( mover in movers )
+ {
+ if( !IsValid( mover ) )
+ continue
+
+ mover.Destroy()
+ }
+ }
+ )
+
+ if ( delayTime > 0.0 )
+ wait delayTime
+
+ EmitSoundOnEntity( moverCenter, "training_scr_center_racks" )
+ EmitSoundOnEntity( moverBackCenter, "training_scr_back_racks" )
+ EmitSoundOnEntity( moverLeft, "training_scr_left_racks" )
+ EmitSoundOnEntity( moverRight, "training_scr_right_racks" )
+
+ wait 15.0 // wait before cleaning up movers
+}
+
+void function FiringRange_DetectWeaponSwitch( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ entity ogWeapon = WaitForPlayerActiveWeapon( player )
+ string ogWeaponName = ogWeapon.GetWeaponClassName()
+
+ while ( 1 )
+ {
+ entity weapon = WaitForPlayerActiveWeapon( player )
+ string weaponName = weapon.GetWeaponClassName()
+
+ if ( weaponName != "" && weaponName != ogWeaponName )
+ {
+ FlagSet( "FiringRangeWeaponSwapped" )
+ break
+ }
+
+ wait 0.5
+ }
+}
+
+void function FiringRange_TrainReload( entity player, entity ogIdleSpot )
+{
+ FlagClear( "PlayerReloaded" )
+
+ SetWeaponHUDEnabled( player, true )
+
+ player.SetExtraWeaponMods( [ "" ] ) // turns off low_ammo_disable
+ thread TakeAmmoFromPlayerASAP( player )
+
+ thread TrainReload_GivePlayerAmmoAfterButtonPressed( player )
+
+ // "Load your weapon."
+ // "Swap in a fresh mag."
+ array<string> reloadNags = [ "og_reload_hint", "og_reload_nag" ]
+
+ wait 1.1 // let pro players reload before prompting
+
+ int nagIdx = 0
+
+ if ( !Flag( "PlayerReloaded" ) )
+ {
+ thread OnscreenHint_DisplayUntilFlag( player, "reload_hint", "PlayerReloaded" )
+
+ float nagInterval = 15
+ float lastNagTime = -100
+
+ while ( !Flag( "PlayerReloaded" ) )
+ {
+ wait 0.1
+
+ if ( Time() - lastNagTime >= nagInterval )
+ {
+ waitthread Training_OG_Talks( reloadNags[nagIdx], ogIdleSpot, "OG_firingrange_talk", "OG_firingrange_idle", true )
+ lastNagTime = Time()
+
+ nagIdx++
+ if ( nagIdx >= reloadNags.len() )
+ nagIdx = 0
+ }
+ }
+ }
+}
+
+void function TrainReload_GivePlayerAmmoAfterButtonPressed( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ FlagWait( "PlayerReloaded" )
+
+ FlagSet( "ReloadTraining_PlayerPressedReload" )
+
+ player.SetActiveWeaponPrimaryAmmoTotal( 50 )
+}
+
+void function FiringRange_InfiniteAmmo_WhenNearRange( entity player, string endFlag )
+{
+ player.EndSignal( "OnDestroy" )
+ FlagEnd( endFlag )
+
+ while ( 1 )
+ {
+ wait 0.5
+
+ if ( !Flag( "PlayerNearFiringRange" ) )
+ continue
+
+ entity weapon = player.GetActiveWeapon()
+
+ if ( weapon == null )
+ continue
+
+ int currAmmo = player.GetWeaponAmmoStockpile( weapon )
+ int magSize = weapon.GetWeaponSettingInt( eWeaponVar.ammo_clip_size )
+ int maxAmmo = weapon.GetWeaponSettingInt( eWeaponVar.ammo_stockpile_max )
+
+ // let player reload before restocking
+ if ( currAmmo > (maxAmmo-magSize + 1) )
+ continue
+
+ printt( "firing range restock ammo" )
+
+ RestockPlayerAmmo_Silent( player )
+ }
+}
+
+void function FiringRange_TrainADS( entity player, entity ref_og_firingRangeSpot )
+{
+ thread FlagSetWhenPlayerADS( player, "PlayerADSed" )
+
+ FlagWaitWithTimeout( "PlayerADSed", 2.5 ) // give player time to ADS before hinting
+
+ if ( !Flag( "PlayerADSed" ) )
+ {
+ // "Aim down the sights when engaging more distant targets."
+ // "You can focus more tightly on your targets if you aim down the sights."
+ array<string> adsNags = [ "og_ads_nag_1", "og_ads_nag_2" ]
+ thread Training_OG_NagPlayerUntilFlag( player, adsNags, 10.0, ref_og_firingRangeSpot, "PlayerADSed", "OG_firingrange_talk", "OG_firingrange_idle" )
+
+ thread OnscreenHint_DisplayUntilFlag( player, "ads_hint", "PlayerADSed" )
+
+ // "To get more precision, aim down the sights of your weapon."
+ waitthread Training_OG_Talks( "og_ads_hint", ref_og_firingRangeSpot, "OG_firingrange_talk", "OG_firingrange_idle", true )
+ }
+
+ FlagWait( "PlayerADSed" )
+}
+
+void function FiringRange_TrainWeaponSwap( entity player, entity ref_og_firingRangeSpot )
+{
+ Signal( player, "FiringRange_StopResettingTargets" )
+
+ if ( !Flag( "FiringRangeWeaponSwapped" ) )
+ {
+ DisplayOnscreenHint( player, "weapon_pickup_hint", 5.0 )
+ thread OnscreenHint_NagUntilFlag( player, "weapon_pickup_hint", "FiringRangeWeaponSwapped", 10.0, 5.0 )
+
+ // "Use a different weapon this time. Grab another one off the rack."
+ thread Training_OG_Talks( "og_weaponswap_hint", ref_og_firingRangeSpot, "OG_firingrange_talk", "OG_firingrange_idle", true )
+
+ // "Switch to a different weapon."
+ array<string> weaponSwapNags = [ "og_weaponswap_nag" ]
+ waitthread Training_OG_NagPlayerUntilFlag( player, weaponSwapNags, 10.0, ref_og_firingRangeSpot, "FiringRangeWeaponSwapped", "OG_firingrange_talk", "OG_firingrange_idle" )
+ }
+}
+
+void function FiringRange_PlayerMustDamageAllTargets( entity player, entity ref_og_firingRangeSpot, bool firstTime, bool resetTargetsAtStart = true )
+{
+ EndSignal( player, "OnDestroy" )
+
+ FlagClear( "FiringRange_AllTargetsKilled" )
+ thread FiringRange_ResetTargets_Think( player, resetTargetsAtStart )
+
+ thread OnscreenHint_DisplayUntilFlag( player, "firingrange_dmg_targets_hint", "FiringRange_AllTargetsKilled" )
+
+ wait 2.0
+
+ // "Gotta take 'em all out before we can move on."
+ // "Just aim, take a breath, and squeeze the trigger."
+ // "In the real world, the targets don't just stand still. They shoot back."
+ DialogueGroup nags = GetDialogueGroup( "shootTargetsNag" )
+
+ int numTargetsKilled = FiringRange_GetNumDamagedTargets()
+
+ while ( !Flag( "FiringRange_AllTargetsKilled" ) )
+ {
+ wait 1.0
+
+ // don't nag player if they recently damaged a target
+ if ( FiringRange_GetNumDamagedTargets() > numTargetsKilled )
+ {
+ numTargetsKilled = FiringRange_GetNumDamagedTargets()
+ TimerReset( "firingRangeNag" )
+
+ continue
+ }
+
+ if ( !TimerCheck( "firingRangeNag" ) )
+ continue
+
+ string nagLine = DialogueGroup_GetNextLine( nags )
+ waitthread Training_OG_Talks( nagLine, ref_og_firingRangeSpot, "OG_firingrange_talk", "OG_firingrange_idle", true )
+
+ TimerReset( "firingRangeNag" )
+ }
+}
+
+void function FiringRangeTargets_Init()
+{
+ string targetScriptName = "firingrange_target"
+ array<entity> targetEnts = GetEntArrayByScriptName( targetScriptName )
+ Assert( targetEnts.len(), "Couldn't get firing range targets with script_name " + targetScriptName )
+
+ foreach ( ent in targetEnts )
+ {
+ ent.SetTakeDamageType( DAMAGE_EVENTS_ONLY )
+ ent.SetDamageNotifications( true )
+
+ entity angleRefEnt = ent.GetLinkEnt()
+ Assert( IsValid( angleRefEnt ), "Firing range target needs an angle reference entity linked" )
+ angleRefEnt.SetParent( ent )
+
+ FiringRangeTarget target
+ target.ent = ent
+ target.angleRefEnt = angleRefEnt
+ target.ogAngles = ent.GetAngles()
+ target.mover = CreateScriptMover( ent.GetOrigin(), ent.GetAngles() )
+ ent.SetParent( target.mover )
+
+ file.firingRangeTargets.append( target )
+ }
+}
+
+int function FiringRange_GetNumDamagedTargets()
+{
+ int numDamaged = 0
+
+ foreach ( target in file.firingRangeTargets )
+ if ( target.wasDamaged )
+ numDamaged++
+
+ return numDamaged
+}
+
+void function FiringRange_ResetAllTargets()
+{
+ foreach ( target in file.firingRangeTargets )
+ FiringRangeTarget_Reset( target )
+}
+
+void function FiringRangeTarget_Reset( FiringRangeTarget target )
+{
+ if ( !IsValid( target.ent ) )
+ return
+
+ target.wasDamaged = false
+
+ thread FiringRangeTarget_RotateBack( target )
+}
+
+void function FiringRange_ResetTargets_Think( entity player, bool resetTargetsAtStart = true )
+{
+ Signal( player, "FiringRange_StopResettingTargets" )
+ EndSignal( player, "FiringRange_StopResettingTargets" )
+ EndSignal( player, "OnDestroy" )
+
+ FlagEnd( "PodOutroStarted" )
+
+ array<FiringRangeTarget> firingRangeTargets = file.firingRangeTargets
+
+ bool firstLoop = true
+ float targetResetWait = 1.75
+
+ while ( 1 )
+ {
+ bool resetTargets = true
+ if ( firstLoop && !resetTargetsAtStart )
+ resetTargets = false
+
+ if ( resetTargets )
+ FiringRange_ResetAllTargets()
+
+ foreach ( target in firingRangeTargets )
+ thread FiringRangeTarget_WaitForDamage( target )
+
+ // wait for all targets to take damage
+ while ( 1 )
+ {
+ wait 0.2
+
+ bool allDamaged = true
+ foreach ( target in firingRangeTargets )
+ {
+ if ( !target.wasDamaged )
+ {
+ allDamaged = false
+ break
+ }
+ }
+
+ if ( allDamaged )
+ break
+ }
+
+ FlagSet( "FiringRange_AllTargetsKilled" )
+
+ wait targetResetWait
+
+ if ( firstLoop )
+ firstLoop = false
+ }
+}
+
+void function FiringRangeTarget_WaitForDamage( FiringRangeTarget target )
+{
+ Signal( target.ent, "Target_WaitForDamage_Start" )
+ EndSignal( target.ent, "Target_WaitForDamage_Start" )
+ EndSignal( target.ent, "OnDestroy" )
+
+ bool first = true
+ while ( 1 )
+ {
+ table result = WaitSignal( target.ent, "TargetDamaged" )
+ vector damagePos = expect vector( result.damagePos )
+
+ if ( first )
+ {
+ thread FiringRangeTarget_FirstDamage( target, damagePos )
+ first = false
+ }
+ }
+
+ return
+}
+
+string function FiringRangeTarget_GetDamageSide( FiringRangeTarget target, vector damagePos )
+{
+ vector upEntPos = target.angleRefEnt.GetOrigin()
+ vector upEntAngles = target.angleRefEnt.GetAngles()
+
+ vector facingVec = AnglesToForward( upEntAngles )
+ vector vecToDamage = Normalize( damagePos - upEntPos )
+ float dot2Damage = DotProduct( vecToDamage, facingVec )
+ printt( "damage dot product:", dot2Damage )
+
+ #if DEV
+ //float debugDrawTime = 3.0
+ //DebugDrawAngles( damagePos, upEntAngles, debugDrawTime )
+ //DebugDrawLine( upEntPos, upEntPos + (facingVec * 200), 200, 200, 0, true, debugDrawTime )
+ #endif
+
+ vector frontFacingVec = AnglesToRight( upEntAngles )
+ bool isOnLeft = IsPointInFrontofLine( damagePos, upEntPos, frontFacingVec )
+
+ string returnStr = "left"
+ if ( isOnLeft )
+ {
+ printt( "LEFT SIDE" )
+ }
+ else
+ {
+ printt( "RIGHT SIDE" )
+ returnStr = "right"
+ }
+
+ return returnStr
+}
+
+void function FiringRangeTarget_FirstDamage( FiringRangeTarget target, vector damagePos )
+{
+ //Assert( !target.wasDamaged, "Target not expected to be damaged yet" )
+ if ( target.wasDamaged )
+ return
+
+ target.wasDamaged = true
+
+ if ( IsAlive( file.player ) )
+ EmitSoundOnEntity( file.player, "training_scr_hit_target" )
+
+ // rotate left by default
+ float rotateAngY = 179.9
+ if ( FiringRangeTarget_GetDamageSide( target, damagePos ) == "right" )
+ rotateAngY *= -1
+
+ vector newAngles = target.ogAngles + Vector( 0, rotateAngY, 0 )
+ thread FiringRangeTarget_Rotate( target, newAngles )
+}
+
+void function FiringRangeTarget_Rotate( FiringRangeTarget target, vector targetAngles )
+{
+ Signal( target.mover, "TargetRotate" )
+ EndSignal( target.mover, "TargetRotate" )
+ EndSignal( target.mover, "OnDestroy" )
+
+ float rotateTime = 0.14
+ float accelTime = 0
+ float decelTime = 0.1
+ target.mover.NonPhysicsRotateTo( targetAngles, rotateTime, accelTime, decelTime )
+
+ wait rotateTime
+}
+
+void function FiringRangeTarget_RotateBack( FiringRangeTarget target )
+{
+ if ( target.ent.GetAngles() == target.ogAngles )
+ return
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, target.ent.GetOrigin(), "training_scr_range_target_spin" )
+
+ waitthread FiringRangeTarget_Rotate( target, target.ogAngles )
+}
+
+
+void function FlagSetWhenPlayerADS( entity player, string setFlag )
+{
+ player.EndSignal( "OnDestroy" )
+
+ while ( player.GetZoomFrac() < 0.9 )
+ wait 0.1
+
+ FlagSet( setFlag )
+}
+
+
+
+// =============================
+// ========= GAUNTLET ==========
+// =============================
+void function Training_Setup_Gauntlet( entity player )
+{
+ entity ogStart = GetEntByScriptName( "og_gauntlet_entrance_attract_spot" )
+ entity og = Training_SpawnOGPilot( ogStart )
+ Training_OG_Idles( ogStart )
+
+ TeleportPlayerAndBT( "startpoint_gauntlet_entrance" )
+}
+
+void function Training_Skipped_Gauntlet( entity player )
+{
+ FlagSet( "Gauntlet_FirstRun_Done" )
+ thread TrainingGauntlet_RemindPlayerAboutMobility( player )
+ thread TrainingGauntlet_CrouchHint( player )
+ thread TrainingGauntlet_SetsDifficulty( player )
+}
+
+void function Training_Gauntlet( entity player )
+{
+ entity og = GetOGPilot()
+
+ entity ref_og_gauntletStartPos = GetEntByScriptName( "og_near_gauntlet_start_pos" )
+ GauntletInfo trainingGauntlet = GetTrainingGauntlet()
+
+ // HACK
+ vector resultsBoardCenterPos = < -4899.15, 666.269, 107.187 >
+
+ if ( !Flag( "PlayerInGauntletEntryway" ) && !trainingGauntlet.isActive && !PlayerCanSeePos( player, resultsBoardCenterPos, true, 90 ) )
+ DisableGauntlet( trainingGauntlet ) // let a rushing player start the gauntlet
+
+ if ( !trainingGauntlet.isActive )
+ FlagWait( "PlayerInGauntletEntryway" )
+
+ if ( !trainingGauntlet.isActive )
+ CheckPoint_Silent()
+
+ waitthread Training_OG_Moves( ref_og_gauntletStartPos, ANIM_OG_LEANING_IDLE )
+
+ thread Training_Gauntlet_FirstRunDialogue( player, trainingGauntlet, ref_og_gauntletStartPos )
+ wait 1.0
+
+ EnableGauntlet( trainingGauntlet )
+ thread Training_Gauntlet_FirstRunGhostPlaybackStart( player, trainingGauntlet )
+ thread TrainingGauntlet_TeleportPlayerAtFinishLine( player )
+ thread TrainingGauntlet_RemindPlayerAboutMobility( player, "Gauntlet_FirstRun_All_VO_Finished" )
+ thread TrainingGauntlet_CrouchHint( player )
+ thread TrainingGauntlet_SetsDifficulty( player )
+
+ thread Training_Gauntlet_WaitForRequiredTime( player, ref_og_gauntletStartPos )
+ FlagWait( "Gauntlet_FirstRun_Done" )
+
+ if ( !trainingGauntlet.isActive )
+ CheckPoint_Silent()
+
+ Gauntlet_StopGhostPlayback( trainingGauntlet )
+
+ waitthread Training_Gauntlet_PostFirstRunDialogue( player, trainingGauntlet )
+}
+
+
+void function Training_Gauntlet_FirstRunDialogue( entity player, GauntletInfo gauntlet, entity ogIdleSpot )
+{
+ player.EndSignal( "OnDestroy" )
+
+ if ( !gauntlet.isActive )
+ {
+ // "Alright. Got a new gauntlet for you to run today."
+ waitthread Training_OG_ScriptedAnim( ogIdleSpot, "OG_gauntlet_start_A" )
+ }
+
+ if ( !gauntlet.isActive )
+ {
+ // "Par time is a minute-forty-five."
+ waitthread Training_OG_ScriptedAnim( ogIdleSpot, "OG_gauntlet_start_B" )
+ }
+
+ if ( !gauntlet.isActive )
+ {
+ // "Gotta do better than that to continue."
+ waitthread Training_OG_ScriptedAnim( ogIdleSpot, "OG_gauntlet_start_C" )
+ }
+
+ Objective_Set( "#TRAINING_OBJ_GAUNTLET_FIRSTRUN" )
+
+ //if ( gauntlet.isActive )
+ // Training_Gauntlet_OG_Creates_FirstRun_Ghost( file.ogPilot )
+
+ AddAnimEvent( file.ogPilot, "create_ghost", Training_Gauntlet_OG_Creates_FirstRun_Ghost )
+
+ // "Follow the ghost, or find your own path."
+ waitthread Training_OG_ScriptedAnim( ogIdleSpot, "OG_gauntlet_start_D" )
+ thread Training_OG_Idles( ogIdleSpot, "OG_gauntlet_start_endidle" )
+
+ DeleteAnimEvent( file.ogPilot, "create_ghost" )
+
+ float minDelayEnd = 5.0 + Time()
+ DialogueGroup firstRunGauntletLore = GetDialogueGroup( "firstRunGauntletLore" )
+ while ( !Flag( "Gauntlet_FirstRun_Done" ) && !firstRunGauntletLore.allPlayed )
+ {
+ wait 1
+
+ if ( !gauntlet.isActive )
+ continue
+
+ if ( Time() < minDelayEnd )
+ continue
+
+ string line = DialogueGroup_GetNextLine( firstRunGauntletLore )
+ PlayDialogue( line, player )
+ }
+
+ FlagSet( "Gauntlet_FirstRun_All_VO_Finished" )
+}
+
+void function Training_Gauntlet_OG_Creates_FirstRun_Ghost( entity og )
+{
+ entity player = file.player
+ if ( !IsValid( player ) )
+ return
+
+ player.Signal( "FirstRun_OG_Creates_Ghost" )
+}
+
+void function TrainingGauntlet_RemindPlayerAboutMobility( entity player, string waitFlag = "" )
+{
+ EndSignal( player, "OnDestroy" )
+ FlagEnd( "PlayerLeavingGauntlet" )
+
+ GauntletInfo trainingGauntlet = GetTrainingGauntlet()
+
+ //if ( waitFlag != "" && !Flag( waitFlag ) )
+ // FlagWait( waitFlag )
+
+ float nagInterval = 300 // 5 minutes
+ float nextNagTime = -1
+
+ float minSampleTime = 15.0
+ float lowWallrunningFrac = 0.05
+
+ bool hasSprinted = false
+
+ FlagWait( "Gauntlet_FirstRun_All_VO_Finished" )
+
+ while ( !Flag( "PlayerLeavingGauntlet" ) )
+ {
+ if ( !trainingGauntlet.isActive )
+ WaitSignal( player, "Gauntlet_RunStarted" )
+
+ float startTime = Time()
+ float samplePoints = 0
+ float samplesWallrunning = 0
+
+ while ( trainingGauntlet.isActive )
+ {
+ wait 0.1
+
+ if ( player.IsSprinting() && !hasSprinted )
+ hasSprinted = true
+
+ samplePoints++
+ if ( player.IsWallRunning() )
+ samplesWallrunning++
+
+ if ( (Time() - startTime) < minSampleTime )
+ continue
+
+ float wallrunningFrac = samplesWallrunning / samplePoints
+ printt( "wallrunningFrac:", wallrunningFrac )
+
+ // Has player done all the stuff we would want to remind them about?
+ if ( wallrunningFrac >= lowWallrunningFrac && hasSprinted )
+ break
+
+ if ( Time() >= nextNagTime )
+ {
+ if ( !hasSprinted )
+ thread TrainingGauntlet_SprintNag( player )
+ else if ( wallrunningFrac < lowWallrunningFrac )
+ thread TrainingGauntlet_WallrunNag( player, trainingGauntlet )
+
+ nextNagTime = Time() + nagInterval
+ }
+
+ break
+ }
+
+ if ( trainingGauntlet.isActive )
+ WaitSignal( player, "Gauntlet_RunStopped" )
+ }
+}
+
+void function TrainingGauntlet_SprintNag( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ float hintDuration = 10.0
+ float hintEndTime = Time() + hintDuration
+ DisplayOnscreenHint( player, "sprint_button_hint", hintDuration )
+
+ EndSignal( player, "DisplayingOnscreenHint" )
+
+ while ( Time() < hintEndTime && !player.IsSprinting() )
+ WaitFrame()
+
+ ClearOnscreenHint( player )
+}
+
+void function TrainingGauntlet_WallrunNag( entity player, GauntletInfo trainingGauntlet )
+{
+ EndSignal( player, "OnDestroy" )
+
+ //printt( "playing mobility nag" )
+
+ DisplayOnscreenHint( player, "gauntlet_wallrun_hint", 8.0 )
+
+ DialogueGroup gauntletHints_wallrun = GetDialogueGroup( "gauntletHints_wallrun" )
+ string line = DialogueGroup_GetNextLine( gauntletHints_wallrun )
+ waitthread PlayDialogue( line, player )
+
+ if ( !trainingGauntlet.isActive )
+ return
+
+ waitthread PlayDialogue( "og_gauntlet_hint_wallrun_capper", player )
+}
+
+void function TrainingGauntlet_CrouchHint( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ string checkFlag = "Gauntlet_PlayerInCrouchHintZone"
+ string nagTimer = "gauntlet_crouchHint"
+
+ TimerInit( nagTimer, 2.5 )
+
+ float waitTime = 1.0
+ bool hintShowing = false
+
+ while ( 1 )
+ {
+ wait waitTime
+
+ if ( !Flag( checkFlag ) )
+ continue
+
+ TimerReset( nagTimer )
+
+ while ( Flag( checkFlag ) )
+ {
+ wait waitTime
+
+ if ( !TimerCheck( "gauntlet_crouchHint" ) )
+ continue
+
+ if ( !hintShowing )
+ {
+ DisplayOnscreenHint( player, "crouch_hint" )
+ hintShowing = true
+ }
+ }
+
+ if ( hintShowing )
+ {
+ ClearOnscreenHint( player )
+ hintShowing = false
+ }
+ }
+}
+
+void function Training_Gauntlet_PostFirstRunDialogue( entity player, GauntletInfo gauntlet )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "Gauntlet_RunStarted" )
+
+ entity ref_og_gauntletResults = GetEntByScriptName( "og_ref_gauntlet_results_display" )
+
+ Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" )
+
+ waitthread Training_OG_Moves( ref_og_gauntletResults, "", 1.0 )
+
+ AddAnimEvent( file.ogPilot, "highlight_results_board_tip", Gauntlet_PostFirstRun_SetRandomTip )
+
+ // "Nice run! See the results board on the wall? You set a new Best Time."
+ // "Everyone has different strengths and weaknesses, so be sure to run this a few times with different weapons."
+ // "Look at the results board for more tips on how to improve."
+ waitthread Training_OG_ScriptedAnim( ref_og_gauntletResults, "OG_Gauntlet_return" )
+
+ DeleteAnimEvent( file.ogPilot, "highlight_results_board_tip" )
+}
+
+// update tip while OG is talking about the tips
+void function Gauntlet_PostFirstRun_SetRandomTip( entity og )
+{
+ entity player = file.player
+ if ( !IsValid( player ) )
+ return
+
+ GauntletInfo gauntlet = GetTrainingGauntlet()
+ Remote_CallFunction_Replay( player, "ScriptCallback_GauntletResultsDisplay_SetRandomTip", gauntlet.id )
+}
+
+void function Training_Gauntlet_FirstRunGhostPlaybackStart( entity player, GauntletInfo gauntlet )
+{
+ if ( !gauntlet.isActive )
+ WaitSignal( player, "Gauntlet_RunStarted", "FirstRun_OG_Creates_Ghost" )
+
+ thread Gauntlet_StartGhostPlayback( gauntlet, GHOST_NAME_FIRSTRUN, "#GAUNTLET_GHOST_NAME_FIRSTRUN" )
+}
+
+void function Training_Gauntlet_WaitForRequiredTime( entity player, entity ogIdleSpot )
+{
+ player.EndSignal( "OnDestroy" )
+
+ GauntletInfo gauntlet = GetTrainingGauntlet()
+
+ // "The first run of the day is always the toughest."
+ // "Too slow! I know you can do better. Give it another try."
+ DialogueGroup firstRunFailedGroup = GetDialogueGroup( "firstRunFailed" )
+
+ int failCount = 0
+ while ( 1 )
+ {
+ // keep setting this here because by default the gauntlet updates its random tip after each finished run
+ Remote_CallFunction_Replay( player, "ScriptCallback_TrainingGauntlet_ResultsDisplay_SetTip", gauntlet.id, 0 )
+
+ player.WaitSignal( "Gauntlet_RunStopped" )
+
+ wait 0.1 // HACK let the gauntlet struct get updated before checking it
+
+ TrainingGauntletStats_PostRunUpdate( player, gauntlet, 0 )
+
+ if ( !gauntlet.runFinished )
+ continue
+
+ failCount++
+
+ if ( gauntlet.bestTime <= TRAINING_GAUNTLET_MAX_TIME_TO_PROGRESS || failCount == NUM_GAUNTLET_FAILS_BEFORE_FORCED_PROGRESS )
+ {
+ if ( gauntlet.bestTime <= TRAINING_GAUNTLET_MAX_TIME_TO_PROGRESS )
+ printt( "Gauntlet moving on: beat required time" )
+ else if ( failCount == NUM_GAUNTLET_FAILS_BEFORE_FORCED_PROGRESS )
+ printt( "Gauntlet moving on: forced progress after", NUM_GAUNTLET_FAILS_BEFORE_FORCED_PROGRESS, "failures" )
+
+ break
+ }
+
+ wait 0.5 // let player get settled before VO starts
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "training_scr_gaunlet_fail_01" )
+
+ string firstRunFailedLine = DialogueGroup_GetNextLine( firstRunFailedGroup )
+ thread Training_OG_Talks_Leaning( firstRunFailedLine, ogIdleSpot, "", "", true )
+
+ Objective_Remind()
+ //DisplayOnscreenHint( player, "gauntlet_first_run_progression_hint", 9.0 )
+
+ if ( failCount == 2 )
+ thread DoSprintEnableDialogForGauntletIfNeeded_Thread( player, 3.5 )
+ }
+
+ TrainingGauntletPostRun_TryAchievements( player, gauntlet )
+
+ FlagSet( "Gauntlet_FirstRun_Done" )
+}
+
+void function DoSprintEnableDialogForGauntletIfNeeded_Thread( entity player, float waitTime )
+{
+ EndSignal( player, "OnDestroy" )
+ wait waitTime
+
+ if ( GetAutosprintEnabled() )
+ return
+
+ Remote_CallFunction_UI( player, "ScriptCallback_OpenAutosprintDialogForGauntlet" )
+}
+
+void function TrainingGauntlet_TeleportPlayerAtFinishLine( entity player )
+{
+ Signal( player, "Gauntlet_StopTeleportingPlayerAtFinishLine" )
+ EndSignal( player, "Gauntlet_StopTeleportingPlayerAtFinishLine" )
+ EndSignal( player, "OnDestroy" )
+
+ entity startEnt = GetEntByScriptName( "results_room_teleport_refA" )
+ entity endEnt = GetEntByScriptName( "results_room_teleport_refB" )
+ vector startPos = startEnt.GetOrigin()
+ vector endPos = endEnt.GetOrigin()
+
+ while ( 1 )
+ {
+ player.WaitSignal( "Gauntlet_PlayerHitFinishTrig" )
+
+ vector currentPos = player.GetOrigin()
+ float offsetX = currentPos.x - startPos.x
+ float offsetY = currentPos.y - startPos.y
+ float offsetZ = currentPos.z - startPos.z
+
+ float newPosX = endPos.x + offsetX
+ float newPosY = endPos.y + offsetY
+ float newPosZ = endPos.z + offsetZ
+
+ vector newPos = < newPosX, newPosY, newPosZ >
+
+ player.SetOrigin( newPos )
+ }
+}
+
+
+void function TrainingGauntlet_SetsDifficulty( entity player )
+{
+ if ( Flag( "PlayerLeavingGauntlet" ) )
+ return
+
+ FlagEnd( "PlayerLeavingGauntlet" )
+
+ player.EndSignal( "OnDestroy" )
+
+ GauntletInfo gauntlet = GetTrainingGauntlet()
+
+ bool diffSetOnce = false
+
+ while ( 1 )
+ {
+ WaitSignal( player, "Gauntlet_RunStopped" )
+
+ if ( file.gauntletMode )
+ return
+
+ wait 0.2 // HACK let the gauntlet struct and file variables get set up after the run
+
+ // extra wait for player to finish seeing their time, etc.
+ wait 2.0
+
+ if ( !gauntlet.runFinished )
+ continue
+
+ if ( !gauntlet.lastRunBestTime )
+ continue
+
+ int prevDiff = -1
+ if ( diffSetOnce )
+ prevDiff = GetConVarInt( "sp_difficulty" )
+
+ Training_SetDifficultyForGauntletTime( gauntlet.bestTime )
+ if ( GetConVarInt( "sp_difficulty" ) > prevDiff )
+ Training_AnnounceDifficultyForGauntletTime( player, 8.0 )
+
+ if ( !diffSetOnce )
+ diffSetOnce = true
+ }
+}
+
+void function Training_SetDifficultyForGauntletTime( float time )
+{
+ float time = file.trainingGauntletStats.bestTime
+ if ( time == -1 )
+ time = TRAINING_GAUNTLET_MAX_TIME_TO_PROGRESS
+
+ int difficulty = DIFFICULTY_EASY
+ //if ( time <= REC_DIFF_GAUNTLET_TIME_MASTER )
+ // difficulty = DIFFICULTY_MASTER
+ // else if
+ if ( time <= REC_DIFF_GAUNTLET_TIME_HARD )
+ difficulty = DIFFICULTY_HARD
+ else if ( time <= REC_DIFF_GAUNTLET_TIME_NORMAL )
+ difficulty = DIFFICULTY_NORMAL
+
+ printt( "GetDifficultyForGauntletTime: diff", difficulty, "for time", time )
+
+ file.trainingGauntletStats.recommendedDifficulty = difficulty
+
+ SetConVarInt( "sp_difficulty", difficulty )
+}
+
+void function Training_AnnounceDifficultyForGauntletTime( entity player, float displayTime = 5.0 )
+{
+ string hintAlias = "hint_diff_"
+ hintAlias += file.trainingGauntletStats.recommendedDifficulty.tostring()
+ DisplayOnscreenHint( player, hintAlias, displayTime )
+}
+
+
+// ======================================================
+// ========= GAUNTLET CHALLENGE (post 1st run) ==========
+// ======================================================
+void function Training_Setup_GauntletChallenge( entity player )
+{
+ entity ogStart = GetEntByScriptName( "og_ref_gauntlet_results_display" )
+ entity og = Training_SpawnOGPilot( ogStart )
+ Training_OG_Idles( ogStart )
+
+ TeleportPlayerAndBT( "playerstart_gauntlet_challenge" )
+}
+
+void function Training_Skipped_GauntletChallenge( entity player )
+{
+}
+
+void function Training_GauntletChallenge( entity player )
+{
+ GauntletInfo gauntlet = GetTrainingGauntlet()
+
+ thread TrainingGauntlet_TeleportPlayerAtFinishLine( player )
+
+ entity ref_og_gauntletLeaderboardPos = GetEntByScriptName( "og_near_leaderboard" )
+
+ // in gauntlet mode OG spawns here before player fade finishes, no need to move him
+ if ( !file.gauntletMode )
+ waitthread Training_OG_Moves( ref_og_gauntletLeaderboardPos, "" )
+
+ Gauntlet_ShowLeaderboard( gauntlet )
+
+ thread GauntletChallenge_GhostsThink( player, gauntlet, "GauntletChallenge_FirstGhostAppear", "PlayerLeavingGauntlet" )
+ thread GauntletChallenge_IntroDialogue( player, gauntlet, ref_og_gauntletLeaderboardPos )
+ thread GauntletChallege_RestartGauntletHint( player, gauntlet )
+
+ FlagWait( "ChallengeIntro_VO_Done" )
+
+ bool installRuiStarted = false
+ if ( !Training_IsGameFullyInstalled() )
+ Remote_CallFunction_Replay( player, "ScriptCallback_ShowInstallProgress", true )
+
+ entity ref_og_gauntletExitPos = GetEntByScriptName( "og_gauntlet_exit_pos" )
+ thread Training_GauntletChallenge_DialogueThink( player, gauntlet, ref_og_gauntletExitPos )
+ thread Training_OG_Moves( ref_og_gauntletExitPos, "OG_all_done_idle" )
+
+ wait 1.0
+ thread Training_LeaveGauntletThink( player, ref_og_gauntletExitPos )
+
+ FlagWait( "PlayerConfirmedGauntletExit" )
+
+ Remote_CallFunction_Replay( player, "ScriptCallback_ShowInstallProgress", false )
+
+ Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" )
+
+ DisableGauntlet( gauntlet )
+
+ FlagWait( "PlayerLeavingGauntlet" )
+
+ Signal( player, "Gauntlet_StopTeleportingPlayerAtFinishLine" )
+
+ if ( file.gauntletMode )
+ {
+ thread Training_PodOutro( player )
+ WaitForever() // stall normal progression
+ }
+ else
+ {
+ thread DisableWeaponDelayed( player, 0.3 ) // disable weapon so crosshairs aren't visible during white screen
+
+ entity destEnt = GetEntByScriptName( "startpoint_titanfall" )
+ waitthread PlayerAndOGTeleport_Fancy( player, destEnt.GetOrigin(), "og_titanfall_start_pos", destEnt.GetAngles() )
+
+ CheckPoint_Silent()
+ }
+}
+
+void function DisableWeaponDelayed( entity player, float delay )
+{
+ EndSignal( player, "OnDestroy" )
+
+ if ( delay > 0 )
+ wait delay
+
+ player.DisableWeapon()
+}
+
+void function GauntletChallege_RestartGauntletHint( entity player, GauntletInfo gauntlet )
+{
+ EndSignal( player, "OnDestroy" )
+
+ float displayTime = 5.0
+ float delayTime = 2.5
+
+ const int RUNS_BETWEEN_REMINDERS = 3
+ int runsSinceLastReminder = 3 // do reminder on first run
+
+ while ( 1 )
+ {
+ if ( !gauntlet.isActive )
+ WaitSignal( player, "Gauntlet_RunStarted" )
+
+ float displayEndTime = -1
+
+ // check if we should remind after run starts
+ if ( runsSinceLastReminder >= RUNS_BETWEEN_REMINDERS )
+ {
+ thread OnscreenHint_DisplayAfterDelay( player, "gauntlet_restart_hint", displayTime, delayTime )
+ displayEndTime = Time() + displayTime + delayTime
+ }
+
+ // wait for run to stop
+ table result = WaitSignal( player, "Gauntlet_RunStopped", "Gauntlet_ForceRestart" )
+ string signal = expect string( result.signal )
+
+ // clear initial hint if player cancelled the run before it would auto clear
+ if ( displayEndTime != -1 && Time() < displayEndTime )
+ ClearOnscreenHint( player )
+
+ // player went back through the starting gate?
+ if ( signal == "Gauntlet_RunStopped" && !gauntlet.runFinished )
+ {
+ // Don't care about how many runs between reminders here- if player does this it means they don't really get it yet
+ thread DisplayOnscreenHint( player, "gauntlet_restart_hint", displayTime )
+ runsSinceLastReminder = 0
+ }
+ // player used menu to restart?
+ else if ( signal == "Gauntlet_ForceRestart" )
+ {
+ runsSinceLastReminder = 0 // reset the counter when force restarted
+ }
+
+ if ( runsSinceLastReminder >= RUNS_BETWEEN_REMINDERS )
+ runsSinceLastReminder = 0
+ if ( gauntlet.runFinished ) // player restarted from menu or went back through the gate
+ runsSinceLastReminder++
+ }
+}
+
+void function GauntletChallenge_IntroDialogue( entity player, GauntletInfo gauntlet, entity ogRef )
+{
+ if ( file.gauntletMode )
+ {
+ thread GauntletChallengeModeOnly_IntroDialogue( player, gauntlet, ogRef )
+ return
+ }
+
+ player.EndSignal( "OnDestroy" )
+
+ if ( !gauntlet.isActive )
+ {
+ // "Now that you're warmed up: if you want a REAL challenge, you can race against other Pilot ghosts."
+ waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_A" )
+ }
+
+ player.Signal( "GauntletChallenge_FirstGhostAppear" )
+
+ if ( !gauntlet.isActive )
+ {
+ // "Word of warning, though- the Pilots who recorded these ghosts are the best in the SRS.
+ waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_B" )
+ }
+
+ if ( !gauntlet.isActive )
+ {
+ // "If you can beat them, you'll be halfway to being a real Pilot."
+ waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_C" )
+ }
+
+ if ( !gauntlet.isActive )
+ {
+ // "Go ahead and run the Gauntlet as much as you want."
+ waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_D" )
+ }
+
+ // "When you're done, I've got something special to show you."
+ waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_E" )
+
+ if ( !Training_IsGameFullyInstalled() )
+ {
+ Objective_Set( "#TRAINING_OBJ_GAUNTLET_CHALLENGE_INSTALLING" )
+ thread ChangeGauntletObjective_OnInstallComplete( player )
+ }
+ else
+ {
+ Objective_Set( "#TRAINING_OBJ_GAUNTLET_CHALLENGE" )
+ }
+
+ FlagSet( "ChallengeIntro_VO_Done" )
+}
+
+void function GauntletChallengeModeOnly_IntroDialogue( entity player, GauntletInfo gauntlet, entity ogRef )
+{
+ player.EndSignal( "OnDestroy" )
+
+ SetSignalDelayed( player, "GauntletChallenge_FirstGhostAppear", 0.25 )
+
+ wait 1.5 // let player teleport and fade in before starting to talk
+
+ if ( !gauntlet.isActive )
+ {
+ // "Go ahead and run the Gauntlet as much as you want."
+ waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_D", true )
+ }
+
+ // "When you're done, I've got something special to show you."
+ //waitthread Training_OG_ScriptedAnim( ogRef, "OG_Leaderboard_E" )
+
+ Objective_Set( "#TRAINING_OBJ_GAUNTLET_CHALLENGE" )
+
+ FlagSet( "ChallengeIntro_VO_Done" )
+}
+
+void function ChangeGauntletObjective_OnInstallComplete( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ while ( !Training_IsGameFullyInstalled() )
+ wait 1.0
+
+ Objective_Set( "#TRAINING_OBJ_GAUNTLET_CHALLENGE" )
+}
+
+void function GauntletChallenge_GhostsThink( entity player, GauntletInfo gauntlet, string signalWait, string endFlag )
+{
+ if ( Flag( endFlag ) )
+ return
+
+ player.EndSignal( "OnDestroy" )
+ gauntlet.signalEnt.EndSignal( "OnDestroy" )
+
+ FlagEnd( endFlag )
+
+ if ( !gauntlet.isActive )
+ WaitSignal( player, "Gauntlet_RunStarted", signalWait )
+
+ thread Gauntlet_ChallengeLeaderboardGhosts( player, gauntlet, "PlayerLeavingGauntlet" )
+}
+
+void function Training_GauntletChallenge_DialogueThink( entity player, GauntletInfo gauntlet, entity ogSpot )
+{
+ player.EndSignal( "OnDestroy" )
+
+ FlagWait( "ChallengeIntro_VO_Done" )
+
+ DialogueGroup clearedLeaderboard = GetDialogueGroup( "clearedLeaderboard" )
+ DialogueGroup defeatedGhost = GetDialogueGroup( "defeatedGhost" )
+ DialogueGroup notBestTime = GetDialogueGroup( "notBestTime" )
+ DialogueGroup newBestTime = GetDialogueGroup( "newBestTime" )
+
+ while ( 1 )
+ {
+ FlagClear( "Gauntlet_PlayingFeedbackVO" )
+
+ WaitSignal( player, "Gauntlet_RunStopped" )
+
+ wait 0.1 // HACK let the gauntlet struct get updated before checking
+
+ TrainingGauntletStats_PostRunUpdate( player, gauntlet, 1 )
+
+ if ( !gauntlet.runFinished )
+ continue
+
+ TrainingGauntletPostRun_TryAchievements( player, gauntlet )
+
+ FlagSet( "Gauntlet_PlayingFeedbackVO" )
+
+ string feedbackLine = ""
+
+ // SPECIAL- defeated a Hero Ghost
+ if ( gauntlet.lastRunDefeatedGhost && !gauntlet.allGhostsDefeated )
+ {
+ array<GauntletGhost> leaderboard = Gauntlet_GetLeaderboard( gauntlet )
+ GauntletGhost playerGhost = Gauntlet_GetPlayerGhost( gauntlet )
+
+ array<string> heroFileNames = [ GHOST_NAME_ALIAS_LASTIMOSA, GHOST_NAME_ALIAS_ANDERSON, GHOST_NAME_ALIAS_BRIGGS ]
+
+ GauntletGhost loserGhost
+ foreach ( ghost in leaderboard )
+ {
+ if ( playerGhost.duration <= ghost.duration && heroFileNames.contains( ghost.fileName ) )
+ {
+ loserGhost = ghost
+ break
+ }
+ }
+
+ switch ( loserGhost.fileName )
+ {
+ case GHOST_NAME_ALIAS_LASTIMOSA:
+ // "Hey - that was my best time! I must be getting slow."
+ feedbackLine = "og_gauntlet_unlocked_leaderboard_entry_og"
+ break
+
+ case GHOST_NAME_ALIAS_ANDERSON:
+ // "Heh. Can't wait to tell Anderson about that. Son of a bitch..."
+ feedbackLine = "og_gauntlet_unlocked_leaderboard_entry_anderson"
+ break
+
+ case GHOST_NAME_ALIAS_BRIGGS:
+ // "You just beat Commander Briggs. Might not stay that way for long though, she's very competitive."
+ feedbackLine = "og_gauntlet_unlocked_leaderboard_entry_briggs"
+ break
+ }
+
+ if ( feedbackLine != "" )
+ waitthread Training_OG_Talks( feedbackLine, ogSpot, "OG_all_done_talk", "OG_all_done_idle", true )
+ }
+
+ // SPECIAL - cleared leaderboard
+ if ( gauntlet.allGhostsDefeated && !clearedLeaderboard.allPlayed )
+ {
+ while ( !clearedLeaderboard.allPlayed && !gauntlet.isActive )
+ {
+ feedbackLine = DialogueGroup_GetNextLine( clearedLeaderboard )
+ waitthread Training_OG_Talks( feedbackLine, ogSpot, "OG_all_done_talk", "OG_all_done_idle", true )
+ }
+ }
+
+ // If we played a line by now, it was Special, so we don't want the generic ones below.
+ if ( feedbackLine != "" )
+ continue
+
+ // LAST RUN: DEFEATED GHOST (GENERIC)
+ if ( gauntlet.lastRunDefeatedGhost && !gauntlet.allGhostsDefeated )
+ {
+ feedbackLine = DialogueGroup_GetNextLine( defeatedGhost )
+ }
+ // LAST RUN: BEAT BEST TIME
+ else if ( gauntlet.lastRunBestTime )
+ {
+ feedbackLine = DialogueGroup_GetNextLine( newBestTime )
+ }
+ // LAST RUN: FAILED TO BEAT BEST TIME
+ else
+ {
+ feedbackLine = DialogueGroup_GetNextLine( notBestTime )
+ }
+
+ Assert( feedbackLine != "" )
+ waitthread Training_OG_Talks( feedbackLine, ogSpot, "OG_all_done_talk", "OG_all_done_idle", true )
+ }
+}
+
+void function TrainingGauntletPostRun_TryAchievements( entity player, GauntletInfo gauntlet )
+{
+ Assert( gauntlet.runFinished )
+
+ GauntletGhost andersonGhost = Gauntlet_GetGhostByFileName( gauntlet, GHOST_NAME_ALIAS_ANDERSON )
+ if ( gauntlet.lastRunTime < andersonGhost.duration )
+ {
+ // Achievement - Beat Pilot Anderson's gauntlet ghost recorder time
+ UnlockAchievement( player, achievements.GAUNTLET_BEAT_ANDERSON )
+ }
+
+ GauntletGhost playerGhost = Gauntlet_GetPlayerGhost( gauntlet )
+ int playerLeaderboardPos = Gauntlet_GetLeaderboardPosition_ForGhostID( gauntlet, playerGhost.id )
+ if ( playerLeaderboardPos <= 2 )
+ {
+ // Achievement - Get a top-3 spot on the Gauntlet scoreboard
+ UnlockAchievement( player, achievements.GAUNTLET_TOPTHREE )
+ }
+}
+
+void function Training_LeaveGauntletThink( entity player, entity ogSpot )
+{
+ string gauntletExitConversation = "Gauntlet_Exit"
+ if ( file.gauntletMode )
+ gauntletExitConversation = "Gauntlet_Exit_TechTest"
+
+ GauntletInfo trainingGauntlet = GetTrainingGauntlet()
+
+ // NOTE conversation callbacks need this defined
+ file.animref_leaveGauntlet = ogSpot
+ AddConversationCallback( gauntletExitConversation, ConvoCallback_TrainingExit )
+ AddConversationCallback( "Titanfall_Intro", ConvoCallback_TitanfallIntro)
+
+ int numTimesInitiated = 0
+
+ while ( !Flag( "PlayerConfirmedGauntletExit" ) )
+ {
+ FlagWait( "Gauntlet_PlayerInExitZone" )
+
+ FlagWaitClear( "Gauntlet_PlayingFeedbackVO" )
+
+ if ( !Flag( "Gauntlet_PlayerInExitZone" ) )
+ continue
+
+ if ( !Training_IsGameFullyInstalled() )
+ {
+ waitthread Gauntlet_GameNotFullyInstalled_Response( player, ogSpot )
+ wait 0.1 // HACK make sure we always wait before continuing
+ continue
+ }
+
+ numTimesInitiated++
+
+ // All done with the gauntlet?
+ waitthread Training_OG_ScriptedAnim( ogSpot, "OG_all_done", true )
+ Training_OG_Idles( ogSpot, "OG_all_done_idle", true )
+
+ // if player rushed to the gauntlet during the anim, don't wait for conversation
+ if ( trainingGauntlet.isActive )
+ continue
+
+ // hint if player can't figure out how to conversate
+ bool displayedOnscreenHint = false
+ if ( numTimesInitiated > 1 && !Flag( "PlayerUsedConversationInterface" ) )
+ {
+ thread OnscreenHint_DisplayAfterDelay( player, "conversation_hint", 5.0, 1.5 )
+ displayedOnscreenHint = true
+ }
+
+ // interactive dialogue: Gauntlet Exit
+ FlagClear( "GauntletExitConvo_FinishedResponse" )
+ thread PlayerConversation( gauntletExitConversation, player, file.ogPilot )
+
+ table result = WaitSignal( player, "ConversationEnded", "PlayerMadeSelection", "Gauntlet_RunStarted" )
+ string sig = expect string( result.signal )
+
+ // bug 202299: Also check flag for player confirmed gauntlet exit
+ // - player can confirm exit, but if player starts a gauntlet run just before the response line ends, convo can "cancel" causing a prog break here
+ if ( sig == "Gauntlet_RunStarted" )
+ {
+ // HACK- wait a bit to make sure player didn't confirm exit before the conversation ended due to gauntlet run starting
+ wait 0.5
+
+ if ( !Flag( "PlayerConfirmedGauntletExit" ) )
+ {
+ StopConversationNow( player )
+ continue
+ }
+ }
+
+ /*
+ #if DEV
+ if ( sig == "Gauntlet_RunStarted" && Flag( "PlayerConfirmedGauntletExit" ) )
+ printt( "BUG CONDITION DEFEATED" )
+ #endif
+ */
+
+ if ( sig == "PlayerMadeSelection" )
+ FlagSet( "PlayerUsedConversationInterface" )
+
+ if ( displayedOnscreenHint )
+ ClearOnscreenHint( player )
+
+ // wait for callback function to finish doing its thing
+ FlagWait( "GauntletExitConvo_FinishedResponse" )
+
+ if ( Flag( "PlayerConfirmedGauntletExit" ) )
+ break
+
+ FlagWaitClear( "Gauntlet_PlayerInExitZone" )
+
+ StopConversationNow( player )
+ }
+
+ if ( file.gauntletMode )
+ {
+ GauntletMode_Finished( player )
+ }
+ else
+ {
+ // interactive dialogue: Titanfall Intro
+ FlagClear( "TitanfallIntroConvo_FinishedResponse" )
+ waitthread PlayerConversation( "Titanfall_Intro", player, file.ogPilot )
+
+ FlagWait( "TitanfallIntroConvo_FinishedResponse" )
+ }
+
+ wait 0.2
+
+ FlagSet( "PlayerLeavingGauntlet" )
+}
+
+void function Gauntlet_GameNotFullyInstalled_Response( entity player, entity ogRef )
+{
+ EndSignal( player, "OnDestroy" )
+
+ float progress = GetGameFullyInstalledProgress()
+ printt( "Game is not fully installed- cannot continue past Gauntlet. Progress:", progress, "/ 1.0" )
+
+ DisplayOnscreenHint( player, "gauntlet_install_hint" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ ClearOnscreenHint( player )
+ }
+ )
+
+ if ( TimerCheck( "installWaitComment" ) )
+ {
+ DialogueGroup installWait = GetDialogueGroup( "installWait" )
+ string line = DialogueGroup_GetNextLine( installWait )
+ waitthread Training_OG_Talks( line, ogRef, "OG_all_done_talk", "OG_all_done_idle", true )
+
+ TimerReset( "installWaitComment" )
+ }
+
+ while ( Flag( "Gauntlet_PlayerInExitZone" ) && !Training_IsGameFullyInstalled() )
+ {
+ wait 0.25
+ }
+}
+
+void function ConvoCallback_TrainingExit( int choice )
+{
+ EndSignal( file.player, "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( )
+ {
+ if ( IsValid( file.player ) )
+ FlagSet( "GauntletExitConvo_FinishedResponse" )
+ }
+ )
+
+ printt( "TRAINING EXIT CALLBACK: player chose", choice )
+ Assert( choice >= 0 && choice <= 2, "Nothing set up for Training Exit convo choice " + choice )
+
+ // prompt timed out
+ if ( choice == 0 )
+ return
+
+ Signal( file.player, "PlayerMadeSelection" )
+
+ // player chooses to stay
+ if ( choice == 2 )
+ {
+ waitthread Training_OG_ScriptedAnim( file.animref_leaveGauntlet, "OG_all_done_respond", true )
+ Training_OG_Idles( file.animref_leaveGauntlet, "OG_all_done_idle", true )
+ return
+ }
+
+ // player chooses to leave
+ FlagSet( "PlayerConfirmedGauntletExit" )
+ printt( "PLAYER CONFIRMED GAUNTLET EXIT" )
+
+ // Good. You're gonna like this.
+ // It's time you learned the other half of being a Pilot: the Titan. Let's go call one in.
+ string anim = "OG_gonna_like_this"
+ if ( file.gauntletMode )
+ {
+ // Good. You're gonna like this.
+ // Let's get back to the real world.
+ anim = "OG_gonna_like_this_tech_test_end"
+ }
+
+ waitthread Training_OG_ScriptedAnim( file.animref_leaveGauntlet, anim, true )
+ Training_OG_Idles( file.animref_leaveGauntlet, "OG_gonna_like_this_endidle", true )
+}
+
+void function ConvoCallback_TitanfallIntro( int choice )
+{
+ EndSignal( file.player, "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( )
+ {
+ if ( IsValid( file.player ) )
+ FlagSet( "TitanfallIntroConvo_FinishedResponse" )
+ }
+ )
+
+ printt( "TITANFALL INTRO CALLBACK: player chose", choice )
+ Assert( choice >= 0 && choice <= 2, "Nothing set up for Titanfall Intro convo choice " + choice )
+
+ // It's only a simulation, Cooper. It's not the real thing.
+ // But first- We're gonna need a little more space
+ string responseAnim = "OG_gonna_like_this_respond_A"
+ if ( choice == 2 )
+ {
+ // That's the spirit.
+ // But first- We're gonna need a little more space
+ responseAnim = "OG_gonna_like_this_respond_B"
+ }
+
+ thread Training_OG_ScriptedAnim( file.animref_leaveGauntlet, responseAnim, true )
+ float duration = GetOGPilot().GetSequenceDuration( responseAnim )
+ wait duration - 1.0
+
+ //Training_OG_Idles( file.animref_leaveGauntlet, "OG_gonna_like_this_endidle", true )
+}
+
+
+// runType: 0 = before beating required time; 1 = in challenge mode
+void function TrainingGauntletStats_PostRunUpdate( entity player, GauntletInfo gauntlet, int runType )
+{
+ Assert( !gauntlet.isActive, "Can't update gauntlet stats reliably while gauntlet is active." )
+
+ // restarted gauntlet from menu
+ if ( !gauntlet.runFinished )
+ {
+ file.trainingGauntletStats.numRestarts++
+ printt( "training gauntlet stats updated: numRestarts", file.trainingGauntletStats.numRestarts )
+
+ SendTrainingGauntletStats( player )
+ return
+ }
+
+ if ( gauntlet.lastRunBestTime )
+ {
+ file.trainingGauntletStats.bestTime = gauntlet.bestTime
+ printt( "training gauntlet stats updated: bestTime", file.trainingGauntletStats.bestTime )
+ }
+
+ if ( runType == 0 )
+ {
+ file.trainingGauntletStats.numRunsBeforeBeatRequiredTime++
+ printt( "training gauntlet stats updated: numRunsBeforeBeatRequiredTime", file.trainingGauntletStats.numRunsBeforeBeatRequiredTime )
+
+ if ( gauntlet.bestTime > TRAINING_GAUNTLET_MAX_TIME_TO_PROGRESS )
+ {
+ // failed to get required time
+ SendTrainingGauntletStats( player )
+ return
+ }
+ else
+ {
+ file.trainingGauntletStats.didBeatRequiredTime = true
+ printt( "training gauntlet stats updated: didBeatRequiredTime", file.trainingGauntletStats.didBeatRequiredTime )
+ }
+ }
+ else if ( runType == 1 )
+ {
+ file.trainingGauntletStats.numChallengeRuns++
+ printt( "training gauntlet stats updated: numChallengeRuns", file.trainingGauntletStats.numChallengeRuns )
+ }
+
+ SendTrainingGauntletStats( player )
+}
+
+
+void function SendTrainingGauntletStats( entity player )
+{
+ printt("======== TRAINING GAUNTLET STATS =========" )
+ printt( "numRestarts:", file.trainingGauntletStats.numRestarts )
+ printt( "numRunsBeforeBeatRequiredTime:", file.trainingGauntletStats.numRunsBeforeBeatRequiredTime )
+ printt( "didBeatRequiredTime:", file.trainingGauntletStats.didBeatRequiredTime )
+ printt( "numChallengeRuns:", file.trainingGauntletStats.numChallengeRuns )
+ printt( "bestTime:", file.trainingGauntletStats.bestTime )
+ printt("========= END TRAINING GAUNTLET STATS ==========" )
+ SendTrainingGauntletStatsToBackend(
+ player,
+ file.trainingGauntletStats.didBeatRequiredTime ? file.trainingGauntletStats.numRunsBeforeBeatRequiredTime : 0,
+ file.trainingGauntletStats.numChallengeRuns,
+ file.trainingGauntletStats.bestTime
+ )
+}
+
+
+bool function Training_IsGameFullyInstalled()
+{
+ #if DEV
+ if ( INSTALL_DELAY_TEST )
+ return file.fakeInstallDone
+ #endif
+
+ return IsGameFullyInstalled()
+}
+
+#if DEV
+void function setfakeinstalldone( bool isDone )
+{
+ file.fakeInstallDone = isDone
+}
+#endif
+
+
+// ===================================
+// ============ TITANFALL ============
+// ===================================
+void function Training_Setup_Titanfall( entity player )
+{
+ entity ogRef_titanfall = GetEntByScriptName( "og_titanfall_start_pos" )
+ entity og = Training_SpawnOGPilot( ogRef_titanfall )
+ Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle" )
+
+ player.DisableWeapon() // player weapon is disabled before teleport to Titanfall area starts
+
+ TeleportPlayerAndBT( "startpoint_titanfall" )
+}
+
+void function Training_Skipped_Titanfall( entity player )
+{
+ player.SetExtraWeaponMods( ["training_low_ammo_disable"] )
+ SetWeaponHUDEnabled( player, false )
+}
+
+void function Training_Titanfall( entity player )
+{
+ thread Titanfall_EnableWeaponDelayed( player, 0.9 )
+
+ thread TakeAmmoFromPlayerASAP( player )
+ thread Titanfall_SpecialWeaponRemove( player )
+
+ entity og = GetOGPilot()
+ entity ogRef_titanfall = GetEntByScriptName( "og_titanfall_start_pos" )
+ Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle" )
+
+ vector twinRefSpawnOrg = TitanfallGlitch_WorldChange_GetOtherWorldPos( ogRef_titanfall.GetOrigin(), true )
+ entity ogRef_titanfall_twin = CreateScriptMover( twinRefSpawnOrg, ogRef_titanfall.GetAngles() )
+ entity ogTwin = Training_SpawnOGTwin( ogRef_titanfall_twin )
+ Training_NPC_Idles_Sitting( ogTwin, ogRef_titanfall_twin, "OG_first_titan_idle" )
+
+ OnThreadEnd(
+ function() : ( player, ogTwin, ogRef_titanfall_twin )
+ {
+ if ( IsValid( player ) )
+ player.UnfreezeControlsOnServer()
+
+ if ( IsValid( ogTwin ) )
+ ogTwin.Destroy()
+
+ if ( IsValid( ogRef_titanfall_twin ) )
+ ogRef_titanfall_twin.Destroy()
+ }
+ )
+
+ //testglitch()
+ //wait 1000000
+
+ thread Titanfall_BT_Think( player, 6.5 )
+
+ wait 2.0
+
+ // "That's my partner, BT. He's a Vanguard-class."
+ // "Homegrown Militia technology."
+ // "The first Titan chassis we designed ourselves. One we didn't have to steal from the IMC."
+ waitthread Training_OG_ScriptedAnim( ogRef_titanfall, "OG_Partner_BT", true )
+ Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle", true )
+
+ wait 1.1 // take a breath between lines
+
+ thread Training_EnableTitanfallAndNag( player, ogRef_titanfall, 25.0 )
+
+ // "Go ahead Rifleman, call in your first Titan."
+ Titanfall_ResetHintVOTimer() // in case player calls in Titan during this line
+ waitthread Training_OG_ScriptedAnim( ogRef_titanfall, "OG_First_Titan", true )
+ Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle", true )
+
+ FlagWait( "PlayerStartedTitanfall" )
+ // Don't start OG's next line until the nag is done, to avoid overlap
+ wait Titanfall_GetHintVORemainingDuration()
+
+ // "Look up, to the sky- there he is."
+ waitthread Training_OG_ScriptedAnim( ogRef_titanfall, "OG_Look_Up", true )
+ Training_OG_Idles_Sitting( ogRef_titanfall, "OG_first_titan_idle", true )
+
+ thread Titanfall_QuickdeathCustomResetPlayerPos( player, file.playerTitanCallInPos, "PodOutroStarted" )
+
+ // wait for titan to appear
+ while ( !GetPlayerTitanInMap( player ) )
+ wait 0.1
+
+ entity titan = GetPlayerTitanInMap( player )
+
+ // "glitch" interrupts titan drop
+ FlagWait( "TitanfallGlitchStart" )
+
+ PlayMusic( "music_training_01_glitch" )
+
+ // Glitch ends when this function ends
+ float totalGlitchTime = 2.9
+ float glitchEndTime = Time() + totalGlitchTime
+
+ wait 1.0
+ player.FreezeControlsOnServer()
+
+ thread Training_OG_ScriptedAnim( ogRef_titanfall, "OG_Pulling_You_Out", true )
+
+ wait glitchEndTime - Time()
+
+ if ( IsValid( titan ) )
+ titan.Destroy()
+
+ if ( IsValid( file.titanTwin ) )
+ file.titanTwin.Destroy()
+}
+
+void function Titanfall_EnableWeaponDelayed( entity player, float delay )
+{
+ EndSignal( player, "OnDestroy" )
+
+ if ( delay > 0 )
+ wait delay
+
+ player.EnableWeapon()
+ player.SetExtraWeaponMods( ["training_low_ammo_disable"] )
+ SetWeaponHUDEnabled( player, false )
+}
+
+void function Titanfall_SpecialWeaponRemove( entity player )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ int numWeaponsStart = weapons.len()
+
+ foreach ( equippedWeapon in weapons )
+ {
+ string equippedWeaponClassName = equippedWeapon.GetWeaponClassName()
+ if ( equippedWeaponClassName == "mp_weapon_lstar" ) // LSTAR display blinks annoyingly if ammo is gone
+ {
+ // give player a weapon if we are going to take their only weapon
+ if ( numWeaponsStart <= 1 )
+ player.GiveWeapon( "mp_weapon_semipistol" )
+
+ player.TakeWeapon( equippedWeaponClassName )
+
+ break
+ }
+ }
+}
+
+void function Titanfall_BT_Think( entity player, float getUpDelay = 0.0 )
+{
+ EndSignal( player, "OnDestroy" )
+
+ entity spawnRef = GetEntByScriptName( "titanfall_bt_spawn_ref" )
+ vector spawnOrg = spawnRef.GetOrigin()
+ vector spawnAng = spawnRef.GetAngles()
+
+ // create BT
+ TitanLoadoutDef loadout = GetTitanLoadoutForPlayer( player )
+ entity bt = CreateNPCTitan( loadout.setFile, player.GetTeam(), spawnOrg, spawnAng, loadout.setFileMods )
+ bt.ai.titanSpawnLoadout = loadout
+ bt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+ DispatchSpawn( bt )
+
+ entity animref = CreateScriptMover( spawnOrg, spawnAng )
+
+ OnThreadEnd(
+ function() : ( bt, animref )
+ {
+ if ( IsValid( bt ) )
+ bt.Destroy()
+
+ if ( IsValid( animref ) )
+ animref.Destroy()
+ }
+ )
+
+ bt.EnableNPCFlag( NPC_IGNORE_FRIENDLY_SOUND )
+ bt.SetEfficientMode( true )
+ bt.SetTouchTriggers( false )
+ bt.SetNoTarget( true )
+ bt.UnsetUsable()
+ bt.SetInvulnerable()
+ bt.EnableRenderAlways()
+
+ bt.SetTitle( "#NPC_BT_NAME" )
+ ShowName( bt )
+
+ DisableTitanfallForLifetimeOfEntityNearOrigin( bt, bt.GetOrigin(), TITANHOTDROP_DISABLE_ENEMY_TITANFALL_RADIUS )
+
+ // HACK this anim pops if it's played off of the animref,
+ // but looks good if played off BT before using the animref for the rest
+ thread PlayAnim( bt, "bt_training_scripted_kneel_idle", bt )
+
+ if ( getUpDelay > 0 )
+ wait getUpDelay
+
+ waitthread PlayAnim( bt, "bt_training_scripted_kneel2stand", animref )
+ thread PlayAnim( bt, "bt_training_scripted_stand_idle", animref )
+
+ FlagWait( "PodOutroStarted" )
+}
+
+void function Titanfall_QuickdeathCustomResetPlayerPos( entity player, vector titanCallInPos, string endFlag )
+{
+ FlagEnd( endFlag )
+
+ entity resetEnt = GetEntByScriptName( "ref_titanfall_quickdeath_reset" )
+ vector resetPos = resetEnt.GetOrigin()
+
+ while ( 1 )
+ {
+ WaitSignal( player, "QuickDeath" )
+
+ vector angToCallInPos = VectorToAngles( titanCallInPos - resetPos )
+ float viewTiltUpAngX = -10.0 // goose the view up a little
+ vector viewAng = <viewTiltUpAngX, angToCallInPos.y, 0>
+
+ player.p.quickDeathOrigin = resetPos
+ player.p.quickDeathAngles = viewAng
+ }
+}
+
+void function Training_EnableTitanfallAndNag( entity player, entity ogRef, float nagDelay )
+{
+ EndSignal( player, "OnDestroy" )
+
+ SetGlobalNetBool( "trainingTitanfallEnabled", true )
+ AddClientCommandCallback( "ClientCommand_TrainingRequestedTitanfall", Training_RequestTitanfall )
+
+ float onscreenHintDisplayTime = nagDelay * 0.5
+ thread OnscreenHint_DisplayAfterDelay( player, "titanfall_hint", onscreenHintDisplayTime, 3.5 ) // extra delay on first screen prompt so pros can call it in
+
+ Objective_SetSilent( "#TRAINING_OBJ_CALL_TITAN" )
+
+ // "Titan's ready. Call it in."
+ // "Titan's ready to drop. On your mark."
+ array<string> titanfallNags = [ "og_titanfall_nag_1", "og_titanfall_nag_2" ]
+
+ DialogueGroup callInTitan = GetDialogueGroup( "callInTitan" )
+ while ( !Flag( "PlayerStartedTitanfall" ) )
+ {
+ FlagWaitWithTimeout( "PlayerStartedTitanfall", nagDelay )
+ if ( Flag( "PlayerStartedTitanfall" ) )
+ break
+
+ thread OnscreenHint_DisplayAfterDelay( player, "titanfall_hint", onscreenHintDisplayTime, 2.5 )
+
+ string line = DialogueGroup_GetNextLine( callInTitan )
+ Titanfall_ResetHintVOTimer()
+ waitthread Training_OG_Talks_Sitting( line, ogRef, "OG_first_titan_talk", "OG_first_titan_idle" )
+ }
+
+ Objective_SetSilent( "#TRAINING_OBJ_DEFAULT" )
+ ClearOnscreenHint( player )
+}
+
+
+// CUSTOM HOTDROP
+bool function Training_RequestTitanfall( entity player, array<string> args )
+{
+ Training_ReplacementTitan( player ) //Separate function because other functions will call ReplacementTitan
+ return true
+}
+
+bool function Training_ReplacementTitan( entity player )
+{
+ Assert( IsAlive( player ) )
+
+ Assert ( !GetPlayerTitanInMap( player ) )
+
+ Point spawnPoint = GetTitanReplacementPoint( player, false )
+ vector origin = spawnPoint.origin
+ vector angles = spawnPoint.angles
+
+ file.playerTitanCallInPos = origin
+
+ FlagSet( "PlayerStartedTitanfall" )
+ SetGlobalNetBool( "trainingTitanfallEnabled", false )
+
+ thread Training_CreateTitanForPlayerAndHotdrop( player, origin, angles )
+
+ return true
+}
+
+#if DEV
+void function testglitch()
+{
+ thread Training_CreateTitanForPlayerAndHotdrop( GetPlayerArray()[0], <2048, -2852, -349>, <0,0,0> )
+}
+#endif
+
+void function Training_CreateTitanForPlayerAndHotdrop( entity player, vector spawnOrg, vector spawnAng )
+{
+ Assert( IsValid( player ) )
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( !IsValid( player ) )
+ return
+
+ player.ClearHotDropImpactTime()
+ }
+ )
+
+ player.EndSignal( "OnDestroy" )
+
+ vector origin = spawnOrg
+ vector angles
+ if ( spawnAng != < 0.0, 0.0, 0.0 > )
+ angles = spawnAng
+ else
+ angles = VectorToAngles( FlattenVector( player.GetViewVector() ) * -1 ) // face the player
+
+ printt( "Dropping replacement titan at " + origin + " with angles " + angles )
+
+ player.Signal( "CalledInReplacementTitan" )
+
+ int playerTeam = player.GetTeam()
+
+ // spawn the Titan that will drop
+ TitanLoadoutDef loadout = GetTitanLoadoutForPlayer( player )
+ entity titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, origin, angles )
+ DispatchSpawn( titan )
+ titan.EndSignal( "OnDeath" )
+
+ titan.SetSkin( 2 ) // "Sarah Briggs" Vanguard skin
+ titan.SetTitle( "#NPC_BT_SPARE_NAME" )
+ HideName( titan )
+ TakeAllWeapons( titan )
+ titan.SetEfficientMode( true )
+ titan.SetTouchTriggers( false )
+ titan.SetNoTarget( true )
+ titan.UnsetUsable() // Disable titan embark
+
+ titan.Hide()
+
+ OnThreadEnd(
+ function() : ( titan )
+ {
+ if ( !IsValid( titan ) )
+ return
+
+ // removed so that model highlight always works for you autotitan
+ // titan.DisableRenderAlways()
+ }
+ )
+
+ // based on "at_hotdrop_drop_2knee_turbo"
+ string animation = "bt_hotdrop_glitch_descent"
+ string postButtonPressSFX = "training_scr_titan_glitch_button_press"
+ string hotdropToGlitchSFX = "training_anim_glitch_scene"
+
+ EmitSoundOnEntity( player, postButtonPressSFX )
+
+ float hotDropAnimDuration = titan.GetSequenceDuration( animation )
+
+ float extraSpawnDelay = 1.5 // bit of extra padding time for "Look up, to the sky" VO to sink in
+ extraSpawnDelay += Titanfall_GetHintVORemainingDuration()
+
+ // Glitched hotdrop anim created by trimming the end from normal hotdrop anim (glitch starts just before the Titan hits the ground)
+ // - add this little bit back so the timer looks "correct" (longer than actual anim duration)
+ float arrivalTimerDuration = 0.7
+ arrivalTimerDuration += (hotDropAnimDuration + extraSpawnDelay) // now add the rest of the normal delays before arrival
+ thread Training_DrawReplacementTitanLocation( player, origin, arrivalTimerDuration )
+
+ wait extraSpawnDelay
+
+ titan.Show()
+ ShowName( titan )
+
+ Attachment result = titan.Anim_GetAttachmentAtTime( animation, "OFFSET", (hotDropAnimDuration - 0.1) )
+ vector maxs = titan.GetBoundingMaxs()
+ vector mins = titan.GetBoundingMins()
+ int mask = titan.GetPhysicsSolidMask()
+ origin = ModifyOriginForDrop( origin, mins, maxs, result.position, mask )
+
+ titan.SetInvulnerable() //Make Titan invulnerable until bubble shield is up. Cleared in OnTitanHotdropImpact
+ titan.EnableRenderAlways()
+
+ int teamNum = TEAM_UNASSIGNED
+ if ( IsValid( player ) )
+ teamNum = player.GetTeam()
+
+ vector fwdToPlayer = player.GetOrigin() - origin
+ vector facingAngles = VectorToAngles( fwdToPlayer )
+ vector facingAngles2D = <facingAngles.x, facingAngles.y, 0>
+ angles = facingAngles2D
+
+ //FadeOutSoundOnEntity( player, postButtonPressSFX, 1.0 )
+ EmitSoundOnEntity( player, hotdropToGlitchSFX )
+
+ thread PlayAnimTeleport( titan, animation, origin, angles )
+ wait hotDropAnimDuration - 0.1
+
+ titan.SetTouchTriggers( true )
+
+ thread TitanfallGlitch( player, titan, origin, angles )
+}
+
+void function Titanfall_ResetHintVOTimer()
+{
+ file.titanfallNagStartTime = Time()
+}
+
+float function Titanfall_GetHintVORemainingDuration()
+{
+ if ( file.titanfallNagStartTime == -1 )
+ return 0
+
+ float duration = TITANFALL_NAG_DURATION - (Time() - file.titanfallNagStartTime)
+ if ( duration < 0 )
+ duration = 0
+
+ return duration
+}
+
+bool function Titanfall_IsHintVOStillPlaying()
+{
+ return Titanfall_GetHintVORemainingDuration() >= 0
+}
+
+void function TitanfallGlitch( entity player, entity titan, vector origin, vector angles )
+{
+ if ( Flag( "PodOutroStarted" ) )
+ return
+ FlagEnd( "PodOutroStarted" )
+
+ player.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDestroy" )
+
+ titan.Signal( "glitch_start" )
+ FlagSet( "TitanfallGlitchStart" )
+
+ if ( IsValid( file.titanTwin ) )
+ file.titanTwin.Destroy()
+
+ entity titanTwin = CreatePropScript( BUDDY_MODEL, titan.GetOrigin(), titan.GetAngles() )
+ file.titanTwin = titanTwin
+ vector twinOrigin = TitanfallGlitch_WorldChange_GetOtherWorldPos( origin, true )
+
+ array<entity> sceneTitans = [ titan, titanTwin ]
+
+ // idle on last frame of descent anim
+ titanTwin.Anim_Stop()
+ titan.Anim_Stop()
+ string endIdleAnim = GetDefaultTitanfallGlitchAnim()
+ thread PlayAnimTeleport( titanTwin, endIdleAnim, twinOrigin, angles )
+ thread PlayAnimTeleport( titan, endIdleAnim, origin, angles )
+
+ // DEPRECATED no more glitchy anims
+ // do this on the hotdropping titan to blend out of the hotdrop better (but makes the proxy on the client out of sync)
+ // - in the future, need to play all the hotdrop anims on the proxy as well, to let it blend the same
+ //thread PlayAnim( titan, endIdleAnim, origin, angles, 0.1 )
+
+ // START GLITCH SEQUENCE
+
+ // setup for client extra flicker
+ titan.Hide()
+ titanTwin.Hide()
+ int eHandle_titan = titan.GetEncodedEHandle()
+ int eHandle_twin = titanTwin.GetEncodedEHandle()
+ Remote_CallFunction_Replay( player, "ScriptCallback_TitanfallGlitch_ExtraFlicker", eHandle_titan )
+ Remote_CallFunction_Replay( player, "ScriptCallback_TitanfallGlitch_ExtraFlicker", eHandle_twin, true )
+ SetGlobalNetBool( "titanGlitch_extraFlicker", true ) // makes him flicker until the world starts changing
+
+ // DEPRECATED Not doing the huge anim moves might work better for the glitch moment, trying it out
+ //thread TitanfallGlitch_CycleTitanGlitchAnims_OnWorldChange( player, titan, titanTwin, origin, twinOrigin, angles )
+
+ float screenFXTime = 4.5
+ float screenFXStartDelay = 0.1
+ thread StartGlitchScreenFX_Delayed( player, screenFXTime, screenFXStartDelay )
+
+ // FIRST IMPACT
+ float firstShakeDuration = 3.0
+
+ float shakeAmplitude = 14.0
+ float screenBlurFrac = 0.25
+ SimpleScreenShake( player, firstShakeDuration, shakeAmplitude, screenBlurFrac )
+
+ // juice it with extra rumble
+ // note- this is not juicing it as much as I would expect... even cranking the amplitude doesn't really work that well
+ float rumbleAmplitude = 50.0
+ float rumbleFrequency = 170
+ float rumbleDuration = 4.5
+ CreateShakeRumbleOnly( player.GetOrigin(), rumbleAmplitude, rumbleFrequency, rumbleDuration )
+
+ // let the titan idle for a moment
+ float postDescentIdleTime = 1.0
+ wait postDescentIdleTime
+
+ float firstShakeDuration_remaining = firstShakeDuration - postDescentIdleTime
+ // DEPRECATED trying without world changes
+ //thread TitanfallGlitch_PlayerWorldChange( player, firstShakeDuration_remaining * 0.9, 0.0 )
+
+ wait firstShakeDuration_remaining
+
+ /* DEPRECATED don't need smaller shakes now that the scene only lasts a short time
+ printt( "FIRST IMPACT DONE")
+
+ // LOOPING SMALLER IMPACTS
+ float shakeDuration_min = 2.5
+ float shakeDuration_max = 3.0
+ shakeAmplitude = 5.5
+ screenBlurFrac = 0.6
+ while ( 1 )
+ {
+ float shakeDuration = RandomFloatRange( shakeDuration_min, shakeDuration_max )
+
+ SimpleScreenShake( player, shakeDuration, shakeAmplitude, screenBlurFrac )
+ thread TitanfallGlitch_PlayerWorldChange( player, shakeDuration * 0.85, shakeDuration * 0.1 )
+
+ wait shakeDuration
+ }
+ */
+}
+
+void function StartGlitchScreenFX_Delayed( entity player, float screenFXTime, float delay )
+{
+ if ( Flag( "PodOutroStarted" ) )
+ return
+ FlagEnd( "PodOutroStarted" )
+
+ EndSignal( player, "OnDestroy" )
+
+ if ( delay > 0 )
+ wait delay
+
+ Remote_CallFunction_Replay( player, "ScriptCallback_PodGlitch_PlayerScreenFX", screenFXTime )
+}
+
+const float GLITCH_WORLD_CHANGE_WAIT_MIN = 0.1
+const float GLITCH_WORLD_CHANGE_WAIT_MAX = 0.35
+const float GLITCH_WORLD_CHANGE_MINWAIT_FOR_EXTRA_FLICKER = 0.25
+
+void function TitanfallGlitch_CycleTitanGlitchAnims_OnWorldChange( entity player, entity mainTitan, entity titanTwin, vector origin, vector twinOrigin, vector angles, float delay = 0.0 )
+{
+ player.EndSignal( "OnDestroy" )
+ mainTitan.EndSignal( "OnDestroy" )
+ titanTwin.EndSignal( "OnDestroy" )
+
+ array<entity> sceneTitans = [ mainTitan, titanTwin ]
+
+ OnThreadEnd(
+ function() : ( player, sceneTitans )
+ {
+ if ( IsValid( player ) )
+ {
+ FlagClear( "PlayerWorldChangeThread" )
+ SetGlobalNetBool( "titanGlitch_extraFlicker", false )
+ }
+
+ foreach ( titan in sceneTitans )
+ if ( IsValid( titan ) )
+ titan.Show()
+ }
+ )
+
+ if ( delay > 0 )
+ wait delay
+
+ array<string> titanAnims = GetTitanfallGlitchAnims()
+ array<string> twinAnims = GetTitanfallGlitchTwinAnims()
+ Assert( titanAnims.len() == twinAnims.len() )
+ int animIdx = GetGlobalNetInt( "titanfallGlitchAnimIdx" )
+
+ while ( 1 )
+ {
+ table result = WaitSignal( player, "Glitch_WorldChanging_Zen", "Glitch_WorldChanging_NonZen" )
+ string signal = expect string( result.signal )
+
+ float worldChangeDuration = expect float( result.postChangeWait )
+
+ //printt( "titan anim:", titanAnims[animIdx] )
+
+ mainTitan.Anim_Stop()
+ titanTwin.Anim_Stop()
+ thread PlayAnimTeleport( mainTitan, titanAnims[animIdx], origin, angles )
+ thread PlayAnimTeleport( titanTwin, twinAnims[animIdx], twinOrigin, angles )
+
+ animIdx++
+ if ( animIdx >= titanAnims.len() )
+ animIdx = 0
+
+ SetGlobalNetInt( "titanfallGlitchAnimIdx", animIdx )
+
+ // update flicker settings & show/hide server versions of the titans
+ if ( signal == "Glitch_WorldChanging_Zen" )
+ {
+ // only do extra flicker if a longer world change pause is happening
+ if ( worldChangeDuration >= GLITCH_WORLD_CHANGE_MINWAIT_FOR_EXTRA_FLICKER )
+ {
+ SetGlobalNetBool( "titanGlitch_extraFlicker", true )
+
+ foreach ( titan in sceneTitans )
+ titan.Hide()
+ }
+ }
+ else if ( signal == "Glitch_WorldChanging_NonZen" )
+ {
+ SetGlobalNetBool( "titanGlitch_extraFlicker", false )
+
+ foreach ( titan in sceneTitans )
+ titan.Show()
+ }
+ else
+ {
+ Assert( false, "Couldn't find signal: " + signal )
+ }
+ }
+}
+
+
+void function TitanfallGlitch_PlayerWorldChange( entity player, float duration, float delay = 0.0 )
+{
+ if ( Flag( "PodOutroStarted" ) )
+ return
+ FlagEnd( "PodOutroStarted" )
+
+ EndSignal( player, "OnDestroy" )
+
+ if ( delay > 0 )
+ wait delay
+
+ FlagWaitClear( "PlayerWorldChangeThread" )
+ FlagSet( "PlayerWorldChangeThread" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ FlagClear( "PlayerWorldChangeThread" )
+ }
+ )
+
+ bool isInZenWorld = true
+
+ float endTime = Time() + duration
+
+ float minWait = GLITCH_WORLD_CHANGE_WAIT_MIN
+ float maxWait = GLITCH_WORLD_CHANGE_WAIT_MAX
+
+ while ( 1 )
+ {
+ // at end time, make sure player pos resets back to zen world before breaking
+ if ( Time() >= endTime && isInZenWorld )
+ break
+
+ vector newpos = TitanfallGlitch_WorldChange_GetOtherWorldPos( player.GetOrigin(), isInZenWorld )
+
+ float postChangeWait
+ string changeSignal
+ entity newSkyCam
+
+ if ( isInZenWorld )
+ {
+ // Switch to non zen world
+ changeSignal = "Glitch_WorldChanging_NonZen"
+ postChangeWait = minWait // always wait min time in non zen world
+
+ newSkyCam = file.skycam_glitch
+ isInZenWorld = false
+ }
+ else
+ {
+ // Switch back to zen world
+ postChangeWait = RandomFloatRange( minWait, maxWait )
+ changeSignal = "Glitch_WorldChanging_Zen"
+
+ newSkyCam = file.skycam_default
+ isInZenWorld = true
+ }
+
+ table<string,float> extraInfo = {}
+ extraInfo["postChangeWait"] <- postChangeWait
+ Signal( player, changeSignal, extraInfo )
+
+ player.SetSkyCamera( newSkyCam )
+ player.SetOrigin( newpos )
+
+ wait postChangeWait
+ }
+}
+
+
+vector function TitanfallGlitch_WorldChange_GetOtherWorldPos( vector currentPos, bool startInZenWorld )
+{
+ entity zenEnt = GetEntByScriptName( "titan_glitch_swap_ref1" )
+ entity nonZenEnt = GetEntByScriptName( "titan_glitch_swap_ref2" )
+ Assert( zenEnt && nonZenEnt )
+ vector zenPos = zenEnt.GetOrigin()
+ vector nonZenPos = nonZenEnt.GetOrigin()
+
+ float offsetX
+ float offsetY
+ float offsetZ
+ float newPosX
+ float newPosY
+ float newPosZ
+
+ if ( startInZenWorld )
+ {
+ offsetX = currentPos.x - zenPos.x
+ offsetY = currentPos.y - zenPos.y
+ offsetZ = currentPos.z - zenPos.z
+
+ newPosX = nonZenPos.x + offsetX
+ newPosY = nonZenPos.y + offsetY
+ newPosZ = nonZenPos.z + offsetZ
+ }
+ else
+ {
+ offsetX = currentPos.x - nonZenPos.x
+ offsetY = currentPos.y - nonZenPos.y
+ offsetZ = currentPos.z - nonZenPos.z
+
+ newPosX = zenPos.x + offsetX
+ newPosY = zenPos.y + offsetY
+ newPosZ = zenPos.z + offsetZ
+ }
+
+ vector newPos = < newPosX, newPosY, newPosZ >
+ return newPos
+}
+
+
+void function Training_DrawReplacementTitanLocation( entity player, vector origin, float delay )
+{
+ float endTime = Time() + delay
+
+ player.SetHotDropImpactDelay( endTime )
+ Remote_CallFunction_Replay( player, "ServerCallback_ReplacementTitanSpawnpoint", origin.x, origin.y, origin.z, endTime )
+ player.WaitSignal( "OnDeath" )
+}
+
+
+// ===================================
+// ============ POD OUTRO ============
+// ===================================
+void function Training_Setup_PodOutro( entity player )
+{
+}
+
+void function Training_Skipped_PodOutro( entity player )
+{
+ Training_EnvArtColorCorrection_SetEnabled( false )
+ SetDoF_Hangar( player )
+
+ Objective_Clear()
+
+ FlagSet( "PodOutroStarted" )
+ FlagSet( "SimPodShutdown_LoudspeakerVO_Done" )
+}
+
+#if DEV
+// bare bones start in pod
+void function DEV_PodOutro( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ TakeAllWeapons( player )
+
+ player.SetExtraWeaponMods( [ "low_ammo_disable" ] )
+ SetWeaponHUDEnabled( player, false )
+
+ Training_EnvArtColorCorrection_SetEnabled( false )
+ SetDoF_Hangar( player )
+
+ thread MeetOG_BackgroundSkits( player )
+
+ entity pod = file.trainingPod
+
+ TrainingPod_PlayerSequence_DoorsOpenIdle( player, false )
+
+ // anim starts at a slightly different spot
+ player.SetOrigin( < 10564, -10235, -6056.9 > )
+ player.SetAngles( < -6, 90, 0 > )
+
+ WaitForever()
+}
+#endif //DEV
+
+void function Training_PodOutro( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ Objective_Clear()
+ CheckPoint_Silent()
+
+ entity pod = file.trainingPod
+
+ FlagSet( "PodOutroStarted" )
+
+ OnThreadEnd(
+ function() : ( player, pod )
+ {
+ if ( IsValid( player ) )
+ {
+ //StopSoundOnEntity( player, "NPE_Scr_SimPod_End" )
+
+ // Not sure if we need any of this
+ //player.Anim_Stop()
+ //ClearAnimViewEntity( player )
+ //player.ClearParent()
+ //player.UnforceStand()
+ }
+
+ if ( IsValid ( pod ) )
+ {
+ // Not sure if we need any of this
+ //pod.Anim_Stop()
+ //thread TrainingPod_ResetLaserEmitterRotation( pod )
+ //thread TrainingPod_KillLasers( pod )
+ //thread TrainingPod_KillGlowFX( pod )
+ //TrainingPod_KillInteriorDLights()
+ }
+ }
+ )
+
+ // Have to do this first so the anim starts centered on the ref attachment angles
+ string podAttach = "REF"
+ int attachID = pod.LookupAttachment( podAttach )
+ vector podRefOrg = pod.GetAttachmentOrigin( attachID )
+ vector podRefAng = pod.GetAttachmentAngles( attachID )
+ player.SetOrigin( podRefOrg )
+ player.SetAngles( podRefAng )
+
+ player.ForceStand()
+
+ TakeAllWeapons( player )
+
+ // start closed idle
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.blendTime = 0.0
+ playerSequence.attachment = podAttach
+ playerSequence.firstPersonAnimIdle = "ptpov_trainingpod_idle"
+ playerSequence.thirdPersonAnimIdle = "pt_trainingpod_idle"
+ playerSequence.viewConeFunction = TrainingPod_ViewConeLock_Strict // so player can't move camera under the pod shutdown screen
+ playerSequence.renderWithViewModels = true
+
+ FirstPersonSequenceStruct podSequence
+ podSequence.blendTime = 0.0
+ podSequence.thirdPersonAnimIdle = "trainingpod_doors_close_idle"
+ podSequence.renderWithViewModels = true
+
+ thread FirstPersonSequence( playerSequence, player, pod )
+ thread FirstPersonSequence( podSequence, pod )
+
+ thread CadillacMoment_MeetOG( player )
+
+ //thread PodOutro_ScreenShake( player )
+
+ Training_EnvArtColorCorrection_SetEnabled( false )
+ SetDoF_Hangar( player )
+
+ // show the "pod shutdown screen"
+ float shutdownScreenWait = 7.0
+ float screenFadeWait = 7.0
+ ScreenFade( player, 0, 0, 0, 255, 0, screenFadeWait, FFADE_OUT | FFADE_STAYOUT )
+ Remote_CallFunction_Replay( player, "ScriptCallback_SimPodShutdownScreen", shutdownScreenWait )
+
+ thread SimPodShutdown_Dialogue( player )
+
+ // HACK, need to wait a bit so player moves into pod before initing shutdown sequence
+ // - otherwise, interior emitter angle setup math vs player eye position will crash the game
+ float waitForPlayerToBeMoved = 1.0
+ wait waitForPlayerToBeMoved
+
+ float shutdownSequence_waitBeforeAnimStart = 10.0
+ thread TrainingPod_Interior_ShutdownSequence( player, shutdownSequence_waitBeforeAnimStart )
+
+ wait screenFadeWait - waitForPlayerToBeMoved
+
+ // HACK reparent the emitters so they look correct, I didn't expect to have to do this
+ TrainingPod_SnapLaserEmittersToAttachPoints()
+
+ // Transition screen FX
+ thread PlayFXOnEntity( FX_POD_SCREEN_OUT, player )
+
+ float fadeInFromShutdownScreenTime = 0.2
+ ScreenFade( player, 0, 0, 0, 255, fadeInFromShutdownScreenTime, 1, FFADE_IN | FFADE_PURGE )
+ wait fadeInFromShutdownScreenTime
+
+ TrainingPod_ViewConeLock_PodClosed( player )
+
+ // start shutdown sequence
+ // HACK- eventually one sound will cover the whole sequence
+ thread HACK_DelayedShutdownSequenceSFX( player, 3.0 )
+
+ player.Signal( "TrainingPod_BeginInteriorShutdown" )
+ wait shutdownSequence_waitBeforeAnimStart
+
+ FirstPersonSequenceStruct playerSequence_podOpens
+ playerSequence_podOpens.blendTime = 0.25
+ playerSequence_podOpens.attachment = podAttach
+ playerSequence_podOpens.firstPersonAnim = "ptpov_trainingpod_doors_open"
+ playerSequence_podOpens.firstPersonAnimIdle = "ptpov_trainingpod_idle"
+ playerSequence_podOpens.thirdPersonAnim = "pt_trainingpod_doors_open"
+ playerSequence_podOpens.thirdPersonAnimIdle = "pt_trainingpod_idle"
+ playerSequence_podOpens.viewConeFunction = TrainingPod_ViewConeLock_SemiStrict
+ playerSequence_podOpens.renderWithViewModels = true
+
+ FirstPersonSequenceStruct podSequence_podOpens
+ podSequence_podOpens.blendTime = 0.25
+ podSequence_podOpens.thirdPersonAnim = "trainingpod_doors_open"
+ podSequence_podOpens.thirdPersonAnimIdle = "trainingpod_doors_open_idle"
+ podSequence_podOpens.renderWithViewModels = true
+
+ thread TrainingPod_TurnOnInteriorDLights_Delayed( player, 1.5 )
+
+ thread FirstPersonSequence( podSequence_podOpens, pod )
+ thread FirstPersonSequence( playerSequence_podOpens, player, pod )
+
+ wait 2.1 // wait until scene starts animating for Meet OG
+}
+
+void function HACK_DelayedShutdownSequenceSFX( entity player, float delayTime )
+{
+ EndSignal( player, "OnDestroy" )
+
+ wait delayTime
+ EmitSoundOnEntityOnlyToPlayerWithSeek( player, player, "NPE_Scr_SimPod_End", 0.7 )
+}
+
+void function PodOutro_ScreenShake( entity player )
+{
+ if ( Flag( "MeetOG_StartScene" ) )
+ return
+ FlagEnd( "MeetOG_StartScene" )
+
+ player.EndSignal( "OnDestroy" )
+
+ float shakeDuration = 2.0
+ float shakeAmplitude = 0.2
+ float screenBlurFrac = 0.0
+ float shakeDelayMin = 1.75
+ float shakeDelayMax = 2.25
+ while ( 1 )
+ {
+ SimpleScreenShake( player, shakeDuration, shakeAmplitude, screenBlurFrac )
+ wait shakeDuration - (shakeDuration * 0.25) // HACK shake dies down
+ wait RandomFloatRange( shakeDelayMin, shakeDelayMax )
+ }
+}
+
+void function SimPodShutdown_LoudspeakerVO( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ wait 4.0 // wait for "I'm pulling you out"
+
+ // "Powering down all non-essential systems."
+ waitthread PlayLoudspeakerVO( "outro_01_1" )
+
+ // "Prepare for Typhon atmospheric entry in less than three minutes."
+ //waitthread PlayLoudspeakerVO( "outro_01" )
+
+ wait 7.0
+
+ // "All personnel to battle stations."
+ waitthread PlayLoudspeakerVO( "outro_01_2" )
+
+ // "This is not a drill."
+ waitthread PlayLoudspeakerVO( "outro_01_3" )
+
+ FlagSet( "SimPodShutdown_LoudspeakerVO_Done" )
+}
+
+void function SimPodShutdown_Dialogue( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ thread SimPodShutdown_LoudspeakerVO( player )
+
+ Assert( IsValid( file.ogPilot ), "Can't find scene entity ogPilot" )
+
+ // "Alright Rifleman, sounds like it's about to hit the fan."
+ waitthread PlayDialogue( "og_hit_the_fan", file.ogPilot )
+
+ // "I'm pulling you out."
+ waitthread PlayDialogue( "og_pulling_you_out", file.ogPilot )
+
+ wait 2.0
+
+ // "Cooper! Ready up!"
+ waitthread PlayDialogue( "grunt_closed_pod_yell_at_player", file.ogPilot ) // HACK play it off OG until the animation idle is legit
+
+ // "Easy Cole, he just left VR. Needs a minute to decompress. He'll be ready to go... trust me."
+ waitthread PlayDialogue( "og_closed_pod_respond_to_grunt", file.ogPilot )
+
+ // "Yes sir."
+ waitthread PlayDialogue( "grunt_yes_sir", file.ogPilot )
+}
+
+#if DEV
+void function shutdownscreentest( float duration = 5.0 )
+{
+ entity player = file.player
+ EndSignal( player, "OnDestroy" )
+
+ float fadeTime = 0.2
+
+ ScreenFade( player, 0, 0, 0, 255, fadeTime, duration, FFADE_OUT | FFADE_STAYOUT )
+ Remote_CallFunction_Replay( player, "ScriptCallback_SimPodShutdownScreen", duration )
+ wait duration
+ ScreenFade( player, 0,0,0, 255, fadeTime, fadeTime, FFADE_IN | FFADE_PURGE )
+}
+#endif
+
+
+
+// =================================
+// ============ MEET OG ============
+// =================================
+void function Training_Setup_MeetOG( entity player )
+{
+ TakeAllWeapons( player )
+
+ thread CadillacMoment_MeetOG( player )
+ TrainingPod_PlayerSequence_DoorsOpenIdle( player )
+
+ wait 0.2
+}
+
+void function Training_Skipped_MeetOG( entity player )
+{
+ // settings for checkpoints after the normal level progression
+ SetDoF_Default( player )
+ player.SetExtraWeaponMods( [] ) // turn off low_ammo_disable
+ SetWeaponHUDEnabled( player, true )
+}
+
+void function Training_MeetOG( entity player )
+{
+ thread MeetOG_BackgroundSkits( player )
+
+ FlagSet( "MeetOG_StartScene" )
+
+ thread MeetOG_ControllerRumble( player )
+
+ FlagWait( "CadillacMoment_MeetOG_StartFadeOut" )
+
+ UnlockAchievement( player, achievements.COMPLETE_TRAINING )
+
+ // Free SP trial? Load Beacon next.
+ if( Script_IsRunningTrialVersion() )
+ {
+ thread FreeTrial_OutroPopup( player, 2.5 )
+ PickStartPoint( "sp_beacon", "Level Start" )
+ }
+ else
+ {
+ thread OutroDifficultyPopup( player, 2.5 )
+
+ // load next level
+ // NOTE this does a screen fade already
+ PickStartPoint( "sp_crashsite", "LevelStart" )
+ }
+
+ // don't ever progress from here to the dev functions beyond
+ WaitForever()
+}
+
+void function FreeTrial_OutroPopup( entity player, float delay )
+{
+ EndSignal( player, "OnDestroy" )
+ wait delay
+
+ Remote_CallFunction_UI( player, "ScriptCallback_Training_FreeTrialMessage" )
+}
+
+void function OutroDifficultyPopup( entity player, float delay )
+{
+ EndSignal( player, "OnDestroy" )
+ wait delay
+
+ Remote_CallFunction_UI( player, "ScriptCallback_Training_SelectSPDifficulty" )
+}
+
+// can't do screen shake for this part because it looks bad (not connected) when player view is 1P animating while parented to the pod
+void function MeetOG_ControllerRumble( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ FlagEnd( "CadillacMoment_MeetOG_StartFadeOut" )
+
+ float shakeDuration = 2.8
+ float shakeAmplitudeMin = 13.5
+ float shakeAmplitudeMax = 14.5
+ float frequency = 155
+ float shakeDelayMin = 4.0
+ float shakeDelayMax = 7.0
+ while ( 1 )
+ {
+ float amplitude = RandomFloatRange( shakeAmplitudeMin, shakeAmplitudeMax )
+
+ //vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048
+ CreateAirShakeRumbleOnly( player.GetOrigin(), amplitude, frequency, shakeDuration )
+ printt( "Shake Rumble:", amplitude )
+ wait shakeDuration * 0.75 // HACK shake dies down pretty early
+
+ wait RandomFloatRange( shakeDelayMin, shakeDelayMax )
+ }
+}
+
+void function CadillacMoment_MeetOG( entity player )
+{
+ thread MeetOG_LoudspeakerVO( player )
+
+ int friendlyTeam = player.GetTeam()
+
+ entity animref = file.animref_hangar
+ vector animrefOrigin = animref.GetOrigin()
+ vector animrefAngles = animref.GetAngles()
+ vector btSpawnOrg = <0,0,0> // to avoid red text errors about BT spawning in solid
+
+ entity animEnt = CreateScriptMover( animrefOrigin, animrefAngles )
+
+ // Spawn scene actors
+ entity og = Training_SpawnOGPilot( animref )
+ Training_OGPilot_SetHelmetOn( og, false )
+ SetTeam( og, TEAM_SPECTATOR ) // turn off his glowy bits so they're accentuated when helmet turns on later
+
+ entity anderson = CreateSoldier( friendlyTeam, animrefOrigin, animrefAngles )
+ DispatchSpawn( anderson )
+ anderson.SetModel( ANDERSON_PILOT_MODEL )
+ anderson.SetTitle( "#TRAINING_ANDERSON_NAME" )
+ //ShowName( anderson )
+ HideName( anderson )
+
+ entity redshirt = CreateSoldier( friendlyTeam, animrefOrigin, animrefAngles )
+ DispatchSpawn( redshirt )
+ redshirt.SetModel( TEAM_MIL_GRUNT_MODEL_LMG )
+ redshirt.SetTitle( "#CPT_COLE" )
+ //ShowName( redshirt )
+ HideName( redshirt )
+
+ TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap()
+ entity bt = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, btSpawnOrg, animrefAngles )
+ SetSpawnOption_AISettings( bt, "npc_titan_buddy" )
+ bt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+ DispatchSpawn( bt )
+ FreeAutoTitan( bt ) // HACK this disables the worldspace BT locator icon
+ TakeAllWeapons( bt )
+
+ array<entity> actors = [ player, bt, og, anderson, redshirt ]
+
+ // Setup scene props
+
+ entity ogHelmet = CreatePropDynamic( OG_PILOT_HELMET_MODEL )
+ ogHelmet.DisableHibernation()
+ file.ogHelmet = ogHelmet
+
+ string knifeTag = "KNIFE"
+ int knifeTagID = og.LookupAttachment( knifeTag )
+ entity dataKnife = CreatePropScript( DATA_KNIFE_MODEL, og.GetAttachmentOrigin( knifeTagID ), og.GetAttachmentAngles( knifeTagID ) )
+ dataKnife.DisableHibernation()
+ dataKnife.SetParent( og, knifeTag, false, 0.0 )
+
+ asset btWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( "mp_titanweapon_xo16_shorty", "playermodel" )
+ Assert( btWeaponModel != $"" )
+ entity btWeapon = CreatePropDynamic( btWeaponModel )
+ btWeapon.SetParent( bt, "PROPGUN" )
+
+ string gruntRifleName = "mp_weapon_rspn101"
+ string andersonWeaponName = "mp_weapon_car"
+ asset gruntRifleModel = GetWeaponInfoFileKeyFieldAsset_Global( gruntRifleName, "playermodel" )
+ asset andersonWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( andersonWeaponName, "playermodel" )
+ asset ogWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( OG_WEAPON, "playermodel" )
+ Assert( gruntRifleModel != $"" )
+ Assert( andersonWeaponModel != $"" )
+ Assert( ogWeaponModel != $"" )
+
+ entity ogWeapon = CreatePropDynamic( ogWeaponModel )
+ ogWeapon.DisableHibernation()
+ ogWeapon.SetParent( og, "PROPGUN" )
+
+ entity andersonWeapon = CreatePropDynamic( andersonWeaponModel )
+ andersonWeapon.DisableHibernation()
+ andersonWeapon.SetParent( anderson, "PROPGUN" )
+
+ entity redshirtWeapon = CreatePropDynamic( gruntRifleModel )
+ redshirtWeapon.DisableHibernation()
+ redshirtWeapon.SetParent( redshirt, "PROPGUN" )
+
+ array<entity> props = [ btWeapon, redshirtWeapon, andersonWeapon, ogWeapon, ogHelmet, dataKnife ]
+
+ OnThreadEnd(
+ function() : ( actors, props, animEnt )
+ {
+ foreach ( weapon in props )
+ {
+ if ( IsValid( weapon ) )
+ {
+ weapon.ClearParent()
+ weapon.Destroy()
+ }
+ }
+
+ foreach ( actor in actors )
+ {
+ if ( IsValid( actor ) )
+ actor.ClearParent()
+
+ if ( IsInvincible( actor ) )
+ ClearInvincible( actor )
+
+ if ( !actor.IsPlayer() )
+ actor.Destroy()
+ }
+
+ if ( IsValid( animEnt ) )
+ animEnt.Destroy()
+ }
+ )
+
+ foreach ( guy in actors )
+ {
+ if ( guy.IsPlayer() )
+ continue
+
+ MakeInvincible( guy )
+ guy.SetEfficientMode( true )
+ Highlight_ClearFriendlyHighlight( guy )
+ }
+
+ string anim_og = "pt_intro_scene_OG"
+ string anim_og_helmet = "helmet_intro_scene_OG"
+ string anim_bt = "BT_intro_scene_OG"
+ string anim_anderson = "pt_intro_scene_Anderson"
+ string anim_redshirt = "pt_intro_scene_grunt"
+
+ string anim_og_idle = "pt_intro_scene_OG_idle"
+ string anim_og_helmet_idle = "helmet_intro_scene_OG_idle"
+ string anim_bt_idle = "BT_intro_scene_OG_idle"
+ string anim_anderson_idle = "pt_intro_scene_Anderson_idle"
+ string anim_redshirt_idle = "pt_intro_scene_grunt_idle"
+
+ thread PlayAnimTeleport( og, anim_og_idle, animEnt )
+ thread PlayAnimTeleport( ogHelmet, anim_og_helmet_idle, animEnt )
+ thread PlayAnimTeleport( bt, anim_bt_idle, animEnt )
+ thread PlayAnimTeleport( anderson, anim_anderson_idle, animEnt )
+ thread PlayAnimTeleport( redshirt, anim_redshirt_idle, animEnt )
+
+ FlagWait( "MeetOG_StartScene" )
+ printt( "STARTING ANIMS: MEET OG" )
+
+ foreach ( guy in actors )
+ guy.Anim_Stop()
+
+ AddAnimEvent( og, "helmet_on", AnimEventCallback_MeetOG_HelmetTurnsOn )
+
+ thread PlayAnimTeleport( og, anim_og, animEnt )
+ thread PlayAnimTeleport( ogHelmet, anim_og_helmet, animEnt )
+ thread PlayAnimTeleport( bt, anim_bt, animEnt )
+ thread PlayAnimTeleport( anderson, anim_anderson, animEnt )
+ thread PlayAnimTeleport( redshirt, anim_redshirt, animEnt )
+
+ player.Anim_Stop()
+
+ // give player weapon for the toss moment
+ entity fpProxy = player.GetFirstPersonProxy()
+ int attachID = fpProxy.LookupAttachment( "PROPGUN" )
+ asset playerWeaponModel = GetWeaponInfoFileKeyFieldAsset_Global( "mp_weapon_vinson", "playermodel" )
+ entity playerWeapon = CreatePropDynamic( playerWeaponModel, fpProxy.GetAttachmentOrigin( attachID ), fpProxy.GetAttachmentAngles( attachID ) )
+ file.playerAnimWeapon = playerWeapon
+ playerWeapon.DisableHibernation()
+ playerWeapon.SetParent( fpProxy, "PROPGUN", false, 0.0 )
+
+ AddAnimEvent( player, "gun_catch", AnimEventCallback_MeetOG_PlayerCatchesWeapon )
+
+ FirstPersonSequenceStruct playerSequence_meetOG
+
+ float viewAnimDelay = 4.0
+ //playerSequence_meetOG.setInitialTime = viewAnimDelay // DEPRECATED animators will adjust so start position is good again}
+ playerSequence_meetOG.blendTime = 0.0
+ playerSequence_meetOG.teleport = false
+ playerSequence_meetOG.attachment = "REF"
+ playerSequence_meetOG.firstPersonAnim = "pov_intro_scene_player"
+ playerSequence_meetOG.thirdPersonAnim = "pt_intro_scene_player"
+ playerSequence_meetOG.viewConeFunction = TrainingPod_ViewConeLock_SemiStrict
+ playerSequence_meetOG.renderWithViewModels = true
+
+ // HACK delay sequence so it transitions better from previous anim
+ TrainingPod_ViewConeLock_SemiStrict( player ) // set this to cover any time between viewmodel anims
+ thread FirstPersonSequence_Delayed( viewAnimDelay, playerSequence_meetOG, player, animEnt )
+ //thread FirstPersonSequence( playerSequence_meetOG, player, animEnt )
+
+ // gradual DOF racking during the scene
+ RackDoF_NearDepth( player, 0, 22, 12.0 )
+ RackDoF_FarDepth( player, 350, 950, 20.0 )
+
+ // find longest anim duration
+ array<string> sceneAnims = [ anim_og, anim_bt, anim_anderson, anim_redshirt ]//, "pt_intro_scene_player" ]
+ float sceneDuration = -1
+ foreach ( anim in sceneAnims )
+ {
+ entity animActor = og
+ if ( anim.find( "BT_") != null )
+ animActor = bt
+ else if ( anim.find( "_player") != null )
+ animActor = player
+
+ float duration = animActor.GetSequenceDuration( anim )
+ if ( duration > sceneDuration )
+ sceneDuration = duration
+ }
+ Assert( sceneDuration > 0 )
+
+ float fadeTime = SP_LEVEL_TRANSITION_FADETIME
+
+ wait sceneDuration - fadeTime
+ FlagSet( "CadillacMoment_MeetOG_StartFadeOut" )
+
+ wait fadeTime
+ FlagSet( "CadillacMoment_MeetOG_Done" )
+}
+
+void function FirstPersonSequence_Delayed( float delay, FirstPersonSequenceStruct sequence, entity player, entity animEnt )
+{
+ EndSignal( player, "OnDestroy" )
+
+ if ( delay > 0 )
+ wait delay
+
+ thread FirstPersonSequence( sequence, player, animEnt )
+}
+
+void function MeetOG_LoudspeakerVO( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+ FlagWait( "SimPodShutdown_LoudspeakerVO_Done" )
+
+ array<string> aliases
+ // "Incoming hostile ship - Designation: IMS Malta. Battle stations."
+ aliases.append( "outro_08" )
+ // "Caution - Fuel leak in Cargo Bay eighty - five. Activating airlock procedures."
+ aliases.append( "outro_12" )
+ // "Titan bays two through five, drop clearance confirmed."
+ aliases.append( "outro_03" )
+ // "All available medical personnel, report to Med Central for tasking."
+ aliases.append( "outro_05" )
+ // "We need all Riflemen to docking bay four. Dropships standing by."
+ aliases.append( "outro_07" )
+ // "Infantry teams Stork three, Elk four, Shark six, Badger one, prepare for emergency atmospheric drop sequence"
+ aliases.append( "outro_04" )
+ // "Titan mech team - Rabbit six, prep the Vanguards."
+ aliases.append( "outro_06" )
+ // "Infantry teams 2nd Militia Fusiliers, Raptor three, target the IMS Malta."
+ aliases.append( "outro_11" )
+ // "Special Recon Squad deploy from Drop Bay thirty-seven - now."
+ aliases.append( "outro_10" )
+
+ thread LoopLoudspeakerVO( aliases )
+}
+
+void function AnimEventCallback_MeetOG_PlayerCatchesWeapon( entity player )
+{
+ Assert( IsValid( file.playerAnimWeapon ) )
+ file.playerAnimWeapon.RenderWithViewModels( true )
+
+ thread MeetOG_PlayerCatchesWeapon_RackNearDOF( player )
+}
+
+void function MeetOG_PlayerCatchesWeapon_RackNearDOF( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ wait 0.7
+
+ RackDOF_NearDepth_ToDefault( player, 1.5 )
+
+ wait 2.0
+
+ RackDoF_NearDepth( player, 0, 22, 1.25 )
+}
+
+
+// ------ OG HELMET TURNING ON ------
+void function AnimEventCallback_MeetOG_HelmetTurnsOn( entity og )
+{
+ thread MeetOG_HelmetTurnsOn( og )
+}
+
+// script MeetOG_HelmetTurnsOn( GetOGPilot() )
+void function MeetOG_HelmetTurnsOn( entity og )
+{
+ entity og = file.ogPilot
+ entity ogHelmet = file.ogHelmet
+ EndSignal( og, "OnDestroy" )
+ EndSignal( ogHelmet, "OnDestroy" )
+
+ // setting his team to friendly makes the helmet light up
+ if ( og.GetTeam() == TEAM_MILITIA )
+ SetTeam( og, TEAM_SPECTATOR )
+
+ ogHelmet.Hide()
+ Training_OGPilot_SetHelmetOn( og, true )
+ wait 0.2
+
+ SetTeam( og, TEAM_MILITIA )
+ wait 0.1
+ SetTeam( og, TEAM_SPECTATOR )
+ wait 0.4
+ SetTeam( og, TEAM_MILITIA )
+ wait 0.1
+ SetTeam( og, TEAM_SPECTATOR )
+ wait 0.1
+ SetTeam( og, TEAM_MILITIA )
+}
+
+#if DEV
+void function MeetOG_HelmetTurnsOff( entity og )
+{
+ entity og = file.ogPilot
+ entity ogHelmet = file.ogHelmet
+
+ if ( og.GetTeam() == TEAM_SPECTATOR )
+ SetTeam( og, TEAM_MILITIA )
+
+ ogHelmet.Show()
+ Training_OGPilot_SetHelmetOn( og, false )
+}
+#endif
+
+
+// ------ POD OUTRO BACKGROUND SKITS ------
+void function MeetOG_BackgroundSkits( entity player, float delay = 0.0 )
+{
+ string endFlag = "CadillacMoment_MeetOG_Done"
+ FlagEnd( endFlag )
+
+ if ( delay > 0 )
+ wait delay
+
+ printt( "!!!! Pod Outro Background Skits START !!!!" )
+
+ thread PodOutro_TitanRacks( endFlag )
+
+ thread PodOutro_Background_Runners( player )
+ thread PodOutro_Foreground_Runners( player )
+
+ thread PodOutro_Background_ATC_Marvins()
+
+ OnThreadEnd(
+ function() : ()
+ {
+ DeleteAllSkitGuys()
+ }
+ )
+
+ WaitForever()
+}
+
+void function PodOutro_TitanRacks( string endFlag )
+{
+ FlagEnd( endFlag )
+
+ array<HangarTitanGroup> hangarTitanGroups
+
+ OnThreadEnd(
+ function() : ( hangarTitanGroups )
+ {
+ foreach ( group in hangarTitanGroups )
+ HangarTitanGroup_Cleanup( group )
+ }
+ )
+
+ float wait_earlyEnd = 24.0
+ bool cleanupAfterAnim = false
+
+ // --- BAY 1 ---
+ HangarTitanGroup bay1
+ entity rack_bay1 = GetEntByScriptName( "hangar_titan_rack_1" )
+ bay1.ref = rack_bay1
+ bay1.rack = rack_bay1
+ bay1.titan = GetEntByScriptName( "hangar_titan_1" )
+ bay1.titanAnim = "bt_rack_prep_titan2"
+ bay1.rackAnim = "rack_rack_prep_rack2"
+ bay1.marvinAnim = "mv_rack_prep_marvin2"
+ bay1.pilotAnim = "pt_rack_prep_pilot2"
+ bay1.pilotModel = PILOT_MODEL_BAY1
+ HangarTitanGroup_Init( bay1 )
+ hangarTitanGroups.append( bay1 )
+ thread HangarTitanGroup_Animate( bay1, endFlag, -1, cleanupAfterAnim )
+
+
+ // --- BAY 2 ---
+ //thread PodOutro_TitanBoot( endFlag, 15.0 )
+
+ HangarTitanGroup bay2
+ entity rack_bay2 = GetEntByScriptName( "hangar_titan_rack_2" )
+ bay2.ref = rack_bay2
+ bay2.rack = rack_bay2
+ bay2.titan = GetEntByScriptName( "hangar_titan_2" )
+ bay2.titanAnim = "bt_rack_prep_titan1"
+ bay2.rackAnim = "rack_rack_prep_rack1"
+ bay2.marvinAnim = "mv_rack_prep_marvin1"
+ bay2.pilotAnim = "pt_rack_prep_pilot1"
+ bay2.pilotModel = PILOT_MODEL_BAY2
+ HangarTitanGroup_Init( bay2 )
+ hangarTitanGroups.append( bay2 )
+ waitthread HangarTitanGroup_Animate( bay2, endFlag, -1, cleanupAfterAnim )
+}
+
+void function PodOutro_TitanBoot( string endFlag, float delay )
+{
+ FlagEnd( endFlag )
+
+ entity refTitan = GetEntByScriptName( "hangar_titan_2" )
+ refTitan.Hide()
+
+ vector refOrg = refTitan.GetOrigin()
+ vector refAng = refTitan.GetAngles()
+ refOrg += < -20, 40, 0> // HACK
+ refOrg = OriginToGround( refOrg )
+
+ entity animref = CreateScriptRef( refOrg, refAng )
+
+ entity titan = CreatePropScript( BUDDY_MODEL, refOrg, refAng )
+ titan.DisableHibernation()
+
+ AddAnimEvent( titan, "hatch_closed", TitanBoot_HatchClosed )
+ entity cockpitLightFX = PlayFXOnEntity( FX_COCKPIT_LIGHT, titan, "HIJACK" )
+
+ SkitGuyInfo titanInfo = AddSkitGuy_Manually( "bootup_titan", titan )
+
+ SkitGuyInfo titanPilotInfo = SpawnSkitGuy( "bootup_pilot", "", animref.GetOrigin(), animref.GetAngles(), TEAM_MILITIA, "npc_soldier" )
+ entity titanPilot = titanPilotInfo.guy
+ titanPilot.SetParent( titan, "HIJACK", false, 0.0 )
+ titanPilot.MarkAsNonMovingAttachment()
+
+ SkitGuyInfo crewInfo = SpawnSkitGuy( "bootup_crew", "", animref.GetOrigin(), animref.GetAngles(), TEAM_MILITIA, "npc_soldier_bish" )
+ entity crew = crewInfo.guy
+
+ OnThreadEnd(
+ function() : ( titanPilotInfo, crewInfo, titanInfo, cockpitLightFX, animref, refTitan )
+ {
+ if ( IsValid_ThisFrame( cockpitLightFX ) )
+ {
+ EntFireByHandle( cockpitLightFX, "Stop", "", 0, null, null )
+ cockpitLightFX.ClearParent()
+ cockpitLightFX.Destroy()
+ }
+
+ if ( IsAlive( titanInfo.guy ) )
+ StopSoundOnEntity( titanInfo.guy, "Wargames_MCOR_TitanActivate" )
+
+ if ( IsValid( animref ) )
+ animref.Destroy()
+
+ DeleteSkitGuy( titanPilotInfo )
+ DeleteSkitGuy( crewInfo )
+ DeleteSkitGuy( titanInfo )
+
+ if ( IsValid( refTitan ) )
+ refTitan.Show()
+ }
+ )
+
+ EmitSoundOnEntity( titan, "Wargames_MCOR_TitanActivate" )
+
+ string pilotAnim = "pt_titan_activation_pilot"
+ string pilotAnim_idle = "pt_titan_activation_pilot_idle"
+ string titanAnim = "at_titan_activation_training_meetOG"
+ string titanAnim_idle = "at_titan_activation_idle"
+ string titanAnim_endIdle = "at_titan_activation_training_meetOG_end_idle"
+ string crewAnim = "pt_titan_activation_crew"
+ string crewAnim_idle = "pt_titan_activation_crew_idle"
+
+ if ( delay > 0 )
+ {
+ thread PlayAnim( crew, crewAnim_idle, animref, null, 0.0 )
+ thread PlayAnim( titan, titanAnim_idle, animref, null, 0.0 )
+ titanPilot.Anim_ScriptedPlay( pilotAnim_idle )
+ titanPilot.Anim_EnableUseAnimatedRefAttachmentInsteadOfRootMotion()
+
+ wait delay
+
+ titanPilot.Anim_Stop()
+ crew.Anim_Stop()
+ titan.Anim_Stop()
+ }
+
+ titanPilot.Anim_ScriptedPlay( pilotAnim )
+ titanPilot.Anim_EnableUseAnimatedRefAttachmentInsteadOfRootMotion()
+ thread PlayAnim( crew, crewAnim, animref, null, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+ thread PlayAnim( titan, titanAnim, animref, null, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+
+ // end it early so he doesn't make stomping sounds when the pod is closed
+ //wait 15.0
+ float duration = titan.GetSequenceDuration( titanAnim )
+ wait duration - 0.1
+
+ printt( "Ending titan boot anim" )
+
+ thread PlayAnim( titan, titanAnim_endIdle, animref, null, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+ crew.Freeze()
+
+ WaitForever()
+}
+
+void function TitanBoot_HatchClosed( entity titan )
+{
+ SkitGuyInfo info = GetSkitGuyInfo_ByName( "bootup_pilot" )
+ DeleteSkitGuy( info )
+}
+
+void function PodOutro_Background_Runners( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ // right to left, across the whole hangar
+ array<Point> path1 = []
+ ScriptedPath_AddPoint( path1, < 11080.5, -10017.2, -6079.97 >, < 0, 177.587, 0 > )
+ ScriptedPath_AddPoint( path1, < 9619.14, -10004.8, -6079.97 >, < 0, 183.021, 0 > )
+
+ // right to left, starts closer to player POV
+ array<Point> path2 = []
+ ScriptedPath_AddPoint( path2, < 11000, -9946.33, -6079.97 >, < 0, 177.587, 0 > )
+ ScriptedPath_AddPoint( path2, < 9619.14, -10004.8, -6079.97 >, < 0, 183.021, 0 > )
+
+ wait 2.0 // wait for OG's first line to be nearly over
+
+ printt( "BACKGROUND RUNNER GROUP 1" )
+
+ thread SpawnSkitGuy_AndRun( "runner_1_1", path2, 0.85, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_hemlok_smg" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_1_2", path2, 0.85, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+
+ wait 6.5 // wait until OG hands the eye to BT
+
+ printt( "BACKGROUND RUNNER GROUP 2" )
+
+ thread SpawnSkitGuy_AndRun( "runner_2_1", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_2_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" )
+
+ wait 4.5 // wait until OG is shaking Anderson's hand
+
+ printt( "BACKGROUND RUNNER GROUP 3" )
+
+ thread SpawnSkitGuy_AndRun( "runner_3_1", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_3_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_3_3", path2, 0.8, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_hemlok_smg" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_3_4", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+
+ wait 4.5 // wait until OG is mounting up
+
+ printt( "BACKGROUND RUNNER GROUP 4" )
+
+ thread SpawnSkitGuy_AndRun( "runner_4_1", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_4_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" )
+
+ wait 6.5 // wait until OG thumbs up
+
+ printt( "BACKGROUND RUNNER GROUP 5" )
+
+ thread SpawnSkitGuy_AndRun( "runner_5_1", path2, 0.85, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_hemlok_smg" )
+ wait 1.2
+ thread SpawnSkitGuy_AndRun( "runner_5_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_5_3", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" )
+ wait 1.5
+ thread SpawnSkitGuy_AndRun( "runner_5_4", path2, 0.85, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_5_3", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" )
+ wait 1.5
+ thread SpawnSkitGuy_AndRun( "runner_5_4", path2, 0.85, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+
+ /*
+ wait 5.0
+
+ wait 4.0
+
+ thread SpawnSkitGuy_AndRun( "runner_2_1", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "runner_2_2", path2, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_shotgun" )
+ */
+}
+
+void function PodOutro_Foreground_Runners( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ // right in front of pod
+ array<Point> pathClose = []
+ ScriptedPath_AddPoint( pathClose, < 10728.2, -10194.2, -6055.97 >, < 0, 178.246, 0 > )
+ ScriptedPath_AddPoint( pathClose, < 10236.8, -10180.3, -6055.97 >, < 0, 178.41, 0 > )
+
+ wait 11.0 // wait for OG to hand eyeball to BT
+
+ thread SpawnSkitGuy_AndRun( "close_runner_1", pathClose, 0.7, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.2
+ thread SpawnSkitGuy_AndRun( "close_runner_2", pathClose, 0.75, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+
+ wait 10.0 // wait for OG to start embarking BT
+
+ thread SpawnSkitGuy_AndRun( "close_runner_4", pathClose, 0.75, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.3
+ thread SpawnSkitGuy_AndRun( "close_runner_5", pathClose, 0.8, TEAM_MILITIA, "npc_soldier_specialist_militia", "mp_weapon_g2" )
+
+ wait 11 // wait for grunt to hand player the weapon
+
+ thread SpawnSkitGuy_AndRun( "close_runner_10", pathClose, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.8
+ thread SpawnSkitGuy_AndRun( "close_runner_11", pathClose, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+ wait 1.0
+ thread SpawnSkitGuy_AndRun( "close_runner_12", pathClose, 0.8, TEAM_MILITIA, "npc_soldier", "mp_weapon_rspn101" )
+}
+
+void function PodOutro_Background_ATC_Marvins()
+{
+ // 1, 2, 3- matches titan bay numbering (right to left)
+ // plain numbers = farther from camera
+ // "a" variants = closer to camera
+ //SkitGuyInfo marvin_1 = SpawnSkitGuy( "atc_marvin_1", "", < 10600, -9841.5, -6079.97 >, < 0, -20, 0 >, TEAM_MILITIA )
+ //thread ATC_Marvin_Think( marvin_1, "mv_trafic_controller_A", 0.0 )
+ //SkitGuyInfo marvin_1a = SpawnSkitGuy( "atc_marvin_1a", "", < 10683.8, -10077.7, -6094.65 >, < 0, 20, 0 >, TEAM_MILITIA )
+ //thread ATC_Marvin_Think( marvin_1a, "mv_trafic_controller_B", 0.0 )
+ SkitGuyInfo marvin_2 = SpawnSkitGuy( "atc_marvin_2", "", < 10460.6, -9861.5, -6079.97 >, < 0, -30, 0 >, TEAM_MILITIA )
+ thread ATC_Marvin_Think( marvin_2, "mv_trafic_controller_B", 0.25 )
+ //SkitGuyInfo marvin_2a = SpawnSkitGuy( "atc_marvin_2a", "", < 10410.6, -10075.6, -6079.97 >, < 0, 30, 0 >, TEAM_MILITIA )
+ //thread ATC_Marvin_Think( marvin_2a, "mv_trafic_controller_A", 0.25 )
+ SkitGuyInfo marvin_3 = SpawnSkitGuy( "atc_marvin_3", "", < 10214.8, -9891.5, -6079.97 >, < 0, -10, 0 >, TEAM_MILITIA )
+ thread ATC_Marvin_Think( marvin_3, "mv_trafic_controller_A", 0.5 )
+ SkitGuyInfo marvin_3a = SpawnSkitGuy( "atc_marvin_3a", "", < 10207.5, -10079.4, -6079.97 >, < 0, 10, 0 >, TEAM_MILITIA )
+ thread ATC_Marvin_Think( marvin_3a, "mv_trafic_controller_B", 0.5 )
+}
+
+void function ATC_Marvin_Think( SkitGuyInfo marvinInfo, string anim, float skipAheadTime = 0.0 )
+{
+ entity marvin = marvinInfo.guy
+
+ EndSignal( marvin, "OnDestroy" )
+
+ entity batonRight = CreatePropDynamic( SAFETY_BATON_MODEL )
+ batonRight.SetOrigin( marvin.GetAttachmentOrigin( marvin.LookupAttachment( "R_HAND" ) ) )
+ // HACK adjust the angles since the tags are different
+ vector angs = marvin.GetAttachmentAngles( marvin.LookupAttachment( "R_HAND" ) ) + Vector( 0, 0, 180 )
+ batonRight.SetAngles( angs )
+ batonRight.SetParent( marvin, "R_HAND", true )
+
+ entity batonLeft = CreatePropDynamic( SAFETY_BATON_MODEL )
+ batonLeft.SetParent( marvin, "L_HAND" )
+
+ marvin.NotSolid() // don't want other NPCs to path around the ATC marvins
+
+ array<entity> cleanupEnts = [ batonRight, batonLeft ]
+
+ OnThreadEnd(
+ function() : ( cleanupEnts )
+ {
+ foreach ( ent in cleanupEnts )
+ {
+ if ( IsValid( ent ) )
+ {
+ ent.ClearParent()
+ ent.Destroy()
+ }
+ }
+ }
+ )
+
+ WaitFrame()
+ marvinInfo.skitAnim = anim
+ /*
+ array<string> anims = [ "mv_trafic_controller_A", "mv_trafic_controller_B" ]
+ while( 1 )
+ {
+ marvin.Anim_Play( anims.getrandom() )
+ marvin.SetPlaybackRate( RandomFloatRange( 0.9, 1.0 ) )
+ waitthread WaittillAnimDone( marvin )
+ }
+ */
+ marvin.SetPlaybackRate( RandomFloatRange( 0.9, 1.0 ) )
+ SkitGuy_PlayAnim( marvinInfo, skipAheadTime )
+
+ WaitForever()
+}
+
+
+
+// =======================================
+// ============ GAUNTLET MODE ============
+// =======================================
+// Just runs the Gauntlet over and over.
+
+void function Training_Setup_GauntletMode( entity player )
+{
+ Training_EnvArtColorCorrection_SetEnabled( true )
+
+ entity ogStart = GetEntByScriptName( "og_near_leaderboard" )
+ entity og = Training_SpawnOGPilot( ogStart )
+ Training_OG_Idles( ogStart, "OG_Leaderboard_D_idle" )
+
+ //TeleportPlayerAndBT( "playerstart_gauntlet_challenge" )
+ // HACK better start position
+ player.SetOrigin( < -5179, 279.129, 32.0313 > )
+ player.SetAngles( < 0, 80.7242, 0 > )
+}
+
+void function Training_GauntletModeStart( entity player )
+{
+ file.gauntletMode = true
+
+ waitthread Training_GauntletChallenge( player )
+}
+
+void function GauntletMode_Finished( entity player )
+{
+ Assert( file.gauntletMode )
+
+ float fadeTime = 3.0
+ ScreenFade( player, 0, 0, 0, 255, fadeTime, -1, FFADE_OUT | FFADE_STAYOUT )
+ wait fadeTime
+
+ // Dump player back to menu
+ ClientCommand( player, "disconnect" )
+
+ WaitForever() // defensive- don't want any extra stuff happening right before level transition
+}
+
+
+
+#if DEV
+// ===============================================
+// ============ RECORD GAUNTLET GHOSTS ===========
+// ===============================================
+void function TrainingGauntlet_RecordGhostStart_FirstRun( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_FIRSTRUN )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_WIP( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_WIP )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_01( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_01 )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_02( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_02 )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_03( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_03 )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_04( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_04 )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_05( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_05 )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_06( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_06 )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_07( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_07 )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_08( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_08 )
+}
+
+void function TrainingGauntlet_RecordGhostStart_Challenge_09( entity player )
+{
+ TrainingGauntlet_RecordGhost_CommonStart( player, GetTrainingGauntlet(), GHOST_NAME_CHAL_09 )
+}
+
+
+void function TrainingGauntlet_RecordGhost_CommonStart( entity player, GauntletInfo gauntlet, string ghostFileName )
+{
+ thread TrainingGauntlet_TeleportPlayerAtFinishLine( player )
+
+ TeleportPlayerAndBT( "gauntlet_startpoint" )
+
+ Gauntlet_Player_GhostRecordOrPlayback( player, gauntlet, ghostFileName )
+
+ while ( 1 )
+ wait 5
+}
+#endif //DEV
+
+
+
+// ============================================
+// ============ TRAINING POD STUFF ============
+// ============================================
+void function SetupTrainingPod()
+{
+ file.trainingPod = GetEntByScriptName( "training_pod" )
+ file.trainingPod.DisableHibernation()
+
+ TrainingPod_SetupInteriorDLights()
+
+ array<string> laserAttachNames = [ "fx_laser_L", "fx_laser_R" ]
+
+ foreach ( attachName in laserAttachNames )
+ {
+ entity emitterEnt = CreateScriptMover( file.trainingPod.GetOrigin() )
+ int attachID = file.trainingPod.LookupAttachment( attachName )
+ vector attachAng = file.trainingPod.GetAttachmentAngles( attachID )
+
+ TrainingPod_LaserEmitter emitter
+ emitter.ent = emitterEnt
+ emitter.attachName = attachName
+ emitter.ogAng = attachAng
+
+ file.trainingPodLaserEmitters.append( emitter )
+ }
+
+ // HACK we do this later as well to reset the emitter positions, so it's a separate function
+ TrainingPod_SnapLaserEmittersToAttachPoints()
+
+ //file.trainingPod.SetAngles( Vector( 0, 109, 0 ) ) // these angles are a little better for seeing the room
+}
+
+void function TrainingPod_SetupInteriorDLights()
+{
+ entity pod = file.trainingPod
+
+ TrainingPod_dLightMapping m1
+ m1.scriptAlias = "console1"
+ m1.fxName = FX_POD_DLIGHT_CONSOLE1
+ m1.attachName = "light_console1"
+ file.trainingPodDLightMappings.append( m1 )
+
+ TrainingPod_dLightMapping m2
+ m2.scriptAlias = "console2"
+ m2.fxName = FX_POD_DLIGHT_CONSOLE2
+ m2.attachName = "light_console2"
+ file.trainingPodDLightMappings.append( m2 )
+
+ //TrainingPod_dLightMapping m3
+ //m3.scriptAlias = "backlight_side_L"
+ //m3.fxName = FX_POD_DLIGHT_BACKLIGHT_SIDE
+ //m3.attachName = "light_back1"
+ //file.trainingPodDLightMappings.append( m3 )
+
+ //TrainingPod_dLightMapping m4
+ //m4.scriptAlias = "backlight_side_R"
+ //m4.fxName = FX_POD_DLIGHT_BACKLIGHT_SIDE
+ //m4.attachName = "light_back2"
+ //file.trainingPodDLightMappings.append( m4 )
+
+ //TrainingPod_dLightMapping m5
+ //m5.scriptAlias = "backlight_top"
+ //m5.fxName = FX_POD_DLIGHT_BACKLIGHT_TOP
+ //m5.attachName = "light_backtop"
+ //file.trainingPodDLightMappings.append( m5 )
+}
+
+void function TrainingPod_TurnOnInteriorDLights_Delayed( entity player, float delay )
+{
+ player.EndSignal( "OnDestroy" )
+
+ wait delay
+
+ TrainingPod_TurnOnInteriorDLight( "console1" )
+ TrainingPod_TurnOnInteriorDLight( "console2" )
+}
+
+void function TrainingPod_TurnOnInteriorDLight( string scriptAlias )
+{
+ entity pod = file.trainingPod
+
+ int idx
+ TrainingPod_dLightMapping thisMapping
+ foreach ( mappingIdx, mapping in file.trainingPodDLightMappings )
+ {
+ if ( mapping.scriptAlias == scriptAlias )
+ {
+ thisMapping = mapping
+ idx = mappingIdx
+ break
+ }
+ }
+
+ Assert ( thisMapping.scriptAlias != "", "Couldn't find pod dlight mapping for alias " + scriptAlias )
+
+ entity fxHandle = PlayLoopFXOnEntity( thisMapping.fxName, pod, thisMapping.attachName )
+ file.trainingPodDLightMappings[ idx ].fxHandle = fxHandle
+}
+
+void function TrainingPod_KillInteriorDLights_Delayed( entity player, float delay )
+{
+ player.EndSignal( "OnDestroy" )
+
+ wait delay
+
+ TrainingPod_KillInteriorDLights()
+}
+
+void function TrainingPod_KillInteriorDLights()
+{
+ foreach ( idx, mapping in file.trainingPodDLightMappings )
+ {
+ if ( !IsValid_ThisFrame( mapping.fxHandle ) )
+ continue
+
+ KillFX( mapping.fxHandle )
+
+ file.trainingPodDLightMappings[ idx ].fxHandle = null
+ }
+}
+
+void function TrainingPod_SnapLaserEmittersToAttachPoints()
+{
+ foreach ( TrainingPod_LaserEmitter emitter in file.trainingPodLaserEmitters )
+ {
+ int attachID = file.trainingPod.LookupAttachment( emitter.attachName )
+ vector attachOrg = file.trainingPod.GetAttachmentOrigin( attachID )
+ vector attachAng = file.trainingPod.GetAttachmentAngles( attachID )
+
+ emitter.ent.ClearParent()
+ emitter.ent.SetOrigin( attachOrg ) // HACK set this to ANYTHING (even 0, 0, 0) and the position is correct, otherwise it's offset from the attachpoint when parented
+ emitter.ent.SetParent( file.trainingPod, emitter.attachName )
+ }
+}
+
+void function TrainingPod_Interior_BootSequence( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ entity pod = file.trainingPod
+
+ TrainingPod_InteriorFX_CommonSetup( pod )
+
+ FlagSet( "PodIntro_InteriorBootSequence_Starting" )
+
+ EmitSoundOnEntity( player, "NPE_Scr_SimPod_PowerUp" )
+
+ // Transition screen FX
+ thread PlayFXOnEntity_Delayed( player, FX_POD_SCREEN_IN, player, 2.35 )
+
+ // GLOW LIGHTS
+ TrainingPod_GlowLightsTurnOn()
+
+ // LASERS
+ float longestSweepTime = -1
+ foreach ( emitter in file.trainingPodLaserEmitters )
+ {
+ float sweepTime = RandomFloatRange( 2.9, 3.15 )
+ if ( sweepTime > longestSweepTime )
+ longestSweepTime = sweepTime
+
+ thread LaserSweep( player, sweepTime, emitter, pod, "top" )
+ }
+
+ wait longestSweepTime
+
+ player.Signal( "PodInteriorSequenceDone" )
+}
+
+void function TrainingPod_InteriorFX_CommonSetup( entity pod )
+{
+ if ( file.trainingPodLaserEmitters.len() )
+ {
+ TrainingPod_KillLasers( pod )
+ TrainingPod_ResetLaserEmitterRotation( pod )
+ }
+
+ TrainingPod_KillGlowFX( pod )
+}
+
+// NOTE startPosition is actually inverted from what I think it should be. Tag orientation issue, maybe?
+void function LaserSweep( entity player, float totalTime, TrainingPod_LaserEmitter emitter, entity pod, string startPosition = "bottom" )
+{
+ //float startTime = Time()
+
+ player.EndSignal( "OnDestroy" )
+ emitter.ent.EndSignal( "OnDeath" )
+
+ emitter.sweepDone = false
+
+ //printt( "emitter og angles:", emitter.GetAngles() )
+
+ vector vecToPlayerEye = ( player.EyePosition() + Vector( 0, 0, 7 ) ) - emitter.ent.GetOrigin() // eye position offset is a HACK, not sure why I need to do that here.
+ vector centerAng = VectorToAngles( vecToPlayerEye )
+ vector topAng = centerAng + Vector( -270, 0, 0 )
+ vector bottomAng = centerAng + Vector( -90, 0, 0 )
+
+ //vector topAng = emitter.GetAngles() + < 90, -8, 0 >
+ //vector bottomAng = emitter.GetAngles() + < -90, 8, 0 >
+
+ //printt( "==== starting at:", startPosition )
+ //printt( "topAng:", topAng )
+ //printt( "bottomAng:", bottomAng )
+ //printt( "centerAng:", centerAng )
+
+ vector lastBigSweepAng
+
+ if ( startPosition == "bottom")
+ {
+ emitter.ent.SetAbsAngles( bottomAng )
+ lastBigSweepAng = bottomAng
+ }
+ else
+ {
+ emitter.ent.SetAbsAngles( topAng )
+ lastBigSweepAng = topAng
+ }
+ //printt( "setting start angles to:", lastBigSweepAng )
+
+ entity fxHandle = PlayLoopFXOnEntity( FX_POD_LASER, emitter.ent )
+ emitter.fxHandle = fxHandle
+
+ int numBigSweeps = 2
+ float finalCenterTime = totalTime * 0.15
+ float bigSweepTime = ( totalTime - finalCenterTime ) / numBigSweeps
+
+ float bigSweep_AccelTime = 0
+ float bigSweep_DecelTime = bigSweepTime * 0.2
+
+ // do the big sweeps
+ vector nextBigSweepAng
+ for ( int i = 0; i < numBigSweeps; i++ )
+ {
+ nextBigSweepAng = topAng
+ if ( lastBigSweepAng == topAng )
+ nextBigSweepAng = bottomAng
+
+ //printt( "rotating to", nextBigSweepAng )
+
+ emitter.ent.NonPhysicsRotateTo( nextBigSweepAng, bigSweepTime, bigSweep_AccelTime, bigSweep_DecelTime )
+
+ float waitTime = bigSweepTime
+ if ( i < numBigSweeps - 1 )
+ waitTime = bigSweepTime - 0.1
+
+ wait waitTime
+
+ lastBigSweepAng = nextBigSweepAng
+ }
+
+ // finish with centering move
+ //printt( "centering to", centerAng )
+
+ float finalCenter_AccelTime = 0
+ float finalCenter_DecelTime = finalCenterTime * 0.2
+
+ emitter.ent.NonPhysicsRotateTo( centerAng, finalCenterTime, finalCenter_AccelTime, finalCenter_DecelTime )
+ wait finalCenterTime
+
+ emitter.sweepDone = true
+ //printt( "laser sweep done, total time", Time() - startTime, "should have been", totalTime )
+}
+
+void function TrainingPod_KillLasers( entity pod, bool doEndCap = false )
+{
+ foreach ( emitter in file.trainingPodLaserEmitters )
+ {
+ if ( IsValid_ThisFrame( emitter.fxHandle ) )
+ {
+ if ( !doEndCap )
+ {
+ //printt( "killing laser FX", emitter.fxHandle )
+ KillFX( emitter.fxHandle )
+ }
+ else
+ {
+ //printt( "killing laser FX with endcap", emitter.fxHandle )
+ KillFXWithEndcap( emitter.fxHandle )
+ }
+ }
+
+ emitter.fxHandle = null
+ }
+}
+
+void function TrainingPod_ResetLaserEmitterRotation( entity pod )
+{
+ if ( !file.trainingPodLaserEmitters.len() )
+ return
+
+ foreach ( emitter in file.trainingPodLaserEmitters )
+ {
+ //reset to start position
+ emitter.ent.NonPhysicsRotateTo( emitter.ogAng, 0.05, 0.0, 0.0 )
+ }
+}
+
+void function TrainingPod_GlowLightsArraySetup()
+{
+ array<TrainingPod_GlowLightRow> rows
+
+ // rows are set up bottom to top
+ // lights are set up outside to in (in = door close seam; opposite for each side)
+ // process two rows per loop (one for each door side)
+
+ TrainingPod_GlowLightRow row1
+ row1.fxSpotsL = [ "fx_glow_L_door012", "fx_glow_L_door013" ]
+ row1.fxSpotsR = [ "fx_glow_R_door014", "fx_glow_R_door013" ]
+ rows.append( row1 )
+
+ TrainingPod_GlowLightRow row2
+ row2.fxSpotsL = [ "fx_glow_L_door014", "fx_glow_L_door011" ]
+ row2.fxSpotsR = [ "fx_glow_R_door012", "fx_glow_R_door011" ]
+ rows.append( row2 )
+
+ TrainingPod_GlowLightRow row3
+ row3.fxSpotsL = [ "fx_glow_L_door09", "fx_glow_L_door010" ]
+ row3.fxSpotsR = [ "fx_glow_R_door09", "fx_glow_R_door010" ]
+ rows.append( row3 )
+
+ TrainingPod_GlowLightRow row4
+ row4.fxSpotsL = [ "fx_glow_L_door07", "fx_glow_L_door08" ]
+ row4.fxSpotsR = [ "fx_glow_R_door07", "fx_glow_R_door08" ]
+ rows.append( row4 )
+
+ TrainingPod_GlowLightRow row5
+ row5.fxSpotsL = [ "fx_glow_L_door05", "fx_glow_L_door06" ]
+ row5.fxSpotsR = [ "fx_glow_R_door05", "fx_glow_R_door06" ]
+ rows.append( row5 )
+
+ TrainingPod_GlowLightRow row6
+ row6.fxSpotsL = [ "fx_glow_L_door03", "fx_glow_L_door04" ]
+ row6.fxSpotsR = [ "fx_glow_R_door03", "fx_glow_R_door04" ]
+ rows.append( row6 )
+
+ TrainingPod_GlowLightRow row7
+ row7.fxSpotsL = [ "fx_glow_L_door01", "fx_glow_L_door02" ]
+ row7.fxSpotsR = [ "fx_glow_R_door01", "fx_glow_R_door02" ]
+ rows.append( row7 )
+
+ file.trainingPodGlowLightRows = rows
+}
+
+void function TrainingPod_GlowLightsTurnOn( bool instantOn = false )
+{
+ //float startTime = Time()
+
+ entity pod = file.trainingPod
+
+ foreach ( TrainingPod_GlowLightRow row in file.trainingPodGlowLightRows )
+ {
+ float loopTime = Time()
+
+ array<string> group1 = [ row.fxSpotsL[0], row.fxSpotsR[0] ]
+ array<string> group2 = [ row.fxSpotsL[1], row.fxSpotsR[1] ]
+ table< int, array < string > > lightgroups
+ lightgroups[0] <- group1
+ lightgroups[1] <- group2
+
+ foreach ( idx, group in lightgroups )
+ {
+ foreach ( attachName in group )
+ {
+ entity fxHandle = PlayLoopFXOnEntity( FX_POD_GLOWLIGHT, pod, attachName )
+ file.trainingPodGlowLightFXHandles.append( fxHandle )
+ }
+
+ if ( !instantOn )
+ wait 0.1
+ }
+
+ /*
+ // both sides have same number of lights
+ int numLights = 2
+ for ( int i = 0; i < numLights; i++ )
+ {
+ foreach ( var side in row )
+ {
+ string attachName = side[ i ]
+ entity fxHandle = PlayLoopFXOnEntity( FX_POD_GLOWLIGHT, pod, attachName )
+ file.trainingPodGlowLightFXHandles.append( fxHandle )
+ }
+
+ if ( lightWait > 0 )
+ wait lightWait
+ }
+
+ if ( rowWait > 0)
+ wait rowWait
+ */
+ }
+
+ //printt( "glow lights turn on took", Time() - startTime, "secs" )
+}
+
+void function TrainingPod_KillGlowFX( entity pod )
+{
+ foreach ( fxHandle in file.trainingPodGlowLightFXHandles )
+ {
+ if ( !IsValid_ThisFrame( fxHandle ) )
+ continue
+
+ KillFX( fxHandle )
+ }
+
+ file.trainingPodGlowLightFXHandles = []
+}
+
+void function TrainingPod_Interior_ShutdownSequence( entity player, float shutdownTime )
+{
+ player.EndSignal( "OnDestroy" )
+
+ entity pod = file.trainingPod
+
+ TrainingPod_InteriorFX_CommonSetup( pod )
+
+ // TURN ON GLOW LIGHTS
+ TrainingPod_GlowLightsTurnOn( true )
+
+ // TURN ON LASERS
+ TrainingPod_LasersInstantOn( player, pod )
+
+ player.WaitSignal( "TrainingPod_BeginInteriorShutdown" )
+
+ thread TrainingPod_LasersShutDown( player, pod, shutdownTime * 0.6 )
+ thread TrainingPod_GlowLightsShutDown( player, pod, shutdownTime )
+
+ wait shutdownTime
+ printt( "interior shutdown done" )
+}
+
+void function TrainingPod_LasersInstantOn( entity player, entity pod )
+{
+ foreach ( emitter in file.trainingPodLaserEmitters )
+ {
+ float dist = Distance( player.EyePosition(), emitter.ent.GetOrigin() )
+ Assert( dist <= 30, "player is usually about 20 units away when we try to set the laser angles. If very far away, the math will crash the game. Dist: " + dist )
+
+ vector vecToPlayerEye = ( player.EyePosition() + Vector( 0, 0, 7 ) ) - emitter.ent.GetOrigin() // eye position offset is a HACK, not sure why I need to do that here.
+ vector centerAng = VectorToAngles( vecToPlayerEye )
+ emitter.ent.NonPhysicsRotateTo( centerAng, 0.1, 0.0, 0.0 ) // SETANGLES DOES NOT WORK! You have to rotate it for the FX to follow.
+
+ emitter.fxHandle = PlayLoopFXOnEntity( FX_POD_LASER, emitter.ent )
+ }
+}
+
+void function TrainingPod_LasersShutDown( entity player, entity pod, float shutdownTime )
+{
+ player.EndSignal( "OnDestroy" )
+
+ foreach ( emitter in file.trainingPodLaserEmitters )
+ {
+ vector vecToPlayerEye = ( player.EyePosition() + Vector( 0, 0, 7 ) ) - emitter.ent.GetOrigin() // eye position offset is a HACK, not sure why I need to do that here.
+ vector centerAng = VectorToAngles( vecToPlayerEye )
+ emitter.ent.NonPhysicsRotateTo( centerAng, 0.1, 0.0, 0.0 ) // SETANGLES DOES NOT WORK! You have to rotate it for the FX to follow.
+ }
+
+ wait shutdownTime * 0.25
+
+ float moveDownTime = shutdownTime * 0.75
+ float accelTime = moveDownTime * 0.25
+ float decelTime = moveDownTime * 0.1
+
+ foreach ( TrainingPod_LaserEmitter emitter in file.trainingPodLaserEmitters )
+ {
+ vector finalAng = emitter.ent.GetAngles() + Vector( 30, 0, 0 ) // not sure why adding pitch makes them appear to drop down
+ emitter.ent.NonPhysicsRotateTo( finalAng, moveDownTime, accelTime, decelTime )
+ }
+
+ wait moveDownTime
+ TrainingPod_KillLasers( pod, true )
+}
+
+void function TrainingPod_GlowLightsShutDown( entity player, entity pod, float shutdownTime )
+{
+ player.EndSignal( "OnDestroy" )
+
+ float shutdownDelay = shutdownTime * 0.65
+ float finishEarly = shutdownTime * 0.1
+ float glowLightShutDownDuration = shutdownTime - shutdownDelay - finishEarly
+
+ wait shutdownDelay
+
+ Assert( glowLightShutDownDuration > 0.0 )
+
+ float timePerLight = glowLightShutDownDuration / file.trainingPodGlowLightFXHandles.len().tofloat()
+
+ foreach ( entity fxHandle in file.trainingPodGlowLightFXHandles )
+ {
+ if ( !IsValid_ThisFrame( fxHandle ) )
+ continue
+
+ thread KillFXWithEndcap( fxHandle )
+ wait timePerLight
+ }
+
+ file.trainingPodGlowLightFXHandles = []
+}
+
+void function TrainingPod_ViewConeLock_Shared( entity player )
+{
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -25 )
+ player.PlayerCone_SetMaxYaw( 25 )
+ player.PlayerCone_SetMinPitch( -30 )
+}
+
+void function TrainingPod_ViewConeLock_PodOpen( entity player )
+{
+ TrainingPod_ViewConeLock_Shared( player )
+ player.PlayerCone_SetMaxPitch( 35 )
+}
+
+void function TrainingPod_ViewConeLock_PodClosed( entity player )
+{
+ TrainingPod_ViewConeLock_Shared( player )
+ player.PlayerCone_SetMaxPitch( 32 )
+}
+
+void function TrainingPod_ViewConeLock_SemiStrict( entity player )
+{
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -10 )
+ player.PlayerCone_SetMaxYaw( 10 )
+ player.PlayerCone_SetMinPitch( -10 )
+ player.PlayerCone_SetMaxPitch( 10 )
+}
+
+void function TrainingPod_ViewConeLock_Strict( entity player )
+{
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( 0 )
+ player.PlayerCone_SetMaxYaw( 0 )
+ player.PlayerCone_SetMinPitch( 0 )
+ player.PlayerCone_SetMaxPitch( 0 )
+}
+
+
+// ==================================================
+// ============ CLIENT COMMAND CALLBACKS ============
+// ==================================================
+
+bool function ClientCommand_Training_SetInputType( entity player, array<string> args )
+{
+ int inputType = args[0].tointeger()
+
+ Assert( inputType == INPUT_TYPE_CONTROLLER || inputType == INPUT_TYPE_KBM )
+ //printt( "Training- client input type updated:", inputType )
+ file.playerInputType = inputType
+ return true
+}
+
+bool function ClientCommand_Training_PlayerPressedUse( entity player, array<string> args )
+{
+ FlagSet( "PlayerPressedUse" )
+ return true
+}
+
+bool function ClientCommand_Training_PlayerReloaded( entity player, array<string> args )
+{
+ FlagSet( "PlayerReloaded" )
+ return true
+}
+
+bool function ClientCommand_LookTarget_Top( entity player, array<string> args )
+{
+ player.ResetIdleTimer()
+ printt( "ClientCommand_LookTarget_Top" )
+ FlagSet( "PlayerLookedAtTopTarget" )
+ return true
+}
+
+bool function ClientCommand_LookTarget_Bottom( entity player, array<string> args )
+{
+ player.ResetIdleTimer()
+ printt( "ClientCommand_LookTarget_Bottom" )
+ FlagSet( "PlayerLookedAtBottomTarget" )
+ return true
+}
+
+
+
+// ===============================
+// ============ DOORS ============
+// ===============================
+void function DoorOpenFast( string doorEntName )
+{
+ entity door = GetEntByScriptName( doorEntName )
+ door.Hide()
+ door.NotSolid()
+
+ entity navBlocker = door.GetLinkEnt()
+ navBlocker.NotSolid()
+ ToggleNPCPathsForEntity( navBlocker, true )
+}
+
+void function DoorCloseFast( string doorEntName )
+{
+ entity door = GetEntByScriptName( doorEntName )
+ door.Show()
+ door.Solid()
+
+ entity navBlocker = door.GetLinkEnt()
+ navBlocker.Solid()
+ ToggleNPCPathsForEntity( navBlocker, false )
+}
+
+
+void function OpenZenGardenExitDoor()
+{
+ DoorOpenFast( "zengarden_door" )
+}
+
+void function CloseZenGardenExitDoor()
+{
+ DoorCloseFast( "zengarden_door" )
+}
+
+void function OpenGauntletDoor()
+{
+ DoorOpenFast( "gauntlet_door" )
+}
+
+
+
+// ===================================
+// ========= LOUDSPEAKER VO ==========
+// ===================================
+void function LoudspeakerVO_Setup()
+{
+ vector loudspeakerPos = <10524, -9660, -5896> // HACK
+ file.loudspeaker = CreateScriptMover( loudspeakerPos, <0,0,0> )
+
+ // ======= PA ANNOUNCEMENTS: POD INTRO =======
+ // "Inbound to Planet Typhon. Subspace rendezvous in approximately 20 minutes."
+ RegisterLoudspeakerVO( "intro_0", "diag_sp_addtional_TR411_01_mcor_shipPA", 8.0 )
+
+ // "Major Anderson, please report to the briefing room."
+ RegisterLoudspeakerVO( "intro_1", "diag_sp_addtional_TR411_02_mcor_grunt1", 3.0 )
+
+ // "Reminder to dock personnel: Titan ordnance is a Type 3 Hazardous Material."
+ RegisterLoudspeakerVO( "intro_2", "diag_sp_addtional_TR411_03_mcor_shipPA", 6.0 )
+
+ // "Captain Cole to communications."
+ RegisterLoudspeakerVO( "intro_3", "diag_sp_addtional_TR411_04_mcor_grunt1", 3.0 )
+
+ // "Running Lifeboat diagnostic test two point one. All Mark Eight lifeboats are in the green."
+ RegisterLoudspeakerVO( "intro_4", "diag_sp_addtional_TR411_05_mcor_shipPA", 6.0 )
+
+ // "3rd Militia Grenadiers - prep dropship MacAllan 17."
+ RegisterLoudspeakerVO( "intro_5", "diag_sp_addtional_TR411_06_mcor_grunt1", 4.0 )
+
+
+ // ======= PA ANNOUNCEMENTS: MEET OG =======
+ // "Prepare for Typhon atmospheric entry in less than three minutes."
+ RegisterLoudspeakerVO( "outro_01", "diag_sp_addtional_TR411_07_mcor_shipPA", 7.0 )
+
+ // "Powering down all non-essential systems."
+ RegisterLoudspeakerVO( "outro_01_1", "diag_sp_outro_TR171_01_01_mcor_shipPA", 4.0 )
+
+ // "All personnel to battle stations."
+ RegisterLoudspeakerVO( "outro_01_2", "diag_sp_outro_TR171_02_01_mcor_shipPA", 3.0 )
+
+ // "This is not a drill."
+ RegisterLoudspeakerVO( "outro_01_3", "diag_sp_outro_TR171_03_01_mcor_shipPA", 3.0 )
+
+ // "Titan bays two through five, drop clearance confirmed."
+ RegisterLoudspeakerVO( "outro_03", "diag_sp_addtional_TR411_09_mcor_shipPA", 5.0 )
+
+ // "Infantry teams Stork three, Elk four, Shark six, Badger one, prepare for emergency atmospheric drop sequence"
+ RegisterLoudspeakerVO( "outro_04", "diag_sp_addtional_TR411_10_mcor_grunt1", 7.0 )
+
+ // "All available medical personnel, report to Med Central for tasking."
+ RegisterLoudspeakerVO( "outro_05", "diag_sp_addtional_TR411_11_mcor_grunt2", 5.0 )
+
+ // "Titan mech team - Rabbit six, prep the Vanguards."
+ RegisterLoudspeakerVO( "outro_06", "diag_sp_addtional_TR411_12_mcor_grunt3", 4.0 )
+
+ // "We need all Riflemen to docking bay four. Dropships standing by."
+ RegisterLoudspeakerVO( "outro_07", "diag_sp_addtional_TR411_13_mcor_grunt1", 5.0 )
+
+ // "Incoming hostile ship - Designation: IMS Malta. Battle stations."
+ RegisterLoudspeakerVO( "outro_08", "diag_sp_addtional_TR411_14_mcor_shipPA", 6.0 )
+
+ // "Special Recon Squad deploy from Drop Bay thirty-seven - now."
+ RegisterLoudspeakerVO( "outro_10", "diag_sp_addtional_TR411_16_mcor_grunt2", 5.0 )
+
+ // "Infantry teams 2nd Militia Fusiliers, Raptor three, target the IMS Malta."
+ RegisterLoudspeakerVO( "outro_11", "diag_sp_addtional_TR411_17_mcor_grunt3", 6.0 )
+
+ // "Caution - Fuel leak in Cargo Bay eighty - five. Decompressing for fire suppression."
+ RegisterLoudspeakerVO( "outro_12", "diag_sp_addtional_TR411_18_mcor_shipPA", 6.0 )
+}
+
+void function RegisterLoudspeakerVO( string scriptAlias, string soundAlias, float duration = 6.0 )
+{
+ Assert( !( scriptAlias in file.loudspeakerVO ), "duplicate scriptAlias " + scriptAlias )
+
+ LoudspeakerVO_Info voInfo
+ voInfo.scriptAlias = scriptAlias
+ voInfo.soundAlias = soundAlias
+ voInfo.duration = duration
+
+ file.loudspeakerVO[ scriptAlias ] <- voInfo
+}
+
+void function PlayLoudspeakerVO( string scriptAlias, float delay = 0.0 )
+{
+ Assert( scriptAlias in file.loudspeakerVO )
+ LoudspeakerVO_Info voInfo = file.loudspeakerVO[ scriptAlias ]
+
+ string soundAlias = voInfo.soundAlias
+
+ if ( delay > 0 )
+ wait delay
+
+ entity emitter = file.loudspeaker
+ emitter.Signal( "LoudspeakerVO_Stop" )
+ emitter.EndSignal( "LoudspeakerVO_Stop" )
+
+ OnThreadEnd(
+ function() : ( emitter, soundAlias )
+ {
+ if ( IsValid( emitter ) )
+ FadeOutSoundOnEntity( emitter, soundAlias, 2.0 )
+ }
+ )
+
+ //printt( "playing loudspeaker VO", scriptAlias, "/", soundAlias )
+ EmitSoundOnEntity( emitter, soundAlias )
+ wait voInfo.duration
+}
+
+void function LoopLoudspeakerVO( array<string> scriptAliases, string endFlag = "", float minPause = -1, float maxPause = -1 )
+{
+ if ( endFlag != "" )
+ {
+ if ( Flag( endFlag ) )
+ return
+
+ FlagEnd( endFlag )
+ }
+
+ while ( 1 )
+ {
+ foreach ( scriptAlias in scriptAliases )
+ {
+ waitthread PlayLoudspeakerVO( scriptAlias )
+
+ if ( minPause > 0 && maxPause >= minPause )
+ wait RandomFloatRange( minPause, maxPause )
+ }
+ }
+}
+
+
+
+// ==============================
+// ============ MISC ============
+// ==============================
+
+entity function Training_SpawnAnOG( entity startSpot )
+{
+ entity og = CreateSoldier( TEAM_MILITIA, startSpot.GetOrigin(), startSpot.GetAngles() )
+ og.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+ DispatchSpawn( og )
+
+ TakeAllWeapons( og )
+
+ og.SetModel( OG_PILOT_MODEL )
+
+ og.SetTitle( "#TRAINING_OG_PILOT_NAME" )
+ ShowName( og )
+
+ Training_OGPilot_SetHelmetOn( og, true )
+
+ og.DisableHibernation()
+ og.SetNoTarget( true )
+ og.UseSequenceBounds( true )
+ MakeInvincible( og )
+ og.kv.scriptedAnimForceInterrupt = true
+
+ og.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+ og.SetHologram()
+
+ return og
+}
+
+void function Training_OGPilot_SetHelmetOn( entity og, bool setOn )
+{
+ int headIdx = og.FindBodyGroup( "head" )
+
+ int submodelIdx = OG_PILOT_MODEL_HEAD_IDX_BARE
+ if ( setOn )
+ submodelIdx = OG_PILOT_MODEL_HEAD_IDX_HELMET
+
+ og.SetBodygroup( headIdx, submodelIdx )
+
+ /*
+ int decalIdx = og.FindBodyGroup( "decal" )
+ if ( decalIdx == -1 )
+ return
+
+ submodelIdx = OG_PILOT_MODEL_DECAL_IDX_BARE
+ if ( setOn )
+ submodelIdx = OG_PILOT_MODEL_DECAL_IDX
+
+ og.SetBodygroup( decalIdx, submodelIdx )
+ */
+}
+
+entity function Training_SpawnOGPilot( entity startSpot )
+{
+ if ( IsValid( file.ogPilot ) )
+ file.ogPilot.Destroy()
+
+ entity og = Training_SpawnAnOG( startSpot )
+ file.ogPilot = og
+
+ return og
+ }
+
+entity function Training_SpawnOGTwin( entity startSpot )
+{
+ if ( IsValid( file.ogTwin ) )
+ file.ogTwin.Destroy()
+
+ entity ogTwin = Training_SpawnAnOG( startSpot )
+ file.ogTwin = ogTwin
+
+ return ogTwin
+}
+
+entity function GetOGPilot()
+{
+ Assert( IsValid( file.ogPilot ) )
+ return file.ogPilot
+}
+
+entity function GetOGTwin()
+{
+ if ( !IsValid( file.ogTwin ) )
+ return null
+
+ return file.ogTwin
+}
+
+
+void function Training_OG_NagPlayerUntilFlag_Sitting( entity player, array<string> nagAliases, float nagInterval, entity idleRef, string endFlag, string talkAnim = "", string idleAnim = "" )
+{
+ entity og = GetOGPilot()
+ Training_NPC_NagPlayerUntilFlag_Sitting( og, player, nagAliases, nagInterval, idleRef, endFlag, talkAnim, idleAnim )
+}
+
+void function Training_OG_NagPlayerUntilFlag( entity player, array<string> nagAliases, float nagInterval, entity idleRef, string endFlag, string talkAnim = "", string idleAnim = "" )
+{
+ entity og = GetOGPilot()
+ Training_NPC_NagPlayerUntilFlag( og, player, nagAliases, nagInterval, idleRef, endFlag, talkAnim, idleAnim )
+}
+
+void function Training_NPC_NagPlayerUntilFlag_Sitting( entity npc, entity player, array<string> nagAliases, float nagInterval, entity idleRef, string endFlag, string talkAnim = "", string idleAnim = "" )
+{
+ if ( talkAnim == "" )
+ talkAnim = ANIM_OG_SITTING_TALK
+
+ if ( idleAnim == "" )
+ idleAnim = ANIM_OG_SITTING_IDLE
+
+ Training_OG_NagPlayerUntilFlag( player, nagAliases, nagInterval, idleRef, endFlag, talkAnim, idleAnim )
+}
+
+void function Training_NPC_NagPlayerUntilFlag( entity npc, entity player, array<string> nagAliases, float nagInterval, entity idleRef, string endFlag, string talkAnim = "", string idleAnim = "" )
+{
+ player.EndSignal( "OnDestroy" )
+
+ if ( talkAnim == "" )
+ talkAnim = ANIM_OG_STANDING_TALK
+
+ if ( idleAnim == "" )
+ idleAnim = ANIM_OG_STANDING_IDLE
+
+ int nagIdx = 0
+ float nextNagTime = Time() + nagInterval
+
+ while ( !Flag( endFlag ) )
+ {
+ if ( Time() > nextNagTime )
+ {
+ waitthread Training_OG_Talks( nagAliases[nagIdx], idleRef, talkAnim, idleAnim, true )
+ nextNagTime = Time() + nagInterval
+
+ nagIdx++
+ if ( nagIdx >= nagAliases.len() )
+ nagIdx = 0
+ }
+
+ wait 0.1
+ }
+}
+
+void function Training_OG_Talks_Sitting( string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false )
+{
+ entity og = GetOGPilot()
+ Training_NPC_Talks_Sitting( og, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend )
+}
+
+void function Training_OG_Talks_Leaning( string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false )
+{
+ entity og = GetOGPilot()
+ Training_NPC_Talks_Leaning( og, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend )
+}
+
+void function Training_OG_Talks( string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false )
+{
+ entity og = GetOGPilot()
+ Training_NPC_Talks( og, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend )
+}
+
+void function Training_OG_Idles_Sitting( entity idleRef, string anim = "", bool useBlend = false )
+{
+ entity og = GetOGPilot()
+ Training_NPC_Idles_Sitting( og, idleRef, anim, useBlend )
+}
+
+void function Training_OG_Idles_SittingAndTalking( entity idleRef, string anim = "", bool useBlend = false )
+{
+ entity og = GetOGPilot()
+ Training_NPC_Idles_SittingAndTalking( og, idleRef, anim, useBlend )
+}
+
+void function Training_OG_Idles_Talking( entity idleRef, string anim = "", bool useBlend = false )
+{
+ entity og = GetOGPilot()
+ Training_NPC_Idles_Talking( og, idleRef, anim, useBlend )
+}
+
+void function Training_OG_Idles( entity idleRef, string anim = "", bool useBlend = false )
+{
+ entity og = GetOGPilot()
+ thread Training_NPC_Idles( og, idleRef, anim, useBlend )
+}
+
+void function Training_OG_ScriptedAnim( entity idleRef, string anim, bool useBlend = false )
+{
+ entity og = GetOGPilot()
+ Training_NPC_ScriptedAnim( og, idleRef, anim, useBlend )
+}
+
+
+void function Training_NPC_Talks_Sitting( entity npc, string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false )
+{
+ if ( talkAnim == "" )
+ talkAnim = ANIM_OG_SITTING_TALK
+
+ if ( idleAnim == "" )
+ idleAnim = ANIM_OG_SITTING_IDLE
+
+ Training_NPC_Talks( npc, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend )
+}
+
+void function Training_NPC_Talks_Leaning( entity npc, string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false )
+{
+ if ( talkAnim == "" )
+ talkAnim = ANIM_OG_LEANING_TALK
+
+ if ( idleAnim == "" )
+ idleAnim = ANIM_OG_LEANING_IDLE
+
+ Training_NPC_Talks( npc, voScriptAlias, idleRef, talkAnim, idleAnim, useBlend )
+}
+
+void function Training_NPC_Talks( entity npc, string voScriptAlias, entity idleRef, string talkAnim = "", string idleAnim = "", bool useBlend = false )
+{
+ npc.EndSignal( "OnDestroy" )
+ npc.EndSignal( "NPC_NewCommand" )
+
+ if ( talkAnim == "" )
+ talkAnim = ANIM_OG_STANDING_TALK
+
+ if ( idleAnim == "" )
+ idleAnim = ANIM_OG_STANDING_IDLE
+
+ npc.Anim_Stop()
+ if ( useBlend )
+ thread PlayAnim( npc, talkAnim, idleRef )
+ else
+ thread PlayAnim( npc, talkAnim, idleRef, null, 0.0 )
+
+ OnThreadEnd(
+ function() : ( npc, idleRef, idleAnim, useBlend )
+ {
+ if ( IsValid( npc ) )
+ Training_NPC_Idles( npc, idleRef, idleAnim, useBlend )
+ }
+ )
+
+ waitthread PlayDialogue( voScriptAlias, npc )
+}
+
+void function Training_NPC_Idles_Sitting( entity npc, entity idleRef, string anim = "", bool useBlend = false )
+{
+ if ( anim == "" )
+ anim = ANIM_OG_SITTING_IDLE
+
+ Training_NPC_Idles( npc, idleRef, anim, useBlend )
+}
+
+void function Training_NPC_Idles_SittingAndTalking( entity npc, entity idleRef, string anim = "", bool useBlend = false )
+{
+ if ( anim == "" )
+ anim = ANIM_OG_SITTING_TALK
+
+ Training_NPC_Idles( npc, idleRef, anim, useBlend )
+}
+
+void function Training_NPC_Idles_Talking( entity npc, entity idleRef, string anim = "", bool useBlend = false )
+{
+ if ( anim == "" )
+ anim = ANIM_OG_STANDING_TALK
+
+ thread Training_NPC_Idles( npc, idleRef, anim, useBlend )
+}
+
+void function Training_NPC_Idles( entity npc, entity idleRef, string anim = "", bool useBlend = false )
+{
+ npc.Signal( "NPC_NewCommand" )
+
+ if ( anim == "" )
+ anim = ANIM_OG_STANDING_IDLE
+
+ npc.Anim_Stop()
+
+ if ( useBlend )
+ thread PlayAnim( npc, anim, idleRef )
+ else
+ thread PlayAnim( npc, anim, idleRef, null, 0.0 )
+}
+
+void function Training_NPC_ScriptedAnim( entity npc, entity idleRef, string anim, bool useBlend = false )
+{
+ npc.Signal( "NPC_NewCommand" )
+
+ npc.Anim_Stop()
+
+ if ( useBlend )
+ PlayAnim( npc, anim, idleRef )
+ else
+ PlayAnim( npc, anim, idleRef, null, 0.0 )
+}
+
+void function Training_OG_Moves_ToSitting( entity moveToRef, string destAnim = "", float moveTimeOverride = -1 )
+{
+ if ( destAnim == "" )
+ destAnim = ANIM_OG_SITTING_IDLE
+
+ Training_OG_Moves( moveToRef, destAnim, moveTimeOverride, true )
+}
+
+void function Training_OG_Moves( entity moveToRef, string destAnim = "", float moveTimeOverride = -1, bool destAnim_isSitting = false )
+{
+ entity og = GetOGPilot()
+ og.Signal( "NPC_NewCommand" )
+
+ int ogAttachIdx = og.LookupAttachment( "CHESTFOCUS" )
+ vector startOrigin = og.GetAttachmentOrigin( ogAttachIdx )
+
+ const vector standingOffset = <0, 0, 42>
+ const vector sittingOffset = <0, 0, 20>
+ vector destHeightOffset = destAnim_isSitting ? sittingOffset : standingOffset
+ vector endOrigin = moveToRef.GetOrigin() + destHeightOffset
+
+ og.Freeze()
+
+ file.ogPilot = null
+
+ if ( IsValid( og ) )
+ {
+ og.NotSolid()
+ DissolveGhost( og )
+ }
+
+ entity newOG = Training_SpawnOGPilot( moveToRef )
+ newOG.Hide()
+
+ entity mover = CreateScriptMover( startOrigin, <0,0,0> )
+ int moverAttachIdx = mover.LookupAttachment( "REF" )
+ EmitSoundOnEntity( mover, "og_dissolve_trail" )
+ file.ogPathMover = mover
+
+ newOG.EndSignal( "OnDestroy" )
+ mover.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( mover, newOG, moveToRef, destAnim_isSitting, destAnim, ogAttachIdx )
+ {
+ if ( IsValid( mover ) )
+ {
+ StopSoundOnEntity( mover, "og_dissolve_trail" )
+ mover.Destroy()
+ file.ogPathMover = null
+ }
+
+ if ( IsValid( newOG ) )
+ {
+ StartParticleEffectOnEntity( newOG, GetParticleSystemIndex( GHOST_FLASH_EFFECT ), FX_PATTACH_POINT, ogAttachIdx )
+ newOG.Show()
+
+ if ( destAnim_isSitting )
+ Training_OG_Idles_Sitting( moveToRef, destAnim )
+ else
+ Training_OG_Idles( moveToRef, destAnim )
+ }
+ }
+ )
+
+ StartParticleEffectOnEntity( mover, GetParticleSystemIndex( GHOST_TRAIL_EFFECT ), FX_PATTACH_POINT_FOLLOW, moverAttachIdx )
+ StartParticleEffectOnEntity( mover, GetParticleSystemIndex( GHOST_FLASH_EFFECT ), FX_PATTACH_POINT, moverAttachIdx )
+ wait 0.5
+
+ float moveSpeed = 1350.0
+ float moveDist = Distance( startOrigin, endOrigin )
+ float moveTime = moveDist / moveSpeed
+ if ( moveTimeOverride > 0 )
+ moveTime = moveTimeOverride
+
+ float accel = moveTime * 0.1
+ float decel = moveTime * 0.1
+ mover.NonPhysicsMoveTo( endOrigin, moveTime, accel, decel )
+ wait moveTime - 0.1
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, endOrigin, "PathHologram_Materialized_training" )
+ StartParticleEffectOnEntity( mover, GetParticleSystemIndex( GHOST_FLASH_EFFECT ), FX_PATTACH_POINT, moverAttachIdx )
+ wait 0.1
+}
+
+entity function TeleportOG( string entName )
+{
+ Assert( IsValid( file.ogPilot ) )
+
+ file.ogPilot.Signal( "NPC_NewCommand" )
+ file.ogPilot.Anim_Stop()
+
+ entity teleportSpot = GetEntByScriptName( entName )
+
+ vector org = teleportSpot.GetOrigin()
+ vector ang = teleportSpot.GetAngles()
+ file.ogPilot.SetOrigin( org )
+ file.ogPilot.SetAngles( ang )
+
+ return teleportSpot
+}
+
+void function NPC_DisableArrivals( entity npc )
+{
+ printt( "disabling arrivals" )
+ npc.EnableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS )
+}
+
+void function NPC_EnableArrivals( entity npc )
+{
+ printt( "enabling arrivals" )
+ npc.DisableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS )
+}
+
+
+void function PlayerAndOGTeleport_Fancy( entity player, vector destPos, string ogTeleportSpotName, vector destAng = < -1, -1, -1 > )
+{
+ EndSignal( player, "OnDeath" )
+ thread FancyTeleport_EffectsAndSound( player, destPos )
+
+ player.WaitSignal( "FancyTeleportStart" )
+
+ entity ogTeleportSpot = TeleportOG( ogTeleportSpotName )
+ Training_OG_Idles_Sitting( ogTeleportSpot )
+
+ MakeInvincible( player )
+ WaitEndFrame() // player will take damage from random hazard triggers otherwise
+
+ player.SetOrigin( destPos )
+ if ( destAng != < -1, -1, -1 > )
+ player.SetAngles( destAng )
+
+ ClearInvincible( player )
+}
+
+void function FancyTeleport_EffectsAndSound( entity player, vector teleportPos )
+{
+ EndSignal( player, "OnDeath" )
+
+ float statusEffect_severity = 2.5
+ float statusEffect_totalDuration = 0.8
+ float statusEffect_easeOutTime = 0.1
+ StatusEffect_AddTimed( player, eStatusEffect.timeshift_visual_effect, statusEffect_severity, statusEffect_totalDuration, statusEffect_easeOutTime )
+
+ wait 0.1
+
+ Remote_CallFunction_Replay( player, "ScriptCallback_PodTransition_PlayerScreenFX" )
+
+ EmitSoundOnEntity( player, "Timeshift_Scr_DeviceShift2Present" )
+
+ wait 0.25 // let screen FX fade screen
+
+ player.Signal( "FancyTeleportStart" )
+
+ //wait holdTime - 0.1
+
+ wait 0.5 // let white screen fade
+
+ EmitSoundOnEntity( player, "training_scr_zen_player_fall" )
+
+ wait 0.2 // let screen clear before pulsing
+
+ entity pulseFXHandle = PlayFX( FX_FANCY_TELEPORT_ENV_PULSE, teleportPos, <0,0,0> )
+ EffectSetControlPointVector( pulseFXHandle, 1, <2.5,50,0> )
+ thread KillFX_Delayed( pulseFXHandle, 0.5 )
+}
+
+entity function WaitForPlayerActiveWeapon( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ entity weapon = null
+ while ( !weapon )
+ {
+ WaitFrame()
+ weapon = player.GetActiveWeapon()
+ }
+
+ return weapon
+}
+
+
+void function GhostRecorder_RepeatUntilFlag( entity player, string endFlag, entity animRef, asset recordedAnim, float extraRepeatDelay = 0.0, bool silentDissolve = false )
+{
+ EndSignal( player, "OnDestroy" )
+ EndSignal( level, "StopRepeatingGhostRecorder" )
+
+ if ( Flag( endFlag ) )
+ return
+ FlagEnd( endFlag )
+
+ string dissolveSFX = "object_dissolve_training"
+ if ( silentDissolve )
+ dissolveSFX = ""
+
+ table<int,entity> t = {}
+ t[0] <- null
+
+ OnThreadEnd(
+ function() : ( t, dissolveSFX )
+ {
+ if ( !t.len() )
+ return
+
+ entity ghost = t[0]
+
+ if ( IsValid( ghost ) )
+ {
+ StopSoundOnEntity( ghost, "PathHologram_Sustain_Loop_3P" )
+ DissolveGhost( ghost, dissolveSFX )
+ }
+ }
+ )
+
+ var rec = LoadRecordedAnimation( recordedAnim )
+ float duration = GetRecordedAnimationDuration( rec )
+
+ const float ghostFadeTime = 1.2
+
+ while ( 1 )
+ {
+ entity ghost = CreateGhost( animRef.GetOrigin() )
+ t[0] = ghost
+
+ EmitSoundOnEntity( ghost, "PathHologram_Sustain_Loop_3P" )
+
+ ghost.PlayRecordedAnimation( rec, <0,0,0>, <0,0,0>, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, animRef )
+ wait duration - ghostFadeTime
+
+ DissolveGhost( ghost, dissolveSFX )
+
+ wait ghostFadeTime
+ wait extraRepeatDelay
+ }
+}
+
+void function Training_TeleportEffect( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ player.MovementDisable()
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ player.MovementEnable()
+ }
+ )
+
+ float fadeTime = 0.3
+ float holdTime = 0.5
+
+ ScreenFade( player, 255, 255, 255, 254, fadeTime, holdTime, FFADE_IN | FFADE_PURGE )
+ EmitSoundOnEntity( player, "NPE_VisualImpair" )
+ wait fadeTime
+ wait holdTime
+ FadeOutSoundOnEntity( player, "NPE_VisualImpair", fadeTime )
+}
+
+void function TakeAmmoFromPlayerASAP( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ entity weapon = WaitForPlayerActiveWeapon( player )
+
+ array<entity> weapons = player.GetMainWeapons()
+
+ foreach ( weapon in weapons )
+ {
+ weapon.SetWeaponPrimaryAmmoCount( 0 )
+ weapon.SetWeaponPrimaryClipCount( 0 )
+ }
+
+ // take offhand weapons player may have collected
+ array<entity> offhands = player.GetOffhandWeapons()
+ foreach ( index, weapon in clone offhands )
+ player.TakeOffhandWeapon( index )
+}
+
+void function Training_WeaponPickups_Init( entity player )
+{
+ Assert( Flag( "EntitiesDidLoad" ) )
+
+ LeveledScriptedWeapons leveledScriptedWeapons = GetAllLeveledScriptWeapons()
+
+ foreach ( ent in leveledScriptedWeapons.infoTargets )
+ thread Training_RecreateWeaponPickup_Think( ent, player )
+}
+
+void function Training_SetWeaponPickupsEmptyAmmo()
+{
+ file.weaponPickupsHaveAmmo = false
+
+ LeveledScriptedWeapons leveledScriptedWeapons = GetAllLeveledScriptWeapons()
+ foreach ( ent in leveledScriptedWeapons.infoTargets )
+ {
+ // fix for player picking up a weapon right before calling this- attachedEnt is empty because it hasn't been recreated yet
+ // - actual fix is to thread, wait for attachedEnts to get the recreated weapon again, and timeout, but going with less risk for now
+ if ( !ent.e.attachedEnts.len() )
+ continue
+
+ entity weaponEnt = ent.e.attachedEnts[0]
+ if ( !IsValid( weaponEnt ) )
+ continue
+
+ weaponEnt.SetWeaponPrimaryAmmoCount( 0 )
+ weaponEnt.SetWeaponPrimaryClipCount( 0 )
+ }
+}
+
+void function Training_SetWeaponPickupsFullAmmo()
+{
+ file.weaponPickupsHaveAmmo = true
+
+ LeveledScriptedWeapons leveledScriptedWeapons = GetAllLeveledScriptWeapons()
+ foreach ( ent in leveledScriptedWeapons.infoTargets )
+ {
+ // fix for player picking up a weapon right before calling this- attachedEnt is empty because it hasn't been recreated yet
+ // - actual fix is to thread, wait for attachedEnts to get the recreated weapon again, and timeout, but going with less risk for now
+ if ( !ent.e.attachedEnts.len() )
+ continue
+
+ entity weaponEnt = ent.e.attachedEnts[0]
+ if( !IsValid( weaponEnt ) )
+ continue
+
+ string weaponClass = weaponEnt.GetWeaponClassName()
+ int defaultTotal = GetWeaponInfoFileKeyField_GlobalInt( weaponClass, "ammo_default_total" )
+ int defaultMag = GetWeaponInfoFileKeyField_GlobalInt( weaponClass, "ammo_clip_size" )
+
+ weaponEnt.SetWeaponPrimaryAmmoCount( defaultTotal )
+ }
+}
+
+void function Training_RecreateWeaponPickup_Think( entity ent, entity player )
+{
+ EndSignal( player, "OnDestroy" )
+ EndSignal( ent, "OnDestroy" )
+
+ const float MATCHING_PICKUP_DIST = 0.5
+ const float NEARBY_SIMILAR_DIST = 200.0
+
+ string pickupEntWeaponClass = ent.GetValueForKey( "script_weapon" )
+
+ while ( ent.e.attachedEnts.len() && IsValid( ent.e.attachedEnts[0] ) )
+ {
+ entity weaponEnt = ent.e.attachedEnts[0]
+
+ while ( IsValid( weaponEnt ) && !weaponEnt.GetOwner() )
+ wait 0.1
+
+ // this is the most reliable way to get a good push vector for if we need to kick another weapon out (pickup ent angles are often not optimal)
+ vector playerPos_onPickup = player.GetOrigin()
+ vector vecToPlayer_whenPickedUp = Normalize( playerPos_onPickup - ent.GetOrigin() )
+
+ ent.e.attachedEnts.remove( 0 )
+
+ wait 2.2 // don't respawn it right away
+
+ bool oldWeapon_similarPickupNearby = false
+ bool pickupEnt_similarPickupNearby = false
+
+ // previous player weapon may be here after swapping
+ array<entity> allPickups = GetWeaponArray( true )
+ entity oldWeapon
+ foreach ( pickup in allPickups )
+ {
+ float distToThisPickup = Distance( pickup.GetOrigin(), ent.GetOrigin() )
+ if ( distToThisPickup <= MATCHING_PICKUP_DIST )
+ {
+ oldWeapon = pickup
+ break
+ }
+ }
+
+ if ( IsValid( oldWeapon ) )
+ {
+ foreach ( pickup in allPickups )
+ {
+ if ( oldWeapon == pickup )
+ continue
+
+ float distToThisPickup = Distance( pickup.GetOrigin(), ent.GetOrigin() )
+ if ( distToThisPickup <= NEARBY_SIMILAR_DIST )
+ {
+ string pickupWeaponClass = pickup.GetWeaponClassName()
+
+ if ( pickupWeaponClass == oldWeapon.GetWeaponClassName() && !oldWeapon_similarPickupNearby )
+ {
+ //printt( "found similar pickup nearby to one the player dropped:", pickupWeaponClass )
+ oldWeapon_similarPickupNearby = true
+ }
+
+ if ( pickupWeaponClass == pickupEntWeaponClass && !pickupEnt_similarPickupNearby )
+ {
+ //printt( "found similar pickup nearby to one that would be recreated:", pickupWeaponClass )
+ pickupEnt_similarPickupNearby = true
+ }
+ }
+ }
+ }
+
+ bool recreatePickup = true
+ bool destroyOldWeapon = false
+ if ( IsValid( oldWeapon ) )
+ {
+ if ( oldWeapon.GetWeaponClassName() == pickupEntWeaponClass )
+ {
+ printt( "old weapon that is here is the same kind of weapon as we would spawn, so don't recreate:", pickupEntWeaponClass )
+ recreatePickup = false // old weapon that is here is the same kind of weapon as we would spawn, so don't recreate
+ }
+
+ if ( oldWeapon_similarPickupNearby )
+ {
+ printt( "Old weapon can be destroyed, because a similar pickup is nearby:", oldWeapon.GetWeaponClassName() )
+ destroyOldWeapon = true
+ }
+
+ if ( !oldWeapon_similarPickupNearby && pickupEnt_similarPickupNearby )
+ {
+ printt( "old weapon is unique to this area and pickup ent has a similar pickup nearby, so don't recreate pickup ent. Old weapon:", oldWeapon.GetWeaponClassName(), "/ pickup ent class:", pickupEntWeaponClass )
+ recreatePickup = false
+ }
+ }
+
+ if ( recreatePickup )
+ {
+ if ( IsValid( oldWeapon ) )
+ {
+ if ( destroyOldWeapon )
+ {
+ printt( "destroying old weapon because similar pickup is nearby:", oldWeapon.GetWeaponClassName() )
+ oldWeapon.Destroy()
+ }
+ else
+ {
+ MoveOldWeapon( ent, oldWeapon, vecToPlayer_whenPickedUp ) // kick the old weapon out of this spot
+ }
+ }
+
+ // cover respawn with a flash effect
+ EmitSoundAtPosition( TEAM_UNASSIGNED, ent.GetOrigin(), "training_scr_rack_weapon_appear" )
+ StartParticleEffectInWorld( GetParticleSystemIndex( GHOST_FLASH_EFFECT ), ent.GetOrigin(), ent.GetAngles() )
+ CreateScriptWeapon( ent )
+
+ // defensive checks
+ if ( !ent.e.attachedEnts.len() || !IsValid( ent.e.attachedEnts[0] ) )
+ {
+ printt( "WARNING! Recreated script pickup FAILED to recreate:", pickupEntWeaponClass, "on ent", ent )
+ continue
+ }
+
+ entity recreatedPickup = ent.e.attachedEnts[0]
+ printt( "training: recreated weapon pickup:", pickupEntWeaponClass, "by spawning:", recreatedPickup )
+
+ if ( !file.weaponPickupsHaveAmmo )
+ {
+ recreatedPickup.SetWeaponPrimaryAmmoCount( 0 )
+ recreatedPickup.SetWeaponPrimaryClipCount( 0 )
+ }
+ }
+ else
+ {
+ if ( IsValid( oldWeapon ) )
+ ent.e.attachedEnts.append( oldWeapon )
+ }
+ }
+
+ printt( "WARNING- Stopping think on pickupEntWeapon:", pickupEntWeaponClass )
+}
+
+void function MoveOldWeapon( entity pickupEnt, entity oldWeapon, vector pushVec = <0,0,0> )
+{
+ // recreate weapon as unconstrained so we can physics push it
+ entity recreatedOldWeapon = Training_RecreatePlayerWeaponPickup( oldWeapon )
+ string recreatedClassName = recreatedOldWeapon.GetWeaponClassName()
+
+ float velocityScalar = 300.0
+
+ var hasSubClass = GetWeaponInfoFileKeyField_Global( recreatedClassName, "weaponSubClass" )
+ if ( hasSubClass )
+ {
+ string weaponSubClass = GetWeaponInfoFileKeyField_GlobalString( recreatedClassName, "weaponSubClass" )
+
+ switch ( weaponSubClass )
+ {
+ case "offhand":
+ case "pistol":
+ velocityScalar = 200
+ break
+
+ case "smg":
+ velocityScalar = 300
+ break
+
+ case "rifle":
+ velocityScalar = 400
+ break
+
+ case "lmg":
+ case "at":
+ velocityScalar = 500
+ break
+ }
+ }
+
+ if ( pushVec == <0,0,0> )
+ pushVec = AnglesToForward( pickupEnt.GetAngles() )
+
+ //vector pushAng = VectorToAngles( pushVec )
+ //vector addVec = AnglesToUp( pushAng ) * (velocityScalar * 0.2)
+ //pushVec += addVec
+ pushVec += <0,0,1>
+
+ printt( "moving old weapon:", oldWeapon.GetWeaponClassName(), "with velocity scalar:", velocityScalar )
+ recreatedOldWeapon.SetVelocity( pushVec * velocityScalar )
+}
+
+entity function Training_RecreatePlayerWeaponPickup( entity oldWeapon )
+{
+ if ( file.scriptCreatedWeaponPickups.len() >= MAX_RECREATED_OLD_WEAPONS )
+ {
+ entity cleanupWeapon = file.scriptCreatedWeaponPickups[0]
+
+ if ( IsValid( cleanupWeapon ) )
+ cleanupWeapon.Destroy()
+
+ file.scriptCreatedWeaponPickups.remove( 0 )
+ }
+
+ string oldWeaponClass = oldWeapon.GetWeaponClassName()
+
+ entity weapon = CreateWeaponEntityByNameWithPhysics( oldWeaponClass, oldWeapon.GetOrigin(), oldWeapon.GetAngles() )
+ weapon.SetVelocity( <0,0,0> )
+
+ SetTargetName( weapon, "_old_player_weapon_" + oldWeaponClass )
+ weapon.kv.fadedist = -1
+
+ array<string> existingMods = oldWeapon.GetMods()
+ weapon.SetMods( existingMods )
+
+ bool doMarkAsLoadoutPickup = false
+ if ( doMarkAsLoadoutPickup )
+ weapon.MarkAsLoadoutPickup()
+
+ HighlightWeapon( weapon )
+
+ oldWeapon.Destroy()
+
+ file.scriptCreatedWeaponPickups.append( weapon )
+
+ return weapon
+}
+
+
+void function Training_WeaponRacks_SetSolidity( bool doSolid )
+{
+ array<entity> racks = GetEntArrayByScriptName( "ineedguns_racks" )
+
+ foreach ( rack in racks )
+ {
+ if ( doSolid )
+ rack.Solid()
+ else
+ rack.NotSolid()
+ }
+}
+
+
+bool function GetAutosprintEnabled()
+{
+ int autosprintSetting = GetConVarInt( AUTOSPRINT_CONVAR_NAME )
+ bool autoSprintEnabled = autosprintSetting > 0 && autosprintSetting < 3 // 0 = none, 3 = titans only
+ return autoSprintEnabled
+}
+
+
+void function EmitSoundOnEntity_Delayed( entity ent, string alias, float delay )
+{
+ ent.EndSignal( "OnDestroy" )
+
+ if ( delay > 0 )
+ wait delay
+
+ EmitSoundOnEntity( ent, alias )
+}
+
+void function PlayFXOnEntity_Delayed( entity player, asset fxAsset, entity ent, float delay )
+{
+ player.EndSignal( "OnDestroy" )
+ ent.EndSignal( "OnDeath" )
+
+ wait delay
+
+ PlayFXOnEntity( fxAsset, ent )
+}
+
+void function KillFX_Delayed( entity fxHandle, float delay )
+{
+ fxHandle.EndSignal( "OnDestroy" )
+
+ if ( delay > 0.0 )
+ wait delay
+
+ KillFX( fxHandle )
+}
+
+void function KillFX( entity fxHandle )
+{
+ if ( !IsValid_ThisFrame( fxHandle ) )
+ return
+
+ fxHandle.SetStopType( "DestroyImmediately" )
+ fxHandle.ClearParent()
+ fxHandle.Destroy()
+}
+
+void function KillFXWithEndcap( entity fxHandle, float killDelay = 1.0 )
+{
+ if ( !IsValid_ThisFrame( fxHandle ) )
+ return
+
+ EffectStop( fxHandle )
+ wait killDelay
+
+ if ( !IsValid_ThisFrame( fxHandle ) )
+ return
+
+ fxHandle.ClearParent()
+ fxHandle.Destroy()
+}
+
+
+void function FlagSetDelayed( string setFlag, float delay )
+{
+ thread FlagSetDelayed_Think( setFlag, delay )
+}
+
+void function FlagSetDelayed_Think( string setFlag, float delay )
+{
+ EndSignal( level, "OnDestroy" )
+
+ if ( delay > 0 )
+ wait delay
+
+ FlagSet( setFlag )
+}
+
+
+void function Training_PlayerQuickdeathSFX( entity player )
+{
+ EndSignal( player, "OnDestroy" )
+
+ while ( 1 )
+ {
+ WaitSignal( player, "QuickDeath" )
+ EmitSoundOnEntity( player, "training_scr_zen_player_fall" )
+ }
+}
+
+
+void function Training_EnvArtColorCorrection_SetEnabled( bool isEnabled )
+{
+ Assert( IsValid( file.envArt_colorCorrectionEnt ), "Called too early?" )
+
+ string setEnabledStr = "Disable"
+ if ( isEnabled)
+ setEnabledStr = "Enable"
+
+ EntFireByHandle( file.envArt_colorCorrectionEnt, setEnabledStr, "", 0, null, null )
+}
+
+
+void function SetDoF_Hangar( entity player )
+{
+ Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetNearDepth", 0, 18 )
+ Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetFarDepth", 450, 1250 )
+}
+
+void function SetDoF_Default( entity player )
+{
+ Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetNearDepthToDefault" )
+ Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetFarDepthToDefault" )
+}
+
+void function RackDoF_NearDepth( entity player, float nearDepthStart, float nearDepthEnd, float rackTime )
+{
+ Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetNearDepth", nearDepthStart, nearDepthEnd, rackTime )
+}
+
+void function RackDOF_NearDepth_ToDefault( entity player, float duration )
+{
+ Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetNearDepthToDefault", duration )
+}
+
+void function RackDoF_FarDepth( entity player, float farDepthStart, float farDepthEnd, float rackTime )
+{
+ Remote_CallFunction_Replay( player, "ScriptCallback_DoF_SetFarDepth", farDepthStart, farDepthEnd, rackTime )
+}
+
+void function SimpleScreenShake( entity player, float duration, float amplitude, float blurMaxIntensity = 0.75 )
+{
+ Remote_CallFunction_Replay( player, "ScriptCallback_SimpleScreenShake", duration, amplitude, blurMaxIntensity )
+}
+
+void function SetWeaponHUDEnabled( entity player, bool setEnabled )
+{
+ file.displayWeaponHUD = setEnabled
+ Remote_CallFunction_Replay( player, "ScriptCallback_SetWeaponHUDEnabled", setEnabled )
+}
+
+
+
+// ---------------------
+// ----- SKIT GUYS -----
+// ---------------------
+SkitGuyInfo function AddSkitGuy_Manually( string name, entity guy )
+{
+ if ( SkitGuyExists( name ) )
+ DeleteSkitGuy( GetSkitGuyInfo_ByName( name ) )
+
+ SkitGuyInfo info
+ info.id = file.skitguys.len()
+ info.guy = guy
+ info.name = name
+
+ file.skitguys.append( info )
+
+ return info
+}
+
+SkitGuyInfo function SpawnSkitGuy( string name, string anim, vector origin, vector angles, int team = TEAM_IMC, string aiSettings = "", string weapon = "mp_weapon_semipistol", bool isRunner = false )
+{
+ if ( SkitGuyExists( name ) )
+ DeleteSkitGuy( GetSkitGuyInfo_ByName( name ) )
+
+ string guyType = "grunt"
+ if ( name.find( "marvin" ) != null )
+ guyType = "marvin"
+
+ // spawn the guy
+ entity guy
+ if ( guyType == "marvin" )
+ {
+ Assert( !isRunner, "Marvins don't run!" )
+
+ guy = CreateEntity( "npc_marvin" )
+
+ DispatchSpawn( guy )
+
+ SetTeam( guy, TEAM_SPECTATOR )
+ guy.SetNPCMoveSpeedScale( 0.6 )
+
+ //TakeAllJobs( guy )
+ }
+ else
+ {
+ guy = CreateSoldier( team, <0,0,0>, <0,0,0> ) // spawn the guy at worldspawn to avoid "npc spawned in solid" red text`
+ SetSpawnOption_Weapon( guy, weapon )
+ if ( aiSettings != "" )
+ SetSpawnOption_AISettings( guy, aiSettings )
+
+ if ( isRunner )
+ guy.kv.alwaysAlert = 1
+
+ DispatchSpawn( guy )
+ }
+
+ guy.SetTitle( "" )
+
+ entity ref = CreateOwnedScriptMover( guy )
+ ref.SetOrigin( origin )
+ ref.SetAngles( angles )
+
+ guy.SetOrigin( ref.GetOrigin() )
+ guy.SetAngles( ref.GetAngles() )
+
+ MakeInvincible( guy )
+ guy.SetEfficientMode( true )
+ guy.EnableNPCFlag( NPC_IGNORE_ALL | NPC_DISABLE_SENSING )
+ guy.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+
+ SkitGuyInfo info
+ info.id = file.skitguys.len()
+ info.guy = guy
+ info.skitRef = ref
+ info.skitAnim = anim
+ info.name = name
+
+ file.skitguys.append( info )
+
+ return info
+}
+
+void function SpawnSkitGuy_AndRun( string name, array<Point> path, float moveSpeedScale, int team, string aiSettings = "", string weaponName = "" )
+{
+ SkitGuyInfo runnerInfo
+ runnerInfo = SpawnSkitGuy( name, "", path[0].origin, path[0].angles, team, aiSettings, weaponName, true )
+
+ entity runner = runnerInfo.guy
+ EndSignal( runner, "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( runnerInfo )
+ {
+ DeleteSkitGuy( runnerInfo )
+ }
+ )
+
+ waitthread ScriptedPath_Run( runnerInfo, path, moveSpeedScale )
+}
+
+void function SpawnSkitGuy_AndRunForever( string name, array<Point> path, float moveSpeedScale, int team, string aiSettings = "", string weaponName = "" )
+{
+ SkitGuyInfo runnerInfo
+ while ( 1 )
+ {
+ runnerInfo = SpawnSkitGuy( name, "", path[0].origin, path[0].angles, team, aiSettings, weaponName )
+ waitthread ScriptedPath_Run( runnerInfo, path, moveSpeedScale )
+ }
+}
+
+void function SkitGuy_PlayAnim( SkitGuyInfo info, float skipAheadTime = 0 )
+{
+ Assert( info.skitAnim != "" )
+ Assert( IsValid( info.skitRef ) )
+
+ entity guy = info.guy
+
+ thread PlayAnim( guy, info.skitAnim, info.skitRef, null, 0.0, skipAheadTime )
+}
+
+bool function SkitGuyExists( string name )
+{
+ foreach ( info in file.skitguys )
+ {
+ if ( info.name == name )
+ return true
+ }
+
+ return false
+}
+
+SkitGuyInfo function GetSkitGuyInfo_ByName( string guyName )
+{
+ SkitGuyInfo thisInfo
+ foreach ( info in file.skitguys )
+ {
+ if ( info.name == guyName )
+ {
+ thisInfo = info
+ return thisInfo
+ }
+ }
+
+ Assert( false, "couldn't find skit guy info by name: " + guyName )
+ unreachable
+}
+
+void function DeleteAllSkitGuys()
+{
+ array<string> deleteNames = []
+
+ foreach ( skitInfo in file.skitguys )
+ deleteNames.append( skitInfo.name )
+
+ foreach ( name in deleteNames )
+ {
+ if ( !SkitGuyExists( name ) )
+ continue
+
+ SkitGuyInfo deleteInfo = GetSkitGuyInfo_ByName( name )
+ DeleteSkitGuy( deleteInfo )
+ }
+}
+
+void function DeleteSkitGuy( SkitGuyInfo info )
+{
+ KillSkitGuy( info )
+
+ int removeIdx = -1
+ foreach ( idx, guyInfo in file.skitguys )
+ {
+ if ( guyInfo.id == info.id )
+ {
+ removeIdx = idx
+ break
+ }
+ }
+
+ if ( removeIdx == -1 )
+ {
+ printt( "WARNING: SkitGuy was already deleted!" )
+ return
+ }
+
+
+ file.skitguys.remove( removeIdx )
+}
+
+void function KillSkitGuy( SkitGuyInfo info )
+{
+ entity guy = info.guy
+ entity skitRef = info.skitRef
+
+ if ( IsValid( skitRef ) )
+ skitRef.Destroy()
+
+ info.skitRef = null
+
+ if ( IsAlive( guy ) )
+ {
+ guy.Anim_Stop()
+ ClearInvincible( guy )
+ }
+
+ if ( IsValid( guy ) )
+ guy.Destroy()
+
+ info.guy = null
+}
+
+#if DEV
+string function NudgeSkitGuy( string name, float offsetX, float offsetY = 0.0, float offsetZ = 0.0 )
+{
+ if ( !SkitGuyExists( name ) )
+ {
+ return "WARNING: SKIT GUY NAME NOT RECOGNIZED: " + name
+ }
+
+ SkitGuyInfo info = GetSkitGuyInfo_ByName( name )
+ entity guy = info.guy
+ entity skitRef = info.skitRef
+ string name = info.name
+
+ vector offset = <offsetX, offsetY, offsetZ>
+
+ if ( IsValid( skitRef ) )
+ skitRef.SetOrigin( skitRef.GetOrigin() + offset )
+ else
+ guy.SetOrigin( guy.GetOrigin() + offset )
+
+ if ( info.skitAnim != "" )
+ SkitGuy_PlayAnim( info )
+
+ printt( "NUDGED:")
+ return PrintSkitGuy( info )
+}
+
+string function PrintSkitGuy( SkitGuyInfo info )
+{
+ entity guy = info.guy
+ entity skitRef = info.skitRef
+ string name = info.name
+
+ string returnStr = name + " origin/angles: " + CreateOriginAnglesString( guy.GetOrigin(), guy.GetAngles() )
+ if ( IsValid( skitRef ) )
+ returnStr = name + " ref origin/angles: " + CreateOriginAnglesString( skitRef.GetOrigin(), skitRef.GetAngles() )
+
+ return returnStr
+}
+#endif //DEV
+
+
+
+// ------------------------------
+// ----- SCRIPTED NPC PATHS -----
+// ------------------------------
+void function ScriptedPath_AddPoint( array<Point> pathpoints, vector origin, vector angles )
+{
+ Point pathpoint
+ pathpoint.origin = origin
+ pathpoint.angles = angles
+
+ pathpoints.append( pathpoint )
+}
+
+void function ScriptedPath_Walk( SkitGuyInfo info, array<Point> path, float moveSpeedScale = 0.8, string idleAnim = "" )
+{
+ NPC_ScriptedPath( SCRIPTED_PATH_WALK, info, path, moveSpeedScale, idleAnim )
+}
+
+void function ScriptedPath_Run( SkitGuyInfo info, array<Point> path, float moveSpeedScale = 1.0, string idleAnim = "" )
+{
+ NPC_ScriptedPath( SCRIPTED_PATH_RUN, info, path, moveSpeedScale, idleAnim )
+}
+
+void function NPC_ScriptedPath( int pathFollowType, SkitGuyInfo info, array<Point> path, float moveSpeedScale = 1.0, string idleAnim = "" )
+{
+ entity guy = info.guy
+
+ guy.EndSignal( "OnDestroy" )
+
+ guy.Anim_Stop()
+
+ guy.EnableNPCMoveFlag( NPCMF_DISABLE_MOVE_TRANSITIONS )
+ guy.EnableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS )
+ guy.SetNPCMoveSpeedScale( moveSpeedScale )
+
+ if ( pathFollowType == SCRIPTED_PATH_RUN )
+ guy.SetAlert() // change his alert state so he will run
+
+ if ( pathFollowType == SCRIPTED_PATH_WALK )
+ guy.SetMoveAnim( "patrol_walk_bored" )
+
+ string waitSignal = "OnEnterGoalRadius" //"OnFinishedAssault"
+ float pathfindingFailTimeout = 20.0
+
+ guy.SetOrigin( path[0].origin )
+ guy.SetAngles( path[0].angles )
+
+ for ( int i = 1; i < path.len(); i++ )
+ {
+ Point pathpoint = path[i]
+ float goalradius = 64.0 // MINIMUM
+ //guy.DisableArrivalOnce( true ) // always want arrivals disabled because they are blended from run anim, not walking
+
+ guy.AssaultPoint( pathpoint.origin )
+ guy.AssaultSetGoalRadius( goalradius )
+
+ WaitSignalTimeout( guy, pathfindingFailTimeout, waitSignal )
+
+ if ( Distance( guy.GetOrigin(), pathpoint.origin ) >= goalradius )
+ {
+ printt( guy, " scripted pathfinding stopped, quitting." )
+ break
+ }
+ }
+
+ if ( idleAnim != "" )
+ {
+ guy.DisableBehavior( "Assault" )
+
+ while ( !guy.IsInterruptable() )
+ wait 0.1
+
+ entity ref = CreateOwnedScriptMover( guy )
+ thread PlayAnim( guy, idleAnim, ref, null, 0.4 )
+
+ WaitForever()
+ }
+ else
+ {
+ DeleteSkitGuy( info )
+ }
+}
+
+
+
+// -----------------------------
+// ----- TITAN GROUP SKITS -----
+// -----------------------------
+void function HangarTitanGroup_Init( HangarTitanGroup group )
+{
+ group.rack_ogPos = group.rack.GetOrigin()
+ group.rack_ogAng = group.rack.GetAngles()
+
+ if ( IsValid( group.titan ) )
+ {
+ if ( group.titanSkin == -1 )
+ group.titanSkin = 1
+ }
+
+ if ( group.marvinAnim != "" )
+ {
+ group.marvin = CreatePropDynamic( MARVIN_MODEL )
+ group.marvin.DisableHibernation()
+ }
+
+ if ( group.pilotAnim != "" )
+ {
+ group.pilot = CreatePropDynamic( group.pilotModel )
+ group.pilot.DisableHibernation()
+ }
+
+ HangarTitanGroup_SetMaxSequenceDuration( group )
+
+ group.isInited = true
+}
+
+void function HangarTitanGroup_SetMaxSequenceDuration( HangarTitanGroup group )
+{
+ table<string,entity> sceneActors = {}
+ sceneActors[ group.titanAnim ] <- group.titan
+ sceneActors[ group.rackAnim ] <- group.rack
+ sceneActors[ group.marvinAnim ] <- group.marvin
+
+ if ( IsValid( group.titan ) )
+ {
+ // set titan to use non posed model
+ group.titan.SetModel( BUDDY_MODEL )
+ group.titan.SetSkin( group.titanSkin )
+ }
+
+ float maxDuration = 0
+ foreach ( anim, actor in sceneActors )
+ {
+ if ( !IsValid( actor ) )
+ continue
+
+ float animDuration = actor.GetSequenceDuration( anim )
+ if ( animDuration > maxDuration )
+ maxDuration = animDuration
+ }
+
+ group.sequenceDuration = maxDuration
+
+ if ( IsValid( group.titan ) )
+ {
+ // set titan back to posed anim
+ group.titan.SetModel( BUDDY_MODEL_POSED_NO_ANIMS )
+ //group.titan.SetSkin( group.titanSkin )
+ }
+}
+
+void function HangarTitanGroup_Animate( HangarTitanGroup group, string endFlag = "", float duration = -1, bool doCleanup = true )
+{
+ if ( endFlag != "" )
+ FlagEnd( endFlag )
+
+ Assert( group.isInited, "Need to call HangarTitanGroup_Init on this group before using" )
+
+ entity ref = group.ref
+ entity titan = group.titan
+ entity rack = group.rack
+ entity marvin = group.marvin
+ entity pilot = group.pilot
+ string titanAnim = group.titanAnim
+ string rackAnim = group.rackAnim
+ string marvinAnim = group.marvinAnim
+ string pilotAnim = group.pilotAnim
+ float animInitialTime = group.animInitialTime
+
+ EndSignal( rack, "OnDestroy" )
+
+ if ( IsValid( marvin ) )
+ EndSignal( marvin, "OnDestroy")
+
+ if ( IsValid( titan ) )
+ {
+ EndSignal( titan, "OnDestroy" )
+
+ // set titan to use non posed model
+ titan.SetModel( BUDDY_MODEL )
+ titan.SetSkin( group.titanSkin )
+ }
+
+ OnThreadEnd(
+ function() : ( doCleanup, group )
+ {
+ if ( doCleanup )
+ HangarTitanGroup_Cleanup( group )
+ }
+ )
+
+ if ( duration == -1 || group.sequenceDuration < duration )
+ duration = group.sequenceDuration
+
+ thread PlayAnim( rack, rackAnim, ref, null, 0.0, animInitialTime )
+
+ if ( IsValid( titan) )
+ thread PlayAnim( titan, titanAnim, ref, null, 0.0, animInitialTime )
+
+ if ( IsValid( marvin ) )
+ thread PlayAnim( marvin, marvinAnim, ref, null, 0.0, animInitialTime )
+
+ if ( IsValid( pilot ) )
+ thread PlayAnim( pilot, pilotAnim, ref, null, 0.0, animInitialTime )
+
+ wait duration
+}
+
+void function HangarTitanGroup_Cleanup( HangarTitanGroup group )
+{
+ HangarTitanGroup_Reset( group )
+
+ if ( IsValid( group.marvin ) )
+ group.marvin.Destroy()
+
+ if ( IsValid( group.pilot ) )
+ group.pilot.Destroy()
+}
+
+void function HangarTitanGroup_Reset( HangarTitanGroup group )
+{
+ entity titan = group.titan
+ entity rack = group.rack
+
+ if ( IsValid( titan ) )
+ {
+ titan.Anim_Stop()
+ titan.SetModel( BUDDY_MODEL_POSED_NO_ANIMS )
+ //titan.SetSkin( group.titanSkin )
+ }
+
+ if ( IsValid( rack ) )
+ {
+ rack.Anim_Stop()
+ rack.SetOrigin( group.rack_ogPos )
+ rack.SetAngles( group.rack_ogAng )
+ }
+}
+
+
+
+#if DEV
+void function skyboxchange( string tName )
+{
+ entity cam = GetEnt( tName )
+ GetPlayerArray()[0].SetSkyCamera( cam )
+}
+
+
+// ======================================================
+// ============ GHOST RECORDER DEV FUNCTIONS ============
+// ======================================================
+void function wallruntest()
+{
+ entity preWallrunRef = GetEntByScriptName( "basic_movement_wallrun_start_ref" )
+ var rec = LoadRecordedAnimation( $"anim_recording/training_record_zengarden_wallrun.rpak" )
+ file.ogPilot.Anim_Stop()
+ file.ogPilot.PlayRecordedAnimation( rec, <0,0,0>, <0,0,0>, DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, preWallrunRef )
+}
+
+void function Record_ZenGarden_Wallrun()
+{
+ thread RecordAnimation_Think( "training_record_zengarden_wallrun", "basic_movement_wallrun_start_ref" )
+}
+
+void function Record_ZenGarden_Slide()
+{
+ thread RecordAnimation_Think( "training_record_zengarden_slide", "zengarden_slide_ref" )
+}
+
+void function Record_ZenGarden_DoubleJump()
+{
+ thread RecordAnimation_Think( "training_record_zengarden_doublejump", "zengarden_doublejump_ref" )
+}
+
+void function RecordAnimation_Think( string filename, string refName )
+{
+ entity player = file.player
+ player.Signal( "RecordAnimation_Start" )
+ player.EndSignal( "RecordAnimation_Start" )
+
+ TeleportPlayerAndBT( refName )
+
+ player.EndSignal( "OnDestroy" )
+
+ entity ref = GetEntByScriptName( refName )
+
+ printt( "READY TO RECORD: " + filename )
+
+ //start recording
+ player.WaitSignal( "ButtonPressedAttack" )
+ printt( "RECORDING STARTED" )
+
+ player.StartRecordingAnimation( ref.GetOrigin(), ref.GetAngles() )
+
+ //stop
+ player.WaitSignal( "ButtonPressedAttack" )
+
+ var recording = player.StopRecordingAnimation()
+
+#if PC_PROG
+ SaveRecordedAnimation( recording, filename )
+#endif
+ printt( "STOP RECORD player org/ang:", player.GetOrigin(), player.GetAngles() )
+}
+#endif //DEV
diff --git a/Northstar.Custom/mod.json b/Northstar.Custom/mod.json
new file mode 100644
index 000000000..87ce75e86
--- /dev/null
+++ b/Northstar.Custom/mod.json
@@ -0,0 +1,186 @@
+disabled{
+ "ApiId" : "Northstar.Custom",
+ "Name" : "Northstar.Custom",
+ "Description" : "Additional content for coop and custom multiplayer servers",
+ "Authors" : [
+ "BobTheBob"
+ ],
+ "Contacts" : [
+ "BobTheBob#1150"
+ ],
+ "Version" : "0.1",
+ "CustomScripts": [
+ {
+ "Path": "northstar_custom_autoprecache.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientCallback": "NorthstarCustomAutoprecache",
+ "ServerCallback": "NorthstarCustomAutoprecache",
+ },
+ {
+ "Path": "_northstar_devcommands.gnut",
+ "RunOn": "SERVER && MP",
+ "ServerCallback": "NorthstarDevCommands_Init"
+ },
+
+ {
+ "Path": "weapons/mp_weapon_peacekraber.nut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemode_sbox.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "Sh_GamemodeSbox_Init",
+ "ServerPreCallback": "Sh_GamemodeSbox_Init"
+ },
+ {
+ "Path": "gamemodes/_gamemode_sbox.gnut",
+ "RunOn": "SERVER && MP"
+ },
+ {
+ "Path": "weapons/mp_weapon_toolgun.nut",
+ "RunOn": "( CLIENT || SERVER ) && MP"
+ },
+ {
+ "Path": "weapons/toolgun/sh_toolgun_tools.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientCallback": "ToolgunTools_Init",
+ "ServerCallback": "ToolgunTools_Init"
+ },
+ {
+ "Path": "weapons/toolgun/sh_toolgun_tool_throw.nut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientCallback": "ToolgunToolThrowEntity_Init",
+ "ServerCallback": "ToolgunToolThrowEntity_Init"
+ },
+ {
+ "Path": "weapons/toolgun/sh_toolgun_tool_explode.nut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientCallback": "ToolgunToolCreateExplosion_Init",
+ "ServerCallback": "ToolgunToolCreateExplosion_Init"
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemode_fw_custom.nut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "SHCreateGamemodeFW_Init",
+ "ServerPreCallback": "SHCreateGamemodeFW_Init"
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemode_gg.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "Sh_GamemodeGG_Init",
+ "ServerPreCallback": "Sh_GamemodeGG_Init"
+ },
+ {
+ "Path": "gamemodes/_gamemode_gg.gnut",
+ "RunOn": "SERVER && MP"
+ },
+ {
+ "Path": "gamemodes/cl_gamemode_gg.gnut",
+ "RunOn": "CLIENT && MP"
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemode_tt.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "Sh_GamemodeTT_Init",
+ "ServerPreCallback": "Sh_GamemodeTT_Init"
+ },
+ {
+ "Path": "gamemodes/_gamemode_tt.gnut",
+ "RunOn": "SERVER && MP"
+ },
+ {
+ "Path": "gamemodes/cl_gamemode_tt.gnut",
+ "RunOn": "CLIENT && MP"
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemode_inf.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "Sh_GamemodeInfection_Init",
+ "ServerPreCallback": "Sh_GamemodeInfection_Init"
+ },
+ {
+ "Path": "gamemodes/_gamemode_inf.gnut",
+ "RunOn": "SERVER && MP"
+ },
+ {
+ "Path": "gamemodes/cl_gamemode_inf.gnut",
+ "RunOn": "CLIENT && MP"
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemode_arena.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "Sh_GamemodeArena_Init",
+ "ServerPreCallback": "Sh_GamemodeArena_Init"
+ },
+ {
+ "Path": "gamemodes/_gamemode_arena.gnut",
+ "RunOn": "SERVER && MP"
+ },
+ {
+ "Path": "gamemodes/cl_gamemode_arena.gnut",
+ "RunOn": "CLIENT && MP"
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemode_kr.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "Sh_GamemodeKR_Init",
+ "ServerPreCallback": "Sh_GamemodeKR_Init"
+ },
+ {
+ "Path": "gamemodes/_gamemode_kr.gnut",
+ "RunOn": "SERVER && MP"
+ },
+ {
+ "Path": "gamemodes/cl_gamemode_kr.gnut",
+ "RunOn": "CLIENT && MP"
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemode_fastball.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "Sh_GamemodeFastball_Init",
+ "ServerPreCallback": "Sh_GamemodeFastball_Init"
+ },
+ {
+ "Path": "gamemodes/_gamemode_fastball.gnut",
+ "RunOn": "SERVER && MP"
+ },
+ {
+ "Path": "gamemodes/_gamemode_fastball_intro.gnut",
+ "RunOn": "SERVER && MP"
+ },
+ {
+ "Path": "gamemodes/cl_gamemode_fastball.gnut",
+ "RunOn": "CLIENT && MP"
+ },
+
+ {
+ "Path": "titan/sh_first_person_embark.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientPreCallback": "FirstPersonEmbark_Init",
+ "ServerPreCallback": "FirstPersonEmbark_Init"
+ },
+ {
+ "Path": "gamemodes/_riff_instagib.gnut",
+ "RunOn": "SERVER && MP",
+ "ServerCallback": "RiffInstagib_Init"
+ },
+ ],
+ "IncludeAdditionalPreexistingScripts": [
+ {
+ "Path": "gamemodes/sh_gamemode_fw.nut",
+ "RunOn": "( CLIENT || SERVER ) && MP"
+ },
+ {
+ "Path": "gamemodes/cl_gamemode_fw.nut",
+ "RunOn": "CLIENT && MP"
+ }
+ ]
+} \ No newline at end of file
diff --git a/Northstar.Custom/playlists_v2.txt b/Northstar.Custom/playlists_v2.txt
new file mode 100644
index 000000000..be938cc36
--- /dev/null
+++ b/Northstar.Custom/playlists_v2.txt
@@ -0,0 +1,5772 @@
+playlists
+{
+ version stable
+ versionNum 3246
+ Gamemodes
+ {
+ defaults
+ {
+ vars
+ {
+ pve_menu 0
+ enable_emotes 0
+ boost_store_mode off
+
+ enable_lts_style_loadout_select 0
+ pick_loadout_extension 0
+ pick_loadout_every_round 0
+ pick_loadout_warp_sound 1
+ tts_menu_join_in_progress 0
+ tts_menu_show_score 1
+ ingame_menu_fd_mode 0
+ limited_editions_available 1
+
+ ai_attack_tethers 1
+ enable_sun_flare_mp_thaw 0
+ enable_sun_flare_mp_forwardbase_kodai 0
+ enable_sun_flare_mp_colony02 0
+ enable_coliseum_updates 1
+ enable_coliseum_party 1
+ enable_spectre_hacking 0
+ maphack_on_minimap_only 0
+ mp_angel_city_available 1
+ angel_city_replacement groundwar
+ angel_city_replacement_index 8
+ double_xp_enabled 0
+ skyshow_enabled 0
+ loadout_selection_enabled 1
+ ai_kill_credit 3
+ always_enable_autotitans 1
+ at_turrets_enabled 1
+ burn_meter_enabled 1
+ classic_mp 1
+ cmdlineMapLoad 0
+ em_npc_fast_kills 20
+ em_npc_kills 20
+ em_player_fast_kills 5
+ em_player_kills 8
+ enable_titanfalls 1
+ evac_dropship_kill_credit 60
+ event_airdrops_enabled 0
+ event_airdrops_respawnTime 180
+ firstpersonspectate_enabled 1
+ gamemode_uses_custom_countdown 0
+ hud_score_enabled 1
+ infinite_doomed_state 1
+ loadout_grace_period 20.0
+ lobby_countdown 60
+ lobby_countdown_min 30
+ max_players 16
+ max_teams 2
+ matchLossProtectionThreshold 10
+ megaturret_kill_credit 20
+ minimap_sonar_pulse_on_respawn 0
+ player_embark_in_solid_checks 1
+ player_kill_credit 10
+ power_ups_enabled 0
+ r2_titan_models 1
+ ranked_wlpct_at 0.05
+ ranked_wlpct_cp 0.15
+ ranked_wlpct_ctf 0.15
+ ranked_wlpct_def 0.1
+ ranked_wlpct_lts 0.15
+ ranked_wlpct_mfd 0.1
+ ranked_wlpct_ps 0.05
+ ranking_supported 1
+ riff_ai_lethality 1
+ riff_allow_npcs 2
+ riff_ammo_limit 0
+ riff_elimination 0
+ riff_floorislava 0
+ riff_minimap_state 0
+ riff_osp 0
+ riff_spawn_as_titan 0
+ riff_titan_availability 0
+ riff_titan_exit_enabled 0
+ riff_titan_queue 0
+ riff_wave_spawn 0
+ rodeo_battery_disembark_to_pickup 1
+ maxExtraRounds -1
+ rts_style_health_bars 1
+ run_evac 0
+ script_leak 0
+ spectre_kill_credit 9
+ target_health_bar 1
+ titan_build_credit_enabled 1
+ titan_build_time 180
+ titan_build_time_use_set_file 0
+ titan_core_build_time 200
+ titan_core_from_titan_damage 1
+ titan_doomstate_variation default
+ titan_health_bar_display default
+ titan_health_chicklet_fx 0
+ titan_kill_credit 30
+ titan_mode_change_allowed 1
+ titan_rebuild_time 180
+ titan_shield_decay 0
+ titan_shield_regen 0
+ titan_spawn_deploy_enabled 1
+ vortex_blocks_melee 0
+ waiting_for_players_countdown_seconds 0
+ waiting_for_players_percentage_desired 70
+ waiting_for_players_timeout_seconds 45
+ wait_before_restarting_matchmaking_time 20
+ gm_hardcore_settings 0
+ spawn_zone_enabled 0
+ is_e3_mode 0
+ fast_tdm 0
+ arena_loadout 0
+ fd_difficulty 0
+ featured_mode_amped_tacticals 0
+ featured_mode_tactikill 0
+ featured_mode_all_grapple 0
+ featured_mode_all_holopilot 0
+ featured_mode_all_phase 0
+ featured_mode_all_ticks 0
+ featured_mode_rocket_arena 0
+ featured_mode_turbo_titans 0
+ featured_mode_shotguns_snipers 0
+
+ // defaults for private_match settings, as all gamemodes don't explicitly set their own
+ boosts_enabled 0
+ respawn_delay 0.0
+
+ //No longer used? No references in script for these
+ xp2coins_modifier 0.1
+ titan_shield_health 0
+ titan_rodeo_variation 1
+ titan_segmented_health 0
+ titan_health_regen 0
+ titan_burncard_rate 28
+ shop_price_modifier 1.0
+ show_enemy_death_icons 0
+ show_friendly_icon 1
+ rtdm_roundscorelimit 2
+ rtdm_roundtimelimit 4
+ rtdm_scorelimit 20
+ prematch_time 30
+ always_titan 0
+ amped_capture_points 0
+ nwrp_enabled 1
+ pilot_burncard_rate 55
+ kill_for_titan 6
+ hud_weapons_enabled 1
+ grunt_burncard_rate 95
+ bc_base_cards 46
+ bc_stash_bonus_per_gen 6
+ burncards_enabled 0
+ cinematic_mode 0
+ coins_bc_sale_modifier 1.0
+ coins_earn_modifier 1.0
+ em_decay_hold 10
+ force_quick_play 0
+ //End of list
+
+ faq_patchnotes_version 11
+ faq_patchnotes_count 10
+
+ faq_community_version 10
+ faq_community_url_00 "https://www.youtube.com/embed/SbdQPdaPme4?autoplay=1"
+ faq_community_url_01 "https://forums.titanfall.com/en-us/discussion/14073/postcards-from-the-frontier-the-patch-notes#latest"
+ faq_community_url_02 "https://www.youtube.com/embed/KyywlZHh3-Q?autoplay=1"
+ faq_community_url_03 "https://www.youtube.com/embed/1LAlKi6E8DY?autoplay=1"
+ faq_community_url_04 "https://www.youtube.com/embed/pzmrZBiLyhE?autoplay=1"
+ faq_community_url_05 "https://www.youtube.com/embed/XBP3Ic0UiQY?autoplay=1"
+ faq_community_url_06 "https://gfycat.com/gifs/detail/ScornfulAnyHamadryad"
+ faq_community_url_07 "https://www.youtube.com/embed/fE7_tVG_t28?autoplay=1"
+ faq_community_url_08 "https://www.youtube.com/embed/RO7QJ5YEa24?autoplay=1"
+ faq_community_url_09 "https://www.youtube.com/embed/rMeaxj8k_3U?autoplay=1"
+ faq_community_url_10 "https://www.youtube.com/embed/JKiuY3Ocwzo?autoplay=1"
+ faq_community_url_11 "https://www.youtube.com/embed/fCCckqclBoA?autoplay=1"
+ faq_community_url_12 "https://www.youtube.com/embed/4MMtDSrcXYU?autoplay=1"
+ faq_community_url_13 "http://www.youtube.com/embed/I_6hViuy3kc?autoplay=1"
+ faq_community_url_14 "http://www.youtube.com/embed/Wtu3fUs9i7M?autoplay=1"
+ faq_community_url_15 "http://www.youtube.com/embed/EXwdWuSuiYA?autoplay=1"
+ faq_community_url_16 "http://www.youtube.com/embed/nHvoaAAhGno?autoplay=1"
+ faq_community_count 17
+
+ mixtape_matchmaking 1 // 0 = old style, 1 = new style
+ mixtape_version 2 // bump to re-trigger the "NEW!" notify.
+ mixtape_checkbox_memory_version 2 // bump to re-enable all checkboxes
+ mixtape_skip_button 0 // disable to not show the 'Y Skip' prompt
+ mixtape_onboarding "aitdm,cp,aitdm,cp,ffa "
+
+ gamemode_name ""
+ gamemode_hint ""
+ gamemode_score_hint ""
+ gamemode_bullet_001 ""
+ gamemode_bullet_002 ""
+ gamemode_bullet_003 ""
+ gamemode_bullet_004 ""
+ gamemode_bullet_005 ""
+
+ coliseum_primary "mp_weapon_lstar"
+ coliseum_primary_attachment ""
+ coliseum_primary_mod1 ""
+ coliseum_primary_mod2 ""
+ coliseum_primary_mod3 ""
+ coliseum_secondary "mp_weapon_softball"
+ coliseum_secondary_mod1 ""
+ coliseum_secondary_mod2 ""
+ coliseum_secondary_mod3 ""
+ coliseum_weapon3 ""
+ coliseum_weapon3_mod1 ""
+ coliseum_weapon3_mod2 ""
+ coliseum_weapon3_mod3 ""
+ coliseum_melee "melee_pilot_emptyhanded"
+ coliseum_special "mp_ability_heal"
+ coliseum_ordnance "mp_weapon_frag_drone"
+ coliseum_passive1 "pas_fast_health_regen"
+ coliseum_passive2 "pas_wallhang"
+
+ idlekick_minutes 3
+ idlekick_min_alive_seconds 40
+
+ fd_tutorial_title "#WATCH_TUTORIAL"
+ fd_tutorial_url "https://www.youtube.com/watch?v=8wS8NQ0XW-w"
+
+ "generic_dialog_header_30" "#MATCHMAKING_PENALTY_ACTIVE"
+ "generic_dialog_message_36" "#MATCHMAKING_PENALTY_GE_5"
+ "generic_dialog_message_35" "#MATCHMAKING_PENALTY_5"
+ "generic_dialog_message_34" "#MATCHMAKING_PENALTY_4"
+ "generic_dialog_message_33" "#MATCHMAKING_PENALTY_3"
+ "generic_dialog_message_32" "#MATCHMAKING_PENALTY_2"
+ "generic_dialog_message_31" "#MATCHMAKING_PENALTY_1"
+ }
+ maps
+ {
+ mp_black_water_canal 1
+ mp_complex3 1
+ mp_crashsite3 1
+ mp_drydock 1
+ mp_eden 1
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_glitch 1
+ mp_rise 1
+ }
+ }
+ at
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_attrition
+ lobbytitle #PL_attrition_lobby
+ description #PL_attrition_desc
+ hint #PL_attrition_hint
+ abbreviation #PL_attrition_abbr
+ em_player_fast_kills 12
+ em_player_kills 15
+ max_players 10
+ riff_allow_npcs 1
+ scorelimit 5000
+ suddendeath_timelimit 2
+ timelimit 15
+ titan_build_time 240
+ titan_rebuild_time 240
+ spawn_zone_enabled 1
+ color "183 60 0 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_AT
+ gamemode_bullet_001 #GAMEMODE_BULLET_AT_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_AT_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_AT_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_AT_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_AT_005
+ }
+ }
+ coliseum
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_coliseum
+ lobbytitle #PL_coliseum_lobby
+ description #PL_coliseum_desc
+ hint #PL_coliseum_hint
+ image coliseum
+ classic_mp 1
+ infinite_doomed_state 0
+ power_ups_enabled 1
+ roundscorelimit 3
+ roundtimelimit 3.0
+ suddendeath_timelimit 0.5
+ max_players 2
+ scorelimit 3
+ timelimit 15
+ loadout_selection_enabled 0
+ waiting_for_players_countdown_seconds 10
+ waiting_for_players_percentage_desired 100
+ waiting_for_players_timeout_seconds 60
+
+ color "88 151 220 255"
+ }
+ maps
+ {
+ mp_coliseum 1
+ mp_coliseum_column 1
+ }
+ }
+ cp
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_hardpoint
+ lobbytitle #PL_hardpoint_lobby
+ description #PL_hardpoint_desc
+ hint #PL_hardpoint_hint
+ abbreviation #PL_hardpoint_abbr
+ image cp
+ amped_capture_points 1
+ scorelimit 400
+ suddendeath_timelimit 2
+ timelimit 10
+ max_players 12
+ color "161 204 255 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_CP
+ gamemode_bullet_001 #GAMEMODE_BULLET_CP_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_CP_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_CP_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_CP_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_CP_005
+ }
+ }
+ ctf
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_capture_the_flag
+ lobbytitle #PL_capture_the_flag_lobby
+ description #PL_capture_the_flag_desc
+ hint #PL_capture_the_flag_hint
+ abbreviation #PL_capture_the_flag_abbr
+ image ctf
+ respawn_delay 8
+ scorelimit 5
+ suddendeath_timelimit 2
+ timelimit 12
+ max_players 10
+ phase_shift_drop_flag 1
+ ctf_flag_return_time 1
+ color "0 119 245 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_CTF
+ gamemode_bullet_001 #GAMEMODE_BULLET_CTF_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_CTF_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_CTF_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_CTF_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_CTF_005
+ }
+ }
+ ffa
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_ffa
+ lobbytitle #PL_ffa_lobby
+ description #PL_ffa_desc
+ hint #PL_ffa_hint
+ abbreviation #PL_ffa_abbr
+ image ffa
+ at_turrets_enabled 1
+ max_players 12
+ max_teams 12
+ scorelimit 25
+ waiting_for_players_countdown_seconds 1
+ waiting_for_players_percentage_desired 10
+ color "246 127 1 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_FFA
+ gamemode_bullet_001 #GAMEMODE_BULLET_FFA_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_FFA_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_FFA_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_FFA_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_FFA_005
+ }
+ }
+ fra
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fra
+ lobbytitle #PL_fra_lobby
+ description #PL_fra_desc
+ hint #PL_fra_hint
+ abbreviation #PL_fra_abbr
+ image ffa
+ at_turrets_enabled 1
+ max_players 12
+ max_teams 12
+ scorelimit 35
+ waiting_for_players_countdown_seconds 1
+ waiting_for_players_percentage_desired 10
+ earn_meter_tick_disabled 1
+ color "254 184 0 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_FFA
+ gamemode_bullet_001 #GAMEMODE_BULLET_FFA_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_FFA_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_FFA_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_FFA_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_FFA_005
+ }
+ }
+ lts
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_last_titan_standing
+ lobbytitle #PL_last_titan_standing_lobby
+ description #PL_last_titan_standing_desc
+ hint #PL_last_titan_standing_hint
+ abbreviation #PL_last_titan_standing_abbr
+ image lts
+ pick_loadout_extension 20.0
+ pick_loadout_every_round 1
+ pick_loadout_warp_sound 0
+ tts_menu_join_in_progress 0
+ tts_menu_show_score 1
+ loadout_grace_period 0.0
+ max_players 10
+ roundscorelimit 3
+ roundtimelimit 3.0
+ scorelimit 0
+ timelimit 3.0
+ waiting_for_players_percentage_desired 100
+ waiting_for_players_countdown_seconds 30
+ maxExtraRounds 3
+ color "128 128 128 255"
+
+ idlekick_minutes 1
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_LTS
+ gamemode_bullet_001 #GAMEMODE_BULLET_LTS_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_LTS_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_LTS_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_LTS_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_LTS_005
+ }
+ }
+ ps
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_pilot_skirmish
+ lobbytitle #PL_pilot_skirmish_lobby
+ description #PL_pilot_skirmish_desc
+ hint #PL_pilot_skirmish_hint
+ abbreviation #PL_pilot_skirmish_abbr
+ image ps
+ scorelimit 100
+ timelimit 10
+ max_players 16
+ spawn_zone_enabled 1
+ color "193 23 23 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_PS
+ gamemode_bullet_001 #GAMEMODE_BULLET_PS_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_PS_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_PS_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_PS_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_PS_005
+ }
+ }
+ tdm
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_pilot_hunter
+ lobbytitle #PL_pilot_hunter_lobby
+ description #PL_pilot_hunter_desc
+ hint #PL_pilot_hunter_hint
+ abbreviation #PL_pilot_hunter_abbr
+ image tdm
+ scorelimit 75
+ suddendeath_timelimit 2
+ timelimit 10
+ max_players 12
+ gm_hardcore_settings 0
+ spawn_zone_enabled 1
+ color "60 183 0 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ gamemode_bullet_001 #GAMEMODE_BULLET_TDM_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_TDM_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_TDM_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_TDM_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_TDM_005
+ }
+ }
+ ttdm
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_titan_brawl
+ lobbytitle #PL_titan_brawl_lobby
+ description #PL_titan_brawl_desc
+ hint #PL_titan_brawl_hint
+ abbreviation #PL_titan_brawl_abbr
+ image lts
+ scorelimit 30
+ suddendeath_timelimit 2
+ timelimit 10
+ max_players 10
+ respawn_delay 10
+ color "23 193 23 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TTDM
+ gamemode_bullet_001 #GAMEMODE_BULLET_TTDM_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_TTDM_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_TTDM_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_TTDM_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_TTDM_005
+ }
+ }
+ aitdm
+ {
+ inherit defaults
+ vars
+ {
+ name "#PL_aitdm"
+ lobbytitle #PL_aitdm_lobby
+ description #PL_aitdm_desc
+ hint #PL_aitdm_hint
+ abbreviation #PL_aitdm_abbr
+ scorelimit 650
+ suddendeath_timelimit 2
+ timelimit 15
+ max_players 12
+ gm_hardcore_settings 0
+ spawn_zone_enabled 0
+ earn_meter_tick_frac 0
+ escalation_enabled 1
+ skyshow_enabled 1
+ riff_allow_npcs 1
+ enable_spectre_hacking 1
+ color "88 220 151 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ gamemode_bullet_001 #GAMEMODE_BULLET_TDM_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_TDM_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_TDM_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_TDM_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_TDM_005
+ }
+ }
+ mfd
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_marked_for_death
+ lobbytitle #PL_marked_for_death_lobby
+ description #PL_marked_for_death_desc
+ hint #PL_marked_for_death_hint
+ abbreviation #PL_marked_for_death_abbr
+ image mfd
+ scorelimit 10
+ timelimit 12
+ max_players 12
+ gm_hardcore_settings 0
+ spawn_zone_enabled 1
+ earn_meter_tick_rate 3.6
+ earn_meter_pilot_multiplier 0.8
+ color "0 245 119 255"
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_MFD
+ }
+ }
+ speedball
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_live_fire
+ lobbytitle #PL_live_fire_lobby
+ description #PL_live_fire_desc
+ hint #PL_speedball_hint
+ abbreviation #PL_live_fire_abbr
+ image lf
+ scorelimit 5
+ roundscorelimit 5
+ roundtimelimit 1.0
+ suddendeath_timelimit 0
+ timelimit 12
+ max_players 12
+ maxExtraRounds 3
+ phase_shift_drop_flag 1
+ ctf_flag_return_time 1
+ color "127 246 1 255"
+
+ idlekick_minutes 1
+ idlekick_min_alive_seconds 20
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_SPEEDBALL
+ gamemode_bullet_001 #GAMEMODE_BULLET_PS_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_PS_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_PS_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_PS_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_PS_005
+ }
+ }
+ fd
+ {
+ inherit defaults
+ vars
+ {
+ hint #PL_fd_desc
+ ingame_menu_fd_mode 1
+ pick_loadout_extension 30.0
+ pick_loadout_every_round 0
+ pick_loadout_warp_sound 1
+ tts_menu_join_in_progress 1
+ tts_menu_show_score 0
+ roundscorelimit 3
+ roundtimelimit 60.0
+ em_player_fast_kills 12
+ em_player_kills 15
+ max_players 4
+ riff_allow_npcs 1
+ scorelimit 2500
+ suddendeath_timelimit 2
+ titan_build_time 240
+ titan_rebuild_time 240
+ spawn_zone_enabled 1
+ respawn_delay 4
+ timelimit 30
+ skyshow_enabled 1
+ max_teams 1
+ riff_wave_spawn 4
+ wave_spawn_interval 25
+ boost_store_mode fd
+ titan_loadout_experiment 0
+ use_new_tacticals 0
+ earn_meter_tick_frac 0
+ ai_attack_tethers 0
+ waiting_for_players_percentage_desired 100
+ fd_titan_health_adjust 0
+ fd_reaper_health_adjust 0
+ fd_mortar_spectre_setup_time 5
+ fd_grunt_at_weapon_users 0
+ fd_grunt_shield_captains 0
+ fd_player_damage_scalar 1.0
+ fd_harvester_health 25000
+ fd_harvester_shield 6000
+ fd_harvester_regen_delay 10.0
+ fd_harvester_regen_time 10.0
+ fd_money_per_round 600
+ fd_wave_buy_time 60.0
+ fd_pro_titan_shields 0
+ fd_at_unlimited_ammo 1
+ enable_match_progress_update 0
+ color "0 184 254 255"
+ aegis_upgrades 1
+
+ riff_team_share_earn_meter 2
+ riff_team_share_earn_meter_scale 0.25
+ earn_meter_titan_multiplier 0.5
+ earn_meter_pilot_multiplier 0.5
+
+ override_boost_cost_burnmeter_harvester_shield -1
+ override_boost_cost_burnmeter_arc_trap -1
+ override_boost_cost_burnmeter_ap_turret_weapon_infinite -1
+ override_boost_cost_burnmeter_rodeo_grenade -1
+ override_boost_cost_burnmeter_amped_weapons_permanent -1
+ override_boost_cost_burnmeter_instant_battery -1
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_FD
+ gamemode_bullet_001 #GAMEMODE_BULLET_FD_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_FD_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_FD_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_FD_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_FD_005
+ }
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_black_water_canal 1
+ mp_homestead 1
+ mp_wargames 1
+ mp_rise 1
+ mp_thaw 1
+ mp_drydock 1
+ mp_angel_city 1
+ }
+ }
+ fd_easy
+ {
+ inherit fd
+ vars
+ {
+ name #PL_fd_easy
+ lobbytitle #PL_fd_easy_lobby
+ description #PL_fd_easy_desc
+ abbreviation #FD_DIFFICULTY_EASY
+
+ image fd_easy
+ fd_difficulty 0
+ boost_store_team_reserve 1
+ fd_titan_health_adjust -5000
+ fd_reaper_health_adjust -2000
+ fd_mortar_spectre_setup_time 10
+ fd_grunt_at_weapon_users 0
+ fd_grunt_shield_captains 0
+ fd_player_damage_scalar 1.0
+ fd_money_per_round 700
+ fd_pro_titan_shields 0
+ fd_harvester_shield 6000
+ fd_at_unlimited_ammo 1
+
+ earn_meter_pilot_multiplier 0.7
+ }
+ }
+ fd_normal
+ {
+ inherit fd
+ vars
+ {
+ name #PL_fd_normal
+ lobbytitle #PL_fd_normal_lobby
+ description #PL_fd_normal_desc
+ abbreviation #FD_DIFFICULTY_NORMAL
+ image fd_normal
+ fd_difficulty 1
+ boost_store_team_reserve 1
+ fd_titan_health_adjust -2500
+ fd_reaper_health_adjust -1000
+ fd_mortar_spectre_setup_time 10
+ fd_grunt_at_weapon_users 0
+ fd_grunt_shield_captains 0
+ fd_player_damage_scalar 1.0
+ fd_money_per_round 700
+ fd_pro_titan_shields 0
+ fd_harvester_shield 6000
+ fd_at_unlimited_ammo 1
+
+ earn_meter_pilot_multiplier 0.7
+ }
+ }
+ fd_hard
+ {
+ inherit fd
+ vars
+ {
+ name #PL_fd_hard
+ lobbytitle #PL_fd_hard_lobby
+ description #PL_fd_hard_desc
+ abbreviation #FD_DIFFICULTY_HARD
+ image fd_hard
+ fd_difficulty 2
+ boost_store_team_reserve 1
+ fd_titan_health_adjust 0
+ fd_reaper_health_adjust 0
+ fd_mortar_spectre_setup_time 5
+ fd_grunt_at_weapon_users 2
+ fd_grunt_shield_captains 0
+ fd_player_damage_scalar 1.5
+ fd_money_per_round 600
+ fd_pro_titan_shields 0
+ fd_harvester_shield 5000
+ fd_at_unlimited_ammo 1
+
+ earn_meter_pilot_multiplier 0.7
+ }
+ }
+ fd_master
+ {
+ inherit fd
+ vars
+ {
+ name #PL_fd_master
+ lobbytitle #PL_fd_master_lobby
+ description #PL_fd_master_desc
+ abbreviation #FD_DIFFICULTY_MASTER
+ image fd_master
+ fd_difficulty 3
+ boost_store_team_reserve 1
+
+ fd_titan_health_adjust 0
+ fd_reaper_health_adjust 0
+ fd_mortar_spectre_setup_time 5
+ fd_grunt_at_weapon_users 4
+ fd_grunt_shield_captains 1
+ fd_player_damage_scalar 2.5
+ fd_money_per_round 600
+ fd_pro_titan_shields 1
+ fd_harvester_shield 4000
+ fd_at_unlimited_ammo 0
+
+ earn_meter_pilot_multiplier 0.7
+ }
+ }
+ fd_insane
+ {
+ inherit fd
+ vars
+ {
+ name #PL_fd_insane
+ lobbytitle #PL_fd_insane_lobby
+ description #PL_fd_insane_desc
+ abbreviation #FD_DIFFICULTY_INSANE
+ image fd_insane
+ fd_difficulty 4
+ boost_store_team_reserve 1
+ riff_minimap_state 1
+ roundscorelimit 1
+
+ fd_titan_health_adjust 0
+ fd_reaper_health_adjust 0
+ fd_mortar_spectre_setup_time 5
+ fd_grunt_at_weapon_users 4
+ fd_grunt_shield_captains 1
+ fd_player_damage_scalar 2.5
+ fd_money_per_round 600
+ fd_pro_titan_shields 1
+ fd_harvester_shield 4000
+ fd_at_unlimited_ammo 0
+
+ earn_meter_pilot_multiplier 0.5
+ }
+ }
+
+// temp before we have a modular way of doing this in the sdk: custom shit lol
+ sbox
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_sbox
+ lobbytitle #PL_sbox_lobby
+ description #PL_sbox_desc
+ abbreviation #PL_sbox_abbr
+
+ max_teams 1
+ classic_mp 0
+ }
+ }
+ fw
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fw
+ lobbytitle #PL_fw_lobby
+ description #PL_fw_desc
+ abbreviation #PL_fw_abbr
+
+ max_players 16
+ max_teams 2
+ classic_mp 1
+ scorelimit 100 // score is a percentage so ofc need max to be 100%
+ }
+ }
+ gg
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_gg
+ lobbytitle #PL_gg_lobby
+ description #PL_gg_desc
+ hint #PL_gg_hint
+ abbreviation #PL_gg_abbr
+ max_players 12
+ max_teams 12
+ classic_mp 1
+
+ scorelimit 21 // temp until we have a way of dynamically setting non-default scorelimit in code
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_FFA
+ gamemode_bullet_001 #GAMEMODE_BULLET_FFA_001
+ gamemode_bullet_002 #GAMEMODE_BULLET_FFA_002
+ gamemode_bullet_003 #GAMEMODE_BULLET_FFA_003
+ gamemode_bullet_004 #GAMEMODE_BULLET_FFA_004
+ gamemode_bullet_005 #GAMEMODE_BULLET_FFA_005
+ }
+ }
+ tt
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_tt
+ lobbytitle #PL_tt_lobby
+ description #PL_tt_desc
+ hint #PL_tt_hint
+ abbreviation #PL_tt_abbr
+ max_players 12
+ max_teams 2
+ classic_mp 1
+
+ scorelimit 20
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ }
+ }
+ inf
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_inf
+ lobbytitle #PL_inf_lobby
+ description #PL_inf_desc
+ hint #PL_inf_hint
+ abbreviation #PL_inf_abbr
+ max_players 12
+ max_teams 2
+ classic_mp 1
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ }
+ }
+ arena
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_arena
+ lobbytitle #PL_arena_lobby
+ description #PL_arena_desc
+ hint #PL_arena_hint
+ abbreviation #PL_arena_abbr
+ max_players 6
+ max_teams 2
+ classic_mp 1
+ boost_store_mode arena
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ }
+ }
+ kr
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_kr
+ lobbytitle #PL_kr_lobby
+ description #PL_kr_desc
+ hint #PL_kr_hint
+ abbreviation #PL_kr_abbr
+
+ max_players 16
+ max_teams 16
+ classic_mp 1
+ timelimit 7.5
+ scorelimit 99999 // arbitrarily high number
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ }
+ }
+ fastball
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fastball
+ lobbytitle #PL_fastball_lobby
+ description #PL_fastball_desc
+ hint #PL_fastball_hint
+ abbreviation #PL_fastball_abbr
+
+ max_players 16
+ max_teams 2
+ classic_mp 1
+ scorelimit 5
+ timelimit 2
+ roundtimelimit 2
+ roundscorelimit 5
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ }
+ }
+
+
+// END OF MP GAMEMODES LINE ----------------------------------------------------
+ solo
+ {
+ inherit defaults
+ maps
+ {
+ sp_beacon 1
+ }
+ }
+ }
+ Playlists
+ {
+ defaults
+ {
+ vars
+ {
+ name #PL_default_name
+ lobbytitle #PL_default_lobbytitle
+ description #PL_default_description
+ image default
+ visible 0
+ }
+ }
+ rise_247
+ {
+ inherit defaults
+ vars
+ {
+ name "#PL_rise"
+ lobbytitle #PL_rise_lobby
+ description #PL_rise_desc
+ abbreviation #PL_rise_abbr
+ image aitdm
+ visible 0
+ }
+ gamemodes
+ {
+ ctf
+ {
+ maps
+ {
+ mp_rise 1
+ }
+ }
+ aitdm
+ {
+ maps
+ {
+ mp_rise 1
+ }
+ }
+ cp
+ {
+ vars
+ {
+
+ }
+
+ maps
+ {
+ mp_rise 1
+ }
+ }
+ at
+ {
+ maps
+ {
+ mp_rise 1
+ }
+ }
+ lts
+ {
+ maps
+ {
+ mp_rise 1
+ }
+ }
+ }
+ }
+ aitdm
+ {
+ inherit defaults
+ vars
+ {
+ name "#PL_aitdm"
+ lobbytitle #PL_aitdm_lobby
+ description #PL_aitdm_desc
+ abbreviation #PL_aitdm_abbr
+ image aitdm
+ mixtape_slot 0
+ visible 1
+ }
+ gamemodes
+ {
+ aitdm
+ {
+ }
+ }
+ }
+ amped_tacticals_aitdm
+ {
+ inherit defaults
+ vars
+ {
+ name "#PL_amped_tacticals"
+ lobbytitle #PL_amped_tacticals_lobby
+ description #PL_amped_tacticals_desc
+ abbreviation #PL_amped_tacticals_abbr
+ image aitdm
+ //mixtape_slot 0
+ visible 0
+ featured_mode_amped_tacticals 1
+ }
+ gamemodes
+ {
+ aitdm
+ {
+ }
+ }
+ }
+ tactikill_aitdm
+ {
+ inherit defaults
+ vars
+ {
+ name "#PL_tactikill"
+ lobbytitle #PL_tactikill_lobby
+ description #PL_tactikill_desc
+ abbreviation #PL_tactikill_abbr
+ image aitdm
+ //mixtape_slot 0
+ visible 0
+ featured_mode_tactikill 1
+ }
+ gamemodes
+ {
+ aitdm
+ {
+ }
+ }
+ }
+ grapple_aitdm
+ {
+ inherit defaults
+ vars
+ {
+ name "#PL_all_grapple"
+ lobbytitle #PL_all_grapple_lobby
+ description #PL_all_grapple_desc
+ abbreviation #PL_all_grapple_abbr
+ image aitdm
+ //mixtape_slot 0
+ visible 0
+ featured_mode_all_grapple 1
+ }
+ gamemodes
+ {
+ aitdm
+ {
+ }
+ }
+ }
+ phase_aitdm
+ {
+ inherit defaults
+ vars
+ {
+ name "#PL_all_phase"
+ lobbytitle #PL_all_phase_lobby
+ description #PL_all_phase_desc
+ abbreviation #PL_all_phase_abbr
+ image aitdm
+ mixtape_promo_slot 8
+ visible 1
+ featured_mode_all_phase 1
+ }
+ gamemodes
+ {
+ aitdm
+ {
+ }
+ }
+ }
+ spicy_aitdm
+ {
+ inherit defaults
+ vars
+ {
+ name "#PL_all_spicy"
+ lobbytitle #PL_all_spicy_lobby
+ description #PL_all_spicy_desc
+ abbreviation #PL_all_spicy_abbr
+ image aitdm
+ mixtape_promo_slot 7
+ visible 1
+ featured_mode_all_ticks 1
+ }
+ gamemodes
+ {
+ aitdm
+ {
+ }
+ }
+ }
+ at
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_attrition
+ lobbytitle #PL_attrition_lobby
+ description #PL_attrition_desc
+ abbreviation #PL_attrition_abbr
+ image at
+ mixtape_slot 1
+ visible 1
+ }
+ gamemodes
+ {
+ at
+ {
+ }
+ }
+ }
+ cp
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_hardpoint
+ lobbytitle #PL_hardpoint_lobby
+ description #PL_hardpoint_desc
+ abbreviation #PL_hardpoint_abbr
+ image cp
+ mixtape_slot 2
+ visible 1
+ }
+ gamemodes
+ {
+ cp
+ {
+ }
+ }
+ }
+ lts
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_last_titan_standing
+ lobbytitle #PL_last_titan_standing_lobby
+ description #PL_last_titan_standing_desc
+ abbreviation #PL_last_titan_standing_abbr
+ image lts
+ mixtape_slot 3
+ visible 1
+ }
+ gamemodes
+ {
+ lts
+ {
+ }
+ }
+ }
+ alts
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_aegis_last_titan_standing
+ lobbytitle #PL_aegis_last_titan_standing_lobby
+ description #PL_aegis_last_titan_standing_desc
+ abbreviation #PL_aegis_last_titan_standing_abbr
+ image lts
+ visible 0
+ aegis_upgrades 1
+ ingame_menu_fd_mode 1
+ }
+ gamemodes
+ {
+ lts
+ {
+ }
+ }
+ }
+ turbo_lts
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_turbo_last_titan_standing
+ lobbytitle #PL_turbo_last_titan_standing_lobby
+ description #PL_turbo_last_titan_standing_desc
+ abbreviation #PL_turbo_last_titan_standing_abbr
+ image lts
+ mixtape_promo_slot 0
+ visible 1
+ featured_mode_turbo_titans 1
+ earn_meter_titan_multiplier 2.0
+ }
+ gamemodes
+ {
+ lts
+ {
+ }
+ }
+ }
+ ctf
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_capture_the_flag
+ lobbytitle #PL_capture_the_flag_lobby
+ description #PL_capture_the_flag_desc
+ abbreviation #PL_capture_the_flag_abbr
+ image ctf
+ mixtape_slot 4
+ mixtape_timeout 120
+ visible 1
+ }
+ gamemodes
+ {
+ ctf
+ {
+ vars
+ {
+ scorelimit 5
+ }
+
+ maps
+ {
+ mp_complex3 1
+ mp_drydock 2
+ mp_glitch 1
+ mp_homestead 2
+ mp_eden 2
+ mp_forwardbase_kodai 1
+ mp_black_water_canal 2
+ mp_glitch 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_relic02 1
+ mp_grave 2
+ mp_homestead 2
+ mp_drydock 2
+ mp_glitch 1
+ mp_thaw 1
+ mp_eden 2
+ mp_black_water_canal 2
+ mp_glitch 1
+ mp_relic02 1
+ }
+ }
+ }
+ }
+ ps
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_pilot_skirmish
+ lobbytitle #PL_pilot_skirmish_lobby
+ description #PL_pilot_skirmish_desc
+ abbreviation #PL_pilot_skirmish_abbr
+ image ps
+ mixtape_slot 5
+ visible 1
+ }
+ gamemodes
+ {
+ ps
+ {
+ vars
+ {
+
+ }
+
+ maps
+ {
+ mp_black_water_canal 1
+ mp_complex3 1
+ mp_crashsite3 1
+ mp_drydock 1
+ mp_eden 1
+ mp_forwardbase_kodai 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_relic02 1
+ mp_grave 1
+ mp_thaw 1
+ mp_homestead 1
+ mp_glitch 1
+ }
+ }
+ }
+ }
+ ttdm
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_titan_brawl
+ lobbytitle #PL_titan_brawl_lobby
+ description #PL_titan_brawl_desc
+ abbreviation #PL_titan_brawl_abbr
+ image ttdm
+ visible 1
+ max_players 10
+ scorelimit 30
+ mixtape_slot 7
+ boosts_enabled 1
+ respawn_delay 10
+ }
+ gamemodes
+ {
+ ttdm
+ {
+ maps
+ {
+ mp_glitch 1
+ mp_colony02 1
+ mp_wargames 1
+ mp_eden 1
+ mp_drydock 1
+ mp_black_water_canal 1
+ mp_thaw 1
+ mp_homestead 1
+ mp_forwardbase_kodai 1
+ mp_angel_city 1
+ mp_grave 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ turbo_ttdm
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_titan_brawl_turbo
+ lobbytitle #PL_titan_brawl_turbo_lobby
+ description #PL_titan_brawl_turbo_desc
+ abbreviation #PL_titan_brawl_turbo_abbr
+ image ttdm
+ visible 1
+ max_players 10
+ scorelimit 30
+ mixtape_slot 8
+ boosts_enabled 1
+ respawn_delay 10
+
+ featured_mode_turbo_titans 1
+ earn_meter_titan_multiplier 2.0
+ }
+ gamemodes
+ {
+ ttdm
+ {
+ maps
+ {
+ mp_glitch 1
+ mp_colony02 1
+ mp_wargames 1
+ mp_eden 1
+ mp_drydock 1
+ mp_black_water_canal 1
+ mp_thaw 1
+ mp_homestead 1
+ mp_forwardbase_kodai 1
+ mp_angel_city 1
+ mp_grave 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ attdm
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_aegis_titan_brawl
+ lobbytitle #PL_aegis_titan_brawl_lobby
+ description #PL_aegis_titan_brawl_desc
+ abbreviation #PL_aegis_titan_brawl_abbr
+ image ttdm
+ visible 0
+ max_players 10
+ scorelimit 30
+// mixtape_promo_slot 0
+ boosts_enabled 1
+ respawn_delay 10
+ aegis_upgrades 1
+ ingame_menu_fd_mode 1
+ }
+ gamemodes
+ {
+ ttdm
+ {
+ maps
+ {
+ mp_glitch 1
+ mp_colony02 1
+ mp_wargames 1
+ mp_eden 1
+ mp_drydock 1
+ mp_black_water_canal 1
+ mp_thaw 1
+ mp_homestead 1
+ mp_forwardbase_kodai 1
+ mp_angel_city 1
+ mp_grave 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ tdm
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_pilot_hunter
+ lobbytitle #PL_pilot_hunter_lobby
+ description #PL_pilot_hunter_desc
+ abbreviation #PL_pilot_hunter_abbr
+ image tdm
+ visible 1
+ max_players 16
+ //mixtape_slot ##
+ }
+ gamemodes
+ {
+ tdm {}
+ }
+ }
+ ffa
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_ffa
+ lobbytitle #PL_ffa_lobby
+ description #PL_ffa_desc
+ abbreviation #PL_ffa_abbr
+ image ffa
+ //mixtape_slot 7
+ mixtape_timeout 120
+ visible 0
+ }
+ gamemodes
+ {
+ ffa
+ {
+ }
+ }
+ }
+ fra
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fra
+ lobbytitle #PL_fra_lobby
+ description #PL_fra_desc
+ abbreviation #PL_fra_abbr
+ image ffa
+ //mixtape_promo_slot 7
+ visible 0
+ }
+ gamemodes
+ {
+ fra
+ {
+ }
+ }
+ }
+ coliseum
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_coliseum
+ lobbytitle #PL_coliseum_lobby
+ description #PL_coliseum_desc
+ image coliseum
+ mixtape_promo_slot 4
+ visible 1
+ }
+ gamemodes
+ {
+ coliseum {}
+ }
+ }
+ private_match
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_private_match
+ lobbytitle #PL_private_match_lobby
+ description #PL_private_match_desc
+ image private_match
+
+ max_players 16
+ lobby_countdown 15
+ ranking_supported 0
+ mixtape_promo_slot 1
+ double_xp_enabled 0
+ visible 1
+
+ // default custom settings
+ match_visibility 2
+ pilot_health_multiplier 1.0
+ earn_meter_pilot_multiplier 1.0
+ earn_meter_pilot_overdrive 1
+ earn_meter_titan_multiplier 1.0
+ }
+ gamemodes
+ {
+ aitdm
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ tdm
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ cp
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ at
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ ctf
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ lts
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ ps
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ speedball
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ mfd
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ ttdm
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+
+ fd_easy
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_black_water_canal 1
+ mp_homestead 1
+ mp_wargames 1
+ mp_rise 1
+ mp_thaw 1
+ mp_drydock 1
+ mp_angel_city 1
+ }
+ }
+
+ fd_normal
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_black_water_canal 1
+ mp_homestead 1
+ mp_wargames 1
+ mp_rise 1
+ mp_thaw 1
+ mp_drydock 1
+ mp_angel_city 1
+ }
+ }
+
+ fd_hard
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_black_water_canal 1
+ mp_homestead 1
+ mp_wargames 1
+ mp_rise 1
+ mp_thaw 1
+ mp_drydock 1
+ mp_angel_city 1
+ }
+ }
+
+ fd_master
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_black_water_canal 1
+ mp_homestead 1
+ mp_wargames 1
+ mp_rise 1
+ mp_thaw 1
+ mp_drydock 1
+ mp_angel_city 1
+ }
+ }
+
+ fd_insane
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_black_water_canal 1
+ mp_homestead 1
+ mp_wargames 1
+ mp_rise 1
+ mp_thaw 1
+ mp_drydock 1
+ mp_angel_city 1
+ }
+ }
+ }
+ }
+ nitro_mixtape
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_nitro_mixtape
+ lobbytitle #PL_nitro_mixtape_lobby
+ description #PL_nitro_mixtape_desc
+ abbreviation #PL_nitro_mixtape_abbr
+ image lf
+ visible 1
+ scorelimit 5
+ //mixtape_promo_slot 0
+ boosts_enabled 1
+ phase_shift_drop_flag 1
+ ctf_flag_return_time 1
+ }
+ gamemodes
+ {
+ ctf
+ {
+ maps
+ {
+ mp_lf_stacks 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_lf_traffic 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ }
+ }
+ mfd
+ {
+ maps
+ {
+ mp_lf_stacks 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_lf_traffic 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ }
+ }
+ ps
+ {
+ maps
+ {
+ mp_lf_stacks 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_lf_traffic 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ }
+ }
+ }
+ }
+ nitro_ffa
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_nitro_ffa
+ lobbytitle #PL_nitro_ffa_lobby
+ description #PL_nitro_ffa_desc
+ abbreviation #PL_nitro_ffa_abbr
+ image lf
+ visible 0
+ scorelimit 5
+// mixtape_promo_slot 0
+ boosts_enabled 1
+ phase_shift_drop_flag 1
+ max_players 6
+ max_teams 6
+ }
+ gamemodes
+ {
+ ffa
+ {
+ maps
+ {
+ mp_lf_stacks 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_lf_traffic 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ }
+ }
+ }
+ }
+ ctf_lf
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_ctf_lf
+ lobbytitle #PL_ctf_lf_lobby
+ description #PL_ctf_lf_desc
+ abbreviation #PL_ctf_lf_abbr
+ image lf
+ visible 0
+ scorelimit 5
+ //mixtape_promo_slot 8
+ boosts_enabled 1
+ phase_shift_drop_flag 1
+ ctf_flag_return_time 1
+ }
+ gamemodes
+ {
+ ctf
+ {
+ maps
+ {
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_glitch 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ }
+ }
+ }
+ }
+ lf
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_live_fire
+ lobbytitle #PL_live_fire_lobby
+ description #PL_live_fire_desc
+ abbreviation #PL_live_fire_abbr
+ image lf
+ visible 1
+ scorelimit 50
+ mixtape_slot 6
+ }
+ gamemodes
+ {
+ speedball
+ {
+ maps
+ {
+ mp_lf_stacks 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_lf_traffic 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ }
+ }
+ }
+ }
+ rocket_lf
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_rocket_arena
+ lobbytitle #PL_rocket_arena_lobby
+ description #PL_rocket_arena_desc
+ abbreviation #PL_rocket_arena_abbr
+ image lf
+ visible 1
+ scorelimit 50
+ roundtimelimit 2.0
+ mixtape_promo_slot 6
+ featured_mode_rocket_arena 1
+ boosts_enabled 1
+ }
+ gamemodes
+ {
+ speedball
+ {
+ maps
+ {
+ mp_lf_stacks 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_lf_traffic 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ }
+ }
+ }
+ }
+ holopilot_lf
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_all_holopilot
+ lobbytitle #PL_all_holopilot_lobby
+ description #PL_all_holopilot_desc
+ abbreviation #PL_all_holopilot_abbr
+ image lf
+ visible 0
+ scorelimit 50
+ //mixtape_slot 6
+ featured_mode_all_holopilot 1
+ }
+ gamemodes
+ {
+ speedball
+ {
+ maps
+ {
+ mp_lf_stacks 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_lf_traffic 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ }
+ }
+ }
+ }
+ mfd
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_marked_for_death
+ lobbytitle #PL_marked_for_death_lobby
+ description #PL_marked_for_death_desc
+ abbreviation #PL_marked_for_death_abbr
+ image mfd
+ visible 0
+// mixtape_promo_slot 5
+ }
+ gamemodes
+ {
+ mfd {}
+ }
+ }
+ //DEV
+ fnf
+ {
+ inherit defaults
+ vars
+ {
+ name "Friday Night Fights"
+ lobbytitle "Friday Night Fights Lobby"
+ description "Fights on Friday"
+ image varietypack
+ visible 0
+// mixtape_promo_slot 4
+ }
+ gamemodes
+ {
+ ctf
+ {
+ maps
+ {
+ mp_relic02 1
+
+ }
+ }
+
+ lts
+ {
+ maps
+ {
+ mp_relic02 1
+ }
+ }
+ }
+ }
+ //DEV
+ fd
+ {
+ inherit defaults
+ vars
+ {
+ name #MATCHMAKING_PVE_PLAY_BUTTON
+ lobbytitle #PL_fd_lobby
+ description #COOP_DESC
+ image ffa
+ visible 1
+ mixtape_promo_slot 9
+ riff_team_share_earn_meter 2
+ riff_team_share_earn_meter_scale 0.25
+ earn_meter_titan_multiplier 0.5
+ ingame_menu_fd_mode 1
+ promo_note #PL_promo_coop
+ }
+ gamemodes
+ {
+ fd
+ {}
+ }
+ }
+ //DEV
+ fd_easy
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fd_easy
+ lobbytitle #PL_fd_easy_lobby
+ description #PL_fd_easy_desc
+ abbreviation #FD_DIFFICULTY_EASY
+ image fd_easy
+ visible 1
+ }
+ gamemodes
+ {
+ fd
+ {}
+ }
+ gamemodesWithAncestor
+ {
+ fd_easy
+ {}
+ }
+ }
+ //DEV
+ fd_normal
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fd_normal
+ lobbytitle #PL_fd_normal_lobby
+ description #PL_fd_normal_desc
+ abbreviation #FD_DIFFICULTY_NORMAL
+ image fd_normal
+ visible 1
+ }
+ gamemodes
+ {
+ fd
+ {}
+ }
+ gamemodesWithAncestor
+ {
+ fd_normal
+ {}
+ }
+ }
+ //DEV
+ fd_hard
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fd_hard
+ lobbytitle #PL_fd_hard_lobby
+ description #PL_fd_hard_desc
+ abbreviation #FD_DIFFICULTY_HARD
+ image fd_hard
+ visible 1
+ }
+ gamemodes
+ {
+ fd
+ {}
+ }
+ gamemodesWithAncestor
+ {
+ fd_hard
+ {}
+ }
+ }
+ //DEV
+ fd_master
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fd_master
+ lobbytitle #PL_fd_master_lobby
+ description #PL_fd_master_desc
+ abbreviation #FD_DIFFICULTY_MASTER
+ image fd_master
+ visible 1
+ }
+ gamemodes
+ {
+ fd
+ {}
+ }
+ gamemodesWithAncestor
+ {
+ fd_master
+ {}
+ }
+ }
+ fd_insane
+ {
+ inherit fd
+ vars
+ {
+ name #PL_fd_insane
+ lobbytitle #PL_fd_insane_lobby
+ description #PL_fd_insane_desc
+ image fd_insane
+ visible 1
+ mixtape_promo_slot 5
+ promo_note #MP_HOMESTEAD
+ }
+ gamemodes
+ {
+ fd
+ {}
+ }
+ gamemodesWithAncestor
+ {
+ fd_insane
+ {
+ maps
+ {
+ mp_homestead 1
+ }
+ }
+ }
+ }
+ //DEV
+ fd_dev
+ {
+ inherit fd_hard
+ vars
+ {
+ pick_loadout_extension 0
+ }
+ }
+
+ gg
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_gg
+ lobbytitle #PL_gg_lobby
+ description #PL_gg_desc
+ abbreviation #PL_gg_abbr
+ image ffa
+ //mixtape_slot 7
+ mixtape_timeout 120
+ visible 0
+ }
+ gamemodes
+ {
+ gg
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ tt
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_tt
+ lobbytitle #PL_tt_lobby
+ description #PL_tt_desc
+ hint #PL_tt_hint
+ abbreviation #PL_tt_abbr
+ visible 0
+ }
+ gamemodes
+ {
+ tt
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ inf
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_inf
+ lobbytitle #PL_inf_lobby
+ description #PL_inf_desc
+ hint #PL_inf_hint
+ abbreviation #PL_inf_abbr
+ visible 0
+ }
+ gamemodes
+ {
+ inf
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ arena
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_arena
+ lobbytitle #PL_arena_lobby
+ description #PL_arena_desc
+ hint #PL_arena_hint
+ abbreviation #PL_arena_abbr
+ max_players 6
+ max_teams 2
+ classic_mp 1
+ boost_store_mode arena
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ }
+ gamemodes
+ {
+ arena
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ kr
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_kr
+ lobbytitle #PL_kr_lobby
+ description #PL_kr_desc
+ hint #PL_kr_hint
+ abbreviation #PL_kr_abbr
+
+ max_players 16
+ max_teams 16
+ classic_mp 1
+ timelimit 7.5
+ scorelimit 99999 // arbitrarily high number
+ }
+ gamemodes
+ {
+ kr
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_lf_stacks 1
+ mp_lf_deck 1
+ mp_lf_meadow 1
+ mp_lf_traffic 1
+ mp_lf_township 1
+ mp_lf_uma 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ fastball
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fastball
+ lobbytitle #PL_fastball_lobby
+ description #PL_fastball_desc
+ hint #PL_fastball_hint
+ abbreviation #PL_fastball_abbr
+
+ max_players 16
+ max_teams 2
+ classic_mp 1
+ scorelimit 5
+ roundtimelimit 2
+ roundscorelimit 5
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ }
+ gamemodes
+ {
+ fastball
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+ fw
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_fw
+ lobbytitle #PL_fw_lobby
+ description #PL_fw_desc
+ hint #PL_fw_hint
+ abbreviation #PL_fw_abbr
+
+ max_players 16
+ max_teams 2
+ classic_mp 1
+ scorelimit 100
+
+ gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
+ }
+ gamemodes
+ {
+ fw
+ {
+ maps
+ {
+ mp_forwardbase_kodai 1
+ mp_grave 1
+ mp_homestead 1
+ mp_thaw 1
+ mp_black_water_canal 1
+ mp_eden 1
+ mp_drydock 1
+ mp_crashsite3 1
+ mp_complex3 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_glitch 1
+ mp_relic02 1
+ mp_wargames 1
+ mp_rise 1
+ }
+ }
+ }
+ }
+
+
+// END OF MP PLAYLISTS LINE ----------------------------------------------------
+
+ "Load a map on the command line"
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_load_a_map_on_the_command_line
+ description #PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work
+
+ cmdlineMapLoad 1
+
+ classic_mp 0
+ prematch_time 4
+ titan_build_time 5
+ titan_build_time_use_set_file 0
+ titan_rebuild_time 5
+ }
+ gamemodes
+ {
+ tdm
+ {
+ maps
+ {
+ mp_lobby 1
+ }
+ }
+ }
+ }
+ buildain
+ {
+ inherit defaults
+ vars
+ {
+ name #PL_pl_rebuild_all_paths
+ description #PL_pl_run_with_ai_ainrebuildonmapstart_2
+
+ classic_mp 0
+ ranking_supported 0
+ }
+ gamemodes
+ {
+ tdm
+ {
+ maps
+ {
+ mp_titan_melee 1
+ mp_titan_rodeo 1
+ mp_angel_city 1
+ mp_colony02 1
+ mp_crashsite2 1
+ mp_beacon 1
+ mp_test_engagement_range 1
+ mp_thaw 1
+ mp_homestead 1
+ mp_grave 1
+ mp_forwardbase_kodai 1
+ mp_box 1
+ mp_lobby 1
+ }
+ }
+ }
+ }
+ solo
+ {
+ inherit defaults
+ vars
+ {
+ always_enable_autotitans 1
+ burn_meter_enabled 0
+ cinematic_mode 1
+ classic_mp 0
+ hud_score_enabled 0
+ max_players 16
+ max_teams 1
+ ranking_supported 0
+ riff_allow_npcs 1
+ riff_minimap_state 1
+ riff_titan_availability 3
+ riff_titan_exit_enabled 3
+ rodeo_battery_disembark_to_pickup 0
+ titan_build_time 300
+ titan_health_bar_display default
+ titan_mode_change_allowed 0
+ titan_rebuild_time 195
+ titan_segmented_health 0
+ titan_shield_regen 1
+ }
+ gamemodes
+ {
+ solo {}
+ }
+ }
+ new_titan_models
+ {
+ inherit defaults
+ vars
+ {
+ classic_mp 0
+ prematch_time 4
+ r2_titan_models 1
+ titan_build_time 20
+ titan_rebuild_time 20
+ }
+ gamemodes
+ {
+ solo
+ {
+ maps
+ {
+ sp_enemies 1
+ }
+ }
+ }
+ }
+ }
+ "LocalizedStrings"
+ {
+ "lang"
+ {
+ "Language" "german"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "Dein Abenteuer in der beeindruckenden Welt des Grenzlandes geht mit \"Postkarten aus dem Grenzland\", dem neuesten DLC für Titanfall 2, weiter. Mit neuen und bekannten Schauplätzen und einer neuen Sammlung von Elitewaffen-Kriegslackierungen hat das Grenzland nie besser ausgesehen.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_00_Q" "\"Postkarten aus dem Grenzland\"-Trailer"
+ "COMMUNITYUPDATE_01_A" "Informiere dich über alle im \"Postkarten aus dem Grenzland\"-Patch enthaltenen Änderungen.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_01_Q" "Postkarten aus dem Grenzland: Die Patch-Notizen"
+ "COMMUNITYUPDATE_02_A" "Das demnächst für Mobilgeräte erscheinende Titanfall: Assault ist ein Echtzeit-RTS im Titanfall-Universum, das wir mit Particle City entwickeln. Hier siehst du das Spiel in Aktion.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_02_Q" "Titanfall: Assault-Veröffentlichungstrailer"
+ "COMMUNITYUPDATE_03_A" "Iniquity führt uns durch das Tutorial und gibt uns grundlegende Tipps zu Titanfall: Assault. Der perfekte Weg, mehr über das Spiel zu erfahren.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_03_Q" "Titanfall: Assault: \"Die Grundlagen\"-Video"
+ "COMMUNITYUPDATE_04_A" "Höre den Machern von Grenzlandverteidigung zu, wenn sie über die Geschichte und Entwicklung des Modus sprechen und sieh uns zu, wie wir einige Runden spielen!\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_04_Q" "Respawn spielt Grenzlandverteidigung"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger hat eine witzige Montage aus zahlreichen coolen Moves mit der Impulsklinge zusammengestellt!\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_05_Q" "Community-Beiträge: Komm zum Punkt"
+ "COMMUNITYUPDATE_06_A" "ConzeyG zeigt uns über reddit die brutale Effizienz des Northstar, wenn er im Todgeweiht-Modus Piloten erledigt.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_06_Q" "Community-Beiträge - Northstar-Pilotenjagd"
+ "COMMUNITYUPDATE_07_A" "Jetzt verfügbar! Grenzlandverteidigung und Rise kehren mit brandneuen, kaufbaren Kriegslackierungen und einer neuen Live Fire-Karte zurück. Hier siehst du alles in Aktion.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_07_Q" "\"Operation Frontier Shield\" Gameplay-Trailer"
+ "COMMUNITYUPDATE_08_A" "Jetzt verfügbar! Die legendäre Kriegsspiele-Karte kehrt in Titanfall 2 zurück und sieht besser denn je aus. Erlebe außerdem die neue Exekution und die neue Live Fire-Karte \"Verkehr\" in Aktion.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_08_Q" "\"Die Kriegsspiele\" Gameplay-Trailer"
+ "COMMUNITYUPDATE_09_A" "Dieser DLC enthält mit dem Monarch den 7. Multiplayer-Titan, eine überarbeitete Version der Relikt-Karte, eine neue Exekution sowie kaufbare Ronin- und Tone-Prime-Titans, mehr Tarnungen, Banner und Nose-Arts. Hier kannst du dir die Action ansehen.\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_09_Q" "\"Monarch's Reign\" Gameplay-Trailer"
+ "COMMUNITYUPDATE_10_A" "Im Grenzland ist nicht alles so, wie es scheint – bereite dich auf den neuesten kostenlosen Inhaltspack zum Herunterladen für Titanfall 2 vor: Störimpuls im Grenzland – mit der neuen Karte \"Glitch\". Die Inspiration dafür war Captain Lastimosas Heimatplanet Harmony, wo vertikale Höhenunterschiede und lange, gewundene Pfade das Landschaftsbild dominieren – ideal, um mit Wandlaufkombinationen elegant über die Karte zu gleiten. Eine neue Live Fire-Karte ist ebenfalls am Start: Deck, mit engen Innenräumen, offenen Höfen und wachsamen Drohnen, die über dir kreisen. Falls du dich überfordert fühlst, kannst du auf die hilfreichen M.R.V.N.s zugreifen, die dir jetzt als brandneue Gruppierung ihre fröhliche Roboterhand reichen. Und zu guter Letzt kannst du noch die neue Impulsklingen-Exekution freischalten, wann immer du es für notwendig hältst, dich durchzusetzen.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_10_Q" "\"Störimpuls im Grenzland\" Gameplay-Trailer"
+ "COMMUNITYUPDATE_11_A" "Kehre auf eine der Lieblingskarten der Fans aus Titanfall 1 zurück: Kolonie. Bringe all die neuen Taktiken und Titans aus Titanfall 2 ein, die alle Piloten & Titans nutzen müssen, um an diesem idyllischen Ort voller enger und verwinkelter Gassen und ungeschützter Dächer zu überleben. Ab dem 30. März für alle Spieler kostenlos erhältlich! Der Kolonierückkehr-DLC enthält außerdem klassische Waffen wie das Sturmgewehr R-101, eine neue Haken-Exekution sowie neue kosmetische Optionen, damit du perfekt aussiehst, wenn du die Stadt im Chaos versinken lässt.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_11_Q" "Trailer - Kolonierückkehr-DLC"
+ "COMMUNITYUPDATE_12_A" "Vorstellung von Live Fire: ein rasanter 6 vs. 6 Piloten-Modus, der aufregende Nahkämpfe an vorderster Front bietet. Enthält zwei neue, speziell für Live Fire entworfene Karten: Stacks und Meadow. Die beiden kostenlosen Karten sind eng umgrenzte Todeszonen, die perfekt auf die Rasanz und Intensität des Modus abgestimmt sind.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_12_Q" "Trailer - Live Fire-DLC"
+ "COMMUNITYUPDATE_13_A" "Erlebe eine Neuauflage der beliebten `1Angel City`0-Karte aus dem ersten Titanfall. Mach dich bereit für die ersten kostenlosen Inhalte zum Herunterladen für Titanfall 2. `1Angel City's Most Wanted`0 ist ab dem 1. Dezember für alle Spieler verfügbar. Die Inhalte bieten neue kosmetische Optionen, die dem Grenzland noch mehr Flair verleihen. \n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_13_Q" "Trailer - Angel City-DLC"
+ "COMMUNITYUPDATE_14_A" "Willkommen zurück!\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_14_Q" "Trailer - Encore"
+ "COMMUNITYUPDATE_15_A" "Zwei Legenden, ein Erbe.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_15_Q" "Trailer - Become One"
+ "COMMUNITYUPDATE_16_A" "Grenzenlos.\n\n\nZum Ansehen `2%[A_BUTTON|MOUSE1]%`0 drücken."
+ "COMMUNITYUPDATE_16_Q" "Trailer - Piloten-Gameplay"
+ "COMMUNITYUPDATE_DESC" "`3Titanfall Community-Updates`0\n\nInfos und Links aus dem ganzen Netz.\n\nWeitere Infos:\n`2%$rui/bullet_point%`0Folge uns auf Twitter `1@Respawn`0\n`2%$rui/bullet_point%`0Folge uns auf `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0Besuche uns auf `1www.respawn.com`0"
+ "COMMUNITYUPDATE_NAME" "Community"
+ "KNB_SUBJECT_00_DESC" "`3Was ist neu in Titanfall?`0\n\nHier erfährst du, was sich in Titanfall 2 geändert hat!"
+ "KNB_SUBJECT_00_NAME" "Spiel-Updates"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 Neue Rufzeichen-Banner der Community\n\n`2%$rui/bullet_point%`0Alle Fürsprechergeschenke können jetzt mit Credits erworben werden\n\n`2%$rui/bullet_point%`0Neue kaufbare Store-Inhalte\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 28. November - Erntezeit"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Pistolen-Primärslot\n\n`2%$rui/bullet_point%`0Halloween-Banner\n\n`2%$rui/bullet_point%`0Balance-Änderungen\n\n`2%$rui/bullet_point%`0Neue kaufbare Store-Inhalte\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 31. Oktober - Süßes und Saures"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0Grenzlandverteidigung - Unterstützung für 3 weitere Karten - Drydock, Angel City und Exoplanet.\n\n`2%$rui/bullet_point%`0Neue Live Fire-Karte - UMA\n\n`2%$rui/bullet_point%`0Neue Piloten-Exekution - Loch in der Wand\n\n`2%$rui/bullet_point%`0Neue kaufbare Store-Inhalte"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 29. August - Postkarten aus dem Grenzland"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Grenzlandverteidigung - Ein neuer Koop-Modus, bei dem du mit drei anderen Spielern ein wichtiges Einsatzziel gegen immer stärker werdende Wellen von KI-Einheiten verteidigst. Kommunikation und Anpassung sind hier entscheidend, um zu überleben.\n\n`2%$rui/bullet_point%`0Neue Karte - Aufstieg\n\n`2%$rui/bullet_point%`0Neue Live Fire-Karte - Township\n\n`2%$rui/bullet_point%`0Neue kaufbare Store-Inhalte"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 25. Juli - Operation Frontier Shield"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0Neue Karte - Kriegsspiele\n\n`2%$rui/bullet_point%`0Neue Live Fire-Karte - Verkehr\n\n`2%$rui/bullet_point%`0Neue Piloten-Exekution - Schattenboxen\n\n`2%$rui/bullet_point%`03. Waffenslot\n\n`2%$rui/bullet_point%`0Privatspiel-Einstellungen"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 27. Juni - Kriegsspiele"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0Neuer Titan – Monarch\n\n`2%$rui/bullet_point%`0Neue Karte – Relikt\n\n`2%$rui/bullet_point%`0Neue Piloten-Exekution – Jetzt siehst du mich\n\n`2%$rui/bullet_point%`0Neue, käuflich zu erwerbende Store-Inhalte"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 30. Mai – Monarch's Reign"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0Neue Karte – Glitch\n\n`2%$rui/bullet_point%`0Neue Live Fire-Karte – Deck\n\n`2%$rui/bullet_point%`0Neue Gruppierung – M.R.V.N.\n\n`2%$rui/bullet_point%`0Neue Piloten-Exekution – Komm zum Punkt\n\n`2%$rui/bullet_point%`0Maximalgeneration auf 100 erhöht"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 25. April – Störimpuls im Grenzland"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0Neue Karte – Kolonie\n\n`2%$rui/bullet_point%`0Neue Piloten-Exekution – Curb-Check\n\n`2%$rui/bullet_point%`0Neue Waffe – R-101\n\n`2%$rui/bullet_point%`0Neue, käuflich zu erwerbende Store-Inhalte"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 30. März – Kolonierückkehr"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0Live Fire – ein neuer Pilot vs. Pilot Eliminierungsmodus! In diesem rundenbasierten 6 vs. 6-Modus ohne Respawns habt ihr nur eine Minute Zeit, das gegnerische Team zu besiegen und die Runde zu gewinnen. Ihr gewinnt die Runde aber auch, wenn euer Team am Ende der Runde die neutrale Flagge hält. Das Team, das zuerst 5 Runden gewinnt, wird zum Sieger erklärt.\n\n`2%$rui/bullet_point%`0Neue Live Fire-Karte – Meadow\n\n`2%$rui/bullet_point%`0Neue Live Fire-Karte – Stacks\n\n`2%$rui/bullet_point%`0Neue Piloten-Exekution – Später Treffer"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 23. Februar – Live Fire"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0Neue Karte – Angel City\n\n`2%$rui/bullet_point%`0Neue Waffe – B3 Wingman Elite\n\n`2%$rui/bullet_point%`0Neue Piloten-Exekution – Innenleben\n\n`2%$rui/bullet_point%`0Neue Titan-Kits\n\n`2%$rui/bullet_point%`0Neue, käuflich zu erwerbende Store-Inhalte"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 30. November – Angel City's Most Wanted"
+ "MP_ANGEL_CITY_FD_WAVE_1" "An den Himmel gekettet"
+ "MP_ANGEL_CITY_FD_WAVE_2" "Formlosigkeit"
+ "MP_ANGEL_CITY_FD_WAVE_3" "Auflehnung"
+ "MP_ANGEL_CITY_FD_WAVE_4" "Ein störrischer Haufen"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Reißleine"
+ "MP_DRYDOCK_FD_WAVE_1" "Auf in fremde Gewässer"
+ "MP_DRYDOCK_FD_WAVE_2" "Sie haben keine Kugeln!"
+ "MP_DRYDOCK_FD_WAVE_3" "Von der Forschung geblendet"
+ "MP_DRYDOCK_FD_WAVE_4" "Hochdrucksystem"
+ "MP_DRYDOCK_FD_WAVE_5" "Auge des Sturms"
+ "MP_THAW_FD_WAVE_1" "Lagebewusstsein"
+ "MP_THAW_FD_WAVE_2" "Zahlenmäßige Stärke"
+ "MP_THAW_FD_WAVE_3" "Handlanger 21"
+ "MP_THAW_FD_WAVE_4" "Spaß im Feuer"
+ "MP_THAW_FD_WAVE_5" "Nutzt eure Waffen"
+ "NO_PRICE" "Angebot abgelaufen"
+ "NO_PRICE_TWO_LINES" "Angebot\nabgelaufen"
+ "PL_aegis_last_titan_standing" "Aegis-LTS"
+ "PL_aegis_last_titan_standing_abbr" "ALTS"
+ "PL_aegis_last_titan_standing_desc" "Aegis-Upgrades aktiviert. Rundenbasiertes Eliminierungsmatch. Sieger ist, wer zuerst 3 Runden für sich entscheidet.\n^FFC83200Spieler: 5 vs. 5 * Starte als Titan\nZeitlimit: 3 Min. pro Runde * Keine Respawns\nMax. Party-Größe: 5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "Aegis Titan Brawl"
+ "PL_aegis_titan_brawl_abbr" "ATB"
+ "PL_aegis_titan_brawl_desc" "Eliminiere gegnerische Titans. Aegis-Upgrades aktiviert.\n^FFC83200Spieler: 5 vs. 5\nZeitlimit: 10 Min.\nMax. Party-Größe: 5"
+ "PL_aegis_titan_brawl_hint" "Neutralisiere gegnerische Titans.\nKein Ausstieg"
+ "PL_aegis_titan_brawl_lobby" "Aegis Titan Brawl-Lobby"
+ "PL_aitdm" "Materialschlacht"
+ "PL_aitdm_abbr" "MAT"
+ "PL_aitdm_desc" "Töte alle Gegner.\n^FFC83200Spieler: 6 vs. 6 *KI\nZeitlimit: 10 Min.\nMax. Party-Größe: 6"
+ "PL_aitdm_lobby" "Materialschlacht-Lobby"
+ "PL_all_grapple" "Angriff auf Titanfall"
+ "PL_all_grapple_abbr" "MAT"
+ "PL_all_grapple_desc" "Klassische Materialschlacht-Regeln, allerdings werden alle Taktikfähigkeiten durch \"Haken\" ersetzt.\n^FFC83200Spieler: 6 vs. 6 *KI\nZeitlimit: 10 Min.\nMax. Party-Größe: 6"
+ "PL_all_grapple_lobby" "Angriff auf Titanfall-Lobby"
+ "PL_all_holopilot" "Totale Verwirrung"
+ "PL_all_holopilot_abbr" "LF"
+ "PL_all_holopilot_desc" "Klassische Live-Fire-Regeln, allerdings werden alle Taktikfähigkeiten durch \"Holopilot\" ersetzt.^FFC83200\nSpieler: 6 vs. 6 *Keine Titans\nZeitlimit: 60 Sek. *Keine Respawns\nMax. Party-Größe: 6"
+ "PL_all_holopilot_lobby" "Totale Verwirrung-Lobby"
+ "PL_all_phase" "Die andere Seite"
+ "PL_all_phase_abbr" "MAT"
+ "PL_all_phase_desc" "Klassische Materialschlacht-Regeln, allerdings werden alle Taktikfähigkeiten durch \"Phase\" ersetzt.\n^FFC83200Spieler: 6 vs. 6 *KI\nZeitlimit: 10 Min.\nMax. Party-Größe: 6"
+ "PL_all_phase_lobby" "Die andere Seite-Lobby"
+ "PL_all_spicy" "Würzige Materialschlacht"
+ "PL_all_spicy_abbr" "MAT"
+ "PL_all_spicy_desc" "Klassische Materialschlacht-Regeln, allerdings werden alle Taktikfähigkeiten durch Ticks ersetzt.\n^FFC83200Spieler: 6 vs. 6 *KI\nZeitlimit: 10 Min.\nMax. Party-Größe: 6"
+ "PL_all_spicy_lobby" "Würzige Materialschlacht-Lobby"
+ "PL_amped_tacticals" "Verstärkte Taktiken"
+ "PL_amped_tacticals_abbr" "MAT"
+ "PL_amped_tacticals_desc" "Klassische Materialschlacht-Regeln, allerdings sind alle Taktikfähigkeiten effektiver.\n^FFC83200Spieler: 6 vs. 6 *KI\nZeitlimit: 10 Min.\nMax. Party-Größe: 6"
+ "PL_amped_tacticals_lobby" "Verstärkte Taktiken-Lobby"
+ "PL_ANGEL_CITY" "Angel City 24/7"
+ "PL_angel_city_abbr" "AC"
+ "PL_angel_city_desc" "Ganz Angel City, die ganze Zeit.\n^FFC83200*Kopfgeldjagd *Materialschlacht\n*Verstärkter Hardpoint"
+ "PL_angel_city_lobby" "Angel City 24/7-Lobby"
+ "PL_at_coop" "Flucht (Koop)"
+ "PL_at_coop_desc" "Du bist nur mit einer Pistole bewaffnet aus einem Gefängnis entkommen. Überlebe, bis du evakuiert werden kannst. Neutralisiere Gegner, um Geld zu erhalten und Waffen zu kaufen. \n^FFC83200Spieler: 6\nMax. Party-Größe: 6"
+ "PL_at_coop_lobby" "Flucht-Lobby"
+ "PL_attrition" "Kopfgeldjagd"
+ "PL_attrition_abbr" "KGJ"
+ "PL_attrition_desc" "Neutralisiere Gegner, um Geld zu erhalten. Verdiene mehr, indem du deine Boni an ausgewiesenen Stellen 'auf die Bank bringst'.\n^FFC83200Spieler: 5 vs. 5 * KI\nZeitlimit: 10 Min.\nMax. Party-Größe: 5"
+ "PL_attrition_lobby" "Kopfgeldjagd-Lobby"
+ "PL_capture_the_flag" "Capture the Flag"
+ "PL_capture_the_flag_abbr" "CTF"
+ "PL_capture_the_flag_desc" "Stiehl die feindliche Flagge und bringe sie zu deiner Basis, während du das feindliche Team davon abhältst, sich deine Flagge zu schnappen!\n^FFC83200Spieler: 5 vs. 5\nZeitlimit: 12 Min.\nMax. Party-Größe: 5"
+ "PL_capture_the_flag_lobby" "CTF-Lobby"
+ "PL_coliseum" "Kolosseum"
+ "PL_coliseum_desc" "Duell mit erweiterter Beweglichkeit in einem Käfig. Schalte deinen Gegner aus, um zu gewinnen. Wer 3 von 5 Runden gewinnt, erhält ein Freund-Geschenk.^FFC83200\nSpieler: 1 vs. 1 *Keine Titans\nZeitlimit: 3 Min. *Keine Respawns\n**^FFFFFF00ERFORDERT KOLOSSEUM-TICKET oder BEZAHLTE TEILNAHMEGEBÜHR"
+ "PL_coliseum_lobby" "Kolosseum-Lobby"
+ "PL_colony" "Kolonie 24/7"
+ "PL_colony_abbr" "KOL"
+ "PL_colony_desc" "Kolonie, rund um die Uhr. ^CCCCCC00Sucht nach Spielen in den Modi ^FFC83200Materialschlacht^CCCCCC00, ^FFC83200Piloten vs. Piloten^CCCCCC00 und ^FFC83200Last Titan Standing^CCCCCC00."
+ "PL_colony_lobby" "Kolonie 24/7-Lobby"
+ "PL_ctf_lf" "CTF (Nitro)"
+ "PL_ctf_lf_abbr" "CTF-N"
+ "PL_ctf_lf_desc" "Rasantes Capture the Flag auf ausgewählten Karten.\n^FFC83200Spieler: 5 vs. 5\nMax. Party-Größe: 5\n^F4D5A600Flagge wird sofort zurückgebracht"
+ "PL_ctf_lf_lobby" "CTF Nitro-Lobby"
+ "PL_default_description" "Standardbeschreibung"
+ "PL_default_lobbytitle" "Standard-Lobbytitel"
+ "PL_default_name" "Standardname"
+ "PL_don" "Doppelt oder nichts"
+ "PL_fd" "Grenzlandverteidigung"
+ "PL_fd_desc" "Verteidige dich gegen Wellen von Truppen der Restflotte"
+ "PL_fd_easy" "Grenzlandverteidigung: Leicht"
+ "PL_fd_easy_desc" "Zerschlage den Gegner"
+ "PL_fd_easy_lobby" "Grenzlandverteidigung: Leicht-Lobby"
+ "PL_fd_hard" "Grenzlandverteidigung: Schwer"
+ "PL_fd_hard_desc" "Können ist erforderlich"
+ "PL_fd_hard_lobby" "Grenzlandverteidigung: Schwer-Lobby"
+ "PL_fd_insane" "Grenzlandverteidigung: Wahnsinn"
+ "PL_fd_insane_desc" "Du wirst nicht überleben"
+ "PL_fd_insane_lobby" "Grenzlandverteidigung: Wahnsinn – Lobby"
+ "PL_fd_lobby" "Grenzlandverteidigung-Lobby"
+ "PL_fd_master" "Grenzlandverteidigung: Meister"
+ "PL_fd_master_desc" "Nur die Besten der Besten werden erfolgreich sein"
+ "PL_fd_master_lobby" "Grenzlandverteidigung: Meister – Lobby"
+ "PL_fd_normal" "Grenzlandverteidigung: Normal"
+ "PL_fd_normal_desc" "Empfohlen für erfahrene Spieler"
+ "PL_fd_normal_lobby" "Grenzlandverteidigung: Normal – Lobby"
+ "PL_ffa" "Frei für alle"
+ "PL_ffa_abbr" "FFA"
+ "PL_ffa_desc" "Jeder Pilot kämpft für sich selbst. Eliminiere alle Gegner.\n^FFC83200Spieler: 1 vs. 11\nZeitlimit: 10 Min.\nMax. Party-Größe: 1"
+ "PL_ffa_lobby" "Frei für alle-Lobby"
+ "PL_fra" "Free Agents"
+ "PL_fra_abbr" "FRA"
+ "PL_fra_desc" "Du kämpfst für dich selbst. Eliminiere Gegner, um zu siegen. Sammle 3 Batterien für einen Titanfall.\n^FFC83200Spieler: 1 vs. 11\nZeitlimit: 15 Min.\nMax. Party-Größe: 1"
+ "PL_fra_lobby" "Free Agents-Lobby"
+ "PL_groud_war_lobby" "8 vs. 8 Mixtape-Lobby"
+ "PL_ground_war" "8 vs. 8 Mixtape"
+ "PL_ground_war_abbr" "8 vs. 8"
+ "PL_ground_war_desc" "Enthält Gefecht und Verstärkter Hardpoint mit einer hohen Spielerzahl auf allen Karten.\n^FFC83200Spieler: 8 vs. 8"
+ "PL_hardpoint" "Verstärkter Hardpoint"
+ "PL_hardpoint_abbr" "VHP"
+ "PL_hardpoint_desc" "Erobere und halte einen Hardpoint, um Punkte zu erringen. Verstärkte Hardpoints bringen die doppelte Punktzahl.\n^FFC83200Spieler: 6 vs. 6\nZeitlimit: 10 Min.\nMax. Party-Größe: 6"
+ "PL_hardpoint_lobby" "Verstärkter Hardpoint-Lobby"
+ "PL_hunted" "Gejagt"
+ "PL_iron_last_titan_standing" "Eiserner LTS"
+ "PL_iron_last_titan_standing_abbr" "ELTS"
+ "PL_iron_last_titan_standing_desc" "Nur Titans. Rundenbasiertes Eliminierungsmatch. Sieger ist, wer zuerst 3 Runden gewinnt.\n^FFC83200Spieler: 5 vs. 5 *KEINE PILOTEN\nZeitlimit: 3 Min. pro Runde * Keine Respawns\nMax. Party-Größe: 5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "Last Titan Standing"
+ "PL_last_titan_standing_abbr" "LTS"
+ "PL_last_titan_standing_desc" "Alle Spieler beginnen dieses rundenbasierte Eliminierungsmatch in Titans. Sieger ist, wer zuerst 3 Runden für sich entscheidet.\n^FFC83200Spieler: 5 vs. 5 * Starte als Titan\nZeitlimit: 3 Min. pro Runde * Keine Respawns\nMax. Party-Größe: 5"
+ "PL_last_titan_standing_lobby" "LTS-Lobby"
+ "PL_limited_time_mode" "Befristeter Modus"
+ "PL_live_fire" "Live Fire"
+ "PL_live_fire_abbr" "LF"
+ "PL_live_fire_desc" "Rasanter Kampf in einer Live Fire-Arena. Gewinne die Runde, indem du alle gegnerischen Piloten neutralisierst oder am Ende der Runde die Flagge hältst.^FFC83200\nSpieler: 6 vs. 6 *Keine Titans\nZeitlimit: 60 Sek. *Keine Respawns\nMax. Party-Größe: 6"
+ "PL_live_fire_lobby" "Live Fire-Lobby"
+ "PL_load_a_map_on_the_command_line" "Lade eine Karte auf der Befehlszeile -devonly"
+ "PL_marked_for_death" "Todgeweiht"
+ "PL_marked_for_death_abbr" "TG"
+ "PL_marked_for_death_desc" "Eliminiere oder beschütze die markierten Ziele.\n^FFC83200Spieler: 6 vs. 6 \nZeitlimit: 12 Min.\nMax. Party-Größe: 6"
+ "PL_marked_for_death_lobby" "Todgeweiht-Lobby"
+ "PL_nitro_ffa" "FFA (Nitro)"
+ "PL_nitro_ffa_abbr" "FFA-N"
+ "PL_nitro_ffa_desc" "Rasantes Free for All auf ausgewählten Karten.\n^FFC83200Spieler: 6\n^F4D5A600Keine Titans\nKeine Boosts"
+ "PL_nitro_ffa_lobby" "FFA Nitro-Lobby"
+ "PL_nitro_mixtape" "Mixtape (Nitro)"
+ "PL_nitro_mixtape_abbr" "MXT-N"
+ "PL_nitro_mixtape_desc" "Rasantes CTF, TG und PvP auf ausgewählten Karten.\n^FFC83200Spieler: 5 vs. 5\nMax. Party-Größe: 5\n^F4D5A600Flagge wird sofort zurückgebracht"
+ "PL_nitro_mixtape_lobby" "Mixtape-Lobby"
+ "PL_pilot_hunter" "Gefecht"
+ "PL_pilot_hunter_abbr" "GEF"
+ "PL_pilot_hunter_desc" "Neutralisiere gegnerische Piloten und Titans. \n^FFC83200Spieler: 8 vs. 8\nZeitlimit: 10 Min.\nMax. Party-Größe: 8"
+ "PL_pilot_hunter_lobby" "Gefecht-Lobby"
+ "PL_pilot_skirmish" "Piloten vs. Piloten"
+ "PL_pilot_skirmish_abbr" "PvP"
+ "PL_pilot_skirmish_desc" "Neutralisiere gegnerische Piloten. Titanfalls nicht erlaubt.\n^FFC83200Spieler: 8 vs. 8 * Keine Titans\nZeitlimit: 10 Min.\nMax. Party-Größe: 8"
+ "PL_pilot_skirmish_lobby" "PvP-Lobby"
+ "PL_pl_rebuild_all_paths" "Neuaufbau aller Wege"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "Mit ai_ainRebuildOnMapStart 2 laufen lassen"
+ "PL_private_match" "Privatspiel-Beta"
+ "PL_private_match_desc" "Spiele ein benutzerdefiniertes Privatspiel auf einer Karte und einem Modus deiner Wahl.\n^FFC83200Wähle NETWORK EINLADEN oder FREUNDE EINLADEN, um zu spielen\n^FFC83200Spieler: 1-16\nKeine Fortschritte"
+ "PL_private_match_lobby" "Privatspiel-Beta-Lobby"
+ "PL_promo_coop" "4-Spieler-KOOP"
+ "PL_raid" "Ãœberfall"
+ "PL_rise" "Aufstieg 24/7"
+ "PL_rise_abbr" "AUF"
+ "PL_rise_desc" "Aufstieg rund um die Uhr. ^CCCCCC00Sucht nach Spielen in den Modi ^FFC83200CTF^CCCCCC00, ^FFC83200Verstärkter Hardpoint^CCCCCC00, ^FFC83200Piloten vs. Piloten^CCCCCC00, ^FFC83200Live Fire^CCCCCC00 und ^FFC83200Last Titan Standing^CCCCCC00."
+ "PL_rise_lobby" "Aufstieg 24/7 – Lobby"
+ "PL_rocket_arena" "Raketenarena"
+ "PL_rocket_arena_abbr" "LF"
+ "PL_rocket_arena_desc" "Klassische Live-Fire-Regeln mit modifizierten EPGs.^FFC83200\nSpieler: 6 vs. 6 *Keine Titans\nZeitlimit: 90 Sek. *Keine Respawns\nMax. Party-Größe: 6"
+ "PL_rocket_arena_lobby" "Raketenarena-Lobby"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "Einstellungen, wenn jemand eine Karte auf der Befehlszeile lädt. Nicht cmdlineMapLoad 1 bearbeiten - dadurch funktioniert es."
+ "PL_speedball" "Live Fire"
+ "PL_speedball_desc" "Kämpfe um die neutrale Flagge. Eliminiere das gegnerische Team oder halte am Ende der Spielzeit die Flagge, um die Runde zu gewinnen. Wer zuerst 5 Runden für sich entscheidet, gewinnt.\n^FFC83200Spieler: 6 vs. 6 *Keine Titans\nZeitlimit: 60 Sekunden pro Runde *Keine Respawns\nMax. Party-Größe: 6"
+ "PL_speedball_lobby" "Live Fire-Lobby"
+ "PL_tactikill" "Taktikill-Materialschlacht"
+ "PL_tactikill_abbr" "MAT"
+ "PL_tactikill_desc" "Klassische Materialschlacht-Regeln, allerdings werden alle Taktikfähigkeiten durch einen Kill zurückgesetzt.\n^FFC83200Spieler: 6 vs. 6 *KI\nZeitlimit: 10 Min.\nMax. Party-Größe: 6"
+ "PL_tactikill_lobby" "Taktikill-Materialschlacht-Lobby"
+ "PL_titan_brawl" "Titan Brawl"
+ "PL_titan_brawl_abbr" "TB"
+ "PL_titan_brawl_desc" "Eliminiere gegnerische Titans. Piloten nicht erlaubt.\n^FFC83200Spieler: 5 vs. 5\nZeitlimit: 10 Min.\nMax. Party-Größe: 5"
+ "PL_titan_brawl_hint" "Neutralisiere gegnerische Titans.\nKein Ausstieg."
+ "PL_titan_brawl_lobby" "Titan Brawl-Lobby"
+ "PL_titan_brawl_turbo" "Turbo-Titan-Brawl"
+ "PL_titan_brawl_turbo_abbr" "TTDM"
+ "PL_titan_brawl_turbo_desc" "Klassische Titan-Brawl-Regeln mit schnellerer Jetschub-und System-Regeneration.\n^FFC83200Spieler: 5 vs. 5\nZeitlimit: 10 Min.\nMax. Party-Größe: 5"
+ "PL_titan_brawl_turbo_hint" "Neutralisiere gegnerische Titans.\nKein Ausstieg"
+ "PL_titan_brawl_turbo_lobby" "Turbo-Titan-Brawl-Lobby"
+ "PL_turbo_last_titan_standing" "Turbo-LTS"
+ "PL_turbo_last_titan_standing_abbr" "LTS"
+ "PL_turbo_last_titan_standing_desc" "Klassische LTS-Regeln mit schnellerer Jetschub-und System-Regeneration.\n^FFC83200Spieler: 5 vs. 5 *Starte als Titan\nZeitlimit: 3 Min. pro Runde\nMax. Party-Größe: 5"
+ "PL_turbo_last_titan_standing_lobby" "Turbo-LTS-Lobby"
+ "PL_variety_pack" "Mixtape"
+ "PL_variety_pack_desc" "Variierende Spielerzahlen und verschiedene Karten in diesen Modi:\n^FFC83200*Kopfgeldjagd *Materialschlacht\n*Last Titan Standing *Verstärkter Hardpoint\n*Piloten vs. Piloten *Capture the Flag"
+ "PL_variety_pack_lobby" "Mixtape-Lobby"
+ "PL_wargames" "Kriegsspiele 24/7"
+ "PL_wargames_abbr" "KSP"
+ "PL_wargames_desc" "Alle Kriegsspiele, rund um die Uhr. ^CCCCCC00Sucht nach Spielen in den Modi ^FFC83200Materialschlacht^CCCCCC00, ^FFC83200CTF^CCCCCC00, ^FFC83200Piloten vs. Piloten^CCCCCC00, ^FFC83200Verstärkter Hardpoint^CCCCCC00 und ^FFC83200Last Titan Standing^CCCCCC00."
+ "PL_wargames_lobby" "Kriegsspiele 24/7 – Lobby"
+ "WATCH_TUTORIAL" "TUTORIAL ANSEHEN"
+ }
+ }
+ "lang"
+ {
+ "Language" "portuguese"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "Sua aventura pelas paisagens incríveis da Fronteira continua com o mais recente DLC para Titanfall 2: Cartões-postais da Fronteira. Trazendo locais novos e familiares junto de uma nova coleção de Pinturas de Guerra para Armas de Elite, a Fronteira nunca esteve tão apresentável.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_00_Q" "Trailer de Cartões-postais da Fronteira"
+ "COMMUNITYUPDATE_01_A" "Leia sobre todas as mudanças trazidas pelo patch Cartões-postais da Fronteira.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_01_Q" "Cartões-postais da Fronteira: Detalhes"
+ "COMMUNITYUPDATE_02_A" "Em breve nos dispositivos móveis, Titanfall: Assault é um empolgante jogo de estratégia em tempo real situado no universo de Titanfall criado em parceria com Particle City. Veja o jogo em ação aqui!\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_02_Q" "Trailer de lançamento de Titanfall: Assault"
+ "COMMUNITYUPDATE_03_A" "Iniquity nos guia pelo tutorial e oferece dicas básicas para Titanfall: Assault, a forma perfeita de conhecer o jogo.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_03_Q" "Titanfall: Assault - Vídeo \"Aprenda o básico\""
+ "COMMUNITYUPDATE_04_A" "Ouça algumas das principais pessoas por trás de Defesa da Fronteira falarem sobre a história e criação do modo e nos veja jogar algumas rodadas!\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_04_Q" "Respawn Joga: Defesa da Fronteira"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger criou uma montagem divertida mostrando vários movimentos impressionantes com a Lâmina Sônica.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_05_Q" "Criações da Comunidade: Direto ao Ponto"
+ "COMMUNITYUPDATE_06_A" "ConzeyG via Reddit demonstra eficiência brutal com a Northstar eliminando Pilotos no modo Marcado para Morrer.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_06_Q" "Criações da Comunidade: Caça com a Northstar"
+ "COMMUNITYUPDATE_07_A" "Já disponível! Defesa da Fronteira retorna junto de Elevação, além de novas pinturas de guerra à venda e um novo mapa de Queima-roupa. Veja tudo aqui.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_07_Q" "Trailer de Operação Escudo da Fronteira"
+ "COMMUNITYUPDATE_08_A" "Já disponível! O icônico mapa Jogos de Guerra retorna para Titanfall 2 mais bonito do que nunca. Confira também a nova execução e o novo mapa de Queima-roupa, Tráfego, em ação.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_08_Q" "Trailer de jogabilidade de \"Jogos de Guerra\""
+ "COMMUNITYUPDATE_09_A" "Este DLC adiciona o 7º Titã do multiplayer: Monarch, uma versão remasterizada do mapa Relíquia, uma nova execução, além de colocar à venda os Titãs Prime Ronin e Tone, mais camuflagens, fundos de distintivo e decorações. Assista à ação aqui.\n\n\nAperte `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_09_Q" "Trailer de jogabilidade: Reino do Monarca"
+ "COMMUNITYUPDATE_10_A" "Nem tudo é o que parece na Fronteira - prepare-se para entrar no mais novo pacote de DLC gratuito para Titanfall 2: Falha na Fronteira, trazendo o novo mapa \"Falha\". Inspirado no planeta natal do Capitão Lastimosa, Harmony, o ambiente é dominado por quedas verticais e longos caminhos tortuosos, em um cenário perfeito para encadear longas corridas por paredões e pairar por toda parte. E um novo mapa de Queima-roupa se junta ao conjunto: Convés, com seus espaços internos estreitos, pátios expostos e drones vigilantes circulando sobre sua cabeça. Se você se sentir sobrecarregado, os sempre prestativos Marvins agora estão na área como uma facção inteiramente nova, para dar aquela mãozinha robótica. Finalmente, está disponível a nova execução Lâmina Sônica, para ser usada sempre que for preciso marcar presença.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_10_Q" "Trailer de jogabilidade: Falha na Fronteira"
+ "COMMUNITYUPDATE_11_A" "É o retorno de um mapa icônico para multiplayer, favorito dos fãs do primeiro Titanfall: Colônia. Com a adição das novas táticas e Titãs de Titanfall 2, os Pilotos e Titãs terão que redobrar a atenção para sobreviver nesta vila idílica e compacta, cheia de becos estreitos, curvas cegas e telhados expostos. Disponível gratuitamente para todos os jogadores a partir do dia 30 de março. O pacote do DLC \"Colônia Renascida\" inclui o retorno de armas clássicas, como o fuzil de assalto modificado R-101, uma nova execução corpo a corpo e novas opções estéticas para você ter o melhor visual enquanto pinta a cidade de vermelho.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_11_Q" "Trailer de jogabilidade: Colônia Renascida"
+ "COMMUNITYUPDATE_12_A" "Apresentamos Queima-roupa, um modo de jogo ultrarrápido exclusivo para confrontos 6 contra 6 entre Pilotos, com foco na competição e no combate a curta distância. Contém dois novos mapas, criados especialmente para esse modo: Pilhas e Prado. São ambientes fechados e mortais, criados sob medida para a natureza ágil e intensa desse modo. \n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_12_Q" "Trailer: Bem-vindo a Queima-roupa"
+ "COMMUNITYUPDATE_13_A" "Experimente a versão renovada de um mapa favorito dos fãs no primeiro Titanfall: `1Angel City`0. Aguarde o primeiro DLC grátis de Titanfall 2, `1O Mais Procurado de Angel City`0, disponível dia 1º de dezembro para todos os jogadores. Esse conteúdo inclui novas opções estéticas para adicionar ainda mais bom gosto à Fronteira. \n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_13_Q" "Trailer: Bem-vindo a Angel City"
+ "COMMUNITYUPDATE_14_A" "Que bom que você voltou.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_14_Q" "Trailer: \"Bis\""
+ "COMMUNITYUPDATE_15_A" "Duas lendas, um legado.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_15_Q" "Trailer de jogabilidade: Tornem-se Um"
+ "COMMUNITYUPDATE_16_A" "Sem limites.\n\n\nPressione `2%[A_BUTTON|MOUSE1]%`0 para visualizar."
+ "COMMUNITYUPDATE_16_Q" "Trailer de jogabilidade (multiplayer): Pilotos"
+ "COMMUNITYUPDATE_DESC" "`3Atualizações da Comunidade de Titanfall`0\n\nInformações e links da web.\n\nPara mais:\n`2%$rui/bullet_point%`0Siga-nos no Twitter `1@Respawn`0\n`2%$rui/bullet_point%`0Curta nossa página em `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0Junte-se a nós em `1www.respawn.com`0"
+ "COMMUNITYUPDATE_NAME" "Comunidade"
+ "KNB_SUBJECT_00_DESC" "`3Quais as novidades de Titanfall?`0\n\nConfira aqui o que mudou em Titanfall 2!"
+ "KNB_SUBJECT_00_NAME" "Atualizações do jogo"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 Novas insígnias de Distintivos criadas pela comunidade\n\n`2%$rui/bullet_point%`0Agora é possível comprar todos os presentes do colaborador com créditos\n\n`2%$rui/bullet_point%`0Novo conteúdo da loja para comprar\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 28 de novembro - Época de Colheita"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Espaço Principal de Pistola\n\n`2%$rui/bullet_point%`0Insígnias de Dia das Bruxas\n\n`2%$rui/bullet_point%`0Alterações no Equilíbrio\n\n`2%$rui/bullet_point%`0Novo Conteúdo Para Compras na Loja\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 31 de outubro - Travessuras e Gostosuras"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0Defesa da Fronteira - Suporte para mais 3 mapas - Doca Seca, Angel City e Exoplaneta.\n\n`2%$rui/bullet_point%`0Novo mapa do Queima-roupa - UMA\n\n`2%$rui/bullet_point%`0Nova execução de Piloto - Parede Perfurada\n\n`2%$rui/bullet_point%`0Novo conteúdo à venda na Loja"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 29 de agosto - Cartões-postais da Fronteira"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Defesa da Fronteira - Um novo modo cooperativo no qual você une forças com até três jogadores para defender um objetivo vital contra as ondas cada vez mais intensas de combatentes da IA. Comunicação e adaptabilidade são as chaves para sobreviver.\n\n`2%$rui/bullet_point%`0Novo Mapa - Elevação\n\n`2%$rui/bullet_point%`0Novo mapa do Queima-roupa - Cidade\n\n`2%$rui/bullet_point%`0Novo conteúdo na Loja"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 25/07 - Operação Escudo da Fronteira"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0Novo mapa - Jogos de Guerra\n\n`2%$rui/bullet_point%`0Novo mapa de Queima-roupa - Tráfego\n\n`2%$rui/bullet_point%`0Nova execução de Piloto - Boxe Simulado\n\n`2%$rui/bullet_point%`03º espaço de arma\n\n`2%$rui/bullet_point%`0Configurações de partida privada"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 27/06 - Jogos de Guerra"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0Novo Titã - Monarch\n\n`2%$rui/bullet_point%`0Novo mapa - Relíquia\n\n`2%$rui/bullet_point%`0Nova execução de Piloto - Agora Você Me Vê\n\n`2%$rui/bullet_point%`0Novo conteúdo à venda na Loja"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 30/05 - Reino do Monarca"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0Novo mapa - Falha\n\n`2%$rui/bullet_point%`0Novo mapa de Queima-roupa - Convés\n\n`2%$rui/bullet_point%`0Nova facção - M.R.V.N.\n\n`2%$rui/bullet_point%`0Nova execução de Piloto - Direto ao Ponto\n\n`2%$rui/bullet_point%`0Geração máxima aumentada para 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 25/04 - Falha na Fronteira"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0Novo mapa - Colônia\n\n`2%$rui/bullet_point%`0Nova execução de Piloto - Esmaga-crânio\n\n`2%$rui/bullet_point%`0Nova arma - R-101\n\n`2%$rui/bullet_point%`0Novo conteúdo à venda na Loja"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 30/03 - Colônia Renascida"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0Queima-roupa - Um novo modo de eliminação entre Pilotos! As partidas são disputadas em rodadas de 6v6 sem reaparecimento. Você terá um minuto para eliminar a equipe inimiga e vencer a rodada. Também é possível vencer se sua equipe estiver em posse da bandeira neutra quando o tempo acabar. A primeira equipe a vencer 5 rodadas ganha a partida.\n\n`2%$rui/bullet_point%`0Novo mapa de Queima-roupa - Prado\n\n`2%$rui/bullet_point%`0Novo mapa de Queima-roupa - Pilhas\n\n`2%$rui/bullet_point%`0Nova execução de Piloto - Golpe Atrasado"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 23/02 - Queima-roupa"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0Novo mapa - Angel City\n\n`2%$rui/bullet_point%`0Nova arma - B3 Wingman de Elite\n\n`2%$rui/bullet_point%`0Nova execução de Piloto - De Dentro para Fora\n\n`2%$rui/bullet_point%`0Novos kits de Titã\n\n`2%$rui/bullet_point%`0Novo conteúdo à venda na Loja"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 30/11 - O Mais Procurado de Angel City"
+ "MP_ANGEL_CITY_FD_WAVE_1" "Ligados aos Céus"
+ "MP_ANGEL_CITY_FD_WAVE_2" "Sem Formas"
+ "MP_ANGEL_CITY_FD_WAVE_3" "Insurgência"
+ "MP_ANGEL_CITY_FD_WAVE_4" "Uma Turma Aguerrida"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Rolando os Dados"
+ "MP_DRYDOCK_FD_WAVE_1" "Nadando Contra a Corrente"
+ "MP_DRYDOCK_FD_WAVE_2" "Eles estão sem balas!"
+ "MP_DRYDOCK_FD_WAVE_3" "Ofuscado pela ciência"
+ "MP_DRYDOCK_FD_WAVE_4" "Sistema de Alta Pressão"
+ "MP_DRYDOCK_FD_WAVE_5" "Olho do Furacão"
+ "MP_THAW_FD_WAVE_1" "Consciência da Situação"
+ "MP_THAW_FD_WAVE_2" "A União Faz a Força"
+ "MP_THAW_FD_WAVE_3" "Capanga 21"
+ "MP_THAW_FD_WAVE_4" "Brincando com Fogo"
+ "MP_THAW_FD_WAVE_5" "Sirva Bem às Armas"
+ "NO_PRICE" "Oferta expirada"
+ "NO_PRICE_TWO_LINES" "Oferta\nexpirada"
+ "PL_aegis_last_titan_standing" "SdT Égide"
+ "PL_aegis_last_titan_standing_abbr" "SdTE"
+ "PL_aegis_last_titan_standing_desc" "Melhorias Égide ativadas. Partida de eliminação por rodada. O primeiro a ganhar 3 rodadas vence.\n^FFC83200Jogadores: 5v5 *Começa como Titã\nLimite de tempo: 3 minutos por rodada *Sem reaparecimento\nTamanho máximo do grupo: 5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "Disputa de Titãs Égide"
+ "PL_aegis_titan_brawl_abbr" "DdTE"
+ "PL_aegis_titan_brawl_desc" "Elimine Titãs inimigos. Melhorias Égide ativadas.\n^FFC83200Jogadores: 5v5\nLimite de tempo: 10 min.\nTamanho máximo do grupo: 5"
+ "PL_aegis_titan_brawl_hint" "Elimine Titãs inimigos.\nSem ejeção"
+ "PL_aegis_titan_brawl_lobby" "Sala de espera: Disputa de Titãs Égide"
+ "PL_aitdm" "Exaustão"
+ "PL_aitdm_abbr" "EXA"
+ "PL_aitdm_desc" "Elimine todos os inimigos.\n^FFC83200Jogadores: 6v6 *IA\nLimite de tempo: 10 min\nTamanho máximo do grupo: 6"
+ "PL_aitdm_lobby" "Sala de espera: Exaustão"
+ "PL_all_grapple" "Ataque com Gancho"
+ "PL_all_grapple_abbr" "EXA"
+ "PL_all_grapple_desc" "Regras de Exaustão clássicas, exceto todas as habilidades Táticas, são substituídas com Gancho.\n^FFC83200Jogadores: 6v6 *IA\nLimite de tempo: 10 min.\nTamanho máximo do grupo: 6"
+ "PL_all_grapple_lobby" "Sala de espera: Ataque com Gancho"
+ "PL_all_holopilot" "A Grande Trapaça"
+ "PL_all_holopilot_abbr" "QR"
+ "PL_all_holopilot_desc" "Regras de Queima-roupa clássicas, exceto todas as habilidades Táticas, são substituídas com Holopiloto.\n^FFC83200Jogadores: 6v6 *Sem Titãs\nLimite de tempo: 60 seg.\nTamanho máximo do grupo: 6"
+ "PL_all_holopilot_lobby" "Sala de espera: A Grande Trapaça"
+ "PL_all_phase" "O Outrolado"
+ "PL_all_phase_abbr" "EXA"
+ "PL_all_phase_desc" "Regras de Exaustão clássicas, exceto todas as habilidades Táticas, são substituídas com Cronossalto.\n^FFC83200Jogadores: 6v6 *IA\nLimite de tempo: 10 min.\nTamanho máximo do grupo: 6"
+ "PL_all_phase_lobby" "Sala de espera: O Outrolado"
+ "PL_all_spicy" "Exaustão Apimentada"
+ "PL_all_spicy_abbr" "EXA"
+ "PL_all_spicy_desc" "Regras de Exaustão clássicas, exceto todas as habilidades Táticas, são substituídas com Pulgões.\n^FFC83200Jogadores: 6v6 *IA\nLimite de tempo: 10 min.\nTamanho máximo do grupo: 6"
+ "PL_all_spicy_lobby" "Sala de espera: Exaustão Apimentada"
+ "PL_amped_tacticals" "Táticas Melhoradas"
+ "PL_amped_tacticals_abbr" "EXA"
+ "PL_amped_tacticals_desc" "Regras de Exaustão clássicas, exceto que todas as habilidades Táticas são mais potentes.\n^FFC83200Jogadores: 6v6 *IA\nLimite de tempo: 10 min.\nTamanho máximo do grupo: 6"
+ "PL_amped_tacticals_lobby" "Sala de espera: Táticas Melhoradas"
+ "PL_ANGEL_CITY" "Angel City Permanente"
+ "PL_angel_city_abbr" "AC"
+ "PL_angel_city_desc" "Somente Angel City, o tempo todo.\n^FFC83200*Recompensa *Exaustão\n*PCA"
+ "PL_angel_city_lobby" "Sala de espera: Angel City Permanente"
+ "PL_at_coop" "Fuga (Coop)"
+ "PL_at_coop_desc" "Você acaba de fugir da prisão só com uma pistola. Sobreviva até o transporte de evacuação chegar. Elimine inimigos para ganhar dinheiro e comprar armas. \n^FFC83200Jogadores: 6\nTamanho máximo do grupo: 6"
+ "PL_at_coop_lobby" "Sala de espera de Fuga"
+ "PL_attrition" "Recompensa"
+ "PL_attrition_abbr" "REC"
+ "PL_attrition_desc" "Elimine os inimigos para ganhar dinheiro. Deposite os bônus em locais específicos para receber ainda mais.\n^FFC83200Jogadores: 5v5 *IA\nLimite de tempo: 10 min\nTamanho máximo do grupo: 5"
+ "PL_attrition_lobby" "Sala de espera: Recompensa"
+ "PL_capture_the_flag" "Captura de Bandeira"
+ "PL_capture_the_flag_abbr" "CDB"
+ "PL_capture_the_flag_desc" "Roube a bandeira do inimigo e leve-a para a sua base. Não deixe os inimigos pegarem a sua bandeira!\n^FFC83200Jogadores: 5v5\nLimite de tempo: 12 min\nTamanho máximo do grupo: 5"
+ "PL_capture_the_flag_lobby" "Sala de espera: CdB"
+ "PL_coliseum" "Coliseu"
+ "PL_coliseum_desc" "Duelo com mobilidade extra numa jaula. Elimine seu oponente para vencer a rodada. Melhor de 5 ganha um Presente do Colaborador.^FFC83200\nJogadores: 1v1 *Sem Titãs\nLimite de tempo: 3 min. *Sem reaparecimento\n**^FFFFFF00REQUER BILHETE DO COLISEU ou PAGAMENTO DA TAXA DE ENTRADA"
+ "PL_coliseum_lobby" "Sala de espera: Coliseu"
+ "PL_colony" "Colônia Permanente"
+ "PL_colony_abbr" "COL"
+ "PL_colony_desc" "Somente Colônia, o tempo todo. ^CCCCCC00Isto vai buscar partidas de ^FFC83200Exaustão^CCCCCC00, ^FFC83200Pilotos vs Pilotos^CCCCCC00 e ^FFC83200Sobrevivência de Titã^CCCCCC00."
+ "PL_colony_lobby" "Sala de espera de Colônia Permanente"
+ "PL_ctf_lf" "CdB (Nitro)"
+ "PL_ctf_lf_abbr" "CdB-N"
+ "PL_ctf_lf_desc" "Captura de Bandeira frenética em mapas selecionados.\n^FFC83200Jogadores: 5v5\nTamanho máximo do grupo: 5\n^F4D5A600Retorno Imediato de Bandeira"
+ "PL_ctf_lf_lobby" "Sala de espera: CdB Nitro"
+ "PL_default_description" "Descrição padrão"
+ "PL_default_lobbytitle" "Título de sala padrão"
+ "PL_default_name" "Nome padrão"
+ "PL_don" "Dobro ou Nada"
+ "PL_fd" "Defesa da Fronteira"
+ "PL_fd_desc" "Defenda-se contra ondas da Frota Remanescente"
+ "PL_fd_easy" "Defesa da Fronteira: Fácil"
+ "PL_fd_easy_desc" "Destrua a oposição"
+ "PL_fd_easy_lobby" "Defesa da Fronteira: Sala de espera (Fácil)"
+ "PL_fd_hard" "Defesa da Fronteira: Difícil"
+ "PL_fd_hard_desc" "Requer habilidade"
+ "PL_fd_hard_lobby" "Defesa da Fronteira: Sala de espera (Difícil)"
+ "PL_fd_insane" "Defesa da Fronteira: Insana"
+ "PL_fd_insane_desc" "Você não vai sobreviver"
+ "PL_fd_insane_lobby" "Sala de espera: Defesa da Fronteira (Insana)"
+ "PL_fd_lobby" "Sala de espera Defesa da Fronteira"
+ "PL_fd_master" "Defesa da Fronteira: Mestre"
+ "PL_fd_master_desc" "Somente os melhores entre os melhores têm êxito"
+ "PL_fd_master_lobby" "Sala de espera: Defesa da Fronteira (Mestre)"
+ "PL_fd_normal" "Defesa da Fronteira: Normal"
+ "PL_fd_normal_desc" "Recomendado para jogadores experientes"
+ "PL_fd_normal_lobby" "Defesa da Fronteira: Sala de espera (Normal)"
+ "PL_ffa" "Cada Um por Si"
+ "PL_ffa_abbr" "CPS"
+ "PL_ffa_desc" "Salve-se quem puder, elimine todos os inimigos.\n^FFC83200Jogadores: 1v11\nLimite de tempo: 10 min\nTamanho máximo do grupo: 1"
+ "PL_ffa_lobby" "Sala de espera: CUPS"
+ "PL_fra" "Contrato Livre"
+ "PL_fra_abbr" "CL"
+ "PL_fra_desc" "Você está por conta própria. Elimine inimigos para vencer. Junte 3 baterias para chamar um Titã.\n^FFC83200Jogadores: 1v11\nLimite de tempo: 15 min.\nTamanho máximo do grupo: 1"
+ "PL_fra_lobby" "Sala de espera: CL"
+ "PL_groud_war_lobby" "Sala de espera: Variedade 8v8"
+ "PL_ground_war" "Variedade 8v8"
+ "PL_ground_war_abbr" "8v8"
+ "PL_ground_war_desc" "Inclui Duelos e Ponto de Controle Amplificado com mais jogadores em todos os mapas.\n^FFC83200Jogadores: 8v8"
+ "PL_hardpoint" "PCA"
+ "PL_hardpoint_abbr" "PCA"
+ "PL_hardpoint_desc" "Capture e mantenha um ponto de controle para acumular pontos. Os Pontos de Controle Amplificados dão pontos em dobro.\n^FFC83200Jogadores: 6v6\nLimite de tempo: 10 min\nTamanho máximo do grupo: 6"
+ "PL_hardpoint_lobby" "Sala de espera: PCA"
+ "PL_hunted" "Caçada"
+ "PL_iron_last_titan_standing" "SdT - Aço"
+ "PL_iron_last_titan_standing_abbr" "SdTA"
+ "PL_iron_last_titan_standing_desc" "Partida de rodadas eliminatórias, exclusiva para Titãs. Vence quem conquistar 3 rodadas primeiro.\n^FFC83200Jogadores: 5v5 *SEM PILOTOS\nLimite de tempo: 3 minutos por rodada *Sem reaparecimento\nTamanho máximo do grupo: 5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "Sobrevivência de Titã"
+ "PL_last_titan_standing_abbr" "SdT"
+ "PL_last_titan_standing_desc" "Todos os jogadores já começam com Titãs nessa partida de eliminação por rodada. O primeiro a ganhar 3 rodadas vence.\n^FFC83200Jogadores: 5v5 *Começa com Titã\nLimite de tempo: 3 min por rodada *Sem reaparecimento\nTamanho máximo do grupo: 5"
+ "PL_last_titan_standing_lobby" "Sala de espera: SdT"
+ "PL_limited_time_mode" "Por tempo limitado"
+ "PL_live_fire" "Queima-roupa"
+ "PL_live_fire_abbr" "QR"
+ "PL_live_fire_desc" "Combate frenético em uma arena de Queima-roupa. Vença a rodada eliminando todos os Pilotos inimigos ou controlando a bandeira quando o tempo acabar.^FFC83200\nJogadores: 6v6 *Sem Titãs\nLimite de tempo: 60 seg. *Sem reaparecimentos\nTamanho máximo do grupo: 6"
+ "PL_live_fire_lobby" "Sala de Espera: Queima-roupa"
+ "PL_load_a_map_on_the_command_line" "Carregue um mapa na linha de comando -apenas desenvolvedores"
+ "PL_marked_for_death" "Marcado para Morrer"
+ "PL_marked_for_death_abbr" "MPM"
+ "PL_marked_for_death_desc" "Elimine ou proteja os alvos marcados.\n^FFC83200Jogadores: 6v6 \nLimite de tempo: 12m\nTamanho máximo do grupo: 6"
+ "PL_marked_for_death_lobby" "Sala de espera: Marcado para Morrer"
+ "PL_nitro_ffa" "CPS (Nitro)"
+ "PL_nitro_ffa_abbr" "CPS-N"
+ "PL_nitro_ffa_desc" "Cada Um Por Si frenético nos mapas selecionados.\n^FFC83200Jogadores: 6\n^F4D5A600Sem Titãs\nSem reforços"
+ "PL_nitro_ffa_lobby" "Sala de espera: CPS Nitro"
+ "PL_nitro_mixtape" "Variedade (Nitro)"
+ "PL_nitro_mixtape_abbr" "VAR-N"
+ "PL_nitro_mixtape_desc" "CdB, MPM e JxJ frenéticos em mapas selecionados.\n^FFC83200Jogadores: 5v5\nTamanho máximo do grupo: 5\n^F4D5A600Retorno Imediato de Bandeira"
+ "PL_nitro_mixtape_lobby" "Sala de espera: Variedade"
+ "PL_pilot_hunter" "Duelos"
+ "PL_pilot_hunter_abbr" "DUE"
+ "PL_pilot_hunter_desc" "Elimine Pilotos e Titãs inimigos. \n^FFC83200Jogadores: 8v8\nLimite de tempo: 10 min\nTamanho máximo do grupo: 8"
+ "PL_pilot_hunter_lobby" "Sala de espera: Duelos"
+ "PL_pilot_skirmish" "Pilotos vs. Pilotos"
+ "PL_pilot_skirmish_abbr" "PvP"
+ "PL_pilot_skirmish_desc" "Elimine os Pilotos inimigos. Não há lançamentos de Titãs.\n^FFC83200Jogadores: 8v8 *Sem Titãs\nLimite de tempo: 10 min\nTamanho máximo do grupo: 8"
+ "PL_pilot_skirmish_lobby" "Sala de espera: PvP"
+ "PL_pl_rebuild_all_paths" "Reconstruir todos os caminhos"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "Executar com ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "Partida Privada (Beta)"
+ "PL_private_match_desc" "Jogue uma partida privada personalizada em um mapa ou modo de sua escolha.\n^FFC83200CONVIDE A REDE ou CONVIDE OS AMIGOS para jogar.\n^FFC83200Jogadores: 1 a 16\nSem progresso"
+ "PL_private_match_lobby" "Sala de Espera Privada (Beta)"
+ "PL_promo_coop" "Cooperação para 4 jogadores"
+ "PL_raid" "Incursão"
+ "PL_rise" "Elevação Permanente"
+ "PL_rise_abbr" "ELE"
+ "PL_rise_desc" "Somente Elevação, o tempo todo. ^CCCCCC00Isto vai buscar partidas de ^FFC83200CdB^CCCCCC00, ^FFC83200Ponto de Controle Amplificado^CCCCCC00, ^FFC83200Pilotos vs Pilotos^CCCCCC00, ^FFC83200Queima-roupa^CCCCCC00 e ^FFC83200Sobrevivência de Titã^CCCCCC00."
+ "PL_rise_lobby" "Sala de espera: Elevação Permanente"
+ "PL_rocket_arena" "Arena de Foguetes"
+ "PL_rocket_arena_abbr" "QR"
+ "PL_rocket_arena_desc" "Regras de Queima-roupa clássicas, com EPGs modificadas.^FFC83200Jogadores: 6v6 *Sem Titãs\nLimite de tempo: 90 seg.\nTamanho máximo do grupo: 6"
+ "PL_rocket_arena_lobby" "Sala de espera: Arena de Foguetes"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "Configurações para quando alguém carrega um mapa na linha de comando. Não edite cmdlineMapLoad 1 abaixo - isso faz isso funcionar."
+ "PL_speedball" "Queima-roupa"
+ "PL_speedball_desc" "Lute pela posse de uma bandeira neutra. Elimine a equipe inimiga ou esteja com a bandeira quando o tempo acabar para vencer a rodada. Vence a equipe que ganhar 5 rodadas primeiro.\n^FFC83200Jogadores: 6v6 *Sem Titãs\nLimite de tempo: 60 segundos por rodada *Sem reaparecimento\nTamanho máximo do grupo: 6"
+ "PL_speedball_lobby" "Sala de Espera: Queima-roupa"
+ "PL_tactikill" "Exaustão em Eliminação Tática"
+ "PL_tactikill_abbr" "EXA"
+ "PL_tactikill_desc" "Regras de Exaustão clássicas, exceto que todas as habilidades Táticas são totalmente reiniciadas com uma eliminação.\n^FFC83200Jogadores: 6v6 *IA\nLimite de tempo: 10 min.\nTamanho máximo do grupo: 6"
+ "PL_tactikill_lobby" "Sala de espera: Exaustão em Eliminação Tática"
+ "PL_titan_brawl" "Disputa de Titãs"
+ "PL_titan_brawl_abbr" "DdT"
+ "PL_titan_brawl_desc" "Elimine Titãs inimigos. Proibido Pilotos.\n^FFC83200Jogadores: 5v5\nLimite de tempo: 10 min.\nTamanho máximo do grupo: 5"
+ "PL_titan_brawl_hint" "Elimine Titãs inimigos.\nSem ejeção"
+ "PL_titan_brawl_lobby" "Sala de espera: Disputa de Titãs"
+ "PL_titan_brawl_turbo" "Disputa de Titãs Turbo"
+ "PL_titan_brawl_turbo_abbr" "DdT"
+ "PL_titan_brawl_turbo_desc" "Regras de Disputa de Titãs clássicas com regeneração de Arrancada e geração de Núcleo mais rápidas.\n^FFC83200Jogadores: 5v5\nLimite de tempo: 10 min.\nTamanho máximo do grupo: 5"
+ "PL_titan_brawl_turbo_hint" "Elimine Titãs inimigos.\nSem ejeção"
+ "PL_titan_brawl_turbo_lobby" "Sala de espera: Disputa de Titãs Turbo"
+ "PL_turbo_last_titan_standing" "SdT Turbo"
+ "PL_turbo_last_titan_standing_abbr" "SdT"
+ "PL_turbo_last_titan_standing_desc" "Regras de SdT clássicas com regeneração de Arrancada e geração de Núcleo mais rápidas.\n^FFC83200Jogadores: 5v5 *Inicia como Titã\nLimite de tempo: 3 min. por rodada *Sem reaparecimentos\nTamanho máximo do grupo: 5"
+ "PL_turbo_last_titan_standing_lobby" "Sala de espera: SdT Turbo"
+ "PL_variety_pack" "Variedade"
+ "PL_variety_pack_desc" "Número variável de jogadores e inclui uma variedade de mapas nos seguintes modos:\n^FFC83200*Recompensa *Exaustão\n*Sobrevivência de Titã *Ponto de Controle Amplificado\n*Pilotos vs. Pilotos *Captura de Bandeira"
+ "PL_variety_pack_lobby" "Sala de espera: Variedade"
+ "PL_wargames" "Jogos de Guerra Permanente"
+ "PL_wargames_abbr" "JDG"
+ "PL_wargames_desc" "Somente Jogos de Guerra, o tempo todo. ^CCCCCC00Isto vai buscar partidas de ^FFC83200Exaustão^CCCCCC00, ^FFC83200Pilotos vs Pilotos^CCCCCC00, ^FFC83200Ponto de Controle Amplificado^CCCCCC00 e ^FFC83200Sobrevivência de Titã^CCCCCC00."
+ "PL_wargames_lobby" "Sala de espera: Jogos de Guerra Permanente"
+ "WATCH_TUTORIAL" "ASSISTA AO TUTORIAL"
+ }
+ }
+ "lang"
+ {
+ "Language" "japanese"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "フロンティアã®çµ¶æ™¯ã‚’巡る冒険ã¯ã€ã‚¿ã‚¤ã‚¿ãƒ³ãƒ•ã‚©ãƒ¼ãƒ« 2ã®æœ€æ–°DLCã€ãƒ•ãƒ­ãƒ³ãƒ†ã‚£ã‚¢ã‹ã‚‰ã®ãƒã‚¹ãƒˆã‚«ãƒ¼ãƒ‰ã®ç™»å ´ã§ã•ã‚‰ã«ç¶šãã¾ã™ã€‚新スãƒãƒƒãƒˆã‚„ãŠé¦´æŸ“ã¿ã®ã‚¹ãƒãƒƒãƒˆã«åŠ ãˆã€å¤šå½©ãªæ–°ã—ã„武器用エリートウォーペイントãŒåŠ ã‚ã£ã¦ã€ãƒ•ãƒ­ãƒ³ãƒ†ã‚£ã‚¢ã®å½©ã‚ŠãŒã‹ã¤ã¦ãªã豊ã‹ã«ãªã‚Šã¾ã—ãŸã€‚\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¦–è´ã€‚"
+ "COMMUNITYUPDATE_00_Q" "「フロンティアã‹ã‚‰ã®ãƒã‚¹ãƒˆã‚«ãƒ¼ãƒ‰ã€ãƒˆãƒ¬ãƒ¼ãƒ©ãƒ¼"
+ "COMMUNITYUPDATE_01_A" "フロンティアã‹ã‚‰ã®ãƒã‚¹ãƒˆã‚«ãƒ¼ãƒ‰ã®ãƒ‘ッãƒã§å¤‰æ›´ã•ã‚ŒãŸç‚¹ã‚’ã™ã¹ã¦ã”紹介ã—ã¾ã™ã€‚\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¡¨ç¤ºã€‚"
+ "COMMUNITYUPDATE_01_Q" "フロンティアã‹ã‚‰ã®ãƒã‚¹ãƒˆã‚«ãƒ¼ãƒ‰:パッãƒãƒŽãƒ¼ãƒˆ"
+ "COMMUNITYUPDATE_02_A" "「タイタンフォール アサルトã€ãŒãƒ¢ãƒã‚¤ãƒ«ã«è¿‘日登場。Particle Cityã¨ã®ææºã§ç”Ÿã¾ã‚ŒãŸã€ã‚¿ã‚¤ã‚¿ãƒ³ãƒ•ã‚©ãƒ¼ãƒ«ã®ä¸–界を舞å°ã«ã—ãŸç™½ç†±ã®ãƒªã‚¢ãƒ«ã‚¿ã‚¤ãƒ ãƒ»ã‚¹ãƒˆãƒ©ãƒ†ã‚¸ãƒ¼ã‚²ãƒ¼ãƒ ã§ã™ã€‚ゲーム動画ã¯ã“ã¡ã‚‰ï¼\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¦–è´ã€‚"
+ "COMMUNITYUPDATE_02_Q" "タイタンフォール アサルト「ローンãƒã€ãƒˆãƒ¬ãƒ¼ãƒ©ãƒ¼"
+ "COMMUNITYUPDATE_03_A" "Iniquityæ°ãŒã€Œã‚¿ã‚¤ã‚¿ãƒ³ãƒ•ã‚©ãƒ¼ãƒ« アサルトã€ã®ãƒãƒ¥ãƒ¼ãƒˆãƒªã‚¢ãƒ«ã‚’プレイã—ãªãŒã‚‰ã€åŸºæœ¬çš„ãªã‚¢ãƒ‰ãƒã‚¤ã‚¹ã‚’紹介ã—ã¾ã™ã€‚ゲームã®åˆæ­©ã‚’覚ãˆã‚‹ã®ã«æœ€é©ã§ã™ã€‚\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¡¨ç¤ºã€‚"
+ "COMMUNITYUPDATE_03_Q" "タイタンフォール アサルト:「入門ã€å‹•ç”»"
+ "COMMUNITYUPDATE_04_A" "フロンティアディフェンスã®ä¸»ãªè£½ä½œã‚¹ã‚¿ãƒƒãƒ•ãŒã€ãƒ¢ãƒ¼ãƒ‰ã®æ­´å²ã¨åˆ¶ä½œã«ã¤ã„ã¦èªžã‚Šã€ãƒ©ã‚¦ãƒ³ãƒ‰ã‚’プレイã—ã¾ã™ï¼\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¦–è´"
+ "COMMUNITYUPDATE_04_Q" "スタッフプレイ:フロンティアディフェンス"
+ "COMMUNITYUPDATE_05_A" "Kevin Youngeræ°ã‹ã‚‰ã€è¯éº—ã«ãƒ‘ルスブレードをæ“る様å­ã‚’大é‡ã«åŽã‚ãŸæ¥½ã—ã„ダイジェスト動画ãŒå±Šãã¾ã—ãŸã€‚\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¦–è´"
+ "COMMUNITYUPDATE_05_Q" "コミュニティー作å“:敵ã¯ãã“ã "
+ "COMMUNITYUPDATE_06_A" "redditã®ConzeyGæ°ãŒã€ãƒžãƒ¼ã‚¯ãƒ»ãƒ•ã‚©ãƒ¼ãƒ»ãƒ‡ã‚¹ã§ãƒŽãƒ¼ã‚¹ã‚¹ã‚¿ãƒ¼ãŒæ®‹é…·ãªã¾ã§ã®åŠ¹çŽ‡ã§ãƒ‘イロットã®æ¯ã®æ ¹ã‚’æ­¢ã‚る様å­ã‚’披露ã—ã¦ãã‚Œã¾ã—ãŸã€‚\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¡¨ç¤ºã€‚"
+ "COMMUNITYUPDATE_06_Q" "コミュニティー作å“:ノーススターã®ãƒãƒ³ãƒˆ"
+ "COMMUNITYUPDATE_07_A" "好評é…信中ï¼ãƒ•ãƒ­ãƒ³ãƒ†ã‚£ã‚¢ãƒ‡ã‚£ãƒ•ã‚§ãƒ³ã‚¹ã¨ãƒžãƒƒãƒ—「ライズã€ãŒå¾©æ´»ã€‚ã•ã‚‰ã«æ–°ã‚¦ã‚©ãƒ¼ãƒšã‚¤ãƒ³ãƒˆã®è²©å£²ãŒå§‹ã¾ã‚Šã€ãƒ©ã‚¤ãƒ–ファイアã«æ–°ãƒžãƒƒãƒ—ãŒç™»å ´ã€‚実際ã®ãƒ—レイã®æ§˜å­ã‚’ã”覧ãã ã•ã„。\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¦–è´ã€‚"
+ "COMMUNITYUPDATE_07_Q" "「フロンティアシールド作戦ã€GPトレーラー"
+ "COMMUNITYUPDATE_08_A" "é…信開始ï¼ã‚·ãƒªãƒ¼ã‚ºã‚’代表ã™ã‚‹ãƒžãƒƒãƒ—「ウォーゲームズã€ãŒç¾Žã—ã•ã‚’増ã—ã¦ã‚¿ã‚¤ã‚¿ãƒ³ãƒ•ã‚©ãƒ¼ãƒ« 2ã«æˆ»ã£ã¦ãã¾ã—ãŸã€‚ã¾ãŸã€æ–°å‡¦åˆ‘攻撃ã¨ãƒ©ã‚¤ãƒ–ファイアã®æ–°ãƒžãƒƒãƒ—「交通ã€ã‚‚ã”紹介ã—ã¾ã™ã€‚\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¦–è´ã€‚"
+ "COMMUNITYUPDATE_08_Q" "「ウォーゲームズã€ã‚²ãƒ¼ãƒ ãƒ—レイトレーラー"
+ "COMMUNITYUPDATE_09_A" "今回ã®DLCã§ã¯ã€7体目ã®ãƒžãƒ«ãƒãƒ—レイヤー用新タイタン「モナークã€ã¨ãƒªãƒžã‚¹ã‚¿ãƒ¼ç‰ˆãƒžãƒƒãƒ—「レリックã€ã€æ–°å‡¦åˆ‘攻撃ã®é…ä¿¡ã«åŠ ãˆã€è²©å£²ã‚¢ã‚¤ãƒ†ãƒ ã«ãƒ­ãƒ¼ãƒ‹ãƒ³ãƒ»ãƒˆãƒ¼ãƒ³ç”¨ã®ãƒ—ライムタイタンã¨æ–°ã—ã„迷彩ã€ãƒãƒŠãƒ¼ã€ãƒŽãƒ¼ã‚ºã‚¢ãƒ¼ãƒˆãŒç™»å ´ã—ã¾ã™ã€‚å‹•ç”»ã¯ã“ã¡ã‚‰ã€‚\n\n\n`2%[A_BUTTON|MOUSE1]% `0ã§è¦–è´ã€‚"
+ "COMMUNITYUPDATE_09_Q" "「モナークレインã€ã‚²ãƒ¼ãƒ ãƒ—レイトレーラー"
+ "COMMUNITYUPDATE_10_A" "フロンティアã§ã¯ç›®ã«æ˜ ã‚‹ã‚‚ã®ãŒã™ã¹ã¦ã§ã¯ãªã„。新マップ「グリッãƒã€ã‚’åŽéŒ²ã—ãŸã‚¿ã‚¤ã‚¿ãƒ³ãƒ•ã‚©ãƒ¼ãƒ« 2用最新無料DLCパックã€ã€Œãƒ•ãƒ­ãƒ³ãƒ†ã‚£ã‚¢ã®ã‚°ãƒªãƒƒãƒã€ã®ç™»å ´ã«å‚™ãˆã‚ˆã†ã€‚ラスティモーサ大尉ã®æ•…郷ã€æƒ‘星ãƒãƒ¼ãƒ¢ãƒ‹ãƒ¼ã‚’モãƒãƒ¼ãƒ•ã«ä½œæˆã•ã‚ŒãŸã€åž‚ç›´ã«ãã³ãˆã‚‹å£ã¨é•·ã蛇行ã—ãŸé“ãŒå¤§åŠã‚’å ã‚るマップã§ã€é•·è·é›¢ã®ã‚¦ã‚©ãƒ¼ãƒ«ãƒ©ãƒ³ã‚’繋ã’ã€ãƒžãƒƒãƒ—中を滑らã‹ã«ç–¾èµ°ã™ã‚‹ã®ã«ã´ã£ãŸã‚Šã®æ§‹æˆã¨ãªã£ã¦ã„ã¾ã™ã€‚ライブファイア用新マップã€ãƒ‡ãƒƒã‚­ã‚‚登場。タイトãªå†…部空間ã¨é–‹ã‘ãŸä¸­åº­ã€ä¸Šç©ºã‚’旋回ã™ã‚‹ç›£è¦–ドローンãŒç‰¹å¾´ã§ã™ã€‚ピンãƒãªæ™‚ã¯ã€ãƒžãƒ¼ãƒ“ンãŸã¡ãŒåŠ›ã‚’貸ã—ã¦ãã‚Œã¾ã™ã€‚ãã—ã¦ã€ãƒ‘ルスブレードを使ã£ãŸã€æ•µã‚’刃ã§è²«ã新処刑攻撃ãŒã‚¢ãƒ³ãƒ­ãƒƒã‚¯å¯èƒ½ã¨ãªã‚Šã¾ã—ãŸã€‚\n\n\n`2%[A_BUTTON|MOUSE1]%`0 ã§å†ç”Ÿã€‚"
+ "COMMUNITYUPDATE_10_Q" "「フロンティアã®ã‚°ãƒªãƒƒãƒã€ トレーラー"
+ "COMMUNITYUPDATE_11_A" "ファンã®é–“ã§äººæ°—ã®é«˜ã„åˆä»£ã®ä¼èª¬ã®ãƒžãƒ«ãƒãƒ—レイヤーマップ「æ¤æ°‘地ã€ã«æˆ»ã‚Šã¾ã—ょã†ã€‚「タイタンフォール 2ã€ã‹ã‚‰ç™»å ´ã—ãŸæ–°ã—ã„戦術アビリティã¨ã‚¿ã‚¤ã‚¿ãƒ³ã®çŸ¥è­˜ã‚’æ´»ã‹ã•ãªã‘ã‚Œã°ã€è£é€šã‚Šã‚„見通ã—ã®åˆ©ã‹ãªã„曲ãŒã‚Šè§’ã€ã‚€ã出ã—ã®å±‹ä¸Šã‚¨ãƒªã‚¢ã‹ã‚‰æˆã‚‹å¯†é›†åº¦ã®é«˜ã„牧歌的ãªæ‘è½ã§ã€ãƒ‘イロットã¨ã‚¿ã‚¤ã‚¿ãƒ³ãŒç”Ÿã延ã³ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。マップã¯3月30日より全プレイヤーã«ç„¡æ–™é…信。DLCパック「コロニーリボーンã€ã«ã¯ä»–ã«ã‚‚ã€æ–°ãƒ‡ã‚¶ã‚¤ãƒ³ã®R-101アサルトライフルã¨ã„ã£ãŸå‰ä½œã§ãŠé¦´æŸ“ã¿ã®æ­¦å™¨ã«åŠ ãˆã€ã‚°ãƒ©ãƒƒãƒ—ルを使ã£ãŸæ–°ã—ã„処刑攻撃ã€è¡—を最高ã«æ ¼å¥½è‰¯ãè¡€ã«æŸ“ã‚る有料ã®æ–°è£…飾アイテムをåŽéŒ²ã—ã¦ã„ã¾ã™ã€‚\n\n\n`2%[A_BUTTON|MOUSE1]%`0 ã§å†ç”Ÿã€‚"
+ "COMMUNITYUPDATE_11_Q" "「コロニーリボーンã€ã‚²ãƒ¼ãƒ ãƒ—レイトレーラー"
+ "COMMUNITYUPDATE_12_A" "`1ライブファイア`0登場:パイロット専用ã€è¶…高速ãª6対6ã®æŽ¥è¿‘戦ã§ã™ã€‚ライブファイア専用ã®æœ€æ–°ãƒžãƒƒãƒ—2種ã€`1スタック`0ã¨`1è‰åŽŸ`0ã‚’åŽéŒ²ã—ã¦ã„ã¾ã™ã€‚ã“れら2種類ã®ç„¡æ–™ãƒžãƒƒãƒ—ã¯ã€é«˜é€Ÿã®æ¿€æˆ¦ãŒå±•é–‹ã™ã‚‹ãƒ¢ãƒ¼ãƒ‰ç‰¹æ€§ã«åˆã‚ã›ã¦åˆ¶ä½œã•ã‚ŒãŸã€ã‚¿ã‚¤ãƒˆã§å¯†é–‰ã•ã‚ŒãŸæ­»ã®ãƒªãƒ³ã‚°ã§ã™ã€‚\n\n\n`2%[A_BUTTON|MOUSE1]%`0ã§å†ç”Ÿã€‚"
+ "COMMUNITYUPDATE_12_Q" "「ライブファイアã€ã‚²ãƒ¼ãƒ ãƒ—レイトレーラー"
+ "COMMUNITYUPDATE_13_A" "元祖タイタンフォールã®äººæ°—マップã€`1エンジェルシティ`0ã®ãƒªãƒžã‚¹ã‚¿ãƒ¼ç‰ˆã‚’体験ã›ã‚ˆï¼2016å¹´12月1日より全プレイヤーã¸é…ä¿¡ã•ã‚Œã‚‹ç„¡æ–™DLC第1å¼¾ã€`1エンジェルシティ・モスト・ウォンテッド`0ã«å‘ã‘ã¦ã‚¹ã‚¿ãƒ³ãƒã‚¤ã—ã¾ã—ょã†ã€‚フロンティアを彩る新ã—ã„装飾オプションもåŽéŒ²ã—ã¦ã„ã¾ã™ã€‚\n\n\n`2%[A_BUTTON|MOUSE1]%`0ã§å†ç”Ÿã€‚"
+ "COMMUNITYUPDATE_13_Q" "「エンジェルシティã€ã‚²ãƒ¼ãƒ ãƒ—レイトレーラー"
+ "COMMUNITYUPDATE_14_A" "ãŠå¸°ã‚Šãªã•ã„。\n\n\n`2%[A_BUTTON|MOUSE1]%`0ã§å†ç”Ÿã€‚"
+ "COMMUNITYUPDATE_14_Q" "絶賛「アンコールã€ãƒˆãƒ¬ãƒ¼ãƒ©ãƒ¼"
+ "COMMUNITYUPDATE_15_A" "2人ã®ä¼èª¬ã€1ã¤ã®ç‰©èªžã€‚\n\n\n`2%[A_BUTTON|MOUSE1]%`0ã§å†ç”Ÿã€‚"
+ "COMMUNITYUPDATE_15_Q" "「Become Oneã€ã‚²ãƒ¼ãƒ ãƒ—レイトレーラー"
+ "COMMUNITYUPDATE_16_A" "ç„¡é™ã®å¯èƒ½æ€§ã€‚\n\n\n`2%[A_BUTTON|MOUSE1]%`0ã§å†ç”Ÿã€‚"
+ "COMMUNITYUPDATE_16_Q" "「パイロットã€ã‚²ãƒ¼ãƒ ãƒ—レイトレーラー"
+ "COMMUNITYUPDATE_DESC" "`3タイタンフォールコミュニティã®æœ€æ–°æƒ…å ±`0\n\nオンラインã‹ã‚‰ã€æƒ…報をãŸã£ã·ã‚Šã¨ãŠå±Šã‘ã—ã¾ã™ã€‚\n\n詳細ã¯ã€æ¬¡ã®æ‰‹é †ã§ã”覧ãã ã•ã„。\n`2%$rui/bullet_point%`0Twitterã§`1@Respawn`0をフォロー\n`2%$rui/bullet_point%`0`1facebook.com/RespawnEntertainment`0ã§ã€Œã„ã„ã­ï¼ã€\n`2%$rui/bullet_point%`0 `1www.respawn.com`0ã‚’ãƒã‚§ãƒƒã‚¯"
+ "COMMUNITYUPDATE_NAME" "コミュニティ"
+ "KNB_SUBJECT_00_DESC" "`3タイタンフォール更新履歴`0\n\nã“ã¡ã‚‰ã‚’ãƒã‚§ãƒƒã‚¯ã—ã¦ã€ã‚¿ã‚¤ã‚¿ãƒ³ãƒ•ã‚©ãƒ¼ãƒ« 2ã®å¤‰æ›´ç‚¹ã‚’確èªã—ã¾ã—ょã†ï¼"
+ "KNB_SUBJECT_00_NAME" "アップデート一覧"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 コミュニティー発ã®æ–°ã‚³ãƒ¼ãƒ«ã‚µã‚¤ãƒ³ã¨ãƒãƒŠãƒ¼ãŒç™»å ´\n\n`2%$rui/bullet_point%`0支æŒè€…ã®ã‚®ãƒ•ãƒˆãŒã™ã¹ã¦ã‚¯ãƒ¬ã‚¸ãƒƒãƒˆã§è³¼å…¥å¯èƒ½ã«\n\n`2%$rui/bullet_point%`0ストアã«è²©å£²ã‚¢ã‚¤ãƒ†ãƒ ã‚’æ–°å…¥è·\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 11月28æ—¥ - ãƒãƒ¼ãƒ™ã‚¹ãƒˆã‚¿ã‚¤ãƒ "
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0メインスロットã®ãƒ”ストル装備\n\n`2%$rui/bullet_point%`0ãƒãƒ­ã‚¦ã‚£ãƒ³ä»•æ§˜ãƒãƒŠãƒ¼\n\n`2%$rui/bullet_point%`0å„種ãƒãƒ©ãƒ³ã‚¹èª¿æ•´\n\n`2%$rui/bullet_point%`0ストアã«è²©å£²ã‚¢ã‚¤ãƒ†ãƒ ã‚’æ–°å…¥è·\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 10月31日 - トリック&トリート"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0フロンティアディフェンス - 対応マップã«ãƒ‰ãƒ©ã‚¤ãƒ‰ãƒƒã‚¯ã€ã‚¨ãƒ³ã‚¸ã‚§ãƒ«ã‚·ãƒ†ã‚£ã€å¤–惑星を追加。\n\n`2%$rui/bullet_point%`0ライブファイアã®æ–°ãƒžãƒƒãƒ— - UMA\n\n`2%$rui/bullet_point%`0パイロットã®å‡¦åˆ‘攻撃 - ホール・イン・ザ・ウォール\n\n`2%$rui/bullet_point%`0ストアã«æ–°ç€å•†å“ãŒç™»å ´"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 8月29æ—¥ - フロンティアã‹ã‚‰ã®ãƒã‚¹ãƒˆã‚«ãƒ¼ãƒ‰"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0フロンティアディフェンス - 自分をå«ã‚最大4人ã®ãƒ—レイヤーã§ãƒ—レイã§ãã‚‹å”力プレイモード。ゴールã¯é‡è¦ãªé˜²è¡›å¯¾è±¡ã‚’ã€æ¬¡ç¬¬ã«æ¿€ã—ã•ã‚’増ã™AIユニットã®ã‚¦ã‚§ãƒ¼ãƒ–ã‹ã‚‰å®ˆã‚Šé€šã™ã“ã¨ã€‚緊密ãªé€£æºã¨è‡¨æ©Ÿå¿œå¤‰ã®å¯¾å¿œãŒå‹åˆ©ã®ã‚«ã‚®ã«ãªã‚‹ã€‚\n\n`2%$rui/bullet_point%`0新マップ - ライズ\n\n`2%$rui/bullet_point%`0新ライブファイアマップ - タウンシップ\n\n`2%$rui/bullet_point%`0ストアã®æ–°è²©å£²ã‚¢ã‚¤ãƒ†ãƒ "
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 7月25日 - フロンティアシールド作戦"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0新マップ - ウォーゲーム\n\n`2%$rui/bullet_point%`0ライブファイア用ã®æ–°ãƒžãƒƒãƒ— - 交通\n\n`2%$rui/bullet_point%`0新パイロット処刑攻撃 - シャドウボクシング\n\n`2%$rui/bullet_point%`0第3ã®æ­¦å™¨ã‚¹ãƒ­ãƒƒãƒˆ\n\n`2%$rui/bullet_point%`0プライベートマッãƒè¨­å®š"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 6月27日 - ウォーゲーム"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point% `0新型タイタン - モナーク\n\n`2%$rui/bullet_point% `0新マップ - レリック\n\n`2%$rui/bullet_point% `0パイロットã®æ–°å‡¦åˆ‘攻撃 - 俺ãŒè¦‹ãˆã‚‹ãª\n\n`2%$rui/bullet_point% `0ストアã«æ–°ç€å•†å“を追加"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 5月30日 - モナークレイン"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point% `0新マップ - グリッãƒ\n\n`2%$rui/bullet_point% `0ライブファイア用新マップ - デッキ\n\n`2%$rui/bullet_point% `0新勢力 - M.R.V.N.\n\n`2%$rui/bullet_point% `0パイロットã®æ–°å‡¦åˆ‘攻撃 - 敵ã¯ãã“ã \n\n`2%$rui/bullet_point% `0世代上é™ã‚’100ã«ä¸Šæ˜‡"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 4月25æ—¥ - フロンティアã®ã‚°ãƒªãƒƒãƒ"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point% `0新マップ - æ¤æ°‘地\n\n`2%$rui/bullet_point% `0パイロットã®æ–°å‡¦åˆ‘攻撃 - ロードãƒã‚§ãƒƒã‚¯\n\n`2%$rui/bullet_point% `0新兵器 - R-101\n\n`2%$rui/bullet_point% `0ストアã«æ–°ç€å•†å“を追加"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 3月30日 - コロニーリボーン"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point% `0ライブファイア - パイロットåŒå£«ãŒã€å‹ã¡æ®‹ã‚Šã‚’è³­ã‘ã¦æˆ¦ã„ã¾ã™ï¼ãƒªã‚¹ãƒãƒ¼ãƒ³ãªã—ã€6対6ã®ãƒ©ã‚¦ãƒ³ãƒ‰åˆ¶ãƒžãƒƒãƒã§ã€1分以内ã«æ•µã‚’全滅ã•ã›ã‚‹ã¨ãƒ©ã‚¦ãƒ³ãƒ‰å‹åˆ©ã§ã™ã€‚ã¾ãŸã€ãƒ©ã‚¦ãƒ³ãƒ‰çµ‚了時ã«è‡ªåˆ†ã®ãƒãƒ¼ãƒ ãŒä¸­ç«‹ã®ãƒ•ãƒ©ãƒƒã‚°ã‚’所æŒã—ã¦ã„ãŸå ´åˆã‚‚å‹åˆ©ã¨ãªã‚Šã¾ã™ã€‚5ラウンド先å–ã—ãŸãƒãƒ¼ãƒ ãŒãƒžãƒƒãƒã®å‹è€…ã¨ãªã‚Šã¾ã™ã€‚\n\n`2%$rui/bullet_point% `0ライブファイア用新マップ - è‰åŽŸ\n\n`2%$rui/bullet_point% `0ライブファイア用新マップ - スタック\n\n`2%$rui/bullet_point% `0パイロットã®æ–°å‡¦åˆ‘攻撃 - レイトアタック"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 2月23日 - ライブファイア"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point% `0新マップ - エンジェルシティ\n\n`2%$rui/bullet_point% `0新兵器 - B3ウィングマン・エリート\n\n`2%$rui/bullet_point% `0パイロットã®æ–°å‡¦åˆ‘攻撃 - インナーピース\n\n`2%$rui/bullet_point% `0新タイタンキット\n\n`2%$rui/bullet_point% `0ストアã«æ–°ç€å•†å“を追加"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan%11月30日 - エンジェルシティモストウォンテッド"
+ "MP_ANGEL_CITY_FD_WAVE_1" "空ã«ç¹‹ãŒã‚Œ"
+ "MP_ANGEL_CITY_FD_WAVE_2" "ä¸å®šå½¢"
+ "MP_ANGEL_CITY_FD_WAVE_3" "æš´å‹•"
+ "MP_ANGEL_CITY_FD_WAVE_4" "é ‘å¼·ãªä¸€ç¾¤"
+ "MP_ANGEL_CITY_FD_WAVE_5" "å±é™ºãªè³­ã‘"
+ "MP_DRYDOCK_FD_WAVE_1" "知らã¬æµ·ã‚’æ³³ã’"
+ "MP_DRYDOCK_FD_WAVE_2" "弾をæŒãŸã¬æ•µ"
+ "MP_DRYDOCK_FD_WAVE_3" "科学ã®ç›®éš ã—"
+ "MP_DRYDOCK_FD_WAVE_4" "高圧力システム"
+ "MP_DRYDOCK_FD_WAVE_5" "å°é¢¨ã®ç›®"
+ "MP_THAW_FD_WAVE_1" "状æ³æŠŠæ¡"
+ "MP_THAW_FD_WAVE_2" "æ•°ã®è„…å¨"
+ "MP_THAW_FD_WAVE_3" "ヘンãƒãƒžãƒ³21"
+ "MP_THAW_FD_WAVE_4" "銃ç«å™¨ã®å–œã³"
+ "MP_THAW_FD_WAVE_5" "銃器を活ã‹ã—切れ"
+ "NO_PRICE" "オファー終了"
+ "NO_PRICE_TWO_LINES" "オファー\n終了"
+ "PL_aegis_last_titan_standing" "イージスLTS"
+ "PL_aegis_last_titan_standing_abbr" "ALTS"
+ "PL_aegis_last_titan_standing_desc" "イージスアップグレードãŒæœ‰åŠ¹ã€‚ラウンドå˜ä½ã§å‹ã¡æ®‹ã‚Šã‚’ç‹™ã†ã€‚å…ˆã«3ラウンドå‹åˆ©ã—ãŸãƒãƒ¼ãƒ ãŒå‹è€…ã¨ãªã‚‹ã€‚\n^FFC83200プレイヤー数:5対5 *タイタンã§ã‚¹ã‚¿ãƒ¼ãƒˆ\n時間制é™:1ラウンド3分 *リスãƒãƒ¼ãƒ³ãªã—\n最大パーティー人数:5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "イージスタイタンブロール"
+ "PL_aegis_titan_brawl_abbr" "ATB"
+ "PL_aegis_titan_brawl_desc" "敵ã®ã‚¿ã‚¤ã‚¿ãƒ³ã‚’倒ã›ã€‚イージスアップグレードãŒæœ‰åŠ¹ã€‚\n^FFC83200プレイヤー数:5対5\n時間制é™:10分\n最大パーティー人数:5"
+ "PL_aegis_titan_brawl_hint" "敵ã®ã‚¿ã‚¤ã‚¿ãƒ³ã‚’倒ã›ã€‚\n脱出ãªã—"
+ "PL_aegis_titan_brawl_lobby" "イージスタイタンブロールã®ãƒ­ãƒ“ー"
+ "PL_aitdm" "消耗戦"
+ "PL_aitdm_abbr" "ATT"
+ "PL_aitdm_desc" "敵をã™ã¹ã¦ç‰‡ä»˜ã‘ã‚。\n^FFC83200プレイヤー数:6対6 *AI\n時間制é™:10分\n最大パーティー人数:6"
+ "PL_aitdm_lobby" "消耗戦ロビー"
+ "PL_all_grapple" "アタック・オン・タイタンフォール"
+ "PL_all_grapple_abbr" "ATT"
+ "PL_all_grapple_desc" "戦術アビリティãŒã™ã¹ã¦ã‚°ãƒ©ãƒƒãƒ—ルã«ç½®ãæ›ã‚る以外ã¯ã€å¾“æ¥ã®æ¶ˆè€—戦ã¨åŒã˜ãƒ«ãƒ¼ãƒ«ã€‚\n^FFC83200プレイヤー数:6対6 *AI\n時間制é™:10分\n最大パーティー人数:6"
+ "PL_all_grapple_lobby" "アタック・オン・タイタンフォールã®ãƒ­ãƒ“ー"
+ "PL_all_holopilot" "è¯éº—ãªã‚‹è¬€ç•¥"
+ "PL_all_holopilot_abbr" "LF"
+ "PL_all_holopilot_desc" "戦術アビリティãŒã™ã¹ã¦ãƒ›ãƒ­ãƒ‘イロットã«ç½®ãæ›ã‚る以外ã¯ã€å¾“æ¥ã®ãƒ©ã‚¤ãƒ–ファイアã¨åŒã˜ãƒ«ãƒ¼ãƒ«ã€‚^FFC83200\nプレイヤー数:6対6 *タイタンãªã—\n時間制é™:60秒 *リスãƒãƒ¼ãƒ³ãªã—\n最大パーティー人数:6"
+ "PL_all_holopilot_lobby" "è¯éº—ãªã‚‹è¬€ç•¥ã®ãƒ­ãƒ“ー"
+ "PL_all_phase" "異次元"
+ "PL_all_phase_abbr" "ATT"
+ "PL_all_phase_desc" "戦術アビリティãŒã™ã¹ã¦ãƒ•ã‚§ãƒ¼ã‚ºã«ç½®ãæ›ã‚る以外ã¯ã€å¾“æ¥ã®æ¶ˆè€—戦ã¨åŒã˜ãƒ«ãƒ¼ãƒ«ã€‚\n^FFC83200プレイヤー数:6対6 *AI\n時間制é™:10分\n最大パーティー人数:6"
+ "PL_all_phase_lobby" "異次元ã®ãƒ­ãƒ“ー"
+ "PL_all_spicy" "è¾›å£æ¶ˆè€—戦"
+ "PL_all_spicy_abbr" "ATT"
+ "PL_all_spicy_desc" "戦術アビリティãŒã™ã¹ã¦ãƒ†ã‚£ãƒƒã‚¯ã«ç½®ãæ›ã‚る以外ã¯ã€å¾“æ¥ã®æ¶ˆè€—戦ã¨åŒã˜ãƒ«ãƒ¼ãƒ«ã€‚\n^FFC83200プレイヤー数:6対6 *AI\n時間制é™:10分\n最大パーティー人数:6"
+ "PL_all_spicy_lobby" "è¾›å£æ¶ˆè€—戦ã®ãƒ­ãƒ“ー"
+ "PL_amped_tacticals" "戦術強化"
+ "PL_amped_tacticals_abbr" "ATT"
+ "PL_amped_tacticals_desc" "戦術アビリティãŒå¼·åŒ–ã•ã‚Œã‚‹ä»¥å¤–ã¯ã€å¾“æ¥ã®æ¶ˆè€—戦ã¨åŒã˜ãƒ«ãƒ¼ãƒ«ã€‚\n^FFC83200プレイヤー数:6対6 *AI\n時間制é™:10分\n最大パーティー人数:6"
+ "PL_amped_tacticals_lobby" "戦術強化ã®ãƒ­ãƒ“ー"
+ "PL_ANGEL_CITY" "エンジェルシティ24/7"
+ "PL_angel_city_abbr" "AC"
+ "PL_angel_city_desc" "エンジェルシティ\n^FFC83200*賞金稼㎠*消耗戦\n*拠点増幅"
+ "PL_angel_city_lobby" "エンジェルシティ24/7ロビー"
+ "PL_at_coop" "脱出(å”力プレイ)"
+ "PL_at_coop_desc" "ピストルã ã‘を手ã«ç›£ç„を脱出ã—ãŸã€‚脱出艇ãŒåˆ°ç€ã™ã‚‹ã¾ã§ç”Ÿã残れ。敵を倒ã—ã¦é‡‘を稼ãŽã€æ­¦å™¨ã‚’購入ã—よã†ã€‚\n^FFC83200プレイヤー数:6\n最大パーティー人数:6"
+ "PL_at_coop_lobby" "脱出ロビー"
+ "PL_attrition" "賞金稼ãŽ"
+ "PL_attrition_abbr" "BH"
+ "PL_attrition_desc" "敵を倒ã—ã¦é‡‘を稼ã’。稼ã„ã ãƒœãƒ¼ãƒŠã‚¹ã‚’特定ã®åœ°ç‚¹ã§ã€Œã‚¢ãƒƒãƒ—ロードã€ã™ã‚Œã°ã•ã‚‰ã«ç²å¾—é¡ãŒä¸Šæ˜‡ã€‚\n^FFC83200プレイヤー数:5対5 *AI\n時間制é™:10分\n最大パーティー人数:5"
+ "PL_attrition_lobby" "賞金稼ãŽãƒ­ãƒ“ー"
+ "PL_capture_the_flag" "キャプãƒãƒ£ãƒ¼ãƒ»ã‚¶ãƒ»ãƒ•ãƒ©ãƒƒã‚°"
+ "PL_capture_the_flag_abbr" "CTF"
+ "PL_capture_the_flag_desc" "敵ã®ãƒ•ãƒ©ãƒƒã‚°ã‚’奪ã£ã¦åŸºåœ°ã¸æŒã¡å¸°ã‚Œã€‚ãã®é–“も敵ãƒãƒ¼ãƒ ã«ã“ã¡ã‚‰ã®ãƒ•ãƒ©ãƒƒã‚°ã‚’奪ã‚ã›ã‚‹ãªï¼\n^FFC83200プレイヤー数:5対5\n時間制é™:12分\n最大パーティー人数:5"
+ "PL_capture_the_flag_lobby" "CTFロビー"
+ "PL_coliseum" "コロシアム"
+ "PL_coliseum_desc" "機動力を強化ã—ã¦ã€æª»ã®ä¸­ã§1対1ã§æˆ¦ã†ã€‚敵を倒ã™ã¨ãƒ©ã‚¦ãƒ³ãƒ‰å‹åˆ©ã¨ãªã‚‹ã€‚3本先å–ã§æ”¯æŒè€…ã®ã‚®ãƒ•ãƒˆã‚’ç²å¾—。^FFC83200\nプレイヤー数:1対1 *タイタンãªã—\n時間制é™:3分 *リスãƒãƒ¼ãƒ³ãªã—\n**^FFFFFF00コロシアムãƒã‚±ãƒƒãƒˆã¾ãŸã¯å…¥å ´æ–™ã®æ”¯æ‰•ã„ãŒå¿…è¦"
+ "PL_coliseum_lobby" "コロシアムロビー"
+ "PL_colony" "æ¤æ°‘地24/7"
+ "PL_colony_abbr" "æ¤æ°‘地"
+ "PL_colony_desc" "ã„ã¤ã§ã‚‚æ¤æ°‘地ã ã‘を。^CCCCCC00ã“ã®ãƒªã‚¹ãƒˆã§ã¯ã€^FFC83200消耗戦^CCCCCC00ã€^FFC83200パイロット対パイロット^CCCCCC00ã¨^FFC83200ラスト・タイタン・スタンディング^CCCCCC00ã®ãƒžãƒƒãƒã‚’検索ã—ã¾ã™ã€‚"
+ "PL_colony_lobby" "æ¤æ°‘地24/7ロビー"
+ "PL_ctf_lf" "CTF(ニトロ)"
+ "PL_ctf_lf_abbr" "CTF-N"
+ "PL_ctf_lf_desc" "é¸æŠžã—ãŸãƒžãƒƒãƒ—ã§ç¹°ã‚Šåºƒã’る高速ã®ã‚­ãƒ£ãƒ—ãƒãƒ£ãƒ¼ãƒ»ã‚¶ãƒ»ãƒ•ãƒ©ãƒƒã‚°ã€‚\n^FFC83200プレイヤー数:5対5\n最大パーティー人数:5\n^F4D5A600フラッグをå³æ™‚返還"
+ "PL_ctf_lf_lobby" "CTFニトロã®ãƒ­ãƒ“ー"
+ "PL_default_description" "デフォルトã®èª¬æ˜Ž"
+ "PL_default_lobbytitle" "デフォルトã®ãƒ­ãƒ“ータイトル"
+ "PL_default_name" "デフォルトã®åå‰"
+ "PL_don" "ダブル・オア・ナッシング"
+ "PL_fd" "フロンティアディフェンス"
+ "PL_fd_desc" "残留艦隊ã®æ³¢çŠ¶æ”»æ’ƒã‚’防ãŽãã‚Œ"
+ "PL_fd_easy" "フロンティアディフェンス:イージー"
+ "PL_fd_easy_desc" "敵è»ã‚’打ã¡ç •ã‘"
+ "PL_fd_easy_lobby" "フロンティアディフェンス:イージー用ロビー"
+ "PL_fd_hard" "フロンティアディフェンス:ãƒãƒ¼ãƒ‰"
+ "PL_fd_hard_desc" "å·§ã¿ãªãƒ—レイãŒå¿…è¦"
+ "PL_fd_hard_lobby" "フロンティアディフェンス:ãƒãƒ¼ãƒ‰ç”¨ãƒ­ãƒ“ー"
+ "PL_fd_insane" "フロンティアディフェンス:マックス"
+ "PL_fd_insane_desc" "生ã残りã¯ä¸å¯èƒ½"
+ "PL_fd_insane_lobby" "フロンティアディフェンス:マックスã®ãƒ­ãƒ“ー"
+ "PL_fd_lobby" "フロンティアディフェンスã®ãƒ­ãƒ“ー"
+ "PL_fd_master" "フロンティアディフェンス:マスター"
+ "PL_fd_master_desc" "真ã®å®ŸåŠ›è€…ã ã‘ãŒæˆåŠŸã‚’ã¤ã‹ã‚€"
+ "PL_fd_master_lobby" "フロンティアディフェンス:マスターã®ãƒ­ãƒ“ー"
+ "PL_fd_normal" "フロンティアディフェンス:レギュラー"
+ "PL_fd_normal_desc" "熟練プレイヤーå‘ã‘"
+ "PL_fd_normal_lobby" "フロンティアディフェンス:レギュラーã®ãƒ­ãƒ“ー"
+ "PL_ffa" "フリー・フォー・オール"
+ "PL_ffa_abbr" "FFA"
+ "PL_ffa_desc" "全パイロットãŒè‡ªåˆ†ã®ãŸã‚ã«æˆ¦ã†ã€‚敵をã™ã¹ã¦å§‹æœ«ã—ã‚。\n^FFC83200プレイヤー数:1対11\n時間制é™:10分\n最大パーティー人数:1"
+ "PL_ffa_lobby" "フリー・フォー・オールロビー"
+ "PL_fra" "フリー・エージェント"
+ "PL_fra_abbr" "FRA"
+ "PL_fra_desc" "1人ãã‚Šã®æˆ¦ã„ã«æŒ‘戦。敵を倒ã›ã°å‹åˆ©ã ã€‚ãƒãƒƒãƒ†ãƒªãƒ¼ã‚’3個回åŽã—ã€ã‚¿ã‚¤ã‚¿ãƒ³ãƒ•ã‚©ãƒ¼ãƒ«ã‚’è¦è«‹ã›ã‚ˆã€‚\n^FFC83200プレイヤー数:1対11\n時間制é™:15分\n最大パーティー人数:1"
+ "PL_fra_lobby" "フリー・エージェントロビー"
+ "PL_groud_war_lobby" "8v8ミックステープロビー"
+ "PL_ground_war" "8v8ミックステープ"
+ "PL_ground_war_abbr" "8v8"
+ "PL_ground_war_desc" "スカーミッシュã¨æ‹ ç‚¹å¢—幅を全マップã«ãŠã„ã¦å¤§äººæ•°ã§ãƒ—レイã§ãã¾ã™ã€‚\n^FFC83200プレイヤー数:8対8"
+ "PL_hardpoint" "拠点増幅"
+ "PL_hardpoint_abbr" "AHP"
+ "PL_hardpoint_desc" "拠点を制圧ã—ã¦å®ˆã‚Šåˆ‡ã‚Œã°ãƒã‚¤ãƒ³ãƒˆã‚’ç²å¾—。増幅ã—ãŸæ‹ ç‚¹ã¯ç²å¾—ãƒã‚¤ãƒ³ãƒˆãŒ2å€ã«ãªã‚‹ã€‚\n^FFC83200プレイヤー数:6対6\n時間制é™:10分\n最大パーティー人数:6"
+ "PL_hardpoint_lobby" "拠点増幅ロビー"
+ "PL_hunted" "逃亡者"
+ "PL_iron_last_titan_standing" "アイアンLTS"
+ "PL_iron_last_titan_standing_abbr" "ILTS"
+ "PL_iron_last_titan_standing_desc" "タイタンã®ã¿ã€ãƒ©ã‚¦ãƒ³ãƒ‰åˆ¶ã®å‹ã¡æŠœã戦。3ラウンド先å–ã§å‹åˆ©ã€‚\n^FFC83200プレイヤー数:5対5 *パイロットãªã—\n時間制é™:1ラウンド3分 *リスãƒãƒ¼ãƒ³ãªã—\n最大パーティー人数:5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "ラスト・タイタン・スタンディング"
+ "PL_last_titan_standing_abbr" "LTS"
+ "PL_last_titan_standing_desc" "全員ãŒã‚¿ã‚¤ã‚¿ãƒ³ã«ä¹—ã£ãŸçŠ¶æ…‹ã§é–‹å§‹ã—ã€ãƒ©ã‚¦ãƒ³ãƒ‰å˜ä½ã§å‹ã¡æ®‹ã‚Šã‚’ç‹™ã†ã€‚å…ˆã«3ラウンドå‹åˆ©ã—ãŸãƒãƒ¼ãƒ ãŒå‹è€…ã¨ãªã‚‹ã€‚\n^FFC83200プレイヤー数:5対5 *タイタンã§ã‚¹ã‚¿ãƒ¼ãƒˆ\n時間制é™:1ラウンド3分 *リスãƒãƒ¼ãƒ³ãªã—\n最大パーティー人数:5"
+ "PL_last_titan_standing_lobby" "LTSロビー"
+ "PL_limited_time_mode" "期間é™å®šãƒ¢ãƒ¼ãƒ‰"
+ "PL_live_fire" "ライブファイア"
+ "PL_live_fire_abbr" "LF"
+ "PL_live_fire_desc" "高速ã®æˆ¦é—˜ãŒãƒ©ã‚¤ãƒ–ファイア・アリーナã§å±•é–‹ã€‚敵パイロットを全滅ã•ã›ã‚‹ã‹ã€ã‚¿ã‚¤ãƒ ã‚ªãƒ¼ãƒãƒ¼æ™‚ã«ãƒ•ãƒ©ãƒƒã‚°ã‚’所æŒã—ã¦ã„ã‚‹ã¨ãƒ©ã‚¦ãƒ³ãƒ‰å‹åˆ©ã€‚^FFC83200\nプレイヤー数:6対6 *タイタンãªã—\n時間制é™:60秒 *リスãƒãƒ¼ãƒ³ãªã—\n最大パーティー人数:6"
+ "PL_live_fire_lobby" "ライブファイアロビー"
+ "PL_load_a_map_on_the_command_line" "コマンド行ã§ãƒžãƒƒãƒ—をロード -devonly"
+ "PL_marked_for_death" "マーク・フォー・デス"
+ "PL_marked_for_death_abbr" "MFD"
+ "PL_marked_for_death_desc" "マークã•ã‚ŒãŸæ¨™çš„ã®å‘½ã‚’ã‚ãる攻防戦。\n^FFC83200プレイヤー数:6対6 \n時間制é™:12分\n最大パーティー人数:6"
+ "PL_marked_for_death_lobby" "マーク・フォー・デス・ロビー"
+ "PL_nitro_ffa" "FFA(ニトロ)"
+ "PL_nitro_ffa_abbr" "FFA-N"
+ "PL_nitro_ffa_desc" "é¸æŠžã—ãŸãƒžãƒƒãƒ—ã§ç¹°ã‚Šåºƒã’る高速ã®ãƒ•ãƒªãƒ¼ãƒ»ãƒ•ã‚©ãƒ¼ãƒ»ã‚ªãƒ¼ãƒ«ã€‚\n^FFC83200プレイヤー数:6\n^F4D5A600タイタンãªã—\nブーストãªã—"
+ "PL_nitro_ffa_lobby" "FFAニトロã®ãƒ­ãƒ“ー"
+ "PL_nitro_mixtape" "ミックステープ(ニトロ)"
+ "PL_nitro_mixtape_abbr" "MXT-N"
+ "PL_nitro_mixtape_desc" "é¸æŠžã—ãŸãƒžãƒƒãƒ—ã§ç¹°ã‚Šåºƒã’る高速ã®CTFã€MFDãŠã‚ˆã³PvP。\n^FFC83200プレイヤー数:5対5\n最大パーティー人数:5\n^F4D5A600フラッグをå³æ™‚返還"
+ "PL_nitro_mixtape_lobby" "ミックステープロビー"
+ "PL_pilot_hunter" "スカーミッシュ"
+ "PL_pilot_hunter_abbr" "SKM"
+ "PL_pilot_hunter_desc" "敵ã®ãƒ‘イロットやタイタンを倒ã›ã€‚\n^FFC83200プレイヤー数:8対8\n時間制é™:10分\n最大パーティー人数:8"
+ "PL_pilot_hunter_lobby" "スカーミッシュã®ãƒ­ãƒ“ー"
+ "PL_pilot_skirmish" "パイロット対パイロット"
+ "PL_pilot_skirmish_abbr" "PvP"
+ "PL_pilot_skirmish_desc" "敵パイロットを撃破ã›ã‚ˆã€‚タイタンフォールç¦æ­¢ã€‚\n^FFC83200プレイヤー数:8対8 *タイタンãªã—\n時間制é™:10分\n最大パーティー人数:8"
+ "PL_pilot_skirmish_lobby" "PvPロビー"
+ "PL_pl_rebuild_all_paths" "進路å†å»º"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "ai_ainRebuildOnMapStart 2 ã§å®Ÿè¡Œ"
+ "PL_private_match" "プライベートマッãƒ(ベータ)"
+ "PL_private_match_desc" "好ããªãƒžãƒƒãƒ—ã¨ãƒ¢ãƒ¼ãƒ‰ã‚’é¸ã‚“ã§ã€ã‚«ã‚¹ã‚¿ãƒžã‚¤ã‚ºã—ãŸãƒ—ライベートマッãƒã‚’プレイã—よã†ã€‚\n^FFC83200ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‹ãƒ•ãƒ¬ãƒ³ãƒ‰ã‚’招待ã—ã¦ãƒ—レイ。\n^FFC83200プレイヤー数:1~16人\n進行ãªã—"
+ "PL_private_match_lobby" "プライベートマッãƒãƒ»ãƒ™ãƒ¼ã‚¿ãƒ­ãƒ“ー"
+ "PL_promo_coop" "4人å”力プレイ"
+ "PL_raid" "レイド"
+ "PL_rise" "ライズ24/7"
+ "PL_rise_abbr" "RIS"
+ "PL_rise_desc" "ã„ã¤ã§ã‚‚ライズã ã‘を。^CCCCCC00ã“ã®ãƒªã‚¹ãƒˆã§ã¯^FFC83200CTF^CCCCCC00ã€^FFC83200拠点増幅^CCCCCC00ã€^FFC83200パイロット対パイロット^CCCCCC00ã€^FFC83200ライブファイア^CCCCCC00ã¨^FFC83200ラスト・タイタン・スタンディング^CCCCCC00ã®ãƒžãƒƒãƒã‚’検索ã—ã¾ã™ã€‚"
+ "PL_rise_lobby" "ライズ24/7ã®ãƒ­ãƒ“ー"
+ "PL_rocket_arena" "ロケットアリーナ"
+ "PL_rocket_arena_abbr" "LF"
+ "PL_rocket_arena_desc" "従æ¥ã®ãƒ©ã‚¤ãƒ–ファイアã¨åŒã˜ãƒ«ãƒ¼ãƒ«ã§ã€æ”¹é€ ã—ãŸEPGを使用ã™ã‚‹ã€‚^FFC83200\nプレイヤー数:6対6 *タイタンãªã—\n時間制é™:90秒 *リスãƒãƒ¼ãƒ³ãªã—\n最大パーティー人数:6"
+ "PL_rocket_arena_lobby" "ロケットアリーナã®ãƒ­ãƒ“ー"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "コマンド行ã§ãƒžãƒƒãƒ—をロードã™ã‚‹éš›ã®è¨­å®šã€‚以下ã®cmdlineMapLoad 1ã¯å®Ÿè¡Œã«å¿…è¦ãªã®ã§ç·¨é›†ã—ãªã„ã§ãã ã•ã„。"
+ "PL_speedball" "ライブファイア"
+ "PL_speedball_desc" "中立ã®ãƒ•ãƒ©ãƒƒã‚°ã‚’奪ã„åˆã†ã€‚敵ãƒãƒ¼ãƒ ã‚’排除ã™ã‚‹ã‹ã€åˆ¶é™æ™‚間終了時ã«è‡ªãƒãƒ¼ãƒ ãŒãƒ•ãƒ©ãƒƒã‚°ã‚’所有ã—ã¦ã„ã‚Œã°ãƒ©ã‚¦ãƒ³ãƒ‰ã«å‹åˆ©ã€‚5ラウンド先å–制。\n^FFC83200プレイヤー数:6対6 *タイタンãªã—\n時間制é™:1ラウンドã«ã¤ã60秒 *リスãƒãƒ¼ãƒ³ãªã—\n最大パーティー人数:6"
+ "PL_speedball_lobby" "ライブファイアロビー"
+ "PL_tactikill" "戦術キル消耗戦"
+ "PL_tactikill_abbr" "ATT"
+ "PL_tactikill_desc" "キル時ã«æˆ¦è¡“アビリティãŒå®Œå…¨ã«ãƒªã‚»ãƒƒãƒˆã•ã‚Œã‚‹ä»¥å¤–ã¯ã€å¾“æ¥ã®æ¶ˆè€—戦ã¨åŒã˜ãƒ«ãƒ¼ãƒ«ã€‚\n^FFC83200プレイヤー数:6対6 *AI\n時間制é™:10分\n最大パーティー人数:6"
+ "PL_tactikill_lobby" "戦術キル消耗戦ã®ãƒ­ãƒ“ー"
+ "PL_titan_brawl" "タイタンブロール"
+ "PL_titan_brawl_abbr" "TB"
+ "PL_titan_brawl_desc" "敵ã®ã‚¿ã‚¤ã‚¿ãƒ³ã‚’倒ã›ã€‚パイロットã¯å‚戦ç¦æ­¢ã€‚\n^FFC83200プレイヤー数:5対5\n時間制é™:10分\n最大パーティー人数:5"
+ "PL_titan_brawl_hint" "敵ã®ã‚¿ã‚¤ã‚¿ãƒ³ã‚’倒ã›ã€‚\né™æ©Ÿãªã—。"
+ "PL_titan_brawl_lobby" "タイタンブロールã®ãƒ­ãƒ“ー"
+ "PL_titan_brawl_turbo" "ターボタイタンブロール"
+ "PL_titan_brawl_turbo_abbr" "TTDM"
+ "PL_titan_brawl_turbo_desc" "ダッシュã¨ã‚³ã‚¢ã®å›žå¾©ãŒæ—©ã¾ã£ãŸçŠ¶æ…‹ã§ã€å¾“æ¥ã®ã‚¿ã‚¤ã‚¿ãƒ³ãƒ–ロールã®ãƒ«ãƒ¼ãƒ«ã§æˆ¦ã†ã€‚\n^FFC83200プレイヤー数:5対5\n時間制é™:10分\n最大パーティー人数:5"
+ "PL_titan_brawl_turbo_hint" "敵ã®ã‚¿ã‚¤ã‚¿ãƒ³ã‚’倒ã›ã€‚\n脱出ãªã—"
+ "PL_titan_brawl_turbo_lobby" "ターボタイタンブロールã®ãƒ­ãƒ“ー"
+ "PL_turbo_last_titan_standing" "ターボLTS"
+ "PL_turbo_last_titan_standing_abbr" "LTS"
+ "PL_turbo_last_titan_standing_desc" "ダッシュã¨ã‚³ã‚¢ã®å›žå¾©ãŒæ—©ã¾ã£ãŸçŠ¶æ…‹ã§ã€å¾“æ¥ã®LTSã®ãƒ«ãƒ¼ãƒ«ã§æˆ¦ã†ã€‚\n^FFC83200プレイヤー数:5対5 *タイタンã§ã‚¹ã‚¿ãƒ¼ãƒˆ\n時間制é™:1ラウンド3分 *リスãƒãƒ¼ãƒ³\n最大パーティー人数:5"
+ "PL_turbo_last_titan_standing_lobby" "ターボLTSã®ãƒ­ãƒ“ー"
+ "PL_variety_pack" "ミックステープ"
+ "PL_variety_pack_desc" "様々ãªãƒ—レイヤー数やマップã§ã€æ¬¡ã®ãƒ¢ãƒ¼ãƒ‰ã‚’プレイã§ãã¾ã™ã€‚\n^FFC83200*賞金稼㎠*消耗戦\n*ラスト・タイタン・スタンディング *拠点増幅\n*パイロット対パイロット *キャプãƒãƒ£ãƒ¼ãƒ»ã‚¶ãƒ»ãƒ•ãƒ©ãƒƒã‚°"
+ "PL_variety_pack_lobby" "ミックステープロビー"
+ "PL_wargames" "ウォーゲーム24/7"
+ "PL_wargames_abbr" "WGM"
+ "PL_wargames_desc" "ã„ã¤ã§ã‚‚ウォーゲームã ã‘を。^CCCCCC00ã“ã®ãƒªã‚¹ãƒˆã§ã¯^FFC83200消耗戦^CCCCCC00ã€^FFC83200CTF^CCCCCC00ã€^FFC83200パイロット対パイロット^CCCCCC00ã€^FFC83200拠点増幅^CCCCCC00ã¨^FFC83200ラスト・タイタン・スタンディング^CCCCCC00ã®ãƒžãƒƒãƒã‚’検索ã—ã¾ã™ã€‚"
+ "PL_wargames_lobby" "ウォーゲーム24/7ã®ãƒ­ãƒ“ー"
+ "WATCH_TUTORIAL" "ãƒãƒ¥ãƒ¼ãƒˆãƒªã‚¢ãƒ«ã‚’見る"
+ }
+ }
+ "lang"
+ {
+ "Language" "russian"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "Продолжите Ñвое приключение на чарующем Фронтире в новом DLC длÑ Titanfall 2 — «Открытки Ñ Фронтира». В нем Ð²Ð°Ñ Ð¶Ð´ÑƒÑ‚ новые и знакомые локации, а также ÑÐ²ÐµÐ¶Ð°Ñ ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Ñлитных раÑкраÑок оружиÑ — таким Фронтир вы еще не видели!\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_00_Q" "Трейлер «Открыток Ñ Фронтира»"
+ "COMMUNITYUPDATE_01_A" "Узнайте, что ждет Ð²Ð°Ñ Ð²Â Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ð¸ «Открытки Ñ Фронтира». \n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_01_Q" "Открытки Ñ Фронтира: изменениÑ"
+ "COMMUNITYUPDATE_02_A" "Titanfall: Assault — Ñто ÑÑ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ð² реальном времени во вÑеленной Titanfall, которую мы разрабатываем длÑ мобильных уÑтройÑтв ÑовмеÑтно Ñо Ñтудией Particle City. ПоÑмотреть на ее игровой процеÑÑ Ð¼Ð¾Ð¶Ð½Ð¾ здеÑÑŒ! \n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_02_Q" "Трейлер к выходу Titanfall: Assault"
+ "COMMUNITYUPDATE_03_A" "Ðа канале Iniquity Games разобрано обучение и даютÑÑ Ñоветы по игре Titanfall Assault. Это Ñамый лучший ÑпоÑоб ознакомитьÑÑ Ñ Ð¸Ð³Ñ€Ð¾Ð¹.\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_03_Q" "Titanfall: Assault: видео «ОÑновы игры»"
+ "COMMUNITYUPDATE_04_A" "Узнайте иÑторию разработки режима «Оборона Фронтира» от неÑкольких ведущих разработчков и поÑмотрите, как мы играем в него!\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_04_Q" "Respawn играет в «Оборону Фронтира»"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger Ñоздал забавный ролик, в котором демонÑтрирует различные приемчики Ñ пульÑирующим клинком.\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_05_Q" "ТворчеÑтво ÑообщеÑтва: Ðа оÑтрие"
+ "COMMUNITYUPDATE_06_A" "ConzeyG Ñ reddit демонÑтрирует жеÑтокую ÑффективноÑÑ‚ÑŒ «ÐордÑтара», одного за другим ÑƒÐ½Ð¸Ñ‡Ñ‚Ð¾Ð¶Ð°Ñ Ð¿Ð¸Ð»Ð¾Ñ‚Ð¾Ð² в режиме «Приговор — Ñмерть».\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_06_Q" "СообщеÑтво: Охота на пилотов «ÐордÑтара»"
+ "COMMUNITYUPDATE_07_A" "Уже в игре! «Оборона Фронтира» возвращаетÑÑ Ð²Ð¼ÐµÑте Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ð¹ «ВоÑход», Ð´Ð»Ñ Ð¿Ð¾ÐºÑƒÐ¿ÐºÐ¸ доÑтупны новые боевые раÑкраÑки, а также поÑвилаÑÑŒ Ð½Ð¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° Ð´Ð»Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ð° «ПереÑтрелка». ОзнакомьтеÑÑŒ Ñ Ð½Ð¾Ð²Ð¾Ð²Ð²ÐµÐ´ÐµÐ½Ð¸Ñми здеÑÑŒ. \n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_07_Q" "Трейлер \"ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Â«Ð©Ð¸Ñ‚ Фронтира»\""
+ "COMMUNITYUPDATE_08_A" "Уже в игре! Ð—Ð½Ð°Ð¼ÐµÐ½Ð¸Ñ‚Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° «Военные игры» возвращаетÑÑ â€” и такой вы ее еще не видели! Реще не пропуÑтите новую казнь и карту Ð´Ð»Ñ Â«ÐŸÐµÑ€ÐµÑтрелки» — «Траффик».\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_08_Q" "Трейлер «Военные игры»"
+ "COMMUNITYUPDATE_09_A" "Это DLC добавлÑет «Монарха» (Ñедьмого Титана Ð´Ð»Ñ Ñетевой игры), переделанную карту «СвÑтынÑ», новую казнь, а также «Ронина Прайм», «Тона Прайм», камуфлÑжи, фоны и риÑунки на ÐºÐ¾Ñ€Ð¿ÑƒÑ Ð² магазин. ОзнакомитьÑÑ Ñ Ð¸Ð³Ñ€Ð¾Ð²Ñ‹Ð¼ процеÑÑом можно здеÑÑŒ.\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_09_Q" "Трейлер «ВлаÑÑ‚ÑŒ монарха»"
+ "COMMUNITYUPDATE_10_A" "Ðовое в беÑплатном DLC «Разлом на Фронтире»: nкарта Ð´Ð»Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ð° «ПереÑтрелка»: «Палуба», nфракциÑ: «Марвины», nказнь Ð´Ð»Ñ Ð¿Ð¸Ð»Ð¾Ñ‚Ð°: «ПульÑирующий клинок».\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_10_Q" "Трейлер «Разлом на Фронтире»"
+ "COMMUNITYUPDATE_11_A" "Ð¡Ñ‚Ð°Ð²ÑˆÐ°Ñ ÐºÑƒÐ»ÑŒÑ‚Ð¾Ð²Ð¾Ð¹ карта Ð´Ð»Ñ Ñетевого режима оригинального Titanfall «КолониÑ» теперь доÑтупна и в Ñиквеле! Пилоты Titanfall 2 могут опробовать Ñвои Ñилы в боÑÑ… на тихих узких улочках, крышах и закрытых двориках небольшой деревеньки. ПереоÑмыÑлить Ñтарые тактики Ñ Ð½Ð¾Ð²Ñ‹Ð¼Ð¸ Титанами и оружием игроки Ñмогут уже Ñ 30 марта — и Ñовершенно беÑплатно! Ð’ наборе DLC «ÐÐ¾Ð²Ð°Ñ ÐºÐ¾Ð»Ð¾Ð½Ð¸Ñ» вы найдете и новые верÑии клаÑÑичеÑкого Ð¾Ñ€ÑƒÐ¶Ð¸Ñ (например, винтовку R-101), новую казнь Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ крюка и ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ð²Ð½ÐµÑˆÐ½ÐµÐ³Ð¾ вида — уÑтраивать резню на улицах города нужно по поÑледней моде!\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_11_Q" "Трейлер «ÐÐ¾Ð²Ð°Ñ ÐºÐ¾Ð»Ð¾Ð½Ð¸Ñ»"
+ "COMMUNITYUPDATE_12_A" "«ПереÑтрелка» — новый адреналиновый режим, в котором две команды из шеÑти пилотов ÑражаютÑÑ Ð´Ð¾ поÑледней капли крови. Ð”Ð»Ñ Ñтого режима мы Ñпециально Ñоздали две новые беÑплатные карты: «Луг» и «Склад». Это компактные закрытые арены, идеально подходÑщие Ð´Ð»Ñ Ð½Ð°Ð¿Ñ€Ñженных боев на близких диÑтанциÑÑ….\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_12_Q" "Трейлер «ПереÑтрелка»"
+ "COMMUNITYUPDATE_13_A" "Ð’Ñеми Ð»ÑŽÐ±Ð¸Ð¼Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° `1Город Ðнгелов`0 из первой чаÑти Titanfall Ñнова в Ñтрою. Первое беÑплатное DLC Ð´Ð»Ñ Titanfall 2 под названием `1ОÑобо опаÑные в Городе Ðнгелов`0 доÑтупно Ð´Ð»Ñ Ð²Ñех игроков Ñ 1 декабрÑ. Ð’ обновлении также еÑÑ‚ÑŒ ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ð²Ð½ÐµÑˆÐ½ÐµÐ³Ð¾ вида, которые немного изменÑÑ‚ Фронтир. \n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_13_Q" "Трейлер «Город Ðнгелов»"
+ "COMMUNITYUPDATE_14_A" "С возвращением!\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_14_Q" "Трейлер «Ðа биÑ»"
+ "COMMUNITYUPDATE_15_A" "Две легенды. Одно наÑледие.\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_15_Q" "Трейлер одиночной игры «Единое целое»"
+ "COMMUNITYUPDATE_16_A" "Ðикаких ограничений.\n\n\nÐ”Ð»Ñ Ð¿Ñ€Ð¾Ñмотра нажмите `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_16_Q" "Трейлер Ñетевой игры «Пилоты»"
+ "COMMUNITYUPDATE_DESC" "`3ÐовоÑти ÑообщеÑтва Titanfall`0\n\nÐовоÑти и ÑÑылки Ñо вÑего Интернета.\n\nХотите еще?\n`2%$rui/bullet_point%`0ПодпишитеÑÑŒ на Ð½Ð°Ñ Ð² Twitter: `1@Respawn`0\n`2%$rui/bullet_point%`0ПоÑтавьте «ÐравитÑÑ» на `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0Заходите на `1www.respawn.com`0."
+ "COMMUNITYUPDATE_NAME" "СообщеÑтво"
+ "KNB_SUBJECT_00_DESC" "`3Что нового в Titanfall?`0\n\nÐовоÑти обновлений Titanfall 2!"
+ "KNB_SUBJECT_00_NAME" "ÐžÐ±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¸Ð³Ñ€Ñ‹"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 Ðовые фоны Ñмблем от ÑообщеÑтва\n\n`2%$rui/bullet_point%`0Ð’Ñе подарки Ñторонника теперь доÑтупны за кредиты\n\n`2%$rui/bullet_point%`0Ðовый платный контент в магазине\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 28 ноÑбрÑ — Ð²Ñ€ÐµÐ¼Ñ Ð¶Ð°Ñ‚Ð²Ñ‹"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Ð“Ð»Ð°Ð²Ð½Ð°Ñ Ñчейка Ð´Ð»Ñ Ð¿Ð¸Ñтолета\n\n`2%$rui/bullet_point%`0Фоны на Ð¥Ñллоуин\n\n`2%$rui/bullet_point%`0Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±Ð°Ð»Ð°Ð½Ñа\n\n`2%$rui/bullet_point%`0Ðовый доÑтупный Ð´Ð»Ñ Ð¿Ð¾ÐºÑƒÐ¿ÐºÐ¸ контент в магазине\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 31 октÑбрÑ: «СлаÑти и ÑтраÑти»"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0«Оборона Фронтира» — добавлена поддержка 3 карт: Док, Город Ðнгелов и Экзопланета.\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° Ð´Ð»Ñ Â«ÐŸÐµÑ€ÐµÑтрелки» — UMA\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ð·Ð½ÑŒ пилота — ДырÑÐ²Ð°Ñ Ñтена\n\n`2%$rui/bullet_point%`0Ðовый доÑтупный Ð´Ð»Ñ Ð¿Ð¾ÐºÑƒÐ¿ÐºÐ¸ контент в магазине"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 29 авгуÑта — Открытки Ñ Ð¤Ñ€Ð¾Ð½Ñ‚Ð¸Ñ€Ð°"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Оборона Фронтира — новый ÑовмеÑтный режим, в котором вам придетÑÑ Ð²Ð¼ÐµÑте Ñ другими игроками защищать важную цель от уÑиливающихÑÑ Ð²Ð¾Ð»Ð½ противников под управлением ИИ. Без Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¸Â ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð°Ð´Ð°Ð¿Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ñ‚ÑŒÑÑ Ñ‚ÑƒÑ‚ не победить.\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð°: «ВоÑход»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° Ð´Ð»Ñ Â«ÐŸÐµÑ€ÐµÑтрелки»: «ПоÑелок»\n\n`2%$rui/bullet_point%`0Ðовый доÑтупный Ð´Ð»Ñ Ð¿Ð¾ÐºÑƒÐ¿ÐºÐ¸ контент в магазине"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 25 июлÑ: Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Â«Ð©Ð¸Ñ‚ Фронтира»"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° — «Военные игры»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° Ð´Ð»Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ð° «ПереÑтрелка» — «Траффик»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ð·Ð½ÑŒ пилота — «Бой Ñ Ñ‚ÐµÐ½ÑŒÑŽÂ»\n\n`2%$rui/bullet_point%`03 Ñчейка Ð´Ð»Ñ Ð¾Ñ€ÑƒÐ¶Ð¸Ñ\n\n`2%$rui/bullet_point%`0ÐаÑтройки чаÑтных матчей"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 27 июнÑ: «Военные игры»"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0Ðовый Титан: «Монарх»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð°: «СвÑтынÑ»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ð·Ð½ÑŒ пилота: Â«Ð˜Ð»Ð»ÑŽÐ·Ð¸Ñ Ð¾Ð±Ð¼Ð°Ð½Ð°Â»\n\n`2%$rui/bullet_point%`0Ðовый контент в магазине"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 30 маÑ: ВлаÑÑ‚ÑŒ монарха"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð°: «Разлом»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° Ð´Ð»Ñ Â«ÐŸÐµÑ€ÐµÑтрелки»: «Палуба»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ Ñ„Ñ€Ð°ÐºÑ†Ð¸Ñ: МРВÐ\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ð·Ð½ÑŒ пилота: «Ðа оÑтрие»\n\n`2%$rui/bullet_point%`0Увеличение макÑ. Ð¿Ð¾ÐºÐ¾Ð»ÐµÐ½Ð¸Ñ Ð´Ð¾ 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 25 апрелÑ: Разлом на Фронтире"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð°: «КолониÑ»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ð·Ð½ÑŒ пилота: «Череподав»\n\n`2%$rui/bullet_point%`0Ðовое оружие: R-101\n\n`2%$rui/bullet_point%`0Ðовый контент в магазине"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 30 марта: ÐÐ¾Ð²Ð°Ñ ÐºÐ¾Ð»Ð¾Ð½Ð¸Ñ"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0«ПереÑтрелка» — новый режим на выбывание Ð´Ð»Ñ Ð¿Ð¸Ð»Ð¾Ñ‚Ð¾Ð²! Ð’Ð°Ñ Ð¶Ð´ÑƒÑ‚ битвы 6 на 6 без возможноÑти возрождениÑ. Выигрывает та команда, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ ÑƒÐ½Ð¸Ñ‡Ñ‚Ð¾Ð¶Ð¸Ñ‚ Ñоперника за одну минуту или будет удерживать нейтральный флаг по окончании отÑчета. ÐŸÐµÑ€Ð²Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°, Ð²Ñ‹Ð¸Ð³Ñ€Ð°Ð²ÑˆÐ°Ñ Ð² пÑти раундах, побеждает.\n\n`2%$rui/bullet_point%`0Ðовые карты Ð´Ð»Ñ Â«ÐŸÐµÑ€ÐµÑтрелки»: «Луг» и «Склад»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ð·Ð½ÑŒ пилота: «Поздний удар»"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 23 февралÑ: ПереÑтрелка"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð°: «Город Ðнгелов»\n\n`2%$rui/bullet_point%`0Ðовое оружие: «Элитный ведомый B3»\n\n`2%$rui/bullet_point%`0ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ð·Ð½ÑŒ пилота: «ВнутренноÑти»\n\n`2%$rui/bullet_point%`0Ðовые наборы Ð´Ð»Ñ Ð¢Ð¸Ñ‚Ð°Ð½Ð¾Ð²\n\n`2%$rui/bullet_point%`0Ðовый контент в магазине"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 30 ноÑбрÑ: ОÑобо опаÑные в Городе Ðнгелов"
+ "MP_ANGEL_CITY_FD_WAVE_1" "Пленники небеÑ"
+ "MP_ANGEL_CITY_FD_WAVE_2" "БеÑформенноÑÑ‚ÑŒ"
+ "MP_ANGEL_CITY_FD_WAVE_3" "ÐœÑтеж"
+ "MP_ANGEL_CITY_FD_WAVE_4" "Ðеумолимый жребий"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Пойти ва-банк"
+ "MP_DRYDOCK_FD_WAVE_1" "Ðеизведанные воды"
+ "MP_DRYDOCK_FD_WAVE_2" "У них кончилиÑÑŒ патроны!"
+ "MP_DRYDOCK_FD_WAVE_3" "ОÑлепленные наукой"
+ "MP_DRYDOCK_FD_WAVE_4" "СиÑтема выÑокого давлениÑ"
+ "MP_DRYDOCK_FD_WAVE_5" "Око бури"
+ "MP_THAW_FD_WAVE_1" "ДейÑÑ‚Ð²Ð¸Ñ Ð¿Ð¾ Ñитуации"
+ "MP_THAW_FD_WAVE_2" "Сила в количеÑтве"
+ "MP_THAW_FD_WAVE_3" "ПриÑпешник-21"
+ "MP_THAW_FD_WAVE_4" "Игры Ñ Ð¾Ð³Ð½ÐµÐ¼"
+ "MP_THAW_FD_WAVE_5" "Одним великанам под Ñилу"
+ "NO_PRICE" "ИÑтекло"
+ "NO_PRICE_TWO_LINES" "ИÑтекло"
+ "PL_aegis_last_titan_standing" "ПиТ «Эгида»"
+ "PL_aegis_last_titan_standing_abbr" "ПиТЭ"
+ "PL_aegis_last_titan_standing_desc" "ДоÑтупны ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ð­Ð³Ð¸Ð´Ñ‹. ÐœÐ½Ð¾Ð³Ð¾Ñ€Ð°ÑƒÐ½Ð´Ð¾Ð²Ð°Ñ Ð¸Ð³Ñ€Ð° до поÑледнего выжившего. Побеждает команда, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¿ÐµÑ€Ð²Ð¾Ð¹ выиграет 3 раунда.\n^FFC83200Игроки: 5 на 5 *Сразу еÑÑ‚ÑŒ Титан\nЛимит времени: 3 минуты на раунд *Без возрождений\nРазмер команды: 5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "Битва Титанов «Эгида»"
+ "PL_aegis_titan_brawl_abbr" "ДТЭ"
+ "PL_aegis_titan_brawl_desc" "Убивайте вражеÑких Титанов. ДоÑтупны ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ð­Ð³Ð¸Ð´Ñ‹.\n^FFC83200Игроки: 5 на 5\nЛимит времени: 10 минут\nРазмер команды: 5"
+ "PL_aegis_titan_brawl_hint" "Убивайте вражеÑких Титанов.\nКатапультирование запрещено"
+ "PL_aegis_titan_brawl_lobby" "Лобби: Битва Титанов «Эгида»"
+ "PL_aitdm" "ИÑтребление"
+ "PL_aitdm_abbr" "ИСТ"
+ "PL_aitdm_desc" "Убить вÑех врагов.\n^FFC83200Игроки: 6 на 6 *ИИ\nВремÑ: 10 мин\nРазмер команды: 6"
+ "PL_aitdm_lobby" "Лобби: ИÑтребление"
+ "PL_all_grapple" "Ðтака на Titanfall"
+ "PL_all_grapple_abbr" "ИСТ"
+ "PL_all_grapple_desc" "КлаÑÑичеÑкое «ИÑтребление», где вÑе тактичеÑкие ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð·Ð°Ð¼ÐµÐ½ÐµÐ½Ñ‹ на крюк-кошку.\n^FFC83200Игроки: 6 на 6 * ИИ\nВремÑ: 10 мин\nРазмер команды: 6"
+ "PL_all_grapple_lobby" "Лобби «Ðтака на Titanfall»"
+ "PL_all_holopilot" "Ð’ÐµÐ»Ð¸ÐºÐ°Ñ Ñ€Ð°Ð·Ð²Ð¾Ð´ÐºÐ°"
+ "PL_all_holopilot_abbr" "ПС"
+ "PL_all_holopilot_desc" "КлаÑÑичеÑÐºÐ°Ñ Â«ÐŸÐµÑ€ÐµÑтрелка», где вÑе тактичеÑкие ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð·Ð°Ð¼ÐµÐ½ÐµÐ½Ñ‹ голопилотом.^FFC83200\nИгроки: 6 на 6 * Без Титанов\nВремÑ: 60 Ñ * Без возрождений\nРазмер команды: 6"
+ "PL_all_holopilot_lobby" "Лобби Â«Ð’ÐµÐ»Ð¸ÐºÐ°Ñ Ñ€Ð°Ð·Ð²Ð¾Ð´ÐºÐ°Â»"
+ "PL_all_phase" "Ð˜Ð½Ð°Ñ Ñторона"
+ "PL_all_phase_abbr" "ИСТ"
+ "PL_all_phase_desc" "КлаÑÑичеÑкое «ИÑтребление», где вÑе тактичеÑкие ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð·Ð°Ð¼ÐµÐ½ÐµÐ½Ñ‹ на фазовый Ñдвиг.\n^FFC83200Игроки: 6 на 6 * ИИ\nВремÑ: 10 мин\nРазмер команды: 6"
+ "PL_all_phase_lobby" "Лобби: Ð˜Ð½Ð°Ñ Ñторона"
+ "PL_all_spicy" "ОÑтрое иÑтребление"
+ "PL_all_spicy_abbr" "ИСТ"
+ "PL_all_spicy_desc" "КлаÑÑичеÑкое «ИÑтребление», где вÑе тактичеÑкие ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð·Ð°Ð¼ÐµÐ½ÐµÐ½Ñ‹ на клещи.\n^FFC83200Игроки: 6 на 6 * ИИ\nВремÑ: 10 мин\nРазмер команды: 6"
+ "PL_all_spicy_lobby" "Лобби: ОÑтрое иÑтребление"
+ "PL_amped_tacticals" "УÑÐ¸Ð»ÐµÐ½Ð½Ð°Ñ Ñ‚Ð°ÐºÑ‚Ð¸ÐºÐ°"
+ "PL_amped_tacticals_abbr" "ИСТ"
+ "PL_amped_tacticals_desc" "КлаÑÑичеÑкое «ИÑтребление», где вÑе тактичеÑкие ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð·Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾ уÑилены.\n^FFC83200Игроки: 6 на 6 * ИИ\nВремÑ: 10 мин\nРазмер команды: 6"
+ "PL_amped_tacticals_lobby" "Лобби «УÑÐ¸Ð»ÐµÐ½Ð½Ð°Ñ Ñ‚Ð°ÐºÑ‚Ð¸ÐºÐ°Â»"
+ "PL_ANGEL_CITY" "Город Ðнгелов 24/7"
+ "PL_angel_city_abbr" "ГÐ"
+ "PL_angel_city_desc" "Город Ðнгелов в трех режимах.\n^FFC83200*Охота за наживой *ИÑтребление\n*УÑиленные опорные пункты"
+ "PL_angel_city_lobby" "Лобби: Город Ðнгелов 24/7"
+ "PL_at_coop" "Побег (ÑовмеÑтно)"
+ "PL_at_coop_desc" "Ð’Ñ‹ Ñбежали из тюрьмы Ñ Ð¾Ð´Ð½Ð¸Ð¼ лишь пиÑтолетом. ПродержитеÑÑŒ до Ð¿Ñ€Ð¸Ð±Ñ‹Ñ‚Ð¸Ñ ÑпаÑательного транÑпорта. Убивайте врагов, чтобы заработать деньги на оружие. \n^FFC83200Игроки: 6\nРазмер команды: 6"
+ "PL_at_coop_lobby" "Лобби: Побег"
+ "PL_attrition" "Охота за наживой"
+ "PL_attrition_abbr" "ОÐ"
+ "PL_attrition_desc" "Зарабатывайте деньги, ÑƒÐ±Ð¸Ð²Ð°Ñ Ð²Ñ€Ð°Ð³Ð¾Ð². Заработайте больше, ÑÐ´Ð°Ð²Ð°Ñ Ð±Ð¾Ð½ÑƒÑ Ð² банк в указанных меÑтах.\n^FFC83200Игроки: 5 на 5 *ИИ\nЛимит времени: 10 мин\nРазмер команды: 5"
+ "PL_attrition_lobby" "Лобби: Охота за наживой"
+ "PL_capture_the_flag" "Захват флага"
+ "PL_capture_the_flag_abbr" "ЗФ"
+ "PL_capture_the_flag_desc" "Захватите вражеÑкий флаг и доÑтавьте его на Ñвою базу. Ðе дайте команде противника захватить ваш флаг!\n^FFC83200Игроки: 5 на 5\nЛимит времени: 12 мин\nРазмер команды: 5"
+ "PL_capture_the_flag_lobby" "Лобби: Захват флага"
+ "PL_coliseum" "Колизей"
+ "PL_coliseum_desc" "Поединок в «клетке» Ñ Ð¿Ð¾Ð²Ñ‹ÑˆÐµÐ½Ð½Ð¾Ð¹ мобильноÑтью. Чтобы выиграть раунд, убейте противника. Игрок, выигравший 3 из 5 раундов, получает подарок Ñторонника.^FFC83200\nИгроки: 1 на 1 *Без Титанов\nЛимит времени: 3 мин *Без возрождениÑ\n**^FFFFFF00ТребуетÑÑ Ð‘Ð˜Ð›Ð•Ð¢ или Ð’ÐЕСЕÐИЕ ПЛÐТЫ"
+ "PL_coliseum_lobby" "Лобби: Колизей"
+ "PL_colony" "«КолониÑ» 24/7"
+ "PL_colony_abbr" "КОЛ"
+ "PL_colony_desc" "«КолониÑ» круглоÑуточно. ^CCCCCC00ПоиÑк матчей в режимах ^FFC83200«ИÑтребление»^CCCCCC00, ^FFC83200«Пилоты против пилотов»^CCCCCC00 и ^FFC83200«ПоÑледний из Титанов»^CCCCCC00."
+ "PL_colony_lobby" "Лобби: «КолониÑ» 24/7"
+ "PL_ctf_lf" "ЗФ (Ðитро)"
+ "PL_ctf_lf_abbr" "ЗФ-Ð"
+ "PL_ctf_lf_desc" "БыÑÑ‚Ñ€Ð°Ñ Ð¸Ð³Ñ€Ð° в режиме «Захват флага» на картах режима «ПереÑтрелка».\n^FFC83200Игроки: 5 на 5\nРазмер команды: 5\n^F4D5A600Мгновенный возврат флага"
+ "PL_ctf_lf_lobby" "Лобби: ЗФ Ðитро"
+ "PL_default_description" "ОпиÑание по умолчанию"
+ "PL_default_lobbytitle" "Ðазвание лобби по умолчанию"
+ "PL_default_name" "Ðазвание по умолчанию"
+ "PL_don" "Ð’Ñе или ничего"
+ "PL_fd" "Оборона Фронтира"
+ "PL_fd_desc" "ОборонÑйтеÑÑŒ от волн наÑтупающих врагов"
+ "PL_fd_easy" "Оборона Фронтира: легко"
+ "PL_fd_easy_desc" "Сокрушите противника"
+ "PL_fd_easy_lobby" "Оборона Фронтира: лобби, легко"
+ "PL_fd_hard" "Оборона Фронтира: Ñложно"
+ "PL_fd_hard_desc" "ТребуетÑÑ Ñ…Ð¾Ñ€Ð¾ÑˆÐµÐµ умение игры"
+ "PL_fd_hard_lobby" "Оборона Фронтира: лобби, Ñ‚Ñжело"
+ "PL_fd_insane" "Оборона Фронтира: безумие"
+ "PL_fd_insane_desc" "Вам не выжить"
+ "PL_fd_insane_lobby" "Лобби: Оборона Фронтира - безумие"
+ "PL_fd_lobby" "Лобби: Оборона Фронтира"
+ "PL_fd_master" "Оборона Фронтира: маÑтер"
+ "PL_fd_master_desc" "ПреуÑпеют только лучшие из лучших"
+ "PL_fd_master_lobby" "Лобби: Оборона Фронтира - маÑтер"
+ "PL_fd_normal" "Оборона Фронтира: нормально"
+ "PL_fd_normal_desc" "РекомендуетÑÑ Ð´Ð»Ñ Ð¾Ð¿Ñ‹Ñ‚Ð½Ñ‹Ñ… игроков"
+ "PL_fd_normal_lobby" "Лобби: «Оборона Фронтира», нормально"
+ "PL_ffa" "Ð’Ñе против вÑех"
+ "PL_ffa_abbr" "ВПВ"
+ "PL_ffa_desc" "Каждый Ñам за ÑебÑ. Убейте вÑех пилотов.\n^FFC83200Игроки: 1 на 11\nЛимит времени: 10 мин\nРазмер команды: 1"
+ "PL_ffa_lobby" "Лобби: Ð’Ñе против вÑех"
+ "PL_fra" "Вольные пилоты"
+ "PL_fra_abbr" "ВПЛ"
+ "PL_fra_desc" "Каждый за ÑебÑ. Убивайте врагов, чтобы победить. Соберите 3 батареи, чтобы вызвать Титана.\n^FFC83200Игроки: 1 на 11\nЛимит времени: 15 мин\nРазмер команды: 1"
+ "PL_fra_lobby" "Лобби: Вольные пилоты"
+ "PL_groud_war_lobby" "Смешанный набор 8 на 8: лобби"
+ "PL_ground_war" "Смешанный набор 8 на 8"
+ "PL_ground_war_abbr" "8 на 8"
+ "PL_ground_war_desc" "Ð’ ÑоÑтаве: «Схватка» и «УÑиленные опорные пункты». Большое количеÑтво игроков на вÑех картах.\n^FFC83200Игроки: 8 на 8"
+ "PL_hardpoint" "УÑиленные опорные пункты"
+ "PL_hardpoint_abbr" "УОП"
+ "PL_hardpoint_desc" "Захватите и удерживайте опорные пункты, чтобы заработать очки. УÑиленные опорные пункты приноÑÑÑ‚ в 2 раза больше очков.\n^FFC83200Игроки: 6 на 6\nЛимит времени: 10 мин\nРазмер команды: 6"
+ "PL_hardpoint_lobby" "Лобби: УÑиленные опорные пункты"
+ "PL_hunted" "Добыча"
+ "PL_iron_last_titan_standing" "Железный ПИТ"
+ "PL_iron_last_titan_standing_abbr" "ЖЛЗÐ"
+ "PL_iron_last_titan_standing_desc" "Только Титаны, Ð¼Ð½Ð¾Ð³Ð¾Ñ€Ð°ÑƒÐ½Ð´Ð¾Ð²Ð°Ñ Ð¸Ð³Ñ€Ð° до поÑледнего выжившего. Побеждает игрок, который первым выиграет в 3 раундах.\n^FFC83200Игроки: 5 на 5 *БЕЗ ПИЛОТОВ\nЛимит времени: 3 мин на раунд *Без возрождений\nРазмер команды: 5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "ПоÑледний из Титанов"
+ "PL_last_titan_standing_abbr" "ПИТ"
+ "PL_last_titan_standing_desc" "Ð’ Ñтой многораундовой игре на выбывание у каждого игрока Ñразу еÑÑ‚ÑŒ Титан. Побеждает команда, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¿ÐµÑ€Ð²Ð¾Ð¹ выиграет 3 раунда.\n^FFC83200Игроки: 5 на 5 *Сразу еÑÑ‚ÑŒ Титан\nЛимит времени: 3 минуты на раунд *Без возрождений\nРазмер команды: 5"
+ "PL_last_titan_standing_lobby" "Лобби: ПоÑледний из Титанов"
+ "PL_limited_time_mode" "(временно)"
+ "PL_live_fire" "ПереÑтрелка"
+ "PL_live_fire_abbr" "ПЕР"
+ "PL_live_fire_desc" "Динамичные бои на арене. Ð”Ð»Ñ Ð¿Ð¾Ð±ÐµÐ´Ñ‹ необходимо перебить вÑех вражеÑких пилотов или удерживать флаг на момент иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ‚Ð°Ð¹Ð¼ÐµÑ€Ð°.^FFC83200\nИгроки: 6 на 6 * Без Титанов\nВремÑ: 60 Ñ * Без возрождений\nРазмер команды: 6"
+ "PL_live_fire_lobby" "Лобби: ПереÑтрелка"
+ "PL_load_a_map_on_the_command_line" "Загрузка карты из командной Ñтроки -devonly"
+ "PL_marked_for_death" "Приговор — Ñмерть"
+ "PL_marked_for_death_abbr" "П-С"
+ "PL_marked_for_death_desc" "Убивайте или защищайте приговоренные цели.\n^FFC83200Игроки: 6 на 6\nВремÑ: 12 мин\nРазмер команды: 6"
+ "PL_marked_for_death_lobby" "Лобби: Приговор — Ñмерть"
+ "PL_nitro_ffa" "ВПВ (Ðитро)"
+ "PL_nitro_ffa_abbr" "ВПВ-Ð"
+ "PL_nitro_ffa_desc" "Стремительные битвы «ВÑе против вÑех» на избранных картах.\n^FFC83200Игроки: 6\n^F4D5A600Без Титанов\nБез уÑилений"
+ "PL_nitro_ffa_lobby" "ВПВ (Ðитро): лобби"
+ "PL_nitro_mixtape" "Смешанный набор (Ðитро)"
+ "PL_nitro_mixtape_abbr" "СÐ-Ð"
+ "PL_nitro_mixtape_desc" "Стремительные битвы ЗФ, П-С и PvP на избранных картах.\n^FFC83200Игроки: 5 на 5\nРазмер команды: 5\n^F4D5A600Мгновенный возврат флага"
+ "PL_nitro_mixtape_lobby" "Смешанный набор: лобби"
+ "PL_pilot_hunter" "Схватка"
+ "PL_pilot_hunter_abbr" "СХВ"
+ "PL_pilot_hunter_desc" "Убивайте вражеÑких пилотов и Титанов. \n^FFC83200Игроки: 8 на 8\nЛимит времени: 10 мин\nРазмер команды: 8"
+ "PL_pilot_hunter_lobby" "Лобби (Схватка)"
+ "PL_pilot_skirmish" "Пилоты против пилотов"
+ "PL_pilot_skirmish_abbr" "ППП"
+ "PL_pilot_skirmish_desc" "Убивайте вражеÑких пилотов. Ð’Ñ‹Ñадка Титана не разрешена.\n^FFC83200Игроки: 8 на 8 *Без Титанов\nЛимит времени: 10 мин\nРазмер команды: 8"
+ "PL_pilot_skirmish_lobby" "Лобби: Пилоты против пилотов"
+ "PL_pl_rebuild_all_paths" "ВоÑÑтановить вÑе пути"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "ЗапуÑк Ñ ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "ЧаÑтный матч (бета)"
+ "PL_private_match_desc" "Проведите чаÑтный матч на карте и в режиме по Ñвоему выбору.\n^FFC83200ПРИГЛÐШÐЙТЕ СЕТЬ или ПРИГЛÐШÐЙТЕ ДРУЗЕЙ на игру\n^FFC83200Игроки: 1-16\nБез прогреÑÑа"
+ "PL_private_match_lobby" "Лобби чаÑтного матча (бета)"
+ "PL_promo_coop" "СовмеÑÑ‚Ð½Ð°Ñ Ð¸Ð³Ñ€Ð° на 4 игроков"
+ "PL_raid" "Ðалет"
+ "PL_rise" "«ВоÑход» 24/7"
+ "PL_rise_abbr" "ВСХ"
+ "PL_rise_desc" "«ВоÑход» 24/7. ^CCCCCC00Ðачинает поиÑк матчей в режимах ^FFC83200ЗФ^CCCCCC00, ^FFC83200«УÑиленные опорные пункты»^CCCCCC00, ^FFC83200«Пилоты против пилотов»^CCCCCC00, ^FFC83200«ПереÑтрелка»^CCCCCC00 и ^FFC83200«ПоÑледний из Титанов»^CCCCCC00."
+ "PL_rise_lobby" "Лобби: «ВоÑход» 24/7"
+ "PL_rocket_arena" "Ð Ð°ÐºÐµÑ‚Ð½Ð°Ñ Ð°Ñ€ÐµÐ½Ð°"
+ "PL_rocket_arena_abbr" "ПС"
+ "PL_rocket_arena_desc" "КлаÑÑичеÑÐºÐ°Ñ Â«ÐŸÐµÑ€ÐµÑтрелка» Ñ модифицированными ЭПУ.^FFC83200\nИгроки: 6 на 6 * Без Титанов\nВремÑ: 90 Ñ * Без возрождений\nРазмер команды: 6"
+ "PL_rocket_arena_lobby" "Лобби: Ð Ð°ÐºÐµÑ‚Ð½Ð°Ñ Ð°Ñ€ÐµÐ½Ð°"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "ÐаÑтройки на Ñлучай загрузки карты из командной Ñтроки. Ðе редактируйте cmdlineMapLoad 1 ниже (Ð±Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ Ñтому вÑе и работает)."
+ "PL_speedball" "ПереÑтрелка"
+ "PL_speedball_desc" "Сражение идет за владение нейтральным флагом. Ð”Ð»Ñ Ð¿Ð¾Ð±ÐµÐ´Ñ‹ в раунде необходимо уничтожить вÑех вражеÑких игроков или захватить флаг до Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð¾Ñ‚Ñчета. Побеждает команда, первой доÑÑ‚Ð¸Ð³ÑˆÐ°Ñ Ð¿Ñти побед.\n^FFC83200Игроков: 6 на 6 *Без Титанов\nОграничение по времени: 60 Ñекунд на раунд *Без возрождений\nРазмер команды: 6"
+ "PL_speedball_lobby" "Лобби: ПереÑтрелка"
+ "PL_tactikill" "ТактичеÑкое иÑтребление"
+ "PL_tactikill_abbr" "ИСТ"
+ "PL_tactikill_desc" "КлаÑÑичеÑкое «ИÑтребление», где убийÑтва полноÑтью воÑÑтанавливают тактичеÑкие ÑпоÑобноÑти.\n^FFC83200Игроки: 6 на 6 * ИИ\nВремÑ: 10 мин\nРазмер команды: 6"
+ "PL_tactikill_lobby" "Лобби «ТактичеÑкое иÑтребление»"
+ "PL_titan_brawl" "Битва Титанов"
+ "PL_titan_brawl_abbr" "БТ"
+ "PL_titan_brawl_desc" "Убивайте вражеÑких Титанов. Пилоты в бою не учаÑтвуют.\n^FFC83200Игроки: 5 на 5\nЛимит времени: 10 мин\nРазмер команды: 5"
+ "PL_titan_brawl_hint" "Убивайте вражеÑких Титанов.\nКатапультирование запрещено"
+ "PL_titan_brawl_lobby" "Лобби: Битва Титанов"
+ "PL_titan_brawl_turbo" "Турбодрака Титанов"
+ "PL_titan_brawl_turbo_abbr" "КМÐБ"
+ "PL_titan_brawl_turbo_desc" "КлаÑÑичеÑÐºÐ°Ñ Â«Ð”Ñ€Ð°ÐºÐ° Титанов» Ñ уÑкоренными перезарÑдкой рывка и зарÑдом Ñдра.\n^FFC83200Игроки: 5 на 5\nВремÑ: 10 мин.\nРазмер команды: 5"
+ "PL_titan_brawl_turbo_hint" "Убивайте вражеÑких Титанов.\nКатапультирование запрещено"
+ "PL_titan_brawl_turbo_lobby" "Лобби: Турбодрака Титанов"
+ "PL_turbo_last_titan_standing" "Турбо-ПИТ"
+ "PL_turbo_last_titan_standing_abbr" "ПИТ"
+ "PL_turbo_last_titan_standing_desc" "КлаÑÑичеÑкий «ПоÑледний из Титанов» Ñ уÑкоренными перезарÑдкой рывка и зарÑдом Ñдра.\n^FFC83200Игроки: 5 на 5 * Сразу еÑÑ‚ÑŒ Титан\nВремÑ: 3 минуты на раунд * Без возрождений\nРазмер команды: 5"
+ "PL_turbo_last_titan_standing_lobby" "Лобби: Турбо-ПИТ"
+ "PL_variety_pack" "Смешанный набор"
+ "PL_variety_pack_desc" "Разное количеÑтво игроков и разнообразные карты. Режимы:\n^FFC83200*Охота за наживой *ИÑтребление\n*ПоÑледний из Титанов *УÑиленные опорные пункты\n*Пилоты против пилотов *Захват флага"
+ "PL_variety_pack_lobby" "Смешанный набор: лобби"
+ "PL_wargames" "«Военные игры» 24/7"
+ "PL_wargames_abbr" "Ð’ÐИ"
+ "PL_wargames_desc" "КруглоÑуточные «Военные игры». ^CCCCCC00Ðачинает поиÑк матчей в режимах ^FFC83200«ИÑтребление»^CCCCCC00, ^FFC83200«ЗФ»^CCCCCC00, ^FFC83200«Пилоты против пилотов»^CCCCCC00, ^FFC83200«УÑиленные опорные пункты»^CCCCCC00 и ^FFC83200«ПоÑледний из Титанов»^CCCCCC00."
+ "PL_wargames_lobby" "Лобби: «Военные игры» 24/7"
+ "WATCH_TUTORIAL" "СМОТРЕТЬ ОБУЧЕÐИЕ"
+ }
+ }
+ "lang"
+ {
+ "Language" "spanish"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "Tu aventura por los espectaculares paisajes de la Frontera continúa con el nuevo DLC de Titanfall 2: Postales de la Frontera. Con ubicaciones nuevas y familiares, así como una nueva colección de pinturas de guerra de armas de élite, la Frontera ahora es más espectacular que nunca.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_00_Q" "Tráiler de juego de «Postales de la Frontera»"
+ "COMMUNITYUPDATE_01_A" "Descubre todas las novedades del parche Postales de la Frontera.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_01_Q" "Postales de la Frontera: notas de parche"
+ "COMMUNITYUPDATE_02_A" "Disponible muy pronto para dispositivos móviles, Titanfall: Assault es un juego de ETR en el universo de Titanfall creado junto a Particle City. ¡Puedes verlo en acción aquí! \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_02_Q" "Tráiler «Lanzamiento» de Titanfall: Assault"
+ "COMMUNITYUPDATE_03_A" "Iniquity nos guía por el tutorial y nos da consejos básicos para Titanfall Assault, la forma perfecta de iniciarse en este juego.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_03_Q" "Titanfall Assault: vídeo «Fundamentos»"
+ "COMMUNITYUPDATE_04_A" "¡Escucha a algunos de los desarrolladores claves de Defensa fronteriza hablar de la historia y creación del modo, mientras jugamos unas rondas. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_04_Q" "Respawn Plays: Defensa fronteriza"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger ha creado un divertido montaje con muchas jugadas espectaculares con el filo de pulso. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_05_Q" "Creaciones de la comunidad: Incisivo"
+ "COMMUNITYUPDATE_06_A" "ConzeyG en reddit demuestra una eficacia brutal con el Northstar eliminando pilotos en Condenado. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_06_Q" "Creaciones de la comunidad: Cazapilotos Northstar"
+ "COMMUNITYUPDATE_07_A" "¡Ya disponible! Vuelve Defensa fronteriza junto con Ascenso, y nuevas pinturas a la venta y un nuevo mapa de Munición real. Puedes verlo en acción aquí.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_07_Q" "Tráiler de juego de «Op. Escudo fronterizo»"
+ "COMMUNITYUPDATE_08_A" "¡Ya disponible! El icónico mapa Juegos de guerra vuelve a Titanfall 2, con mejores gráficos que nunca. Echa también un vistazo a la nueva ejecución y al nuevo mapa de Munición real.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_08_Q" "Tráiler de juego de «Juegos de guerra»"
+ "COMMUNITYUPDATE_09_A" "Este DLC marca la llegada del 7.º titán de Multijugador, el Monarch; un mapa remasterizado, Reliquia, y una nueva ejecución, así como artículos adquiribles como los nuevos titanes prime Ronin y Tone, más camuflajes, indicativos y decoraciones. Disfruta de la acción aquí.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para ver."
+ "COMMUNITYUPDATE_09_Q" "Tráiler de juego de «Reino del Monarch»"
+ "COMMUNITYUPDATE_10_A" "En la Frontera no todo es lo que parece. Prepárate para el nuevo DLC gratuito de Titanfall 2, Un glitch en la Frontera, con el nuevo mapa «Glitch». Inspirado en el planeta natal del capitán Lastimosa, Armonía, aquí encontrarás un paisaje dominado por desniveles verticales y rutas largas y serpenteantes, ideal para encadenar carreras por muros y así cruzar ágilmente el mapa. Y, además, un nuevo mapa de Munición real, «Cubierta», con espacios constreñidos, patios expuestos y drones que acechan patrullando desde arriba. Y si te sientes abrumado(a), los siempre serviciales marvins han llegado como una nueva facción para echarte una mano (robótica). Finalmente, ya se puede desbloquear la nueva ejecución con el filo de pulso, para cuando quieras ponerte «incisivo(a)».\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_10_Q" "Tráiler de juego «Un glitch en la Frontera»"
+ "COMMUNITYUPDATE_11_A" "Vuelve uno de los mapas multijugador favoritos del Titanfall original: el icónico Colonia. Añádele a esto las nuevas tácticas y titanes de Titanfall 2, y los pilotos y titanes tendrán que exprimir todas sus habilidades para sobrevivir en este asentamiento idílico y denso lleno de callejones, esquinas traicioneras y tejados expuestos. Disponible el 30 de marzo para todos los jugadores. El pack de DLC «Colony Reborn» incluye el retorno de armas clásicas como el rifle de asalto decorado R-101, una nueva ejecución con el garfio y nuevas opciones cosméticas a la venta para destacar en combate mientras arrasas a tus enemigos.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_11_Q" "Tráiler de juego «Colony Reborn»"
+ "COMMUNITYUPDATE_12_A" "Presentamos Munición real, un frenético modo 6 contra 6 solo para pilotos centrado en combates competitivos a corta distancia. Con dos nuevos mapas diseñados específicamente para este modo: Chimeneas y Prado. Estos dos mapas gratuitos son zonas de combate letales y constreñidas creadas específicamente para este modo frenético e intenso.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_12_Q" "Tráiler de juego «Bienvenidos a Munición real»"
+ "COMMUNITYUPDATE_13_A" "Disfruta de la versión remasterizada de uno de los mapas favoritos de los fans del Titanfall original: `1Angel City`0. Prepárate para el primer DLC gratis para Titanfall 2 con `1Más buscados de Angel City`0, disponible el 1 de diciembre de 2016 para todos los jugadores. Incluye nuevas opciones cosméticas para darle un toque fresco a la Frontera. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_13_Q" "Tráiler de juego «Bienvenidos a Angel City»"
+ "COMMUNITYUPDATE_14_A" "Bienvenidos.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_14_Q" "Tráiler de premios «Bis»"
+ "COMMUNITYUPDATE_15_A" "Dos leyendas, un legado.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_15_Q" "Tráiler Un jugador «Sed uno en combate»"
+ "COMMUNITYUPDATE_16_A" "Sin límites.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_16_Q" "Tráiler de juego Multijugador «Pilotos»"
+ "COMMUNITYUPDATE_DESC" "`3Noticias de comunidad de Titanfall`0\n\nInformación y enlaces en la red.\n\nPara más información:\n`2%$rui/bullet_point%`0Síguenos en Twitter `1@Respawn`0\n`2%$rui/bullet_point%`0Hazte fan en `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0Únete en `1www.respawn.com`0"
+ "COMMUNITYUPDATE_NAME" "Comunidad"
+ "KNB_SUBJECT_00_DESC" "`3¿Qué novedades trae Titanfall?`0\n\n¡Consulta aquí qué ha cambiado en Titanfall 2!"
+ "KNB_SUBJECT_00_NAME" "Actualizaciones"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 nuevas banderas de indicativos creadas por la comunidad\n\n`2%$rui/bullet_point%`0Todos los regalos de partidario ahora pueden adquirirse con créditos\n\n`2%$rui/bullet_point%`0Nuevo contenido adquirible en la tienda\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 28 de noviembre: Cosecha"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Espacio principal de pistola\n\n`2%$rui/bullet_point%`0Banderas de Halloween\n\n`2%$rui/bullet_point%`0Cambios de equilibrio\n\n`2%$rui/bullet_point%`0Nuevo contenido adquirible en la tienda\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 31 de octubre: Trucos o golosinas"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0Defensa fronteriza: - Compatibilidad para 3 mapas más - Dique seco, Angel City y Exoplaneta.\n\n`2%$rui/bullet_point%`0Nuevo mapa de Munición real - UMA\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto - Agujero en el muro\n\n`2%$rui/bullet_point%`0Nuevo contenido adquirible en la tienda"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 29 de agosto - Postales de la Frontera"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Defensa fronteriza: un nuevo modo cooperativo donde unes fuerzas con hasta 3 jugadores más para defender un objetivo crucial de oleadas cada vez más intensas de combatientes de IA. La comunicación y la adaptabilidad son claves para sobrevivir.\n\n`2%$rui/bullet_point%`0Mapa nuevo: Ascenso\n\n`2%$rui/bullet_point%`0Mapa nuevo de Munición real: Municipio\n\n`2%$rui/bullet_point%`0Nuevo contenido adquirible en la tienda"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 25 de julio: operación Escudo fronterizo"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0Nuevo mapa: Juegos de guerra\n\n`2%$rui/bullet_point%`0Nuevo mapa de Munición real: Tráfico\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Lucha con tu sombra\n\n`2%$rui/bullet_point%`03.er espacio de arma\n\n`2%$rui/bullet_point%`0Ajustes de partida privada"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 27 de junio: Juegos de guerra"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0Nuevo titán: Monarch\n\n`2%$rui/bullet_point%`0Nuevo mapa: Reliquia\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Ahora me ves\n\n`2%$rui/bullet_point%`0Nuevo contenido adquirible en la tienda"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 30 de mayo: Reino del Monarch"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0Nuevo mapa: Glitch\n\n`2%$rui/bullet_point%`0Nuevo mapa de Munición real: Cubierta\n\n`2%$rui/bullet_point%`0Nueva facción: Marvins\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Incisivo\n\n`2%$rui/bullet_point%`0Generación máxima aumentada a 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 25 de abril: Un glitch en la Frontera"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0Nuevo mapa: Colonia\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Aplastacráneos\n\n`2%$rui/bullet_point%`0Nueva arma: R-101\n\n`2%$rui/bullet_point%`0Nuevo contenido adquirible en la tienda"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 30 de marzo: Colony Reborn"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0Munición real: un nuevo modo de juego de eliminación de pilotos contra pilotos. Modo 6 contra 6 sin reaparición, donde tienes un minuto para eliminar al equipo rival para ganar la ronda. También ganas la ronda si tu equipo conserva la bandera neutral cuando el tiempo se agote. Vence el primero en ganar 5 rondas.\n\n`2%$rui/bullet_point%`0Nuevo mapa de Munición real: Prado\n\n`2%$rui/bullet_point%`0Nuevo mapa de Munición real: Chimeneas\n\n`2%$rui/bullet_point%`0Nueva ejecución: Impacto tardío"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 23 de febrero: Munición real"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0Nuevo mapa: Angel City\n\n`2%$rui/bullet_point%`0Nueva arma: B3 Wingman de élite\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Destruido desde dentro\n\n`2%$rui/bullet_point%`0Nuevos kits de titán\n\n`2%$rui/bullet_point%`0Nuevo contenido adquirible en la tienda"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 30 de noviembre: Más buscados de Angel City"
+ "MP_ANGEL_CITY_FD_WAVE_1" "Encadenado al cielo"
+ "MP_ANGEL_CITY_FD_WAVE_2" "Amorfo"
+ "MP_ANGEL_CITY_FD_WAVE_3" "Insurgencia"
+ "MP_ANGEL_CITY_FD_WAVE_4" "Grupo obstinado"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Jugarse el cuello"
+ "MP_DRYDOCK_FD_WAVE_1" "Nadar en aguas extrañas"
+ "MP_DRYDOCK_FD_WAVE_2" "¡No tienen balas!"
+ "MP_DRYDOCK_FD_WAVE_3" "Cegados por la ciencia"
+ "MP_DRYDOCK_FD_WAVE_4" "Sistema de altas presiones"
+ "MP_DRYDOCK_FD_WAVE_5" "Ojo de la tormenta"
+ "MP_THAW_FD_WAVE_1" "Conciencia de situación"
+ "MP_THAW_FD_WAVE_2" "Superioridad numérica"
+ "MP_THAW_FD_WAVE_3" "Secuaz 21"
+ "MP_THAW_FD_WAVE_4" "Diversión en el fuego"
+ "MP_THAW_FD_WAVE_5" "Aprovecha las armas"
+ "NO_PRICE" "Promoción expirada"
+ "NO_PRICE_TWO_LINES" "Promoción\nexpirada"
+ "PL_aegis_last_titan_standing" "UTEP Aegis"
+ "PL_aegis_last_titan_standing_abbr" "UTEPA"
+ "PL_aegis_last_titan_standing_desc" "Mejoras de Aegis activadas. Modo de eliminación por rondas. Vence el primero en ganar 3 rondas.\n^FFC83200Jugadores: 5 contra 5 *Empiezas como titán\nLímite de tiempo: 3 min por ronda *Sin reaparición\nTamaño máximo de grupo: 5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "Batalla de titanes Aegis"
+ "PL_aegis_titan_brawl_abbr" "BDTA"
+ "PL_aegis_titan_brawl_desc" "Elimina titanes enemigos. Mejoras de Aegis activadas.\n^FFC83200Jugadores: 5 contra 5\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 5"
+ "PL_aegis_titan_brawl_hint" "Elimina titanes enemigos.\nSin eyección"
+ "PL_aegis_titan_brawl_lobby" "Vestíbulo de Batalla de titanes Aegis"
+ "PL_aitdm" "Desgaste"
+ "PL_aitdm_abbr" "DGT"
+ "PL_aitdm_desc" "Elimina a todos los enemigos.\n^FFC83200Jugadores: 6 contra 6 *IA\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 6"
+ "PL_aitdm_lobby" "Vestíbulo de Desgaste"
+ "PL_all_grapple" "Ataque en Titanfall"
+ "PL_all_grapple_abbr" "DGT"
+ "PL_all_grapple_desc" "Reglas de Desgaste clásico, excepto que todas las funciones tácticas se reemplazan por el garfio.\n^FFC83200Jugadores: 6 contra 6 *IA\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 6"
+ "PL_all_grapple_lobby" "Vestíbulo de Ataque en Titanfall"
+ "PL_all_holopilot" "El gran engaño"
+ "PL_all_holopilot_abbr" "MR"
+ "PL_all_holopilot_desc" "Reglas de Munición real clásico, excepto que todas las funciones tácticas se reemplazan por holopiloto.^FFC83200\nJugadores: 6 contra 6 *Sin titanes\nLímite de tiempo: 60 s *Sin reapariciones\nTamaño máximo de grupo: 6"
+ "PL_all_holopilot_lobby" "Vestíbulo de El gran engaño"
+ "PL_all_phase" "El otro lado"
+ "PL_all_phase_abbr" "DGT"
+ "PL_all_phase_desc" "Reglas de Desgaste clásico, excepto que todas las funciones tácticas se reemplazan por Fase.\n^FFC83200Jugadores: 6 contra 6 *IA\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 6"
+ "PL_all_phase_lobby" "Vestíbulo de El otro lado"
+ "PL_all_spicy" "Desgaste animado"
+ "PL_all_spicy_abbr" "DGT"
+ "PL_all_spicy_desc" "Reglas de Desgaste clásico, excepto que todas las funciones tácticas se reemplazan con garrapatas.\n^FFC83200Jugadores: 6 contra 6 *IA\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 6"
+ "PL_all_spicy_lobby" "Vestíbulo de Desgaste animado"
+ "PL_amped_tacticals" "Tácticas +"
+ "PL_amped_tacticals_abbr" "DGT"
+ "PL_amped_tacticals_desc" "Reglas de Desgaste clásico, excepto que las funciones tácticas son más potentes.\n^FFC83200Jugadores: 6 contra 6 *IA\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 6"
+ "PL_amped_tacticals_lobby" "Vestíbulo de Tácticas +"
+ "PL_ANGEL_CITY" "Angel City 24/7"
+ "PL_angel_city_abbr" "AC"
+ "PL_angel_city_desc" "Solo Angel City, todo el tiempo.\n^FFC83200*Cazarrecompensas *Desgaste\n*Fortines Cargados"
+ "PL_angel_city_lobby" "Vestíbulo de Angel City 24/7"
+ "PL_at_coop" "Evasión (coop.)"
+ "PL_at_coop_desc" "Acabas de escapar de una prisión con solo una pistola. Sobrevive hasta que llegue un transporte. Elimina enemigos para ganar dinero y comprar armas. \n^FFC83200Jugadores: 6\nTamaño máximo de grupo: 6"
+ "PL_at_coop_lobby" "Vestíbulo de Evasión"
+ "PL_attrition" "Cazarrecompensas"
+ "PL_attrition_abbr" "CR"
+ "PL_attrition_desc" "Elimina enemigos para ganar dinero. Consigue más ingresando tus bonificaciones en el banco, en los lugares designados.\n^FFC83200Jugadores: 5 contra 5 *IA\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 5"
+ "PL_attrition_lobby" "Vestíbulo de Cazarrecompensas"
+ "PL_capture_the_flag" "Captura la bandera"
+ "PL_capture_the_flag_abbr" "CLB"
+ "PL_capture_the_flag_desc" "¡Hazte con la bandera enemiga y llévala hasta tu base, a la vez que evitas que el enemigo te robe la tuya!\n^FFC83200Jugadores: 5 contra 5\nLímite de tiempo: 12 min\nTamaño máximo de grupo: 5"
+ "PL_capture_the_flag_lobby" "Vestíbulo de CLB"
+ "PL_coliseum" "Coliseo"
+ "PL_coliseum_desc" "Combate 1 contra 1 con movilidad mejorada en una jaula. Elimina a tu rival para ganar una ronda. El vencedor de 3 de 5 gana un regalo de partidario de recompensa.^FFC83200\nJugadores: 1 contra 1 *Sin titanes\nLímite de tiempo: 3 min *Sin reapariciones\n**^FFFFFF00REQUIERE TICKETS DE COLISEO o PAGAR ENTRADA"
+ "PL_coliseum_lobby" "Vestíbulo de Coliseo"
+ "PL_colony" "Colonia 24/7"
+ "PL_colony_abbr" "COL"
+ "PL_colony_desc" "Mapa Colonia, continuamente. ^CCCCCC00Buscará partidas de ^FFC83200Desgaste^CCCCCC00, ^FFC83200Pilotos contra pilotos^CCCCCC00 y ^FFC83200Último titán en pie^CCCCCC00."
+ "PL_colony_lobby" "Vestíbulo de Colonia 24/7"
+ "PL_ctf_lf" "CLB (Nitro)"
+ "PL_ctf_lf_abbr" "CLB-N"
+ "PL_ctf_lf_desc" "Frenético Captura la bandera en mapas seleccionados.\n^FFC83200Jugadores: 5 contra 5\nTamaño máximo de grupo: 5\n^F4D5A600Recuperación instantánea de banderas"
+ "PL_ctf_lf_lobby" "Vestíbulo CLB Nitro"
+ "PL_default_description" "Descripción predeterminada"
+ "PL_default_lobbytitle" "Título de vestíbulo por defecto"
+ "PL_default_name" "Nombre predeterminado"
+ "PL_don" "Doble o nada"
+ "PL_fd" "Defensa fronteriza"
+ "PL_fd_desc" "Defiende contra las oleadas de las fuerzas de la Flota Remanente"
+ "PL_fd_easy" "Defensa fronteriza: fácil"
+ "PL_fd_easy_desc" "Aplasta a los rivales."
+ "PL_fd_easy_lobby" "Defensa fronteriza: vestíbulo fácil"
+ "PL_fd_hard" "Defensa fronteriza: difícil"
+ "PL_fd_hard_desc" "Se requiere mucha habilidad"
+ "PL_fd_hard_lobby" "Defensa fronteriza: vestíbulo difícil"
+ "PL_fd_insane" "Defensa fronteriza: demencial"
+ "PL_fd_insane_desc" "No sobrevivirás"
+ "PL_fd_insane_lobby" "Vestíbulo de Defensa fronteriza: demencial"
+ "PL_fd_lobby" "Vestíbulo de Defensa fronteriza"
+ "PL_fd_master" "Defensa fronteriza: maestro"
+ "PL_fd_master_desc" "Solo los mejores triunfarán"
+ "PL_fd_master_lobby" "Vestíbulo de Defensa fronteriza: maestro"
+ "PL_fd_normal" "Defensa fronteriza: normal"
+ "PL_fd_normal_desc" "Recomendado para jugadores experimentados"
+ "PL_fd_normal_lobby" "Vestíbulo de Defensa fronteriza: normal"
+ "PL_ffa" "Batalla campal"
+ "PL_ffa_abbr" "BC"
+ "PL_ffa_desc" "Cada piloto por su cuenta; elimina a todos los enemigos.\n^FFC83200Jugadores: 1 contra 11\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 1"
+ "PL_ffa_lobby" "Vestíbulo de Batalla campal"
+ "PL_fra" "Agentes libres"
+ "PL_fra_abbr" "AL"
+ "PL_fra_desc" "Cada piloto, por su cuenta. Elimina enemigos para ganar. Reúne 3 baterías para pedir un titán.\n^FFC83200Jugadores: 1 contra 11\nLímite de tiempo: 15 min\nTamaño máximo de grupo: 1"
+ "PL_fra_lobby" "Vestíbulo de Agentes libres"
+ "PL_groud_war_lobby" "Vestíbulo de Variado 8c8"
+ "PL_ground_war" "Variado 8c8"
+ "PL_ground_war_abbr" "8c8"
+ "PL_ground_war_desc" "Incluye Escaramuza y Fortines cargados, con gran número de pilotos en todos los mapas.\n^FFC83200Jugadores: 8 contra 8"
+ "PL_hardpoint" "Fortines cargados"
+ "PL_hardpoint_abbr" "FC"
+ "PL_hardpoint_desc" "Captura y conserva los fortines para ganar puntos. Los fortines cargados proporcionan el doble de puntos.\n^FFC83200Jugadores: 6 contra 6\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 6"
+ "PL_hardpoint_lobby" "Vestíbulo de Fortines cargados"
+ "PL_hunted" "Cazado"
+ "PL_iron_last_titan_standing" "UTEP de acero"
+ "PL_iron_last_titan_standing_abbr" "UTEPA"
+ "PL_iron_last_titan_standing_desc" "Modo de eliminación por rondas, solo titanes. Vence el primero en ganar 3 rondas.\n^FFC83200Jugadores: 5 contra 5 *SIN PILOTOS\nLímite de tiempo: 3 min por ronda *Sin reaparición\nTamaño máximo de grupo: 5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "Último titán en pie"
+ "PL_last_titan_standing_abbr" "UTEP"
+ "PL_last_titan_standing_desc" "En esta partida de eliminación por rondas, todos los jugadores empiezan en un titán. Gana el primero en vencer en 3 rondas.\n^FFC83200Jugadores: 5 contra 5 *Empiezas como titán\nLímite de tiempo: 3 min por ronda *Sin reapariciones\nTamaño máximo de grupo: 5"
+ "PL_last_titan_standing_lobby" "Vestíbulo de UTEP"
+ "PL_limited_time_mode" "Por tiempo limitado"
+ "PL_live_fire" "Munición real"
+ "PL_live_fire_abbr" "MR"
+ "PL_live_fire_desc" "Combate frenético en una arena de munición real. Gana la ronda eliminando a todos los pilotos enemigos o conservando la bandera cuando se agote el tiempo.^FFC83200\nJugadores: 6 contra 6 *Sin titanes\nLímite de tiempo: 60 s *Sin reapariciones\nTamaño máximo de grupo: 6"
+ "PL_live_fire_lobby" "Vestíbulo de Munición real"
+ "PL_load_a_map_on_the_command_line" "Carga un mapa en la línea de comandos. Solo para desarrollo."
+ "PL_marked_for_death" "Condenado"
+ "PL_marked_for_death_abbr" "CON"
+ "PL_marked_for_death_desc" "Elimina o protege los blancos marcados.\n^FFC83200Jugadores: 6 contra 6 \nLímite de tiempo: 12 min\nTamaño máximo de grupo: 6"
+ "PL_marked_for_death_lobby" "Vestíbulo de Condenado"
+ "PL_nitro_ffa" "BC (Nitro)"
+ "PL_nitro_ffa_abbr" "BC-N"
+ "PL_nitro_ffa_desc" "Frenética Batalla campal en mapas seleccionados.\n^FFC83200Jugadores: 6\n^F4D5A600Sin titanes\nSin potenciadores"
+ "PL_nitro_ffa_lobby" "Vestíbulo de BC (Nitro)"
+ "PL_nitro_mixtape" "Variado (Nitro)"
+ "PL_nitro_mixtape_abbr" "VRD-N"
+ "PL_nitro_mixtape_desc" "Frenéticos CLB, CON y JcJ en mapas seleccionados.\n^FFC83200Jugadores: 5 contra 5\nTamaño máximo de grupo: 5\n^F4D5A600Recuperación instantánea de banderas"
+ "PL_nitro_mixtape_lobby" "Vestíbulo de Variado"
+ "PL_pilot_hunter" "Escaramuza"
+ "PL_pilot_hunter_abbr" "ECM"
+ "PL_pilot_hunter_desc" "Elimina pilotos y titanes enemigos. \n^FFC83200Jugadores: 8 contra 8\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 8"
+ "PL_pilot_hunter_lobby" "Vestíbulo de Escaramuza"
+ "PL_pilot_skirmish" "Pilotos contra pilotos"
+ "PL_pilot_skirmish_abbr" "PcP"
+ "PL_pilot_skirmish_desc" "Elimina pilotos enemigos. No se permiten titanes.\n^FFC83200Jugadores: 8 contra 8 *Sin titanes\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 8"
+ "PL_pilot_skirmish_lobby" "Vestíbulo de PcP"
+ "PL_pl_rebuild_all_paths" "Reconstruir todas las rutas"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "Ejecutar con ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "Partida privada (beta)"
+ "PL_private_match_desc" "Juega una partida privada personalizada en el modo y mapa que prefieras.\n^FFC83200Invitar red o Invitar amigos para jugar\n^FFC83200Jugadores: 1-16\nSin progresión"
+ "PL_private_match_lobby" "Vestíbulo de partida privada (beta)"
+ "PL_promo_coop" "Coop. de 4 jugadores"
+ "PL_raid" "Incursión"
+ "PL_rise" "Ascenso 24/7"
+ "PL_rise_abbr" "ASC"
+ "PL_rise_desc" "Mapa Ascenso, continuamente. ^CCCCCC00Buscará partidas de ^FFC83200CLB^CCCCCC00, ^FFC83200Fortines cargados^CCCCCC00, ^FFC83200Pilotos contra pilotos^CCCCCC00, ^FFC83200Munición real^CCCCCC00 y ^FFC83200Último titán en pie^CCCCCC00."
+ "PL_rise_lobby" "Vestíbulo de Ascenso 24/7"
+ "PL_rocket_arena" "Arena balística"
+ "PL_rocket_arena_abbr" "MR"
+ "PL_rocket_arena_desc" "Reglas de Munición real clásico con EPG modificadas.^FFC83200\nJugadores: 6 contra 6 *Sin titanes\nLímite de tiempo: 90 s *Sin reapariciones\nTamaño máximo de grupo: 6"
+ "PL_rocket_arena_lobby" "Vestíbulo de Arena balística"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "Ajustes para cuando alguien carga un mapa desde la línea de comandos. No editar cmdlineMapLoad 1: eso hace que esto funcione."
+ "PL_speedball" "Munición real"
+ "PL_speedball_desc" "Lucha por la posesión de una bandera neutral. Elimina al equipo enemigo o mantén la posesión de la bandera cuando se agote el tiempo para vencer en la ronda. Gana el primero en imponerse en 5 rondas.\n^FFC83200Jugadores: 6 contra 6 *Sin titanes\nLímite de tiempo: 60 segundos por ronda *Sin reapariciones\nTamaño máximo de grupo: 6"
+ "PL_speedball_lobby" "Vestíbulo de Munición real"
+ "PL_tactikill" "Desgaste táctico"
+ "PL_tactikill_abbr" "DGT"
+ "PL_tactikill_desc" "Reglas de Desgaste clásico, excepto que las funciones tácticas se recuperan completamente al conseguir eliminaciones.\n^FFC83200Jugadores: 6 contra 6 *IA\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 6"
+ "PL_tactikill_lobby" "Vestíbulo de Desgaste táctico"
+ "PL_titan_brawl" "Batalla de titanes"
+ "PL_titan_brawl_abbr" "BDT"
+ "PL_titan_brawl_desc" "Elimina titanes enemigos. Pilotos no permitidos.\n^FFC83200Jugadores: 5 contra 5\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 5"
+ "PL_titan_brawl_hint" "Elimina titanes enemigos.\nSin eyección."
+ "PL_titan_brawl_lobby" "Vestíbulo de Batalla de titanes"
+ "PL_titan_brawl_turbo" "Batalla de titanes turbo"
+ "PL_titan_brawl_turbo_abbr" "BDT"
+ "PL_titan_brawl_turbo_desc" "Reglas de Batalla de titanes clásico con regeneración de impulso y generación de núcleo más rápidas.\n^FFC83200Jugadores: 5 contra 5\nLímite de tiempo: 10 min\nTamaño máximo de grupo: 5"
+ "PL_titan_brawl_turbo_hint" "Elimina titanes enemigos.\nSin eyección"
+ "PL_titan_brawl_turbo_lobby" "Vestíbulo de Batalla de titanes turbo"
+ "PL_turbo_last_titan_standing" "UTEP turbo"
+ "PL_turbo_last_titan_standing_abbr" "UTEP"
+ "PL_turbo_last_titan_standing_desc" "Reglas de UTEP clásico con regeneración de impulso y generación de núcleo más rápidas.\n^FFC83200Jugadores: 5 contra 5 *Empiezas como titán\nLímite de tiempo: 3 min por ronda *Sin reapariciones\nTamaño máximo de grupo: 5"
+ "PL_turbo_last_titan_standing_lobby" "Vestíbulo de UTEP turbo"
+ "PL_variety_pack" "Variado"
+ "PL_variety_pack_desc" "Número de jugadores variables. Incluye diversos mapas de los siguientes modos:\n^FFC83200*Cazarrecompensas *Desgaste\n*Último titán en pie *Fortines cargados\n*Pilotos contra pilotos *Captura la bandera"
+ "PL_variety_pack_lobby" "Vestíbulo de Variado"
+ "PL_wargames" "Juegos de guerra 24/7"
+ "PL_wargames_abbr" "JDG"
+ "PL_wargames_desc" "Mapa Juegos de guerra, continuamente. ^CCCCCC00Buscará partidas de ^FFC83200Desgaste^CCCCCC00, ^FFC83200CLB^CCCCCC00, ^FFC83200Pilotos contra pilotos^CCCCCC00, ^FFC83200Fortines cargados^CCCCCC00 y ^FFC83200Último titán en pie^CCCCCC00."
+ "PL_wargames_lobby" "Vestíbulo de Juegos de guerra 24/7"
+ "WATCH_TUTORIAL" "VER TUTORIAL"
+ }
+ }
+ "lang"
+ {
+ "Language" "tchinese"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "é€éŽæœ€æ–°ç™¼ä½ˆçš„《Titanfall 2:邊境明信片》DLC,您將å¯ä»¥åœ¨é‚Šå¢ƒç¹¼çºŒæ‚¨çš„驚奇冒險。內å«å…¨æ–°å’Œæ‚¨ç†Ÿæ‚‰çš„地點,以åŠå…¨æ–°çš„è英武器戰鬥塗è£æ”¶é›†å“,邊境將會有更美好的景觀。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_00_Q" "「邊境明信片ã€é å‘Šç‰‡"
+ "COMMUNITYUPDATE_01_A" "瞭解邊境明信片修補檔的所有變更內容。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_01_Q" "邊境明信片:修補檔說明"
+ "COMMUNITYUPDATE_02_A" "å³å°‡åœ¨è¡Œå‹•è£ç½®ä¸ŠæŽ¨å‡ºçš„《Titanfall:çªæ“Šã€‹æ˜¯ä¸€æ¬¾ä½æ–¼ Titanfall 世界中刺激的å³æ™‚ç­–ç•¥éŠæˆ²ï¼Œå®ƒæ˜¯èˆ‡ Particle City åˆä½œå®Œæˆã€‚到這裡觀看這些精彩內容ï¼\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_02_Q" "《Titanfall:çªæ“Šã€‹\發行\é å‘Šç‰‡"
+ "COMMUNITYUPDATE_03_A" "Iniquity 帶領我們完æˆã€ŠTitanfall:çªæ“Šã€‹çš„教學關å¡ä¸¦åˆ†äº«åŸºæœ¬çš„游戲訣竅。這是瞭解這款游戲的絕佳途徑。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_03_Q" "《Titanfall:çªæ“Šã€‹ï¼š\學習基本æ“作\影片"
+ "COMMUNITYUPDATE_04_A" "è½è½é‚Šå¢ƒé˜²ç¦¦å¹•å¾Œçš„一些é‡è¦äººç‰©è«‡è«–關於這個模å¼çš„一些歷å²å’Œè£½ä½œéŽç¨‹ï¼Œä¸¦è§€çœ‹å¹¾å›žåˆçš„éŠçŽ©éŽç¨‹ï¼\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_04_Q" "Respawn 一起玩邊境防禦"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger 製作了一些有趣的集錦,展ç¾ä½¿ç”¨è„ˆè¡åˆ€åšå‡ºçš„大é‡ç¾Žå¦™å‹•ä½œã€‚\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_05_Q" "社群創作:å‰å¾€æ“šé»ž"
+ "COMMUNITYUPDATE_06_A" "ConzeyG é€éŽ Reddit 展ç¾åœ¨çµæ®ºæ¨™è¨˜æ¨¡å¼ä¸­ä½¿ç”¨åŒ—極星擊破éµé¦­çš„驚人效率。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_06_Q" "社群創作:北極星çµæ®ºéµé¦­"
+ "COMMUNITYUPDATE_07_A" "ç¾å·²é–‹æ”¾ï¼é‚Šå¢ƒé˜²ç¦¦èˆ‡å´›èµ·åœ°åœ–將一åŒè¿”回,å¦å¤–還有一系列全新戰鬥塗è£ä¾›ä½ è³¼è²·å’Œä¸€å¼µæ–°çš„烈ç«æˆ°å ´åœ°åœ–。到這裡觀看這些精彩內容。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_07_Q" "「邊境神盾任務ã€çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_08_A" "ç¾å·²é–‹æ”¾ï¼ç¶“典的戰爭éŠæˆ²åœ°åœ–以全新姿態é‡è¿”《Titanfall 2》。更別錯éŽå…¨æ–°è™•æ±ºå‹•ç•«èˆ‡çƒˆç«æˆ°å ´åœ°åœ– - 交通。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_08_Q" "「戰爭éŠæˆ²ã€çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_09_A" "æ­¤ DLC å…§å«ç¬¬ 7 具多人éŠæˆ²æ³°å¦ï¼šã€Œå¸çŽ‹ã€ï¼Œç‚ºã€Œéºè·¡ã€çš„é‡è£½åœ°åœ–,並新增了å¯è³¼è²·çš„至尊浪人以åŠè‡³å°Šå¼·åŠ›æ³°å¦ã€æ›´å¤šè¿·å½©ã€æ——幟和機頭è—術。於此處觀看影åƒã€‚\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_09_Q" "「å¸çŽ‹çš„統治ã€éŠæˆ²çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_10_A" "邊境中暗è—å±æ©Ÿ - 準備進入《Titanfall 2》最新的å…è²» DLC:邊境故障,其中將包å«å…¨æ–°åœ°åœ–「異常ã€ã€‚å—拉斯æ摩沙上尉的家鄉哈墨尼星所啟發的環境設計,其中大多為垂直空間與連綿曲折的é“路,éžå¸¸é©åˆä½¿ç”¨é€£éŽ–蹬å£ä¾†æ©«è·¨æ­¤åœ°åœ–。å¦å¤–還有一張全新烈ç«æˆ°å ´åœ°åœ–å³å°‡ç™»å ´ï¼šç”²æ¿ï¼Œå…¶ä¸­åŒ…å«ç‹¹çª„的室內空間ã€ç©ºæ› çš„廣場和在空中盤旋的機器人。如果本次更新讓您感到壓力太大,您最得力的助手馬文機器人ç¾åœ¨å°‡æˆç‚ºå…¨æ–°é™£ç‡Ÿä¸¦åŠ©æ‚¨ä¸€è‡‚之力。最後,全新的脈è¡åˆ€è™•æ±ºå‹•ç•«å·²é–‹æ”¾ï¼Œä¸¦åœ¨æ‚¨éœ€è¦çš„時候隨時供您使用。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_10_Q" "「邊境故障ã€éŠæˆ²çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_11_A" "é‡è¿”粉絲們最愛的åˆä»£ã€ŠTitanfall》地圖:殖民地。混åˆäº†ã€ŠTitanfall 2》中所有全新戰術技能與泰å¦ï¼Œæ³°å¦èˆ‡éµé¦­å€‘想在這張充滿狹窄巷弄ã€æ­»è§’與開放屋頂的鄉æ‘地圖中存活必須隨時ä¿æŒè­¦è¦ºã€‚æ–¼ 3 月 30 æ—¥å°æ‰€æœ‰çŽ©å®¶å…費開放。「殖民地é‡ç”Ÿã€DLC 組åˆåŒ…中å«æœ‰é…·ç‚«çš„經典武器 R-101 æ­¥æ§ã€å…¨æ–°å‹¾çˆªè™•æ±ºå‹•ç•«å’Œå…¨æ–°å¤–觀é¸é …供您購買,讓您能夠在浴血奮戰的åŒæ™‚展ç¾æ‚¨ç¨æœ‰çš„魅力。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_11_Q" "「殖民地é‡ç”Ÿã€éŠæˆ²çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_12_A" "「烈ç«æˆ°å ´ã€ç°¡ä»‹ï¼šè¶…快節å¥çš„ 6 å° 6 éµé¦­å°æˆ°æ¨¡å¼ï¼Œè®“您體驗邊境上刺激的近è·é›¢æˆ°é¬¥ã€‚「烈ç«æˆ°å ´ã€åŒ…å«å…©å¼µå…¨æ–°çš„專屬地圖:「堆ç©åœ°ã€å’Œã€Œè‰åŽŸã€ã€‚這兩張å…費地圖æ供了å°é–‰å¼çš„å°æˆ°ç©ºé–“,是專門為快節å¥çš„近戰模å¼è€Œè¨­è¨ˆã€‚\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_12_Q" "「歡迎來到烈ç«æˆ°å ´ã€éŠæˆ²çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_13_A" "體驗經éŽé‡è£½çš„åˆä»£ã€ŠTitanfall》玩家最愛的`1天使城`0地圖。為在 12 月 1 日與`1天使城通ç·ä»¤`0一åŒå‘所有玩家發佈的首個å…費《Titanfall 2》DLC åšå¥½æº–備。此內容包å«æ–°çš„外觀é¸é …,å¯è®“您在邊境上更加英姿煥發。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_13_Q" "「歡迎來到天使城ã€éŠæˆ²çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_14_A" "歡迎回來。\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_14_Q" "「Encoreã€è®šè­½å½±ç‰‡"
+ "COMMUNITYUPDATE_15_A" "å…©ä½å‚³å¥‡ï¼Œä¸€æ¬¡éºè´ˆã€‚\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_15_Q" "「åˆè€Œç‚ºä¸€ã€å–®äººæ¨¡å¼éŠæˆ²çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_16_A" "ç„¡é™çš„å¯èƒ½ã€‚\n\n\n按 `2%[A_BUTTON|MOUSE1]%`0 觀看。"
+ "COMMUNITYUPDATE_16_Q" "「éµé¦­ã€å¤šäººæ¨¡å¼éŠæˆ²çŽ©æ³•é å‘Šç‰‡"
+ "COMMUNITYUPDATE_DESC" "`3Titanfall 社群更新`0\n\n來自網路的資訊和連çµã€‚\n\n更多內容:\n`2%$rui/bullet_point%`0在 Twitter 上關注我們 `1@Respawn`0\n`2%$rui/bullet_point%`0在 `1facebook.com/RespawnEntertainment`0 追蹤我們\n`2%$rui/bullet_point%`0造訪 `1www.respawn.com`0 加入我們"
+ "COMMUNITYUPDATE_NAME" "社群"
+ "KNB_SUBJECT_00_DESC" "`3《Titanfall》有什麼新內容?`0\n\n從此處瞭解《Titanfall 2》的改變ï¼"
+ "KNB_SUBJECT_00_NAME" "éŠæˆ²æ›´æ–°"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 社群建立的全新呼å«ä»£è™Ÿæ——幟\n\n`2%$rui/bullet_point%`0ç¾åœ¨èƒ½å¤ ä½¿ç”¨é»žæ•¸è³¼è²·æ‰€æœ‰æ“護禮包\n\n`2%$rui/bullet_point%`0全新商城é“具供你購買\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 11 月 28 日 - 收穫季節"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0主手æ§æ¬„\n\n`2%$rui/bullet_point%`0è¬è–節旗幟\n\n`2%$rui/bullet_point%`0平衡性變更\n\n`2%$rui/bullet_point%`0全新商城é“具供你購買\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 10 月 31 æ—¥ - è¬è–節活動"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0邊境防禦 - æ”¯æ´ 3 å¼µé¡å¤–的地圖:乾塢ã€å¤©ä½¿åŸŽå’Œç³»å¤–行星。\n\n`2%$rui/bullet_point%`0全新烈ç«æˆ°å ´åœ°åœ– - UMA\n\n`2%$rui/bullet_point%`0全新éµé¦­è™•æ±ºå‹•ç•« - ç©¿é€éšœå£\n\n`2%$rui/bullet_point%`0全新商城é“具供你購買"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 8 月 29 日 - 邊境明信片"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0邊境防禦 - 全新åˆä½œæ¨¡å¼è®“你與其他三å玩家一åŒä¿è­·é‡è¦ç›®æ¨™ä¸¦æ“Šé€€ä¸€æ³¢æ³¢é€æ¼¸å¢žå¼·çš„ AI 敵人。æºé€šèˆ‡é©æ‡‰åŠ›å°‡æ˜¯ç”Ÿå­˜çš„é—œéµã€‚\n\n`2%$rui/bullet_point%`0全新地圖 - 崛起\n\n`2%$rui/bullet_point%`0全新烈ç«æˆ°å ´åœ°åœ– - å°éŽ®\n\n`2%$rui/bullet_point%`0全新商城é“具供你購買"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 7 月 25 日 - 邊境神盾任務"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0新地圖 - 戰爭éŠæˆ²\n\n`2%$rui/bullet_point%`0新烈ç«æˆ°å ´åœ°åœ– - 交通\n\n`2%$rui/bullet_point%`0æ–°éµé¦­è™•æ±ºå‹•ç•« - 幻影拳腳\n\n`2%$rui/bullet_point%`0第三武器欄\n\n`2%$rui/bullet_point%`0ç§äººæ¯”賽設置"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 6 月 27 æ—¥ - 戰爭éŠæˆ²"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0å…¨æ–°æ³°å¦ - å¸çŽ‹\n\n`2%$rui/bullet_point%`0全新地圖 - éºè·¡\n\n`2%$rui/bullet_point%`0全新éµé¦­è™•æ±ºå‹•ç•« - 無影無蹤\n\n`2%$rui/bullet_point%`0全新商城é“具供你購買"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 5 月 30 æ—¥ - å¸çŽ‹çš„統治"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0全新地圖 - 異常\n\n`2%$rui/bullet_point%`0全新烈ç«æˆ°å ´åœ°åœ– - 甲æ¿\n\n`2%$rui/bullet_point%`0全新陣營 - 馬文機器人\n\n`2%$rui/bullet_point%`0全新éµé¦­è™•æ±ºå‹•ç•« - 刀刀見骨\n\n`2%$rui/bullet_point%`0é‡ç”Ÿä¸Šé™æå‡è‡³ 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 4 月 25 日 - 邊境故障"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0全新地圖 - 殖民地\n\n`2%$rui/bullet_point%`0全新éµé¦­è™•æ±ºå‹•ç•« - 猛擊\n\n`2%$rui/bullet_point%`0全新武器 - R-101\n\n`2%$rui/bullet_point%`0全新商城é“具供你購買"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 3 月 30 æ—¥ - 殖民地é‡ç”Ÿ"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0烈ç«æˆ°å ´ - 全新éµé¦­å°éµé¦­æ®²æ»…模å¼ï¼é€™æ˜¯ä¸€å ´ 6 å° 6 回åˆæ¨¡å¼ä¸”ä¸èƒ½é‡ç”Ÿï¼Œä½ å°‡æœ‰ä¸€åˆ†é˜æ™‚間殲滅敵隊來è´å¾—此回åˆã€‚你也能夠é€éŽåœ¨è¨ˆæ™‚çµæŸæ™‚æŒæœ‰ä¸­ç«‹æ——幟來å–å‹ã€‚å…ˆè´å¾— 5 個回åˆçš„隊ä¼å°‡è´å¾—比賽。\n\n`2%$rui/bullet_point%`0全新烈ç«æˆ°å ´åœ°åœ– - è‰åŽŸ\n\n`2%$rui/bullet_point%`0全新烈ç«æˆ°å ´åœ°åœ– - å †ç©åœ°\n\n`2%$rui/bullet_point%`0全新éµé¦­è™•æ±ºå‹•ç•« - 快速攻擊"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 2 月 23 æ—¥ - 烈ç«æˆ°å ´"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0全新地圖 - 天使城\n\n`2%$rui/bullet_point%`0全新武器 - B3 幫手è英\n\n`2%$rui/bullet_point%`0全新éµé¦­è™•æ±ºå‹•ç•« - 瞬間爆炸\n\n`2%$rui/bullet_point%`0全新泰å¦è£å‚™\n\n`2%$rui/bullet_point%`0全新商城é“具供你購買"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 11 月 30 æ—¥ - 天使城通ç·ä»¤"
+ "MP_ANGEL_CITY_FD_WAVE_1" "被縛於天際"
+ "MP_ANGEL_CITY_FD_WAVE_2" "ç„¡å½¢"
+ "MP_ANGEL_CITY_FD_WAVE_3" "æš´å‹•"
+ "MP_ANGEL_CITY_FD_WAVE_4" "一群固執的人"
+ "MP_ANGEL_CITY_FD_WAVE_5" "手氣絕佳"
+ "MP_DRYDOCK_FD_WAVE_1" "游入陌生水域"
+ "MP_DRYDOCK_FD_WAVE_2" "他們沒å­å½ˆäº†ï¼"
+ "MP_DRYDOCK_FD_WAVE_3" "å—科學誤導"
+ "MP_DRYDOCK_FD_WAVE_4" "高壓系統"
+ "MP_DRYDOCK_FD_WAVE_5" "風暴之眼"
+ "MP_THAW_FD_WAVE_1" "狀態æ„è­˜"
+ "MP_THAW_FD_WAVE_2" "人多力é‡å¤§"
+ "MP_THAW_FD_WAVE_3" "追隨者 21"
+ "MP_THAW_FD_WAVE_4" "ç«ä¸­ä½œæ¨‚"
+ "MP_THAW_FD_WAVE_5" "å—œæ§å¦‚命"
+ "NO_PRICE" "優惠已éŽæœŸ"
+ "NO_PRICE_TWO_LINES" "優惠\nå·²éŽæœŸ"
+ "PL_aegis_last_titan_standing" "神盾泰å¦æ®Šæ­»æˆ°"
+ "PL_aegis_last_titan_standing_abbr" "ALTS"
+ "PL_aegis_last_titan_standing_desc" "神盾å‡ç´šå•Ÿç”¨ã€‚回åˆåˆ¶æ·˜æ±°è³½ã€‚å…ˆè´ 3 場者å‹ã€‚\n^FFC83200玩家:5 å° 5 *開始時有泰å¦\n時間é™åˆ¶ï¼šæ¯å ´ 3 åˆ†é˜ *ç„¡é‡ç”Ÿ\n最大派å°äººæ•¸ï¼š5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "神盾泰å¦çˆ­é¬¥"
+ "PL_aegis_titan_brawl_abbr" "ATTDM"
+ "PL_aegis_titan_brawl_desc" "擊殺敵方泰å¦ã€‚神盾å‡ç´šå•Ÿç”¨ã€‚\n^FFC83200玩家:5 å° 5\n時間é™åˆ¶ï¼š10 分é˜\n最大派å°äººæ•¸ï¼š5"
+ "PL_aegis_titan_brawl_hint" "擊殺敵方泰å¦ã€‚\n無彈射"
+ "PL_aegis_titan_brawl_lobby" "神盾泰å¦çˆ­é¬¥å¤§å»³"
+ "PL_aitdm" "消耗戰"
+ "PL_aitdm_abbr" "ATT"
+ "PL_aitdm_desc" "擊殺所有敵人。\n^FFC83200玩家:6v6 *AI\n時間é™åˆ¶ï¼š10 分é˜\n最大派å°äººæ•¸ï¼š6"
+ "PL_aitdm_lobby" "消耗戰大廳"
+ "PL_all_grapple" "進擊的泰å¦"
+ "PL_all_grapple_abbr" "消耗戰"
+ "PL_all_grapple_desc" "傳統消耗戰è¦å‰‡ï¼Œä½†æ˜¯æˆ°è¡“技能被鉤爪å–代。\n^FFC83200玩家:6v6 *電腦\n時間é™åˆ¶ï¼š10 分é˜\n最大隊ä¼äººæ•¸ï¼š6"
+ "PL_all_grapple_lobby" "進擊的泰å¦å¤§å»³"
+ "PL_all_holopilot" "大騙局"
+ "PL_all_holopilot_abbr" "烈ç«æˆ°å ´"
+ "PL_all_holopilot_desc" "傳統烈ç«æˆ°å ´è¦å‰‡ï¼Œä½†æ˜¯æˆ°è¡“技能被幻影éµé¦­å–代。\n^FFC83200玩家:6v6 *ç„¡æ³°å¦\n時間é™åˆ¶ï¼š60 秒 *ç„¡é‡ç”Ÿ\n最大隊ä¼äººæ•¸ï¼š6"
+ "PL_all_holopilot_lobby" "大騙局大廳"
+ "PL_all_phase" "異次元"
+ "PL_all_phase_abbr" "消耗戰"
+ "PL_all_phase_desc" "傳統消耗戰è¦å‰‡ï¼Œä½†æ˜¯æˆ°è¡“技能被轉移å–代。\n^FFC83200玩家:6v6 *電腦\n時間é™åˆ¶ï¼š10 分é˜\n最大隊ä¼äººæ•¸ï¼š6"
+ "PL_all_phase_lobby" "異次元大廳"
+ "PL_all_spicy" "ç«ç†±æ¶ˆè€—戰"
+ "PL_all_spicy_abbr" "消耗戰"
+ "PL_all_spicy_desc" "傳統消耗戰è¦å‰‡ï¼Œä½†æ˜¯æˆ°è¡“技能被炸蛛å–代。\n^FFC83200玩家:6v6 *電腦\n時間é™åˆ¶ï¼š10 分é˜\n最大隊ä¼äººæ•¸ï¼š6"
+ "PL_all_spicy_lobby" "ç«ç†±æ¶ˆè€—戰大廳"
+ "PL_amped_tacticals" "強化戰術技能"
+ "PL_amped_tacticals_abbr" "消耗戰"
+ "PL_amped_tacticals_desc" "傳統消耗戰è¦å‰‡ï¼Œä½†æ˜¯æˆ°è¡“技能將更加強大。\n^FFC83200玩家:6v6 *電腦\n時間é™åˆ¶ï¼š10 分é˜\n最大隊ä¼äººæ•¸ï¼š6"
+ "PL_amped_tacticals_lobby" "強化戰術技能大廳"
+ "PL_ANGEL_CITY" "天使城 24/7"
+ "PL_angel_city_abbr" "AC"
+ "PL_angel_city_desc" "天使城,永é éƒ½åœ¨ã€‚\n^FFC83200*è³žé‡‘è¿½ç· *消耗戰\n*強化據點"
+ "PL_angel_city_lobby" "天使城 24/7 大廳"
+ "PL_at_coop" "逃脫(åˆä½œæ¨¡å¼ï¼‰"
+ "PL_at_coop_desc" "您剛剛逃離了監ç„且身上åªæœ‰ä¸€æŠŠæ‰‹æ§ã€‚存活至撤離艦到來。擊殺敵人來ç²å¾—金錢並用其購買武器。\n^FFC83200玩家:6\n最大派å°äººæ•¸ï¼š6"
+ "PL_at_coop_lobby" "逃脫大廳"
+ "PL_attrition" "賞金追ç·"
+ "PL_attrition_abbr" "BH"
+ "PL_attrition_desc" "擊殺敵人ç²å–金錢。é€éŽåœ¨æŒ‡å®šåœ°é»žã€Œå„²å­˜ã€ç´…利以ç²å¾—é¡å¤–金錢。\n^FFC83200玩家:5 å° 5 *AI\n 時間é™åˆ¶ï¼š10 分é˜\n最大派å°äººæ•¸ï¼š5"
+ "PL_attrition_lobby" "賞金追ç·å¤§å»³"
+ "PL_capture_the_flag" "奪旗"
+ "PL_capture_the_flag_abbr" "CTF"
+ "PL_capture_the_flag_desc" "奪å–敵方旗幟並將其帶回你的基地,åŒæ™‚阻止å°æ–¹å¥ªå–你的旗幟ï¼\n^FFC83200玩家:5 å° 5\n時間é™åˆ¶ï¼š12 分é˜\n最大派å°äººæ•¸ï¼š5"
+ "PL_capture_the_flag_lobby" "奪旗大廳"
+ "PL_coliseum" "競技場"
+ "PL_coliseum_desc" "在éµç± è£¡ä»¥å¼·åŒ–行動力進行一å°ä¸€æˆ°é¬¥ã€‚æ¯æ¬¡æ“Šæ®ºå°æ‰‹ä¾¿è´ä¸€å›žåˆã€‚5 戰 3 å‹è€…å¯ç²å¾—æ“護禮包çŽå‹µã€‚^FFC83200\n玩家:1 å° 1 *ç„¡æ³°å¦\n時間é™åˆ¶ï¼š3 åˆ†é˜ *ç„¡é‡ç”Ÿ\n**^FFFFFF00需è¦ç«¶æŠ€å ´é–€ç¥¨æˆ–支付入場費"
+ "PL_coliseum_lobby" "競技場大廳"
+ "PL_colony" "殖民地 24/7"
+ "PL_colony_abbr" "COL"
+ "PL_colony_desc" "所有殖民地,æŒçºŒä¸æ–·ã€‚ ^CCCCCC00這將會æœå°‹^FFC83200消耗戰^CCCCCC00ã€^FFC83200éµé¦­å°éµé¦­^CCCCCC00ã€åŠ^FFC83200æ³°å¦æ®Šæ­»æˆ°^CCCCCC00的比賽。"
+ "PL_colony_lobby" "殖民地 24/7 大廳"
+ "PL_ctf_lf" "奪旗(氮氧)"
+ "PL_ctf_lf_abbr" "氮氧奪旗"
+ "PL_ctf_lf_desc" "在é¸æ“‡çš„地圖中進行快節å¥çš„奪旗比賽。\n^FFC83200玩家:5 å° 5\n最大隊ä¼äººæ•¸ï¼š5\n^F4D5A600ç«‹å³æ­¸é‚„旗幟"
+ "PL_ctf_lf_lobby" "氮氧奪旗大廳"
+ "PL_default_description" "é è¨­èªªæ˜Ž"
+ "PL_default_lobbytitle" "é è¨­å¤§å»³å稱"
+ "PL_default_name" "é è¨­å稱"
+ "PL_don" "加注翻å€"
+ "PL_fd" "邊境防禦"
+ "PL_fd_desc" "抵抗數波的殘餘部隊勢力"
+ "PL_fd_easy" "邊境防禦:簡單"
+ "PL_fd_easy_desc" "粉碎å°æ‰‹"
+ "PL_fd_easy_lobby" "邊境防禦:簡單大廳"
+ "PL_fd_hard" "邊境防禦:困難"
+ "PL_fd_hard_desc" "必備高超的技巧"
+ "PL_fd_hard_lobby" "邊境防禦:困難大廳"
+ "PL_fd_insane" "邊境防禦:瘋狂"
+ "PL_fd_insane_desc" "你將無法生存"
+ "PL_fd_insane_lobby" "邊境防禦:瘋狂大廳"
+ "PL_fd_lobby" "邊境防禦大廳"
+ "PL_fd_master" "邊境防禦:大師"
+ "PL_fd_master_desc" "åªæœ‰æœ€é ‚å°–çš„è英能夠æˆåŠŸ"
+ "PL_fd_master_lobby" "邊境防禦:大師大廳"
+ "PL_fd_normal" "邊境防守:普通"
+ "PL_fd_normal_desc" "推薦給資深玩家"
+ "PL_fd_normal_lobby" "邊境防守:普通大廳"
+ "PL_ffa" "混戰"
+ "PL_ffa_abbr" "FFA"
+ "PL_ffa_desc" "所有éµé¦­ç‚ºè‡ªå·±è€Œæˆ°ï¼Œæ“Šæ®ºæ‰€æœ‰æ•µäººã€‚\n^FFC83200玩家:1 å° 11\n時間é™åˆ¶ï¼š10 分é˜\n最大派å°äººæ•¸ï¼š1"
+ "PL_ffa_lobby" "混戰大廳"
+ "PL_fra" "自由混戰"
+ "PL_fra_abbr" "FRA"
+ "PL_fra_desc" "你將孤身作戰。擊殺敵人來å–å‹ã€‚收集 3 枚電池後å¯å‘¼å«æ³°å¦ã€‚\n^FFC83200玩家:1 å° 11\n時間é™åˆ¶ï¼š15 分é˜\n最大派å°äººæ•¸ï¼š1"
+ "PL_fra_lobby" "自由混戰大廳"
+ "PL_groud_war_lobby" "8 å° 8 綜åˆå¤§å»³"
+ "PL_ground_war" "8 å° 8 綜åˆ"
+ "PL_ground_war_abbr" "8v8"
+ "PL_ground_war_desc" "收錄了「å°è¦æ¨¡æˆ°é¬¥ã€å’Œã€Œå¼·åŒ–據點ã€ï¼Œä¸¦åœ¨æ¯å¼µåœ°åœ–上都有高玩家人數。\n^FFC83200玩家:8å°8"
+ "PL_hardpoint" "強化據點"
+ "PL_hardpoint_abbr" "AHP"
+ "PL_hardpoint_desc" "佔領並堅守é‡åœ°ä¾†ç²å¾—分數。強化的é‡åœ°æ供雙å€åˆ†æ•¸ã€‚\n^FFC83200玩家:6 å° 6\n時間é™åˆ¶ï¼š10 分é˜\n最大派å°äººæ•¸ï¼š6"
+ "PL_hardpoint_lobby" "強化據點大廳"
+ "PL_hunted" "é­çµæ®º"
+ "PL_iron_last_titan_standing" "éµè¡€æ³°å¦æ®Šæ­»æˆ°"
+ "PL_iron_last_titan_standing_abbr" "ILTS"
+ "PL_iron_last_titan_standing_desc" "僅泰å¦åƒåŠ çš„回åˆåˆ¶æ®²æ»…æˆ°ã€‚å…ˆè´ 3 回åˆè€…ç²å‹ã€‚\n^FFC83200玩家:5 å° 5 *ç„¡éµé¦­\n時間é™åˆ¶ï¼šæ¯å›žåˆ 3 åˆ†é˜ *ç„¡é‡ç”Ÿ\n最大派å°äººæ•¸ï¼š5"
+ "PL_iron_last_titan_standing_lobby" " "
+ "PL_last_titan_standing" "æ³°å¦æ®Šæ­»æˆ°"
+ "PL_last_titan_standing_abbr" "LTS"
+ "PL_last_titan_standing_desc" "所有玩家開始此回åˆåˆ¶æ·˜æ±°è³½æ™‚皆有泰å¦ã€‚å…ˆè´ 3 場者å‹ã€‚\n^FFC83200玩家:5 å° 5 *開始時有泰å¦\n時間é™åˆ¶ï¼šæ¯å ´ 3 åˆ†é˜ *ç„¡é‡ç”Ÿ\n最大派å°äººæ•¸ï¼š5"
+ "PL_last_titan_standing_lobby" "殊死大廳"
+ "PL_limited_time_mode" "é™æ™‚模å¼"
+ "PL_live_fire" "烈ç«æˆ°å ´"
+ "PL_live_fire_abbr" "LF"
+ "PL_live_fire_desc" "在烈ç«ç«¶æŠ€å ´ä¸­é€²è¡Œå¿«ç¯€å¥çš„戰鬥。在回åˆå…§æ“Šæ®ºæ‰€æœ‰æ•µæ–¹éµé¦­æˆ–是在時間çµæŸæ™‚æŒæœ‰æ——幟å³å¯ç²å‹ã€‚^FFC83200\n玩家:6 å° 6 *ç„¡æ³°å¦\n時間é™åˆ¶ï¼š60 秒 *ç„¡é‡ç”Ÿ\n最大派å°äººæ•¸ï¼š6"
+ "PL_live_fire_lobby" "烈ç«æˆ°å ´å¤§å»³"
+ "PL_load_a_map_on_the_command_line" "在命令行載入地圖ï¼devonly"
+ "PL_marked_for_death" "çµæ®ºæ¨™è¨˜"
+ "PL_marked_for_death_abbr" "MFD"
+ "PL_marked_for_death_desc" "擊殺或ä¿è­·è¢«æ¨™è¨˜çš„目標。\n^FFC83200玩家:6 å° 6\n時間é™åˆ¶ï¼š12 分é˜\n最大派å°äººæ•¸ï¼š6"
+ "PL_marked_for_death_lobby" "çµæ®ºæ¨™è¨˜å¤§å»³"
+ "PL_nitro_ffa" "混戰(氮氧)"
+ "PL_nitro_ffa_abbr" "混戰-氮氧"
+ "PL_nitro_ffa_desc" "在特定地圖上進行快節å¥çš„混戰。\n^FFC83200玩家:6\n^F4D5A600ç„¡æ³°å¦\n無強化"
+ "PL_nitro_ffa_lobby" "混戰氮氧大廳"
+ "PL_nitro_mixtape" "綜åˆï¼ˆæ°®æ°§ï¼‰"
+ "PL_nitro_mixtape_abbr" "綜åˆ-氮氧"
+ "PL_nitro_mixtape_desc" "在特定地圖上進行快節å¥çš„奪旗ã€çµæ®ºæ¨™è¨˜èˆ‡éµé¦­å°éµé¦­ã€‚\n^FFC83200玩家:5 å° 5\n最大隊ä¼äººæ•¸ï¼š5\n^F4D5A600ç«‹å³æ­¸é‚„旗幟"
+ "PL_nitro_mixtape_lobby" "綜åˆå¤§å»³"
+ "PL_pilot_hunter" "å°è¦æ¨¡æˆ°é¬¥"
+ "PL_pilot_hunter_abbr" "SKM"
+ "PL_pilot_hunter_desc" "擊殺敵方éµé¦­èˆ‡æ³°å¦ã€‚\n^FFC83200玩家:8 å° 8\n時間é™åˆ¶ï¼š10 分é˜\n最大派å°äººæ•¸ï¼š8"
+ "PL_pilot_hunter_lobby" "å°è¦æ¨¡æˆ°é¬¥å¤§å»³"
+ "PL_pilot_skirmish" "éµé¦­å°éµé¦­"
+ "PL_pilot_skirmish_abbr" "PvP"
+ "PL_pilot_skirmish_desc" "擊殺敵方éµé¦­ã€‚無法呼å«æ³°å¦ã€‚\n^FFC83200玩家:8 å° 8 *ç„¡æ³°å¦\n時間é™åˆ¶ï¼š10 分é˜\n最大派å°äººæ•¸ï¼š8"
+ "PL_pilot_skirmish_lobby" "éµé¦­å°éµé¦­å¤§å»³"
+ "PL_pl_rebuild_all_paths" "é‡å»ºæ‰€æœ‰è·¯ç·š"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "執行 ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "ç§äººæ¯”è³½ (Beta 測試)"
+ "PL_private_match_desc" "使用您é¸æ“‡çš„地圖和模å¼é€²è¡Œè‡ªè¨‚çš„ç§äººæ¯”賽。\n^FFC83200邀請網路或邀請好å‹åŠ å…¥\n^FFC83200玩家:1-16 人\n無進度"
+ "PL_private_match_lobby" "ç§äººæ¯”賽大廳 (Beta 測試)"
+ "PL_promo_coop" "4 人åˆä½œæ¨¡å¼"
+ "PL_raid" "çªè¥²"
+ "PL_rise" "崛起 24/7"
+ "PL_rise_abbr" "RIS"
+ "PL_rise_desc" "僅é™å´›èµ·ï¼ŒæŒçºŒä¸æ–·ã€‚^CCCCCC00這將æœå°‹^FFC83200奪旗^CCCCCC00ã€^FFC83200強化據點^CCCCCC00ã€^FFC83200éµé¦­å°éµé¦­^CCCCCC00ã€^FFC83200烈ç«æˆ°å ´^CCCCCC00與^FFC83200æ³°å¦æ®Šæ­»æˆ°^CCCCCC00比賽。"
+ "PL_rise_lobby" "崛起 24/7 大廳"
+ "PL_rocket_arena" "ç«ç®­ç«¶æŠ€å ´"
+ "PL_rocket_arena_abbr" "烈ç«æˆ°å ´"
+ "PL_rocket_arena_desc" "傳統烈ç«æˆ°å ´è¦å‰‡ï¼Œä½†æ˜¯é…有改è£ç‰ˆ EPG。^FFC83200\n玩家:6v6*ç„¡æ³°å¦\n時間é™åˆ¶ï¼š90 秒*ç„¡é‡ç”Ÿ\n最大隊ä¼äººæ•¸ï¼š6"
+ "PL_rocket_arena_lobby" "ç«ç®­ç«¶æŠ€å ´å¤§å»³"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "æœ‰äººåœ¨å‘½ä»¤è¡Œè¼‰å…¥åœ°åœ–æ™‚çš„è¨­å®šã€‚è«‹å‹¿ç·¨è¼¯ä¸‹é¢ cmdlineMapLoad 1ï¼æœ‰é€™å€‹æ‰èƒ½ä½¿ç”¨æœ¬åŠŸèƒ½ã€‚"
+ "PL_speedball" "烈ç«æˆ°å ´"
+ "PL_speedball_desc" "æ¶å¥ªä¸­ç«‹æ——幟。消滅敵隊或是在比賽çµæŸæ™‚æŒæœ‰æ——幟者è´å¾—ä¸€å±€ã€‚å…ˆè´ 5 局者å‹ã€‚\n^FFC83200玩家:6 å° 6 *ç„¡æ³°å¦\n時間é™åˆ¶ï¼šæ¯å±€ 60 秒 *ç„¡é‡ç”Ÿ\n最大派å°äººæ•¸ï¼š6"
+ "PL_speedball_lobby" "烈ç«æˆ°å ´å¤§å»³"
+ "PL_tactikill" "戰術擊殺消耗戰"
+ "PL_tactikill_abbr" "消耗戰"
+ "PL_tactikill_desc" "傳統消耗戰è¦å‰‡ï¼Œä½†æ˜¯æˆ°è¡“技能在擊殺時將會刷新。\n^FFC83200玩家:6v6 *電腦\n時間é™åˆ¶ï¼š10 分é˜\n最大隊ä¼äººæ•¸ï¼š6"
+ "PL_tactikill_lobby" "戰術擊殺消耗戰大廳"
+ "PL_titan_brawl" "æ³°å¦çˆ­é¬¥"
+ "PL_titan_brawl_abbr" "TTDM"
+ "PL_titan_brawl_desc" "擊殺敵方泰å¦ï¼Œä¸å¯ä½¿ç”¨éµé¦­ã€‚\n^FFC83200玩家:5 å° 5\n時間é™åˆ¶ï¼š10 分é˜\n最大派å°äººæ•¸ï¼š5"
+ "PL_titan_brawl_hint" "擊殺敵方泰å¦ã€‚\n無彈射"
+ "PL_titan_brawl_lobby" "æ³°å¦çˆ­é¬¥å¤§å»³"
+ "PL_titan_brawl_turbo" "渦輪泰å¦çˆ­é¬¥"
+ "PL_titan_brawl_turbo_abbr" "æ³°å¦çˆ­é¬¥"
+ "PL_titan_brawl_turbo_desc" "傳統泰å¦çˆ­é¬¥è¦å‰‡ï¼Œä½†æ˜¯æ“有更快的è¡åˆºæ¢å¾©èˆ‡æ ¸å¿ƒæå‡çŽ‡ã€‚\n^FFC83200玩家:5 å° 5\n時間é™åˆ¶ï¼š10 分é˜\n最大隊ä¼äººæ•¸ï¼š5"
+ "PL_titan_brawl_turbo_hint" "擊殺敵方泰å¦ã€‚\n無彈射"
+ "PL_titan_brawl_turbo_lobby" "渦輪泰å¦çˆ­é¬¥å¤§å»³"
+ "PL_turbo_last_titan_standing" "渦輪殊死戰"
+ "PL_turbo_last_titan_standing_abbr" "æ³°å¦æ®Šæ­»æˆ°"
+ "PL_turbo_last_titan_standing_desc" "傳統泰å¦æ®Šæ­»æˆ°è¦å‰‡ï¼Œä½†æ˜¯æ“有更快的è¡åˆºæ¢å¾©èˆ‡æ ¸å¿ƒæå‡çŽ‡ã€‚\n^FFC83200玩家:5 å° 5 *開始時有泰å¦\n時間é™åˆ¶ï¼šæ¯å›žåˆ 3 åˆ†é˜ *ç„¡é‡ç”Ÿ\n最大隊ä¼äººæ•¸ï¼š5"
+ "PL_turbo_last_titan_standing_lobby" "渦輪泰å¦æ®Šæ­»æˆ°å¤§å»³"
+ "PL_variety_pack" "綜åˆ"
+ "PL_variety_pack_desc" "綜åˆçŽ©å®¶åŒ…å«ä¸‹åˆ—模å¼ä¸­çš„å„種地圖:\n^FFC83200*è³žé‡‘è¿½ç· *消耗戰\n*æ³°å¦æ®Šæ­»æˆ° *強化據點\n*éµé¦­å°éµé¦­ *奪旗"
+ "PL_variety_pack_lobby" "綜åˆå¤§å»³"
+ "PL_wargames" "戰爭éŠæˆ² 24/7"
+ "PL_wargames_abbr" "WGM"
+ "PL_wargames_desc" "戰爭éŠæˆ²å…¨ç³»åˆ—,æŒçºŒä¸Šæ¼”。 ^CCCCCC00這將會æœå°‹^FFC83200消耗戰^CCCCCC00ã€^FFC83200奪旗^CCCCCC00ã€^FFC83200éµé¦­å°éµé¦­^CCCCCC00ã€^FFC83200強化據點^CCCCCC00,åŠ^FFC83200æ³°å¦æ®Šæ­»æˆ°^CCCCCC00ç­‰å°æˆ°ã€‚"
+ "PL_wargames_lobby" "戰爭éŠæˆ² 24/7 大廳"
+ "WATCH_TUTORIAL" "觀看教學"
+ }
+ }
+ "lang"
+ {
+ "Language" "italian"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "La tua avventura nei panorami eccezionali della Frontiera continua con l'ultimo DLC di Titanfall 2: Cartoline dalla Frontiera. Con luoghi nuovi e familiari e una nuova collezione di colori da guerra per armi Elite, la Frontiera non è mai stata così bella.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_00_Q" "Trailer \"Cartoline dalla Frontiera\""
+ "COMMUNITYUPDATE_01_A" "Leggi tutte le novità introdotte nella patch Cartoline dalla Frontiera.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_01_Q" "Cartoline dalla Frontiera: note sulla patch"
+ "COMMUNITYUPDATE_02_A" "Presto disponibile su dispositivi mobili, Titanfall: Assault è un videogioco strategico in tempo reale ambientato nell’universo Titanfall e realizzato in collaborazione con Particle City. Guardalo in azione! \n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_02_Q" "Trailer di \"Lancio\" Titanfall: Assault"
+ "COMMUNITYUPDATE_03_A" "Iniquity ci illustra il tutorial e ci fornisce suggerimenti di base per Titanfall: Assault. Questo è il modo perfetto per iniziare a conoscere il gioco.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_03_Q" "Video Titanfall Assault: \"Impara le basi\""
+ "COMMUNITYUPDATE_04_A" "Ascolta alcune delle persone chiave che hanno lavorato a Difesa Frontiera parlare della storia e della creazione della modalità e giocare a qualche partita! \n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_04_Q" "Respawn gioca a Difesa Frontiera"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger ha creato un montaggio divertente mostrando innumerevoli mosse con Lama a impulsi. \n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_05_Q" "Creazioni community: Arriva al punto"
+ "COMMUNITYUPDATE_06_A" "ConzeyG su reddit mostra una brutale efficienza: Northstar che elimina i piloti nella modalità Marchiato a morte. \n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_06_Q" "Creazioni : Cacciatore di piloti Northstar"
+ "COMMUNITYUPDATE_07_A" "Disponibile ora! Difesa Frontiera torna con Ascesa, nuovi colori da guerra acquistabili e una nuova mappa Live Fire.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_07_Q" "Trailer gameplay \"Scudo frontiera operazioni\""
+ "COMMUNITYUPDATE_08_A" "Disponibile ora! L’apprezzata mappa Giochi di guerra torna in Titanfall 2, più bella che mai. Scopri anche la nuova esecuzione e la nuova mappa Live Fire, Traffico in azione.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_08_Q" "Trailer gameplay “Giochi di guerraâ€"
+ "COMMUNITYUPDATE_09_A" "In questo DLC presentiamo: Monarch, il settimo Titan per multigiocatore; una rivisitazione della mappa Relitto; una nuova esecuzione e i Titan Ronin Prime e Tone Prime acquistabili; altre mimetiche, bandiere e decorazioni. Non perderti l'azione qui.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_09_Q" "Trailer gameplay “Regno di Monarchâ€"
+ "COMMUNITYUPDATE_10_A" "Nulla è come sembra nella Frontiera: preparati a entrare nel nuovo pack DLC gratuito per Titanfall 2: Un Glitch nella Frontiera, che contiene la nuova mappa “Glitchâ€. Ispirato dal pianeta Harmony del capitano Lastimosa, l’ambiente è dominato da cadute verticali e lunghi percorsi tortuosi, perfetti per concatenare lunghe corse sui muri, per poi planare in tutta la mappa. È inoltre inclusa una nuova mappa Live Fire: Ponte, che vanta spazi interni ristretti, cortili esposti e droni vigili che volteggiano sulla testa. Se dovessi sentirti sopraffatto, non preoccuparti: i M.R.V.N., sempre pronti a darti una mano robotica, si uniscono al gioco come una fazione completamente nuova. Infine, è possibile sbloccare l’esecuzione Lama a impulsi, perché possa essere utilizzata tutte le volte che desideri “far arrivare il messaggioâ€.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_10_Q" "Trailer gameplay “Un Glitch nella Frontieraâ€"
+ "COMMUNITYUPDATE_11_A" "Torna una delle mappe multigiocatore più apprezzate del Titanfall originale: Colonia. Con tutte le nuove tattiche e i nuovi Titan di Titanfall 2, Piloti e Titan dovranno rimanere sempre all’erta per sopravvivere in questo villaggio idilliaco e molto popolato fatto di vicoli, angoli ciechi e tetti esposti; disponibile gratuitamente il 30 marzo per tutti i giocatori. Il pack DLC “Colonia Rinata†comprende il ritorno di armi classiche come il fucile d’assalto R-101 modificato, una nuova esecuzione rampino e nuove opzioni estetiche da acquistare per apparire sempre al meglio mentre dipingi di rosso la città.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_11_Q" "Trailer gameplay \"Colonia Rinata\""
+ "COMMUNITYUPDATE_12_A" "Presentazione di Live Fire: una modalità ultra-rapida 6v6 per piloti che pone in prima linea il combattimento a distanza ravvicinata. Sono incluse due nuove mappe progettate specificamente per Live Fire: Cataste e Prateria. Queste due mappe gratuite sono aree di gioco strette e mortali progettate specificamente per la natura intensa e rapida di questa nuova modalità.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_12_Q" "Trailer gameplay “Benvenuti in Live Fireâ€"
+ "COMMUNITYUPDATE_13_A" "Prova una versione rivisitata di una della mappe preferite del Titanfall originale, `1Angel City`0. Tieniti pronto per il primo DLC gratuito per Titanfall 2 con `1Ricercato di Angel City`0 disponibile il 1 dicembre. Questo contenuto comprende nuove opzioni estetiche per aggiungere un po’ di eleganza alla Frontiera. \n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_13_Q" "Trailer gameplay “Benvenuti in Angel Cityâ€"
+ "COMMUNITYUPDATE_14_A" "Bentornato.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_14_Q" "Trailer \"Encore\""
+ "COMMUNITYUPDATE_15_A" "Due leggende, una sola eredità.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_15_Q" "Trailer “Diventate una cosa solaâ€"
+ "COMMUNITYUPDATE_16_A" "Senza limiti.\n\n\nPremi `2%[A_BUTTON|MOUSE1]%`0 per visualizzare."
+ "COMMUNITYUPDATE_16_Q" "Trailer multigiocatore “Pilotiâ€"
+ "COMMUNITYUPDATE_DESC" "`3Aggiornamenti community Titanfall`0\n\nInfo e collegamenti da tutto il Web.\n\nPer altre informazioni:\n`2%$rui/bullet_point%`0Seguici su Twitter `1@Respawn`0\n`2%$rui/bullet_point%`0Metti un like su `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0Unisciti a noi visitando l’indirizzo `1www.respawn.com`0"
+ "COMMUNITYUPDATE_NAME" "Community"
+ "KNB_SUBJECT_00_DESC" "`3Quali sono le novità di Titanfall?`0\n\nQui potrai vedere tutte le novità in Titanfall 2!"
+ "KNB_SUBJECT_00_NAME" "Aggiornamenti"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 Nuovi banner distintivo creati dalla community\n\n`2%$rui/bullet_point%`0Tutti i regali del difensore sono ora acquistabili con crediti\n\n`2%$rui/bullet_point%`0Nuovo contenuto negozio acquistabile\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 28 novembre - Momento della raccolta"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Slot primario pistola\n\n`2%$rui/bullet_point%`0Banner Halloween\n\n`2%$rui/bullet_point%`0Modifiche al bilanciamento\n\n`2%$rui/bullet_point%`0Nuovi contenuti del negozio acquistabili\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 31 ottobre - Dolcetti e scherzetti"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0Difesa Frontiera - Supporto per tre mappe aggiuntive - Bacino di carenaggio, Angel City ed Esopianeta.\n\n`2%$rui/bullet_point%`0Nuova mappa Live Fire - UMA\n\n`2%$rui/bullet_point%`0Nuova esecuzione pilota - Buco nel muro\n\n`2%$rui/bullet_point%`0Nuovo contenuto acquistabile nello store"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 29 agosto - Cartoline dalla frontiera"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Difesa Frontiera: una nuova modalità cooperativa in cui unisci le forze con un massimo di altri tre giocatori per difendere un obiettivo vitale da ondate sempre più intense di combattenti IA. La comunicazione e l'adattamento sono la chiave per la sopravvivenza.\n\n`2%$rui/bullet_point%`0Nuova mappa: Ascesa\n\n`2%$rui/bullet_point%`0Nuova mappa Live Fire: Cittadina\n\n`2%$rui/bullet_point%`0Nuovo contenuto acquistabile nel negozio"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 25 luglio - Operazione Scudo Frontiera "
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0Nuova mappa - Giochi di guerra\n\n`2%$rui/bullet_point%`0Nuova mappa Live Fire - Traffico\n\n`2%$rui/bullet_point%`0Nuova esecuzione pilota - Scatola ombra\n\n`2%$rui/bullet_point%`03° slot arma\n\n`2%$rui/bullet_point%`0Impostazioni partita privata"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 27 giugno - Giochi di guerra"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0Nuovo Titan - Monarch\n\n`2%$rui/bullet_point%`0Nuova mappa - Relitto\n\n`2%$rui/bullet_point%`0Nuova esecuzione pilota - Ora mi vedi\n\n`2%$rui/bullet_point%`0Nuovo contenuto acquistabile nello store"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 30 maggio - Regno di Monarch"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0Nuova mappa - Glitch\n\n`2%$rui/bullet_point%`0Nuova mappa Live Fire - Ponte\n\n`2%$rui/bullet_point%`0Nuova fazione - M.R.V.N.\n\n`2%$rui/bullet_point%`0Nuova esecuzione pilota - Arriva al punto\n\n`2%$rui/bullet_point%`0Generazione max portata a 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 25 aprile - Glitch nella Frontiera"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0Nuova mappa - Colonia\n\n`2%$rui/bullet_point%`0Nuova esecuzione pilota - Spaccatesta\n\n`2%$rui/bullet_point%`0Nuova arma - R-101\n\n`2%$rui/bullet_point%`0Nuovo contenuto acquistabile nello store"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 30 marzo - Colonia Rinata"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0Live Fire - Una nuova modalità di gioco a eliminazione Pilota contro Pilota! 6v6 basata su round senza rientri, avrai un minuto per eliminare la squadra rivale per vincere il round. Puoi vincere il round anche se la tua squadra è in possesso della bandiera neutra allo scadere del tempo. Vince la partita la squadra che si aggiudica per prima 5 round.\n\n`2%$rui/bullet_point%`0Nuova mappa Live Fire - Prateria\n\n`2%$rui/bullet_point%`0Nuova mappa Live Fire - Cataste\n\n`2%$rui/bullet_point%`0Nuova esecuzione pilota - Colpo ritardato"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 23 febbraio - Live Fire"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0Nuova mappa - Angel City\n\n`2%$rui/bullet_point%`0Nuova arma - B3 Wingman Elite\n\n`2%$rui/bullet_point%`0Nuova esecuzione pilota - Dal di dentro\n\n`2%$rui/bullet_point%`0Nuovi kit Titan\n\n`2%$rui/bullet_point%`0Nuovo contenuto acquistabile nello store"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 30 novembre - Ricercato di Angel City"
+ "MP_ANGEL_CITY_FD_WAVE_1" "Incatenato al cielo"
+ "MP_ANGEL_CITY_FD_WAVE_2" "Informe"
+ "MP_ANGEL_CITY_FD_WAVE_3" "Insurrezione"
+ "MP_ANGEL_CITY_FD_WAVE_4" "Un gruppo testardo"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Sfida la sorte"
+ "MP_DRYDOCK_FD_WAVE_1" "Nuotare in acque straniere"
+ "MP_DRYDOCK_FD_WAVE_2" "Non hanno proiettili!"
+ "MP_DRYDOCK_FD_WAVE_3" "Accecato dalla scienza"
+ "MP_DRYDOCK_FD_WAVE_4" "Sistema ad alta pressione"
+ "MP_DRYDOCK_FD_WAVE_5" "Occhio del ciclone"
+ "MP_THAW_FD_WAVE_1" "Percezione della situazione"
+ "MP_THAW_FD_WAVE_2" "La forza è nei numeri"
+ "MP_THAW_FD_WAVE_3" "Scagnozzo 21"
+ "MP_THAW_FD_WAVE_4" "Divertimento nel fuoco"
+ "MP_THAW_FD_WAVE_5" "Rendere onore alle pistole"
+ "NO_PRICE" "Offerta scaduta"
+ "NO_PRICE_TWO_LINES" "Offerta\nscaduta"
+ "PL_aegis_last_titan_standing" "Aegis ST"
+ "PL_aegis_last_titan_standing_abbr" "AST"
+ "PL_aegis_last_titan_standing_desc" "Aggiornamenti Aegis abilitati. Scontro organizzato in round a eliminazione. La vittoria va a chi vince per primo 3 round.\n^FFC83200Giocatori: 5v5 *Inizia come Titan\nTempo limite: 3 m per round *Nessun rientro\nDimensioni max gruppo: 5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "Scontro tra Titan Aegis"
+ "PL_aegis_titan_brawl_abbr" "ATTDM"
+ "PL_aegis_titan_brawl_desc" "Uccidi Titan nemici. Aggiornamenti Aegis abilitati.\n^FFC83200Giocatori: 5v5\nTempo limite: 10m\nDimensioni max gruppo: 5"
+ "PL_aegis_titan_brawl_hint" "Uccidi i Titan nemici.\nNessuna espulsione"
+ "PL_aegis_titan_brawl_lobby" "Lobby scontro tra Titan Aegis"
+ "PL_aitdm" "Logoramento"
+ "PL_aitdm_abbr" "LOG"
+ "PL_aitdm_desc" "Uccidi tutti i nemici.\n^FFC83200Giocatori: 6v6 *IA\nTempo limite: 10 min.\nDimensioni max gruppo: 6"
+ "PL_aitdm_lobby" "Lobby Logoramento"
+ "PL_all_grapple" "Attacco in Titanfall"
+ "PL_all_grapple_abbr" "ATT"
+ "PL_all_grapple_desc" "Regole di logoramento classiche, ad eccezione del fatto che tutte le abilità tattiche sono sostituite con Rampino.\n^FFC83200Giocatori: 6v6 *AI\nTempo limite: 10 m\nDimensioni max gruppo: 6"
+ "PL_all_grapple_lobby" "Attacco alla Lobby Titanfall"
+ "PL_all_holopilot" "Il grande inganno"
+ "PL_all_holopilot_abbr" "LF"
+ "PL_all_holopilot_desc" "Regole di Live Fire classiche, ad eccezione del fatto che tutte le abilità tattiche sono sostituite con Pilota olografico.^FFC83200\nGiocatori: 6v6 *No Titan\nTempo limite: 60 s *Nessun rientro\nDimensioni max gruppo: 6"
+ "PL_all_holopilot_lobby" "Loggy Il grande inganno"
+ "PL_all_phase" "L’Altra Parte"
+ "PL_all_phase_abbr" "ATT"
+ "PL_all_phase_desc" "Regole di logoramento classiche, ad eccezione del fatto che tutte le abilità tattiche sono sostituite con Fasico.\n^FFC83200Giocatori: 6v6 *AI\nTempo limite: 10 m\nDimensioni max gruppo: 6"
+ "PL_all_phase_lobby" "Lobby L’Altra Parte"
+ "PL_all_spicy" "Logoramento speziato"
+ "PL_all_spicy_abbr" "ATT"
+ "PL_all_spicy_desc" "Regole di logoramento classiche, ad eccezione del fatto che tutte le abilità tattiche sono sostituite con Tick.\n^FFC83200Giocatori: 6v6 *AI\nTempo limite: 10 m\nDimensioni max gruppo: 6"
+ "PL_all_spicy_lobby" "Lobby Logoramento speziato"
+ "PL_amped_tacticals" "Tattiche amplificate"
+ "PL_amped_tacticals_abbr" "ATT"
+ "PL_amped_tacticals_desc" "Regole di logoramento classiche, ad eccezione del fatto che le abilità tattiche sono più potenti.\n^FFC83200Giocatori: 6v6 *AI\nTempo limite: 10 m\nDimensioni max gruppo: 6"
+ "PL_amped_tacticals_lobby" "Lobby Tattiche amplificate"
+ "PL_ANGEL_CITY" "Angel City 24/7"
+ "PL_angel_city_abbr" "AC"
+ "PL_angel_city_desc" "Sempre e solo Angel City.\n^FFC83200*Caccia alle taglie *Logoramento\n*PCA"
+ "PL_angel_city_lobby" "Lobby Angel City 24/7"
+ "PL_at_coop" "Fuga (Co-op)"
+ "PL_at_coop_desc" "Sei appena evaso di prigione e hai con te solo una pistola. Sopravvivi fino all'arrivo di una nave da evacuazione. Uccidi i nemici per guadagnare denaro e acquistare armi. \n^FFC83200Giocatori: 6\nDimensioni max gruppo: 6"
+ "PL_at_coop_lobby" "Lobby Escape"
+ "PL_attrition" "Caccia alle taglie"
+ "PL_attrition_abbr" "CT"
+ "PL_attrition_desc" "Uccidi i nemici per guadagnare soldi. Ottienine di più depositando i bonus nei luoghi designati.\n^FFC83200Giocatori: 5v5 *IA\nTempo limite: 10 min.\nDimensioni max gruppo: 5"
+ "PL_attrition_lobby" "Lobby Caccia alle taglie"
+ "PL_capture_the_flag" "Cattura bandiera"
+ "PL_capture_the_flag_abbr" "CLB"
+ "PL_capture_the_flag_desc" "Ruba la bandiera nemica e riportala alla tua base mentre impedisci alla squadra avversaria di prendere la tua!\n^FFC83200Giocatori: 5v5\nTempo limite: 12 min.\nDimensioni max gruppo: 5"
+ "PL_capture_the_flag_lobby" "Lobby CB"
+ "PL_coliseum" "Colosseo"
+ "PL_coliseum_desc" "Uno-contro-uno in gabbia con mobilità incrementata. Vince il round chi uccide l'avversario. Chi si aggiudica 3 round su 5 vince un regalo del difensore.^FFC83200\nGiocatori: 1v1 *Niente Titan\nTempo limite: 3 min. *Nessun rientro\n**^FFFFFF00RICHIEDE BIGLIETTO COLOSSEO o PAGAMENTO DEL COSTO D'INGRESSO"
+ "PL_coliseum_lobby" "Lobby Colosseo"
+ "PL_colony" "Colonia 24/7"
+ "PL_colony_abbr" "COL"
+ "PL_colony_desc" "Sempre e soltanto Colonia. ^CCCCCC00Questa operazione effettuerà ricerche di corrispondenze di ^FFC83200Logoramento^CCCCCC00, ^FFC83200Piloti contro piloti^CCCCCC00 e ^FFC83200Sopravvivenza Titan^CCCCCC00."
+ "PL_colony_lobby" "Lobby Colonia 24/7"
+ "PL_ctf_lf" "CB (Nitro)"
+ "PL_ctf_lf_abbr" "CB-N"
+ "PL_ctf_lf_desc" "Un frenetico Cattura bandiera in mappe selezionate.\n^FFC83200Giocatori: 5v5\nDimensioni max gruppo: 5\n^F4D5A600Torna Bandiera istantanea"
+ "PL_ctf_lf_lobby" "Lobby Nitro CB"
+ "PL_default_description" "Descrizione predefinita"
+ "PL_default_lobbytitle" "Titolo lobby predefinito"
+ "PL_default_name" "Nome predefinito"
+ "PL_don" "Lascia o raddoppia"
+ "PL_fd" "Difesa Frontiera"
+ "PL_fd_desc" "Difenditi contro le ondate delle forze della flotta Remnant"
+ "PL_fd_easy" "Difesa Frontiera: facile"
+ "PL_fd_easy_desc" "Sconfiggi l’opposizione"
+ "PL_fd_easy_lobby" "Difesa Frontiera: Lobby facile"
+ "PL_fd_hard" "Difesa Frontiera: difficile"
+ "PL_fd_hard_desc" "Sono necessarie buone abilità di gioco"
+ "PL_fd_hard_lobby" "Difesa Frontiera: Lobby difficile"
+ "PL_fd_insane" "Difesa Frontiera: folle"
+ "PL_fd_insane_desc" "Non sopravviverai"
+ "PL_fd_insane_lobby" "Difesa Frontiera: Lobby folle"
+ "PL_fd_lobby" "Lobby Difesa Frontiera"
+ "PL_fd_master" "Difesa Frontiera: maestro"
+ "PL_fd_master_desc" "Solo i migliori dei migliori sopravviveranno"
+ "PL_fd_master_lobby" "Difesa Frontiera: Lobby maestro"
+ "PL_fd_normal" "Difesa Frontiera: normale"
+ "PL_fd_normal_desc" "Consigliato per giocatori esperti"
+ "PL_fd_normal_lobby" "Difesa Frontiera: lobby normale"
+ "PL_ffa" "Tutti contro tutti"
+ "PL_ffa_abbr" "TCT"
+ "PL_ffa_desc" "Ogni pilota è solo. Uccidi tutti i nemici.\n^FFC83200Giocatori: 1v11\nTempo limite: 10 min.\nDimensioni max gruppo: 1"
+ "PL_ffa_lobby" "Lobby Tutti contro tutti"
+ "PL_fra" "Azione libera"
+ "PL_fra_abbr" "AL"
+ "PL_fra_desc" "Ogni pilota lotta per sé. Uccidi i nemici per vincere. Taccogli 3 batterie per un Titanfall.\n^FFC83200Giocatori: 1v11\nLimite di tempo: 15m\nDimensioni max gruppo: 1"
+ "PL_fra_lobby" "Lobby Azione libera"
+ "PL_groud_war_lobby" "Lobby Remix 8v8"
+ "PL_ground_war" "Remix 8v8"
+ "PL_ground_war_abbr" "8v8"
+ "PL_ground_war_desc" "Include Scontro e Punto di controllo amplificato, con elevato numero di giocatori su tutte le mappe.\n^FFC83200Giocatori: 8v8"
+ "PL_hardpoint" "PCA"
+ "PL_hardpoint_abbr" "PCA"
+ "PL_hardpoint_desc" "Cattura e mantieni un punto di controllo per ottenere punti. I punti di controllo amplificati danno punti doppi.\n^FFC83200Giocatori: 6v6\nTempo limite: 10 min.\nDimensioni max gruppo: 6"
+ "PL_hardpoint_lobby" "Lobby PCA"
+ "PL_hunted" "Preda"
+ "PL_iron_last_titan_standing" "ST d'acciaio"
+ "PL_iron_last_titan_standing_abbr" "STA"
+ "PL_iron_last_titan_standing_desc" "Solo Titan, scontro organizzato in round a eliminazione. La vittoria va a chi vince per primo 3 round.\n^FFC83200Giocatori: 5v5 *NO PILOTI\nTempo limite: 3 min per round *Nessun rientro\nDimensioni max gruppo: 5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "Sopravvivenza Titan"
+ "PL_last_titan_standing_abbr" "ST"
+ "PL_last_titan_standing_desc" "Tutti i giocatori iniziano a bordo dei Titan in questo scontro organizzato in round a eliminazione. La vittoria va a chi vince per primo 3 round.\n^FFC83200Giocatori: 5v5 *Inizi come Titan\nTempo limite: 3 min. per round *Nessun rientro\nDimensioni max gruppo: 5"
+ "PL_last_titan_standing_lobby" "Lobby ST"
+ "PL_limited_time_mode" "A tempo limitato"
+ "PL_live_fire" "Live Fire"
+ "PL_live_fire_abbr" "LF"
+ "PL_live_fire_desc" "Combattimento rapido in un'arena Live Fire. Vinci il round uccidendo tutti i piloti nemici oppure tenendo la bandiera allo scadere del tempo.^FFC83200\nGiocatori: 6v6 *Niente Titan\nTempo limite: 60 s *Nessun rientro\nDimensioni max gruppo: 6"
+ "PL_live_fire_lobby" "Lobby Live Fire"
+ "PL_load_a_map_on_the_command_line" "Carica una mappa dalla linea di comando -solo sviluppatori"
+ "PL_marked_for_death" "Marchiato a morte"
+ "PL_marked_for_death_abbr" "MAM"
+ "PL_marked_for_death_desc" "Uccidi o proteggi i bersagli marchiati.\n^FFC83200Giocatori: 6v6 \nTempo limite: 12 m\nDimensioni max gruppo: 6"
+ "PL_marked_for_death_lobby" "Lobby Marchiato a morte"
+ "PL_nitro_ffa" "FFA (Nitro)"
+ "PL_nitro_ffa_abbr" "FFA-N"
+ "PL_nitro_ffa_desc" "Un frenetico Tutti contro tutti su mappe selezionate.\n^FFC83200Giocatori: 6\n^F4D5A600No Titan\nNo potenziamenti"
+ "PL_nitro_ffa_lobby" "Lobby Nitro FFA"
+ "PL_nitro_mixtape" "Remix (Nitro)"
+ "PL_nitro_mixtape_abbr" "MXT-N"
+ "PL_nitro_mixtape_desc" "Un frenetico CB, MFD e PvP in mappe selezionate.\n^FFC83200Giocatori: 5v5\nDimensioni max gruppo: 5\n^F4D5A600Torna Bandiera istantanea"
+ "PL_nitro_mixtape_lobby" "Lobby Remix"
+ "PL_pilot_hunter" "Scontro"
+ "PL_pilot_hunter_abbr" "SCN"
+ "PL_pilot_hunter_desc" "Uccidi piloti e Titan nemici. \n^FFC83200Giocatori: 8v8\nTempo limite: 10 min.\nDimensioni max gruppo: 8"
+ "PL_pilot_hunter_lobby" "Lobby Scontro"
+ "PL_pilot_skirmish" "Piloti contro piloti"
+ "PL_pilot_skirmish_abbr" "PvP"
+ "PL_pilot_skirmish_desc" "Uccidi i piloti nemici. Titanfall non consentiti.\n^FFC83200Giocatori: 8v8 *Nessun Titan\nTempo limite: 10 min.\nDimensioni max gruppo: 8"
+ "PL_pilot_skirmish_lobby" "Lobby PvP"
+ "PL_pl_rebuild_all_paths" "Ricostruisci tutti i percorsi"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "Esegui con ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "Partita privata Beta"
+ "PL_private_match_desc" "Gioca una partita privata personalizzata in una mappa e modalità a scelta.\n^FFC83200INVITA NETWORK o INVITA AMICI per giocare\n^FFC83200Giocatori: 1-16\nNessun progresso"
+ "PL_private_match_lobby" "Lobby Partita privata Beta"
+ "PL_promo_coop" "CO-OP per 4 giocatori"
+ "PL_raid" "Raid"
+ "PL_rise" "Ascesa 24/7"
+ "PL_rise_abbr" "ASC"
+ "PL_rise_desc" "Sempre e soltanto Ascesa. ^CCCCCC00Questa operazione effettuerà una ricerca per corrispondenze di ^FFC83200Cattura Bandiera^CCCCCC00, ^FFC83200Punto di controllo amplificato^CCCCCC00, ^FFC83200Piloti contro piloti^CCCCCC00, ^FFC83200Live Fire^CCCCCC00 e ^FFC83200Sopravvivenza Titan^CCCCCC00."
+ "PL_rise_lobby" "Lobby Ascesa 24/7"
+ "PL_rocket_arena" "Arena razzi"
+ "PL_rocket_arena_abbr" "LF"
+ "PL_rocket_arena_desc" "Regole di Live Fire classiche con EPG modificati.^FFC83200\nGiocatori: 6v6 *No Titan\nTempo limite: 90 s *Nessun rientro\nDimensioni max gruppo: 6"
+ "PL_rocket_arena_lobby" "Lobby Arena razzi"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "Impostazioni per il caricamento di una mappa dalla linea di comando. Non modificare il comando sottostante cmdlineMapLoad 1 perché è essenziale per il funzionamento."
+ "PL_speedball" "Live Fire"
+ "PL_speedball_desc" "Combatti per il possesso di una bandiera neutrale. Elimina la squadra nemica o possiedi la bandiera allo scadere del tempo per vincere il round. Vince il primo ad aggiudicarsi 5 round.\n^FFC83200Giocatori: 6v6 *Niente Titan\nTempo limite: 60 secondi per round *Nessun rientro\nDimensioni max gruppo: 6"
+ "PL_speedball_lobby" "Lobby Live Fire"
+ "PL_tactikill" "Logoramento Uccisione tattica"
+ "PL_tactikill_abbr" "ATT"
+ "PL_tactikill_desc" "Regole di logoramento classiche, ad eccezione del fatto che le abilità tattiche sono ripristinate completamente al momento dell’uccisione.\n^FFC83200Giocatori: 6v6 *AI\nTempo limite: 10 m\nDimensioni max gruppo: 6"
+ "PL_tactikill_lobby" "Lobby Logoramento Uccisione tattica"
+ "PL_titan_brawl" "Scontro tra Titan"
+ "PL_titan_brawl_abbr" "STT"
+ "PL_titan_brawl_desc" "Uccidi i Titan nemici. Piloti non consentiti.\n^FFC83200Giocatori: 5v5\nTempo limite: 10m\nDimensioni max gruppo: 5"
+ "PL_titan_brawl_hint" "Uccidi i Titan nemici.\nNessuna espulsione"
+ "PL_titan_brawl_lobby" "Lobby Scontro tra Titan"
+ "PL_titan_brawl_turbo" "Scontro tra Titan Turbo"
+ "PL_titan_brawl_turbo_abbr" "TTDM"
+ "PL_titan_brawl_turbo_desc" "Regole di Scontro tra Titan classiche con rigenerazione Scatto e generazione Nucleo più rapide.\n^FFC83200Giocatori: 5v5\nTempo limite: 10 m\nDimensioni max gruppo: 5"
+ "PL_titan_brawl_turbo_hint" "Uccidi i Titan nemici.\nNessuna espulsione"
+ "PL_titan_brawl_turbo_lobby" "Lobby Scontro tra Titan Turbo"
+ "PL_turbo_last_titan_standing" "Turbo ST"
+ "PL_turbo_last_titan_standing_abbr" "ST"
+ "PL_turbo_last_titan_standing_desc" "Regole ST classiche con rigenerazione Scatto e generazione Nucleo più rapide.\n^FFC83200Giocatori: 5v5 *Inizia come Titan\nTempo limite: 3 m per round *Nessun rientro\nDimensioni max gruppo: 5"
+ "PL_turbo_last_titan_standing_lobby" "Lobby ST Turbo"
+ "PL_variety_pack" "Remix"
+ "PL_variety_pack_desc" "Un numero di giocatori variabile e varie mappe in queste modalità:\n^FFC83200*Caccia alle taglie *Logoramento\n*Sopravvivenza Titan *Punto di controllo amplificato\n*Piloti contro piloti *Cattura bandiera"
+ "PL_variety_pack_lobby" "Lobby Remix"
+ "PL_wargames" "Giochi di guerra 24/7"
+ "PL_wargames_abbr" "GDG"
+ "PL_wargames_desc" "Sempre e soltanto Giochi di guerra. ^CCCCCC00Questa operazione effettuerà ricerche di corrispondenze di ^FFC83200Logoramento^CCCCCC00, ^FFC83200Cattura Bandiera^CCCCCC00, ^FFC83200Piloti contro piloti^CCCCCC00, ^FFC83200Punto di controllo amplificato^CCCCCC00 e ^FFC83200Sopravvivenza Titan^CCCCCC00."
+ "PL_wargames_lobby" "Lobby Giochi di guerra 24/7"
+ "WATCH_TUTORIAL" "GUARDA IL TUTORIAL"
+ }
+ }
+ "lang"
+ {
+ "Language" "english"
+ "Tokens"
+ {
+ "NO_PRICE" "Offer Expired"
+ "NO_PRICE_TWO_LINES" "Offer\nExpired"
+ "WATCH_TUTORIAL" "WATCH TUTORIAL"
+ "PL_promo_coop" "4 Player CO-OP"
+ "PL_limited_time_mode" "Limited Time Mode"
+ "PL_speedball" "Live Fire"
+ "PL_speedball_lobby" "Live Fire Lobby"
+ "PL_speedball_desc" "Fight for possession of a netural flag. Eliminate the enemy team or possess the flag when the clock runs out to win the round. Winner is the first to 5 rounds won.\n^FFC83200Players: 6v6 *No Titans\nTime Limit: 60 seconds per round *No Respawns\nMax Party Size: 6"
+ "PL_raid" "Raid"
+ "PL_hunted" "Hunted"
+ "PL_don" "Double or Nothing"
+ "PL_fd" "Frontier Defense"
+ "PL_fd_lobby" "Frontier Defense Lobby"
+ "PL_fd_desc" "Defend against waves of Remnant Fleet forces"
+ "PL_fd_easy" "Frontier Defense: Easy"
+ "PL_fd_easy_lobby" "Frontier Defense: Easy Lobby"
+ "PL_fd_easy_desc" "Crush the opposition"
+ "PL_fd_normal" "Frontier Defense: Regular"
+ "PL_fd_normal_lobby" "Frontier Defense: Regular Lobby"
+ "PL_fd_normal_desc" "Recommended for experienced players"
+ "PL_fd_hard" "Frontier Defense: Hard"
+ "PL_fd_hard_lobby" "Frontier Defense: Hard Lobby"
+ "PL_fd_hard_desc" "Skilled play is required"
+ "PL_fd_master" "Frontier Defense: Master"
+ "PL_fd_master_lobby" "Frontier Defense: Master Lobby"
+ "PL_fd_master_desc" "Only the best of the best will succeed"
+ "PL_fd_insane" "Frontier Defense: Insane"
+ "PL_fd_insane_lobby" "Frontier Defense: Insane Lobby"
+ "PL_fd_insane_desc" "You will not survive"
+ "PL_angel_city" "Angel City 24/7"
+ "PL_angel_city_lobby" "Angel City 24/7 Lobby"
+ "PL_angel_city_desc" "All Angel City, all the time.\n^FFC83200*Bounty Hunt *Attrition\n*Amped Hardpoint"
+ "PL_angel_city_abbr" "AC"
+ "PL_colony" "Colony 24/7"
+ "PL_colony_lobby" "Colony 24/7 Lobby"
+ "PL_colony_desc" "All Colony, all the time. ^CCCCCC00This will search for matches of ^FFC83200Attrition^CCCCCC00, ^FFC83200Pilots vs Pilots^CCCCCC00, and ^FFC83200Last Titan Standing^CCCCCC00."
+ "PL_colony_abbr" "COL"
+ "PL_rise" "Rise 24/7"
+ "PL_rise_lobby" "Rise 24/7 Lobby"
+ "PL_rise_desc" "All Rise, all the time. ^CCCCCC00This will search for matches of ^FFC83200CTF^CCCCCC00, ^FFC83200Amped Hardpoint^CCCCCC00, ^FFC83200Pilots vs Pilots^CCCCCC00, ^FFC83200Live Fire^CCCCCC00, and ^FFC83200Last Titan Standing^CCCCCC00."
+ "PL_rise_abbr" "RIS"
+ "PL_wargames" "Wargames 24/7"
+ "PL_wargames_lobby" "Wargames 24/7 Lobby"
+ "PL_wargames_desc" "All Wargames, all the time. ^CCCCCC00This will search for matches of ^FFC83200Attrition^CCCCCC00, ^FFC83200CTF^CCCCCC00, ^FFC83200Pilots vs Pilots^CCCCCC00, ^FFC83200Amped Hardpoint^CCCCCC00, and ^FFC83200Last Titan Standing^CCCCCC00."
+ "PL_wargames_abbr" "WGM"
+ "PL_ctf_lf" "CTF (Nitro)"
+ "PL_ctf_lf_lobby" "CTF Nitro Lobby"
+ "PL_ctf_lf_desc" "Fast paced Capture the Flag in selected maps.\n^FFC83200Players: 5v5\nMax Party Size: 5\n^F4D5A600Instant Flag Returns // No Titans\nNo Boosts // Phase Shift Drops Flag"
+ "PL_ctf_lf_abbr" "CTF-N"
+
+ "PL_nitro_mixtape" "Mixtape (Nitro)"
+ "PL_nitro_mixtape_lobby" "Mixtape Lobby"
+ "PL_nitro_mixtape_desc" "Fast paced CTF, MFD and PvP on selected maps.\n^FFC83200Players: 5v5\nMax Party Size: 5\n^F4D5A600Instant Flag Returns // No Titans\nNo Boosts // Phase Shift Drops Flag"
+ "PL_nitro_mixtape_abbr" "MXT-N"
+
+ "PL_nitro_ffa" "FFA (Nitro)"
+ "PL_nitro_ffa_lobby" "FFA Nitro Lobby"
+ "PL_nitro_ffa_desc" "Fast paced Free for All on selected maps.\n^FFC83200Players: 6\n^F4D5A600No Titans\nNo Boosts"
+ "PL_nitro_ffa_abbr" "FFA-N"
+
+ "PL_aitdm" "Attrition"
+ "PL_aitdm_lobby" "Attrition Lobby"
+ "PL_aitdm_desc" "Kill all enemies.\n^FFC83200Players: 6v6 *AI\nTime Limit: 10m\nMax Party Size: 6"
+ "PL_aitdm_abbr" "ATT"
+ "PL_attrition" "Bounty Hunt"
+ "PL_attrition_lobby" "Bounty Hunt Lobby"
+ "PL_attrition_desc" "Kill enemies to earn money. Earn more by 'banking' your bonus at designated locations.\n^FFC83200Players: 5v5 *AI\nTime Limit: 10m\nMax Party Size: 5"
+ "PL_attrition_abbr" "BH"
+ "PL_at_coop" "Escape (Co-op)"
+ "PL_at_coop_lobby" "Escape Lobby"
+ "PL_at_coop_desc" "You've just escaped prison with nothing more than a pistol. Survive until an evac can arrive. Kill enemies to earn money and buy weapons. \n^FFC83200Players: 6\nMax Party Size: 6"
+ "PL_pilot_skirmish" "Pilots vs. Pilots"
+ "PL_pilot_skirmish_lobby" "PvP Lobby"
+ "PL_pilot_skirmish_desc" "Kill enemy pilots. Titanfalls not permitted.\n^FFC83200Players: 8v8 *No Titans\nTime Limit: 10m\nMax Party Size: 8"
+ "PL_pilot_skirmish_abbr" "PvP"
+ "PL_hardpoint" "Amped Hardpoint"
+ "PL_hardpoint_lobby" "Amped Hardpoint Lobby"
+ "PL_hardpoint_desc" "Capture and hold a hardpoint to earn points. Amped Hardpoints give double points.\n^FFC83200Players: 6v6\nTime Limit: 10m\nMax Party Size: 6"
+ "PL_hardpoint_abbr" "AHP"
+ "PL_capture_the_flag" "Capture the Flag"
+ "PL_capture_the_flag_lobby" "CTF Lobby"
+ "PL_capture_the_flag_desc" "Steal the enemy flag and return it to your base while stopping the enemy team from taking your flag!\n^FFC83200Players: 5v5\nTime Limit: 12m\nMax Party Size: 5"
+ "PL_capture_the_flag_abbr" "CTF"
+ "PL_last_titan_standing" "Last Titan Standing"
+ "PL_last_titan_standing_lobby" "LTS Lobby"
+ "PL_last_titan_standing_desc" "All players start in Titans in this round-based elimination match-up. Winner is the first to 3 rounds won.\n^FFC83200Players: 5v5 *Start as Titan\nTime Limit: 3m per round *No Respawns\nMax Party Size: 5"
+ "PL_last_titan_standing_abbr" "LTS"
+ "PL_iron_last_titan_standing" "Iron LTS"
+ "PL_iron_last_titan_standing_lobby" ""
+ "PL_iron_last_titan_standing_desc" "Titans-only, round-based elimination match-up. Winner is the first to 3 rounds won.\n^FFC83200Players: 5v5 *NO PILOTS\nTime Limit: 3m per round *No Respawns\nMax Party Size: 5"
+ "PL_iron_last_titan_standing_abbr" "ILTS"
+ "PL_aegis_last_titan_standing" "Aegis LTS"
+ "PL_aegis_last_titan_standing_lobby" ""
+ "PL_aegis_last_titan_standing_desc" "Aegis Upgrades enabled. Round-based elimination match-up. Winner is the first to 3 rounds won.\n^FFC83200Players: 5v5 *Start as Titan\nTime Limit: 3m per round *No Respawns\nMax Party Size: 5"
+ "PL_aegis_last_titan_standing_abbr" "ALTS"
+ "PL_pilot_hunter" "Skirmish"
+ "PL_pilot_hunter_lobby" "Skirmish Lobby"
+ "PL_pilot_hunter_desc" "Kill enemy pilots and Titans. \n^FFC83200Players: 8v8\nTime Limit: 10m\nMax Party Size: 8"
+ "PL_pilot_hunter_abbr" "SKM"
+ "PL_titan_brawl" "Titan Brawl"
+ "PL_titan_brawl_lobby" "Titan Brawl Lobby"
+ "PL_titan_brawl_desc" "Kill enemy Titans. Pilots not permitted.\n^FFC83200Players: 5v5\nTime Limit: 10m\nMax Party Size: 5"
+ "PL_titan_brawl_abbr" "TTDM"
+ "PL_titan_brawl_hint" "Kill enemy Titans.\nNo Ejection // No Disembark"
+ "PL_aegis_titan_brawl" "Aegis Titan Brawl"
+ "PL_aegis_titan_brawl_lobby" "Aegis Titan Brawl Lobby"
+ "PL_aegis_titan_brawl_desc" "Kill enemy Titans. Aegis Upgrades enabled.\n^FFC83200Players: 5v5\nTime Limit: 10m\nMax Party Size: 5"
+ "PL_aegis_titan_brawl_abbr" "ATTDM"
+ "PL_aegis_titan_brawl_hint" "Kill enemy Titans.\nNo Ejection // No Disembark"
+ "PL_variety_pack" "Mixtape"
+ "PL_variety_pack_lobby" "Mixtape Lobby"
+ "PL_variety_pack_desc" "Varying player counts and including a variety of maps on these modes:\n^FFC83200*Bounty Hunt *Attrition\n*Last Titan Standing *Amped Hardpoint\n*Pilots vs. Pilots *Capture the Flag"
+ "PL_coliseum" "Coliseum"
+ "PL_coliseum_lobby" "Coliseum Lobby"
+ "PL_coliseum_desc" "One on one combat with enhanced mobility in a cage. Kill your opponent, win a round. Best 3 out of 5 wins an Advocate Gift reward.^FFC83200\nPlayers: 1v1 *No Titans\nTime Limit: 3m *No Respawns\n**^FFFFFF00REQUIRES COLISEUM TICKET or PAID ENTRY FEE"
+ "PL_ground_war" "8v8 Mixtape"
+ "PL_groud_war_lobby" "8v8 Mixtape Lobby"
+ "PL_ground_war_desc" "Includes Skirmish and Amped Hardpoint, with high player counts on all maps.\n^FFC83200Players: 8v8"
+ "PL_ground_war_abbr" "8v8"
+ "PL_ffa" "Free for All"
+ "PL_ffa_lobby" "Free for All Lobby"
+ "PL_ffa_desc" "Every pilot for themself, kill all enemies.\n^FFC83200Players: 1v11\nTime Limit: 10m\nMax Party Size: 1"
+ "PL_ffa_abbr" "FFA"
+ "PL_fra" "Free Agents"
+ "PL_fra_lobby" "Free Agents Lobby"
+ "PL_fra_desc" "You're running solo. Kill enemies to win. Collect 3 batteries for a Titanfall.\n^FFC83200Players: 1v11\nTime Limit: 15m\nMax Party Size: 1"
+ "PL_fra_abbr" "FRA"
+ "PL_default_name" "Default name"
+ "PL_default_lobbytitle" "Default lobbytitle"
+ "PL_default_description" "Default description"
+ "PL_load_a_map_on_the_command_line" "Load a map on the command line -devonly"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "Settings for when someone loads a map on the command line. Do not edit the cmdlineMapLoad 1 below - this makes this work."
+ "PL_pl_rebuild_all_paths" "Rebuild all paths"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "Run with ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "Private Match Beta"
+ "PL_private_match_lobby" "Private Match Beta Lobby"
+ "PL_private_match_desc" "Play a custom, private match on a map and mode of your choice.\n^FFC83200INVITE NETWORK or INVITE FRIENDS to play\n^FFC83200Players: 1-16\nNo Progression"
+
+ "PL_live_fire" "Live Fire"
+ "PL_live_fire_lobby" "Live Fire Lobby"
+ "PL_live_fire_desc" "Fast-paced combat in a Live Fire Arena. Win the round by killing all enemy pilots or possessing the flag when the timer runs out.^FFC83200\nPlayers: 6v6 *No Titans\nTime Limit: 60s *No Respawns\nMax Party Size: 6"
+ "PL_live_fire_abbr" "LF"
+
+ "PL_marked_for_death" "Marked For Death"
+ "PL_marked_for_death_lobby" "Marked For Death Lobby"
+ "PL_marked_for_death_desc" "Kill or protect the marked targets.\n^FFC83200Players: 6v6 \nTime Limit: 12m\nMax Party Size: 6"
+ "PL_marked_for_death_abbr" "MFD"
+
+ "PL_amped_tacticals" "Amped Tacticals"
+ "PL_amped_tacticals_lobby" "Amped Tacticals Lobby"
+ "PL_amped_tacticals_desc" "Classic Attrition rules except Tactical abilities are more powerful.\n^FFC83200Players: 6v6 *AI\nTime Limit: 10m\nMax Party Size: 6"
+ "PL_amped_tacticals_abbr" "ATT"
+
+ "PL_tactikill" "Tactikill Attrition"
+ "PL_tactikill_lobby" "Tactikill Attrition Lobby"
+ "PL_tactikill_desc" "Classic Attrition rules except Tactical abilities are fully reset upon kill.\n^FFC83200Players: 6v6 *AI\nTime Limit: 10m\nMax Party Size: 6"
+ "PL_tactikill_abbr" "ATT"
+
+ "PL_all_grapple" "Attack on Titanfall"
+ "PL_all_grapple_lobby" "Attack on Titanfall Lobby"
+ "PL_all_grapple_desc" "Classic Attrition rules except all Tactical abilities are replaced with Grapple.\n^FFC83200Players: 6v6 *AI\nTime Limit: 10m\nMax Party Size: 6"
+ "PL_all_grapple_abbr" "ATT"
+
+ "PL_all_holopilot" "The Great Bamboozle"
+ "PL_all_holopilot_lobby" "The Great Bamboozle Lobby"
+ "PL_all_holopilot_desc" "Classic Live Fire rules except all Tactical abilities are replaced with Holopilot.^FFC83200\nPlayers: 6v6 *No Titans\nTime Limit: 60s *No Respawns\nMax Party Size: 6"
+ "PL_all_holopilot_abbr" "LF"
+
+ "PL_all_phase" "The Otherside"
+ "PL_all_phase_lobby" "The Otherside Lobby"
+ "PL_all_phase_desc" "Classic Attrition rules except all Tactical abilities are replaced with Phase.\n^FFC83200Players: 6v6 *AI\nTime Limit: 10m\nMax Party Size: 6"
+ "PL_all_phase_abbr" "ATT"
+
+ "PL_rocket_arena" "Rocket Arena"
+ "PL_rocket_arena_lobby" "Rocket Arena Lobby"
+ "PL_rocket_arena_desc" "Classic Live Fire rules with modified EPGs.^FFC83200\nPlayers: 6v6 *No Titans\nTime Limit: 90s *No Respawns\nMax Party Size: 6"
+ "PL_rocket_arena_abbr" "LF"
+
+ "PL_turbo_last_titan_standing" "Turbo LTS"
+ "PL_turbo_last_titan_standing_lobby" "Turbo LTS Lobby"
+ "PL_turbo_last_titan_standing_desc" "Classic LTS rules with faster Dash regen and Core generation.\n^FFC83200Players: 5v5 *Start as Titan\nTime Limit: 3m per round *No Respawns\nMax Party Size: 5"
+ "PL_turbo_last_titan_standing_abbr" "LTS"
+
+ "PL_all_spicy" "Spicy Attrition"
+ "PL_all_spicy_lobby" "Spicy Attrition Lobby"
+ "PL_all_spicy_desc" "Classic Attrition rules except all Tactical abilities are replaced with Ticks.\n^FFC83200Players: 6v6 *AI\nTime Limit: 10m\nMax Party Size: 6"
+ "PL_all_spicy_abbr" "ATT"
+
+ "PL_titan_brawl_turbo" "Turbo Titan Brawl"
+ "PL_titan_brawl_turbo_lobby" "Turbo Titan Brawl Lobby"
+ "PL_titan_brawl_turbo_desc" "Classic Titan Brawl rules with faster Dash regen and Core generation.\n^FFC83200Players: 5v5\nTime Limit: 10m\nMax Party Size: 5"
+ "PL_titan_brawl_turbo_abbr" "TTDM"
+ "PL_titan_brawl_turbo_hint" "Kill enemy Titans.\nNo Ejection // No Disembark"
+
+ "PL_sbox" "Sandbox"
+ "PL_sbox_lobby" "Sandbox Lobby"
+ "PL_sbox_desc" "like gmod but worse"
+ "PL_sbox_abbr" "SBOX"
+ "GAMEMODE_SBOX" "Sandbox"
+
+ "PL_gg" "Gun Game"
+ "PL_gg_lobby" "Gun Game Lobby"
+ "PL_gg_desc" "Get a kill with each gun to win."
+ "PL_gg_abbr" "GG"
+ "GAMEMODE_GG" "Gun Game"
+
+ "PL_tt" "Titan Tag"
+ "PL_tt_lobby" "Titan Tag Lobby"
+ "PL_tt_desc" "Earn points while in your titan. Destroy a titan to get your own."
+ "PL_tt_abbr" "TT"
+ "GAMEMODE_TT" "Titan Tag"
+
+ "PL_inf" "Infection"
+ "PL_inf_lobby" "Infection Lobby"
+ "PL_inf_desc" "Survivors are infected when killed."
+ "PL_inf_abbr" "INF"
+ "GAMEMODE_INF" "Infection"
+ "INFECTION_YOU_ARE_INFECTED" "You've been Infected!"
+ "INFECTION_KILL_SURVIVORS" "Infect All Remaining Survivors."
+ "INFECTION_FIRST_INFECTED" "%s1 is the First Infected."
+ "INFECTION_LAST_SURVIVOR" "%s1 is the Last Survivor!"
+ "INFECTION_KILL_LAST_SURVIVOR" "Infect them before time runs out!"
+ "INFECTION_YOU_ARE_LAST_SURVIVOR" "You are the Last Survivor!"
+ "INFECTION_SURVIVE_LAST_SURVIVOR" "Survive."
+
+ // these are defined in r1_english but titan war is a shit name so i'm changing it to another one that was referenced in development
+ "GAMEMODE_fw" "Frontier War"
+ "PL_fw" "Frontier War"
+ "PL_fw_lobby" "Frontier War Lobby"
+ "PL_fw_desc" "Destroy the enemy's harvester and protect your own"
+ "PL_fw_abbr" "FW"
+
+ "GAMEMODE_kr" "Amped Killrace"
+ "PL_kr" "Amped Killrace"
+ "PL_kr_lobby" "Amped Killrace Lobby"
+ "PL_kr_desc" "Get kills to increase the length of your killrace. Collect the flag to start it. Set the record to win"
+ "PL_kr_abbr" "KR"
+ "SCOREBOARD_KR_RECORD" "Kill Record"
+ "KR_NEW_RACER" "%s1 is the amped killracer"
+ "KR_YOU_ARE_NEW_RACER" "You are the amped killracer"
+ "KR_YOU_SET_NEW_RECORD" "Set a New Kill Record!"
+ "KR_FLAG_INCOMING" "Flag incoming"
+ "KR_COLLECT_FLAG" "Collect it to become the Killracer!"
+ "KR_ENEMY_KILLRACE_OVER" "%s1's killrace is over"
+ "KR_YOUR_KILLRACE_OVER" "Your killrace is over"
+ "KR_YOUR_KILLRACE_SCORE" "You got %s1 kills."
+
+ "GAMEMODE_fastball" "Fastball"
+ "PL_fastball" "Fastball"
+ "PL_fastball_lobby" "Fastball Lobby"
+ "PL_fastball_desc" "Permadeath. Hack control panels to win rounds and respawn your teammates."
+ "PL_fastball_abbr" "FB"
+ "FASTBALL_PANEL_CAPTURED" "%s1 captured panel %s2"
+ "SCOREBOARD_FASTBALL_HACKS" "Panels Captured"
+
+ // FAQ - Community
+ //
+
+ "COMMUNITYUPDATE_NAME" "Community"
+ "COMMUNITYUPDATE_DESC" "`3Titanfall Community Updates`0\n\nGet latest info and see stuff we think is rad from around the web.\n\nFor more:\n`2%$rui/bullet_point%`0Follow us on Twitter `1@Respawn`0\n`2%$rui/bullet_point%`0Like us on `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0Join us at `1www.respawn.com`0"
+
+ "COMMUNITYUPDATE_00_Q" "\"Postcards from the Frontier\" Trailer"
+ "COMMUNITYUPDATE_00_A" "Your adventure across the stunning vistas of the Frontier continues with the latest DLC drop for Titanfall 2: Postcards From the Frontier. Featuring new and familiar locations along with a new collection of Elite Weapon Warpaints, the Frontier has never looked better.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_01_Q" "Postcards from the Frontier: The Patch Notes"
+ "COMMUNITYUPDATE_01_A" "Read all the changes that come with the Postcards from the Frontier patch.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_02_Q" "Titanfall: Assault \"Launch\" Trailer"
+ "COMMUNITYUPDATE_02_A" "Coming soon to mobile devices, Titanfall: Assault is an exciting real time RTS set in the Titanfall universe in partnership with Particle City. Watch it in action here! \n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_03_Q" "Titanfall Assault: \"Learn the Basics\" video"
+ "COMMUNITYUPDATE_03_A" "Iniquity walks us through the tutorial and gives basic tips for Titanfall Assault. This is the perfect way to get started learning about the game.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_04_Q" "Respawn Plays Frontier Defense"
+ "COMMUNITYUPDATE_04_A" "Listen to some of the key folks behind Frontier Defense talk about the history and making of the mode and watch us play a few rounds! \n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_05_Q" "Community Creations: Get to the Point"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger created a fun montage showing off a plethora of sweet moves with the Pulse Blade. \n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_06_Q" "Community Creations: Northstar Pilot Hunt"
+ "COMMUNITYUPDATE_06_A" "ConzeyG via reddit shows off brutal efficiency with Northstar picking off Pilots in Marked for Death mode. \n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_07_Q" "\"Operation Frontier Shield\" Gameplay Trailer"
+ "COMMUNITYUPDATE_07_A" "Available now! Frontier Defense returns along with Rise as well as brand new warpaints to buy and new a new Live Fire map. Watch it all in action here.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_08_Q" "\"The War Games\" Gameplay Trailer"
+ "COMMUNITYUPDATE_08_A" "Available now! The iconic War Games map returns to Titanfall 2 looking better than ever. Also check out the new execution and new live fire map, Traffic in action.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_09_Q" "\"Monarch's Reign\" Gameplay Trailer"
+ "COMMUNITYUPDATE_09_A" "This DLC features the addition of the 7th multiplayer Titan: Monarch, a remaster of the map, Relic, a new execution as well as purchasable Ronin and Tone Prime Titans, more camos, banners, and nose art. Watch the action here.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_10_Q" "\"A Glitch in the Frontier\" Gameplay Trailer"
+ "COMMUNITYUPDATE_10_A" "All is not as it appears on the Frontier - prepare to enter the newest free DLC pack for Titanfall 2: A Glitch in the Frontier - featuring the new map “Glitchâ€. Inspired by Captain Lastimosa’s home planet of Harmony, vertical drops and long, twisting paths dominate the environment, perfect for chaining together long wall runs to seamlessly glide across the map. A new Live Fire map also joins the fold: Deck, which features tight interior spaces, exposed courtyards, and watchful drones circling overhead. If you’re feeling overwhelmed, the ever-helpful M.R.V.N.s are now here as an all-new faction to lend a cheery robotic hand. Finally, the new Pulse Blade execution is available to unlock to use whenever you feel the need to get your point across.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_11_Q" "\"Colony Reborn\" Gameplay Trailer"
+ "COMMUNITYUPDATE_11_A" "Return to an iconic fan favorite multiplayer map from the original Titanfall: Colony. Mix in all of the new tacticals & Titans from Titanfall 2, and Pilots & Titans will have to keep their wits about them to survive in this tightly-packed idyllic village of back alleys, blind corners and exposed rooftops; available for free on March 30th for all players. The “Colony Reborn†DLC pack includes the return of classic weapons like the tricked out R-101 assault rifle, a new grapple execution, and new cosmetic options for purchase to look your best while you paint the town red.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_12_Q" "\"Welcome to Live Fire\" Gameplay Trailer"
+ "COMMUNITYUPDATE_12_A" "Introducing Live Fire: a lightning fast 6v6 Pilot only mode that brings competitive, close quarter combat to the forefront. Featuring two brand new maps designed specifically for Live Fire: Stacks and Meadow. These two free maps are tight, enclosed death boxes designed specifically for the fast-paced, intense nature of the mode.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_13_Q" "\"Welcome to Angel City\" Gameplay Trailer"
+ "COMMUNITYUPDATE_13_A" "Experience a remastered version of the fan favorite map from the original Titanfall, `1Angel City`0. Stand by for the first free DLC for Titanfall 2 with `1Angel City's Most Wanted`0 available on December 1st for all players. This content includes new cosmetic options to add more flair to to the Frontier. \n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_14_Q" "\"Encore\" Accolades Trailer"
+ "COMMUNITYUPDATE_14_A" "Welcome back.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_15_Q" "\"Become One\" Single Player Gameplay Trailer"
+ "COMMUNITYUPDATE_15_A" "Two legends, one legacy.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ "COMMUNITYUPDATE_16_Q" "\"Pilots\" Multiplayer Gameplay Trailer"
+ "COMMUNITYUPDATE_16_A" "Limitless.\n\n\nPress `2%[A_BUTTON|MOUSE1]%`0 to view."
+
+ // faq_community_count = 17
+
+ // FAQ - Patch Notes
+ //
+
+ "KNB_SUBJECT_00_NAME" "Game Updates"
+ "KNB_SUBJECT_00_DESC" "`3What's New in Titanfall?`0\n\nCheck here to see what's changed in Titanfall 2!"
+
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 New community created callsign banners\n\n`2%$rui/bullet_point%`0All advocate gifts are now purchasable with credits\n\n`2%$rui/bullet_point%`0New Purchasable Store Content\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% November 28 - Harvest Time"
+
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Pistol Primary Slot\n\n`2%$rui/bullet_point%`0Halloween Banners\n\n`2%$rui/bullet_point%`0Balance Changes\n\n`2%$rui/bullet_point%`0New Purchasable Store Content\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% October 31 - Tricks and Treats"
+
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0Frontier Defense - Support for 3 more maps - Drydock, Angel City and Exoplanet.\n\n`2%$rui/bullet_point%`0New Live Fire Map - UMA\n\n`2%$rui/bullet_point%`0New Pilot Execution - Hole In The Wall\n\n`2%$rui/bullet_point%`0New Purchasable Store Content"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% August 29 - Postcards from the Frontier"
+
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Frontier Defense - A new cooperative mode where you join forces with up to three other players to defend a vital objective from increasingly intense waves of AI combatants. Communication and adaptation are key to surviving.\n\n`2%$rui/bullet_point%`0New Map - Rise\n\n`2%$rui/bullet_point%`0New Live Fire Map - Township\n\n`2%$rui/bullet_point%`0New Purchasable Store Content"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% July 25 - Operation Frontier Shield"
+
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0New Map - Wargames\n\n`2%$rui/bullet_point%`0New Live Fire Map - Traffic\n\n`2%$rui/bullet_point%`0New Pilot Execution - Shadow Boxing\n\n`2%$rui/bullet_point%`03rd Weapon Slot\n\n`2%$rui/bullet_point%`0Private Match Settings"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% June 27 - The War Games"
+
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0New Titan - Monarch\n\n`2%$rui/bullet_point%`0New Map - Relic\n\n`2%$rui/bullet_point%`0New Pilot Execution - Now You See Me\n\n`2%$rui/bullet_point%`0New Purchasable Store Content"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% May 30 - Monarch's Reign"
+
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0New Map - Glitch\n\n`2%$rui/bullet_point%`0New Live Fire Map - Deck\n\n`2%$rui/bullet_point%`0New Faction - M.R.V.N.\n\n`2%$rui/bullet_point%`0New Pilot Execution - Get to the Point\n\n`2%$rui/bullet_point%`0Max Gen increased to 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% Apr 25 - A Glitch in the Frontier"
+
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0New Map - Colony\n\n`2%$rui/bullet_point%`0New Pilot Execution - Curb Check\n\n`2%$rui/bullet_point%`0New Weapon - R-101\n\n`2%$rui/bullet_point%`0New Purchasable Store Content"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% Mar 30 - Colony Reborn"
+
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0Live Fire - A new Pilot vs Pilot elimination game mode! It’s a 6v6 round-based with no respawns, you will have one minute to eliminate the opposing team in order to win the round. You can also win the round if your team is holding the neutral flag when the round timer ends. The team to win 5 rounds first wins the match.\n\n`2%$rui/bullet_point%`0New Live Fire Map - Meadow\n\n`2%$rui/bullet_point%`0New Live Fire Map - Stacks\n\n`2%$rui/bullet_point%`0New Pilot Execution - Late Hit"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% Feb 23 - Live Fire"
+
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0New Map - Angel City\n\n`2%$rui/bullet_point%`0New Weapon - B3 Wingman Elite\n\n`2%$rui/bullet_point%`0New Pilot Execution - Inner Pieces\n\n`2%$rui/bullet_point%`0New Titan Kits\n\n`2%$rui/bullet_point%`0New Purchasable Store Content"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% Nov 30 - Angel City's Most Wanted"
+
+ "MP_ANGEL_CITY_FD_WAVE_1" "Chained to the Sky"
+ "MP_ANGEL_CITY_FD_WAVE_2" "Formlessness"
+ "MP_ANGEL_CITY_FD_WAVE_3" "Insurgency"
+ "MP_ANGEL_CITY_FD_WAVE_4" "An Obstinate Lot"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Roll the Hard Six"
+
+ "MP_DRYDOCK_FD_WAVE_1" "To Swim in Strange Water"
+ "MP_DRYDOCK_FD_WAVE_2" "They've got no bullets!"
+ "MP_DRYDOCK_FD_WAVE_3" "Blinded by Science"
+ "MP_DRYDOCK_FD_WAVE_4" "High Pressure System"
+ "MP_DRYDOCK_FD_WAVE_5" "Eye of the Storm"
+
+ "MP_THAW_FD_WAVE_1" "Situational Awareness"
+ "MP_THAW_FD_WAVE_2" "Strength in Numbers"
+ "MP_THAW_FD_WAVE_3" "Henchman 21"
+ "MP_THAW_FD_WAVE_4" "Fun in the Fire"
+ "MP_THAW_FD_WAVE_5" "Serve Well the Guns"
+ }
+ }
+ "lang"
+ {
+ "Language" "french"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "Vos aventures à travers les environnements de la Frontière continuent avec le nouveau DLC de Titanfall 2 : Cartes postales de la Frontière. Personnalisez votre arsenal avec des peintures de guerre d'élite et combattez dans des lieux nouveaux ou familiers. La Frontière n'aura jamais été aussi belle !\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_00_Q" "Trailer \"Cartes postales de la Frontière\""
+ "COMMUNITYUPDATE_01_A" "Consultez la liste de changements inclus au patch Cartes postales de la Frontière.\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_01_Q" "Cartes postales de la Frontière : Notes "
+ "COMMUNITYUPDATE_02_A" "Prochainement disponible sur appareils mobiles, Titanfall: Assault est un STR captivant dans l'univers Titanfall développé en collaboration avec Particle City. Regardez la vidéo ! \n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_02_Q" "Titanfall: Assault - Trailer de lancement"
+ "COMMUNITYUPDATE_03_A" "Iniquity nous présente le didacticiel et les bases de Titanfall: Assault. Obtenez vite plus d'informations sur les mécaniques de ce nouveau jeu.\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_03_Q" "Titanfall: Assault - \"Les bases du jeu\""
+ "COMMUNITYUPDATE_04_A" "Regardez les créateurs de Défense frontalière évoquer l'histoire et la conception de ce mode de jeu manette en main ! \n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_04_Q" "Respawn Plays : Défense frontalière"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger a créé un montage de ses meilleures actions avec sa lame à impulsion. \n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_05_Q" "Créations communautaires : impulsion fatale"
+ "COMMUNITYUPDATE_06_A" "ConzeyG (sur reddit) nous démontre de toute son efficacité avec le Titan Northstar dans le mode de jeu Condamnation. \n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_06_Q" "Créations : Northstar chasseur de pilotes "
+ "COMMUNITYUPDATE_07_A" "Disponible ! Défense frontalière revient avec la carte Expansion, de nouvelles peintures de guerre payantes et une nouvelle carte Live Fire. Regardez vite le trailer !\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0."
+ "COMMUNITYUPDATE_07_Q" "Trailer gameplay \"Opération Frontier Shield\""
+ "COMMUNITYUPDATE_08_A" "Disponible dès maintenant ! La carte à succès Jeux de guerre fait son grand retour dans Titanfall 2. Découvrez également une exécution inédite et Trafic, une toute nouvelle carte Live Fire.\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_08_Q" "Trailer gameplay \"Jeux de guerre\""
+ "COMMUNITYUPDATE_09_A" "Notre nouveau DLC inclut le Monarch (7e Titan multijoueur du jeu), une version remasterisée de la carte Relique, une exécution inédite, ainsi que du contenu payant incluant les Titans Ronin Prime et Tone Prime avec des nouveaux camouflages, bannières et personnalisations de cockpit. Regardez vite le trailer !\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_09_Q" "Trailer gameplay \"Règne du Monarch\""
+ "COMMUNITYUPDATE_10_A" "Les apparences sont parfois trompeuses à la Frontière ! Préparez-vous pour Glitch frontalier, le nouveau DLC gratuit de Titanfall 2 qui inclut la carte inédite \"Glitch\". Inspirée par Harmony, la planète natale du capitaine Lastimosa, cette carte regorge de voies verticales et de longs chemins tortueux qui vous permettront d'enchaîner les courses sur les murs. Découvrez également \"Pont\", une toute nouvelle carte Live Fire avec des sections intérieures étroites et des zones exposées survolées par des drones aériens. Vous ne serez pas seuls sur ces nouveaux champs de bataille : les serviables Marvins s'invitent au combat en tant que nouvelle faction pour vous prêter main forte ! Débloquez également la nouvelle exécution Impulsion fatale pour faire passer un message à tous vos ennemis.\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_10_Q" "Trailer gameplay \"Glitch frontalier\""
+ "COMMUNITYUPDATE_11_A" "Découvrez la nouvelle version de Colonie, l'une des cartes multijoueurs favorites des fans du premier Titanfall ! Combinez les différentes capacités tactiques et les classes de Titan de Titanfall 2 à compter du 30 mars pour éliminer vos adversaires à travers les ruelles tortueuses, les recoins dangereux et les toitures exposées de ce village idyllique. Le DLC gratuit \"Nouvelle colonie\" inclut des armes mythiques comme la nouvelle version du fusil d'assaut R-101, une exécution au grappin inédite et de nouvelles options cosmétiques payantes pour combattre avec encore plus de style !\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_11_Q" "Trailer gameplay \"Nouvelle colonie\""
+ "COMMUNITYUPDATE_12_A" "Découvrez Live Fire, un tout nouveau mode de jeu pour pilotes proposant des affrontements ultra-rapides à 6c6 en combat rapproché. Combattez sur deux nouvelles cartes spécialement conçues pour Live Fire : Empilements et Prairie. Ces deux cartes comportent des zones de jeu resserrées parfaitement adaptées au caractère intense et débridé de ce nouveau mode de jeu.\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_12_Q" "Trailer gameplay \"Live Fire\""
+ "COMMUNITYUPDATE_13_A" "Découvrez la version remasterisée d'`1Angel City`0, la carte favorite des joueurs du premier Titanfall ! Préparez-vous pour le premier DLC gratuit de Titanfall 2, `1Liste noire d'Angel City`0, qui sera disponible le 1er décembre pour tous les joueurs. Ce nouveau contenu inclut également des optimisations cosmétiques qui vous aideront à vous démarquer sur les champs de bataille de la Frontière. \n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_13_Q" "Trailer gameplay \"Angel City\""
+ "COMMUNITYUPDATE_14_A" "Bon retour parmi nous !\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_14_Q" "Trailer - \"Encore\""
+ "COMMUNITYUPDATE_15_A" "Deux légendes, un héritage.\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_15_Q" "Trailer Solo - \"Ne faites plus qu'un\""
+ "COMMUNITYUPDATE_16_A" "Sans limite.\n\n\nAppuyez sur `2%[A_BUTTON|MOUSE1]%`0 pour voir la vidéo."
+ "COMMUNITYUPDATE_16_Q" "Trailer multijoueur - \"Pilotes\""
+ "COMMUNITYUPDATE_DESC" "`3Titanfall - Mises à jour de la communauté`0\n\nDes infos et des liens en provenance de tout le web.\n\nPour ne rien manquer :\n`2%$rui/bullet_point%`0Suivez-nous sur Twitter `1@Respawn`0\n`2%$rui/bullet_point%`0Rendez-vous sur `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0Rejoignez-nous sur `1www.respawn.com`0"
+ "COMMUNITYUPDATE_NAME" "Communauté"
+ "KNB_SUBJECT_00_DESC" "`3Quoi de neuf dans Titanfall ?`0\n\nPrenez connaissance des nouvelles fonctionnalités de Titanfall 2 !"
+ "KNB_SUBJECT_00_NAME" "Mises à jour du jeu"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 Nouvelles bannières créées par la communauté\n\n`2%$rui/bullet_point%`0Tous les cadeaux sont désormais achetables avec des crédits\n\n`2%$rui/bullet_point%`0Nouveau contenu payant dans la Boutique\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 28 novembre - Récolte"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Emplacement principal pour pistolet\n\n`2%$rui/bullet_point%`0Bannières Halloween\n\n`2%$rui/bullet_point%`0Optimisations de l'équilibre\n\n`2%$rui/bullet_point%`0Nouveau contenu payant dans la Boutique\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 31 octobre - Bundle Halloween"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0Défense frontalière - Prise en charge de 3 cartes supplémentaires : Cale sèche, Angel City et Exoplanète.\n\n`2%$rui/bullet_point%`0Nouvelle carte Live Fire - UMA\n\n`2%$rui/bullet_point%`0Nouvelle exécution de pilote - Trou dans le mur\n\n`2%$rui/bullet_point%`0Nouveau contenu payant dans la Boutique"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 29 août - Cartes postales de la Frontière"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Défense frontalière - Nouveau mode coopératif, qui vous propose de vous allier à un maximum de trois autres joueurs pour défendre un objectif stratégique face à des vagues de combattants IA de plus en plus intenses. Votre survie dépendra de vos facultés de communication et d'adaptation !\n\n`2%$rui/bullet_point%`0Nouvelle carte - Expansion\n\n`2%$rui/bullet_point%`0Nouvelle carte Live Fire - Cité\n\n`2%$rui/bullet_point%`0Nouveau contenu payant dans la Boutique"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 25 juillet - Opération Frontier Shield"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0Nouvelle carte - Jeux de guerre\n\n`2%$rui/bullet_point%`0Nouvelle carte Live Fire - Trafic\n\n`2%$rui/bullet_point%`0Nouvelle exécution de pilote - Boxe furtive\n\n`2%$rui/bullet_point%`0Troisième emplacement d'armement\n\n`2%$rui/bullet_point%`0Paramètres de partie privée"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 27 juin - Jeux de guerre"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0Nouveau Titan - Monarch\n\n`2%$rui/bullet_point%`0Nouvelle carte - Relique\n\n`2%$rui/bullet_point%`0Nouvelle exécution - Vision mortelle\n\n`2%$rui/bullet_point%`0Nouveau contenu payant dans la Boutique"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 30 mai - Règne du Monarch"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0Nouvelle carte - Glitch\n\n`2%$rui/bullet_point%`0Nouvelle carte Live Fire - Pont\n\n`2%$rui/bullet_point%`0Nouvelle faction - Marvin\n\n`2%$rui/bullet_point%`0Nouvelle exécution de pilote - Impulsion fatale\n\n`2%$rui/bullet_point%`0Niveau de génération maximal augmenté à 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 25 avril - Glitch frontalier"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0Nouvelle carte - Colonie\n\n`2%$rui/bullet_point%`0Nouvelle exécution de pilote - Coup du grappin\n\n`2%$rui/bullet_point%`0Nouvelle arme - R-101\n\n`2%$rui/bullet_point%`0Nouveau contenu payant dans la Boutique"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 30 mars - Nouvelle colonie"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0Live Fire - Un nouveau mode Pilotes contre pilotes à élimination ! Dans ce mode à 6c6 en plusieurs manches, vous disposerez d'une minute pour éliminer l'équipe adverse et remporter la manche. Vous pourrez aussi gagner la manche si votre équipe est en possession du drapeau neutre à la fin du temps imparti. La première équipe à gagner 5 manches remporte la partie.\n\n`2%$rui/bullet_point%`0Nouvelle carte Live Fire - Prairie\n\n`2%$rui/bullet_point%`0Nouvelle carte Live Fire - Empilements\n\n`2%$rui/bullet_point%`0Nouvelle exécution de pilote - Frappe spéciale"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 23 février - Live Fire"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0Nouvelle carte - Angel City\n\n`2%$rui/bullet_point%`0Nouvelle arme - Wingman B3 Élite\n\n`2%$rui/bullet_point%`0Nouvelle exécution de pilote - Viscéral\n\n`2%$rui/bullet_point%`0Nouveaux kits pour Titan\n\n`2%$rui/bullet_point%`0Nouveau contenu payant dans la Boutique"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 30 novembre - Liste noire d'Angel City"
+ "MP_ANGEL_CITY_FD_WAVE_1" "Enchaînement céleste"
+ "MP_ANGEL_CITY_FD_WAVE_2" "Menace informe"
+ "MP_ANGEL_CITY_FD_WAVE_3" "Insurrection"
+ "MP_ANGEL_CITY_FD_WAVE_4" "Obstination"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Tout pour le tout"
+ "MP_DRYDOCK_FD_WAVE_1" "En eaux troubles"
+ "MP_DRYDOCK_FD_WAVE_2" "En manque de munitions"
+ "MP_DRYDOCK_FD_WAVE_3" "Aveuglement scientifique"
+ "MP_DRYDOCK_FD_WAVE_4" "Haute pression"
+ "MP_DRYDOCK_FD_WAVE_5" "Oeil du cyclone"
+ "MP_THAW_FD_WAVE_1" "Connaissance situationnelle"
+ "MP_THAW_FD_WAVE_2" "La force par le nombre"
+ "MP_THAW_FD_WAVE_3" "Hommes de main"
+ "MP_THAW_FD_WAVE_4" "Divertissement ardent"
+ "MP_THAW_FD_WAVE_5" "Pour l'honneur"
+ "NO_PRICE" "Offre expirée"
+ "NO_PRICE_TWO_LINES" "Offre\nexpirée"
+ "PL_aegis_last_titan_standing" "DT Aegis"
+ "PL_aegis_last_titan_standing_abbr" "DTA"
+ "PL_aegis_last_titan_standing_desc" "Optimisations Aegis autorisées. Plusieurs manches à élimination. La première équipe à gagner 3 manches remporte la partie.\n^FFC83200Joueurs : 5c5 *Début en Titan\nLimite de temps : 3 min par manche *Aucune réapparition\nTaille max. du groupe : 5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "Combat de Titan Aegis"
+ "PL_aegis_titan_brawl_abbr" "CDTA"
+ "PL_aegis_titan_brawl_desc" "Détruisez les Titans ennemis. Optimisations Aegis autorisées.\n^FFC83200Joueurs : 5c5\nLimite de temps : 10 min\nTaille max. du groupe : 5"
+ "PL_aegis_titan_brawl_hint" "Détruisez les Titans ennemis.\nPas d'éjection"
+ "PL_aegis_titan_brawl_lobby" "Salon Combat de Titan Aegis"
+ "PL_aitdm" "Attrition"
+ "PL_aitdm_abbr" "ATT"
+ "PL_aitdm_desc" "Tuez tous vos ennemis.\n^FFC83200Joueurs : 6c6 *IA\nLimite de temps : 10 min\nTaille max. du groupe : 6"
+ "PL_aitdm_lobby" "Salon Attrition"
+ "PL_all_grapple" "Attaque sur Titanfall"
+ "PL_all_grapple_abbr" "ATT"
+ "PL_all_grapple_desc" "Règles Attrition classiques avec toutes les capacités tactiques remplacées par le grappin.\n^FFC83200Joueurs : 6c6 *IA\nLimite de temps : 10 min\nTaille max. du groupe : 6"
+ "PL_all_grapple_lobby" "Salon Attaque sur Titanfall"
+ "PL_all_holopilot" "Holo-bataille"
+ "PL_all_holopilot_abbr" "LF"
+ "PL_all_holopilot_desc" "Règles Live Fire classiques avec toutes les capacités tactiques remplacées par le module holo-pilote.^FFC83200\nJoueurs : 6c6 *Aucun Titan\nLimite de temps : 60 s *Aucune réapparition\nTaille max. du groupe : 6"
+ "PL_all_holopilot_lobby" "Salon Holo-bataille"
+ "PL_all_phase" "De l'autre côté"
+ "PL_all_phase_abbr" "ATT"
+ "PL_all_phase_desc" "Règles Attrition classiques avec toutes les capacités tactiques remplacées par la téléportation.\n^FFC83200Joueurs : 6c6 *IA\nLimite de temps : 10 min\nTaille max. du groupe : 6"
+ "PL_all_phase_lobby" "Salon De l'autre côté"
+ "PL_all_spicy" "Attritique"
+ "PL_all_spicy_abbr" "ATT"
+ "PL_all_spicy_desc" "Règles Attrition classiques avec toutes les capacités tactiques remplacées par des tiques.\n^FFC83200Joueurs : 6c6 *IA\nLimite de temps : 10 min\nTaille max. du groupe : 6"
+ "PL_all_spicy_lobby" "Salon Attritique"
+ "PL_amped_tacticals" "Tactiques amplifiées"
+ "PL_amped_tacticals_abbr" "ATT"
+ "PL_amped_tacticals_desc" "Règles Attrition classiques avec amplification de toutes les capacités tactiques.\n^FFC83200Joueurs : 6c6 *IA\nLimite de temps : 10 min\nTaille max. du groupe : 6"
+ "PL_amped_tacticals_lobby" "Salon Tactiques amplifiées"
+ "PL_ANGEL_CITY" "Angel City 24/7"
+ "PL_angel_city_abbr" "AC"
+ "PL_angel_city_desc" "Tout Angel City, tout le temps.\n^FFC83200*Chasse aux primes *Attrition\n*Point clé amplifié"
+ "PL_angel_city_lobby" "Salon Angel City 24/7"
+ "PL_at_coop" "Évasion (Coop)"
+ "PL_at_coop_desc" "Vous venez de vous évader de prison avec un pistolet pour seule arme. Survivez jusqu’à l’arrivée du vaisseau d’évacuation. Abattez des ennemis pour gagner de l’argent et acheter des armes. \n^FFC83200Joueurs : 6\n^FFC83200Taille max. du groupe : 6"
+ "PL_at_coop_lobby" "Salon Évasion"
+ "PL_attrition" "Chasse aux primes"
+ "PL_attrition_abbr" "CP"
+ "PL_attrition_desc" "Abattez vos ennemis pour gagner de l'argent. Augmentez votre score en encaissant des bonus aux emplacements désignés.\n^FFC83200Joueurs : 5c5 *IA\nLimite de temps : 10 min\nTaille max. du groupe : 5"
+ "PL_attrition_lobby" "Salon Chasse aux primes"
+ "PL_capture_the_flag" "Capture de drapeau"
+ "PL_capture_the_flag_abbr" "CDD"
+ "PL_capture_the_flag_desc" "Emparez-vous du drapeau adverse et rapportez-le à votre base, tout en empêchant l’ennemi de prendre le vôtre.\n^FFC83200Joueurs : 5c5\nLimite de temps : 12 min\nTaille max. du groupe : 5"
+ "PL_capture_the_flag_lobby" "Salon CDD"
+ "PL_coliseum" "Colisée"
+ "PL_coliseum_desc" "Duels rapides en cage à 3 manches gagnantes avec cadeau. Abattez votre adversaire pour remporter la manche.^FFC83200\nJoueurs : 1c1 *Aucun Titan\nLimite de temps : 3 min *Aucune réapparition\n**^FFFFFF00REQUIERT TICKET COLISÉE ou INSCRIPTION PAYANTE"
+ "PL_coliseum_lobby" "Salon Colisée"
+ "PL_colony" "Colonie 24/7"
+ "PL_colony_abbr" "COL"
+ "PL_colony_desc" "Affrontements exclusivement sur la carte Colonie. ^CCCCCC00Vous allez chercher des parties en modes ^FFC83200Attrition^CCCCCC00, ^FFC83200Pilotes contre pilotes^CCCCCC00 et ^FFC83200Dernier Titan^CCCCCC00."
+ "PL_colony_lobby" "Salon Colonie 24/7"
+ "PL_ctf_lf" "CDD (Nitro)"
+ "PL_ctf_lf_abbr" "CDD-N"
+ "PL_ctf_lf_desc" "Affrontements rapides en Capture de drapeau sur une sélection de cartes.\n^FFC83200Joueurs : 5c5\nTaille max. du groupe : 5\n^F4D5A600Retours instantanés du drapeau"
+ "PL_ctf_lf_lobby" "Salon CDD Nitro"
+ "PL_default_description" "Description par défaut"
+ "PL_default_lobbytitle" "Nom salon par défaut"
+ "PL_default_name" "Nom par défaut"
+ "PL_don" "Quitte ou double"
+ "PL_fd" "Défense frontalière"
+ "PL_fd_desc" "Défendez-vous contre des vagues de forces ennemies."
+ "PL_fd_easy" "Défense frontalière : Facile"
+ "PL_fd_easy_desc" "Éliminez l'opposition !"
+ "PL_fd_easy_lobby" "Salon Défense frontalière : Facile"
+ "PL_fd_hard" "Défense frontalière : Difficile"
+ "PL_fd_hard_desc" "Résistez en développant des stratégies avancées."
+ "PL_fd_hard_lobby" "Défense frontalière : Salon difficile"
+ "PL_fd_insane" "Défense frontalière : Extrême"
+ "PL_fd_insane_desc" "Vous ne survivrez pas."
+ "PL_fd_insane_lobby" "Défense frontalière : Salon extrême"
+ "PL_fd_lobby" "Salon Défense frontalière"
+ "PL_fd_master" "Défense frontalière : Élite"
+ "PL_fd_master_desc" "Seuls les meilleurs pilotes gagneront."
+ "PL_fd_master_lobby" "Défense frontalière : Salon élite"
+ "PL_fd_normal" "Défense frontalière : Standard"
+ "PL_fd_normal_desc" "Recommandé aux joueurs chevronnés"
+ "PL_fd_normal_lobby" "Défense frontalière : salon Standard"
+ "PL_ffa" "Chacun pour soi"
+ "PL_ffa_abbr" "CPS"
+ "PL_ffa_desc" "Chaque pilote joue seul et doit abattre tous ses ennemis.\n^FFC83200Joueurs : 1c11\nLimite de temps : 10 min\nTaille max. du groupe : 1"
+ "PL_ffa_lobby" "Salon Chacun pour soi"
+ "PL_fra" "Agent libre"
+ "PL_fra_abbr" "AL"
+ "PL_fra_desc" "Vous jouez pour vous. Abattez vos ennemis pour gagner. Collectez 3 batteries pour appeler votre Titan.\n^FFC83200Joueurs : 1c11\nLimite de temps : 15 min\nTaille max. du groupe : 1"
+ "PL_fra_lobby" "Salon Agent libre"
+ "PL_groud_war_lobby" "Salon Mixtape 8c8"
+ "PL_ground_war" "Mixtape 8c8"
+ "PL_ground_war_abbr" "8c8"
+ "PL_ground_war_desc" "Inclut les modes Escarmouche et Point clé amplifié avec un nombre de joueurs élevé sur toutes les cartes.\n^FFC83200Joueurs : 8c8"
+ "PL_hardpoint" "Point clé amplifié"
+ "PL_hardpoint_abbr" "PCA"
+ "PL_hardpoint_desc" "Capturez et contrôlez des points clés pour gagner des points (amplifier les points clés rapporte 2X plus).\n^FFC83200Joueurs : 6c6\nLimite de temps : 10 min\nTaille max. du groupe : 6"
+ "PL_hardpoint_lobby" "Salon Point clé amplifié"
+ "PL_hunted" "Traque"
+ "PL_iron_last_titan_standing" "DT d'Acier"
+ "PL_iron_last_titan_standing_abbr" "DTA"
+ "PL_iron_last_titan_standing_desc" "Titans uniquement avec plusieurs manches à élimination. La première équipe à gagner 3 manches remporte la partie.\n^FFC83200Joueurs : 5c5 *PAS DE PILOTES\nLimite de temps : 3 min par manche *Aucune réapparition\nTaille max. du groupe : 5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "Dernier Titan"
+ "PL_last_titan_standing_abbr" "DT"
+ "PL_last_titan_standing_desc" "Chaque joueur commence à bord d'un Titan dans plusieurs manches à élimination. La première équipe à gagner 3 manches remporte la partie.\n^FFC83200Joueurs : 5c5 *Début en Titan\nLimite de temps : 3 min par manche *Aucune réapparition\nTaille max. du groupe : 5"
+ "PL_last_titan_standing_lobby" "Salon DT"
+ "PL_limited_time_mode" "Mode à durée limitée"
+ "PL_live_fire" "Live Fire"
+ "PL_live_fire_abbr" "LF"
+ "PL_live_fire_desc" "Combats rapides dans une arène Live Fire. Abattez tous les pilotes ennemis ou détenez le drapeau à la fin du temps imparti pour remporter la manche.^FFC83200\nJoueurs : 6c6 *Aucun Titan\nLimite de temps : 60 s *Aucune réapparition\nTaille max. du groupe : 6"
+ "PL_live_fire_lobby" "Salon Live Fire"
+ "PL_load_a_map_on_the_command_line" "Charger carte par ligne de commande –dev uniquement"
+ "PL_marked_for_death" "Condamnation"
+ "PL_marked_for_death_abbr" "COND"
+ "PL_marked_for_death_desc" "Abattez ou protégez les cibles désignées.\n^FFC83200Joueurs : 6c6 \nLimite de temps : 12 min\nTaille max. du groupe : 6"
+ "PL_marked_for_death_lobby" "Salon Condamnation"
+ "PL_nitro_ffa" "CPS (Nitro)"
+ "PL_nitro_ffa_abbr" "CPS-N"
+ "PL_nitro_ffa_desc" "Affrontements rapides en Chacun pour soi sur une sélection de cartes.\n^FFC83200Joueurs : 6\n^F4D5A600Pas de Titans\nAucun Boost"
+ "PL_nitro_ffa_lobby" "Salon CPS Nitro"
+ "PL_nitro_mixtape" "Mixtape (Nitro)"
+ "PL_nitro_mixtape_abbr" "MXT-N"
+ "PL_nitro_mixtape_desc" "Affrontements rapides en CDD, COND et PcP sur une sélection de cartes.\n^FFC83200Joueurs : 5c5\nTaille max. du groupe : 5\n^F4D5A600Retours instantanés du drapeau"
+ "PL_nitro_mixtape_lobby" "Salon Mixtape"
+ "PL_pilot_hunter" "Escarmouche"
+ "PL_pilot_hunter_abbr" "ESC"
+ "PL_pilot_hunter_desc" "Éliminez les pilotes et Titans ennemis. \n^FFC83200Joueurs : 8c8\nLimite de temps : 10 min\nTaille max. du groupe : 8"
+ "PL_pilot_hunter_lobby" "Salon Escarmouche"
+ "PL_pilot_skirmish" "Pilotes contre pilotes"
+ "PL_pilot_skirmish_abbr" "PcP"
+ "PL_pilot_skirmish_desc" "Tuez les pilotes ennemis. Aucun largage de Titan n’est autorisé.\n^FFC83200Joueurs : 8c8 *Aucun Titan\nLimite de temps : 10 min\nTaille max. du groupe : 8"
+ "PL_pilot_skirmish_lobby" "Salon PcP"
+ "PL_pl_rebuild_all_paths" "Tout reconstruire"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "Exécuter avec ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "Partie privée bêta"
+ "PL_private_match_desc" "Jouez une partie privée personnalisée sur le mode et la carte de votre choix.\n^FFC83200Choisissez INVITER RÉSEAU ou INVITER AMIS pour jouer.\n^FFC83200Joueurs : 1-16\nAucune progression"
+ "PL_private_match_lobby" "Salon Partie privée bêta"
+ "PL_promo_coop" "COOP 4 joueurs"
+ "PL_raid" "Raid"
+ "PL_rise" "Expansion 24/7"
+ "PL_rise_abbr" "EXP"
+ "PL_rise_desc" "Affrontements exclusivement sur la carte Expansion. ^CCCCCC00Vous allez chercher des parties en modes ^FFC83200CDD^CCCCCC00, ^FFC83200Point clé amplifié^CCCCCC00, ^FFC83200Pilotes contre pilotes^CCCCCC00, ^FFC83200Live Fire^CCCCCC00 et ^FFC83200Dernier Titan^CCCCCC00."
+ "PL_rise_lobby" "Salon Expansion 24/7"
+ "PL_rocket_arena" "Arène à roquettes"
+ "PL_rocket_arena_abbr" "LF"
+ "PL_rocket_arena_desc" "Règles Live Fire classiques avec EPG modifiés.^FFC83200\nJoueurs : 6c6 *Aucun Titan\nLimite de temps : 90 s *Aucune réapparition\nTaille max. du groupe : 6"
+ "PL_rocket_arena_lobby" "Salon Arène à roquettes"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "Paramètres pour le chargement utilisateur d’une carte par ligne de commande. Ne pas éditer la commande \"cmdlineMapLoad 1\" ci-dessous, elle est requise pour le chargement."
+ "PL_speedball" "Live Fire"
+ "PL_speedball_desc" "Disputez-vous la possession d'un drapeau neutre. Éliminez l'équipe ennemie ou détenez le drapeau à la fin de la manche. La première équipe à gagner 5 manches remporte la partie.\n^FFC83200Joueurs : 6c6 *Aucun Titan\nLimite de temps : 60 s par manche *Aucune réapparition\nTaille max. du groupe : 6"
+ "PL_speedball_lobby" "Salon Live Fire"
+ "PL_tactikill" "Attrition Frag tactique"
+ "PL_tactikill_abbr" "ATT"
+ "PL_tactikill_desc" "Règles Attrition classiques avec réinitialisation des capacités tactiques après un frag.\n^FFC83200Joueurs : 6c6 *IA\nLimite de temps : 10 min\nTaille max. du groupe : 6"
+ "PL_tactikill_lobby" "Salon Attrition Frag tactique"
+ "PL_titan_brawl" "Combat de Titan"
+ "PL_titan_brawl_abbr" "CDT"
+ "PL_titan_brawl_desc" "Détruisez les Titans ennemis. Les pilotes ne sont pas autorisés.\n^FFC83200Joueurs : 5c5\nLimite de temps : 10 min\nTaille max. du groupe : 5"
+ "PL_titan_brawl_hint" "Détruisez les Titans ennemis.\nPas d'éjection"
+ "PL_titan_brawl_lobby" "Salon Combat de Titan"
+ "PL_titan_brawl_turbo" "Combat de Titan Turbo"
+ "PL_titan_brawl_turbo_abbr" "CDT"
+ "PL_titan_brawl_turbo_desc" "Règles Combat de Titan classiques avec régénération de propulsion et génération de noyau plus rapides.\n^FFC83200Joueurs : 5c5\nLimite de temps : 10 min\nTaille max. du groupe : 5"
+ "PL_titan_brawl_turbo_hint" "Détruisez les Titans ennemis.\nPas d'éjection"
+ "PL_titan_brawl_turbo_lobby" "Salon Combat de Titan Turbo"
+ "PL_turbo_last_titan_standing" "DT Turbo"
+ "PL_turbo_last_titan_standing_abbr" "DT"
+ "PL_turbo_last_titan_standing_desc" "Règles DT classiques avec régénération de propulsion et génération de noyau plus rapides.\n^FFC83200Joueurs : 5c5 *Début en Titan\nLimite de temps : 3 min par manche *Aucune réapparition\nTaille max. du groupe : 5"
+ "PL_turbo_last_titan_standing_lobby" "Salon DT Turbo"
+ "PL_variety_pack" "Mixtape"
+ "PL_variety_pack_desc" "Jouez avec différents nombres de joueurs et cartes dans plusieurs modes de jeu :\n^FFC83200*Chasse aux primes *Attrition\n*Dernier Titan *Point clé amplifié\n*Pilotes contre pilotes *Capture de drapeau"
+ "PL_variety_pack_lobby" "Salon Mixtape"
+ "PL_wargames" "Jeux de guerre 24/7"
+ "PL_wargames_abbr" "JDG"
+ "PL_wargames_desc" "Affrontements exclusivement sur la carte Jeux de guerre. ^CCCCCC00Vous allez chercher des parties en modes ^FFC83200Attrition^CCCCCC00, ^FFC83200CDD^CCCCCC00, ^FFC83200Pilotes contre pilotes^CCCCCC00, ^FFC83200Point clé amplifié^CCCCCC00 et ^FFC83200Dernier Titan^CCCCCC00."
+ "PL_wargames_lobby" "Salon Jeux de guerre 24/7"
+ "WATCH_TUTORIAL" "VOIR DIDACTICIEL"
+ }
+ }
+ "lang"
+ {
+ "Language" "polish"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "Kontynuuj swojÄ… przygodÄ™ na zapierajÄ…cych dech w piersi Kresach dziÄ™ki „Pocztówkom z Kresówâ€, najnowszemu DLC do Titanfall 2. WzbogaciliÅ›my grÄ™ o nowe obszary i szeroki wachlarz elitarnych barw wojennych broni. Kresy jeszcze nigdy nie wyglÄ…daÅ‚y tak dobrze!\n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_00_Q" "Zwiastun „Pocztówek z Kresówâ€!"
+ "COMMUNITYUPDATE_01_A" "Zapoznaj siÄ™ ze wszystkimi zmianami, które wprowadziliÅ›my w aktualizacji „Pocztówki z Kresówâ€.\n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_01_Q" "„Pocztówki z Kresówâ€: informacje o aktualizacji"
+ "COMMUNITYUPDATE_02_A" "Wkrótce na urządzeniach mobilnych dostępna będzie gra Titanfall: Assault. To strategia czasu rzeczywistego rozgrywająca się w świecie Titanfall, która powstała we współpracy z Particle City. Zobacz ją w akcji!\n\n\nWciśnij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_02_Q" "Titanfall: Assault – zwiastun premierowy"
+ "COMMUNITYUPDATE_03_A" "Iniquity pokazuje nam samouczek i udziela podstawowych porad do Titanfall: Assault. To idealny sposób na zaznajomienie się z grą.\n\n\nWciśnij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_03_Q" "Titanfall: Assault – poznaj podstawy rozgrywki"
+ "COMMUNITYUPDATE_04_A" "PosÅ‚uchaj twórców „Obrony Kresówâ€, którzy opowiadajÄ… o kulisach powstania tego trybu i zobacz, jak rozgrywamy kilka gier! \n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_04_Q" "Respawn w akcji: Obrona Kresów"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger przygotował film prezentujący szeroki wachlarz zagrań z użyciem ostrza pulsacyjnego. \n\n\nWciśnij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_05_Q" "Prace społeczności – Przejdźmy do rzeczy"
+ "COMMUNITYUPDATE_06_A" "ConzeyG prezentuje w serwisie reddit zabójczÄ… skuteczność Polaris w trybie „Na celownikuâ€. \n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_06_Q" "Filmy fanów – Polaris na polowaniu"
+ "COMMUNITYUPDATE_07_A" "Już jest! „Obrona Kresów†powraca wraz z mapÄ… „Powstanieâ€, a także nowymi barwami wojennymi do kupienia oraz mapÄ… do trybu „Szybki szturmâ€. Zobacz caÅ‚ość w akcji.\n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_07_Q" "Operacja „Tarcza Kresów†– zwiastun z rozgrywką"
+ "COMMUNITYUPDATE_08_A" "Już dostÄ™pne! SÅ‚ynna mapa „Gry wojenne†powraca do Titanfall 2 w odÅ›wieżonej postaci. Oprócz tego gracze bÄ™dÄ… mogli cieszyć siÄ™ nowÄ… egzekucjÄ… i „Ruchem ulicznymâ€, czyli nowÄ… mapÄ… do Szybkiego szturmu.\n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_08_Q" "„Gry wojenne†– zwiastun z rozgrywką"
+ "COMMUNITYUPDATE_09_A" "Ten dodatek obejmuje MonarchÄ™, czyli siódmego Tytana dostÄ™pnego w trybie wieloosobowym, odÅ›wieżonÄ… mapÄ™ „Reliktâ€, nowÄ… egzekucjÄ™, a także Tytany Prime: Ronina i Tona oraz wiÄ™cej kamuflaży, sztandarów i grafik dziobowych do kupienia. Åšledź akcjÄ™ tutaj.\n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_09_Q" "Zwiastun - rozgrywka w „RzÄ…dach Monarchyâ€"
+ "COMMUNITYUPDATE_10_A" "Nie wszystko jest takie, jak siÄ™ wydaje na Kresach – przygotujcie siÄ™ na „BÅ‚Ä…d na Kresachâ€, najnowsze darmowe DLC do Titanfall 2, zawierajÄ…ce nowÄ… mapÄ™ „BÅ‚Ä…dâ€. InspiracjÄ… dla jej stworzenia byÅ‚a ojczysta planeta kapitana Lastimosy, Harmonia. Mapa obfituje w wysokie urwiska i dÅ‚ugie, krÄ™te Å›cieżki. To idealna przestrzeÅ„ dla dÅ‚ugich sekwencji skoków po Å›cianach, które pozwolÄ… bÅ‚yskawicznie przemieszczać siÄ™ po mapie. Gra zostanie również wzbogacona o nowÄ… mapÄ™ do trybu „Szybki szturmâ€, „PokÅ‚adâ€, peÅ‚nÄ… ciasnych pomieszczeÅ„ i otwartych dziedziÅ„ców. Nie zabraknie tam również dronów, pilnie Å›ledzÄ…cych potyczki z góry. JeÅ›li czujesz, że wróg ciÄ™ przytÅ‚acza, skorzystaj z pomocy nowej frakcji Marvinów, która chÄ™tnie wyciÄ…gnie do ciebie pomocnÄ…, mechanicznÄ… dÅ‚oÅ„. Ponadto, piloci, którzy chcÄ… przekazać wrogowi jasny komunikat, mogÄ… odblokować egzekucjÄ™ „Ostrze pulsacyjneâ€\n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_10_Q" "Zwiastun - prezentacja „BÅ‚Ä™du na Kresachâ€"
+ "COMMUNITYUPDATE_11_A" "Powróć na uwielbianą przez fanów mapę z oryginalnego Titanfall: Kolonia. Dodajmy do niej nowe zdolności taktyczne i Tytany z Titanfall 2, a piloci i Tytany będą musieli mieć oczy dookoła głowy, aby przeżyć w tej wiosce pełnej wąskich uliczek, ślepych zaułków i odkrytych dachów. Mapa będzie dostępna za darmo dla wszystkich graczy 30 marca. Pakiet DLC „Nowa Kolonia†zawiera także klasyczną broń, karabin R-101, nową egzekucję oraz opcje kosmetyczne do kupienia, dzięki którym będziesz wyglądać bardzo stylowo podczas siania zniszczenia.\n\n\nWciśnij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_11_Q" "Zwiastun - rozgrywka w „Nowej Koloniiâ€"
+ "COMMUNITYUPDATE_12_A" "Poznajcie „Szybki szturm†– niezwykle szybki tryb 6 na 6 dla pilotów, w którym nie zabraknie zaciÄ™tych potyczek na krótkich dystansach. Specjalnie na potrzeby tego trybu przygotowaliÅ›my dwie nowe mapy: „Stosy†i „Polanaâ€. SÄ… to ciasne przestrzenie, na których toczyć siÄ™ bÄ™dzie dynamiczna i zażarta rywalizacja.\n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_12_Q" "Zwiastun – rozgrywka w tr. „Szybki szturmâ€"
+ "COMMUNITYUPDATE_13_A" "Poznaj odÅ›wieżonÄ… wersjÄ™ `1Miasta Aniołów`0, ulubionej mapy graczy z pierwszej części Titanfall. Przygotuj siÄ™ na nadejÅ›cie pierwszego darmowego pakietu DLC do Titanfall 2 – zestawu `1„Renegat z Miasta Aniołówâ€`0, który udostÄ™pnimy wszystkim graczom już 1 grudnia. Zestaw zawiera nowe opcje kosmetyczne, dziÄ™ki którym wyróżnisz siÄ™ podczas walk na Kresach. \n\n\nWciÅ›nij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_13_Q" "Zwiastun - rozgrywka w Mieście Aniołów"
+ "COMMUNITYUPDATE_14_A" "Witaj z powrotem.\n\n\nWciśnij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_14_Q" "Zwiastun – „Bisâ€"
+ "COMMUNITYUPDATE_15_A" "Dwie legendy, jedna historia.\n\n\nWciśnij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_15_Q" "Zwiastun – rozgrywka w trybie fabularnym"
+ "COMMUNITYUPDATE_16_A" "Bez ograniczeń.\n\n\nWciśnij `2%[A_BUTTON|MOUSE1]%`0, aby zobaczyć."
+ "COMMUNITYUPDATE_16_Q" "Zwiastun – rozgrywka pilotów w trybie online"
+ "COMMUNITYUPDATE_DESC" "`3Co nowego u społeczności Titanfall?`0\n\nInformacje i linki z całego świata.\n\nAby dowiedzieć się więcej:\n`2%$rui/bullet_point%`0śledź nas na Twitterze `1@Respawn`0\n`2%$rui/bullet_point%`0polub nasz profil na `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0dołącz do nas na stronie `1www.respawn.com`0"
+ "COMMUNITYUPDATE_NAME" "Społeczność"
+ "KNB_SUBJECT_00_DESC" "`3Co nowego w Titanfall?`0\n\nZajrzyj tutaj, by zobaczyć zmiany w Titanfall 2!"
+ "KNB_SUBJECT_00_NAME" "Aktualizacje gry"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 Nowe, zaprojektowane przez społeczność identyfikatory.\n\n`2%$rui/bullet_point%`0Wszystkie prezenty patrona można teraz kupić za pomocą kredytów.\n\n`2%$rui/bullet_point%`0Nowa zawartość do kupienia w sklepie.\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 28 listopada – Pora żniw"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Pistolet w głównym miejscu na broń\n\n`2%$rui/bullet_point%`0Halloweenowe identyfikatory\n\n`2%$rui/bullet_point%`0Zmiany w wyważaniu\n\n`2%$rui/bullet_point%`0Nowa zawartość do kupienia w sklepie\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 31 października – Cukierki i psikusy"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0Obrona Kresów – Obsługa 3 dodatkowych map: Suchy Dok, Miasto Aniołów i Egzoplaneta.\n\n`2%$rui/bullet_point%`0Nowa mapa do \"Szybkiego szturmu\" - UMA\n\n`2%$rui/bullet_point%`0Nowa egzekucja pilota – \"Dziura w ścianie\"\n\n`2%$rui/bullet_point%`0Nowa zawartość dostępna w sklepie"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 29 sierpnia – Pocztówki z Kresów"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Obrona Kresów – nowy tryb współpracy, w którym łączysz siły z maksymalnie trzema innymi graczami, aby chronić ważny cel przed coraz silniejszymi falami przeciwników sterowanych przez SI. Komunikacja i przystosowanie się do sytuacji są kluczem do przetrwania.\n\n`2%$rui/bullet_point%`0Nowa mapa – Powstanie\n\n`2%$rui/bullet_point%`0Nowa mapa do trybu „Szybki szturm†– Miasto\n\n`2%$rui/bullet_point%`0Nowa zawartość do kupienia w sklepie"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 25 lipca – operacja „Tarcza Kresówâ€"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0Nowa mapa – „Gry wojenneâ€\n\n`2%$rui/bullet_point%`0Nowa mapa do Szybkiego szturmu – „Ruch ulicznyâ€\n\n`2%$rui/bullet_point%`0Nowa egzekucja pilota – Walka z cieniem\n\n`2%$rui/bullet_point%`0Trzecie miejsce na broÅ„\n\n`2%$rui/bullet_point%`0Ustawienia gier prywatnych"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 27 czerwca – Gry wojenne"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0Nowy Tytan – Monarcha\n\n`2%$rui/bullet_point%`0Nowa mapa – Relikt\n\n`2%$rui/bullet_point%`0Nowa egzekucja pilota – A kuku\n\n`2%$rui/bullet_point%`0Nowa zawartość dostępna w sklepie"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 30 maja: RzÄ…dy Monarchy"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0Nowa mapa – Błąd\n\n`2%$rui/bullet_point%`0Nowa mapa do szybkiego szturmu – Pokład\n\n`2%$rui/bullet_point%`0Nowa frakcja – MARVIN\n\n`2%$rui/bullet_point%`0Nowa egzekucja pilota – Na ostrzu noża\n\n`2%$rui/bullet_point%`0Zwiększenie maksymalnej generacji do 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 25 kwietnia: BÅ‚Ä…d na Kresach"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0Nowa mapa – Kolonia\n\n`2%$rui/bullet_point%`0Nowa egzekucja pilota – Uziemienie\n\n`2%$rui/bullet_point%`0Nowa broń – R-101\n\n`2%$rui/bullet_point%`0Nowa zawartość dostępna w sklepie"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 30 marca – Nowa Kolonia"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0Szybki szturm – nowy tryb eliminacyjny, w którym zmagają się wyłącznie piloci! To oparty na rundach tryb 6 na 6, w którym nie można się odradzać. Gracze mają minutę na wyeliminowanie drużyny przeciwnej. Możliwe jest także wygranie rundy poprzez kontrolowanie neutralnej flagi, gdy upłynie czas. O wygranej decyduje zwycięstwo w pięciu rundach.\n\n`2%$rui/bullet_point%`0Nowa mapa do szybkiego szturmu – Polana\n\n`2%$rui/bullet_point%`0Nowa mapa do szybkiego szturmu – Stosy\n\n`2%$rui/bullet_point%`0Nowa egzekucja pilota – Późne uderzenie"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 23 lutego: Szybki szturm"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0Nowa mapa – Miasto aniołów\n\n`2%$rui/bullet_point%`0Nowa broń – Elitarny skrzydłowy B3\n\n`2%$rui/bullet_point%`0Nowa egzekucja pilota – Rozrywacz\n\n`2%$rui/bullet_point%`0Nowe zestawy Tytana\n\n`2%$rui/bullet_point%`0Nowa zawartość dostępna w sklepie"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 30 listopada: Renegat z Miasta Aniołów"
+ "MP_ANGEL_CITY_FD_WAVE_1" "Przykuty do nieba"
+ "MP_ANGEL_CITY_FD_WAVE_2" "Bezkształtność"
+ "MP_ANGEL_CITY_FD_WAVE_3" "Rewolta"
+ "MP_ANGEL_CITY_FD_WAVE_4" "Uparta zgraja"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Twarda szóstka"
+ "MP_DRYDOCK_FD_WAVE_1" "Na nieznanych wodach"
+ "MP_DRYDOCK_FD_WAVE_2" "Wystrzelali siÄ™!"
+ "MP_DRYDOCK_FD_WAVE_3" "Oślepieni blaskiem nauki"
+ "MP_DRYDOCK_FD_WAVE_4" "System wywierania nacisku"
+ "MP_DRYDOCK_FD_WAVE_5" "Oko cyklonu"
+ "MP_THAW_FD_WAVE_1" "Świadomość sytuacji"
+ "MP_THAW_FD_WAVE_2" "Liczy się liczebność"
+ "MP_THAW_FD_WAVE_3" "Siepacz 21"
+ "MP_THAW_FD_WAVE_4" "Zabawa z ogniem"
+ "MP_THAW_FD_WAVE_5" "Obsada dział"
+ "NO_PRICE" "Oferta wygasła"
+ "NO_PRICE_TWO_LINES" "Oferta\nwygasła"
+ "PL_aegis_last_titan_standing" "DoT z EgidÄ…"
+ "PL_aegis_last_titan_standing_abbr" "DoTE"
+ "PL_aegis_last_titan_standing_desc" "Ulepszenia Egidy dostępne. Tryb eliminacyjny, wygrywa drużyna, która jako pierwsza wygra 3 rundy.\n^FFC83200Gracze: 5 na 5 *Rozpoczynasz grę w Tytanie\nLimit czasu: 3 min na rundę *Bez odrodzeń\nMaksymalny rozmiar grupy: 5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "Starcie Tytanów z Egidą"
+ "PL_aegis_titan_brawl_abbr" "STE"
+ "PL_aegis_titan_brawl_desc" "Zniszcz wrogie Tytany. Ulepszenia Egidy dostępne.\n^FFC83200Gracze: 5 na 5\nLimit czasu: 10 min\nMaksymalny rozmiar grupy: 5"
+ "PL_aegis_titan_brawl_hint" "Zniszcz wrogie Tytany.\nBrak katapultowania"
+ "PL_aegis_titan_brawl_lobby" "Lobby: Starcie Tytanów z Egidą"
+ "PL_aitdm" "Wyniszczenie"
+ "PL_aitdm_abbr" "WYN"
+ "PL_aitdm_desc" "Zabij wszystkich wrogów.\n^FFC83200Gracze: 6 na 6 *AI\nLimit czasowy: 10m\nMaksymalny rozmiar grupy: 6"
+ "PL_aitdm_lobby" "Wyniszczenie – lobby"
+ "PL_all_grapple" "Atak na Tytany"
+ "PL_all_grapple_abbr" "W"
+ "PL_all_grapple_desc" "Klasyczne zasady wyniszczenia, poza tym, że wszystkie zdolności taktyczne zostają zastąpione liną z hakiem.\n^FFC83200Gracze: 6 na 6 *SI\nLimit czasowy: 10 min\nMaksymalna wielkość grupy: 6"
+ "PL_all_grapple_lobby" "Lobby: Atak na Tytany"
+ "PL_all_holopilot" "Wielka ściema"
+ "PL_all_holopilot_abbr" "SS"
+ "PL_all_holopilot_desc" "Klasyczne zasady szybkiego szturmu, poza tym, że wszystkie zdolności taktyczne zostają zastąpione holopilotem.\n^FFC83200Gracze: 6 na 6 *Bez Tytanów\nLimit czasowy: 60 s *Bez odrodzeń\nMaksymalna wielkość grupy: 6"
+ "PL_all_holopilot_lobby" "Lobby: Wielka ściema"
+ "PL_all_phase" "Druga strona"
+ "PL_all_phase_abbr" "W"
+ "PL_all_phase_desc" "Klasyczne zasady wyniszczenia, poza tym, że wszystkie zdolności taktyczne zostają zastąpione przeskokiem fazowym.\n^FFC83200Gracze: 6 na 6 *SI\nLimit czasowy: 10 min\nMaksymalna wielkość grupy: 6"
+ "PL_all_phase_lobby" "Lobby: Druga strona"
+ "PL_all_spicy" "Pikantne wyniszczenie"
+ "PL_all_spicy_abbr" "W"
+ "PL_all_spicy_desc" "Klasyczne zasady wyniszczenia, poza tym, że wszystkie zdolności taktyczne zostają zastąpione kleszczami.\n^FFC83200Gracze: 6 na 6 *SI\nLimit czasowy: 10 min\nMaksymalna wielkość grupy: 6"
+ "PL_all_spicy_lobby" "Lobby: Pikantne wyniszczenie"
+ "PL_amped_tacticals" "Wzmocnione zdolności taktyczne"
+ "PL_amped_tacticals_abbr" "W"
+ "PL_amped_tacticals_desc" "Klasyczne zasady wyniszczenia, poza tym, że wszystkie zdolności taktyczne są potężniejsze.\n^FFC83200Gracze: 6 na 6 *SI\nLimit czasowy: 10 min\nMaksymalna wielkość grupy: 6"
+ "PL_amped_tacticals_lobby" "Lobby: Wzmocnione zdolności taktyczne"
+ "PL_ANGEL_CITY" "Miasto Aniołów 24/7"
+ "PL_angel_city_abbr" "MA"
+ "PL_angel_city_desc" "NieustajÄ…ce walki w MieÅ›cie Aniołów.\n^FFC83200*Åowy *Wyniszczenie\n*Obrona UmocnieÅ„"
+ "PL_angel_city_lobby" "Miasto Aniołów 24/7 – lobby"
+ "PL_at_coop" "Ucieczka (współpraca)"
+ "PL_at_coop_desc" "Właśnie uciekłeś z więzienia. Masz przy sobie jedynie pistolet. Musisz przeżyć do czasu ewakuacji. Eliminuj przeciwników, żeby zdobyć pieniądze na zakup broni. \n^FFC83200Gracze: 6\nMaksymalny rozmiar grupy: 6"
+ "PL_at_coop_lobby" "Lobby: Ucieczka"
+ "PL_attrition" "Åowy"
+ "PL_attrition_abbr" "ÅN"
+ "PL_attrition_desc" "Zabijaj wrogów, aby zdobywać punkty. Zyskaj jeszcze więcej, deponując punkty w wyznaczonych miejscach.\n^FFC83200Gracze: 5 na 5 *SI\nLimit czasu: 10 min.\nMaksymalny rozmiar grupy: 5"
+ "PL_attrition_lobby" "Lobby: Åowy"
+ "PL_capture_the_flag" "Walka o flagÄ™"
+ "PL_capture_the_flag_abbr" "WOF"
+ "PL_capture_the_flag_desc" "Odbierz wrogom flagę i zanieś ją do swojej bazy, jednocześnie chroniąc własną flagę przed atakami przeciwnej drużyny!\n^FFC83200Gracze: 5 na 5\nLimit czasu: 12 min.\nMaksymalny rozmiar grupy: 5"
+ "PL_capture_the_flag_lobby" "Lobby: Walka o flagÄ™"
+ "PL_coliseum" "Koloseum"
+ "PL_coliseum_desc" "Potyczka w klatce jeden na jednego z usprawnionymi zdolnoÅ›ciami poruszania siÄ™. Gracz, który wygra 3 z 5 rund, otrzyma w nagrodÄ™ prezent patrona.^FFC83200\nGracze: 1 na 1 *Bez Tytanów\nLimit czasu: 3 min. *Bez odrodzeÅ„\n**^FFFFFF00WYMAGA BILETU DO KOLOSEUM lub UISZCZENIA OPÅATY WPISOWEJ"
+ "PL_coliseum_lobby" "Lobby: Koloseum"
+ "PL_colony" "Kolonia 24/7"
+ "PL_colony_abbr" "KOL"
+ "PL_colony_desc" "NieustajÄ…ce walki w Kolonii. ^CCCCCC00Spowoduje wyszukiwanie gier w trybach ^FFC83200Wyniszczenia^CCCCCC00, ^FFC83200Piloci kontra piloci^CCCCCC00 i ^FFC83200Do ostatniego Tytana^CCCCCC00."
+ "PL_colony_lobby" "Kolonia 24/7 – lobby"
+ "PL_ctf_lf" "WoF (Nitro)"
+ "PL_ctf_lf_abbr" "WoF-N"
+ "PL_ctf_lf_desc" "Dynamiczna rozgrywka w trybie „Walka o flagę†na wybranych mapach.\n^FFC83200Gracze: 5 na 5\nMaksymalny rozmiar grupy: 5\n^F4D5A600Natychmiastowe odniesienie flagi"
+ "PL_ctf_lf_lobby" "Lobby: WoF (Nitro)"
+ "PL_default_description" "Domyślny opis"
+ "PL_default_lobbytitle" "Domyślny tytuł lobby"
+ "PL_default_name" "Domyślna nazwa"
+ "PL_don" "Podwójne ryzyko"
+ "PL_fd" "Obrona Kresów"
+ "PL_fd_desc" "Broń się przed falami sił floty Ocalałych"
+ "PL_fd_easy" "Obrona Kresów: niski"
+ "PL_fd_easy_desc" "Zmiażdż opozycję"
+ "PL_fd_easy_lobby" "Obrona Kresów: niski (lobby)"
+ "PL_fd_hard" "Obrona Kresów: wysoki"
+ "PL_fd_hard_desc" "W tym starciu liczą się umiejętności"
+ "PL_fd_hard_lobby" "Obrona Kresów: wysoki (lobby)"
+ "PL_fd_insane" "Obrona Kresów: szalony"
+ "PL_fd_insane_desc" "Nie przeżyjesz"
+ "PL_fd_insane_lobby" "Obrona Kresów: szalony (lobby)"
+ "PL_fd_lobby" "Lobby: Obrona Kresów"
+ "PL_fd_master" "Obrona Kresów: mistrzowski"
+ "PL_fd_master_desc" "PrzetrwajÄ… tylko najlepsi z najlepszych"
+ "PL_fd_master_lobby" "Obrona Kresów: mistrzowski (lobby)"
+ "PL_fd_normal" "Obrona Kresów: normalny"
+ "PL_fd_normal_desc" "Zalecane dla doświadczonych graczy"
+ "PL_fd_normal_lobby" "Obrona Kresów: normalny (lobby)"
+ "PL_ffa" "Każdy na każdego"
+ "PL_ffa_abbr" "KNK"
+ "PL_ffa_desc" "Każdy na każdego, musisz wyeliminować wszystkich przeciwników.\n^FFC83200Gracze: 1 na 11\nLimit czasu: 10 min.\nMaksymalny rozmiar grupy: 1"
+ "PL_ffa_lobby" "Lobby: Każdy na każdego"
+ "PL_fra" "Wolni strzelcy"
+ "PL_fra_abbr" "WS"
+ "PL_fra_desc" "Każdy pilot działa sam. Eliminuj przeciwników i zbierz 3 baterie, aby wezwać Tytana.\n^FFC83200Gracze: 1 na 11\nLimit czasu: 15 min\nMaksymalny rozmiar grupy: 1"
+ "PL_fra_lobby" "Lobby: Wolni strzelcy"
+ "PL_groud_war_lobby" "8 na 8 Remix – lobby"
+ "PL_ground_war" "8 na 8 Remix"
+ "PL_ground_war_abbr" "8 na 8"
+ "PL_ground_war_desc" "Zawiera tryby „Starcie pilotów†i „Obrona umocnień†z dużą liczbą graczy na każdej z map.\n^FFC83200Gracze: 8 na 8"
+ "PL_hardpoint" "Obrona umocnień"
+ "PL_hardpoint_abbr" "OU"
+ "PL_hardpoint_desc" "Przejmij i utrzymaj umocnienie, aby zdobyć punkty. Wzmocnione umocnienia są warte dwa razy więcej punktów.\n^FFC83200Gracze: 6 na 6\nLimit czasu: 10 min.\nMaksymalny rozmiar grupy: 6"
+ "PL_hardpoint_lobby" "Lobby: Obrona umocnień"
+ "PL_hunted" "Åšcigany"
+ "PL_iron_last_titan_standing" "Żelazne DoT"
+ "PL_iron_last_titan_standing_abbr" "ŻDoT"
+ "PL_iron_last_titan_standing_desc" "Tylko Tytany, tryb eliminacyjny, walka toczy się do 3 wygranych rund.\n^FFC83200Gracze: 5 na 5 *BEZ PILOTÓW\nLimit czasu: 3 min na rundę *Bez odrodzeń\nMaksymalny rozmiar grupy: 5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "Do ostatniego Tytana"
+ "PL_last_titan_standing_abbr" "DOT"
+ "PL_last_titan_standing_desc" "Wszyscy gracze rozpoczynają eliminacje w trybie rundowym, siedząc w swoich Tytanach. Wygrywa drużyna, która jako pierwsza wygra 3 rundy.\n^FFC83200Gracze: 5 na 5 *Rozpoczynasz grę w Tytanie\nLimit czasu: 3 min na rundę *Bez odrodzeń\nMaksymalny rozmiar grupy: 5"
+ "PL_last_titan_standing_lobby" "Lobby: DoT"
+ "PL_limited_time_mode" "(Tymczasowo)"
+ "PL_live_fire" "Szybki szturm"
+ "PL_live_fire_abbr" "SS"
+ "PL_live_fire_desc" "Dynamiczna potyczka na arenie do trybu Szybki szturm. Aby wygrać rundę, musisz wyeliminować wszystkich pilotów z przeciwnej drużyny lub być w posiadaniu flagi, gdy czas dobiegnie końca.^FFC83200\nGracze: 6 na 6 *Bez Tytanów\nLimit czasu: 60 s *Bez odrodzeń\nMaksymalny rozmiar grupy: 6"
+ "PL_live_fire_lobby" "Lobby: Szybki szturm"
+ "PL_load_a_map_on_the_command_line" "Wczytaj mapę w wierszu poleceń – tylko dla deva."
+ "PL_marked_for_death" "Na celowniku"
+ "PL_marked_for_death_abbr" "NC"
+ "PL_marked_for_death_desc" "Zabijaj lub chroń oznaczone cele.\n^FFC83200Gracze: 6 na 6 \nLimit czasu: 12 min\nMaksymalny rozmiar grupy: 6"
+ "PL_marked_for_death_lobby" "Lobby: Na celowniku"
+ "PL_nitro_ffa" "KNK (Nitro)"
+ "PL_nitro_ffa_abbr" "KNK-N"
+ "PL_nitro_ffa_desc" "Dynamiczne starcia w trybie Każdy na każdego na wybranych mapach.\n^FFC83200Gracze: 6\n^F4D5A600Brak Tytanów\nBrak wzmocnień"
+ "PL_nitro_ffa_lobby" "Lobby: KNK Nitro "
+ "PL_nitro_mixtape" "Remix (Nitro)"
+ "PL_nitro_mixtape_abbr" "RMX-N"
+ "PL_nitro_mixtape_desc" "Dynamiczna rozgrywka w trybach „Walka o flagÄ™â€, „Na celowniku†i PvP na wybranych mapach.\n^FFC83200Gracze: 5 na 5\nMaksymalny rozmiar grupy: 5\n^F4D5A600Natychmiastowe odniesienie flagi"
+ "PL_nitro_mixtape_lobby" "Lobby: Remix"
+ "PL_pilot_hunter" "Starcie pilotów"
+ "PL_pilot_hunter_abbr" "SP"
+ "PL_pilot_hunter_desc" "Zabij wrogich pilotów i ich Tytany. \n^FFC83200Gracze: 8 na 8\nLimit czasu: 10 min.\nMaksymalny rozmiar grupy: 8"
+ "PL_pilot_hunter_lobby" "Lobby: Starcie pilotów"
+ "PL_pilot_skirmish" "Piloci kontra piloci"
+ "PL_pilot_skirmish_abbr" "PvP"
+ "PL_pilot_skirmish_desc" "Zabij wrogich pilotów. Zrzuty Tytanów niedozwolone.\n^FFC83200Gracze: 8 na 8 *Bez Tytanów\nLimit czasu: 10 min.\nMaksymalny rozmiar grupy: 8"
+ "PL_pilot_skirmish_lobby" "Lobby: PvP"
+ "PL_pl_rebuild_all_paths" "Odnów wszystkie ścieżki"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "Uruchamianie z: ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "Gra prywatna (beta)"
+ "PL_private_match_desc" "Rozegraj własną grę prywatną i samodzielnie wybierz tryb i mapę.\n^FFC83200ZAPROŚ SIEĆ lub ZAPROŚ ZNAJOMYCH do gry\n^FFC83200Gracze: 1-16\nBez postępów"
+ "PL_private_match_lobby" "Lobby: Gra prywatna (beta)"
+ "PL_promo_coop" "4-OS. TRYB KOOPERACJI"
+ "PL_raid" "Rajd"
+ "PL_rise" "Powstanie 24/7"
+ "PL_rise_abbr" "POW"
+ "PL_rise_desc" "Powstanie non-stop. ^CCCCCC00Wyszukiwane będą gry w trybach: ^FFC83200WoF^CCCCCC00, ^FFC83200Obrona umocnień^CCCCCC00, ^FFC83200Piloci kontra piloci^CCCCCC00, ^FFC83200Szybki szturm^CCCCCC00 i ^FFC83200Do ostatniego Tytana^CCCCCC00."
+ "PL_rise_lobby" "Powstanie 24/7 – lobby"
+ "PL_rocket_arena" "Rakietowa arena"
+ "PL_rocket_arena_abbr" "SS"
+ "PL_rocket_arena_desc" "Klasyczne zasady szybkiego szturmu ze zmodyfikowanymi EPG.^FFC83200\nGracze: 6 na 6 *Bez Tytanów\nLimit czasowy: 90s *Bez odrodzeń\nMaksymalny rozmiar grupy: 6"
+ "PL_rocket_arena_lobby" "Lobby: Rakietowa arena"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "Ustawienia w przypadku wczytania mapy poprzez wiersz poleceń. Nie edytować cmdlineMapLoad 1 – wszystko będzie działać."
+ "PL_speedball" "Szybki szturm"
+ "PL_speedball_desc" "Walcz o przejęcie neutralnej flagi. Wyeliminuj drużynę przeciwną lub bądź w posiadaniu flagi, gdy runda dobiegnie końca. Gra toczy się do 5 zwycięstw.\n^FFC83200Gracze: 6 na 6 *Bez Tytanów\nLimit czasu: 60 s/tura * Bez odrodzeń\nMaksymalny rozmiar grupy: 6"
+ "PL_speedball_lobby" "Szybki szturm – lobby"
+ "PL_tactikill" "Taktyczne wyniszczenie"
+ "PL_tactikill_abbr" "W"
+ "PL_tactikill_desc" "Klasyczne zasady wyniszczenia, poza tym, że wszystkie zdolności taktyczne zostają zresetowane przy zabójstwie.\n^FFC83200Gracze: 6 na 6 *SI\nLimit czasowy: 10 min\nMaksymalna wielkość grupy: 6"
+ "PL_tactikill_lobby" "Lobby: Taktyczne wyniszczenie"
+ "PL_titan_brawl" "Starcie Tytanów"
+ "PL_titan_brawl_abbr" "ST"
+ "PL_titan_brawl_desc" "Zniszcz wrogie Tytany. Bez pilotów.\n^FFC83200Gracze: 5 na 5\nLimit czasu: 10 min\nMaksymalny rozmiar grupy: 5"
+ "PL_titan_brawl_hint" "Zniszcz wrogie Tytany.\nBrak katapultowania"
+ "PL_titan_brawl_lobby" "Lobby: Starcie Tytanów"
+ "PL_titan_brawl_turbo" "Turbo starcie Tytanów"
+ "PL_titan_brawl_turbo_abbr" "ST"
+ "PL_titan_brawl_turbo_desc" "Klasyczne zasady starcia Tytanów z szybszym odnawianiem zrywów oraz ładowaniem rdzenia.\n^FFC83200Gracze: 5 na 5\nLimit czasowy: 10 min\nMaksymalny rozmiar grupy: 5"
+ "PL_titan_brawl_turbo_hint" "Zniszcz wrogie Tytany.\nBrak katapultowania"
+ "PL_titan_brawl_turbo_lobby" "Lobby: Turbo starcie Tytanów"
+ "PL_turbo_last_titan_standing" "Turbo DoT"
+ "PL_turbo_last_titan_standing_abbr" "DOT"
+ "PL_turbo_last_titan_standing_desc" "Klasyczne zasady DoT z szybszym odnawianiem zrywów oraz ładowaniem rdzenia.\n^FFC83200Gracze: 5v5 *Rozpoczynasz grę w Tytanie\nLimit czasowy: 3 min na każdą rundę *Bez odrodzeń\nMaksymalny rozmiar grupy: 5"
+ "PL_turbo_last_titan_standing_lobby" "Lobby: Turbo DoT"
+ "PL_variety_pack" "Remix"
+ "PL_variety_pack_desc" "Zmienna liczba graczy i wybór różnych map; rozgrywka w trybach takich jak:\n^FFC83200*Åowy *Wyniszczenie\n*Do ostatniego Tytana *Obrona umocnieÅ„\n*Piloci kontra piloci *Walka o flagÄ™"
+ "PL_variety_pack_lobby" "Remix – lobby"
+ "PL_wargames" "Gry wojenne 24/7"
+ "PL_wargames_abbr" "GW"
+ "PL_wargames_desc" "Gry wojenne non-stop. ^CCCCCC00Wyszukiwanie gier w trybach^FFC83200Wyniszczenia^CCCCCC00, ^FFC83200WOF^CCCCCC00, ^FFC83200Piloci kontra piloci^CCCCCC00, ^FFC83200Obrona umocnień^CCCCCC00, i ^FFC83200Do ostatniego Tytana^CCCCCC00."
+ "PL_wargames_lobby" "Gry wojenne 24/7 – lobby"
+ "WATCH_TUTORIAL" "OBEJRZYJ SAMOUCZEK"
+ }
+ }
+ "lang"
+ {
+ "Language" "mspanish"
+ "Tokens"
+ {
+ "COMMUNITYUPDATE_00_A" "Tu aventura por los asombrosos paisajes de la Frontera continúa con el último DLC de Titanfall 2: Postales de la Frontera. La Frontera se ve mejor que nunca con ubicaciones nuevas y conocidas, además de la nueva colección de pinturas de guerra de arma élite.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_00_Q" "Tráiler de \"Postales de la Frontera\""
+ "COMMUNITYUPDATE_01_A" "Lee todos los cambios que llegan con el parche de Postales de la Frontera.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_01_Q" "Notas sobre parches de Postales de la Frontera"
+ "COMMUNITYUPDATE_02_A" "Próximamente disponible para celulares, Titanfall: Assault es un juego de disparos en tiempo real ambientado en el universo de Titanfall con la colaboración de Particle City. Míralo en acción aquí. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_02_Q" "Tráiler de \"lanzamiento\" de Titanfall: Assault"
+ "COMMUNITYUPDATE_03_A" "Iniquity nos muestra el tutorial y nos da consejos básicos para Titanfall: Assault. Esta es la forma perfecta de iniciarse en el juego.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_03_Q" "Video \"aprende lo básico\" de Titanfall: Assault"
+ "COMMUNITYUPDATE_04_A" "Conoce la historia y la creación del modo a través de algunas de las personas clave que se encuentran tras Defensa fronteriza y mira cómo jugamos unas rondas. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_04_Q" "Partidas de Respawn: Defensa fronteriza"
+ "COMMUNITYUPDATE_05_A" "Kevin Younger creó un montaje divertido en el que presume de grandes movimientos con la cuchilla de pulso. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_05_Q" "Creaciones de la comunidad: dale en el centro"
+ "COMMUNITYUPDATE_06_A" "ConzeyG presume a través de reddit de manejar a Northstar de forma asombrosa mientras recoge pilotos en el modo Marcado para morir. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_06_Q" "Creaciones : cacería de pilotos con Northstar"
+ "COMMUNITYUPDATE_07_A" "¡Ya disponible! Defensa fronteriza regresa junto con Ascenso y nuevas pinturas de guerra que podrás comprar y un nuevo mapa de Fuego vivo. Mira todo esto en acción aquí.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_07_Q" "Tráiler de juego \"Operación Escudo fronterizo\""
+ "COMMUNITYUPDATE_08_A" "¡Ya disponible! El icónico mapa Juegos bélicos regresa a Titanfall 2 y se ve mejor que nunca. No te olvides de checar la nueva ejecución y el nuevo mapa Tráfico de Fuego vivo en acción.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_08_Q" "Tráiler de juego de \"Juegos bélicos\""
+ "COMMUNITYUPDATE_09_A" "Este DLC incluye al séptimo titán del multijugador: Monarch, el mapa Reliquia remasterizado y una nueva ejecución. Además podrás comprar los titanes Ronin y Tone Prime, más camuflajes, banderas y decoraciones para el fuselaje. Mira la acción aquí.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_09_Q" "Tráiler de juego de \"Reino de Monarch\""
+ "COMMUNITYUPDATE_10_A" "No todo es lo que parece en la Frontera. Prepárate para el nuevo pack de DLC de Titanfall 2: Un Fallo en la Frontera, que incluye el nuevo mapa “Falloâ€. Está inspirado en Harmony, el planeta en el que se crió el capitán Lastimosa, repleto de caídas verticales y senderos eternos y serpenteantes, perfectos para encadenar carreras por las paredes y recorrer todo el mapa sin interrupciones. También se une al pack un nuevo mapa de Fuego vivo: Muelle, que cuenta con espacios internos estrechos, patios desprotegidos y drones de vigilancia que te observan desde las alturas. Si crees que esto te supera, los M.R.V.N. ya están aquí dispuestos a echarte una mano robótica. Por último, puedes desbloquear la ejecución cuchilla de pulso y usarla cuando más la necesites.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_10_Q" "Tráiler de juego de \"Un Fallo en la Frontera\""
+ "COMMUNITYUPDATE_11_A" "Regresa el icónico mapa favorito de los aficionados del multijugador del Titanfall original: Colonia, con las nuevas tácticas y titanes de Titanfall 2, en el que los pilotos y los titanes tendrán que ingeniárselas para sobrevivir a esta ciudad idílica repleta de callejones sin salida, esquinas con poca visibilidad y azoteas expuestas. Este mapa estará disponible el 30 de marzo para todos los jugadores. El pack de DLC “Colonia Renacida†incluye armas clásicas como el rifle de asalto R-101, una nueva ejecución de arpeo y nuevas opciones estéticas que podrás comprar para verte lo mejor posible mientras bañas la ciudad de sangre enemiga.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_11_Q" "Tráiler de juego de \"Colonia Renacida\""
+ "COMMUNITYUPDATE_12_A" "Presentamos Fuego vivo: un modo vertiginoso de 6vs6 exclusivo para pilotos que se centra en el combate competitivo y de corto alcance. Incluye dos mapas nuevos diseñados específicamente para Fuego vivo: Aglomeración y Pradera. Estos mapas gratuitos, diseñados específicamente para este modo intenso y frenético, son cajas letales estrechas y cerradas.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_12_Q" "Tráiler de juego \"Bienvenidos a Fuego vivo\""
+ "COMMUNITYUPDATE_13_A" "Disfruta la versión remasterizada del mapa favorito de los aficionados del Titanfall original, `1Ciudad Ãngel`0. Permanece atento al primer DLC gratis de Titanfall 2 con `1El más deseado de Ciudad Ãngel`0 disponible el 1 de diciembre para todos los jugadores. Este contenido incluye nuevas opciones estéticas para darle más estilo a la Frontera. \n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_13_Q" "Tráiler de juego \"Bienvenidos a Ciudad Ãngel\""
+ "COMMUNITYUPDATE_14_A" "Bienvenido de nuevo.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_14_Q" "Tráiler de comentarios \"Encore\""
+ "COMMUNITYUPDATE_15_A" "Dos leyendas, un legado.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_15_Q" "Tráiler de juego de un jugador \"Sean uno\""
+ "COMMUNITYUPDATE_16_A" "Sin límites.\n\n\nPulsa `2%[A_BUTTON|MOUSE1]%`0 para verlo."
+ "COMMUNITYUPDATE_16_Q" "Tráiler de juego de multijugador \"Pilotos\""
+ "COMMUNITYUPDATE_DESC" "`3Actualizaciones en la comunidad de Titanfall`0\n\nInformación y enlaces de la web.\n\nPara más información:\n`2%$rui/bullet_point%`0Síguenos en Twitter `1@Respawn`0\n`2%$rui/bullet_point%`0Danos me gusta en `1facebook.com/RespawnEntertainment`0\n`2%$rui/bullet_point%`0Únete a nosotros en `1www.respawn.com`0"
+ "COMMUNITYUPDATE_NAME" "Comunidad"
+ "KNB_SUBJECT_00_DESC" "`3¿Qué es nuevo en Titanfall?`0\n\n¡Chécalo aquí para ver qué ha cambiado en Titanfall 2!"
+ "KNB_SUBJECT_00_NAME" "Actualizaciones"
+ "KNB_SUBJECT_00_SUB_00_A" "`2%$rui/bullet_point%`010 nuevas banderas creadas por la comunidad\n\n`2%$rui/bullet_point%`0Todos los regalos del defensor se pueden comprar ahora con créditos\n\n`2%$rui/bullet_point%`0Nuevo contenido en la tienda para comprar\n\n"
+ "KNB_SUBJECT_00_SUB_00_Q" "%$rui/hud/scoreboard/status_titan% 28 de noviembre: Recolector de tiempo"
+ "KNB_SUBJECT_00_SUB_01_A" "`2%$rui/bullet_point%`0Espacio principal de la pistola\n\n`2%$rui/bullet_point%`0Banderas de Halloween\n\n`2%$rui/bullet_point%`0Cambios de equilibrio\n\n`2%$rui/bullet_point%`0Nuevo contenido en la tienda para comprar\n\n"
+ "KNB_SUBJECT_00_SUB_01_Q" "%$rui/hud/scoreboard/status_titan% 31 de octubre: Trucos y tratos"
+ "KNB_SUBJECT_00_SUB_02_A" "`2%$rui/bullet_point%`0Defensa fronteriza compatible con 3 mapas más: Dique seco, Ciudad Ãngel y Exoplaneta.\n\n`2%$rui/bullet_point%`0Nuevo mapa de Fuego vivo: UMA\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Agujero en el muro\n\n`2%$rui/bullet_point%`0Nuevo contenido en la tienda para comprar"
+ "KNB_SUBJECT_00_SUB_02_Q" "%$rui/hud/scoreboard/status_titan% 29 de agosto: Postales de la Frontera"
+ "KNB_SUBJECT_00_SUB_03_A" "`2%$rui/bullet_point%`0Defensa fronteriza: un nuevo modo cooperativo en el que unes tus fuerzas con hasta 3 otros jugadores para defender un objetivo vital de las oleadas de combatientes de la IA que van aumentando de intensidad. La comunicación y la adaptación son claves para la sobrevivencia.\n\n`2%$rui/bullet_point%`0Nuevo mapa: Ascenso\n\n`2%$rui/bullet_point%`0Nuevo mapa de Fuego vivo: Municipio\n\n`2%$rui/bullet_point%`0Nuevo contenido en la tienda para comprar"
+ "KNB_SUBJECT_00_SUB_03_Q" "%$rui/hud/scoreboard/status_titan% 25 de julio: Operación Escudo fronterizo"
+ "KNB_SUBJECT_00_SUB_04_A" "`2%$rui/bullet_point%`0Nuevo mapa: Juegos bélicos\n\n`2%$rui/bullet_point%`0Nuevo mapa de Fuego vivo: Tráfico\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Golpe fantasma\n\n`2%$rui/bullet_point%`0Tercer espacio de arma\n\n`2%$rui/bullet_point%`0Configuración de la partida privada"
+ "KNB_SUBJECT_00_SUB_04_Q" "%$rui/hud/scoreboard/status_titan% 27 de junio: Juegos bélicos"
+ "KNB_SUBJECT_00_SUB_05_A" "`2%$rui/bullet_point%`0Nuevo titán: Monarch\n\n`2%$rui/bullet_point%`0Nuevo mapa: Reliquia\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Ahora me ves\n\n`2%$rui/bullet_point%`0Nuevo contenido en la tienda para comprar"
+ "KNB_SUBJECT_00_SUB_05_Q" "%$rui/hud/scoreboard/status_titan% 30 de mayo: Reino de Monarch"
+ "KNB_SUBJECT_00_SUB_06_A" "`2%$rui/bullet_point%`0Nuevo mapa: Fallo\n\n`2%$rui/bullet_point%`0Nuevo mapa de Fuego vivo: Muelle\n\n`2%$rui/bullet_point%`0Nueva facción: M.R.V.N.\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Dale en el centro\n\n`2%$rui/bullet_point%`0Generación máxima aumentada a 100"
+ "KNB_SUBJECT_00_SUB_06_Q" "%$rui/hud/scoreboard/status_titan% 25 de abril: Un Fallo en la Frontera"
+ "KNB_SUBJECT_00_SUB_07_A" "`2%$rui/bullet_point%`0Nuevo mapa: Colonia\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Rompecráneos\n\n`2%$rui/bullet_point%`0Nueva arma: R-101\n\n`2%$rui/bullet_point%`0Nuevo contenido en la tienda para comprar"
+ "KNB_SUBJECT_00_SUB_07_Q" "%$rui/hud/scoreboard/status_titan% 30 de marzo: Colonia Renacida"
+ "KNB_SUBJECT_00_SUB_08_A" "`2%$rui/bullet_point%`0Fuego vivo: un nuevo modo de eliminaciones de Batalla de pilotos. Una partida de rondas de 6vs6 sin regeneraciones en la que tendrás un minuto para eliminar al equipo contrario y ganar la ronda. También podrás ganar la ronda si tu equipo tiene la bandera neutral cuando el temporizador llegue a cero. El primer equipo que gane 5 rondas ganará la partida.\n\n`2%$rui/bullet_point%`0Nuevo mapa de Fuego vivo: Pradera\n\n`2%$rui/bullet_point%`0Nuevo mapa de Fuego vivo: Aglomeración\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Último disparo"
+ "KNB_SUBJECT_00_SUB_08_Q" "%$rui/hud/scoreboard/status_titan% 23 de febrero: Fuego vivo"
+ "KNB_SUBJECT_00_SUB_09_A" "`2%$rui/bullet_point%`0Nuevo mapa: Ciudad Ãngel\n\n`2%$rui/bullet_point%`0Nueva arma: B3 Wingman Élite\n\n`2%$rui/bullet_point%`0Nueva ejecución de piloto: Piezas internas\n\n`2%$rui/bullet_point%`0Nuevs kits de titán\n\n`2%$rui/bullet_point%`0Nuevo contenido en la tienda para comprar"
+ "KNB_SUBJECT_00_SUB_09_Q" "%$rui/hud/scoreboard/status_titan% 30 de nov.: El más deseado de Ciudad Ãngel"
+ "MP_ANGEL_CITY_FD_WAVE_1" "Encadenado al cielo"
+ "MP_ANGEL_CITY_FD_WAVE_2" "Sin forma"
+ "MP_ANGEL_CITY_FD_WAVE_3" "Levantamiento"
+ "MP_ANGEL_CITY_FD_WAVE_4" "Un grupo tenaz"
+ "MP_ANGEL_CITY_FD_WAVE_5" "Arriésgate"
+ "MP_DRYDOCK_FD_WAVE_1" "Nadando en aguas turbias"
+ "MP_DRYDOCK_FD_WAVE_2" "¡No tienen balas!"
+ "MP_DRYDOCK_FD_WAVE_3" "Cegados por la ciencia"
+ "MP_DRYDOCK_FD_WAVE_4" "Sistema de alta presión"
+ "MP_DRYDOCK_FD_WAVE_5" "Ojo de la tormenta"
+ "MP_THAW_FD_WAVE_1" "Atención a los alrededores"
+ "MP_THAW_FD_WAVE_2" "La unión nos hace más fuertes"
+ "MP_THAW_FD_WAVE_3" "Secuaz 21"
+ "MP_THAW_FD_WAVE_4" "Diversión en el fuego"
+ "MP_THAW_FD_WAVE_5" "Escoge las armas adecuadas"
+ "NO_PRICE" "Oferta caducada"
+ "NO_PRICE_TWO_LINES" "Oferta\ncaducada"
+ "PL_aegis_last_titan_standing" "UTEP Aegis"
+ "PL_aegis_last_titan_standing_abbr" "UTEPA"
+ "PL_aegis_last_titan_standing_desc" "Mejoras Aegis activadas. Eliminación por rondas. Gana el primero que gane 3 rondas.\n^FFC83200Jugadores: 5vs5 *Comienza como titán\nLímite de tiempo: 3 min. por ronda *Sin regeneraciones\nTamaño máximo del grupo: 5"
+ "PL_aegis_last_titan_standing_lobby" "PL_aegis_last_titan_standing_lobby"
+ "PL_aegis_titan_brawl" "Pelea de titanes Aegis"
+ "PL_aegis_titan_brawl_abbr" "PdTA"
+ "PL_aegis_titan_brawl_desc" "Elimina a los titanes enemigos. Mejoras Aegis activadas.\n^FFC83200Jugadores: 5vs5\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 5"
+ "PL_aegis_titan_brawl_hint" "Elimina a los titanes enemigos.\nSin eyección"
+ "PL_aegis_titan_brawl_lobby" "Lobby de Pelea de titanes Aegis"
+ "PL_aitdm" "Deterioro"
+ "PL_aitdm_abbr" "DET"
+ "PL_aitdm_desc" "Elimina a todos los enemigos.\n^FFC83200Jugadores: 6vs6 *IA\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 6"
+ "PL_aitdm_lobby" "Lobby de Deterioro"
+ "PL_all_grapple" "Ataque a los titanes"
+ "PL_all_grapple_abbr" "DET"
+ "PL_all_grapple_desc" "Las reglas clásicas de Deterioro, pero todas las habilidades tácticas se reemplazan por el arpeo.\n^FFC83200Jugadores: 6vs6 *IA\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 6"
+ "PL_all_grapple_lobby" "Lobby de Ataque a los titanes"
+ "PL_all_holopilot" "El gran engaño"
+ "PL_all_holopilot_abbr" "FV"
+ "PL_all_holopilot_desc" "Las reglas clásicas de Fuego vivo, pero todas las habilidades tácticas se reemplazan por el holopiloto.^FFC83200\nJugadores: 6vs6 *Sin titanes\nLímite de tiempo: 60 s *Sin regeneraciones\nNúmero máximo de miembros del grupo: 6"
+ "PL_all_holopilot_lobby" "Lobby de El gran engaño"
+ "PL_all_phase" "El otro lado"
+ "PL_all_phase_abbr" "DET"
+ "PL_all_phase_desc" "Las reglas clásicas de Deterioro, pero todas las habilidades tácticas se reemplazan por la fase.\n^FFC83200Jugadores: 6vs6 *IA\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 6"
+ "PL_all_phase_lobby" "Lobby de El otro lado"
+ "PL_all_spicy" "Deterioro picante"
+ "PL_all_spicy_abbr" "DET"
+ "PL_all_spicy_desc" "Las reglas clásicas de Deterioro, pero todas las habilidades tácticas se reemplazan por garrapatas.\n^FFC83200Jugadores: 6vs6 *IA\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 6"
+ "PL_all_spicy_lobby" "Lobby de Deterioro picante"
+ "PL_amped_tacticals" "Tácticas +"
+ "PL_amped_tacticals_abbr" "DET"
+ "PL_amped_tacticals_desc" "Las reglas clásicas de Deterioro, pero todas las habilidades tácticas son más potentes.\n^FFC83200Jugadores: 6vs6 *IA\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 6"
+ "PL_amped_tacticals_lobby" "Lobby de Tácticas +"
+ "PL_ANGEL_CITY" "Ciudad Ãngel 24/7"
+ "PL_angel_city_abbr" "CA"
+ "PL_angel_city_desc" "Toda Ciudad Ãngel, todo el tiempo.\n^FFC83200*Cazarrecompensas *Deterioro\n*Pto. Clave Amplificado"
+ "PL_angel_city_lobby" "Lobby de Ciudad Ãngel 24/7"
+ "PL_at_coop" "Escape (cooperativo)"
+ "PL_at_coop_desc" "Recién escapaste de la cárcel y solo tienes una pistola. Sobrevive hasta que puedas evacuar. Elimina enemigos para ganar dinero y comprar armas. \n^FFC83200Jugadores: 6\nNúmero máximo de miembros del grupo: 6"
+ "PL_at_coop_lobby" "Lobby de Escape"
+ "PL_attrition" "Cazarrecompensas"
+ "PL_attrition_abbr" "CAZ"
+ "PL_attrition_desc" "Elimina enemigos para ganar dinero. Obtén más depositando tus bonificaciones en el banco.\n^FFC83200Jugadores: 5vs5 *IA\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 5"
+ "PL_attrition_lobby" "Lobby de Cazarrecompensas"
+ "PL_capture_the_flag" "Captura la bandera"
+ "PL_capture_the_flag_abbr" "CLB"
+ "PL_capture_the_flag_desc" "¡Roba la bandera enemiga y regrésala a tu base mientras evitas que el equipo enemigo tome la tuya!\n^FFC83200Jugadores: 5vs5\nLímite de tiempo: 12 min.\nTamaño máximo del grupo: 5"
+ "PL_capture_the_flag_lobby" "Lobby de CLB"
+ "PL_coliseum" "Coliseo"
+ "PL_coliseum_desc" "Combate individual con movilidad mejorada en una jaula. Elimina a tu oponente y gana una ronda. El mejor en 3 de 5 rondas gana un regalo del defensor.^FFC83200\nJugadores: 1vs1 *Sin titanes\nLímite de tiempo: 3 min *Sin regeneraciones\n**^FFFFFF00REQUIERE BOLETO DE COLISEO o CUOTA DE ENTRADA PAGADA"
+ "PL_coliseum_lobby" "Lobby de Coliseo"
+ "PL_colony" "Colonia 24/7"
+ "PL_colony_abbr" "COL"
+ "PL_colony_desc" "Colonia, sin interrupciones. ^CCCCCC00Buscará partidas de^FFC83200Deterioro^CCCCCC00, ^FFC83200Batalla de pilotos^CCCCCC00 y ^FFC83200Último titán en pie^CCCCCC00."
+ "PL_colony_lobby" "Lobby de Colonia 24/7"
+ "PL_ctf_lf" "CLB (Nitro)"
+ "PL_ctf_lf_abbr" "CLB-N"
+ "PL_ctf_lf_desc" "El frenético Captura la bandera en mapas seleccionados.\n^FFC83200Jugadores: 5vs5\nTamaño máximo del grupo: 5\n^F4D5A600Regresos de bandera al instante"
+ "PL_ctf_lf_lobby" "Lobby de Nitro de CLB"
+ "PL_default_description" "Descripción predeterminada"
+ "PL_default_lobbytitle" "Título de lobby predeterminado"
+ "PL_default_name" "Nombre predeterminado"
+ "PL_don" "Doble o nada"
+ "PL_fd" "Defensa fronteriza"
+ "PL_fd_desc" "Defiéndete contra las oleadas de las fuerzas restantes de la Flota."
+ "PL_fd_easy" "Defensa fronteriza: Fácil"
+ "PL_fd_easy_desc" "Aplasta a la oposición"
+ "PL_fd_easy_lobby" "Defensa fronteriza: Lobby de dificultad fácil"
+ "PL_fd_hard" "Defensa fronteriza: Difícil"
+ "PL_fd_hard_desc" "Se requieren jugadas hábiles."
+ "PL_fd_hard_lobby" "Defensa fronteriza: Lobby de dificultad difícil"
+ "PL_fd_insane" "Defensa fronteriza: demencial"
+ "PL_fd_insane_desc" "No sobrevivirás"
+ "PL_fd_insane_lobby" "Lobby de Defensa fronteriza: demencial"
+ "PL_fd_lobby" "Lobby de Defensa fronteriza"
+ "PL_fd_master" "Defensa fronteriza: maestro"
+ "PL_fd_master_desc" "Solo los mejores saldrán con vida"
+ "PL_fd_master_lobby" "Lobby de Defensa fronteriza: maestro"
+ "PL_fd_normal" "Defensa fronteriza: normal"
+ "PL_fd_normal_desc" "Recomendado para jugadores con experiencia"
+ "PL_fd_normal_lobby" "Defensa fronteriza: Lobby de dificultad normal"
+ "PL_ffa" "Todos contra todos"
+ "PL_ffa_abbr" "TCT"
+ "PL_ffa_desc" "Cada piloto por su cuenta; elimina a todos los enemigos.\n^FFC83200Jugadores: 1vs11\nLímite de tiempo: 10 min.\nTamaño máximo de grupo: 1"
+ "PL_ffa_lobby" "Lobby de Todos contra todos"
+ "PL_fra" "Agentes Libres"
+ "PL_fra_abbr" "AGL"
+ "PL_fra_desc" "Estás por tu cuenta. Elimina enemigos para ganar. Recoge 3 baterías para un despliegue de titán.\n^FFC83200Jugadores: 1vs11\nLímite de tiempo: 15 min\nTamaño máximo del grupo: 1"
+ "PL_fra_lobby" "Lobby de Agentes Libres"
+ "PL_groud_war_lobby" "Lobby diverso 8vs8"
+ "PL_ground_war" "Diverso 8vs8"
+ "PL_ground_war_abbr" "8vs8"
+ "PL_ground_war_desc" "Incluye Escaramuza y Punto clave amplificado con gran número de jugadores en todos los mapas.\n^FFC83200Jugadores: 8vs8"
+ "PL_hardpoint" "Punto clave amplificado"
+ "PL_hardpoint_abbr" "PCA"
+ "PL_hardpoint_desc" "Captura y mantén un punto clave para ganar puntos. Los puntos clave amplificados dan el doble de puntos.\n^FFC83200Jugadores: 6vs6\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 6"
+ "PL_hardpoint_lobby" "Lobby de Punto clave amplificado"
+ "PL_hunted" "Cazado"
+ "PL_iron_last_titan_standing" "UTEP de hierro"
+ "PL_iron_last_titan_standing_abbr" "UTEPH"
+ "PL_iron_last_titan_standing_desc" "Solo para titanes, partida de rondas por eliminación. Gana el primero con 3 rondas ganadas.\n^FFC83200Jugadores: 5vs5 *SIN PILOTOS\nLímite de tiempo: 3 min por ronda *Sin regeneraciones\nNúmero máximo de miembros del grupo: 5"
+ "PL_iron_last_titan_standing_lobby" "PL_iron_last_titan_standing_lobby"
+ "PL_last_titan_standing" "Último titán en pie"
+ "PL_last_titan_standing_abbr" "UTEP"
+ "PL_last_titan_standing_desc" "Todos los jugadores inician en titanes en esta partida de rondas por eliminación. Gana el primero con 3 rondas ganadas.\n^FFC83200Jugadores: 5vs5 *Comienza como titán\nLímite de tiempo: 3 min. por ronda *Sin regeneraciones\nTamaño máximo del grupo: 5"
+ "PL_last_titan_standing_lobby" "Lobby de UTEP"
+ "PL_limited_time_mode" "Por tiempo limitado"
+ "PL_live_fire" "Fuego vivo"
+ "PL_live_fire_abbr" "FV"
+ "PL_live_fire_desc" "Combate frenético en un escenario de Fuego vivo. Elimina pilotos enemigos o ten la bandera cuando se acabe el tiempo para ganar la ronda.^FFC83200\nJugadores: 6vs6 *Sin titanes\nLímite de tiempo: 60 s *Sin regeneraciones\nNúmero máximo de miembros del grupo: 6"
+ "PL_live_fire_lobby" "Looby de Fuego vivo"
+ "PL_load_a_map_on_the_command_line" "Carga un mapa en la línea de comandos. Solo para desarrollo."
+ "PL_marked_for_death" "Marcado para morir"
+ "PL_marked_for_death_abbr" "MPM"
+ "PL_marked_for_death_desc" "Elimina o protege los objetivos marcados.\n^FFC83200Jugadores: 6vs6 \nLímite de tiempo: 12 min\nNúmero máximo de miembros del grupo: 6"
+ "PL_marked_for_death_lobby" "Lobby de Marcado para morir"
+ "PL_nitro_ffa" "TCT (Nitro)"
+ "PL_nitro_ffa_abbr" "TCT-N"
+ "PL_nitro_ffa_desc" "Todos contra todos acelerado en mapas seleccionados.\n^FFC83200Jugadores: 6\n^F4D5A600Sin titanes\nSin potenciadores"
+ "PL_nitro_ffa_lobby" "Lobby de TCT (Nitro)"
+ "PL_nitro_mixtape" "Diverso (Nitro)"
+ "PL_nitro_mixtape_abbr" "DVS-N"
+ "PL_nitro_mixtape_desc" "CLB, MPM y BDP acelerados en mapas seleccionados.\n^FFC83200Jugadores: 5vs5\nTamaño máximo del grupo: 5\n^F4D5A600Regresos de bandera al instante"
+ "PL_nitro_mixtape_lobby" "Lobby de Diverso"
+ "PL_pilot_hunter" "Escaramuza"
+ "PL_pilot_hunter_abbr" "ECM"
+ "PL_pilot_hunter_desc" "Elimina a los pilotos y titanes enemigos. \n^FFC83200Jugadores: 8vs8\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 8"
+ "PL_pilot_hunter_lobby" "Lobby de Escaramuza"
+ "PL_pilot_skirmish" "Batalla de pilotos"
+ "PL_pilot_skirmish_abbr" "BDP"
+ "PL_pilot_skirmish_desc" "Elimina a los pilotos enemigos. No se permiten despliegues de titán.\n^FFC83200Jugadores: 8vs8 *Sin titanes\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 8"
+ "PL_pilot_skirmish_lobby" "Lobby BDP"
+ "PL_pl_rebuild_all_paths" "Reconstruir todos los caminos"
+ "PL_pl_run_with_ai_ainrebuildonmapstart_2" "Ejecutar con ai_ainRebuildOnMapStart 2"
+ "PL_private_match" "Partida privada (beta)"
+ "PL_private_match_desc" "Juega una partida privada personalizada en el mapa y modo que quieras.\n^FFC83200INVITAR RED o INVITAR AMIGOS para jugar\n^FFC83200Jugadores: 1-16\nSin progresión"
+ "PL_private_match_lobby" "Lobby de la Beta de partida privada"
+ "PL_promo_coop" "COOPERATIVO de 4 jugadores"
+ "PL_raid" "Incursión"
+ "PL_rise" "Ascenso 24/7"
+ "PL_rise_abbr" "ASC"
+ "PL_rise_desc" "Ascenso, sin interrupciones. ^CCCCCC00Buscará partidas de ^FFC83200CLB^CCCCCC00, ^FFC83200Punto clave amplificado^CCCCCC00, ^FFC83200Batalla de pilotos^CCCCCC00, ^FFC83200Fuego vivo^CCCCCC00 y ^FFC83200Último titán en pie^CCCCCC00."
+ "PL_rise_lobby" "Lobby de Ascenso 24/7"
+ "PL_rocket_arena" "Zona de misiles"
+ "PL_rocket_arena_abbr" "FV"
+ "PL_rocket_arena_desc" "Las reglas clásicas de Fuego vivo con EPGs modificadas.^FFC83200\nJugadores: 6vs6 *Sin titanes\nLímite de tiempo: 90 s *Sin regeneraciones\nTamaño máximo del grupo: 6"
+ "PL_rocket_arena_lobby" "Lobby de Zona de misiles"
+ "PL_settings_for_when_someone_loads_a_map_on_the_command_line_do_not_edit_the_cmdlinemapload_1_below_-_this_makes_this_work" "Configuraciones para cuando alguien carga un mapa en la línea de comandos. No edites la cmdlineMapLoad 1 debajo porque se encarga de que esto funcione."
+ "PL_speedball" "Fuego vivo"
+ "PL_speedball_desc" "Lucha por la posesión de una bandera neutral. Elimina al equipo enemigo o ten la bandera cuando se acabe el tiempo para ganar la ronda. Gana el primero con 5 rondas ganadas.\n^FFC83200Jugadores: 6vs6 *Sin titanes\nLímite de tiempo: 60 segundos por ronda *Sin regeneraciones\nNúmero máximo de miembros del grupo: 6"
+ "PL_speedball_lobby" "Looby de Fuego vivo"
+ "PL_tactikill" "Eliminación táctica en Deterioro"
+ "PL_tactikill_abbr" "DET"
+ "PL_tactikill_desc" "Las reglas clásicas de Deterioro, pero todas las habilidades tácticas se reemplazan por completo con cada eliminación.\n^FFC83200Jugadores: 6vs6 *IA\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 6"
+ "PL_tactikill_lobby" "Lobby de Eliminación táctica en Deterioro"
+ "PL_titan_brawl" "Pelea de titanes"
+ "PL_titan_brawl_abbr" "PdT"
+ "PL_titan_brawl_desc" "Elimina a los titanes enemigos. No se permiten pilotos.\n^FFC83200Jugadores: 5vs5\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 5"
+ "PL_titan_brawl_hint" "Elimina a los titanes enemigos.\nSin eyección"
+ "PL_titan_brawl_lobby" "Lobby de Pelea de titanes"
+ "PL_titan_brawl_turbo" "Pelea de titanes turbo"
+ "PL_titan_brawl_turbo_abbr" "PdTT"
+ "PL_titan_brawl_turbo_desc" "Las reglas clásicas de Pelea de titanes con regeneración de impulsos y generación de núcleo más rápidas.\n^FFC83200Jugadores: 5vs5\nLímite de tiempo: 10 min.\nTamaño máximo del grupo: 5"
+ "PL_titan_brawl_turbo_hint" "Elimina a los titanes enemigos.\nSin eyección"
+ "PL_titan_brawl_turbo_lobby" "Lobby de Pelea de titanes turbo"
+ "PL_turbo_last_titan_standing" "UTEP turbo"
+ "PL_turbo_last_titan_standing_abbr" "UTEP"
+ "PL_turbo_last_titan_standing_desc" "Las reglas clásicas de UTEP con regeneración de impulsos y generación de núcleo más rápidas.\n^FFC83200Jugadores: 5vs5 *Comienza como titán\nLímite de tiempo: 3 min. por ronda *Sin regeneraciones\nTamaño máximo del grupo: 5"
+ "PL_turbo_last_titan_standing_lobby" "Lobby de UTEP turbo"
+ "PL_variety_pack" "Diverso"
+ "PL_variety_pack_desc" "Número variado de jugadores con una gran variedad de mapas en estos modos:\n^FFC83200*Cazarrecompensas *Deterioro\n*Último titán en pie *Punto clave\n*Pilotos contra pilotos *Captura la bandera"
+ "PL_variety_pack_lobby" "Lobby diverso"
+ "PL_wargames" "Juegos de guerra 24/7"
+ "PL_wargames_abbr" "JDG"
+ "PL_wargames_desc" "Juegos de guerra, sin interrupciones. ^CCCCCC00Buscará partidas de ^FFC83200Deterioro^CCCCCC00, ^FFC83200CLB^CCCCCC00, ^FFC83200Batalla de pilotos^CCCCCC00, ^FFC83200Punto clave amplificado^CCCCCC00 y ^FFC83200Último titán en pie^CCCCCC00."
+ "PL_wargames_lobby" "Lobby de Juegos de guerra 24/7"
+ "WATCH_TUTORIAL" "VER TUTORIAL"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/levels/mp_box.rson b/Northstar.Custom/scripts/levels/mp_box.rson
new file mode 100644
index 000000000..1a8d1ee70
--- /dev/null
+++ b/Northstar.Custom/scripts/levels/mp_box.rson
@@ -0,0 +1,8 @@
+//C:\depot\r2dev\game autofastfunction.pl
+
+IsTestMap: false
+When: "SERVER"
+Scripts:
+[
+ mp/levels/mp_box.nut
+] \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/_northstar_devcommands.gnut b/Northstar.Custom/scripts/vscripts/_northstar_devcommands.gnut
new file mode 100644
index 000000000..ebac1e3c0
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/_northstar_devcommands.gnut
@@ -0,0 +1,60 @@
+untyped
+global function NorthstarDevCommands_Init
+
+void function NorthstarDevCommands_Init()
+{
+ AddClientCommandCallback( "noclip", ClientCommandCallbackToggleNoclip )
+ AddClientCommandCallback( "script", ClientCommandCallbackEvalScript )
+ AddClientCommandCallback( "kill", ClientCommandCallbackKill )
+}
+
+bool function ClientCommandCallbackToggleNoclip( entity player, array<string> args )
+{
+ if ( GetConVarInt( "sv_cheats" ) != 1 )
+ return true
+
+ //if ( player.IsNoclipping() )
+ // DisableNoclipForEntityIndex( player.GetIndexForEntity() )
+ //else
+ // EnableNoclipForEntityIndex( player.GetIndexForEntity() )
+
+ // new way that doesn't require native stuff yay
+ if ( player.IsNoclipping() )
+ player.SetPhysics( MOVETYPE_WALK ) // just hoping this is the right movetype, not much of a way to check
+ else
+ player.SetPhysics( MOVETYPE_NOCLIP )
+
+ return true
+}
+
+bool function ClientCommandCallbackEvalScript( entity player, array<string> args )
+{
+ if ( args.len() < 1 || GetConVarInt( "sv_cheats" ) != 1 )
+ return true
+
+ // todo: rewrite this at some point to use a concommand because clientcommands can't just take in a single string with spaces, quotes etc
+ // should just have the concommand call a clientcommand manually with properly formatted args
+ string joinedArgs = args[0]
+ for ( int i = 1; i < args.len(); i++ )
+ joinedArgs += " " + args[i]
+
+ try
+ {
+ compilestring( joinedArgs )()
+ }
+ catch (exception)
+ {
+ // should probably send this to the client at some point
+ // no need to log here because compilestring errors already do that
+ }
+
+ return true
+}
+
+bool function ClientCommandCallbackKill( entity player, array<string> args )
+{
+ if ( IsAlive( player ) && GetConVarInt( "sv_cheats" ) == 1 )
+ player.Die()
+
+ return true
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_arena.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_arena.gnut
new file mode 100644
index 000000000..4e6217e85
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_arena.gnut
@@ -0,0 +1,99 @@
+global function GameModeArena_Init
+
+struct {
+ entity imcBoostStore
+ entity militiaBoostStore
+
+ entity imcShield
+ entity militiaShield
+} file
+
+void function GameModeArena_Init()
+{
+ AddCallback_EntitiesDidLoad( CreateBoostStores )
+ AddCallback_GameStateEnter( eGameState.Prematch, StartBuyPhase )
+ AddCallback_GameStateEnter( eGameState.Playing, FinishBuyPhase )
+
+ // todo: need a custom intro for this that allows players to move, buy etc in prematch
+ // if that's actually possible lol not sure it is
+}
+
+void function CreateBoostStores()
+{
+ array<entity> startspawns = GetEntArrayByClass_Expensive( "info_spawnpoint_human_start" ) // easier to do this than use a spawn callback imo
+
+ vector imcAverageOrigin
+ float imcAverageAngle
+ int imcNumSpawns
+
+ vector militiaAverageOrigin
+ float militiaAverageAngle
+ int militiaNumSpawns
+
+ foreach ( entity startspawn in startspawns )
+ {
+ if ( startspawn.GetTeam() == TEAM_IMC )
+ {
+ imcAverageOrigin += startspawn.GetOrigin()
+ imcAverageAngle += startspawn.GetAngles().y
+ imcNumSpawns++
+ }
+ else
+ {
+ militiaAverageOrigin += startspawn.GetOrigin()
+ militiaAverageAngle += startspawn.GetAngles().y
+ militiaNumSpawns++
+ }
+ }
+
+ // create imc boost store
+ vector finalPositionImc = < imcAverageOrigin.x / imcNumSpawns, imcAverageOrigin.y / imcNumSpawns, imcAverageOrigin.z / imcNumSpawns >
+ finalPositionImc += ( 200 * AnglesToForward( < 0, imcAverageAngle / imcNumSpawns, 0 > ) )
+ CreateBoostStoreLocation( TEAM_IMC, finalPositionImc, < 0, 0, 0 >, true )
+
+ vector finalPositionMilitia = < militiaAverageOrigin.x / militiaNumSpawns, militiaAverageOrigin.y / militiaNumSpawns, militiaAverageOrigin.z / militiaNumSpawns >
+ finalPositionMilitia += ( 200 * AnglesToForward( < 0, militiaAverageAngle / militiaNumSpawns, 0 > ) )
+ CreateBoostStoreLocation( TEAM_MILITIA, finalPositionMilitia, < 0, 0, 0 >, true )
+
+ // createbooststorelocation is void so have to do this
+ // also boost store code is just fully fucked lol, teams only get set on open so can't compare teams at this point
+ // sorry if someone else makes their own boost stores lol this'll just break
+ // if there's some way to get the invisible crates used for boost stores i will be very happy
+
+ if ( GetBoostStores().len() != 2 )
+ print( "_gamemode_arena.gnut: there are more than 2 boost stores, very bad no good" )
+
+ file.imcBoostStore = GetBoostStores()[0]
+ file.militiaBoostStore = GetBoostStores()[1]
+}
+
+void function StartBuyPhase()
+{
+ //file.imcShield = CreateBubbleShieldWithSettings( TEAM_IMC, file.imcBoostStore.GetOrigin(), <0,0,0>, null, 15.0 )
+ //file.militiaShield = CreateBubbleShieldWithSettings( TEAM_MILITIA, file.militiaBoostStore.GetOrigin(), <0,0,0>, null, 15.0 )
+
+ entity bubbleShield = CreateEntity( "prop_dynamic" )
+ bubbleShield.SetValueForModelKey( $"models/fx/xo_shield.mdl" )
+ bubbleShield.kv.solid = 0
+ bubbleShield.kv.rendercolor = "255 255 255" // white
+ bubbleShield.kv.modelscale = 2.25
+ bubbleShield.SetOrigin( file.imcBoostStore.GetOrigin() )
+ DispatchSpawn( bubbleShield )
+
+ file.imcShield = bubbleShield
+
+ //SetTeam( bubbleShield, TEAM_IMC )
+
+ // current problem, there is seemingly no way of getting a shield we can resize which actually resizes the collision
+ // could probably just damage players that try to leave lol
+
+ OpenBoostStores()
+}
+
+void function FinishBuyPhase()
+{
+ file.imcShield.Destroy()
+ //file.militiaShield.Destroy()
+
+ CloseBoostStores()
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_fastball.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_fastball.gnut
new file mode 100644
index 000000000..52b563d4b
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_fastball.gnut
@@ -0,0 +1,194 @@
+untyped
+global function GamemodeFastball_Init
+
+struct {
+ // first panel is a, second is b, third is c
+ array<Point> panelSpawns
+} file
+
+void function GamemodeFastball_Init()
+{
+ // used for intro
+ PrecacheModel( $"models/titans/buddy/titan_buddy.mdl" )
+ PrecacheParticleSystem( $"P_BT_eye_SM" )
+
+ // used for respawn
+ PrecacheParticleSystem( $"P_pod_screen_lasers_OUT" )
+
+ SetRoundBased( true )
+ SetRespawnsEnabled( false )
+ Riff_ForceSetEliminationMode( eEliminationMode.Pilots )
+
+ // implementing intro in a different file because it'll likely be big
+ ClassicMP_SetCustomIntro( GamemodeFastballIntroSetup, 14.5 ) // bit of a guess number
+ AddCallback_EntitiesDidLoad( SpawnPanelsForLevel )
+ AddCallback_GameStateEnter( eGameState.Prematch, ResetPanels )
+ SetTimeoutWinnerDecisionFunc( FastballDecideWinner )
+
+ AddCallback_OnClientConnected( FastballInitPlayer )
+ AddCallback_OnPlayerKilled( FastballOnPlayerKilled ) // move this to a system in _gamestate soon!!
+
+ // setup spawns
+ // first is a, second is b, third is c
+
+ FastballAddBuddySpawnForLevel( "mp_angel_city", TEAM_IMC, < 2281.39, -3333.06, 200.031 >, < 0, 91.23, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_angel_city", TEAM_MILITIA, < -4139.57, 4684.4, 41.0313 >, <0, -14.326, 0 > )
+ FastballAddPanelSpawnsForLevel( "mp_angel_city", [
+ < 2055.94, 2040.03, 128.031 >, < 0, -180, 0 >,
+ < -274.757, 2255.22, 400.031 >, < 0, -176.251, 0 >,
+ < -3208.28, 2741.17, 128.031 >, < 0, -0.0821686, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_thaw", TEAM_MILITIA, < 2049.29, -4085.22, -274.839 >, < 0, 89.2991, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_thaw", TEAM_IMC, < 834.484, 2664.28, -380.515 >, < 0.532141, -90.875, -0.542593 > )
+ FastballAddPanelSpawnsForLevel( "mp_thaw", [
+ < -1026.71, -1691.93, -319.969 >, < 0, 90, 0 >,
+ < 1836.07, -538.823, -64.1245 >, < 0, -135, 0 >,
+ < 2840.15, 1321.17, -63.9688 >, < 0, 0, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_wargames", TEAM_MILITIA, < -4848.87, 682.17, -127.969 >, < 0, 0, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_wargames", TEAM_IMC, < 2960.78, 1229.36, -127.969 >, < 7.89891e-005, 146.505, 1.38387e-005 > )
+ FastballAddPanelSpawnsForLevel( "mp_wargames", [
+ < -691.961, 1888.56, 112.031 >, < 0, 90, 0 >,
+ < -1072.03, -508.229, -127.969 >, < 0, 180, 0 >,
+ < -24.291, -1403.69, -119.969 >, < 0, -90, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_eden", TEAM_MILITIA, < -2404.4, 1738.07, 167.767 >, <0, -40.0894, 0> )
+ FastballAddBuddySpawnForLevel( "mp_eden", TEAM_IMC, < 5248.01, 414.698, 77.1051 >, < 0, 180, 0 > )
+ FastballAddPanelSpawnsForLevel( "mp_eden", [
+ < 704.333, 1530.18, 144.031 >, < 0, 90, 0 >,
+ < -108.43, 272.638, 72.0313 >, < 0, -90, 0 >,
+ <1044.25, -1145.68, 68.0313>, < 0, 180, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_black_water_canal", TEAM_MILITIA, < 1222.88, -5050.63, -187.763 >, < 0, 45, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_black_water_canal", TEAM_IMC, < -235.375, 4736.75, -255.969 >, < 0, -90, 0 > )
+ FastballAddPanelSpawnsForLevel( "mp_black_water_canal", [
+ < 1566.13, -731.539, -63.9688 >, < 0, -90, 0 >,
+ < 502.603, 1102.92, 260.031 >, < 0, 152.328, 0 >,
+ < 2337.37, 2099.91, -26.9688 >, < 0, 0, 0 >
+ ])
+
+ FastballAddBuddySpawnForLevel( "mp_grave", TEAM_MILITIA, < 11026.8, -5163.18, 1885.64 >, < 0, 155.05, 0 > )
+ FastballAddBuddySpawnForLevel( "mp_grave", TEAM_IMC, < -1952, -3120, 1993.33 >, < 0, 0, 0 > )
+ FastballAddPanelSpawnsForLevel( "mp_grave", [
+ < 5204.54, -2726.54, 2376.03 >, < 0, 90, 0 >,
+ < 6001.58, -4126.61, 2252.03 >, < 0, -135, 0 >,
+ < 3595.96, -4568.04, 2376.03 >, < 0, -135, 0 >
+ ])
+}
+
+void function SpawnPanelsForLevel()
+{
+ int panelId
+ foreach ( Point panelSpawn in file.panelSpawns )
+ {
+ entity panel = CreatePanel( panelSpawn.origin, panelSpawn.angles )
+ panel.s.panelId <- panelId++
+ }
+}
+
+void function ResetPanels()
+{
+ foreach ( entity panel in GetAllControlPanels() )
+ SetTeam( panel, TEAM_UNASSIGNED )
+}
+
+void function FastballInitPlayer( entity player )
+{
+ foreach ( entity panel in GetAllControlPanels() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_FastballUpdatePanelRui", panel.GetEncodedEHandle(), panel.s.panelId )
+}
+
+void function FastballAddPanelSpawnsForLevel( string level, array<vector> positionsAndOrigins )
+{
+ if ( GetMapName() != level )
+ return
+
+ for ( int i = 0; i < positionsAndOrigins.len(); i += 2 )
+ {
+ Point spawnPoint
+ spawnPoint.origin = positionsAndOrigins[ i ]
+ spawnPoint.angles = positionsAndOrigins[ i + 1 ]
+
+ file.panelSpawns.append( spawnPoint )
+ }
+}
+
+entity function CreatePanel( vector origin, vector angles )
+{
+ entity panel = CreateEntity( "prop_control_panel" )
+ panel.SetValueForModelKey( $"models/communication/terminal_usable_imc_01.mdl" )
+ panel.SetOrigin( origin )
+ panel.SetAngles( angles )
+ panel.kv.solid = SOLID_VPHYSICS
+ DispatchSpawn( panel )
+
+ panel.SetModel( $"models/communication/terminal_usable_imc_01.mdl" )
+ panel.s.onPlayerFinishesUsing_func = FastballOnPanelHacked
+
+ Highlight_SetNeutralHighlight( panel, "sp_enemy_pilot" )
+
+ return panel
+}
+
+// control panel code isn't very statically typed, pain
+function FastballOnPanelHacked( panel, player, success )
+{
+ expect entity( panel )
+ expect entity( player )
+ expect bool( success )
+
+ if ( !success )
+ return
+
+ print( panel + " was hacked by " + player )
+ PanelFlipsToPlayerTeamAndUsableByEnemies( panel, player )
+ player.SetPlayerGameStat( PGS_ASSAULT_SCORE, player.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 )
+
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_FastballPanelHacked", panel.GetEncodedEHandle(), panel.s.panelId, player.GetEncodedEHandle() )
+
+ // respawn dead players
+ foreach ( entity deadPlayer in GetPlayerArrayOfTeam( player.GetTeam() ) )
+ {
+ if ( !IsAlive( deadPlayer ) )
+ {
+ deadPlayer.SetOrigin( player.GetOrigin() )
+ deadPlayer.RespawnPlayer( null )
+ Remote_CallFunction_NonReplay( deadPlayer, "ServerCallback_FastballRespawnPlayer" )
+ }
+ }
+}
+
+void function FastballOnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( GetPlayerArrayOfTeam_Alive( victim.GetTeam() ).len() == 0 )
+ SetWinner( GetOtherTeam( victim.GetTeam() ) )
+}
+
+int function FastballDecideWinner()
+{
+ int militiaPanels
+ int imcPanels
+
+ foreach ( entity panel in GetAllControlPanels() )
+ {
+ if ( panel.GetTeam() == TEAM_MILITIA )
+ militiaPanels++
+ else if ( panel.GetTeam() == TEAM_IMC )
+ imcPanels++
+ }
+
+ if ( militiaPanels > imcPanels )
+ return TEAM_MILITIA
+ else if ( imcPanels > militiaPanels )
+ return TEAM_IMC
+
+ return TEAM_UNASSIGNED
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut
new file mode 100644
index 000000000..6a1d0bbdd
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut
@@ -0,0 +1,208 @@
+untyped
+global function GamemodeFastballIntroSetup
+global function FastballAddBuddySpawnForLevel
+
+struct {
+ float introStartTime
+
+ table< int, Point > buddySpawns
+ entity militiaBuddy
+ entity imcBuddy
+
+ table<entity, bool> playersHoldingJump
+} file
+
+void function GamemodeFastballIntroSetup()
+{
+ RegisterSignal( "fastball_start_throw" )
+ RegisterSignal( "fastball_release" )
+
+ AddCallback_GameStateEnter( eGameState.Prematch, OnPrematchStart )
+ AddCallback_OnClientConnected( AddPlayerToFastballIntro )
+}
+
+void function FastballAddBuddySpawnForLevel( string level, int team, vector origin, vector angles )
+{
+ if ( GetMapName() != level )
+ return
+
+ Point spawnPoint
+ spawnPoint.origin = origin
+ spawnPoint.angles = angles
+
+ file.buddySpawns[ team ] <- spawnPoint
+}
+
+void function OnPrematchStart()
+{
+ ClassicMP_OnIntroStarted()
+
+ file.introStartTime = Time()
+ file.playersHoldingJump = {} // clear it
+
+ Point militiaBuddySpawn
+ Point imcBuddySpawn
+
+ // figure out positions if there's none manually specified
+ if ( file.buddySpawns.len() == 0 )
+ {
+ array<entity> militiaSpawns
+ array<entity> imcSpawns
+
+ foreach ( entity spawnpoint in GetEntArrayByClass_Expensive( "info_spawnpoint_titan_start" ) )
+ {
+ // trace from top to bottom
+ float result = TraceHullSimple( spawnpoint.GetOrigin() + < 0, 0, 250 >, spawnpoint.GetOrigin(), < -200, -200, 0 >, < 200, 200, 400 >, null )
+
+ // don't need to trace much, as long as it's over 0 that means it fits
+ // cases where it's over 0 but less than 1 are usually caused by terrain
+ if ( result > 0 )
+ {
+ if ( spawnpoint.GetTeam() == TEAM_MILITIA )
+ militiaSpawns.append( spawnpoint )
+ else
+ imcSpawns.append( spawnpoint )
+ }
+ }
+
+ entity milititaSpawnEnt = militiaSpawns[ RandomInt( militiaSpawns.len() ) ]
+ militiaBuddySpawn.origin = milititaSpawnEnt.GetOrigin()
+ militiaBuddySpawn.angles = milititaSpawnEnt.GetAngles()
+
+ entity imcSpawnEnt = imcSpawns[ RandomInt( imcSpawns.len() ) ]
+ imcBuddySpawn.origin = imcSpawnEnt.GetOrigin()
+ imcBuddySpawn.angles = imcSpawnEnt.GetAngles()
+ }
+ else
+ {
+ militiaBuddySpawn = file.buddySpawns[ TEAM_MILITIA ]
+ imcBuddySpawn = file.buddySpawns[ TEAM_IMC ]
+ }
+
+ file.militiaBuddy = CreatePropDynamic( $"models/titans/buddy/titan_buddy.mdl" )
+ file.militiaBuddy.SetOrigin( militiaBuddySpawn.origin )
+ file.militiaBuddy.SetAngles( militiaBuddySpawn.angles )
+ thread AnimateBuddy( file.militiaBuddy )
+
+ file.imcBuddy = CreatePropDynamic( $"models/titans/buddy/titan_buddy.mdl" )
+ file.imcBuddy.SetOrigin( imcBuddySpawn.origin )
+ file.imcBuddy.SetAngles( imcBuddySpawn.angles )
+ thread AnimateBuddy( file.imcBuddy )
+
+ foreach ( entity player in GetPlayerArray() )
+ thread FastballPlayer( player )
+}
+
+void function AnimateBuddy( entity buddy )
+{
+ print( "buddy spawn at " + buddy.GetOrigin() + " " + buddy.GetAngles() )
+
+ thread PlayAnim( buddy, "bt_beacon_fastball_throw_end" )
+
+ // play dialogue at the right time
+ buddy.WaitSignal( "fastball_start_throw" )
+ float diagDuration = EmitSoundOnEntity( buddy, "diag_sp_spoke1_BE117_04_01_mcor_bt" ) // trust me
+ StartParticleEffectOnEntity( buddy, GetParticleSystemIndex( $"P_BT_eye_SM" ), FX_PATTACH_POINT_FOLLOW, buddy.LookupAttachment( "EYEGLOW" ) )
+
+ wait diagDuration
+ if ( GetGameState() != eGameState.Playing )
+ ClassicMP_OnIntroFinished()
+
+ buddy.WaitSignal( "fastball_release" )
+ wait 5.0
+ buddy.Destroy()
+}
+
+void function AddPlayerToFastballIntro( entity player )
+{
+ if ( GetGameState() == eGameState.Prematch )
+ thread FastballPlayer( player )
+}
+
+void function FastballPlayer( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+
+ OnThreadEnd( function() : ( player )
+ {
+ RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
+ player.ClearParent()
+ ClearPlayerAnimViewEntity( player )
+ player.DeployWeapon()
+ player.PlayerCone_Disable()
+
+ RemoveButtonPressedPlayerInputCallback( player, IN_JUMP, PlayerHoldingJumpInIntro )
+ RemoveButtonReleasedPlayerInputCallback( player, IN_JUMP, PlayerNoLongerHoldingJumpInIntro )
+ })
+
+ FirstPersonSequenceStruct throwSequence
+ throwSequence.attachment = "REF"
+ throwSequence.useAnimatedRefAttachment = true
+ throwSequence.hideProxy = true
+ throwSequence.viewConeFunction = ViewConeFastball // this seemingly does not trigger for some reason
+ throwSequence.firstPersonAnim = "ptpov_beacon_fastball_throw_end"
+ // mp models seemingly have no 3p animation for this
+ throwSequence.firstPersonBlendOutTime = 0.0
+ throwSequence.teleport = true
+ throwSequence.setInitialTime = Time() - file.introStartTime
+
+ // get our buddy
+ entity buddy
+ if ( player.GetTeam() == TEAM_MILITIA )
+ buddy = file.militiaBuddy
+ else
+ buddy = file.imcBuddy
+
+ // respawn the player
+ player.SetOrigin( buddy.GetOrigin() )
+ player.RespawnPlayer( null )
+ player.Hide()
+ player.HolsterWeapon()
+
+ // hide hud, fade screen out from black
+ AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
+ ScreenFadeFromBlack( player, 0.5, 0.5 )
+
+ // start fp sequence
+ thread FirstPersonSequence( throwSequence, player, buddy )
+
+ // manually do this because i can't get viewconefastball to work
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -50 )
+ player.PlayerCone_SetMaxYaw( 25 )
+ player.PlayerCone_SetMinPitch( -15 )
+ player.PlayerCone_SetMaxPitch( 15 )
+
+ buddy.WaitSignal( "fastball_start_throw" )
+ // lock in their final angles at this point
+ vector throwVel = AnglesToForward( player.EyeAngles() ) * 950
+ throwVel.z = 675.0
+
+ // allow players to gain extra height by holding jump after this point too
+ AddButtonPressedPlayerInputCallback( player, IN_JUMP, PlayerHoldingJumpInIntro )
+ AddButtonReleasedPlayerInputCallback( player, IN_JUMP, PlayerNoLongerHoldingJumpInIntro )
+
+ // wait for it to finish
+ buddy.WaitSignal( "fastball_release" )
+
+ if ( player in file.playersHoldingJump && file.playersHoldingJump[ player ] )
+ throwVel.z = 850.0
+
+ // have to correct this manually here since due to no 3p animation our position isn't set right during this sequence
+ player.SetOrigin( buddy.GetAttachmentOrigin( buddy.LookupAttachment( "FASTBALL_R" ) ) )
+ player.Show()
+ player.SetVelocity( throwVel )
+
+ TryGameModeAnnouncement( player )
+}
+
+void function PlayerHoldingJumpInIntro( entity player )
+{
+ file.playersHoldingJump[ player ] <- true
+}
+
+void function PlayerNoLongerHoldingJumpInIntro( entity player )
+{
+ file.playersHoldingJump[ player ] <- false
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_gg.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_gg.gnut
new file mode 100644
index 000000000..fd41236c4
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_gg.gnut
@@ -0,0 +1,111 @@
+global function GamemodeGG_Init
+
+void function GamemodeGG_Init()
+{
+ SetSpawnpointGamemodeOverride( FFA )
+
+ Evac_SetEnabled( false )
+ SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period
+ SetWeaponDropsEnabled( false )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ Riff_ForceBoostAvailability( eBoostAvailability.Disabled )
+
+ AddCallback_OnPlayerRespawned( OnPlayerRespawned )
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+
+ AddCallback_GameStateEnter( eGameState.WinnerDetermined, OnWinnerDetermined )
+}
+
+void function OnPlayerRespawned( entity player )
+{
+ UpdateLoadout( player )
+ thread OnPlayerRespawned_Threaded( player )
+}
+
+void function OnPlayerRespawned_Threaded( entity player )
+{
+ // bit of a hack, need to rework earnmeter code to have better support for completely disabling it
+ // rn though this just waits for earnmeter code to set the mode before we set it back
+ WaitFrame()
+ PlayerEarnMeter_SetMode( player, eEarnMeterMode.DISABLED )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || !attacker.IsPlayer() )
+ return
+
+ if ( attacker == victim ) // suicide
+ {
+ string message = victim.GetPlayerName() + " committed suicide."
+ foreach ( entity player in GetPlayerArray() )
+ SendHudMessage( player, message, -1, 0.4, 255, 0, 0, 0, 0, 3, 0.15 )
+
+ if ( GameRules_GetTeamScore( victim.GetTeam() ) != 0 )
+ {
+ AddTeamScore( victim.GetTeam(), -1 ) // get absolutely fucking destroyed lol
+ victim.AddToPlayerGameStat( PGS_ASSAULT_SCORE, -1 )
+ }
+ }
+ else
+ {
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) != eDamageSourceId.melee_pilot_emptyhanded )
+ {
+ AddTeamScore( attacker.GetTeam(), 1 )
+ attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 )
+ UpdateLoadout( attacker )
+ }
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == eDamageSourceId.human_execution )
+ {
+ string message = victim.GetPlayerName() + " got executed."
+ foreach ( entity player in GetPlayerArray() )
+ SendHudMessage( player, message, -1, 0.4, 255, 0, 0, 0, 0, 3, 0.15 )
+
+ if ( GameRules_GetTeamScore( victim.GetTeam() ) != 0 )
+ {
+ AddTeamScore( victim.GetTeam(), -1 ) // get absolutely fucking destroyed lol
+ victim.AddToPlayerGameStat( PGS_ASSAULT_SCORE, -1 )
+ }
+ }
+ }
+}
+
+void function UpdateLoadout( entity player )
+{
+ int currentWeaponIndex = GameRules_GetTeamScore( player.GetTeam() )
+ array<GunGameWeapon> weapons = GetGunGameWeapons()
+
+ if ( currentWeaponIndex >= weapons.len() )
+ currentWeaponIndex = weapons.len() - 1
+
+ if ( currentWeaponIndex > 18 ) // play end of game music for special weapons
+ PlayMusicToAll( eMusicPieceID.LEVEL_LAST_MINUTE ) // this *shouldn't* overlap if done multiple times
+
+ GunGameWeapon weapon = weapons[ currentWeaponIndex ]
+
+ foreach ( entity weapon in player.GetMainWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ foreach ( entity weapon in player.GetOffhandWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ if ( weapon.offhandSlot != -1 )
+ {
+ // TEMP: give archer so player so player has a weapon which lets them use offhands
+ // need to replace this with a custom empty weapon at some point
+ player.GiveWeapon( "mp_weapon_rocket_launcher" )
+
+ player.GiveOffhandWeapon( weapon.weapon, weapon.offhandSlot, weapon.mods )
+ }
+ else
+ player.GiveWeapon( weapon.weapon, weapon.mods )
+
+ player.GiveOffhandWeapon( "melee_pilot_emptyhanded", OFFHAND_MELEE )
+}
+
+void function OnWinnerDetermined()
+{
+ SetRespawnsEnabled( false )
+ SetKillcamsEnabled( false )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_inf.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_inf.gnut
new file mode 100644
index 000000000..b7af9b856
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_inf.gnut
@@ -0,0 +1,176 @@
+global function GamemodeInfection_Init
+
+struct {
+ bool hasHadFirstInfection = false
+ array<entity> playersToNotifyOfInfection
+} file
+
+void function GamemodeInfection_Init()
+{
+ SetSpawnpointGamemodeOverride( FFA )
+ SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period
+ SetWeaponDropsEnabled( false )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ Riff_ForceBoostAvailability( eBoostAvailability.Disabled )
+
+ AddCallback_OnClientConnected( InfectionInitPlayer )
+ AddCallback_OnPlayerKilled( InfectionOnPlayerKilled )
+ AddCallback_OnPlayerRespawned( RespawnInfected )
+ AddCallback_GameStateEnter( eGameState.Playing, SelectFirstInfected )
+
+ SetTimeoutWinnerDecisionFunc( TimeoutCheckSurvivors )
+}
+
+void function InfectionInitPlayer( entity player )
+{
+ if ( GetGameState() < eGameState.Playing )
+ SetTeam( player, INFECTION_TEAM_SURVIVOR )
+ else
+ InfectPlayer( player )
+}
+
+void function SelectFirstInfected()
+{
+ thread SelectFirstInfectedDelayed()
+}
+
+void function SelectFirstInfectedDelayed()
+{
+ wait 10.0 + RandomFloat( 5.0 )
+
+ array<entity> players = GetPlayerArray()
+ entity infected = players[ RandomInt( players.len() ) ]
+
+ InfectPlayer( infected )
+ RespawnInfected( infected )
+}
+
+void function InfectionOnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() )
+ return
+
+ if ( victim.GetTeam() == INFECTION_TEAM_SURVIVOR )
+ InfectPlayer( victim )
+
+ if ( attacker.IsPlayer() )
+ attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 )
+}
+
+void function InfectPlayer( entity player )
+{
+ SetTeam( player, INFECTION_TEAM_INFECTED )
+ player.SetPlayerGameStat( PGS_ASSAULT_SCORE, 0 ) // reset kills
+ file.playersToNotifyOfInfection.append( player )
+
+ // check how many survivors there are
+ array<entity> survivors = GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR )
+ if ( survivors.len() == 0 )
+ SetWinner( INFECTION_TEAM_INFECTED )
+ else if ( survivors.len() == 1 )
+ SetLastSurvivor( survivors[ 0 ] )
+
+ if ( !file.hasHadFirstInfection )
+ {
+ file.hasHadFirstInfection = true
+
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ if ( player != otherPlayer )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_AnnounceFirstInfected", player.GetEncodedEHandle() )
+
+ PlayMusicToAll( eMusicPieceID.GAMEMODE_1 )
+ }
+}
+
+void function RespawnInfected( entity player )
+{
+ if ( player.GetTeam() != INFECTION_TEAM_INFECTED )
+ return
+
+ // notify newly infected players of infection
+ if ( file.playersToNotifyOfInfection.contains( player ) )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_YouAreInfected" )
+ file.playersToNotifyOfInfection.remove( file.playersToNotifyOfInfection.find( player ) )
+ }
+
+ // set camo to pond scum
+ player.SetSkin( 1 )
+ player.SetCamo( 110 )
+
+ // stats for infected
+ StimPlayer( player, 9999.9 ) // can't do endless since we don't get the visual effect in endless
+ player.SetMaxHealth( 50 )
+
+ // set loadout
+ foreach ( entity weapon in player.GetMainWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ foreach ( entity weapon in player.GetOffhandWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ // TEMP: give archer so player so player has a weapon which lets them use offhands
+ // need to replace this with a custom empty weapon at some point
+ //player.GiveWeapon( "mp_weapon_rocket_launcher" )
+ player.GiveWeapon( "mp_weapon_mgl" )
+ player.GiveOffhandWeapon( "melee_pilot_emptyhanded", OFFHAND_MELEE )
+
+ thread PlayInfectedSounds( player )
+}
+
+void function PlayInfectedSounds( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+
+ float nextRandomSound
+ while ( true )
+ {
+ WaitFrame()
+
+ int meleeState = player.PlayerMelee_GetState()
+ if ( nextRandomSound < Time() || meleeState != 0 )
+ {
+ string selectedSound
+ if ( CoinFlip() )
+ selectedSound = "prowler_vocal_attack"
+ else
+ selectedSound = "prowler_vocal_attackmiss"
+
+ bool canSeeSurvivor
+ foreach ( entity survivor in GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ) )
+ if ( TraceLineSimple( player.GetOrigin(), survivor.GetOrigin(), survivor ) == 1.0 )
+ canSeeSurvivor = true
+
+ // _int sounds are less agressive so only play them if we aren't in some sorta fight
+ if ( player.GetHealth() == player.GetMaxHealth() || !canSeeSurvivor || meleeState != 0 )
+ selectedSound += "_int"
+
+ EmitSoundOnEntity( player, selectedSound )
+
+ nextRandomSound = Time() + max( 2.5, RandomFloat( 12.0 ) )
+ while ( player.PlayerMelee_GetState() != 0 ) // need to ensure this is updated
+ WaitFrame()
+ }
+ }
+}
+
+void function SetLastSurvivor( entity player )
+{
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_AnnounceLastSurvivor", player.GetEncodedEHandle() )
+
+ Highlight_SetEnemyHighlight( player, "enemy_sonar" )
+ thread CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) )
+
+ if ( GameTime_TimeLeftSeconds() > 45 )
+ SetServerVar( "gameEndTime", Time() + 45.0 )
+}
+
+int function TimeoutCheckSurvivors()
+{
+ if ( GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ).len() > 0 )
+ return INFECTION_TEAM_SURVIVOR
+
+ return INFECTION_TEAM_INFECTED
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_kr.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_kr.gnut
new file mode 100644
index 000000000..cf9d6bc57
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_kr.gnut
@@ -0,0 +1,126 @@
+global function GamemodeKR_Init
+
+struct {
+ float currentHighestKillraceAmount
+ int currentKillraceScore
+ entity currentRacer
+ array<vector> flagSpawnPoints
+} file
+
+void function GamemodeKR_Init()
+{
+ PrecacheModel( CTF_FLAG_MODEL )
+
+ SetSpawnpointGamemodeOverride( FFA )
+
+ Evac_SetEnabled( false )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ Riff_ForceBoostAvailability( eBoostAvailability.Disabled )
+
+ AddSpawnCallback( "info_hardpoint", AddFlagSpawnPoint )
+
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+ AddCallback_OnTouchHealthKit( "item_flag", StartPlayerKillrace )
+
+ AddCallback_GameStateEnter( eGameState.Playing, StartKillraceSpawnThink )
+}
+
+void function AddFlagSpawnPoint( entity hardpoint )
+{
+ if ( hardpoint.HasKey( "hardpointGroup" ) && ( hardpoint.kv.hardpointGroup == "A" || hardpoint.kv.hardpointGroup == "B" || hardpoint.kv.hardpointGroup == "C" ) )
+ file.flagSpawnPoints.append( hardpoint.GetOrigin() )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || !attacker.IsPlayer() || attacker == victim )
+ return
+
+ float killRaceTime = attacker.GetPlayerNetTime( "killRaceTime" ) + 5.0
+ attacker.SetPlayerNetTime( "killRaceTime", killRaceTime )
+ if ( killRaceTime > file.currentHighestKillraceAmount )
+ file.currentHighestKillraceAmount = killRaceTime
+ if ( file.currentRacer != null )
+ file.currentKillraceScore++
+}
+
+bool function StartPlayerKillrace( entity player, entity flag )
+{
+ float killRaceTime = player.GetPlayerNetTime( "killRaceTime" )
+ if ( killRaceTime > 0.0 )
+ {
+ thread PlayerKillrace( player, killRaceTime )
+ return true // delete the flag entity
+ }
+
+ return false // keep it alive
+}
+
+void function PlayerKillrace( entity player, float raceTime )
+{
+ file.currentKillraceScore = 0
+ file.currentRacer = player
+ int oldMaxHealth = player.GetMaxHealth()
+
+ player.SetMaxHealth( oldMaxHealth * 10 )
+ player.SetHealth( player.GetMaxHealth() )
+
+ foreach ( entity weapon in player.GetMainWeapons() )
+ foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
+ weapon.AddMod( mod )
+
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_NewKillRacer", player.GetEncodedEHandle(), Time() + raceTime )
+
+ float raceEnd = Time() + raceTime
+ while ( raceEnd > Time() && IsAlive( player ) )
+ WaitFrame()
+
+ player.SetPlayerNetTime( "killRaceTime", 0.0 )
+ player.SetMaxHealth( oldMaxHealth )
+
+ foreach ( entity weapon in player.GetMainWeapons() )
+ foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) )
+ weapon.RemoveMod( mod )
+
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_EndKillrace", player.GetEncodedEHandle(), file.currentKillraceScore )
+
+ if ( GameRules_GetTeamScore( player.GetTeam() ) < file.currentKillraceScore )
+ {
+ GameRules_SetTeamScore( player.GetTeam(), file.currentKillraceScore )
+ player.SetPlayerGameStat( PGS_ASSAULT_SCORE, file.currentKillraceScore )
+ }
+
+ thread KillraceSpawnThink() // go to spawn next flag
+}
+
+void function StartKillraceSpawnThink()
+{
+ thread KillraceSpawnThink()
+}
+
+void function KillraceSpawnThink()
+{
+ file.currentHighestKillraceAmount = 0
+ file.currentRacer = null
+ file.currentKillraceScore = 0
+ float time = Time()
+ while ( time + 20.0 > Time() && file.currentHighestKillraceAmount < 25 )
+ WaitFrame()
+
+ vector spawnpos = file.flagSpawnPoints[ RandomInt( file.flagSpawnPoints.len() ) ]
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_FlagSpawnIncoming", spawnpos.x, spawnpos.y, spawnpos.z, Time() + 15 )
+
+ wait 15.0
+
+ // create a flag
+ entity flag = CreateEntity( "item_flag" )
+ flag.SetValueForModelKey( CTF_FLAG_MODEL )
+ SetTargetName( flag, "krflag" )
+ DispatchSpawn( flag )
+ flag.SetModel( CTF_FLAG_MODEL )
+ flag.SetOrigin( spawnpos + < 0, 0, flag.GetBoundingMaxs().z / 2 > ) // get it out of the ground
+ flag.SetVelocity( < 0, 0, 1 > ) // make it do gravity again
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_sbox.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_sbox.gnut
new file mode 100644
index 000000000..27581aeac
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_sbox.gnut
@@ -0,0 +1,49 @@
+untyped
+global function GamemodeSbox_Init
+
+struct {
+ array<entity> spawnpoints
+} file
+
+void function GamemodeSbox_Init()
+{
+ SetConVarInt( "sv_cheats", 1 ) // cheats on by default
+
+ // cache spawnpoints
+ //file.spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" )
+ // todo just use a spawn callback for this rather than weird late cache in spawn
+
+ AddCallback_OnClientConnected( SboxSpawnPlayer )
+ AddDeathCallback( "player", SboxRespawnPlayer )
+}
+
+void function SboxSpawnPlayer( entity player )
+{
+ if ( player.GetPlayerSettings() == "spectator" ) // if they haven't spawned yet
+ player.SetPlayerSettings( "pilot_grapple_male" )
+
+ if ( file.spawnpoints.len() == 0 ) // have to cache late rather than on init due to spawnpoints not existing in init
+ file.spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" )
+
+ if ( GetGameState() != eGameState.Playing ) // hacky but can't set this in init either
+ SetGameState( eGameState.Playing )
+
+ entity spawnpoint = file.spawnpoints[ RandomInt( file.spawnpoints.len() ) ]
+
+ ScreenFadeFromBlack( player, 0.0, 0.0 ) // HACK before non-classicmp intros are ready, remove the blackscreen we get from waitingforplayers
+ player.RespawnPlayer( spawnpoint )
+ player.GiveWeapon( "mp_weapon_toolgun" )
+}
+
+void function SboxRespawnPlayer( entity player, var damageInfo )
+{
+ thread SboxRespawnPlayerThreaded( player )
+}
+
+void function SboxRespawnPlayerThreaded( entity player )
+{
+ // todo: replace this with real respawn logic when that's ready
+
+ wait 2.5
+ SboxSpawnPlayer( player )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_tt.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_tt.gnut
new file mode 100644
index 000000000..6a53ef87b
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_gamemode_tt.gnut
@@ -0,0 +1,71 @@
+global function GamemodeTT_Init
+
+struct {
+ entity lastPlayerDropped
+} file
+
+void function GamemodeTT_Init()
+{
+ SetSpawnpointGamemodeOverride( TEAM_DEATHMATCH )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+
+ AddCallback_GameStateEnter( eGameState.Playing, OnEnterPlaying )
+
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+ AddDeathCallback( "npc_titan", OnTitanKilled )
+}
+
+void function OnEnterPlaying()
+{
+ thread DropRandomTitan()
+}
+
+void function DropRandomTitan()
+{
+ array<entity> players = GetPlayerArray()
+
+ if ( players.len() == 1 )
+ file.lastPlayerDropped = null // don't wanna loop forever if only 1 player
+
+ entity titanPlayer
+ do {
+ titanPlayer = players[ RandomInt( players.len() ) ]
+ } while ( titanPlayer == file.lastPlayerDropped )
+
+ DropTitanForPlayer( titanPlayer, 5.0 )
+}
+
+void function DropTitanForPlayer( entity player, float delay )
+{
+ wait delay
+
+ file.lastPlayerDropped = player
+ CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) )
+}
+
+void function AttemptToDropTitanForKill( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsTitan() )
+ return
+
+ if ( !attacker.IsPlayer() || victim == attacker )
+ thread DropRandomTitan()
+ else
+ thread DropTitanForPlayer( attacker, 2.0 )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim.IsTitan() )
+ AttemptToDropTitanForKill( victim, attacker, damageInfo )
+ else if ( attacker.IsTitan() )
+ {
+ AddTeamScore( attacker.GetTeam(), 1 )
+ }
+}
+
+void function OnTitanKilled( entity victim, var damageInfo )
+{
+ if ( IsPetTitan( victim ) )
+ AttemptToDropTitanForKill( victim, DamageInfo_GetAttacker( damageInfo ), damageInfo )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/_riff_instagib.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/_riff_instagib.gnut
new file mode 100644
index 000000000..b3868359e
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/_riff_instagib.gnut
@@ -0,0 +1,65 @@
+global function RiffInstagib_Init
+
+struct {
+ table<entity, int> playerWeapons
+ array<string> instagibWeapons
+} file
+
+void function RiffInstagib_Init()
+{
+ if ( GetCurrentPlaylistVarInt( "riff_instagib", 0 ) == 0 )
+ return
+
+ SetLoadoutGracePeriodEnabled( false )
+ SetWeaponDropsEnabled( false )
+
+ file.instagibWeapons = [
+ "mp_weapon_sniper",
+ "mp_weapon_wingman",
+ "mp_weapon_defender",
+ "mp_weapon_arena3",
+ "mp_weapon_wingman_n",
+ "mp_weapon_doubletake",
+ ]
+ file.instagibWeapons.randomize()
+
+ AddCallback_OnPlayerRespawned( InstagibSetWeapons )
+ AddCallback_OnPlayerKilled( InstagibCycleWeaponsForKill )
+ AddCallback_OnClientDisconnected( InstagibCleanupClient )
+}
+
+void function InstagibSetWeapons( entity player )
+{
+ if ( !( player in file.playerWeapons ) )
+ file.playerWeapons[ player ] <- 0
+
+ player.SetMaxHealth( 1 )
+ InstagibUpdateWeapons( player )
+}
+
+void function InstagibUpdateWeapons( entity player )
+{
+ foreach( entity weapon in player.GetMainWeapons() )
+ player.TakeWeaponNow( weapon.GetWeaponClassName() )
+
+ player.TakeWeaponNow( player.GetOffhandWeapon( OFFHAND_RIGHT ).GetWeaponClassName() )
+ if ( !HasOffhandWeapon( player, "mp_weapon_grenade_sonar" ) )
+ player.GiveOffhandWeapon( "mp_weapon_grenade_sonar", OFFHAND_RIGHT )
+
+ player.GiveWeapon( file.instagibWeapons[ file.playerWeapons[ player ] ] )
+}
+
+void function InstagibCycleWeaponsForKill( entity victim, entity attacker, var damageInfo )
+{
+ if ( !victim.IsPlayer() || !attacker.IsPlayer() || victim == attacker )
+ return
+
+ file.playerWeapons[ attacker ] = ( file.playerWeapons[ attacker ] + 1 ) % file.instagibWeapons.len()
+ InstagibUpdateWeapons( attacker )
+}
+
+void function InstagibCleanupClient( entity player )
+{
+ if ( player in file.playerWeapons )
+ delete file.playerWeapons[ player ]
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut
new file mode 100644
index 000000000..a0e8618fb
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut
@@ -0,0 +1,35 @@
+global function ClGameModeArena_Init
+global function ServerCallback_CreateMoneyParticles
+
+struct {
+ var moneyRui
+} file
+
+void function ClGameModeArena_Init()
+{
+ AddCallback_OnClientScriptInit( CreateArenaUI )
+}
+
+void function CreateArenaUI( entity player )
+{
+ var rui = CreateCockpitRui( $"ui/fd_score_splash.rpak", 500 )
+ RuiTrackInt( rui, "pointValue", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "FD_money" ) )
+ RuiTrackInt( rui, "pointStack", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "FD_money256" ) )
+ file.moneyRui = rui
+}
+
+void function ServerCallback_CreateMoneyParticles( int playerHandle, int amount )
+{
+ // largely taken from cl_gamemode_fd
+ entity player = GetEntityFromEncodedEHandle( playerHandle )
+
+ vector randDir2D = < RandomFloatRange( -1, 1 ), 1, 0 >
+ randDir2D = Normalize( randDir2D )
+
+ var rui = RuiCreate( $"ui/at_score_popup.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 100 )
+ RuiSetInt( rui, "scoreVal", amount )
+ RuiSetGameTime( rui, "startTime", Time() )
+ RuiSetFloat3( rui, "pos", player.EyePosition() )
+ RuiSetFloat2( rui, "driftDir", randDir2D )
+ RuiSetBool( rui, "showNormalPoints", false )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut
new file mode 100644
index 000000000..80dc548ab
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut
@@ -0,0 +1,96 @@
+global function ClGamemodeFastball_Init
+global function ServerCallback_FastballUpdatePanelRui
+global function ServerCallback_FastballPanelHacked
+global function ServerCallback_FastballRespawnPlayer
+
+struct {
+ var panelARui
+ var panelBRui
+ var panelCRui
+} file
+
+void function ClGamemodeFastball_Init()
+{
+ ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_lts.rpak" )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "Music_Beacon_14_BTThrowThruFirstCrane", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "Music_Beacon_14_BTThrowThruFirstCrane", TEAM_MILITIA )
+
+ AddCallback_OnClientScriptInit( FastballCreateRui )
+}
+
+void function FastballCreateRui( entity player )
+{
+ file.panelARui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 )
+ file.panelBRui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 )
+ file.panelCRui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 )
+
+}
+
+void function ServerCallback_FastballUpdatePanelRui( int panelHandle, int id )
+{
+ entity panel = GetEntityFromEncodedEHandle( panelHandle )
+ var rui
+ if ( id == 0 )
+ rui = file.panelARui
+ else if ( id == 1 )
+ rui = file.panelBRui
+ else if ( id == 2 )
+ rui = file.panelCRui
+
+ RuiSetInt( rui, "hardpointId", id )
+ RuiTrackFloat3( rui, "pos", panel, RUI_TRACK_OVERHEAD_FOLLOW )
+ RuiSetInt( rui, "viewerTeam", GetLocalClientPlayer().GetTeam() )
+ ////RuiTrackInt( rui, "cappingTeam", null, RUI_TRACK_SCRIPT_NETWORK_VAR_GLOBAL_INT, GetNetworkedVariableIndex( "panel" + id + "progress" ) )
+ RuiTrackInt( rui, "hardpointTeamRelation", panel, RUI_TRACK_TEAM_RELATION_VIEWPLAYER )
+
+ RuiSetBool( rui, "isVisible", true )
+}
+
+void function ServerCallback_FastballPanelHacked( int panelHandle, int id, int capturingPlayerHandle )
+{
+ ServerCallback_FastballUpdatePanelRui( panelHandle, id ) // may not be necessary, just wanna ensure this is always right
+
+ entity panel = GetEntityFromEncodedEHandle( panelHandle )
+ entity capturingPlayer = GetEntityFromEncodedEHandle( capturingPlayerHandle )
+
+ if ( capturingPlayer == GetLocalViewPlayer() )
+ return
+
+ string panelIdString
+ if ( id == 0 )
+ panelIdString = "A"
+ if ( id == 1 )
+ panelIdString = "B"
+ else if ( id == 2 )
+ panelIdString = "C"
+
+ AnnouncementData announcement = Announcement_Create( Localize( "#FASTBALL_PANEL_CAPTURED", capturingPlayer.GetPlayerName(), panelIdString ) )
+
+ if ( capturingPlayer.GetTeam() == GetLocalViewPlayer().GetTeam() )
+ Announcement_SetTitleColor( announcement, < 0, 0, 1 > )
+ else
+ Announcement_SetTitleColor( announcement, < 1, 0, 0 > )
+
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+}
+
+void function ServerCallback_FastballRespawnPlayer()
+{
+ thread FastballRespawnPlayerEffects_Threaded()
+}
+
+void function FastballRespawnPlayerEffects_Threaded()
+{
+ // sometimes this seems to get called before the player has respawned clientside, so we just wait until the client thinks they're alive
+ entity player = GetLocalViewPlayer()
+
+ while ( !IsAlive( player ) )
+ WaitFrame()
+
+ StartParticleEffectOnEntity( player.GetCockpit(), GetParticleSystemIndex( $"P_pod_screen_lasers_OUT" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut
new file mode 100644
index 000000000..de8a34491
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut
@@ -0,0 +1,26 @@
+global function ClGamemodeGG_Init
+
+void function ClGamemodeGG_Init()
+{
+ // add ffa gamestate asset
+ ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" )
+
+ // add music for mode, this is copied directly from the ffa/fra music registered in cl_music.gnut
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_MILITIA )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut
new file mode 100644
index 000000000..56763bd4a
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut
@@ -0,0 +1,75 @@
+global function ClGamemodeInfection_Init
+global function ServerCallback_YouAreInfected
+global function ServerCallback_AnnounceFirstInfected
+global function ServerCallback_AnnounceLastSurvivor
+
+void function ClGamemodeInfection_Init()
+{
+ //ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_beacon_8a_jumpingsuccess", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_beacon_8a_jumpingsuccess", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "Music_Beacon_24_BTLob", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "Music_Beacon_24_BTLob", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_titanwar_lastminute", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_titanwar_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC )
+}
+
+void function ServerCallback_YouAreInfected()
+{
+ // heavily based on mfd code
+ entity localPlayer = GetLocalViewPlayer()
+
+ StartParticleEffectOnEntity( localPlayer.GetCockpit(), GetParticleSystemIndex( $"P_MFD" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+ EmitSoundOnEntity( localPlayer, "UI_InGame_MarkedForDeath_PlayerMarked" )
+ HideEventNotification()
+ AnnouncementData announcement = Announcement_Create( "#INFECTION_YOU_ARE_INFECTED" )
+ Announcement_SetSubText( announcement, "#INFECTION_KILL_SURVIVORS" )
+ Announcement_SetTitleColor( announcement, <1,0,0> )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( localPlayer, announcement )
+}
+
+void function ServerCallback_AnnounceFirstInfected( int survivorEHandle )
+{
+ entity player = GetEntityFromEncodedEHandle( survivorEHandle )
+
+ AnnouncementData announcement = Announcement_Create( Localize( "#INFECTION_FIRST_INFECTED", player.GetPlayerName() ) )
+ //Announcement_SetSubText( announcement, "#INFECTION_KILL_LAST_SURVIVOR" )
+ Announcement_SetTitleColor( announcement, <1,0,0> )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+}
+
+void function ServerCallback_AnnounceLastSurvivor( int survivorEHandle )
+{
+ entity player = GetEntityFromEncodedEHandle( survivorEHandle )
+
+ string announcementString = Localize( "#INFECTION_LAST_SURVIVOR", player.GetPlayerName() )
+ string announcementSubString = "#INFECTION_KILL_LAST_SURVIVOR"
+ if ( player == GetLocalViewPlayer() )
+ {
+ announcementString = "#INFECTION_YOU_ARE_LAST_SURVIVOR"
+ announcementSubString = "#INFECTION_SURVIVE_LAST_SURVIVOR"
+ }
+
+ AnnouncementData announcement = Announcement_Create( announcementString )
+ Announcement_SetSubText( announcement, announcementSubString )
+ Announcement_SetTitleColor( announcement, <1,0,0> )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut
new file mode 100644
index 000000000..269057c79
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut
@@ -0,0 +1,241 @@
+global function ClGamemodeKR_Init
+
+global function ShowTimeGainOnKill
+global function ServerCallback_FlagSpawnIncoming
+global function ServerCallback_NewKillRacer
+global function ServerCallback_EndKillrace
+
+struct {
+ var currentTimeRui
+ var currentTimeAdditionRui
+ var flagRui
+ var flagIncomingRui
+ var killRacerRui
+
+ bool isCurrentlyInRace = false
+ float currentTimeAmount
+ float currentTimeLastAdditionTime
+ float currentTimeAdditionCombined
+} file
+
+void function ClGamemodeKR_Init()
+{
+ // add ffa gamestate asset
+ ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" )
+
+ // add music for mode, this is copied directly from the ffa/fra music registered in cl_music.gnut
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_MILITIA )
+
+ AddCallback_OnClientScriptInit( CreateKRUI )
+ AddCreateCallback( "item_flag", OnFlagCreated )
+ AddDestroyCallback( "item_flag", OnFlagDestroyed )
+}
+
+void function CreateKRUI( entity player )
+{
+ file.currentTimeRui = CreateCockpitRui( $"ui/titan_protocol_text_center.rpak", 200 )
+ RuiSetInt( file.currentTimeRui, "lineNum", 1 )
+ RuiSetGameTime( file.currentTimeRui, "startTime", 0.0 )
+ UpdateCurrentTimeAmount()
+
+ file.flagRui = CreateCockpitRui( $"ui/speedball_flag_marker.rpak", 200 )
+ RuiSetBool( file.flagRui, "playerIsCarrying", false )
+ RuiSetBool( file.flagRui, "isCarried", false )
+
+ file.killRacerRui = CreateCockpitRui( $"ui/mfd_target_marker.rpak", 200 )
+ RuiSetBool( file.killRacerRui, "isVisible", false )
+ RuiSetImage( file.killRacerRui, "markedIcon", $"rui/hud/gametype_icons/mfd/mfd_enemy" )
+ RuiSetBool( file.killRacerRui, "isMarked", true )
+}
+
+void function OnFlagCreated( entity flag )
+{
+ if ( IsValid( file.flagIncomingRui ) )
+ RuiDestroy( file.flagIncomingRui )
+
+ RuiSetBool( file.flagRui, "isVisible", true )
+ RuiTrackFloat3( file.flagRui, "pos", flag, RUI_TRACK_OVERHEAD_FOLLOW )
+}
+
+void function OnFlagDestroyed( entity flag )
+{
+ RuiSetBool( file.flagRui, "isVisible", false )
+}
+
+void function ShowTimeGainOnKill( entity player, float oldVal, float newVal, bool actuallyChanged )
+{
+ if ( file.isCurrentlyInRace || player != GetLocalViewPlayer() || !actuallyChanged )
+ return
+
+ if ( newVal > oldVal ) // time increase: likely given on kill
+ {
+ float amount = newVal - oldVal
+
+ // show a combined number on the addition rui if last addition was recent enough
+ float additionShowAmount = amount
+ file.currentTimeAdditionCombined += amount
+ if ( Time() - file.currentTimeLastAdditionTime < 1.25 )
+ amount = file.currentTimeAdditionCombined
+ else
+ {
+ file.currentTimeAdditionRui = CreateCockpitRui( $"ui/titan_protocol_text_center.rpak", 200 )
+ RuiSetInt( file.currentTimeAdditionRui, "lineNum", 2 )
+ file.currentTimeAdditionCombined = amount
+ }
+
+ RuiSetString( file.currentTimeAdditionRui, "displayString", "+" + amount + "s 00ms " ) // formatted so that it lines up with other rui
+ RuiSetGameTime( file.currentTimeAdditionRui, "startTime", Time() )
+ RuiSetGameTime( file.currentTimeAdditionRui, "endTime", Time() + 1.5 )
+
+ file.currentTimeLastAdditionTime = Time()
+
+ thread UpdateFullTimeAmountAfterAdditionDone( file.currentTimeLastAdditionTime )
+ }
+ else // time decrease either a reset or
+ UpdateCurrentTimeAmount()
+}
+
+void function UpdateFullTimeAmountAfterAdditionDone( float previousAdditionTime )
+{
+ wait 1.25
+
+ if ( previousAdditionTime == file.currentTimeLastAdditionTime ) // if not, there's been another addition since this was last updated and we'll wait for that instead
+ UpdateCurrentTimeAmount()
+}
+
+void function UpdateCurrentTimeAmount( float overrideTime = -1 )
+{
+ if ( overrideTime == -1 )
+ file.currentTimeAmount = GetLocalViewPlayer().GetPlayerNetTime( "killRaceTime" )
+ else
+ file.currentTimeAmount = overrideTime
+
+ string currentTimeString
+ int seconds = file.currentTimeAmount.tointeger()
+
+ string secondsString = seconds.tostring()
+ if ( secondsString.len() < 2 ) // pad to 2 chars
+ secondsString = "0" + secondsString
+ currentTimeString += secondsString + "s "
+
+ string msString = ( ( file.currentTimeAmount - file.currentTimeAmount.tointeger() ) * 100 ).tostring()
+ if ( msString.len() < 2 ) // pad to 2 chars
+ msString = "0" + msString
+ currentTimeString += msString.slice( 0, 2 ) + "ms "
+
+ RuiSetString( file.currentTimeRui, "displayString", currentTimeString )
+ RuiSetGameTime( file.currentTimeRui, "endTime", Time() + 99999.0 ) // arbitrarily large number so this doesn't disappear
+}
+
+void function ServerCallback_FlagSpawnIncoming( float x, float y, float z , float spawnTime )
+{
+ print( "flagspawn: < " + x + ", " + y + ", " + z + " > in " + ( spawnTime - Time() ) + " seconds" )
+
+ AnnouncementData announcement = Announcement_Create( Localize( "#KR_FLAG_INCOMING", spawnTime.tostring() ) )
+ Announcement_SetSubText( announcement, "#KR_COLLECT_FLAG" )
+ Announcement_SetTitleColor( announcement, < 0, 0, 1 > )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 )
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+
+ RuiSetFloat3( file.flagRui, "pos", < x, y, z > )
+ RuiSetBool( file.flagRui, "isVisible", true )
+
+ file.flagIncomingRui = RuiCreate( $"ui/titanfall_timer.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 )
+ RuiTrackFloat3( file.flagIncomingRui, "playerPos", GetLocalViewPlayer(), RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiSetFloat3( file.flagIncomingRui, "pos", < x, y, z > + < 0, 0, 48 > )
+ RuiSetGameTime( file.flagIncomingRui, "impactTime", spawnTime )
+}
+
+void function ServerCallback_NewKillRacer( int playerHandle, float endTime )
+{
+ entity player = GetEntityFromEncodedEHandle( playerHandle )
+
+ string announcementMessage = Localize( "#KR_NEW_RACER", player.GetPlayerName() )
+ string announcementSubMessage
+ if ( player == GetLocalViewPlayer() )
+ {
+ file.isCurrentlyInRace = true
+ thread LerpTimeDuringRace( endTime )
+
+ StartParticleEffectOnEntity( player.GetCockpit(), GetParticleSystemIndex( $"P_MFD" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+ EmitSoundOnEntity( player, "UI_InGame_MarkedForDeath_PlayerMarked" )
+ HideEventNotification()
+
+ announcementMessage = "#KR_YOU_ARE_NEW_RACER"
+ announcementSubMessage = "#KR_YOU_SET_NEW_RECORD"
+ }
+ else
+ {
+ // mark the player
+ RuiTrackFloat3( file.killRacerRui, "pos", player, RUI_TRACK_OVERHEAD_FOLLOW )
+ RuiSetBool( file.killRacerRui, "isVisible", true )
+ }
+
+ AnnouncementData announcement = Announcement_Create( announcementMessage )
+ Announcement_SetSubText( announcement, announcementSubMessage )
+ Announcement_SetTitleColor( announcement, < 1, 0, 0 > )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 )
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+}
+
+void function LerpTimeDuringRace( float endTime )
+{
+ while ( Time() < endTime )
+ {
+ // manually update this here so we can get more frequent updates than what we'd get with networked vars
+ UpdateCurrentTimeAmount( endTime - Time() )
+ WaitFrame()
+ }
+
+ UpdateCurrentTimeAmount( 0.0 )
+ file.isCurrentlyInRace = false
+}
+
+void function ServerCallback_EndKillrace( int playerHandle, int score )
+{
+ entity player = GetEntityFromEncodedEHandle( playerHandle )
+
+ vector colour = < 0, 0, 1 >
+ string announcementMessage = Localize( "#KR_ENEMY_KILLRACE_OVER", player.GetPlayerName() )
+ string announcementSubMessage
+ if ( player == GetLocalViewPlayer() )
+ {
+ StartParticleEffectOnEntity( GetLocalViewPlayer().GetCockpit(), GetParticleSystemIndex( $"P_MFD_unmark" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+ colour = < 1, 0, 0 >
+ announcementMessage = "#KR_YOUR_KILLRACE_OVER"
+ announcementSubMessage = Localize( "#KR_YOUR_KILLRACE_SCORE", score )
+ }
+
+ AnnouncementData announcement = Announcement_Create( announcementMessage )
+ Announcement_SetSubText( announcement, announcementSubMessage )
+ Announcement_SetTitleColor( announcement, colour )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 )
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+
+ RuiSetBool( file.killRacerRui, "isVisible", false )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut
new file mode 100644
index 000000000..b73ed9587
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut
@@ -0,0 +1,26 @@
+global function ClGamemodeTT_Init
+
+void function ClGamemodeTT_Init()
+{
+ // register gamestate asset, this is default so not necessary but doing it anyway
+ ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ps.rpak" )
+
+ // add music for mode, this is copied directly from the attrition/tdm music registered in cl_music.gnut
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_pilothunt_intro_flyin", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_pilothunt_intro_flyin", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_pilothunt_epilogue_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_pilothunt_epilogue_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_pilothunt_epilogue_win", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_pilothunt_epilogue_win", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_pilothunt_epilogue_lose", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_pilothunt_epilogue_lose", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_pilothunt_almostdone", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_pilothunt_almostdone", TEAM_MILITIA )
+
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_pilothunt_lastminute", TEAM_IMC )
+ RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_pilothunt_lastminute", TEAM_MILITIA )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut
new file mode 100644
index 000000000..b634f1d38
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut
@@ -0,0 +1,58 @@
+global function Sh_GamemodeArena_Init
+
+global const string GAMEMODE_ARENA = "arena"
+
+void function Sh_GamemodeArena_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeArena )
+ AddCallback_OnRegisteringCustomNetworkVars( ArenaRegisterNetworkVars )
+}
+
+void function CreateGamemodeArena()
+{
+ GameMode_Create( GAMEMODE_ARENA )
+ GameMode_SetName( GAMEMODE_ARENA, "#GAMEMODE_arena" )
+ GameMode_SetDesc( GAMEMODE_ARENA, "#PL_arena_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_ARENA, "gnrc_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_ARENA, 5, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_ARENA, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_SetColor( GAMEMODE_ARENA, [147, 204, 57, 255] )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_ARENA, GameModeArena_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_ARENA, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_ARENA, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_ARENA, ClGameModeArena_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_ARENA, CompareAssaultScore )
+ #endif
+}
+
+void function ArenaRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_ARENA )
+ return
+
+ // boost store stuff
+ Remote_RegisterFunction( "ServerCallback_OpenBoostStore" )
+ Remote_RegisterFunction( "ServerCallback_UpdateMoney" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTeamReserve" )
+ Remote_RegisterFunction( "ServerCallback_UpdatePlayerHasBattery" )
+ Remote_RegisterFunction( "ServerCallback_UpdateAmpedWeaponState" )
+ Remote_RegisterFunction( "ServerCallback_BoostStoreTitanHint" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTurretCount" )
+
+ RegisterNetworkedVariable( "boostStoreOpen", SNDC_GLOBAL, SNVT_BOOL, false )
+ RegisterNetworkedVariable( "FD_money", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "FD_money256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+
+ // these are required to prevent crashes in fd code that's called from menu_boost_store
+ RegisterNetworkedVariable( "numSuperRodeoGrenades", SNDC_PLAYER_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_waveActive", SNDC_GLOBAL, SNVT_BOOL, false )
+
+ // arena-exclusive stuff
+ Remote_RegisterFunction( "ServerCallback_CreateMoneyParticles" )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut
new file mode 100644
index 000000000..734e24ce5
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut
@@ -0,0 +1,45 @@
+global function Sh_GamemodeFastball_Init
+
+global const string GAMEMODE_FASTBALL = "fastball"
+
+void function Sh_GamemodeFastball_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeFastball )
+ AddCallback_OnRegisteringCustomNetworkVars( FastballRegisterNetworkVars )
+}
+
+void function CreateGamemodeFastball()
+{
+ GameMode_Create( GAMEMODE_FASTBALL )
+ GameMode_SetName( GAMEMODE_FASTBALL, "#GAMEMODE_FASTBALL" )
+ GameMode_SetDesc( GAMEMODE_FASTBALL, "#PL_fastball_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_FASTBALL, "grnc_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_FASTBALL, 1, 0 )
+ GameMode_SetDefaultScoreLimits( GAMEMODE_FASTBALL, 5, 0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_FASTBALL_HACKS", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_DEATHS", PGS_DEATHS, 2 )
+ GameMode_SetColor( GAMEMODE_FASTBALL, [147, 204, 57, 255] )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_FASTBALL, GamemodeFastball_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_FASTBALL, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_FASTBALL, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_FASTBALL, ClGamemodeFastball_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_FASTBALL, CompareAssaultScore )
+ #endif
+}
+
+void function FastballRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_FASTBALL )
+ return
+
+ Remote_RegisterFunction( "ServerCallback_FastballUpdatePanelRui" )
+ Remote_RegisterFunction( "ServerCallback_FastballPanelHacked" )
+ Remote_RegisterFunction( "ServerCallback_FastballRespawnPlayer" )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut
new file mode 100644
index 000000000..8c6e3f635
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut
@@ -0,0 +1,73 @@
+// this script only exists to create the fw gamemode
+// all client/shared gamelogic is still done in the gamemode's respective client and shared scripts
+// these scripts are shipped with the game's official build so no need to recreate these
+// their paths are gamemodes/cl_gamemode_fw.nut and gamemodes/sh_gamemode_fw.nut, respectively
+
+global function SHCreateGamemodeFW_Init
+
+void function SHCreateGamemodeFW_Init()
+{
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeFW )
+ AddCallback_OnRegisteringCustomNetworkVars( FWOnRegisteringNetworkVars )
+}
+
+void function CreateGamemodeFW()
+{
+ //entity e = CreateEntity("npc_turret_mega"); SetAISettingsWrapper( e, "npc_turret_mega_fortwar" ); e.SetOrigin(GetPlayerArray()[0].GetOrigin()); SetTeam(e,3); DispatchSpawn(e)
+
+ // we have to manually add the client/shared scripts to scripts.rson atm so we need to prevent compile errors when they aren't included
+ // best way to do this is to just ignore this whole block for now and wait until we don't have to add them manually
+
+ GameMode_Create( FORT_WAR )
+ GameMode_SetName( FORT_WAR, "#GAMEMODE_fw" )
+ GameMode_SetDesc( FORT_WAR, "#PL_fw_desc" )
+ GameMode_SetGameModeAnnouncement( FORT_WAR, "ffa_modeDesc" ) // fw lines are unfortunately not registered to faction dialogue
+
+ #if SERVER
+ //GameMode_AddServerInit( FORT_WAR, GamemodeFW_Init ) // doesn't exist yet lol
+ #elseif CLIENT
+ GameMode_AddClientInit( FORT_WAR, CLGamemodeFW_Init )
+ #endif
+ #if !UI
+ GameMode_AddSharedInit( FORT_WAR, SHGamemodeFW_Init )
+ #endif
+}
+
+void function FWOnRegisteringNetworkVars()
+{
+ if ( GAMETYPE != FORT_WAR )
+ return
+
+ RegisterNetworkedVariable( "turretSite1", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite2", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite3", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite4", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite5", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite6", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite7", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite8", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "turretSite9", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "turretStateFlags1", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags2", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags3", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags4", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags5", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags6", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags7", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags8", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "turretStateFlags9", SNDC_GLOBAL, SNVT_INT )
+
+ RegisterNetworkedVariable( "imcTowerThreatLevel", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "milTowerThreatLevel", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampAlertA", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampStressA", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampAlertB", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampStressB", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampAlertC", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampStressC", SNDC_GLOBAL, SNVT_INT )
+
+ #if CLIENT
+ CLFortWar_RegisterNetworkFunctions()
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut
new file mode 100644
index 000000000..4ea9ac207
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut
@@ -0,0 +1,164 @@
+global function Sh_GamemodeGG_Init
+global function GetGunGameWeapons
+
+global const string GAMEMODE_GG = "gg"
+
+global struct GunGameWeapon
+{
+ string weapon
+ array<string> mods
+ int offhandSlot = -1
+}
+
+struct {
+ array<GunGameWeapon> weapons
+} file
+
+void function Sh_GamemodeGG_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeGG )
+}
+
+void function CreateGamemodeGG()
+{
+ GameMode_Create( GAMEMODE_GG )
+ GameMode_SetName( GAMEMODE_GG, "#GAMEMODE_GG" )
+ GameMode_SetDesc( GAMEMODE_GG, "#PL_gg_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_GG, "ffa_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_GG, 10, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_GG, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_GG, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_SetColor( GAMEMODE_GG, [147, 204, 57, 255] )
+
+
+ // setup guns
+
+ // smgs
+ // car
+ GunGameWeapon ggCar = { weapon = "mp_weapon_car", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggCar )
+
+ // alternator
+ GunGameWeapon ggAlternator = { weapon = "mp_weapon_alternator_smg", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggAlternator )
+
+ // volt
+ GunGameWeapon ggVolt = { weapon = "mp_weapon_hemlok_smg", ... }
+ file.weapons.append( ggVolt )
+
+
+ // rifles
+ // hemlok
+ GunGameWeapon ggHemlok = { weapon = "mp_weapon_hemlok", mods = [ ], ... }
+ file.weapons.append( ggHemlok )
+
+ // flatline
+ GunGameWeapon ggFlatline = { weapon = "mp_weapon_vinson", mods = [ "hcog" ], ... }
+ file.weapons.append( ggFlatline )
+
+ // r201
+ GunGameWeapon ggR101 = { weapon = "mp_weapon_rspn101", ... }
+ file.weapons.append( ggR101 )
+
+
+ // lmgs
+ // devotion
+ GunGameWeapon ggDevotion = { weapon = "mp_weapon_esaw", ... }
+ file.weapons.append( ggDevotion )
+
+ // l-star
+ GunGameWeapon ggLstar = { weapon = "mp_weapon_lstar", mods = [ "pas_run_and_gun" ], ... }
+ if ( RandomInt( 100 ) <= 5 )
+ ggLstar.mods.append( "rcee" ) // easter egg mod that changes the screen of the lstar
+
+ file.weapons.append( ggLstar )
+
+
+ // shotguns
+ // eva-8
+ GunGameWeapon ggEva = { weapon = "mp_weapon_shotgun", ... }
+ file.weapons.append( ggEva )
+
+ // mastiff
+ GunGameWeapon ggMastiff = { weapon = "mp_weapon_mastiff", ... }
+ file.weapons.append( ggMastiff )
+
+
+ // grenadiers
+ // softball
+ GunGameWeapon ggSoftball = { weapon = "mp_weapon_softball", ... }
+ file.weapons.append( ggSoftball )
+
+ // epg
+ GunGameWeapon ggEpg = { weapon = "mp_weapon_epg", mods = [ "jump_kit" ], ... }
+ file.weapons.append( ggEpg )
+
+
+ // primary pistols
+ // mozambique
+ GunGameWeapon ggMozam = { weapon = "mp_weapon_shotgun_pistol", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggMozam )
+
+ // wingman elite
+ GunGameWeapon ggWme = { weapon = "mp_weapon_wingman_n", mods = [ "pas_run_and_gun", "ricochet" ], ... }
+ file.weapons.append( ggWme )
+
+
+ // snipers
+ // double take
+ GunGameWeapon ggTaketake = { weapon = "mp_weapon_doubletake", ... }
+ file.weapons.append( ggTaketake )
+
+ // kraber
+ GunGameWeapon ggKraber = { weapon = "mp_weapon_sniper", mods = [ "pas_fast_ads", "ricochet" ], ... }
+ file.weapons.append( ggKraber )
+
+
+ // secondary pistols
+ // re-45
+ GunGameWeapon ggRe45 = { weapon = "mp_weapon_autopistol", mods = [ "pas_run_and_gun", "temp_sight" ], ... }
+ file.weapons.append( ggRe45 )
+
+ // p2016
+ GunGameWeapon ggP2016 = { weapon = "mp_weapon_semipistol", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggP2016 )
+
+ // wingman
+ GunGameWeapon ggWingman = { weapon = "mp_weapon_wingman", mods = [ "pas_run_and_gun" ], ... }
+ file.weapons.append( ggWingman )
+
+
+ // final/special weapons
+ // charge rifle
+ GunGameWeapon ggChargeRifle = { weapon = "mp_weapon_defender", ... }
+ file.weapons.append( ggChargeRifle )
+
+ // pulse blade
+ GunGameWeapon ggPulseBlade = { weapon = "mp_weapon_grenade_sonar", mods = [ "pas_power_cell", "amped_tacticals" ], offhandSlot = 0 }
+ file.weapons.append( ggPulseBlade )
+
+
+ // set this to the number of guns
+ GameMode_SetDefaultScoreLimits( GAMEMODE_GG, file.weapons.len(), 0 )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_GG, GamemodeGG_Init )
+ GameMode_AddServerInit( GAMEMODE_GG, GamemodeFFAShared_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_GG, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_GG, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_GG, ClGamemodeGG_Init )
+ GameMode_AddClientInit( GAMEMODE_GG, GamemodeFFAShared_Init )
+ GameMode_AddClientInit( GAMEMODE_GG, ClGamemodeFFA_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_GG, CompareAssaultScore )
+ GameMode_AddSharedInit( GAMEMODE_GG, GamemodeFFA_Dialogue_Init )
+ #endif
+}
+
+array<GunGameWeapon> function GetGunGameWeapons()
+{
+ return file.weapons
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut
new file mode 100644
index 000000000..b73dc1940
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut
@@ -0,0 +1,56 @@
+global function Sh_GamemodeInfection_Init
+
+global const string GAMEMODE_INFECTION = "inf"
+global const int INFECTION_TEAM_SURVIVOR = TEAM_MILITIA
+global const int INFECTION_TEAM_INFECTED = TEAM_IMC
+
+void function Sh_GamemodeInfection_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeInfection )
+ AddCallback_OnRegisteringCustomNetworkVars( InfectionRegisterNetworkVars )
+}
+
+void function CreateGamemodeInfection()
+{
+ GameMode_Create( GAMEMODE_INFECTION )
+ GameMode_SetName( GAMEMODE_INFECTION, "#GAMEMODE_inf" )
+ GameMode_SetDesc( GAMEMODE_INFECTION, "#PL_inf_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_INFECTION, "ffa_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_INFECTION, 5, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_INFECTION, "#SCOREBOARD_KILLS", PGS_ASSAULT_SCORE, 2 )
+ GameMode_SetColor( GAMEMODE_INFECTION, [147, 204, 57, 255] )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_INFECTION, GamemodeInfection_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_INFECTION, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_INFECTION, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_INFECTION, ClGamemodeInfection_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_INFECTION, CompareAssaultScoreAndInfection )
+ #endif
+}
+
+void function InfectionRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_INFECTION )
+ return
+
+ Remote_RegisterFunction( "ServerCallback_YouAreInfected" )
+ Remote_RegisterFunction( "ServerCallback_AnnounceFirstInfected" )
+ Remote_RegisterFunction( "ServerCallback_AnnounceLastSurvivor" )
+}
+
+int function CompareAssaultScoreAndInfection( entity a, entity b )
+{
+ // survivors should be on top, then sort by assault score
+
+ if ( a.GetTeam() == INFECTION_TEAM_INFECTED && b.GetTeam() == INFECTION_TEAM_SURVIVOR )
+ return 1
+ else if ( a.GetTeam() == INFECTION_TEAM_SURVIVOR && b.GetTeam() == INFECTION_TEAM_INFECTED )
+ return -1
+
+ return CompareAssaultScore( a, b )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut
new file mode 100644
index 000000000..2a320077f
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut
@@ -0,0 +1,53 @@
+global function Sh_GamemodeKR_Init
+
+global const string GAMEMODE_KR = "kr"
+
+void function Sh_GamemodeKR_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeKR )
+ AddCallback_OnRegisteringCustomNetworkVars( KRRegisterNetworkVars )
+}
+
+void function CreateGamemodeKR()
+{
+ GameMode_Create( GAMEMODE_KR )
+ GameMode_SetName( GAMEMODE_KR, "#GAMEMODE_kr" )
+ GameMode_SetDesc( GAMEMODE_KR, "#PL_kr_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_KR, "ffa_modeDesc" )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_KR, 10, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_KR, "#SCOREBOARD_KR_RECORD", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_KR, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_SetColor( GAMEMODE_KR, [147, 204, 57, 255] )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_KR, GamemodeKR_Init )
+ GameMode_AddServerInit( GAMEMODE_KR, GamemodeFFAShared_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_KR, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_KR, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_KR, ClGamemodeKR_Init )
+ GameMode_AddClientInit( GAMEMODE_KR, GamemodeFFAShared_Init )
+ GameMode_AddClientInit( GAMEMODE_KR, ClGamemodeFFA_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_KR, CompareAssaultScore )
+ GameMode_AddSharedInit( GAMEMODE_KR, GamemodeFFA_Dialogue_Init )
+ #endif
+}
+
+void function KRRegisterNetworkVars()
+{
+ if ( GAMETYPE != GAMEMODE_KR )
+ return
+
+ Remote_RegisterFunction( "ServerCallback_FlagSpawnIncoming" )
+ Remote_RegisterFunction( "ServerCallback_NewKillRacer" )
+ Remote_RegisterFunction( "ServerCallback_EndKillrace" )
+
+ RegisterNetworkedVariable( "killRaceTime", SNDC_PLAYER_EXCLUSIVE, SNVT_TIME, 0.0 )
+
+ #if CLIENT
+ RegisterNetworkedVariableChangeCallback_time( "killRaceTime", ShowTimeGainOnKill )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut
new file mode 100644
index 000000000..893d94108
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut
@@ -0,0 +1,20 @@
+global function Sh_GamemodeSbox_Init
+
+global const string GAMEMODE_SBOX = "sbox"
+
+void function Sh_GamemodeSbox_Init()
+{
+ // create custom gametype
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeSbox )
+}
+
+void function CreateGamemodeSbox()
+{
+ GameMode_Create( GAMEMODE_SBOX )
+ GameMode_SetName( GAMEMODE_SBOX, "#PL_sbox" )
+ GameMode_SetDesc( GAMEMODE_SBOX, "#PL_sbox_desc" )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_SBOX, GamemodeSbox_Init )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut
new file mode 100644
index 000000000..9e8798430
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut
@@ -0,0 +1,34 @@
+global function Sh_GamemodeTT_Init
+
+global const string GAMEMODE_TT = "tt"
+
+void function Sh_GamemodeTT_Init()
+{
+ // create custom gamemode
+ AddCallback_OnCustomGamemodesInit( CreateGamemodeTT )
+}
+
+void function CreateGamemodeTT()
+{
+ GameMode_Create( GAMEMODE_TT )
+ GameMode_SetName( GAMEMODE_TT, "#GAMEMODE_TT" )
+ GameMode_SetDesc( GAMEMODE_TT, "#PL_tt_desc" )
+ GameMode_SetGameModeAnnouncement( GAMEMODE_TT, "gnrc_modeDesc" )
+ GameMode_SetDefaultScoreLimits( GAMEMODE_TT, 20, 0 )
+ GameMode_SetDefaultTimeLimits( GAMEMODE_TT, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_TITAN_KILLS", PGS_TITAN_KILLS, 1 )
+ GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_SetColor( GAMEMODE_TT, [200, 40, 40, 255] )
+
+ #if SERVER
+ GameMode_AddServerInit( GAMEMODE_TT, GamemodeTT_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_TT, RateSpawnpoints_Generic )
+ GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_TT, RateSpawnpoints_Generic )
+ #elseif CLIENT
+ GameMode_AddClientInit( GAMEMODE_TT, ClGamemodeTT_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( GAMEMODE_TT, CompareAssaultScore )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut b/Northstar.Custom/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut
new file mode 100644
index 000000000..3a8ac8e75
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut
@@ -0,0 +1,262 @@
+untyped
+
+global function RiffFloorIsLavaShared_Init
+
+global function GetFogHeight
+global function GetLethalFogTopTitan
+global function GetLethalFogTop
+global function GetLethalFogBottom
+global function GetVisibleFogTop
+global function GetVisibleFogBottom
+global function GetMaxTitanSpawnFogHeight
+
+global function IsEntInSafeVolume
+global function IsEntInLethalVolume
+
+struct
+{
+ float fogDepth = 64.0
+ float maxTitanSpawnFogDepth = 170.0
+ array lethalTitanVolumes
+ array lethalPilotVolumes
+ array safePilotVolumes
+ bool volumesDebug = false
+ table< string, float > lethalFogHeights
+} file
+
+function RiffFloorIsLavaShared_Init()
+{
+ switch ( GetMapName() )
+ {
+ case "mp_lagoon":
+ AddLethalTitanVolume( Vector( -45.656845, 3555.449463, 40.422455 ), Vector( 1209.944092, 5599.152832, 234.813217 ) )
+ AddLethalTitanVolume( Vector( -5232.395020, 205.406250, 0.031250 ), Vector( -777.285400, 4075.119385, 300.634771 ) )
+ AddLethalTitanVolume( Vector( -4686.448730, 4190.655273, 20.642021 ), Vector( -41.171387, 9072.019043, 200.697632 ) )
+ AddLethalTitanVolume( Vector( -7586.861328, 4072.843994, 0.031254 ), Vector( -7012.854004, 4614.723145, 302.714966 ) )
+ break
+
+ case "mp_nexus":
+ AddLethalTitanVolume( Vector( 1567.173523, -27.374023, 209.422455 ), Vector( 2516.944092, 2585.152832, 500.813217 ) )
+ AddLethalTitanVolume( Vector( -2825.766113, 5056.203125, 243.706253 ), Vector( -2255.893555, 5688.334961, 400.251160 ) )
+ AddLethalTitanVolume( Vector( -5717.068359, -349.599976, 189.669785 ), Vector( -4960.125000, 758.196350, 400.268097 ) )
+ AddLethalTitanVolume( Vector( -3292.942139, 1713.916626, 233.749817 ), Vector( -2322.137695, 3091.497070, 477.462799 ) )
+ AddLethalTitanVolume( Vector( -878.712769, -5878.528809, 71.145332 ), Vector( 338.741943, -5014.183594, 443.146179 ) )
+ AddLethalTitanVolume( Vector( -6930.957031, -1277.388550, 107.619537 ), Vector( -6574.779297, -779.338013, 685.485901 ) )
+ break
+
+ case "mp_outpost_207":
+ AddSafePilotVolume( Vector( 2359.524658, -631.065918, -256.714142 ), Vector( 2623.051270, -182.453323, -220.125641 ) )
+
+ AddLethalTitanVolume( Vector( -100.349350, 2218.763916, -330.968750 ), Vector( 2561.511230, 4030.028320, -133.065369 ) )
+ AddLethalTitanVolume( Vector( -452.031647, 282.244629, -255.968750 ), Vector( 2241.971069, 1594.146851, -100.212967 ) )
+ break
+
+ case "mp_training_ground":
+ AddSafePilotVolume( Vector( -2618.053223, -3435.505615, 40.215054 ), Vector( -2309.167236, -3321.788330, 146.218491 ) )
+ AddSafePilotVolume( Vector( -3187.767090, -2886.333496, 45.746925 ), Vector( -2865.753174, -2681.679443, 109.089279 ) )
+ AddSafePilotVolume( Vector( -3717.815674, -2350.831543, 47.694588 ), Vector( -3431.980957, -2145.194092, 120.640717 ) )
+
+ AddLethalTitanVolume( Vector( -3439.702179, -2227.359741, -8.036909 ), Vector( 2185.765076, 2384.459412, 225.199013 ) )
+ AddLethalTitanVolume( Vector( -3200.747681, -4456.148926, 0.0 ), Vector( -1261.621826, -3000.667480, 160.689011 ) )
+ AddLethalTitanVolume( Vector( 1261.621826, 3000.667480, 0.0 ), Vector( 2700.747681, 4456.148926, 160.689011 ) )
+ AddLethalTitanVolume( Vector( -3291.510986, 3483.724609, 4.031250 ), Vector( -2018.871826, 4463.995850, 122.675621 ) )
+ AddLethalTitanVolume( Vector( 2018.871826, -3638.995850, 4.031250 ), Vector( 2241.510986, -3483.724609, 122.675621 ) )
+ AddLethalTitanVolume( Vector( -2798.816528, -2302.519897, -30.285933 ), Vector( -1561.589355, -791.616699, 300.917297 ) )
+ AddLethalTitanVolume( Vector( 3809.276123, 1639.001587, 11.272846 ), Vector( 4056.847412, 1862.587036, 100.205643 ) )
+ AddLethalTitanVolume( Vector( -4189.979492, -3298.505127, -5.597572 ), Vector( -3398.622803, -560.027344, 147.054291 ) )
+ break
+
+ case "mp_runoff":
+ AddLethalPilotVolume( Vector( -621.502319, -5743.472656, 299.838928 ), Vector( -397.317047, -5578.512207, 425.437927 ) )
+ break
+ }
+}
+
+float function GetFogHeight()
+{
+ string mapName = GetMapName()
+
+ file.lethalFogHeights = {}
+ file.lethalFogHeights[ "mp_angel_city" ] <- 216.0 // cp ctf mfd
+ file.lethalFogHeights[ "mp_lagoon" ] <- 98.0 // cp mfd
+ file.lethalFogHeights[ "mp_nexus" ] <- 310.0 // mfd
+ file.lethalFogHeights[ "mp_o2" ] <- 40.0 // mfd
+ file.lethalFogHeights[ "mp_outpost_207" ] <- -225.0 // mfd
+ file.lethalFogHeights[ "mp_training_ground" ] <- 80.0 // cp mfd
+ file.lethalFogHeights[ "mp_harmony_mines" ] <- 260.0 // cp ctf mfd
+ file.lethalFogHeights[ "mp_haven" ] <- 128.0 // mfd
+
+ // good map, needs spawns, etc...
+ file.lethalFogHeights[ "mp_rise" ] <- 420.0 // mfd
+ file.lethalFogHeights[ "mp_runoff" ] <- 340.0 // mfd
+ file.lethalFogHeights[ "mp_zone_18" ] <- 460.0 // mfd
+ file.lethalFogHeights[ "mp_sandtrap" ] <- 64.0
+
+ // these don't work as well
+ file.lethalFogHeights[ "mp_swampland" ] <- 350.0 // mfd
+ file.lethalFogHeights[ "mp_backwater" ] <- 320.0 // mfd
+ file.lethalFogHeights[ "mp_airbase" ] <- 450.0
+ file.lethalFogHeights[ "mp_boneyard" ] <- 64.0
+ file.lethalFogHeights[ "mp_colony" ] <- 270.0
+ file.lethalFogHeights[ "mp_corporate" ] <- -765.0
+ file.lethalFogHeights[ "mp_fracture" ] <- 270.0
+ file.lethalFogHeights[ "mp_overlook" ] <- 16.0
+ file.lethalFogHeights[ "mp_relic" ] <- 475.0
+ file.lethalFogHeights[ "mp_smugglers_cove" ] <- 400.0
+ file.lethalFogHeights[ "mp_wargames" ] <- 64.0
+ file.lethalFogHeights[ "mp_switchback" ] <- 840.0
+
+ file.lethalFogHeights[ "mp_chin_rodeo_express" ] <- 1580.0
+
+ // custom: titanfall 2 maps
+ // TODO: really need a modular system here
+ file.lethalFogHeights[ "mp_colony02" ] <- 270.0 // map changed name from tf1 => tf2
+ file.lethalFogHeights[ "mp_glitch" ] <- 200.0
+ file.lethalFogHeights[ "mp_grave" ] <- 2350.0
+ file.lethalFogHeights[ "mp_homestead" ] <- 64.0
+ file.lethalFogHeights[ "mp_forwardbase_kodai" ] <- 930.0
+ file.lethalFogHeights[ "mp_thaw" ] <- 32.0
+ file.lethalFogHeights[ "mp_black_water_canal" ] <- 32.0
+ file.lethalFogHeights[ "mp_eden" ] <- 175.0
+ file.lethalFogHeights[ "mp_drydock" ] <- 300.0
+ file.lethalFogHeights[ "mp_crashsite3" ] <- 800.0 // crashsite is just as awful for this as it is for anything else
+ file.lethalFogHeights[ "mp_complex3" ] <- 630.0
+ file.lethalFogHeights[ "mp_relic02" ] <- 250.0 // not great, tf1's would honestly be worse though imo
+
+ // lf maps: overall a bit hit or miss, many likely have spawn issues
+ file.lethalFogHeights[ "mp_lf_stacks" ] <- -9999.0 // entirely nonworking, breaks spawns no matter what from what i can tell, could potentially use safe zones for this?
+ file.lethalFogHeights[ "mp_lf_deck" ] <- -9999.0 // nonworking fogcontroller so fog is invisible
+ file.lethalFogHeights[ "mp_lf_uma" ] <- 64.0
+ file.lethalFogHeights[ "mp_lf_meadow" ] <- 64.0
+ file.lethalFogHeights[ "mp_lf_traffic" ] <- 50.0
+ file.lethalFogHeights[ "mp_lf_township" ] <- 64.0
+
+ if ( mapName in file.lethalFogHeights )
+ return file.lethalFogHeights[ mapName ]
+
+ return 64.0
+}
+
+float function GetLethalFogTopTitan()
+{
+ float fogTop = GetLethalFogTop()
+
+ switch ( GetMapName() )
+ {
+ case "mp_lagoon":
+ case "mp_nexus":
+ case "mp_outpost_207":
+ case "mp_training_ground":
+ case "mp_chin_rodeo_express":
+ return fogTop
+ }
+
+ return fogTop + 256.0
+}
+
+float function GetLethalFogTop()
+{
+ return GetFogHeight() - file.fogDepth * 0.2
+}
+
+float function GetLethalFogBottom()
+{
+ return GetFogHeight() - file.fogDepth * 0.7
+}
+
+float function GetVisibleFogTop()
+{
+ return GetFogHeight() + file.fogDepth * 0.5
+}
+
+float function GetVisibleFogBottom()
+{
+ return GetFogHeight() - file.fogDepth * 0.5
+}
+
+float function GetMaxTitanSpawnFogHeight()
+{
+ return GetFogHeight() - file.maxTitanSpawnFogDepth
+}
+
+function AddLethalTitanVolume( vector volumeMins, vector volumeMaxs )
+{
+ Assert( volumeMins.x < volumeMaxs.x )
+ Assert( volumeMins.y < volumeMaxs.y )
+ Assert( volumeMins.z < volumeMaxs.z )
+
+ file.lethalTitanVolumes.append( { mins = volumeMins, maxs = volumeMaxs } )
+}
+
+function AddLethalPilotVolume( vector volumeMins, vector volumeMaxs )
+{
+ Assert( volumeMins.x < volumeMaxs.x )
+ Assert( volumeMins.y < volumeMaxs.y )
+ Assert( volumeMins.z < volumeMaxs.z )
+
+ file.lethalPilotVolumes.append( { mins = volumeMins, maxs = volumeMaxs } )
+}
+
+function AddSafePilotVolume( vector volumeMins, vector volumeMaxs )
+{
+ Assert( volumeMins.x < volumeMaxs.x )
+ Assert( volumeMins.y < volumeMaxs.y )
+ Assert( volumeMins.z < volumeMaxs.z )
+
+ file.safePilotVolumes.append( { mins = volumeMins, maxs = volumeMaxs } )
+}
+
+function IsEntInSafeVolume( entity ent )
+{
+ if ( ent.IsPlayer() )
+ {
+ foreach ( volume in file.safePilotVolumes )
+ {
+ vector entOrg = ent.GetOrigin()
+
+ #if SERVER
+ if ( file.volumesDebug )
+ DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 0, 0, 255, 1, 0.1 )
+ #endif
+
+ if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) )
+ return true
+ }
+ }
+}
+
+function IsEntInLethalVolume( entity ent )
+{
+ if ( ent.IsTitan() )
+ {
+ foreach ( volume in file.lethalTitanVolumes )
+ {
+ vector entOrg = ent.GetOrigin()
+
+ #if SERVER
+ if ( file.volumesDebug )
+ DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 255, 255, 0, 1, 0.1 )
+ #endif
+
+ if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) )
+ return true
+ }
+ }
+ else if ( ent.IsPlayer() )
+ {
+ foreach ( volume in file.lethalPilotVolumes )
+ {
+ vector entOrg = ent.GetOrigin()
+
+ #if SERVER
+ if ( file.volumesDebug )
+ DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 255, 255, 0, 1, 0.1 )
+ #endif
+
+ if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) )
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/Northstar.Custom/scripts/vscripts/mp/levels/mp_box.nut b/Northstar.Custom/scripts/vscripts/mp/levels/mp_box.nut
new file mode 100644
index 000000000..a18280091
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/mp/levels/mp_box.nut
@@ -0,0 +1,6 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/northstar_custom_autoprecache.gnut b/Northstar.Custom/scripts/vscripts/northstar_custom_autoprecache.gnut
new file mode 100644
index 000000000..62d089f74
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/northstar_custom_autoprecache.gnut
@@ -0,0 +1,14 @@
+untyped
+global function NorthstarCustomAutoprecache
+
+void function NorthstarCustomAutoprecache()
+{
+ PrecacheWeapon( "mp_weapon_peacekraber" )
+ PrecacheWeapon( "melee_pilot_kunai" )
+
+ if ( GAMETYPE == GAMEMODE_SBOX )
+ PrecacheWeapon( "mp_weapon_toolgun" )
+
+ // will include this as a custom asset when mod v2 is written
+ //PrecacheModel( $"models/titans/buddy/titan_buddy.mdl" )
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/titan/sh_first_person_embark.gnut b/Northstar.Custom/scripts/vscripts/titan/sh_first_person_embark.gnut
new file mode 100644
index 000000000..0c95ae4cd
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/titan/sh_first_person_embark.gnut
@@ -0,0 +1,41 @@
+global function FirstPersonEmbark_Init
+
+#if CLIENT
+ global function ServerCallback_HideHudForFPEmbark
+#endif
+
+void function FirstPersonEmbark_Init()
+{
+ // atm do this no matter what playlist we're on since playlist overrides seem to get sent to clients after networkvar registration
+ // not nice but whatever lol
+ AddCallback_OnRegisteringCustomNetworkVars( FirstPersonEmbark_RegisterCustomNetworkFunctions )
+
+ // busted rn lol
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 0 )
+ return
+
+ #if CLIENT
+ AddCallback_PlayerClassChanged( ShowHudOnEmbarkFinished )
+ #endif
+}
+
+void function FirstPersonEmbark_RegisterCustomNetworkFunctions()
+{
+ Remote_RegisterFunction( "ServerCallback_HideHudForFPEmbark" )
+}
+
+#if CLIENT
+void function ServerCallback_HideHudForFPEmbark()
+{
+ thread MainHud_TurnOff_RUI( true )
+ HidePermanentCockpitRui()
+}
+
+void function ShowHudOnEmbarkFinished( entity player )
+{
+ if ( !player.IsTitan() )
+ return
+
+ ShowPermanentCockpitRui()
+}
+#endif \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/titan/sh_titan_embark.gnut b/Northstar.Custom/scripts/vscripts/titan/sh_titan_embark.gnut
new file mode 100644
index 000000000..f9df27306
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/titan/sh_titan_embark.gnut
@@ -0,0 +1,2253 @@
+untyped
+
+global function TitanEmbark_Init
+
+global function TitanCanStand
+global function DebugEmbarkTimes
+global function TitanIsCurrentlyEmbarkableForPlayer
+global function PlayerCanEmbarkTitan
+global function PlayerCanImmediatelyEmbarkTitan
+global function FindBestEmbark
+global function GenerateEmbarkActionTable
+global function FindEmbarkActionForCriteria
+global function GetRandomEmbarkAction
+global function PlayerCanDisembarkTitan
+global function IsPlayerDisembarking
+
+#if SERVER
+ global function ForceScriptedEmbark
+ global function PlayerCanEmbarkIntoTitan
+ global function IsPlayerEmbarking
+ global function PlayerEmbarksTitan
+ global function PlayerLungesToEmbark
+ global function FindBestEmbarkForNpcAnim
+ global function PlayerDisembarksTitan
+ global function ForcedTitanDisembark
+ global function ForcedTitanDisembarkCustomAnims
+ global function PhaseEmbarkPhaseStart
+ global function PhaseEmbarkPhaseStop
+ global function OverrideCockpitLightFX
+ global function StartCockpitLightThink
+ global function CockpitLightStop
+ global function Embark_DelayedFadeOut
+ global function PlayerIsFarOffTheGround
+
+ global function SetSmallDisembarkFailSafeTeleportVector //TODO: Re-examine this for next game, probably should have different values for SP versus MP
+ global function SetLargeDisembarkFailSafeTeleportVector //TODO: Re-examine this for next game, probably should have different values for SP versus MP
+#endif
+
+#if DEV
+ global function SetEmbarkDebugPrint
+#endif
+
+const FAST_EMBARK = 1
+const SKIP_AHEAD_TIME = 2.0
+
+#if MP
+const EMBARK_FADE_TIME = 0.2
+#else
+const EMBARK_FADE_TIME = 0.3
+#endif
+
+struct
+{
+ asset cockpitLightFX = $"xo_cockpit_dlight"
+ bool embarkDebugPrint = false
+ vector smallDisembarkFailSafeTeleportVector = < 400, 400, 200 >
+ vector largeDisembarkFailSafeTeleportVector = < 600, 600, 600 >
+} file
+
+function TitanEmbark_Init()
+{
+ if ( reloadingScripts )
+ return
+
+ level.pilotDisembarkBounds <- {}
+ local end = {}
+ end.up <- 50.363811
+ end.forward <- 110.146927
+ end.right <- 13.045869
+ end.yaw <- -8.381051
+
+ local start = {}
+ start.up <- 156.750015
+ start.forward <- -13.429688
+ start.right <- -11.374998
+ start.yaw <- 0.409042
+
+ RefreshTitanEmbarkActions()
+
+ level.pilotDisembarkBounds.end <- end
+ level.pilotDisembarkBounds.start <- start
+
+ RegisterSignal( "OnComplete" )
+ RegisterSignal( "startembark" ) // temp
+
+ RegisterSignal( "DisembarkingTitan" )
+ RegisterSignal( "player_embarks_titan" )
+
+ #if SERVER
+ // add all the embark anims with this suffix
+ AddEmbarkAnims( "titan_atlas", "atlas", true )
+ AddEmbarkAnims( "titan_buddy", "buddy", true )
+ AddEmbarkAnims( "titan_ogre", "ogre", true )
+ AddEmbarkAnims( "titan_stryder", "stryder", true )
+
+ // AddEmbarkAudio( "titan_atlas", "atlas" )
+ // AddEmbarkAudio( "titan_buddy", "buddy" )
+ // AddEmbarkAudio( "titan_ogre", "ogre" )
+ // AddEmbarkAudio( "titan_stryder", "stryder" )
+
+ RegisterSignal( "titanKneel" )
+ RegisterSignal( "titanStand" )
+ RegisterSignal( "titanEmbark" )
+ RegisterSignal( "PhaseEmbarkPhaseStop" )
+
+ RegisterSignal( "CockpitLightStop" )
+ PrecacheParticleSystem( $"xo_cockpit_dlight" )
+
+ AddClientCommandCallback( "TitanDisembark", ClientCommand_TitanDisembark ) //
+ AddClientCommandCallback( "TitanKneel", ClientCommand_TitanKneel ) //
+ //AddClientCommandCallback( "TitanStand", ClientCommand_TitanStand ) //
+ AddClientCommandCallback( "TitanNextMode", ClientCommand_TitanNextMode ) //
+
+ AddCallback_OnTitanBecomesPilot( TitanBecomesPilot_UpdateRodeoRiderHud )
+ AddCallback_OnPilotBecomesTitan( PilotBecomesTitan_UpdateRodeoRiderHud )
+ #endif
+}
+
+void function OverrideCockpitLightFX( asset fx )
+{
+ file.cockpitLightFX = fx
+}
+
+void function AddEmbarkAnims( string titan, string titanSubClass, bool thirdPersonOnly = false )
+{
+ // anims are string-constructed from these types:
+ local Array =
+ [
+ "kneel_front",
+ "kneel_behind",
+ "kneel_right",
+ "kneel_left",
+ "kneel_airgrab",
+
+ "stand_front",
+ "stand_right",
+ "stand_left",
+ "stand_behind",
+ "stand_airgrab",
+
+ "above_right",
+ "above_left",
+ "kneel_above_right",
+ "kneel_above_left",
+ ]
+
+
+ // force consistency in animation names
+ foreach ( item in Array )
+ {
+ array<string> suffixes = [ "", "_fast" ]
+ foreach ( suffix in suffixes )
+ {
+ //printt( "Adding base " + item + " to " + titan )
+ local thirdPersonAlias = "pt_mount_" + item + suffix
+ local firstPersonAlias = "ptpov_mount_" + item + suffix
+ local thirdPersonAnim = "pt_mount_" + titanSubClass + "_" + item + suffix
+ local firstPersonAnim = "ptpov_mount_" + titanSubClass + "_" + item + suffix
+
+ if ( thirdPersonOnly )
+ firstPersonAnim = ""
+
+ AddAnimAlias( titanSubClass, thirdPersonAlias, thirdPersonAnim )
+ AddAnimAlias( titanSubClass, firstPersonAlias, firstPersonAnim )
+ }
+ }
+}
+
+function AddEmbarkAudio( titan, titanSubClass )
+{
+ // audio files are string-constructed from these types:
+ local Array =
+ [
+ "Kneeling_Front",
+ "Kneeling_Behind",
+ "Kneeling_Right",
+ "Kneeling_Left",
+ "Kneeling_AboveRight",
+ "Kneeling_AboveLeft",
+
+ "Standing_Front",
+ "Standing_Behind",
+ "Standing_Airgrab",
+ "Standing_AboveRight",
+ "Standing_AboveLeft"
+ ]
+
+
+ // force consistency in audio file names
+ foreach ( item in Array )
+ {
+ //printt( "Adding base " + item + " to " + titan )
+ local thirdPersonAlias = "Embark_" + item + "_3P"
+ local firstPersonAlias = "Embark_" + item + "_1P"
+ local thirdPersonAnim = titanSubClass + "_Embark_" + item + "_3P"
+ local firstPersonAnim = titanSubClass + "_Embark_" + item + "_1P"
+
+ AddAudioAlias( titanSubClass, thirdPersonAlias, thirdPersonAnim )
+ AddAudioAlias( titanSubClass, firstPersonAlias, firstPersonAnim )
+ }
+}
+
+function RefreshTitanEmbarkActions()
+{
+ if ( "titanEmbarkActions" in level )
+ {
+ delete level.titanEmbarkActions
+ delete level.titanEmbarkFarthestDistance
+ }
+
+ local groundDist = 260
+
+ level.titanEmbarkActions <- []
+ level.titanEmbarkFarthestDistance <- 0
+ local action
+
+ action =
+ {
+ direction = Vector( 1, 0, 0 )
+ distance = groundDist
+ embark = "front"
+ minDot = 0.4
+ priority = 1 // tried after priority 0 actions
+ titanCanStandRequired = false
+ //onGround = null // either
+ useAnimatedRefAttachment = true
+ alignFrontEnabled = true
+ canSkipAhead = true
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_front"
+ thirdPersonKneelingAlias = "pt_mount_kneel_front"
+ firstPersonStandingAlias = "ptpov_mount_stand_front"
+ thirdPersonStandingAlias = "pt_mount_stand_front"
+ titanKneelingAnim = "at_mount_kneel_front"
+ titanStandingAnim = "at_mount_stand_front"
+
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 1, 0, 0 )
+ distance = groundDist
+ embark = "front"
+ minDot = -1
+ priority = 2 // tried after priority 1 actions
+ titanCanStandRequired = false
+ //onGround = null // either
+ useAnimatedRefAttachment = true
+ alignFrontEnabled = true
+ canSkipAhead = true
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_front"
+ thirdPersonKneelingAlias = "pt_mount_kneel_front"
+ firstPersonStandingAlias = "ptpov_mount_stand_front"
+ thirdPersonStandingAlias = "pt_mount_stand_front"
+ titanKneelingAnim = "at_mount_kneel_front"
+ titanStandingAnim = "at_mount_stand_front"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 0, 1, 0 )
+ distance = groundDist
+ embark = "left"
+ minDot = 0.4
+ priority = 1 // tried after priority 0 actions
+ titanCanStandRequired = true
+ useAnimatedRefAttachment = true
+ alignFrontEnabled = true
+ canSkipAhead = true
+ //onGround = null // either
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_left"
+ thirdPersonKneelingAlias = "pt_mount_kneel_left"
+ firstPersonStandingAlias = "ptpov_mount_stand_front"
+ thirdPersonStandingAlias = "pt_mount_stand_left"
+ titanKneelingAnim = "at_mount_kneel_left"
+ titanStandingAnim = "at_mount_stand_left"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Left_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Left_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 0, -1, 0 )
+ distance = groundDist
+ embark = "right"
+ minDot = 0.4
+ priority = 1 // tried after priority 0 actions
+ titanCanStandRequired = true
+ useAnimatedRefAttachment = true
+ alignFrontEnabled = true
+ canSkipAhead = true
+ //onGround = null // either
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_right"
+ thirdPersonKneelingAlias = "pt_mount_kneel_right"
+ firstPersonStandingAlias = "ptpov_mount_stand_front"
+ thirdPersonStandingAlias = "pt_mount_stand_right"
+ titanKneelingAnim = "at_mount_kneel_right"
+ titanStandingAnim = "at_mount_stand_right"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Right_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Right_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+
+ action =
+ {
+ direction = Vector( -1, 0, 0 )
+ distance = groundDist
+ embark = "behind"
+ minDot = 0.4
+ priority = 1 // tried after priority 0 actions
+ titanCanStandRequired = true
+ useAnimatedRefAttachment = true
+ canSkipAhead = false
+ //onGround = null // either
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_behind"
+ thirdPersonKneelingAlias = "pt_mount_kneel_behind"
+ firstPersonStandingAlias = "ptpov_mount_stand_behind"
+ thirdPersonStandingAlias = "pt_mount_stand_behind"
+ titanKneelingAnim = "at_mount_kneel_behind"
+ titanStandingAnim = "at_mount_stand_behind"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Behind_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Behind_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Behind_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Behind_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 0, 0, 1 ) // 0 -1 1
+ distance = 350
+ embark = "above_close"
+ minDot = 0.88
+ canSkipAhead = false
+ priority = 0 // priority actions are checked first
+
+ titanCanStandRequired = true
+ useAnimatedRefAttachment = true
+ //onGround = false // must be in air
+
+ animSets =
+ {
+ right =
+ {
+ direction = Vector( 0, -1, 0 ) // 0 -1 1
+ firstPersonKneelingAlias = "ptpov_mount_kneel_above_right"
+ thirdPersonKneelingAlias = "pt_mount_kneel_above_right"
+ firstPersonStandingAlias = "ptpov_mount_above_right"
+ thirdPersonStandingAlias = "pt_mount_above_right"
+ titanKneelingAnim = "at_mount_kneel_above"
+ titanStandingAnim = "at_mount_above_right"
+
+ audioSet = //An annoying exception to how the audioSet is organized, better this than the alternative and having to find the "best audio set"
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_AboveRight_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_AboveRight_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_AboveRight_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_AboveRight_3P"
+ }
+ }
+
+ left =
+ {
+ direction = Vector( 0, 1, 0 ) // 0 -1 1
+ firstPersonKneelingAlias = "ptpov_mount_kneel_above_left"
+ thirdPersonKneelingAlias = "pt_mount_kneel_above_left"
+ firstPersonStandingAlias = "ptpov_mount_above_left"
+ thirdPersonStandingAlias = "pt_mount_above_left"
+ titanKneelingAnim = "at_mount_kneel_above"
+ titanStandingAnim = "at_mount_above_left"
+
+ audioSet = //An annoying exception to how the audioSet is organized, better this than the alternative and having to find the "best audio set"
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_AboveLeft_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_AboveLeft_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_AboveLeft_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_AboveLeft_3P"
+ }
+ }
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ action =
+ {
+ direction = Vector( 0, 0, 1 )
+ distance = 275
+ embark = "above_grab"
+ minDot = 0.3
+ titanCanStandRequired = true
+ //onGround = null // false // must be in air
+ useAnimatedRefAttachment = true
+ //lungeCheck = true
+ canSkipAhead = true
+
+ alignFrontEnabled = true
+
+ priority = 0 // priority actions are checked first
+
+ animSet =
+ {
+ firstPersonKneelingAlias = "ptpov_mount_kneel_airgrab"
+ thirdPersonKneelingAlias = "pt_mount_kneel_airgrab"
+ titanKneelingAnim = "at_mount_kneel_airgrab"
+
+ firstPersonStandingAlias = "ptpov_mount_stand_airgrab"
+ thirdPersonStandingAlias = "pt_mount_stand_airgrab"
+ titanStandingAnim = "at_mount_stand_airgrab"
+ }
+
+ audioSet =
+ {
+ firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P"
+ thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P"
+ firstPersonStandingAudioAlias = "Embark_Standing_Front_1P"
+ thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P"
+ }
+ }
+ level.titanEmbarkActions.append( action )
+
+ local autoParms =
+ [
+ "lungeCheck"
+ "alignFrontEnabled"
+ ]
+
+ foreach ( action in level.titanEmbarkActions )
+ {
+ if ( action.distance > level.titanEmbarkFarthestDistance )
+ {
+ level.titanEmbarkFarthestDistance = action.distance
+ }
+ foreach ( parm in autoParms )
+ {
+ if ( !( parm in action ) )
+ action[ parm ] <- false
+ }
+ }
+}
+
+function DebugEmbarkTimes()
+{
+ local settings = [ "atlas", "ogre", "stryder" ]
+
+ array< asset > models = [ $"models/Humans/imc_pilot/male_cq/imc_pilot_male_cq.mdl", $"models/humans/pilot/female_cq/pilot_female_cq.mdl" ]
+ local times = {}
+
+ foreach ( model in models )
+ {
+ times[ model ] <- []
+ entity prop = CreatePropDynamic( model, Vector(0,0,0), Vector(0,0,0) )
+ printt( "Human model: " + model )
+
+ foreach ( setting in settings )
+ {
+ printt( "Titan: " + setting )
+ foreach ( action in level.titanEmbarkActions )
+ {
+ printt( "Embark Direction: " + action.embark )
+ if ( "animSet" in action )
+ {
+ local animation = GetAnimFromAlias( setting, action.animSet.thirdPersonKneelingAlias )
+ local time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Kneeling: " + time )
+
+ animation = GetAnimFromAlias( setting, action.animSet.thirdPersonStandingAlias )
+ time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Standing: " + time )
+ }
+
+ if ( "animSets" in action )
+ {
+ local animation = GetAnimFromAlias( setting, action.animSets.left.thirdPersonKneelingAlias )
+ local time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Kneeling Left: " + time )
+
+ animation = GetAnimFromAlias( setting, action.animSets.left.thirdPersonStandingAlias )
+ time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Standing Left: " + time )
+
+ animation = GetAnimFromAlias( setting, action.animSets.right.thirdPersonKneelingAlias )
+ time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Kneeling Right: " + time )
+
+ animation = GetAnimFromAlias( setting, action.animSets.right.thirdPersonStandingAlias )
+ time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ printt( "Standing Right: " + time )
+ }
+
+ printt( " " )
+ }
+ }
+
+ prop.Kill_Deprecated_UseDestroyInstead()
+ }
+
+ printt( "Time comparison: " )
+ bool wrong = false
+ for ( int i = 0; i < times[ models[0] ].len(); i++ )
+ {
+ if ( times[models[0]][i].time == times[models[1]][i].time )
+ {
+ printt( "MATCH: " + ( i + 1 ) + " times: " + times[models[0]][i].time + " " + times[models[1]][i].time + " " + times[models[1]][i].animation )
+ }
+ else
+ {
+ printt( "MISMATCH: " + ( i + 1 ) + " times: " + times[models[0]][i].time + " " + times[models[1]][i].time + " " + times[models[1]][i].animation )
+ wrong = true
+ }
+ }
+// Assert( !wrong, "Times did not match between male and female, see above" )
+}
+
+
+#if SERVER
+bool function ClientCommand_TitanKneel( entity player, array<string> args )
+{
+ entity titan = player.GetPetTitan()
+ if ( !IsAlive( titan ) )
+ return true
+
+ titan.Signal( "titanKneel" )
+ titan.s.standQueued = false
+ return true
+}
+
+/*bool function ClientCommand_TitanStand( entity player, array<string> args )
+{
+ entity titan = player.GetPetTitan()
+ if ( !IsAlive( titan ) )
+ return true
+
+ titan.Signal( "titanStand" )
+ titan.s.standQueued = true
+ titan.s.kneelQueued = false
+ return true
+}*/
+
+bool function ClientCommand_TitanNextMode( entity player, array<string> args )
+{
+ if ( !IsAlive( player ) )
+ return true
+
+ entity titan = player.GetPetTitan()
+ if ( IsAlive( titan ) )
+ NPCTitanNextMode( titan, player )
+
+ return true
+}
+#endif // SERVER
+
+
+function EmbarkLine( player, titan )
+{
+ player.EndSignal( "startembark" )
+ local ref = player.LookupAttachment( "ref" )
+ local hijack = titan.LookupAttachment( "hijack" )
+ local origin
+ for ( ;; )
+ {
+ origin = titan.GetAttachmentOrigin( hijack )
+ DebugDrawLine( player.GetOrigin(), origin, 255, 0, 0, true, 0.15 )
+
+ origin = player.GetAttachmentOrigin( ref )
+ DebugDrawLine( player.GetOrigin(), origin, 0, 255, 0, true, 0.15 )
+ WaitFrame()
+ }
+}
+
+
+#if SERVER
+function PlayerEmbarksTitan( entity player, entity titan, table embark )
+{
+ //player.SetOrigin( Vector(314.971405, -1826.728638, 116.031250))
+ //player.SetAngles( Vector(0.000000, 133.945892, 0.000000))
+ //titan.SetOrigin( Vector(284.887970, -1622.180542, 112.093750))
+ //titan.SetAngles(Vector(0.000000, 112.264252, 0))
+
+ entity soul = titan.GetTitanSoul()
+ string settings = GetSoulTitanSubClass( soul )
+ printt( "TitanEmbarkDebug: Player ", player.GetOrigin(), player.GetAngles(), " Titan ", titan.GetOrigin(), titan.GetAngles(), settings, GetMapName() )
+
+
+ Assert( IsAlive( titan ) )
+ Assert( IsAlive( player ) )
+
+ player.SetInvulnerable()
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "TitanEjectionStarted" )
+ titan.EndSignal( "OnDeath" )
+
+ titan.Signal( "player_embarks_titan" )
+ player.Signal( "player_embarks_titan" )
+
+// Assert( !InSolid( titan ), titan + " is in solid" )
+
+ DisableCloak( player )
+
+ entity groundEntity = titan.GetGroundEntity()
+ vector startOrigin = titan.GetGroundRelativePos()
+ vector startAngles = titan.GetAngles()
+
+ OnThreadEnd(
+ function() : ( player, groundEntity, startOrigin, startAngles )
+ {
+ if ( IsValid( player ) )
+ {
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() & (~CE_FLAG_EMBARK) )
+ TitanEmbark_PlayerCleanup( player )
+ player.p.isEmbarking = false
+
+ if ( IsAlive( player ) )
+ {
+ if ( player.IsTitan() ) //Defensive fix, sometimes in SP (when game is shutting down etc ) this can run without the player being alive
+ {
+ TitanEmbarkFailsafe( player, null, groundEntity, startOrigin )
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanEmbark" )
+ LetTitanPlayerShootThroughBubbleShield( player )
+ }
+ else
+ {
+ player.Die() //Defensive fix, sometimes in SP (when game is shutting down etc ) this can run without the player being alive
+ }
+
+ }
+ }
+ }
+ )
+
+ // track the embarking player so we can kill him if the titan dies
+ titan.s.embarkingPlayer <- player
+ player.p.isEmbarking = true
+
+ #if HAS_STATS
+ UpdatePlayerStat( player, "misc_stats", "titanEmbarks", 1 )
+ #endif
+ #if SERVER && MP
+ PIN_AddToPlayerCountStat( player, "embarks" )
+ PIN_PlayerAbility( player, "", "embark", {}, 0 )
+ #endif
+
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_EMBARK )
+
+ waitthread PlayerEmbarksTitan_PlayerBecomesTitan( player, titan, embark )
+}
+
+function PlayerEmbarksTitan_PlayerBecomesTitan( entity player, entity titan, table embark )
+{
+ // a place to store the player finish time
+ table e
+
+ e.threads <- 0
+ e.embarkAction <- embark.action
+ e.animSet <- embark.animSet
+ e.audioSet <- embark.audioSet
+
+ e.canStand <- TitanCanStand( titan )
+
+ e.shouldDoRegularEmbark <- ShouldDoRegularEmbark( titan )
+
+ // player and titan do their anims and wait for each other to finish
+ thread TitanEmbark_TitanEmbarks( player, titan, e )
+ waitthread TitanEmbark_PlayerEmbarks( player, titan, e )
+}
+
+bool function ShouldDoRegularEmbark( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+
+ if ( IsSingleplayer() && GetSoulPlayerSettings( soul ) == "titan_buddy" )
+ {
+ return titan.GetNPCState() == "combat" || titan.GetNPCState() == "alert"
+ }
+
+ return true
+}
+
+bool function TitanHasLeftAndRightEmbarkAnims( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ string settings = GetSoulPlayerSettings( soul )
+ var hasAnims = Dev_GetPlayerSettingByKeyField_Global( settings, "hasLeftRightEmbarks" )
+ if ( hasAnims != null && hasAnims == 1 )
+ {
+ return true
+ }
+
+ return false
+}
+
+function TitanEmbark_PlayerCleanup( player )
+{
+ expect entity( player )
+
+ player.SetSyncedEntity( null )
+ DeployViewModelAndEnableWeapons( player )
+ player.UnforceStand()
+ player.UnforceCrouch()
+ //printt("Clearing invulnerable")
+ player.ClearInvulnerable()
+ player.ClearParent()
+ //Let player jump in air after getting out if he wants to
+ player.TouchGround()
+ player.Anim_Stop()
+}
+
+
+void function ForceScriptedEmbark( entity player, entity titan )
+{
+ HolsterViewModelAndDisableWeapons( player )
+ ClearPlayerAnimViewEntity( player )
+ player.ClearParent()
+ PilotBecomesTitan( player, titan )
+
+ thread PlayAnim( player, "cqb_idle_mp" )
+ player.Anim_Stop()
+
+ player.SetOrigin( titan.GetOrigin() )
+ vector angles = titan.GetAngles()
+ angles.z = 0
+ angles.x = 0
+ player.SetAngles( angles )
+ player.SnapEyeAngles( angles )
+
+ SetStanceStand( player.GetTitanSoul() )
+
+ TitanEmbark_PlayerCleanup( player )
+ if ( player.ContextAction_IsBusy() )
+ player.ContextAction_ClearBusy()
+}
+
+
+function TitanEmbarkFailsafe( entity player, entity titan, entity groundEnt, vector startOrigin )
+{
+ if ( GetCurrentPlaylistVarInt( "player_embark_in_solid_checks", 0 ) != 1 )
+ return
+
+ #if DEV
+ if ( file.embarkDebugPrint )
+ {
+ if ( IsValid( titan ) )
+ printt( "TitanEmbarkFailsafe, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + startOrigin )
+ else
+ printt( "TitanEmbarkFailsafe, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", null titan, safeStartPoint: " + startOrigin )
+ }
+ #endif
+
+ if ( !PutEntityInSafeSpot( player, titan, groundEnt, startOrigin, player.GetOrigin() ) )
+ player.SetOrigin( startOrigin )
+
+ #if DEV
+ if ( file.embarkDebugPrint )
+ {
+ if ( IsValid( titan ) )
+ printt( "TitanEmbarkFailsafe, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + startOrigin )
+ else
+ printt( "TitanEmbarkFailsafe, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", null titan, safeStartPoint: " + startOrigin )
+ }
+ #endif
+}
+
+function TitanEmbark_PlayerEmbarks( entity player, entity titan, table e )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "TitanEjectionStarted" )
+ titan.EndSignal( "OnDeath" )
+
+ e.threads++
+ OnThreadEnd(
+ function() : ( player, e )
+ {
+ if ( IsValid( player ) )
+ {
+ // ensure these are cleared regardless
+ ClearPlayerAnimViewEntity( player )
+
+ if ( player.ContextAction_IsBusy() )
+ player.ContextAction_ClearBusy()
+ }
+
+ e.threads--
+ if ( !e.threads )
+ {
+ Signal( e, "OnComplete" )
+ }
+ }
+ )
+
+ player.ContextAction_SetBusy()
+
+ bool standing = false
+
+ if ( e.canStand )
+ {
+ if ( e.shouldDoRegularEmbark )
+ {
+ player.ForceStand()
+ switch ( titan.GetTitanSoul().GetStance() )
+ {
+ case STANCE_KNEELING:
+ case STANCE_KNEEL:
+ standing = false
+ break
+
+ default:
+ standing = true
+ break
+ }
+ }
+ else
+ {
+ standing = false
+ }
+ }
+ else
+ {
+ player.ForceCrouch()
+ }
+
+ HolsterViewModelAndDisableWeapons( player )
+
+ FirstPersonSequenceStruct sequence
+ sequence.attachment = "hijack"
+ sequence.useAnimatedRefAttachment = expect bool ( e.embarkAction.useAnimatedRefAttachment )
+ sequence.blendTime = 0.5
+
+ entity soul = titan.GetTitanSoul()
+ string settings = GetSoulTitanSubClass( soul )
+
+ local hasViewCone
+
+ // string thirdPersonAudio
+ // string firstPersonAudio
+
+ if ( standing )
+ {
+ sequence.firstPersonAnim = GetAnimFromAlias( settings, e.animSet.firstPersonStandingAlias )
+ sequence.thirdPersonAnim = GetAnimFromAlias( settings, e.animSet.thirdPersonStandingAlias )
+ hasViewCone = false
+ // thirdPersonAudio = GetAudioFromAlias( settings, e.audioSet.thirdPersonStandingAudioAlias )
+ // firstPersonAudio = GetAudioFromAlias( settings, e.audioSet.firstPersonStandingAudioAlias )
+
+ }
+ else
+ {
+ sequence.firstPersonAnim = GetAnimFromAlias( settings, e.animSet.firstPersonKneelingAlias )
+ sequence.thirdPersonAnim = GetAnimFromAlias( settings, e.animSet.thirdPersonKneelingAlias )
+ hasViewCone = true
+ // thirdPersonAudio = GetAudioFromAlias( settings, e.audioSet.thirdPersonKneelingAudioAlias )
+ // firstPersonAudio = GetAudioFromAlias( settings, e.audioSet.firstPersonKneelingAudioAlias )
+ }
+
+ sequence.thirdPersonAnimIdle = "pt_mount_idle"
+
+ bool doFirstPersonAnim = true
+
+ if ( sequence.firstPersonAnim == "" )
+ {// if there is no first person anim, then there must be a third person camera
+ sequence.thirdPersonCameraAttachments.append( "VDU" )
+ sequence.thirdPersonCameraVisibilityChecks = true
+ doFirstPersonAnim = false
+ }
+
+ if ( hasViewCone )
+ {
+ sequence.viewConeFunction = EmbarkViewCone
+ thread DelayedClearViewCone( player )
+ }
+
+ //thread DelayedDisableEmbarkPlayerHud( player, sequence )
+
+ AddAnimEvent( player, "phase_shift_start", PhaseEmbarkPhaseStart )
+ AddAnimEvent( player, "phase_shift_stop", PhaseEmbarkPhaseStop )
+ AddAnimEvent( titan, "cockpit_light_start", CockpitLightStart )
+ AddAnimEvent( titan, "cockpit_light_stop", CockpitLightStop )
+
+ OnThreadEnd(
+ function() : ( player, titan )
+ {
+ if ( IsValid( player ) )
+ {
+ Signal( player, "PhaseEmbarkPhaseStop" )
+ DeleteAnimEvent( player, "phase_shift_start" )
+ DeleteAnimEvent( player, "phase_shift_stop" )
+ }
+
+ if ( IsAlive( titan ) ) //Consider clearing titan.s.embarkingPlayer here?
+ titan.Die()
+ }
+ )
+
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_HideHudForFPEmbark" )
+
+ // fp embark hacks
+ entity viewControl = CreateEntity( "point_viewcontrol" )
+ viewControl.kv.spawnflags = 56
+ DispatchSpawn( viewControl )
+
+ viewControl.SetParent( player, "headshot" )
+ viewControl.SetOrigin( < 4, 0, 0 > )
+ viewControl.SetAngles( < 0, 0, 0 > )
+ player.SetViewEntity( viewControl, false )
+ }
+
+ thread FirstPersonSequence( sequence, player, titan )
+ // EmitDifferentSoundsOnEntityForPlayerAndWorld( firstPersonAudio, thirdPersonAudio, titan, player )
+
+ float animDuration = player.GetSequenceDuration( sequence.thirdPersonAnim )
+
+ if ( ShouldSkipAheadIntoEmbark( standing, player, titan, e ) )
+ {
+ local duration = player.GetSequenceDuration( sequence.thirdPersonAnim )
+ if ( duration >= SKIP_AHEAD_TIME )
+ {
+ player.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME )
+ entity viewModel = player.GetFirstPersonProxy()
+
+ if ( IsValid( viewModel ) && EntHasModelSet( viewModel ) && doFirstPersonAnim ) //JFS: Defensive fix for player not having view models sometimes
+ viewModel.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME )
+
+ animDuration = SKIP_AHEAD_TIME
+ }
+ }
+
+ thread Embark_DelayedFadeOut( player, titan, animDuration )
+
+ WaittillAnimDone( player )
+
+ Signal( player, "PhaseEmbarkPhaseStop" )
+
+ ClearPlayerAnimViewEntity( player )
+ PilotBecomesTitan( player, titan )
+
+ thread PlayAnim( player, "cqb_idle_mp" )
+ player.Anim_Stop()
+ player.SetVelocity( <0,0,0> )
+
+ player.SetOrigin( titan.GetOrigin() )
+ local angles = titan.GetAngles()
+ angles.z = 0
+ angles.x = 0
+ player.SetAngles( angles )
+ player.SnapEyeAngles( angles )
+
+ // soul stuff should be from anim event
+ Assert( IsServer() )
+ SetStanceStand( player.GetTitanSoul() )
+ titan.Destroy()
+}
+
+void function Embark_DelayedFadeOut( entity player, entity titan, float delay )
+{
+ if ( !IsAlive( player ) )
+ return
+
+ if ( !IsValid( titan ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ wait delay - EMBARK_FADE_TIME
+
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 0 )
+ {
+ ScreenFadeToBlack( player, EMBARK_FADE_TIME, EMBARK_FADE_TIME + 0.2 ) // a little extra so we stay black
+ wait EMBARK_FADE_TIME
+ }
+ else
+ {
+ OnThreadEnd( function() : ( player )
+ {
+ player.ClearViewEntity()
+ })
+
+ wait EMBARK_FADE_TIME - 0.2
+ ScreenFadeToBlack( player, 0.2, 0.4 )
+ wait 0.2
+ player.ClearViewEntity() // make sure player is in normal first person again
+ }
+
+ ScreenFadeFromBlack( player, EMBARK_FADE_TIME, EMBARK_FADE_TIME )
+}
+
+void function PlayStartupSounds( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return
+ entity player = soul.GetBossPlayer()
+ if ( !IsValid( player ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+
+ string titanSettings = GetSoulPlayerSettings( soul )
+ var startupSound = Dev_GetPlayerSettingByKeyField_Global( titanSettings, "startup_sound" )
+
+ if ( startupSound != null )
+ EmitSoundOnEntityOnlyToPlayer( player, player, expect string(startupSound) )
+}
+
+
+void function CockpitLightStart( entity titan )
+{
+ thread StartCockpitLightThink( titan, 5.0 )
+}
+
+void function StartCockpitLightThink( entity titan, float timeout )
+{
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "CockpitLightStop" )
+
+ int attachID = titan.LookupAttachment( "HIJACK" )
+ int fxID = GetParticleSystemIndex( file.cockpitLightFX )
+ entity fx = StartParticleEffectOnEntity_ReturnEntity( titan, fxID, FX_PATTACH_POINT_FOLLOW, attachID )
+
+ OnThreadEnd(
+ function() : ( fx )
+ {
+ if ( IsValid( fx ) )
+ EffectStop( fx )
+ }
+ )
+
+ if ( timeout < 0 )
+ WaitForever()
+ else
+ wait timeout
+}
+
+void function CockpitLightStop( entity titan )
+{
+ titan.Signal( "CockpitLightStop" )
+}
+
+void function PhaseEmbarkPhaseStart( entity player )
+{
+ player.MakeInvisible()
+ PlayPhaseShiftDisappearFX( player )
+ EmitSoundOnEntity( player, "pilot_phaseembark_activate_3p" )
+
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 )
+ {
+ player.PhaseShiftBegin( 0.0, 0.2 )
+ player.GetPetTitan().SetForceVisibleInPhaseShift( true ) // doesn't work for some reason
+ }
+
+ thread PhaseEmbarkPhaseCleanup( player )
+}
+
+void function PhaseEmbarkPhaseCleanup( player )
+{
+ EndSignal( player, "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ {
+ player.MakeVisible()
+ }
+ }
+ )
+
+ WaitSignal( player, "PhaseEmbarkPhaseStop" )
+}
+
+void function PhaseEmbarkPhaseStop( entity player )
+{
+ Signal( player, "PhaseEmbarkPhaseStop" )
+ PlayPhaseShiftDisappearFX( player )
+ EmitSoundOnEntity( player, "pilot_phaseembark_end_3p" )
+
+ if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 )
+ player.PhaseShiftCancel()
+}
+
+function ShouldSkipAheadIntoEmbark( standing, player, titan, e )
+{
+ if ( !standing )
+ return false
+
+ if ( !e.embarkAction.canSkipAhead )
+ return false
+
+ local playerEye = player.EyePosition()
+ local titanOrg = titan.GetOrigin()
+ local vec = playerEye - titanOrg
+ vec.Norm()
+ vec.z = 0
+ local start = playerEye
+ local end = playerEye + vec * 24
+
+ if ( Distance( player.GetOrigin(), titan.GetOrigin() ) >= 145 )
+ return false
+
+ local mask = TRACE_MASK_PLAYERSOLID
+ TraceResults result = TraceLine( start, end, [ titan, player ], mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( start, result.endPos, 0, 255, 0, true, 10.0 )
+ //DebugDrawLine( result.endPos, end, 255, 0, 0, true, 10.0 )
+ return result.fraction < 1.0
+}
+#endif // SERVER
+
+function DelayedDisableEmbarkPlayerHud( player, sequence )
+{
+ player.EndSignal( "OnDeath" )
+
+ local duration = player.GetSequenceDuration( sequence.thirdPersonAnim )
+ wait duration - 1.0
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_EMBARK )
+}
+
+#if SERVER
+function DelayedClearViewCone( player )
+{
+ player.EndSignal( "OnDeath" )
+ wait 1.0
+ player.PlayerCone_SetLerpTime( 0.5 )
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( 0 )
+ player.PlayerCone_SetMaxYaw( 0 )
+ player.PlayerCone_SetMinPitch( 0 )
+ player.PlayerCone_SetMaxPitch( 0 )
+}
+
+void function EmbarkViewCone( entity player )
+{
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -70 )
+ player.PlayerCone_SetMaxYaw( 60 )
+ player.PlayerCone_SetMinPitch( -80 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+function TitanEmbark_TitanEmbarks( player, titan, e )
+{
+ expect entity( player )
+ expect entity( titan )
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "TitanEjectionStarted" )
+ titan.EndSignal( "OnDeath" )
+
+ titan.ContextAction_SetBusy()
+
+ AddAnimEvent( titan, "play_startup_sound", PlayStartupSounds )
+
+ e.threads++
+ OnThreadEnd(
+ function() : ( e, player, titan )
+ {
+ e.threads--
+ if ( !e.threads )
+ {
+ Signal( e, "OnComplete" )
+ }
+
+ if ( !IsAlive( player ) && IsAlive( titan ) )
+ {
+ titan.Anim_Stop()
+ titan.ContextAction_ClearBusy()
+ DeleteAnimEvent( titan, "play_startup_sound" )
+ }
+ }
+ )
+
+ local soul = titan.GetTitanSoul()
+
+ // dont let other players get in
+
+ local animation
+// local waittillAnimDone
+ local alignFront
+ bool standing = false
+
+ if ( e.canStand )
+ {
+ if ( e.shouldDoRegularEmbark )
+ {
+ // default
+ switch ( titan.GetTitanSoul().GetStance() )
+ {
+ case STANCE_KNEELING:
+ case STANCE_KNEEL:
+ animation = e.animSet.titanKneelingAnim
+ alignFront = false
+ break
+
+ default:
+ animation = e.animSet.titanStandingAnim
+ if ( TitanHasLeftAndRightEmbarkAnims( titan ) )
+ alignFront = false
+ else
+ alignFront = true
+ standing = true
+ break
+ }
+ }
+ else
+ {
+ // special for BT if he is in casual mode
+ animation = e.animSet.titanKneelingAnim
+ alignFront = false
+ }
+ }
+ else
+ {
+ animation = "at_mount_kneel_without_standing"
+ alignFront = false
+// waittillAnimDone = false
+ }
+
+ //sequence.blendTime = 0.5
+
+ printt("This is mount animation name: " + animation )
+ if ( e.embarkAction.alignFrontEnabled && alignFront )
+ {
+ local titanOrg = titan.GetOrigin()
+ local vec = player.GetOrigin() - titanOrg
+ local angles = VectorToAngles( vec )
+ angles.x = 0
+ angles.z = 0
+ thread PlayAnimGravityClientSyncing( titan, animation, titanOrg, angles )
+ }
+ else
+ {
+ thread PlayAnimGravityClientSyncing( titan, animation )
+ }
+
+ if ( ShouldSkipAheadIntoEmbark( standing, player, titan, e ) )
+ {
+ local duration = titan.GetSequenceDuration( animation )
+
+ if ( duration >= SKIP_AHEAD_TIME ) // failsafe
+ titan.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME )
+ }
+
+ // titan will become player now
+ WaitForever()
+}
+
+bool function ClientCommand_TitanDisembark( entity player, array<string> args )
+{
+ if ( !PlayerCanDisembarkTitan( player ) )
+ return true
+
+ ScreenFade( player, 0, 1, 0, 255, 0.2, 0.2, FFADE_IN | FFADE_PURGE )
+ player.CockpitStartDisembark()
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" )
+
+ thread PlayerDisembarksTitan( player )
+
+ return true
+}
+
+void function ForcedTitanDisembark( entity player )
+{
+ Assert( PlayerCanDisembarkTitan( player ) )
+
+ player.CockpitStartDisembark()
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" )
+
+ waitthread PlayerDisembarksTitan( player )
+}
+
+function ForcedTitanDisembarkCustomAnims( entity player, FirstPersonSequenceStruct functionref( entity, entity ) playerSequenceFunc, FirstPersonSequenceStruct functionref( entity, entity ) titanSequenceFunc )
+{
+ Assert( PlayerCanDisembarkTitan( player ) )
+
+ player.CockpitStartDisembark()
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" )
+
+ player.p.isCustomDisembark = true
+ waitthread PlayerDisembarksTitanWithSequenceFuncs( player, playerSequenceFunc, titanSequenceFunc )
+ player.p.isCustomDisembark = false
+}
+
+#endif // SERVER
+
+function PlayerCanDisembarkTitan( entity player )
+{
+ if ( !player.IsTitan() )
+ return false
+
+ if ( !IsAlive( player ) )
+ return false
+
+ if ( IsValid( player.GetParent() ) )
+ return false
+
+ if ( Riff_TitanExitEnabled() == eTitanExitEnabled.Never )
+ return false
+
+ if ( !CanDisembark( player ) )
+ return false
+
+ #if SERVER
+ if ( player.IsNoclipping() )
+ return false
+ // client doesn't know these things
+ if ( IsPlayerDisembarking( player ) )
+ return false
+ if ( IsPlayerEmbarking( player ) )
+ return false
+ #endif
+
+ if ( !HasSoul( player ) )
+ return false
+
+ local soul = player.GetTitanSoul()
+ if ( soul.IsEjecting() )
+ return false
+
+ Assert( soul == player.GetTitanSoul() )
+
+ return true
+}
+
+#if SERVER
+
+function PlayerDisembarksTitan( player )
+{
+ expect entity( player )
+ PlayerDisembarksTitanWithSequenceFuncs( player, GetDisembarkSequenceForPlayer, GetDisembarkSequenceForTitan )
+}
+
+void function PlayerDisembarksTitanWithSequenceFuncs( entity player, FirstPersonSequenceStruct functionref( entity, entity ) playerSequenceFunc, FirstPersonSequenceStruct functionref( entity, entity ) titanSequenceFunc )
+{
+ //printt( "Player disembarking with origin " + player.GetOrigin() + " and yaw " + player.GetAngles().y )
+
+ //player.SetOrigin( Vector(420.847626, -5214.960938, 173.789520) )
+ //player.SetAngles( Vector(0.000000, 179.572052, 0.000000 ) )
+
+ printt( "TitanDisembarkDebug: Player ", player.GetOrigin(), player.GetAngles(), GetMapName() )
+
+ player.EndSignal( "OnDeath" )
+ player.Signal( "DisembarkingTitan" )
+
+ //Assert( !InSolid( player ), player + " is in solid" )
+
+ local e = {}
+ e.titan <- null
+ e.PilotCleanUpDone <- false
+
+ e.startOrigin <- player.GetOrigin()
+ //e.startAngles <- player.GetAngles()
+
+ player.p.isDisembarking = true
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_DISEMBARK )
+
+ bool wasCustomDisembark = player.p.isCustomDisembark
+
+ player.ContextAction_SetBusy()
+
+ OnThreadEnd(
+ function() : ( player, e )
+ {
+ if ( IsValid( player ) )
+ {
+ PlayerEndsDisembark( player, e )
+
+ local titan = e.titan
+ if ( !IsValid( titan ) )
+ titan = null
+ }
+
+ if ( IsAlive( expect entity( e.titan ) ) )
+ {
+ if ( IsAlive( player ) )
+ {
+ thread PlayerOwnsTitanUntilSeparation( player, e.titan, 80 )
+ }
+
+ delete e.titan.s.disembarkingPlayer
+ ClearInvincible( expect entity( e.titan ) )
+
+
+ //Make Titan get up immediately if he's kneeling and can stand
+ //If he can't get up, well, then since the titan doesn't move when crouched he's going to be stuck...
+ if ( !( "embarkingPlayer" in e.titan.s ) )
+ {
+ thread TitanNPC_Think( expect entity( e.titan ) ) //titan.s.disableAutoTitanConversation is deleted inside here
+ }
+ }
+ }
+ )
+
+ bool standing = player.IsStanding()
+
+ player.SetInvulnerable()
+ player.SnapFeetToEyes()
+
+ entity titan = CreateAutoTitanForPlayer_ForTitanBecomesPilot( player )
+ DispatchSpawn( titan )
+ e.titan = titan
+
+ if ( !PlayerIsFarOffTheGround( player, [ player,titan ] ) ) //PlayerIsFarOffTheGround() necessary now for R2 because we have Titans that can jump/geo where Titans can fall down from large heights. Without check, mid-air disembarking will cause player to be teleported to the ground
+ {
+ vector ornull clampedPos = NavMesh_ClampPointForAIWithExtents( titan.GetOrigin(), titan, file.smallDisembarkFailSafeTeleportVector )
+ if ( clampedPos == null )
+ clampedPos = NavMesh_ClampPointForAIWithExtents( titan.GetOrigin(), titan, file.largeDisembarkFailSafeTeleportVector )
+
+ if ( clampedPos != null )
+ {
+ expect vector( clampedPos )
+ vector titanOrigin = titan.GetOrigin()
+
+ array<entity> ignoreEnts = []
+ ignoreEnts.append( titan )
+
+ TraceResults result = TraceHull( titanOrigin, titanOrigin, titan.GetBoundingMins(), titan.GetBoundingMaxs(), ignoreEnts, TRACE_MASK_TITANSOLID, TRACE_COLLISION_GROUP_NONE )
+
+ // expensive checks to make sure titan doesn't teleport to navmesh on other side of wall usually in invalid places
+ if ( result.startSolid ||
+ TraceLineSimple( titanOrigin + Vector( 0, 0, 128 ), clampedPos + Vector( 0, 0, 0 ), titan ) == 1.0 ||
+ TraceLineSimple( titanOrigin + Vector( 0, 0, 200 ), clampedPos + Vector( 0, 0, 0 ), titan ) == 1.0 ||
+ TraceLineSimple( titanOrigin + Vector( 0, 0, 200 ), clampedPos + Vector( 0, 0, 128 ), titan ) == 1.0 )
+ {
+ #if DEV
+ if ( file.embarkDebugPrint )
+ {
+ printt( "PlayerDisembarksTitanWithSequenceFuncs, player origin: " + player.GetOrigin()+ ", titan origin: " + titan.GetOrigin() + ", clampedPos: " + clampedPos )
+ }
+ #endif
+ titan.SetOrigin( clampedPos )
+ titan.ForceCheckGroundEntity()
+ }
+ }
+ }
+ else
+ {
+ #if DEV
+ if ( file.embarkDebugPrint )
+ {
+ printt( "PlayerIsFarOffGround() returned true, skip doing NavMesh_ClampPointForAIWithExtents checks" )
+ }
+ #endif
+ }
+
+ titan.s.disembarkingPlayer <- player
+ titan.EndSignal( "OnDeath" )
+
+ player.SetSyncedEntity( titan )
+ titan.s.disableAutoTitanConversation <- true
+
+ Assert( titan.IsTitan() )
+ Assert( IsAlive( player ) )
+ Assert( player.IsTitan() )
+ //Set player to be temporarily invulnerable. Will be removed at end of animation
+ //printt("Set player invulnerable")
+ HolsterViewModelAndDisableWeapons( player ) //Holstering weapon before becoming pilot so we don't play the holster animation as a pilot. Player as Titan won't play the holster animation either since it'll be interrupted by the disembark animation
+
+ TitanBecomesPilot( player, titan )
+
+ string titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() )
+ switch ( titanSubClass )
+ {
+ case "ogre":
+ ShowMainTitanWeapons( titan ) //JFS: Because we hide the Titan's weapons upon kneeling for ogre
+ break
+ }
+
+ titan.s.disembarkTime <- Time() // disembark debounce
+
+ // compound strings into these animations:
+ // pt_dismount_atlas_stand
+ // pt_dismount_ogre_stand
+ // pt_dismount_stryder_stand
+ // pt_dismount_atlas_crouch
+ // pt_dismount_ogre_crouch
+ // pt_dismount_stryder_crouch
+ // ptpov_dismount_atlas_stand
+ // ptpov_dismount_ogre_stand
+ // ptpov_dismount_stryder_stand
+ // ptpov_dismount_atlas_crouch
+ // ptpov_dismount_ogre_crouch
+ // ptpov_dismount_stryder_crouch
+
+ FirstPersonSequenceStruct playerSequence = playerSequenceFunc( player, titan )
+ FirstPersonSequenceStruct titanSequence = titanSequenceFunc( player, titan )
+
+ #if SERVER
+ StatusEffect_StopAll( player, eStatusEffect.lockon_detected_titan )
+ #endif
+
+ player.ForceStand()
+
+ thread FirstPersonSequence( titanSequence, titan )
+ thread FirstPersonSequence( playerSequence, player, titan )
+
+ if ( !wasCustomDisembark )
+ thread ClearParentBeforeIntersect( player, titan, playerSequence.thirdPersonAnim, e )
+
+ if ( !standing )
+ {
+ SetStanceKneel( titan.GetTitanSoul() )
+ }
+
+ //player.Anim_EnablePlanting()
+
+ #if SERVER && MP
+ PIN_AddToPlayerCountStat( player, "disembarks" )
+ PIN_PlayerAbility( player, "", "disembark", {}, 0 )
+ #endif
+
+ WaittillAnimDone( player )
+}
+
+bool function PlayerIsFarOffTheGround( entity player, array<entity> ignoreEnts )
+{
+ vector boundingMaxs = player.GetBoundingMaxs()
+ float halfHeight = boundingMaxs.z / 2.0
+
+ vector startpos = player.GetOrigin()
+ vector endpos = startpos
+ endpos.z -= halfHeight
+
+ TraceResults result = TraceHull( startpos, endpos, player.GetBoundingMins(), player.GetBoundingMaxs(), ignoreEnts, TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER )
+ //PrintTraceResults( result )
+
+ if ( result.startSolid )
+ return false
+
+ if ( result.allSolid )
+ return false
+
+ return ( result.fraction >= 1.0 )
+}
+
+FirstPersonSequenceStruct function GetDisembarkSequenceForPlayer( entity player, entity titan )
+{
+ string titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() )
+
+ string player3pAnim, player1pAnim
+ if ( player.IsStanding() )
+ {
+ player3pAnim = "pt_dismount_" + titanSubClass + "_stand"
+ player1pAnim = "ptpov_dismount_" + titanSubClass + "_stand"
+ }
+ else
+ {
+ player3pAnim = "pt_dismount_" + titanSubClass + "_crouch"
+ player1pAnim = "ptpov_dismount_" + titanSubClass + "_crouch"
+ }
+
+ if ( player.HasPassive( ePassives.PAS_FAST_EMBARK ) )
+ {
+ player1pAnim += "_fast"
+ player3pAnim += "_fast"
+ }
+
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.blendTime = 0
+ playerSequence.teleport = true
+ playerSequence.attachment = "hijack"
+ playerSequence.thirdPersonAnim = player3pAnim
+ playerSequence.firstPersonAnim = player1pAnim
+ playerSequence.useAnimatedRefAttachment = true
+
+ return playerSequence
+}
+
+FirstPersonSequenceStruct function GetDisembarkSequenceForTitan( entity player, entity titan )
+{
+ bool standing = player.IsStanding()
+
+ string titanDisembarkAnim
+ if ( standing )
+ titanDisembarkAnim = "at_dismount_stand"
+ else
+ titanDisembarkAnim = "at_dismount_crouch"
+
+ if ( player.HasPassive( ePassives.PAS_FAST_EMBARK ) )
+ titanDisembarkAnim += "_fast"
+
+ vector origin = titan.GetOrigin()
+ vector angles = titan.EyeAngles()
+ angles.z = 0
+ angles.x = 0
+
+ FirstPersonSequenceStruct titanSequence
+ titanSequence.blendTime = 0.3
+ titanSequence.thirdPersonAnim = titanDisembarkAnim
+ if ( !standing )
+ titanSequence.thirdPersonAnimIdle = "at_MP_embark_idle_blended"
+ titanSequence.gravity = true
+ titanSequence.origin = origin
+ titanSequence.angles = angles
+
+ return titanSequence
+}
+
+function DelayedSafePlayerLocationForDisembark( entity player, entity titan )
+{
+ float currentTime = Time()
+ float allowedTime = player.p.isCustomDisembark ? 10.0 : 2.0
+
+ player.EndSignal( "OnDestroy" )
+
+ while( IsPlayerDisembarking( player ) )
+ {
+ Assert( Time() - currentTime < allowedTime ) // Failsafe of waiting 2 seconds in case SOMETHING REALLY GOES WRONG.
+ if ( !IsAlive( player ) )
+ return
+
+ WaitFrame()
+ }
+
+ if ( player.ContextAction_IsActive() ) //Immediately after disembarking player might have gotten pulled into another context action e.g. embarking into evac dropship
+ return
+
+ player.ClearParent()
+ player.PlayerCone_Disable()
+ player.ViewOffsetEntity_Clear()
+ player.GetFirstPersonProxy().HideFirstPersonProxy()
+
+ vector safeStartPoint
+
+ if ( IsValid( titan ) )
+ {
+ vector titanBoundingMaxs = titan.GetBoundingMaxs()
+ float halfTitanHeight = titanBoundingMaxs.z * 0.5
+ safeStartPoint = titan.GetOrigin() + < 0, 0, halfTitanHeight > //Let the start point of PutEntityInSafeSpot be closer to where the player is when disebmarking instead of the ground origin
+ }
+ else
+ {
+ titan = null
+ safeStartPoint = player.GetOrigin()
+ }
+
+ #if DEV
+ if ( file.embarkDebugPrint )
+ printt( "DelayedSafePlayerLocationForDisembark, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + safeStartPoint )
+ #endif
+
+
+
+ if ( !PutEntityInSafeSpot( player, titan, null, safeStartPoint, player.GetOrigin() ) )
+ player.SetOrigin( safeStartPoint )
+
+ #if DEV
+ if ( file.embarkDebugPrint )
+ printt( "DelayedSafePlayerLocationForDisembark, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + safeStartPoint )
+ #endif
+
+}
+
+function ClearParentBeforeIntersect( entity player, entity titan, anim, e )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnAnimationDone" )
+ player.EndSignal( "OnAnimationInterrupted" )
+ //local mins = player.GetBoundingMins()
+ //local maxs = player.GetBoundingMaxs()
+
+ OnThreadEnd(
+ function() : ( player, e )
+ {
+ if ( IsValid( player ) )
+ thread DelayedSafePlayerLocationForDisembark( player, expect entity( e.titan ) )
+ }
+ )
+
+ wait 0.25
+
+ vector lastOrigin = player.GetOrigin()
+ for ( ;; )
+ {
+ if ( EntityInSolid( player, titan, 24 ) )
+ break
+
+ lastOrigin = player.GetOrigin()
+ WaitFrame()
+ }
+
+ player.SetOrigin( lastOrigin )
+}
+
+function LockedViewCone( human )
+{
+ human.PlayerCone_FromAnim()
+ human.PlayerCone_SetMinYaw( 0 )
+ human.PlayerCone_SetMaxYaw( 0 )
+ human.PlayerCone_SetMinPitch( 0 )
+ human.PlayerCone_SetMaxPitch( 0 )
+}
+
+
+function PlayerOwnsTitanUntilSeparation( player, titan, dist )
+{
+ titan.SetOwner( player )
+
+ player.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function () : ( player, titan )
+ {
+ if ( !IsValid( titan ) )
+ return
+
+ titan.SetOwner( null )
+ }
+ )
+
+ // wait until player moves away
+ local distSqr = dist * dist
+ for ( ;; )
+ {
+ if ( DistanceSqr( titan.GetOrigin(), player.GetOrigin() ) > distSqr )
+ break
+
+ wait 0.5
+ }
+}
+
+function PlayerEndsDisembark( player, e )
+{
+ thread PlayerEndsDisembarkThread( player, e )
+}
+
+function PlayerEndsDisembarkThread( player, e )
+{
+ expect entity( player )
+ player.EndSignal( "OnDestroy" )
+
+ if ( e.PilotCleanUpDone )
+ return
+
+ e.PilotCleanUpDone = true
+ bool wasCustomDisembark = player.p.isCustomDisembark
+
+ wait 0.1
+
+ ClearPlayerAnimViewEntity( player )
+ if ( player.ContextAction_IsBusy() )
+ player.ContextAction_ClearBusy()
+
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() & (~CE_FLAG_DISEMBARK) )
+
+ player.Show()
+ TitanEmbark_PlayerCleanup( player )
+ player.p.isDisembarking = false
+
+ //// give a player a boost out the door
+ //
+ //
+ if ( IsAlive( player ) && !wasCustomDisembark )
+ {
+ local angles = player.EyeAngles()
+ if ( IsValid( e.titan ) )
+ angles = e.titan.GetAngles()
+
+ angles.x = 0
+ angles.z = 0
+ local forward = AnglesToForward( angles )
+ local up = AnglesToUp( angles )
+ local vel = forward * 250 + up * 200
+ player.SetVelocity( vel )
+ //DebugDrawLine( player.GetOrigin(), player.GetOrigin() + forward * 500, 255, 0, 0, true, 5.0 )
+ }
+}
+
+function IsPlayerEmbarking( player )
+{
+ expect entity ( player )
+ return player.p.isEmbarking
+}
+#endif // SERVER
+
+function IsPlayerDisembarking( player )
+{
+ expect entity ( player )
+ return player.p.isDisembarking
+}
+
+function PlayerCanEmbarkIntoTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function
+{
+ if ( player.IsNoclipping() )
+ return false
+
+ if ( !TitanIsCurrentlyEmbarkableForPlayer( player, titan ) )
+ return false
+
+ return FindBestEmbark( player, titan ) != null
+}
+
+bool function TitanIsCurrentlyEmbarkableForPlayer( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function
+{
+ if ( !CanEmbark( player ) )
+ return false
+
+ if ( player.Anim_IsActive() )
+ return false
+
+ if ( !player.IsHuman() )
+ return false
+
+ if ( player.ContextAction_IsActive() )
+ return false
+
+ if ( !titan.IsEntAlive() )
+ return false
+
+ if ( titan.ContextAction_IsActive() )
+ return false
+
+ if ( !titan.IsInterruptable() )
+ return false
+
+ if ( IsValid( titan.GetParent() ) )
+ return false
+
+ if ( !HasSoul( titan ) )
+ return false
+
+ local soul = titan.GetTitanSoul()
+
+ if ( GetDoomedState( titan ) && !PROTO_AlternateDoomedState() )
+ return false
+
+ if ( soul.IsEjecting() )
+ return false
+
+ #if SERVER
+ // client doesn't know these things
+ if ( IsPlayerEmbarking( player ) )
+ return false
+
+ if ( IsPlayerDisembarking( player ) )
+ return false
+ #endif
+
+ if ( "disembarkTime" in titan.s )
+ {
+ if ( Time() - titan.s.disembarkTime < 1.65 )
+ return false
+ }
+
+ return true
+}
+
+function FindEmbarkActionForCriteria( criteria )
+{
+ local embarkAction
+ foreach ( option in level.titanEmbarkActions )
+ {
+ bool failed = false
+ foreach ( key, value in criteria )
+ {
+ if ( value != option[key] )
+ {
+ failed = true
+ break
+ }
+ }
+
+ if ( !failed )
+ {
+ embarkAction = option
+ break
+ }
+ }
+
+ return embarkAction
+
+}
+
+function GetRandomEmbarkAction()
+{
+ return level.titanEmbarkActions[ RandomInt( level.titanEmbarkActions.len() ) ]
+}
+
+function FindBestEmbark( entity player, entity titan, bool doDistCheck = true )
+{
+// if ( IsServer() )
+// printt( "finding best embark for " + player + " to " + titan )
+ vector playerPos = player.GetOrigin()
+ vector titanPos = titan.GetOrigin()
+
+ vector relTitanToPlayerDir = CalculateTitanToPlayerDir( titan, player )
+
+ local bestAction = null
+ float bestDot = -2
+ float dist = 0
+
+ if ( doDistCheck )
+ {
+ dist = Distance( playerPos, titanPos )
+ if ( dist > level.titanEmbarkFarthestDistance )
+ return null
+ }
+ //if ( IsServer() )
+ // printt( "dist: " + dist )
+
+ for ( int i = 0; i < 3; i++ )
+ {
+ bestAction = GetBestEmbarkAction( i, player, titan, dist, relTitanToPlayerDir )
+ if ( bestAction != null )
+ break
+ }
+
+ if ( bestAction == null )
+ return null
+
+ return GenerateEmbarkActionTable( player, titan, bestAction, relTitanToPlayerDir )
+}
+
+vector function CalculateTitanToPlayerDir( entity titan, entity player )
+{
+ vector playerPos = player.GetOrigin()
+ vector titanPos = titan.GetOrigin()
+
+ vector absTitanToPlayerDir
+ if ( playerPos == titanPos )
+ {
+ absTitanToPlayerDir = Vector( 1, 0, 0 )
+ }
+ else
+ {
+ vector angles = player.EyeAngles()
+ vector forward = AnglesToForward( angles )
+
+ absTitanToPlayerDir = ( playerPos - titanPos )
+
+
+ absTitanToPlayerDir.Norm()
+
+// not needed cause we can't get in without a legal use
+// // is the target in my fov?
+// if ( forward.Dot( absTitanToPlayerDir * -1 ) < 0.77 )
+// return null
+ }
+
+ vector titanAngles = titan.GetAngles()
+ titanAngles.x = 0
+ if ( titan.GetTitanSoul().GetStance() >= STANCE_STANDING )
+ titanAngles = AnglesCompose( titanAngles, Vector( 0, -30, 0 ) )
+
+ vector relTitanToPlayerDir = CalcRelativeVector( titanAngles, absTitanToPlayerDir )
+ return relTitanToPlayerDir
+}
+
+function GenerateEmbarkActionTable( entity player, entity titan, bestAction, var relTitanToPlayerDir = null )
+{
+ bool useFastAnims = player.IsPlayer() && player.HasPassive( ePassives.PAS_FAST_EMBARK )
+
+ if ( relTitanToPlayerDir == null )
+ {
+ relTitanToPlayerDir = CalculateTitanToPlayerDir( titan, player )
+ expect vector( relTitanToPlayerDir )
+ }
+ else
+ {
+ expect vector( relTitanToPlayerDir )
+ }
+
+ local Table = {}
+ Table.action <- bestAction
+
+ if ( "animSet" in bestAction )
+ {
+ Table.animSet <- bestAction.animSet
+ Table.audioSet <- bestAction.audioSet
+ }
+ else
+ {
+ local bestAnimSet
+ local bestAudioSet
+ local bestDot = -2
+ Assert( "animSets" in bestAction, "Table has no animSet and no animSets!" )
+ foreach ( animSet in bestAction.animSets )
+ {
+ local dot = relTitanToPlayerDir.Dot( animSet.direction )
+
+ if ( dot > bestDot )
+ {
+ bestAnimSet = animSet
+ bestAudioSet = animSet.audioSet
+ bestDot = dot
+ }
+ }
+
+ Table.animSet <- bestAnimSet
+ Table.audioSet <- bestAudioSet
+ }
+
+ if ( useFastAnims )
+ {
+ Table.animSet = clone Table.animSet
+
+ foreach ( string idx, item in Table.animSet )
+ {
+ if ( IsString( item ) )
+ Table.animSet[ idx ] = item + "_fast"
+ }
+ }
+
+ return Table
+}
+
+function GetBestEmbarkAction( int priority, entity player, entity titan, float dist, vector relTitanToPlayerDir )
+{
+ local bestAction = null
+ local bestDot = -2
+
+ foreach ( action in level.titanEmbarkActions )
+ {
+ if ( action.priority != priority )
+ continue
+
+ if ( dist > action.distance )
+ {
+ //if ( IsServer() )
+ //printt( "Failed: Action " + action.embark + " had dist " + action.distance + " vs actual dist " + dist )
+ continue
+ }
+
+ if ( action.lungeCheck )
+ {
+ if ( player.IsNPC() )
+ continue
+
+ if ( player.Lunge_IsActive() != action.lungeCheck )
+ continue
+ }
+
+ local dot = relTitanToPlayerDir.Dot( action.direction )
+
+ if ( dot < action.minDot )
+ {
+ //if ( IsServer() )
+ //printt( "Failed: Action " + action.embark + " had dot " + dot )
+ continue
+ }
+
+ if ( expect bool( action.titanCanStandRequired ) && !TitanCanStand( titan ) )
+ {
+ //if ( IsServer() )
+ //printt( "Failed: Action " + action.embark + " cant stand" )
+ continue
+ }
+
+ if ( dot > bestDot )
+ {
+ //if ( IsServer() )
+ //printt( "Action " + action.embark + " had dot " + dot )
+ bestAction = action
+ bestDot = dot
+ }
+ }
+
+ return bestAction
+}
+
+function FindBestEmbarkForNpcAnim( entity npc, entity titan )
+{
+ bool doDistCheck = false
+ return FindBestEmbark( npc, titan, doDistCheck )
+}
+
+
+
+
+bool function TitanCanStand( entity titan )
+{
+ #if SERVER
+ vector maxs = titan.GetBoundingMaxs()
+ vector mins = titan.GetBoundingMins()
+
+ vector start = titan.GetOrigin()
+ vector end = titan.GetOrigin()
+ entity soul = titan.GetTitanSoul()
+ entity ignoreEnt = null
+
+ if ( IsValid( soul.soul.bubbleShield ) )
+ ignoreEnt = soul.soul.bubbleShield
+ int mask = titan.GetPhysicsSolidMask()
+ //printt( "mask has " + MaskTester( mask ) )
+ TraceResults result = TraceHull( start, end, mins, maxs, ignoreEnt, mask, TRACE_COLLISION_GROUP_NONE )
+ //printt( "start " + start + " end " + end )
+
+ //DebugDrawLine( start, result.endPos, 0, 255, 0, true, 5.0 )
+ //DebugDrawLine( result.endPos, end, 255, 0, 0, true, 5.0 )
+
+ bool canStand = result.fraction >= 1.0
+ titan.SetCanStand( canStand )
+ return canStand
+ #else
+ return titan.GetCanStand() != 0
+ #endif
+}
+
+bool function PlayerCanEmbarkTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function
+{
+ PerfStart( PerfIndexClient.PlayerCanEmbarkTitan1 )
+ if ( !TitanIsCurrentlyEmbarkableForPlayer( player, titan ) )
+ {
+ PerfEnd( PerfIndexClient.PlayerCanEmbarkTitan1 )
+ return false
+ }
+ PerfEnd( PerfIndexClient.PlayerCanEmbarkTitan1 )
+
+ PerfStart( PerfIndexClient.FindBestEmbark )
+ bool res = FindBestEmbark( player, titan ) != null
+ PerfEnd( PerfIndexClient.FindBestEmbark )
+
+ return res
+}
+
+bool function PlayerCanImmediatelyEmbarkTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function
+{
+ if ( "embarkingPlayer" in titan.s )
+ return false
+
+ if ( player.IsNoclipping() )
+ return false
+
+ if ( !IsAlive( player ) || !IsAlive( titan ) )
+ return false
+
+ return FindBestEmbark( player, titan ) != null
+}
+
+#if SERVER
+function PlayerLungesToEmbark( entity player, entity ent )
+{
+ Assert( TitanIsCurrentlyEmbarkableForPlayer( player, ent ) )
+
+ if ( PlayerCanImmediatelyEmbarkTitan( player, ent ) )
+ {
+ table embarkDirection = expect table( FindBestEmbark( player, ent ) )
+ thread PlayerEmbarksTitan( player, ent, embarkDirection )
+ return
+ }
+
+ if ( player.IsNoclipping() )
+ return
+
+ // already lunging
+ if ( player.Lunge_IsActive() )
+ return
+
+ if ( ShouldStopLunging( player, ent ) )
+ return
+
+ player.Lunge_SetTargetEntity( ent, false )
+ player.Lunge_SetSmoothTime( 3.0 )
+}
+
+void function TitanBecomesPilot_UpdateRodeoRiderHud( entity playerTitan, entity npc_titan )
+{
+ entity rodeoPilot = GetRodeoPilot( npc_titan )
+ if ( !IsValid( rodeoPilot ) )
+ return
+
+ Remote_CallFunction_Replay( rodeoPilot, "ServerCallback_UpdateRodeoRiderHud" )
+}
+
+void function PilotBecomesTitan_UpdateRodeoRiderHud( entity playerTitan, entity npc_titan )
+{
+ entity rodeoPilot = GetRodeoPilot( playerTitan )
+ if ( !IsValid( rodeoPilot ) )
+ return
+
+ Remote_CallFunction_Replay( rodeoPilot, "ServerCallback_UpdateRodeoRiderHud" )
+
+}
+
+void function SetSmallDisembarkFailSafeTeleportVector( vector value ) //TODO: Re-examine this for next game, probably should have different values for SP versus MP
+{
+ file.smallDisembarkFailSafeTeleportVector = value
+}
+
+void function SetLargeDisembarkFailSafeTeleportVector( vector value ) //TODO: Re-examine this for next game, probably should have different values for SP versus MP
+{
+ file.largeDisembarkFailSafeTeleportVector = value
+}
+
+#endif // SERVER
+
+#if DEV
+void function SetEmbarkDebugPrint( bool value )
+{
+ file.embarkDebugPrint = value
+}
+#endif \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/weapons/mp_weapon_peacekraber.nut b/Northstar.Custom/scripts/vscripts/weapons/mp_weapon_peacekraber.nut
new file mode 100644
index 000000000..a9da541f9
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/weapons/mp_weapon_peacekraber.nut
@@ -0,0 +1,162 @@
+untyped
+
+// created by JustANormalUser#0001 on discord
+
+global function OnWeaponPrimaryAttack_peacekraber;
+global function OnWeaponDeactivate_peacekraber
+global function OnWeaponActivate_peacekraber
+
+#if SERVER
+global function OnWeaponNpcPrimaryAttack_peacekraber
+#endif // #if SERVER
+
+
+const PEACEKRABER_MAX_BOLTS = 11 // this is the code limit for bolts per frame... do not increase.
+bool isWeaponActive = false;
+entity clientWeapon = null;
+
+struct
+{
+ float[2][PEACEKRABER_MAX_BOLTS] boltOffsets = [
+ [0.0, 0.0], // center
+ [0.0, 1.0], // top
+ [-0.683, 0.327],
+ [0.683, 0.327],
+ [-0.636, -0.683],
+ [0.636, -0.683],
+ [0.0, 0.5],
+ [-0.342, 0.174],
+ [0.342, 0.174],
+ [-0.318, -0.342],
+ [0.318, -0.342],
+ ]
+
+ int maxAmmo
+ float ammoRegenTime
+} file
+// "OnWeaponActivate" "OnWeaponActivate_peacekraber"
+// "OnWeaponDeactivate" "OnWeaponDeactivate_peacekraber"
+void function OnWeaponActivate_peacekraber (entity weapon) {
+ #if CLIENT
+ if (!weapon.GetWeaponOwner().IsPlayer() || weapon.GetWeaponOwner() != GetLocalViewPlayer()) return;
+ isWeaponActive = true;
+ clientWeapon = weapon;
+ thread CrosshairCycle();
+ #endif
+}
+
+void function OnWeaponDeactivate_peacekraber (entity weapon) {
+ #if CLIENT
+ if (!weapon.GetWeaponOwner().IsPlayer() || weapon.GetWeaponOwner() != GetLocalViewPlayer()) return;
+ isWeaponActive = false;
+ #endif
+}
+#if CLIENT
+void function CrosshairCycle() {
+ var rui = RuiCreate( $"ui/crosshair_shotgun.rpak", clGlobal.topoCockpitHudPermanent, RUI_DRAW_COCKPIT, 0 )
+ RuiSetFloat(rui, "adjustedSpread", 0.1)
+ array<int> spreadFrac = [1, 0.65, 0.45, 0.2]
+ array<vector> colors = [<1, 1, 1>, <0.666, 1, 1>, <0.333, 1, 1>, <0, 1, 1>]
+ int chargeLevel;
+ float chargeFrac;
+ while (isWeaponActive) {
+ WaitFrame()
+ chargeLevel = clientWeapon.GetWeaponChargeLevel();
+ chargeFrac = clientWeapon.GetWeaponChargeFraction();
+ RuiSetFloat3(rui, "teamColor", colors[chargeLevel]);
+ switch (chargeLevel) {
+ case 0:
+ if (chargeFrac > 0.266) {
+ RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.266, 0.333, 0.1, 0.065))
+ }
+ else RuiSetFloat(rui, "adjustedSpread", 0.1)
+ break;
+ case 1:
+ if (chargeFrac > 0.6) {
+ RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.6, 0.666, 0.065, 0.045))
+ }
+ else RuiSetFloat(rui, "adjustedSpread", 0.065)
+ break;
+ case 2:
+ if (chargeFrac > 0.933) {
+ RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.933, 1, 0.045, 0.02))
+ }
+ else RuiSetFloat(rui, "adjustedSpread", 0.045)
+ break;
+ case 3:
+ RuiSetFloat(rui, "adjustedSpread", 0.025)
+ break;
+ default:
+ break;
+ }
+ }
+
+ RuiDestroy(rui);
+ clientWeapon = null;
+}
+#endif
+
+var function OnWeaponPrimaryAttack_peacekraber( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if CLIENT
+ weapon.EmitWeaponSound( "Weapon_Titan_Sniper_LevelTick_2" )
+ #endif
+
+ return FireWeaponPlayerAndNPC( attackParams, true, weapon )
+}
+
+#if SERVER
+var function OnWeaponNpcPrimaryAttack_peacekraber( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ return FireWeaponPlayerAndNPC( attackParams, false, weapon )
+}
+#endif // #if SERVER
+
+function FireWeaponPlayerAndNPC( WeaponPrimaryAttackParams attackParams, bool playerFired, entity weapon )
+{
+ entity owner = weapon.GetWeaponOwner()
+ bool shouldCreateProjectile = false
+ if ( IsServer() || weapon.ShouldPredictProjectiles() )
+ shouldCreateProjectile = true
+ #if CLIENT
+ if ( !playerFired )
+ shouldCreateProjectile = false
+ #endif
+
+ vector attackAngles = VectorToAngles( attackParams.dir )
+ vector baseUpVec = AnglesToUp( attackAngles )
+ vector baseRightVec = AnglesToRight( attackAngles )
+
+ if ( shouldCreateProjectile )
+ {
+ weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 )
+ array<int> spreadFrac = [1, 0.65, 0.45, 0.2]
+
+ for ( int index = 0; index < PEACEKRABER_MAX_BOLTS; index++ )
+ {
+ vector upVec = baseUpVec * file.boltOffsets[index][1] * 0.05 * spreadFrac[weapon.GetWeaponChargeLevel()]
+ vector rightVec = baseRightVec * file.boltOffsets[index][0] * 0.05 * spreadFrac[weapon.GetWeaponChargeLevel()]
+
+ vector attackDir = attackParams.dir + upVec + rightVec
+ float projectileSpeed = 2800
+
+ if ( weapon.GetWeaponClassName() == "mp_weapon_peacekraber" )
+ {
+ projectileSpeed = 6400
+ }
+
+ entity bolt = weapon.FireWeaponBolt( attackParams.pos, attackDir, projectileSpeed, damageTypes.largeCaliber | DF_SHOTGUN, damageTypes.largeCaliber | DF_SHOTGUN, playerFired, index )
+ if ( bolt )
+ {
+ bolt.kv.gravity = 0.4 // 0.09
+
+ if ( weapon.GetWeaponClassName() == "mp_weapon_peacekraber" )
+ bolt.SetProjectileLifetime( RandomFloatRange( 1.0, 1.3 ) )
+ else
+ bolt.SetProjectileLifetime( RandomFloatRange( 0.50, 0.65 ) )
+ }
+ }
+ }
+
+ return 1
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/weapons/mp_weapon_toolgun.nut b/Northstar.Custom/scripts/vscripts/weapons/mp_weapon_toolgun.nut
new file mode 100644
index 000000000..94bd7429b
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/weapons/mp_weapon_toolgun.nut
@@ -0,0 +1,39 @@
+untyped
+global function OnWeaponActivate_weapon_toolgun
+global function OnWeaponDeactivate_weapon_toolgun
+global function OnWeaponPrimaryAttack_weapon_toolgun
+global function OnWeaponStartZoomIn_weapon_toolgun
+global function OnWeaponStartZoomOut_weapon_toolgun
+#if SERVER
+global function OnWeaponNpcPrimaryAttack_weapon_toolgun
+#endif
+
+void function OnWeaponActivate_weapon_toolgun( entity weapon )
+{
+ CallToolOnEquipped( weapon.GetOwner(), weapon )
+}
+
+void function OnWeaponDeactivate_weapon_toolgun( entity weapon )
+{
+ CallToolOnUnequipped( weapon.GetOwner(), weapon )
+}
+
+var function OnWeaponPrimaryAttack_weapon_toolgun( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ CallToolOnFired( weapon.GetOwner(), weapon, attackParams )
+}
+
+void function OnWeaponStartZoomIn_weapon_toolgun( entity weapon )
+{
+ CallToolOnAds( weapon.GetOwner(), weapon )
+}
+
+void function OnWeaponStartZoomOut_weapon_toolgun( entity weapon )
+{
+ CallToolOnUnAds( weapon.GetOwner(), weapon )
+}
+
+var function OnWeaponNpcPrimaryAttack_weapon_toolgun( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ // do nothing for now, maybe make it launch nukes or something later that could be funny
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut b/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut
new file mode 100644
index 000000000..512c538c9
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut
@@ -0,0 +1,30 @@
+global function ToolgunToolCreateExplosion_Init
+
+void function ToolgunToolCreateExplosion_Init()
+{
+ AddCallback_OnToolgunToolsInit( CreateToolgunToolCreateExplosion )
+}
+
+void function CreateToolgunToolCreateExplosion()
+{
+ ToolgunTool createExplosionTool
+ createExplosionTool.toolName = "Create explosion"
+ createExplosionTool.toolDescription = "Creates an explosion"
+
+ createExplosionTool.onFired = ToolgunToolCreateExplosion_Fire
+
+ RegisterTool( createExplosionTool )
+}
+
+void function ToolgunToolCreateExplosion_Fire( entity player, entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if SERVER
+ int dist = 55555 // should hit edge of map at all times ideally
+ vector forward = AnglesToForward( player.EyeAngles() )
+
+ // raycast to explosion position
+ TraceResults trace = TraceLine( player.EyePosition(), player.EyePosition() + ( dist * forward ), null, TRACE_MASK_NPCSOLID )
+ // make explosion
+ Explosion( trace.endPos, player, player, 90, 90, 100, 100, 0, trace.endPos, 5000, damageTypes.explosive, eDamageSourceId.burn, "exp_small" )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut b/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut
new file mode 100644
index 000000000..d6975c6de
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut
@@ -0,0 +1,24 @@
+global function ToolgunToolThrowEntity_Init
+
+void function ToolgunToolThrowEntity_Init()
+{
+ AddCallback_OnToolgunToolsInit( CreateToolgunToolThrow )
+}
+
+void function CreateToolgunToolThrow()
+{
+ ToolgunTool throwTool
+ throwTool.toolName = "Throw entity"
+ throwTool.toolDescription = "Spawns and throws the currently selected entity type"
+
+ throwTool.onFired = ToolgunToolThrow_OnFired
+
+ RegisterTool( throwTool )
+}
+
+void function ToolgunToolThrow_OnFired( entity player, entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if SERVER
+ ClientCommand( player, "ent_throw npc_frag_drone" )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut b/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut
new file mode 100644
index 000000000..4d7e9d899
--- /dev/null
+++ b/Northstar.Custom/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut
@@ -0,0 +1,196 @@
+untyped
+global function ToolgunTools_Init
+global function AddCallback_OnToolgunToolsInit
+
+global function RegisterTool
+global function CallToolOnEquipped
+global function CallToolOnUnequipped
+global function CallToolOnFired
+global function CallToolOnAds
+global function CallToolOnUnAds
+
+#if SERVER
+global function SetToolgunAmmoCount
+global function SetToolgunProscreen
+#endif // #if SERVER
+
+global struct ToolgunTool
+{
+ string toolName = ""
+ string toolDescription = ""
+
+ void functionref( entity, entity ) onEquipped
+ void functionref( entity, entity ) onUnequipped
+ void functionref( entity, entity, WeaponPrimaryAttackParams ) onFired
+ void functionref( entity, entity ) onAds
+ void functionref( entity, entity ) onUnAds
+}
+
+struct ToolgunPlayerSettings
+{
+ int selectedToolIndex
+ int ammoCount
+ int proscreenNumber
+}
+
+// doing preprocessor defs inside the struct seemed to cause compiler errors so we define them separately
+#if CLIENT
+struct {
+ array<void functionref()> onToolgunToolsInitCallbacks
+
+ array<ToolgunTool> tools
+ ToolgunPlayerSettings clientPlayerSettings
+} file
+#endif // #if CLIENT
+
+#if SERVER
+struct {
+ array<void functionref()> onToolgunToolsInitCallbacks
+
+ array<ToolgunTool> tools
+ // serverside playersettings
+ table<int, ToolgunPlayerSettings> playerSettings
+} file
+#endif // #if SERVER
+
+void function AddCallback_OnToolgunToolsInit( void functionref() callback )
+{
+ file.onToolgunToolsInitCallbacks.append( callback )
+}
+
+void function ToolgunTools_Init()
+{
+ //#if SERVER
+ //AddCallback_OnClientConnecting( InitialiseToolgunSettings )
+ //AddCallback_OnClientDisconnected( DestroyToolgunSettings )
+ //#endif // #if SERVER
+ //
+ //// need this threaded so we can wait a frame
+ //thread ToolgunTools_InitThreaded()
+}
+
+void function ToolgunTools_InitThreaded()
+{
+ // wait a frame for tools to all init and add their callbacks
+ WaitFrame()
+
+ // call callbacks
+ foreach ( void functionref() callback in file.onToolgunToolsInitCallbacks )
+ callback()
+}
+
+void function RegisterTool( ToolgunTool toolStruct )
+{
+ file.tools.append( toolStruct )
+}
+
+void function CallToolOnEquipped( entity player, entity weapon )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onEquipped != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onEquipped( player, weapon )
+ #endif // #if CLIENT
+
+ #if SERVER
+ // set ammo and proscreen numbers when equipped
+ weapon.SetProScreenIntValForIndex( PRO_SCREEN_INT_LIFETIME_KILLS, file.playerSettings[ player.GetPlayerIndex() ].proscreenNumber )
+ weapon.SetWeaponPrimaryClipCount( file.playerSettings[ player.GetPlayerIndex() ].ammoCount )
+
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onEquipped != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onEquipped( player, weapon )
+ #endif // #if SERVER
+}
+
+void function CallToolOnUnequipped( entity player, entity weapon )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnequipped != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnequipped( player, weapon )
+ #endif // #if CLIENT
+
+ #if SERVER
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon )
+ #endif // #if SERVER
+}
+
+void function CallToolOnFired( entity player, entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onFired != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onFired( player, weapon, attackParams )
+ #endif // #if CLIENT
+
+ #if SERVER
+ // ammocount needs to be +1 because we lose 1 ammo immediately after this function is run
+ weapon.SetWeaponPrimaryClipCount( file.playerSettings[ player.GetPlayerIndex() ].ammoCount + 1 )
+
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onFired != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onFired( player, weapon, attackParams )
+ #endif // #if SERVER
+}
+
+void function CallToolOnAds( entity player, entity weapon )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onAds != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onAds( player, weapon )
+ #endif // #if CLIENT
+
+ #if SERVER
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon )
+ #endif // #if SERVER
+}
+
+void function CallToolOnUnAds( entity player, entity weapon )
+{
+ #if CLIENT
+ if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnAds != null )
+ file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnAds( player, weapon )
+ #endif // #if CLIENT
+
+ #if SERVER
+ if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null )
+ file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon )
+ #endif // #if SERVER
+}
+
+#if SERVER
+void function InitialiseToolgunSettings( entity player )
+{
+ print( "initialising toolgun settings for player " + player )
+
+ ToolgunPlayerSettings playerSettings
+ playerSettings.selectedToolIndex = 0
+
+ file.playerSettings[ player.GetPlayerIndex() ] <- playerSettings
+}
+
+void function DestroyToolgunSettings( entity player )
+{
+ delete file.playerSettings[ player.GetPlayerIndex() ]
+}
+
+void function SetToolgunAmmoCount( entity player, int ammoCount )
+{
+ entity currentWeapon = player.GetActiveWeapon()
+ if ( ammoCount > currentWeapon.GetWeaponPrimaryClipCountMax() ) // setting clipcount to over max clipcount crashes so we need to prevent that
+ return
+
+ ToolgunPlayerSettings playerSettings = file.playerSettings[ player.GetPlayerIndex() ]
+ playerSettings.ammoCount = ammoCount
+
+ if ( currentWeapon.GetWeaponClassName() == "mp_weapon_toolgun" ) // set ammocount immediately if we've got toolgun equipped already
+ currentWeapon.SetWeaponPrimaryClipCount( ammoCount )
+}
+
+void function SetToolgunProscreen( entity player, int proscreenNumber )
+{
+ ToolgunPlayerSettings playerSettings = file.playerSettings[ player.GetPlayerIndex() ]
+ playerSettings.proscreenNumber = proscreenNumber
+
+ if ( player.GetActiveWeapon().GetWeaponClassName() == "mp_weapon_toolgun" ) // set proscreen number immediately if we've got toolgun equipped already
+ player.GetActiveWeapon().SetProScreenIntValForIndex( PRO_SCREEN_INT_LIFETIME_KILLS, proscreenNumber )
+}
+#endif // #if SERVER \ No newline at end of file
diff --git a/Northstar.Custom/scripts/weapons/melee_pilot_emptyhanded.txt b/Northstar.Custom/scripts/weapons/melee_pilot_emptyhanded.txt
new file mode 100644
index 000000000..032867af8
--- /dev/null
+++ b/Northstar.Custom/scripts/weapons/melee_pilot_emptyhanded.txt
@@ -0,0 +1,149 @@
+WeaponData
+{
+ // General
+ "printname" "Melee"
+ "shortprintname" "Melee"
+ "description" "Description needed"
+ "longdesc" "Description needed"
+
+ "menu_icon" "rui/hud/common/melee_icon"
+ "hud_icon" "rui/hud/common/melee_icon"
+
+ "weaponClass" "human"
+ "weaponType" "melee"
+ "body_type" "battle_rifle"
+ "fire_mode" "offhand_melee"
+ "never_drop" "1"
+
+ // Models
+ "viewmodel" "models/weapons/empty_handed/ptpov_emptyhand.mdl"
+ "playermodel" "models/weapons/empty_handed/w_empty_handed_human.mdl"
+
+ // Melee
+ "melee_can_hit_humansized" "1"
+ "melee_can_hit_titans" "0"
+ "melee_raise_recovery_animtime_normal" "0.75"
+ "melee_raise_recovery_animtime_quick" "0.4"
+ "melee_range" "60"
+ "melee_rumble_on_hit" "pilot_melee_hit"
+ "melee_rumble_on_hit_partial" "pilot_melee_hit_partial"
+ "melee_freezelook_on_hit" "0.25"
+ //"melee_sound_attack_1p" "Player_Melee_Backhand_1P"
+ "melee_sound_attack_3p" "player_melee_kick_3p"
+ "melee_anim_3p" "ACT_MP_MELEE_KNIFE_FIRST"
+ "damage_flags" "DF_MELEE | DF_KNOCK_BACK"
+ "impact_effect_table" "melee_human"
+ "impulse_force" "35000"
+ "offhand_keep_primary_in_hand" "1"
+
+ "zoom_effects" "0"
+
+ MP_BASE
+ {
+ "melee_lunge_target_range" "110"
+ "melee_lunge_target_angle" "30"
+ "melee_damage" "100"
+ "melee_damage_heavyarmor" "0"
+ "melee_attack_animtime" "0"
+ "melee_lunge_time" "0.3"
+ "melee_anim_1p_number" "2"
+ }
+
+ SP_BASE
+ {
+ "melee_lunge_target_range" "130"
+ "melee_lunge_target_angle" "40"
+ "melee_damage" "125"
+ "melee_damage_heavyarmor" "125"
+ "melee_attack_animtime" "0.0"
+ "melee_lunge_time" "0.2"
+ "melee_anim_1p_number" "1" // ACT_VM_MELEE_ATTACK1, 2, or 3
+ }
+
+ //
+
+ "ammo_suck_behavior" "melee_weapons"
+ "viewkick_spring" "melee"
+
+ "viewkick_pitch_base" "-1.75"
+ "viewkick_pitch_random" "0.75"
+ "viewkick_pitch_softScale" "0.3"
+ "viewkick_pitch_hardScale" "5.5"
+
+ "viewkick_yaw_base" "1.0"
+ "viewkick_yaw_random" "0.38"
+ "viewkick_yaw_softScale" "0.38"
+ "viewkick_yaw_hardScale" "15.5"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.6"
+ "viewkick_roll_randomMax" "0.8"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "20.75"
+
+ //
+ "damage_rodeo" "100"
+ "aimassist_disable_hipfire" "1"
+ "aimassist_disable_ads" "1"
+
+ // Bob
+ //"bob_cycle_time" "0.45"
+ //"bob_vert_dist" "0.075"
+ //"bob_horz_dist" "0.05"
+ //"bob_max_speed" "150"
+ //"bob_pitch" "0.75"
+ //"bob_yaw" "1"
+ //"bob_roll" "-0.75"
+
+ // Sway
+ //"sway_rotate_attach" "SWAY_ROTATE"
+ //"sway_min_x" "-0.3"
+ //"sway_min_y" "-0.5"
+ //"sway_min_z" "-0.5"
+ //"sway_max_x" "0.3"
+ //"sway_max_y" "0.5"
+ //"sway_max_z" "0.1"
+ //"sway_min_pitch" "-3"
+ //"sway_min_yaw" "-3.5"
+ //"sway_min_roll" "-2"
+ //"sway_max_pitch" "3"
+ //"sway_max_yaw" "3.5"
+ //"sway_max_roll" "3"
+ //"sway_translate_gain" "10"
+ //"sway_rotate_gain" "12"
+ //"sway_move_forward_translate_x" "0"
+ //"sway_move_forward_translate_z" "-0.5"
+ //"sway_move_back_translate_x" "-2"
+ //"sway_move_back_translate_z" "-1"
+ //"sway_move_left_translate_y" "-1"
+ //"sway_move_left_translate_z" "-0.5"
+ //"sway_move_left_rotate_roll" "-2"
+ //"sway_move_right_translate_y" "1"
+ //"sway_move_right_translate_z" "-0.5"
+ //"sway_move_right_rotate_roll" "4"
+ //"sway_move_up_translate_z" "-1"
+ //"sway_move_down_translate_z" "1"
+ //"sway_turn_left_rotate_yaw" "-1"
+ //"sway_turn_right_rotate_yaw" "1"
+ //"sway_turn_up_rotate_pitch" "1"
+ //"sway_turn_down_rotate_pitch" "-1"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "deployfirst_time" "1.25"
+ "sprintcycle_time" ".55"
+
+ Mods
+ {
+ rocket_arena
+ {
+ "melee_damage" "33"
+ }
+
+ // mod for testing low damage, high knockback "shove" melee rebalance
+ test_push
+ {
+ "melee_damage" "5"
+ "impulse_force" "150000"
+ }
+ }
+}
diff --git a/Northstar.Custom/scripts/weapons/melee_pilot_kunai.txt b/Northstar.Custom/scripts/weapons/melee_pilot_kunai.txt
new file mode 100644
index 000000000..e8b7c181a
--- /dev/null
+++ b/Northstar.Custom/scripts/weapons/melee_pilot_kunai.txt
@@ -0,0 +1,149 @@
+WeaponData
+{
+ // General
+ "printname" "Melee"
+ "shortprintname" "Melee"
+ "description" "Description needed"
+ "longdesc" "Description needed"
+
+ "menu_icon" "ui/temp"
+ "hud_icon" "ui/temp"
+
+ "weaponClass" "human"
+ "weaponType" "melee"
+ "body_type" "battle_rifle"
+ "fire_mode" "offhand_melee"
+ "never_drop" "1"
+
+ // Models
+ "viewmodel" "models/weapons/kunai/ptpov_kunai.mdl"
+ "playermodel" "models/weapons/kunai/w_kunai.mdl"
+
+ // Melee
+ "melee_can_hit_humansized" "1"
+ "melee_can_hit_titans" "0"
+ "melee_raise_recovery_animtime_normal" "0.25"
+ "melee_raise_recovery_animtime_quick" "0.01"
+ "melee_range" "40"
+ "melee_rumble_on_hit" "pilot_melee_hit"
+ "melee_freezelook_on_hit" "0.0"
+ "melee_sound_attack_1p" "Player_Melee_Backhand_1P"
+ "melee_sound_attack_3p" "player_melee_kick_3p"
+ "melee_anim_1p_number" "1" // ACT_VM_MELEE_ATTACK1, 2, or 3
+ "melee_anim_3p" "ACT_MP_MELEE_KNIFE_FIRST"
+ "damage_flags" "DF_MELEE | DF_KNOCK_BACK"
+ "impact_effect_table" "melee_human"
+ "impulse_force" "25000"
+
+ "zoom_effects" "0"
+
+ MP_BASE
+ {
+ "melee_lunge_target_range" "130"
+ "melee_lunge_target_angle" "30"
+ "melee_damage" "300"
+ "melee_damage_heavyarmor" "300"
+ "melee_attack_animtime" "1.0"
+ "melee_lunge_time" "0.125"
+ }
+
+ SP_BASE
+ {
+ "melee_lunge_target_range" "200"
+ "melee_lunge_target_angle" "40"
+ "melee_damage" "130"
+ "melee_damage_heavyarmor" "130"
+ "melee_attack_animtime" "0.0"
+ "melee_lunge_time" "0.2"
+ }
+
+ //
+ "ammo_suck_behavior" "melee_weapons"
+ "viewkick_spring" "melee"
+
+ "viewkick_pitch_base" "-1.75"
+ "viewkick_pitch_random" "0.75"
+ "viewkick_pitch_softScale" "0.3"
+ "viewkick_pitch_hardScale" "10.5"
+
+ "viewkick_yaw_base" "-1.0"
+ "viewkick_yaw_random" "0.38"
+ "viewkick_yaw_softScale" "0.38"
+ "viewkick_yaw_hardScale" "30.5"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.6"
+ "viewkick_roll_randomMax" "0.8"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "10.0"
+
+ //
+ "damage_rodeo" "100"
+ "aimassist_disable_hipfire" "1"
+ "aimassist_disable_ads" "1"
+
+ // Bob
+ "bob_cycle_time" "0.45"
+ "bob_vert_dist" "0.075"
+ "bob_horz_dist" "0.05"
+ "bob_max_speed" "150"
+ "bob_pitch" "0.75"
+ "bob_yaw" "1"
+ "bob_roll" "-0.75"
+
+ // View Drift
+ "viewdrift_hipfire_stand_scale_pitch" "0.325"
+ "viewdrift_hipfire_crouch_scale_pitch" "0.275"
+ "viewdrift_hipfire_air_scale_pitch" "0.5"
+ "viewdrift_hipfire_stand_scale_yaw" "0.12"
+ "viewdrift_hipfire_crouch_scale_yaw" "0.10"
+ "viewdrift_hipfire_air_scale_yaw" "0.22"
+ "viewdrift_hipfire_speed_pitch" "0.6"
+ "viewdrift_hipfire_speed_yaw" "1.22"
+
+ // Sway
+ "sway_rotate_attach" "SWAY_ROTATE"
+ "sway_min_x" "-0.3"
+ "sway_min_y" "-0.5"
+ "sway_min_z" "-0.5"
+ "sway_max_x" "0.3"
+ "sway_max_y" "0.5"
+ "sway_max_z" "0.1"
+ "sway_min_pitch" "-3"
+ "sway_min_yaw" "-3.5"
+ "sway_min_roll" "-2"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "3.5"
+ "sway_max_roll" "3"
+ "sway_translate_gain" "10"
+ "sway_rotate_gain" "12"
+ "sway_move_forward_translate_x" "0"
+ "sway_move_forward_translate_z" "-0.5"
+ "sway_move_back_translate_x" "-2"
+ "sway_move_back_translate_z" "-1"
+ "sway_move_left_translate_y" "-1"
+ "sway_move_left_translate_z" "-0.5"
+ "sway_move_left_rotate_roll" "-2"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "4"
+ "sway_move_up_translate_z" "-1"
+ "sway_move_down_translate_z" "1"
+ "sway_turn_left_rotate_yaw" "-1"
+ "sway_turn_right_rotate_yaw" "1"
+ "sway_turn_up_rotate_pitch" "1"
+ "sway_turn_down_rotate_pitch" "-1"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "deployfirst_time" "1.25"
+ "sprintcycle_time" ".55"
+
+ Mods
+ {
+ test_push
+ {
+ "melee_damage" "5"
+ "impulse_force" "50000"
+ }
+ }
+}
diff --git a/Northstar.Custom/scripts/weapons/mp_weapon_peacekraber.txt b/Northstar.Custom/scripts/weapons/mp_weapon_peacekraber.txt
new file mode 100644
index 000000000..c5e61f938
--- /dev/null
+++ b/Northstar.Custom/scripts/weapons/mp_weapon_peacekraber.txt
@@ -0,0 +1,347 @@
+WeaponData
+{
+ // General
+ "printname" "#WPN_PEACEKRABER"
+ "shortprintname" "#WPN_PEACEKRABER_SHORT"
+ "description" "#WPN_PEACEKRABER_DESC"
+ "longdesc" "#WPN_PEACEKRABER_LONGDESC"
+ "weaponClass" "human"
+ "weaponSubClass" "projectile_shotgun"
+ "body_type" "close_quarters"
+ "fire_mode" "semi-auto"
+ "pickup_hold_prompt" "Hold [USE] [WEAPONNAME]"
+ "pickup_press_prompt" "[USE] [WEAPONNAME]"
+ "aimassist_adspull_weaponclass" "broad"
+ "minimap_reveal_distance" "32000"
+ "leveled_pickup" "0"
+
+ "viewmodel" "models/weapons/at_rifle/ptpov_at_rifle.mdl"
+ "playermodel" "models/weapons/at_rifle/w_at_rifle.mdl"
+
+ "OnWeaponActivate" "OnWeaponActivate_peacekraber"
+ "OnWeaponDeactivate" "OnWeaponDeactivate_peacekraber"
+ "OnWeaponPrimaryAttack" "OnWeaponPrimaryAttack_peacekraber"
+ "OnWeaponChargeLevelIncreased" "OnWeaponChargeLevelIncreased_titanweapon_sniper"
+ "OnWeaponNpcPrimaryAttack" "OnWeaponNpcPrimaryAttack_peacekraber"
+
+ "projectilemodel" "models/dev/empty_model.mdl"
+ "tracer_effect" "weapon_tracers_shotgun"
+ "impact_effect_table" "inc_bullet"
+ "projectile_trail_effect_0" "P_plasma_proj_MD"
+ "vortex_absorb_effect" "wpn_vortex_projectile_shotgun_FP"
+ "vortex_absorb_effect_third_person" "wpn_vortex_projectile_shotgun"
+ "vortex_absorb_sound" "Vortex_Shield_AbsorbBulletSmall"
+ "vortex_absorb_sound_1P_VS_3P" "Vortex_Shield_AbsorbBulletSmall_1P_VS_3P"
+
+ // Menu
+ "menu_category" "special"
+ "menu_anim_class" "medium"
+ "stat_damage" "98"
+ "stat_range" "15"
+ "stat_accuracy" "15"
+ "stat_rof" "20"
+
+ "ammo_display" "bar"
+ "impulse_force" "10000"
+
+ "impact_effect_table" "titan_shotgun_bullet"
+
+ "charge_time" "1.5"
+ "charge_levels" "3"
+ "charge_cooldown_time" "1.5"
+ "charge_cooldown_delay" "1.25"
+ "charge_is_triggered_by_ADS" "1"
+ "charge_end_forces_fire" "0"
+ "charge_sound_1p" "Weapon_Titan_Sniper_WindUp"
+ "charge_sound_stop_when_full" "1"
+ "charge_sound_seek_to_charge_fraction" "1"
+ "charge_full_sound_1p" "Weapon_Titan_Sniper_SustainLoop"
+ "charge_drain_sound_1p" "Weapon_Titan_Sniper_WindDown"
+ "charge_drain_sound_stop_when_empty" "1"
+ "charge_drain_sound_seek_to_charge_fraction" "1"
+ // Spread
+ "spread_stand_hip" "0"
+ "spread_stand_hip_run" "0"
+ "spread_stand_hip_sprint" "0"
+ "spread_stand_ads" "0"
+ "spread_crouch_hip" "0"
+ "spread_crouch_ads" "0"
+ "spread_air_hip" "0"
+ "spread_air_ads" "0"
+
+ // Damage - When Used by Players
+ "damage_type" "bullet"
+ "damage_near_distance" "500"
+ "damage_far_distance" "1000"
+ "damage_near_value" "11"
+ "damage_far_value" "11"
+ "damage_near_value_titanarmor" "20"
+ "damage_far_value_titanarmor" "15"
+
+ // Ammo
+ "ammo_stockpile_max" "20"
+ "ammo_default_total" "25"
+ "ammo_clip_size" "5"
+ "ammo_size_segmented_reload" "5"
+ "ammo_display_as_clips" "0"
+ "reload_is_segmented" "0"
+
+ "reload_time" "2.45"
+ "reload_time_late1" "2.175"
+ "reload_time_late2" "0"
+ "reloadempty_time" "3.35"
+ "reloadempty_time_late1" "2.5"
+ "reloadempty_time_late2" "1.7"
+ "reloadempty_time_late2" "1.1"
+ "rechamber_time" "0.756"
+ "viewmodel_offset_ads" "0 0 0"
+
+
+ "vortex_absorb_effect" "wpn_vortex_projectile_40mm_FP"
+ "vortex_absorb_effect_third_person" "wpn_vortex_projectile_40mm"
+ "vortex_absorb_sound" "Vortex_Shield_AbsorbBulletLarge"
+ "vortex_absorb_sound_1p_vs_3p" "Vortex_Shield_AbsorbBulletLarge_1P_VS_3P"
+
+ "sound_dryfire" "shotgun_dryfire"
+ "fire_sound_1_player_1p" "Weapon_Leadwall_Fire_1P"
+ "fire_sound_1_player_3p" "Weapon_Leadwall_Fire_3P"
+ "fire_sound_1_npc" "Weapon_Leadwall_Fire_3P"
+ "sound_zoom_in" "Weapon_EVA8_ADS_In"
+ "sound_zoom_out" "Weapon_EVA8_ADS_Out"
+
+ "fx_shell_eject_view" "wpn_shelleject_shotshell_FP"
+ "fx_shell_eject_world" "wpn_shelleject_shotshell"
+ "fx_shell_eject_attach" "shell"
+
+ "fx_muzzle_flash_view" "wpn_muzzleflash_40mm_fp"
+ "fx_muzzle_flash_world" "wpn_muzzleflash_40mm"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+
+ "critical_hit_damage_scale" "1.3"
+ "critical_hit" "1"
+ "projectile_inherit_owner_velocity_scale" "0.2"
+
+ "bolt_hitsize" "0.5"
+ "bolt_hitsize_grow1_time" "0.075"
+ "bolt_hitsize_grow1_size" "4.0"
+ "bolt_hitsize_grow2_time" "0.075"
+ "bolt_hitsize_grow2_size" "4.0"
+ "bolt_hitsize_growfinal_lerptime" "0.18"
+ "bolt_hitsize_growfinal_size" "6.0"
+ "bolt_bounce_frac" "1.0"
+
+
+ "bolt_gravity_enabled" "1"
+
+ // Behavior
+ "fire_rate" "1.33"
+ "zoom_time_in" "0.2"
+ "zoom_time_out" "0.2"
+ "zoom_fov" "50"
+ "holster_time" "0.5"
+ "deploy_time" "0.8"
+ "lower_time" "0.25"
+ "raise_time" "0.3"
+ "vortex_refire_behavior" "bullet"
+ "allow_empty_fire" "0"
+ "reload_enabled" "1"
+ "allow_empty_click" "1"
+ "empty_reload_only" "0"
+ "trigger_snipercam" "0"
+ "allow_headshots" "0"
+ "primary_fire_does_not_block_sprint" "1"
+ "ads_move_speed_scale" "0.5"
+ "aimassist_disable_hipfire" "0"
+ "aimassist_disable_ads" "0"
+ "aimassist_disable_hipfire_titansonly" "1"
+ "aimassist_disable_ads_titansonly" "1"
+
+ "ammo_suck_behavior" "primary_weapons"
+
+ // View Kick
+ "viewkick_spring" "shotgun"
+
+ "viewkick_pitch_base" "-2.25"
+ "viewkick_pitch_random" "1"
+ "viewkick_pitch_softScale" "0.4"
+ "viewkick_pitch_hardScale" "2.0"
+
+ "viewkick_yaw_base" "-0.95"
+ "viewkick_yaw_random" "0.5"
+ "viewkick_yaw_softScale" "0.5"
+ "viewkick_yaw_hardScale" "2.0"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.6"
+ "viewkick_roll_randomMax" "0.8"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "2.75"
+
+ "viewkick_hipfire_weaponFraction" "0.1"
+ "viewkick_hipfire_weaponFraction_vmScale" "0.0"
+ "viewkick_ads_weaponFraction" "1.0"
+ "viewkick_ads_weaponFraction_vmScale" "0.15"
+
+ "viewkick_perm_pitch_base" "0"
+ "viewkick_perm_pitch_random" "0.0"
+
+ //
+ "viewmodel_shake_forward" "0.5"
+ "viewmodel_shake_up" "0.2"
+ "viewmodel_shake_right" "0.0"
+
+ // Bob
+ //"bob_cycle_time" "0.45"
+ //"bob_vert_dist" "0.1"
+ //"bob_horz_dist" "0.1"
+ //"bob_max_speed" "150"
+ //"bob_pitch" "0.75"
+ //"bob_yaw" "0.5"
+ //"bob_roll" "-0.75"
+
+
+ // Rumble
+ "fire_rumble" "pilot_singleshot_strong_fire"
+
+ // Sway
+ "sway_rotate_attach" "SWAY_ROTATE"
+ "sway_min_x" "-0.5"
+ "sway_min_y" "-0.4"
+ "sway_min_z" "-0.6"
+ "sway_max_x" "0.5"
+ "sway_max_y" "0.4"
+ "sway_max_z" "0.6"
+ "sway_min_pitch" "-3"
+ "sway_min_yaw" "-2.5"
+ "sway_min_roll" "-4"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "2.5"
+ "sway_max_roll" "4"
+ "sway_translate_gain" "2.5"
+ "sway_rotate_gain" "7"
+ "sway_move_forward_translate_x" "-0.1"
+ "sway_move_forward_translate_z" "-0.5"
+ "sway_move_back_translate_x" "0.2"
+ "sway_move_back_translate_z" "-0.2"
+ "sway_move_left_translate_y" "-1"
+ "sway_move_left_translate_z" "-0.5"
+ "sway_move_left_rotate_roll" "-4"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "4"
+ "sway_move_up_translate_z" "-1"
+ "sway_move_down_translate_z" "1"
+ "sway_turn_left_rotate_yaw" "1"
+ "sway_turn_right_rotate_yaw" "-1"
+
+ "sway_turn_left_translate_y" "-.2"
+ "sway_turn_right_translate_y" ".2"
+ "sway_turn_up_translate_z" "-.2"
+ "sway_turn_down_translate_z" ".2"
+ "sway_turn_up_translate_x" "-.1"
+ "sway_turn_down_translate_x" ".1"
+
+ "sway_turn_left_rotate_roll" "-4"
+ "sway_turn_right_rotate_roll" "4"
+ "sway_turn_up_rotate_pitch" "-3"
+ "sway_turn_down_rotate_pitch" "3"
+ "sway_turn_up_rotate_roll" "0.8"
+ "sway_turn_down_rotate_roll" "-0.8"
+
+ // Zoomed Sway
+ "sway_rotate_attach_zoomed" "SWAY_ROTATE_ZOOMED"
+ "sway_rotate_attach_blend_time_zoomed" "0.2"
+ "sway_rotate_gain_zoomed" "5"
+
+ "sway_min_yaw_zoomed" "-0.04"
+ "sway_max_yaw_zoomed" "0.04"
+ "sway_turn_left_rotate_yaw_zoomed" "-0.085"
+ "sway_turn_right_rotate_yaw_zoomed" "0.085"
+
+ "sway_min_roll_zoomed" "-1"
+ "sway_max_roll_zoomed" "1"
+ "sway_turn_left_rotate_roll_zoomed" "-1"
+ "sway_turn_right_rotate_roll_zoomed" "1"
+
+ "sway_move_right_rotate_roll_zoomed" "0.2"
+ "sway_move_left_rotate_roll_zoomed" "-0.2"
+
+ "sway_min_pitch_zoomed" "-0.01"
+ "sway_max_pitch_zoomed" "0.01"
+ "sway_turn_up_rotate_pitch_zoomed" "0.09"
+ "sway_turn_down_rotate_pitch_zoomed" "-0.09"
+
+ // NPC
+ "proficiency_poor_spreadscale" "7.0"
+ "proficiency_average_spreadscale" "5.0"
+ "proficiency_good_spreadscale" "3.33333"
+ "proficiency_very_good_spreadscale" "3.66667"
+
+ "npc_min_range" "0"
+ "npc_max_range" "1250"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "1.25"
+ "npc_rest_time_between_bursts_max" "1.75"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "bob_tilt_angle" "0.5"
+ "sway_turn_angle_factor" "-0.5"
+ "sway_turn_origin_factor" "0"
+ "sway_turn_angle_factor_zoomed" "0"
+ "sway_turn_origin_factor_zoomed" "0.05"
+ "sway_move_angle_factor" "0.15"
+ "sway_move_origin_factor" "0.15"
+ "sway_move_angle_factor_zoomed" "0"
+ "sway_move_origin_factor_zoomed" "0.03"
+ "sway_gain" "10.0"
+ "deployfirst_time" "1.0"
+ "deploycatch_time" "1.33"
+ "sprintcycle_time" ".55"
+ "sprint_fractional_anims" "0"
+
+ "projectile_lifetime" "0.5"
+ "projectile_damage_reduction_per_bounce" "0.0"
+ "projectile_damages_owner" "0"
+
+ // Crosshair
+ "red_crosshair_range" "300"
+ active_crosshair_count "0"
+
+ "ui6_enable" "1"
+ UiData6
+ {
+ "ui" "ui/b3wing_ammo_counter"
+ "mesh" "models/weapons/attachments/sniper_scope_rui_upper"
+ Args
+ {
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ clipCount weapon_stockpileClipCount
+ }
+ }
+ RUI_CrosshairData
+ {
+ DefaultArgs
+ {
+ adjustedSpread weapon_spread
+ isSprinting player_is_sprinting
+ isReloading weapon_is_reloading
+ teamColor crosshair_team_color
+ isAmped weapon_is_amped
+ chargeFrac player_chargeFrac
+ crosshairMovementX weapon_is_reloading
+ crosshairMovementY weapon_is_reloading
+ }
+
+ Crosshair_2
+ {
+ "ui" "ui/crosshair_lstar"
+ "base_spread" "4"
+ Args
+ {
+
+ }
+ }
+} \ No newline at end of file
diff --git a/Northstar.Custom/scripts/weapons/mp_weapon_sniper.txt b/Northstar.Custom/scripts/weapons/mp_weapon_sniper.txt
new file mode 100644
index 000000000..ede43d7de
--- /dev/null
+++ b/Northstar.Custom/scripts/weapons/mp_weapon_sniper.txt
@@ -0,0 +1,651 @@
+WeaponData
+{
+ // General
+ "printname" "#WPN_SNIPER"
+ "shortprintname" "#WPN_SNIPER_SHORT"
+ "description" "#WPN_SNIPER_DESC"
+ "longdesc" "#WPN_SNIPER_LONGDESC"
+
+ "menu_icon" "r2_ui/menus/loadout_icons/primary_weapon/primary_kraber"
+ "hud_icon" "r2_ui/menus/loadout_icons/primary_weapon/primary_kraber"
+
+ "weaponClass" "human"
+ "weaponSubClass" "sniper"
+ "body_type" "heavy"
+ "fire_mode" "semi-auto"
+ "pickup_hold_prompt" "Hold [USE] [WEAPONNAME]"
+ "pickup_press_prompt" "[USE] [WEAPONNAME]"
+ "minimap_reveal_distance" "32000"
+ "leveled_pickup" "1"
+
+ "OnWeaponActivate" "OnWeaponActivate_weapon_sniper"
+ "OnClientAnimEvent" "OnClientAnimEvent_weapon_sniper"
+ "OnWeaponPrimaryAttack" "OnWeaponPrimaryAttack_weapon_sniper"
+ "OnWeaponNpcPrimaryAttack" "OnWeaponNpcPrimaryAttack_weapon_sniper"
+ "OnProjectileCollision" "OnProjectileCollision_weapon_sniper"
+
+ "viewmodel_offset_ads" "0 0 0"
+
+ // Menu
+ "menu_category" "sniper"
+ "menu_anim_class" "large"
+ "stat_damage" "100"
+ "stat_range" "100"
+ "stat_accuracy" "60"
+ "stat_rof" "15"
+
+ "ads_dof_disable" "1"
+
+ // Models
+ "viewmodel" "models/weapons/at_rifle/ptpov_at_rifle.mdl"
+ "playermodel" "models/weapons/at_rifle/w_at_rifle.mdl"
+ "projectilemodel" "models/weapons/bullets/projectile_20mm.mdl"
+
+ // Effects
+ "impact_effect_table" "titan_bullet"
+ "projectile_trail_effect_0" "weapon_kraber_projectile"
+ "projectile_do_predict_impact_effects" "1"//0"
+ "vortex_absorb_effect" "wpn_vortex_projectile_20mm_FP"
+ "vortex_absorb_effect_third_person" "wpn_vortex_projectile_20mm"
+ "vortex_absorb_sound" "Vortex_Shield_AbsorbBulletLarge"
+ "vortex_absorb_sound_1p_vs_3p" "Vortex_Shield_AbsorbBulletLarge_1P_VS_3P"
+ "projectile_adjust_to_gun_barrel" "1"
+
+ // Sounds
+ "sound_dryfire" "rifle_dryfire"
+ "sound_pickup" "wpn_pickup_Rifle_1P"
+ "sound_zoom_in" "Weapon_Rangemaster_Kraber_ADS_In"
+ "sound_zoom_out" "Weapon_Rangemaster_Kraber_ADS_Out"
+ "fire_sound_1_player_1p" "large_shell_drop"
+ "fire_sound_1_player_3p" "large_shell_drop"
+ "fire_sound_1_npc" "large_shell_drop"
+ "fire_sound_2_player_1p" "Weapon_Kraber_Fire_1P"
+ "fire_sound_2_player_3p" "Weapon_Kraber_Fire_3P"
+ "fire_sound_2_npc" "Weapon_Kraber_Fire_npc"
+
+ "low_ammo_sound_name_1" "Kraber_LowAmmo_Shot1"
+ "low_ammo_sound_name_2" "Kraber_LowAmmo_Shot2"
+ "low_ammo_sound_name_3" "Kraber_LowAmmo_Shot3"
+
+ "fx_shell_eject_view" "wpn_shelleject_rifle_large_FP"
+ "fx_shell_eject_world" "wpn_shelleject_rifle_large"
+ "fx_shell_eject_attach" "shell"
+ "fx_shell_eject_attach_scoped" "shell_scoped"
+
+ "fx_muzzle_flash_view" "wpn_muzzleflash_snp_hmn_FP"
+ "fx_muzzle_flash_world" "wpn_muzzleflash_snp_hmn"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+ "fx_muzzle_flash_attach_scoped" "muzzle_flash_scoped"
+
+ // Bolt info
+ "bolt_hitsize" "0.0"
+ "bolt_hitsize_grow1_time" "0.035"
+ "bolt_hitsize_grow1_size" "0.5"
+ "bolt_hitsize_grow2_time" "0.08"
+ "bolt_hitsize_grow2_size" "1.0"
+ "bolt_hitsize_growfinal_lerptime" "0.18"
+ "bolt_hitsize_growfinal_size" "2.0"
+
+ "bolt_gravity_enabled" "1"
+ "bolt_gravity_amount" "0.2500"//0.500"
+
+ "bolt_bounce_frac" "0.000"
+ "projectile_damage_reduction_per_bounce" "0.0"
+ "projectile_damages_owner" "0"
+ "projectile_ricochet_max_count" "0"
+
+ "pass_through_depth" "64"
+ "pass_through_damage_preserved_scale" "1"
+
+ "bolt_speed" "10000"
+
+ // Damage - When Used by Players
+ "damage_flags" "DF_BULLET | DF_KNOCK_BACK | DF_DISMEMBERMENT"
+ "damage_type" "bullet"
+
+ "damage_headshot_scale" "1.5"
+
+ "impulse_force" "10"
+
+ "ammo_clip_size" "4"
+
+ "titanarmor_critical_hit_required" "1"
+ "critical_hit" "1"
+
+
+ MP_BASE
+ {
+ "ammo_default_total" "40"
+ "ammo_stockpile_max" "40"
+ "ammo_no_remove_from_stockpile" "1"
+ "ammo_min_to_fire" "1"
+
+
+ "damage_near_value" "350"
+ "damage_far_value" "350"
+ "damage_near_value_titanarmor" "430"
+ "damage_far_value_titanarmor" "430"
+ "damage_rodeo" "900"
+ "damage_near_distance" "2000"
+ "damage_far_distance" "15000"
+
+ "red_crosshair_range" "15000"
+
+ "critical_hit_damage_scale" "1"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "49"
+ "npc_damage_far_value" "49"
+ "npc_damage_near_value_titanarmor" "50"
+ "npc_damage_far_value_titanarmor" "25"
+
+ "npc_accuracy_multiplier_pilot" "1.0"
+
+ "npc_suppress_lsp_allowed" "0"
+
+ "enable_highlight_networking_on_creation" "<KEEP_DEFAULT>"
+
+ "damage_heavyarmor_nontitan_scale" "0.35"
+
+ "zoom_toggle_lerp_time" "<KEEP_DEFAULT>"
+ "zoom_toggle_fov" "<KEEP_DEFAULT>"
+ }
+
+ SP_BASE
+ {
+ "zoom_toggle_lerp_time" "0.2"
+ "zoom_toggle_fov" "7"
+
+ "ammo_default_total" "12"
+ "ammo_stockpile_max" "28"
+ "ammo_no_remove_from_stockpile" "0"
+ "ammo_min_to_fire" "1"
+
+
+ "damage_near_value" "200"
+ "damage_far_value" "200"
+ "damage_near_value_titanarmor" "500"
+ "damage_far_value_titanarmor" "500"
+ "damage_rodeo" "900"
+ "damage_near_distance" "2000"
+ "damage_far_distance" "15000"
+
+ "red_crosshair_range" "15000"
+
+ "critical_hit_damage_scale" "1.5"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "49"
+ "npc_damage_far_value" "49"
+ "npc_damage_near_value_titanarmor" "50"
+ "npc_damage_far_value_titanarmor" "25"
+
+ "npc_accuracy_multiplier_pilot" "3.0"
+
+ "npc_suppress_lsp_allowed" "<KEEP_DEFAULT>"
+
+ "enable_highlight_networking_on_creation" "1"
+
+ "damage_heavyarmor_nontitan_scale" "1"
+ }
+
+ // NPC
+ "proficiency_poor_spreadscale" "3.0"
+ "proficiency_average_spreadscale" "2.2"
+ "proficiency_good_spreadscale" "2.0"
+ "proficiency_very_good_spreadscale" "1.5"
+
+ "npc_min_engage_range" "500"
+ "npc_max_engage_range" "8000"
+ "npc_min_engage_range_heavy_armor" "500"
+ "npc_max_engage_range_heavy_armor" "8000"
+ "npc_min_range" "0"
+ "npc_max_range" "8000"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "2.0"
+ "npc_rest_time_between_bursts_max" "3.0"
+
+ "dof_zoom_nearDepthStart" "0"
+ "dof_zoom_nearDepthEnd" "0"
+ "dof_nearDepthStart" "4.750"
+ "dof_nearDepthEnd" "9"
+
+ // Behavior
+ "fire_rate" "1.85"
+ "zoom_time_in" "0.4"//0.48"//1.1"//1.25"//0.45"
+ "zoom_time_out" "0.3"
+ "zoom_fov" "26.26" //3x zoom
+ "zoom_scope_frac_start" "0.2"//0.2"
+ "zoom_scope_frac_end" "0.7"//0.85"
+ "zoom_angle_shift_pitch" "0.3"
+ "zoom_angle_shift_yaw" "0.65"
+ "rechamber_time" "1.60"//1.30"
+ "reload_time" "2.5"
+ "reload_time_late1" "1.52"
+ "reload_time_late2" "0.63"
+ "reloadempty_time" "3.61"
+ "reloadempty_time_late1" "2.63"
+ "reloadempty_time_late2" "1.75"
+ "reloadempty_time_late3" "0.55"
+ "holster_time" "0.5"
+ "deploy_time" "0.8"
+ "lower_time" "0.25"
+ "raise_time" "0.3"
+ "vortex_refire_behavior" "bullet"
+ "allow_empty_fire" "0"
+ "reload_enabled" "1"
+ "allow_empty_click" "1"
+ "empty_reload_only" "0"
+ "trigger_snipercam" "1"
+ "allow_headshots" "1"
+ "ads_move_speed_scale" "0.5"//0.35"
+ "aimassist_disable_hipfire" "1"
+ "aimassist_disable_ads" "1"
+ "aimassist_disable_hipfire_titansonly" "1"
+ "aimassist_disable_ads_titansonly" "1"
+ "gamepad_use_yaw_speed_for_pitch_ads" "1"
+ "ads_fov_zoomfrac_start" "0.5"
+ "ads_fov_zoomfrac_end" "0.9"
+
+ "sprint_fractional_anims" "0"
+
+ // Spread
+ "spread_stand_hip" "10"
+ "spread_stand_hip_run" "12"
+ "spread_stand_ads" "0.0" //"0.1"
+ "spread_crouch_hip" "8"
+ "spread_crouch_ads" "0"
+ "spread_air_hip" "12"
+ "spread_air_ads" "0.0" //".15"
+
+ "ammo_suck_behavior" "primary_weapons"
+
+ // View Kick
+ "viewkick_spring" "sniper"
+
+ "viewkick_pitch_base" "-0.5625"//"-1.25"//
+ "viewkick_pitch_random" "0.0225"//"0.05"//
+ "viewkick_pitch_softScale" "0.8"
+ "viewkick_pitch_hardScale" "2"
+
+ "viewkick_yaw_base" "-0.135"//"-0.3"//
+ "viewkick_yaw_random" "0.045"//"0.1"//
+ "viewkick_yaw_softScale" "1.0"
+ "viewkick_yaw_hardScale" "1.5"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.5"
+ "viewkick_roll_randomMax" "0.5"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "3.0"
+
+ "viewkick_hipfire_weaponFraction" "0.4"
+ "viewkick_hipfire_weaponFraction_vmScale" "0.55"
+ "viewkick_ads_weaponFraction" "0.0"//"0.45"//
+ "viewkick_ads_weaponFraction_vmScale" "0.0"//"-0.3"//
+
+ "viewkick_perm_pitch_base" "0.0"
+ "viewkick_perm_pitch_random" "0.0"
+ "viewkick_perm_pitch_random_innerexclude" "0.0"
+ "viewkick_perm_yaw_base" "0.0"
+ "viewkick_perm_yaw_random" "0.0"
+ "viewkick_perm_yaw_random_innerexclude" "0.0"
+
+ //
+ "viewmodel_shake_forward" "0.5"
+ "viewmodel_shake_up" "0.1"
+ "viewmodel_shake_right" "0.0"
+
+ // Bob
+ "bob_cycle_time" "0.4"
+ "bob_vert_dist" "0.19"
+ "bob_horz_dist" "0.1"
+ "bob_max_speed" "150"
+ "bob_pitch" "0.75"
+ "bob_yaw" "-1.7"
+ "bob_roll" "1.2"
+
+ // Bob_Zoomed
+ "bob_cycle_time_zoomed" "0.4"
+ "bob_vert_dist_zoomed" "0.0025"
+ "bob_horz_dist_zoomed" "0.0025"
+ "bob_max_speed_zoomed" "150"
+ //"bob_pitch_zoomed" "0.002"
+ //"bob_yaw_zoomed" "-.002"
+ //"bob_roll_zoomed" ".002"
+
+ // Rumble
+ "fire_rumble" "rumble_sniper"
+
+ // Sway
+ "sway_rotate_attach" "SWAY_ROTATE"
+ "sway_min_x" "-0.5"
+ "sway_min_y" "-0.5"
+ "sway_min_z" "-0.6"
+ "sway_max_x" "0.5"
+ "sway_max_y" "0.5"
+ "sway_max_z" "0.6"
+ "sway_min_pitch" "-3"
+ "sway_min_yaw" "-2.5"
+ "sway_min_roll" "-4"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "2.5"
+ "sway_max_roll" "4"
+ "sway_translate_gain" "2.5"
+ "sway_rotate_gain" "7"
+ "sway_move_forward_translate_x" "-0.1"
+ "sway_move_forward_translate_z" "-0.5"
+ "sway_move_back_translate_x" "0.2"
+ "sway_move_back_translate_z" "-0.2"
+ "sway_move_left_translate_y" "-1"
+ "sway_move_left_translate_z" "-0.5"
+ "sway_move_left_rotate_roll" "-4"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "4"
+ "sway_move_up_translate_z" "-1"
+ "sway_move_down_translate_z" "1"
+ "sway_turn_left_rotate_yaw" "-2.5"
+ "sway_turn_right_rotate_yaw" "2.5"
+
+ "sway_turn_left_translate_y" ".5"
+ "sway_turn_right_translate_y" "-.5"
+ "sway_turn_up_translate_z" ".2"
+ "sway_turn_down_translate_z" "-.2"
+ "sway_turn_up_translate_x" ".1"
+ "sway_turn_down_translate_x" "-.1"
+
+ "sway_turn_left_rotate_roll" "4"
+ "sway_turn_right_rotate_roll" "-4"
+ "sway_turn_up_rotate_pitch" "3"
+ "sway_turn_down_rotate_pitch" "-3"
+ "sway_turn_up_rotate_roll" "-0.8"
+ "sway_turn_down_rotate_roll" "0.8"
+
+ // Zoomed Sway
+ "sway_rotate_attach_zoomed" "jx_c_pov"
+ "sway_rotate_attach_blend_time_zoomed" "0.2"
+ "sway_rotate_gain_zoomed" "5"
+
+ "sway_min_yaw_zoomed" "-0.085"
+ "sway_max_yaw_zoomed" "0.085"
+ "sway_turn_left_rotate_yaw_zoomed" "0.085"
+ "sway_turn_right_rotate_yaw_zoomed" "-0.085"
+
+ "sway_min_roll_zoomed" "-1"
+ "sway_max_roll_zoomed" "1"
+ "sway_turn_left_rotate_roll_zoomed" "-1"
+ "sway_turn_right_rotate_roll_zoomed" "1"
+
+ "sway_move_right_rotate_roll_zoomed" "0.2"
+ "sway_move_left_rotate_roll_zoomed" "-0.2"
+
+ "sway_min_pitch_zoomed" "-0.25"
+ "sway_max_pitch_zoomed" "0.25"
+ "sway_turn_up_rotate_pitch_zoomed" "-0.25"
+ "sway_turn_down_rotate_pitch_zoomed" "0.25"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "sprintcycle_time" ".55"
+ "is_sniper" "1"
+
+ // Bodygroups:
+ "bodygroup1_name" "scope_dcom"
+ "bodygroup1_set" "1"
+ "bodygroup2_name" "scope_zoom"
+ "bodygroup2_set" "0"
+ "bodygroup3_name" "ammo"
+ "bodygroup3_set" "1"
+ "bodygroup4_name" "scope_outline"
+ "bodygroup4_set" "0"
+ "bodygroup5_name" "scope_oracle"
+ "bodygroup5_set" "0"
+ "bodygroup6_name" "proscreen"
+ "bodygroup6_set" "0"
+ // "bodygroup7_name" "suppressor_sq_lg"
+ // "bodygroup7_set" "0"
+
+ "bodygroup_ads_scope_name" "ads_scopes"
+ "bodygroup_ads_scope_set" "3"
+
+
+ "anim_alt_idleAttack" "0"
+
+ "clip_bodygroup" "at_rifle_magazine"
+ "clip_bodygroup_index_shown" "0"
+ "clip_bodygroup_index_hidden" "1"
+ "clip_bodygroup_show_for_milestone_0" "1"
+ "clip_bodygroup_show_for_milestone_1" "0"
+ "clip_bodygroup_show_for_milestone_2" "1"
+ "clip_bodygroup_show_for_milestone_3" "1"
+
+ "bodygroup_ammo_index_count" "6"
+
+ Mods
+ {
+ iron_sights
+ {
+
+ }
+ scope_4x
+ {
+ //Use this for Variable Zoom
+ "ui7_enable" "1"
+
+ "bodygroup1_set" "0"
+ "bodygroup2_set" "0"
+ "bodygroup4_set" "1"
+ "bodygroup5_set" "0"
+ "bodygroup_ads_scope_set" "2"
+ "zoom_toggle_lerp_time" "0.2"
+ "zoom_toggle_fov" "7"
+ //"ui6_enable" "1"
+ }
+ extended_ammo
+ {
+ "ammo_stockpile_max" "90"
+ "ammo_clip_size" "7"
+ "ammo_default_total" "90"
+ }
+ stabilizer
+ {
+ "bodygroup1_set" "0"
+ "bodygroup2_set" "0"
+ "bodygroup4_set" "0"
+ "bodygroup5_set" "1"
+ "bodygroup_ads_scope_set" "0"
+
+ // "viewdrift_ads_stand_scale_pitch" "*0.5"
+ // "viewdrift_ads_crouch_scale_pitch" "*0.5"
+ // "viewdrift_ads_air_scale_pitch" "*0.5"
+ // "viewdrift_ads_air_scale_yaw" "*0.5"
+ // "viewdrift_ads_stand_scale_yaw" "*0.5"
+ // "viewdrift_ads_crouch_scale_yaw" "*0.5"
+ // "viewdrift_ads_speed_pitch" "*0.5"
+ // "viewdrift_ads_speed_yaw" "*0.5"
+
+ "viewmodel_offset_ads" "0 -2.95 0.31"
+ //"viewmodel_offset_lerp_endFrac" "1"
+
+ "ads_fov_zoomfrac_start" "0.3"
+ "ads_fov_zoomfrac_end" "0.8"
+
+ "dof_zoom_nearDepthStart" "7.0"
+ "dof_zoom_nearDepthEnd" "7.2"
+
+ "anim_alt_idleAttack" "1"
+ }
+ ricochet
+ {
+ "bolt_bounce_frac" "0.7"
+ "projectile_damage_reduction_per_bounce" "0.0"
+ "projectile_damages_owner" "0"
+ "projectile_ricochet_max_count" "2"
+ }
+ slammer
+ {
+ }
+ threat_scope
+ {
+ "bodygroup1_set" "0"
+ "bodygroup2_set" "1"
+ "bodygroup4_set" "0"
+ "bodygroup5_set" "0"
+ "bodygroup_ads_scope_set" "1"
+
+ "threat_scope_enabled" "1"
+ "threat_scope_bounds_width" "1.5"
+ "threat_scope_bounds_height" "1.1"
+ "threat_scope_zoomfrac_start" "0.85"
+ "viewmodel_offset_ads" "0 0.5 0"
+ dof_zoom_focusArea_horizontal 0.068
+ dof_zoom_focusArea_top 0.065
+ dof_zoom_focusArea_bottom -0.046
+ }
+ pro_screen
+ {
+ "ui8_enable" "1"
+ "bodygroup6_set" "1"
+ }
+ tactical_cdr_on_kill
+ {
+
+ }
+ pas_fast_reload
+ {
+ "reload_time" "*0.7"
+ "reload_time_late1" "*0.7"
+ "reloadempty_time" "*0.7"
+ "reloadempty_time_late1" "*0.7"
+ }
+ pas_fast_ads
+ {
+ //Fast ADS
+ "zoom_time_in" "*0.5"
+ "zoom_time_out" "*0.6"
+ }
+ pas_fast_swap
+ {
+ //Fast Swap
+ "fast_swap_to" "1"
+ }
+ burn_mod_sniper
+ {
+ "is_burn_mod" "1"
+ //FX
+ "fx_muzzle_flash_view" "wpn_muzzleflash_snp_hmn_FP_burn"
+ "fx_muzzle_flash_world" "wpn_muzzleflash_snp_hmn_burn"
+ "projectile_trail_effect_0" "weapon_kraber_projectile_burn"
+ //"impact_effect_table" "titan_bullet_elec"
+
+ "damage_near_value" "450"
+ "damage_far_value" "450"
+ "damage_near_value_titanarmor" "580"
+ "damage_far_value_titanarmor" "580"
+
+ // reimplement tf1 amped kraber
+ "explosion_damage" "150"
+ "explosion_damage_heavy_armor" "150"
+ "explosion_inner_radius" "25"
+ "explosionradius" "75"
+ "explosion_shake_radius" "250"
+ "explosion_shake_amplitude" "10"
+ "explosion_shake_frequency" "50"
+ "explosion_shake_duration" "0.6"
+ "impact_effect_table" "exp_small"
+ }
+ pve_elite
+ {
+ //"ammo_stockpile_max" "90"
+ //"ammo_clip_size" "7"
+ //"ammo_default_total" "90"
+ //"reload_time" "0.1"
+ //"fire_rate" "0.1"
+ "npc_damage_near_value" "70"
+ "npc_damage_far_value" "70"
+ }
+ }
+
+ "ui1_enable" "1"
+ UiData1
+ {
+ "ui" "ui/kraber_ammo_counter"
+ "mesh" "models/weapons/attachments/kraber_rui_lower"
+ Args
+ {
+ vis player_zoomfrac
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ }
+ }
+
+ "ui6_enable" "0"
+ UiData6
+ {
+ "ui" "ui/red_dot_basic"
+ "mesh" "models/weapons/attachments/sniper_scope_rui_upper"
+ Args
+ {
+ vis player_zoomfrac
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ clipCount weapon_stockpileClipCount
+ }
+ }
+
+ "ui7_enable" "0"
+ UiData7
+ {
+ "ui" "ui/variable_zoom_crosshair"
+ "mesh" "models/weapons/attachments/attach_scope_ads_2_crosshair"
+ Args
+ {
+ vis player_zoomfrac
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ clipCount weapon_stockpileClipCount
+ }
+ }
+
+ "ui8_enable" "0"
+ UiData8
+ {
+ "ui" "ui/pro_screen_panel"
+ "mesh" "models/weapons/attachments/pro_screen_rui_upper"
+ Args
+ {
+ proValue proscreen_int0
+ proOwnedByPlayer proscreen_owner_is_player
+ }
+ }
+
+ active_crosshair_count "1"
+ rui_crosshair_index "0"
+
+ RUI_CrosshairData
+ {
+ DefaultArgs
+ {
+ adjustedSpread weapon_spread
+ adsFrac player_zoomFrac
+ isSprinting player_is_sprinting
+ isReloading weapon_is_reloading
+ teamColor crosshair_team_color
+ isAmped weapon_is_amped
+ crosshairMovementX crosshair_movement_x
+ crosshairMovementY crosshair_movement_y
+ }
+
+ Crosshair_1
+ {
+ "ui" "ui/crosshair_sniper_amped"
+ "base_spread" "10"
+ Args
+ {
+ isFiring weapon_is_firing
+ }
+ }
+ }
+}
diff --git a/Northstar.Custom/scripts/weapons/mp_weapon_toolgun.txt b/Northstar.Custom/scripts/weapons/mp_weapon_toolgun.txt
new file mode 100644
index 000000000..37e9bc256
--- /dev/null
+++ b/Northstar.Custom/scripts/weapons/mp_weapon_toolgun.txt
@@ -0,0 +1,587 @@
+WeaponData
+{
+ // General
+ "printname" "#WPN_TOOLGUN"
+ "shortprintname" "#WPN_TOOLGUN_SHORT"
+ "description" "#WPN_TOOLGUN_DESC"
+ "longdesc" "#WPN_TOOLGUN_LONGDESC"
+
+ "fast_swap_to" "1"
+
+ "menu_icon" "r2_ui/menus/loadout_icons/secondary_weapon/secondary_mozambique"
+ "hud_icon" "r2_ui/menus/loadout_icons/secondary_weapon/secondary_mozambique"
+
+ "weaponClass" "human"
+ "weaponSubClass" "projectile_shotgun"
+ "body_type" "close_quarters"
+ "fire_mode" "auto"
+ "pickup_hold_prompt" "Hold [USE] [WEAPONNAME]"
+ "pickup_press_prompt" "[USE] [WEAPONNAME]"
+ "minimap_reveal_distance" "32000"
+ "leveled_pickup" "1"
+
+ "OnWeaponActivate" "OnWeaponActivate_weapon_toolgun"
+ "OnWeaponDeactivate" "OnWeaponDeactivate_weapon_toolgun"
+ "OnWeaponPrimaryAttack" "OnWeaponPrimaryAttack_weapon_toolgun"
+ "OnWeaponStartZoomIn" "OnWeaponStartZoomIn_weapon_toolgun"
+ "OnWeaponStartZoomOut" "OnWeaponStartZoomOut_weapon_toolgun"
+ "OnWeaponNpcPrimaryAttack" "OnWeaponNpcPrimaryAttack_weapon_toolgun"
+
+ // Menu
+ "menu_category" "handgun"
+ "menu_anim_class" "small"
+ "stat_damage" "57"
+ "stat_range" "40"
+ "stat_accuracy" "44"
+ "stat_rof" "27"
+
+ // Models
+ "viewmodel" "models/weapons/pstl_sa3/ptpov_pstl_sa3.mdl"
+ "playermodel" "models/weapons/pstl_sa3/w_pstl_sa3.mdl"
+ "activitymodifier" "pistol"
+ "holster_type" "pistol"
+
+ // Effects
+ "tracer_effect" "weapon_tracers_shotgun"
+ "impact_effect_table" "bullet_mastiff"
+ "vortex_impact_effect" "P_impact_xo_shield_cp"
+ "vortex_absorb_effect" "wpn_vortex_projectile_shotgun_FP"
+ "vortex_absorb_effect_third_person" "wpn_vortex_projectile_shotgun"
+ "vortex_absorb_sound" "Vortex_Shield_AbsorbBulletSmall"
+ "vortex_absorb_sound_1P_VS_3P" "Vortex_Shield_AbsorbBulletSmall_1P_VS_3P"
+ "projectile_adjust_to_gun_barrel" "1"
+
+ "projectilemodel" "models/dev/empty_model.mdl"
+ //"projectile_trail_effect_0" "P_dragonsbreath_trail"
+ "projectile_trail_effect_0" "P_mastiff_proj"
+
+ "sound_dryfire" "shotgun_dryfire"
+ "sound_pickup" "wpn_pickup_Pistol_1P"
+ "fire_sound_1_player_1p" "weapon_shotgunpistol_fire_suppressed_1p"
+ "fire_sound_1_player_3p" "weapon_shotgunpistol_fire_suppressed_3p"
+ "fire_sound_1_npc" "Weapon_ShotgunPistol_Fire_NPC"
+ "sound_zoom_in" "Weapon_EVA8_ADS_In"
+ "sound_zoom_out" "Weapon_EVA8_ADS_Out"
+
+ "low_ammo_sound_name_1" "ShotgunPistol_LowAmmo_Shot1"
+ "low_ammo_sound_name_2" "ShotgunPistol_LowAmmo_Shot2"
+
+ //"fx_shell_eject_view" "wpn_shelleject_shotshell_FP"
+ //"fx_shell_eject_world" "wpn_shelleject_shotshell"
+ //"fx_shell_eject_attach" "shell"
+
+ "fx_muzzle_flash_view" "P_wpn_muz_mastiff_FP"
+ "fx_muzzle_flash_world" "P_wpn_muz_mastiff"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+
+ "damage_flags" "DF_SHOTGUN | DF_BULLET | DF_KNOCK_BACK | DF_DISMEMBERMENT"
+ "damage_type" "bullet"
+ "damage_headshot_scale" "1.5"
+
+ "explosion_inner_radius" "16"
+ "explosionradius" "32"
+ "impulse_force" "0"
+
+
+ "critical_hit_damage_scale" "1"
+
+ "projectile_inherit_owner_velocity_scale" "0.0"
+
+ "ammo_clip_size" "6" // arbitrarily large number
+ "titanarmor_critical_hit_required" "1"
+
+
+
+ MP_BASE
+ {
+ "ammo_default_total" "6"
+ "ammo_stockpile_max" "6"
+ "ammo_no_remove_from_stockpile" "1"
+ "ammo_min_to_fire" "0"
+
+ "aimassist_adspull_weaponclass" "broad"
+
+ "critical_hit" "1"
+
+ // Damage - When Used by Players
+ "damage_near_value" "30"
+ "damage_far_value" "25"
+ "damage_near_value_titanarmor" "30"
+ "damage_far_value_titanarmor" "20"
+ // "explosion_damage" "50" // 150
+ // "explosion_damage_heavy_armor" "50" // 150
+ "damage_near_distance" "750"
+ "damage_far_distance" "1000"
+
+ "red_crosshair_range" "750"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "11"
+ "npc_damage_far_value" "5"
+ "npc_damage_near_value_titanarmor" "<KEEP_DEFAULT>"
+ "npc_damage_far_value_titanarmor" "<KEEP_DEFAULT>"
+ "npc_damage_near_distance" "500"
+ "npc_damage_far_distance" "1000"
+
+ // NPC
+ "npc_min_engage_range" "0"
+ "npc_max_engage_range" "1000"
+ "npc_min_engage_range_heavy_armor" "100"
+ "npc_max_engage_range_heavy_armor" "2000"
+ "npc_min_range" "0"
+ "npc_max_range" "8000"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "0.5"
+ "npc_rest_time_between_bursts_max" "0.5"
+
+ "enable_highlight_networking_on_creation" "<KEEP_DEFAULT>"
+
+ "damage_heavyarmor_nontitan_scale" "0.35"
+ }
+
+ SP_BASE
+ {
+ "ammo_default_total" "20"
+ "ammo_stockpile_max" "32"
+ "ammo_no_remove_from_stockpile" "0"
+ "ammo_min_to_fire" "1"
+
+ "aimassist_adspull_weaponclass" "broad_sp"
+
+ "critical_hit" "0"
+
+ // Damage - When Used by Players
+ "damage_near_value" "30"
+ "damage_far_value" "20"
+ "damage_near_value_titanarmor" "30"
+ "damage_far_value_titanarmor" "20"
+ // "explosion_damage" "50"
+ // "explosion_damage_heavy_armor" "50"
+ "damage_near_distance" "750"
+ "damage_far_distance" "850"
+
+ "red_crosshair_range" "750"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "11"
+ "npc_damage_far_value" "5"
+ "npc_damage_near_value_titanarmor" "10"
+ "npc_damage_far_value_titanarmor" "8"
+ "npc_damage_near_distance" "500"
+ "npc_damage_far_distance" "1000"
+
+ // NPC
+ "npc_min_engage_range" "0"
+ "npc_max_engage_range" "1000"
+ "npc_min_engage_range_heavy_armor" "100"
+ "npc_max_engage_range_heavy_armor" "2000"
+ "npc_min_range" "0"
+ "npc_max_range" "8000"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "0.7"
+ "npc_rest_time_between_bursts_max" "1.0"
+
+ "enable_highlight_networking_on_creation" "1"
+
+ "damage_heavyarmor_nontitan_scale" "1"
+ }
+
+ "proficiency_poor_spreadscale" "5.0"
+ "proficiency_average_spreadscale" "5.0"
+ "proficiency_good_spreadscale" "3.66667"
+ "proficiency_very_good_spreadscale" "3.66667"
+
+ "viewmodel_offset_ads" "0 0 0"
+ "dof_zoom_nearDepthStart" "4.750"
+ "dof_zoom_nearDepthEnd" "11.00"
+ "dof_nearDepthStart" "0"
+ "dof_nearDepthEnd" "0"
+
+ //"bolt_hitsize" "20"
+
+ "bolt_hitsize" "0.5"
+ "bolt_hitsize_grow1_time" "0.055"
+ "bolt_hitsize_grow1_size" "5.0"
+ "bolt_hitsize_grow2_time" "0.18"
+ "bolt_hitsize_grow2_size" "7.5"
+ "bolt_hitsize_growfinal_lerptime" "0.18"
+ "bolt_hitsize_growfinal_size" "7.5"
+
+ "bolt_gravity_enabled" "1"
+
+ // Behavior
+ "fire_rate" "3.0"
+ "zoom_time_in" "0.2"
+ "zoom_time_out" "0.15"
+ "zoom_fov" "55"
+ "reload_time" "2.1"
+ "reload_time_late1" "1.05"
+ "reloadempty_time" "2.1"
+ "reloadempty_time_late1" "1.05"
+ "holster_time" "0.3"
+ "deploy_time" "0.4"
+ "lower_time" "0.2"
+ "raise_time" "0.2"
+ "vortex_refire_behavior" "bullet"
+ "allow_empty_fire" "0"
+ "reload_enabled" "0"
+ "allow_empty_click" "1"
+ "empty_reload_only" "0"
+ "trigger_snipercam" "0"
+ "allow_headshots" "1"
+ "headshot_distance" "1400"
+ "primary_fire_does_not_block_sprint" "0"
+ "ads_move_speed_scale" "0.5"
+ "aimassist_disable_hipfire" "1"
+ "aimassist_disable_ads" "1"
+ "aimassist_disable_hipfire_titansonly" "0"
+ "aimassist_disable_ads_titansonly" "0"
+
+ "sprint_fractional_anims" "0"
+
+ // Spread
+ "spread_stand_hip" "0"
+ "spread_stand_hip_run" "0"
+ "spread_stand_hip_sprint" "0"
+ "spread_stand_ads" "0"
+ "spread_crouch_hip" "0"
+ "spread_crouch_ads" "0"
+ "spread_air_hip" "0"
+ "spread_air_ads" "0"
+
+ // Spread on NPCs affects their initial shooting direction
+ // Don't make this a large number or the damage/tracers won't be even remotely parallel to their barrel
+
+ "ammo_suck_behavior" "primary_weapons"
+
+ // View Kick
+ "viewkick_spring" "shotgun"
+
+ "viewkick_pitch_base" "-2.25"
+ "viewkick_pitch_random" "1"
+ "viewkick_pitch_softScale" "1.4"
+ "viewkick_pitch_hardScale" "0.5"
+
+ "viewkick_yaw_base" "-0.95"
+ "viewkick_yaw_random" "0.5"
+ "viewkick_yaw_softScale" "1.5"
+ "viewkick_yaw_hardScale" "0.5"
+
+ "viewkick_roll_base" "0"
+ "viewkick_roll_randomMin" "0.6"
+ "viewkick_roll_randomMax" "0.8"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "2.75"
+
+ "viewkick_hipfire_weaponFraction" "0.1"
+ "viewkick_hipfire_weaponFraction_vmScale" "0.0"
+ "viewkick_ads_weaponFraction" "1.0"
+ "viewkick_ads_weaponFraction_vmScale" "0.15"
+
+ "viewkick_perm_pitch_base" "0"
+ "viewkick_perm_pitch_random" "0.0"
+
+ //
+ "viewmodel_shake_forward" "0.5"
+ "viewmodel_shake_up" "0.2"
+ "viewmodel_shake_right" "0.0"
+
+ // Bob
+ "bob_cycle_time" "0.4"
+ "bob_vert_dist" "0.19"
+ "bob_horz_dist" "0.1"
+ "bob_max_speed" "150"
+ "bob_pitch" "0.75"
+ "bob_yaw" "-1.7"
+ "bob_roll" "1.2"
+
+ // Bob_Zoomed
+ "bob_cycle_time_zoomed" "0.4"
+ "bob_vert_dist_zoomed" "0.01"
+ "bob_horz_dist_zoomed" "0.01"
+ "bob_max_speed_zoomed" "150"
+ //"bob_pitch_zoomed" "0.002"
+ //"bob_yaw_zoomed" "-.002"
+ //"bob_roll_zoomed" ".002"
+
+ // Rumble
+ "fire_rumble" "rumble_pistol_heavy"
+
+ // Sway
+ "sway_rotate_attach" "SWAY_ROTATE"
+ "sway_min_x" "-0.5"
+ "sway_min_y" "-0.5"
+ "sway_min_z" "-0.6"
+ "sway_max_x" "0.5"
+ "sway_max_y" "0.5"
+ "sway_max_z" "0.6"
+ "sway_min_pitch" "-3"
+ "sway_min_yaw" "-2.5"
+ "sway_min_roll" "-4"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "2.5"
+ "sway_max_roll" "4"
+ "sway_translate_gain" "2.5"
+ "sway_rotate_gain" "7"
+ "sway_move_forward_translate_x" "-0.1"
+ "sway_move_forward_translate_z" "-0.5"
+ "sway_move_back_translate_x" "0.2"
+ "sway_move_back_translate_z" "-0.2"
+ "sway_move_left_translate_y" "-1"
+ "sway_move_left_translate_z" "-0.5"
+ "sway_move_left_rotate_roll" "-4"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "4"
+ "sway_move_up_translate_z" "-1"
+ "sway_move_down_translate_z" "1"
+ "sway_turn_left_rotate_yaw" "-2.5"
+ "sway_turn_right_rotate_yaw" "2.5"
+
+ "sway_turn_left_translate_y" ".5"
+ "sway_turn_right_translate_y" "-.5"
+ "sway_turn_up_translate_z" ".2"
+ "sway_turn_down_translate_z" "-.2"
+ "sway_turn_up_translate_x" ".1"
+ "sway_turn_down_translate_x" "-.1"
+
+ "sway_turn_left_rotate_roll" "4"
+ "sway_turn_right_rotate_roll" "-4"
+ "sway_turn_up_rotate_pitch" "3"
+ "sway_turn_down_rotate_pitch" "-3"
+ "sway_turn_up_rotate_roll" "-0.8"
+ "sway_turn_down_rotate_roll" "0.8"
+
+ // Zoomed Sway
+ "sway_rotate_attach_zoomed" "SWAY_ROTATE_ZOOMED"
+ "sway_rotate_attach_blend_time_zoomed" "0.2"
+ "sway_rotate_gain_zoomed" "5"
+
+ "sway_min_yaw_zoomed" "-0.073"
+ "sway_max_yaw_zoomed" "0.073"
+ "sway_turn_left_rotate_yaw_zoomed" "-0.085"
+ "sway_turn_right_rotate_yaw_zoomed" "0.085"
+
+ "sway_min_roll_zoomed" "-4"
+ "sway_max_roll_zoomed" "4"
+ "sway_turn_left_rotate_roll_zoomed" "0"
+ "sway_turn_right_rotate_roll_zoomed" "0"
+
+ "sway_move_right_rotate_roll_zoomed" "0.2"
+ "sway_move_left_rotate_roll_zoomed" "-0.2"
+
+ "sway_min_pitch_zoomed" "-0.03"
+ "sway_max_pitch_zoomed" "0.03"
+ "sway_turn_up_rotate_pitch_zoomed" "0.07"
+ "sway_turn_down_rotate_pitch_zoomed" "-0.07"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "bob_tilt_angle" "0.5"
+ "sway_turn_angle_factor" "-0.5"
+ "sway_turn_origin_factor" "0"
+ "sway_turn_angle_factor_zoomed" "0"
+ "sway_turn_origin_factor_zoomed" "0.05"
+ "sway_move_angle_factor" "0.15"
+ "sway_move_origin_factor" "0.15"
+ "sway_move_angle_factor_zoomed" "0"
+ "sway_move_origin_factor_zoomed" "0.03"
+ "sway_gain" "10.0"
+ "deployfirst_time" "1.0"
+ "deploycatch_time" "1.33"
+ "sprintcycle_time" ".55"
+
+ // Bodygroups:
+ "bodygroup1_name" "suppressor_cyl_med"
+ "bodygroup1_set" "0"
+ "bodygroup2_name" "suppressor_sq_med"
+ "bodygroup2_set" "1" // enable suppressor bodygroup by default
+ "bodygroup3_name" "sight_cqh"
+ "bodygroup3_set" "0"
+ "bodygroup4_name" "sight_acgs"
+ "bodygroup4_set" "0"
+ "bodygroup5_name" "sight_cro"
+ "bodygroup5_set" "0"
+ "bodygroup6_name" "proscreen"
+ "bodygroup6_set" "1" // enable proscreen bodygroup by default
+
+ "clip_bodygroup" "pstl_sa3_shell"
+ "clip_bodygroup_index_shown" "0"
+ "clip_bodygroup_index_hidden" "1"
+ "clip_bodygroup_show_for_milestone_0" "1"
+ "clip_bodygroup_show_for_milestone_1" "0"
+ "clip_bodygroup_show_for_milestone_2" "0"
+ "clip_bodygroup_show_for_milestone_3" "0"
+
+ Mods
+ {
+ iron_sights
+ {
+ }
+ extended_ammo
+ {
+ "ammo_stockpile_max" "160"
+ "ammo_clip_size" "8"
+ "ammo_default_total" "160"
+ }
+ silencer //HACK JFS: Doesn't get applied on amped weapons. See bug 170460
+ {
+ "bodygroup2_set" "1"
+ "silenced" "1"
+ "fire_sound_1_player_1p" "weapon_shotgunpistol_fire_suppressed_1p"
+ "fire_sound_1_player_3p" "weapon_shotgunpistol_fire_suppressed_3p"
+ "damage_near_value" "--10"
+ "damage_far_value" "--4"
+ //"bodygroup2_set" "1"
+
+ //"rumble" "4"
+ //"tracer_effect" "P_wpn_tracer_pistol"
+ "minimap_reveal_distance" "1"
+
+ "fx_muzzle_flash_view" "wpn_muzzleflash_pistol_sup_FP"
+ "fx_muzzle_flash_world" "wpn_muzzleflash_pistol_sup"
+ //"fx_muzzle_flash_attach" "muzzle_flash_suppressor_sq"
+ }
+ pas_run_and_gun
+ {
+ "primary_fire_does_not_block_sprint" "1"
+ "crosshair_force_sprint_fade_disabled" "1"
+ }
+ alt_spread
+ {
+ }
+ hcog
+ {
+ "bodygroup3_set" "1"
+ "bodygroup4_set" "0"
+ "bodygroup5_set" "0"
+ "viewmodel_offset_ads" "0 -6 -0.79"
+ "dof_zoom_nearDepthStart" "0.750"
+ "dof_zoom_nearDepthEnd" "8.000"
+ }
+ threat_scope
+ {
+ "bodygroup3_set" "0"
+ "bodygroup4_set" "0"
+ "bodygroup5_set" "1"
+ "threat_scope_enabled" "1"
+ "threat_scope_bounds_tagname1" "SCR_TR_CRO"
+ "threat_scope_bounds_tagname2" "SCR_BL_CRO"
+ "viewmodel_offset_ads" "0 -10 -0.83"
+ "dof_zoom_nearDepthStart" "5.040"
+ "dof_zoom_nearDepthEnd" "5.737"
+ }
+ pro_screen
+ {
+ "ui8_enable" "1"
+ "bodygroup6_set" "1"
+ }
+ pas_fast_reload
+ {
+ "reload_time" "*0.7"
+ "reload_time_late1" "*0.7"
+ "reloadempty_time" "*0.7"
+ "reloadempty_time_late1" "*0.7"
+ }
+ pas_fast_ads
+ {
+ //Fast ADS
+ "zoom_time_in" "*0.5"
+ "zoom_time_out" "*0.6"
+ }
+ pas_fast_swap
+ {
+ //Fast Swap
+ "fast_swap_to" "1"
+ }
+ tactical_cdr_on_kill
+ {
+
+ }
+ burn_mod_shotgun_pistol
+ {
+ "damage_near_value" "++5"
+ "damage_far_value" "++10"
+ "damage_near_value_titanarmor" "++30"
+ "damage_far_value_titanarmor" "++30"
+ "is_burn_mod" "1"
+
+ //Effects
+ "fx_muzzle_flash_view" "P_wpn_muz_SGPistol_amp_FP"
+ "fx_muzzle_flash_world" "P_wpn_muz_mastiff_amp"
+ "projectile_trail_effect_0" "P_mastiff_proj_amp"
+ "impact_effect_table" "titan_bullet_elec"
+ }
+
+ }
+
+
+ "ui1_enable" "1"
+ "ui1_draw_cloaked" "1"
+ UiData1
+ {
+ "ui" "ui/sa3_crosshair"
+ "mesh" "models/weapons/attachments/sa3_rui_upper"
+ Args
+ {
+ vis player_zoomfrac
+ }
+ }
+
+ "ui2_enable" "1"
+ UiData2
+ {
+ "ui" "ui/sa3_ammo_counter"
+ "mesh" "models/weapons/attachments/sa3_rui_lower"
+ Args
+ {
+ vis player_zoomfrac
+ ammo weapon_ammo
+ clipSize weapon_clipSize
+ clipCount weapon_stockpileClipCount
+ }
+ }
+
+ "ui8_enable" "1"
+ UiData8
+ {
+ "ui" "ui/pro_screen_panel"
+ "mesh" "models/weapons/attachments/pro_screen_rui_upper"
+ Args
+ {
+ proValue proscreen_int0
+ proOwnedByPlayer proscreen_owner_is_player
+ }
+ }
+
+ active_crosshair_count "1"
+ rui_crosshair_index "0"
+
+ RUI_CrosshairData
+ {
+ DefaultArgs
+ {
+ adjustedSpread weapon_spread
+ adsFrac player_zoomFrac
+ isSprinting player_is_sprinting
+ isReloading weapon_is_reloading
+ teamColor crosshair_team_color
+ isAmped weapon_is_amped
+ crosshairMovementX crosshair_movement_x
+ crosshairMovementY crosshair_movement_y
+ }
+
+ Crosshair_1
+ {
+ "ui" "ui/crosshair_mozambique"
+ "base_spread" "0.0"
+ Args
+ {
+ isFiring weapon_is_firing
+ }
+ }
+ }
+}
diff --git a/Northstar.CustomServers/cfg/server/persistent_player_data_version_231.pdef b/Northstar.CustomServers/cfg/server/persistent_player_data_version_231.pdef
new file mode 100644
index 000000000..c4af215ab
--- /dev/null
+++ b/Northstar.CustomServers/cfg/server/persistent_player_data_version_231.pdef
@@ -0,0 +1,1515 @@
+int initializedVersion
+int announcementVersionSeen
+
+int xp
+int previousXP
+int credits
+int xp_match[20]
+int xp_count[20]
+
+int netWorth
+
+bool matchWin
+bool matchScoreEvent
+bool matchComplete
+bool matchSquadBonus
+
+bool showGameSummary
+bool regenShowNew
+bool spawnAsTitan
+bool haveSeenCustomCoop
+bool factionGiftsFixed
+
+bool isACheater
+bool spendDoubleColiseumTickets
+
+int privateMatchState
+
+int playlistShuffle_seed
+bool playlistShuffle_seedFlip
+int playlistShuffle_curIndex
+
+// we don't use titanClasses for this because it doesn't contain an entry that would equal null/none
+string{16} lastFDTitanRef
+int lastFDDifficulty
+
+bool ultimateEdition
+
+//#############################
+// LISTS OF THINGS IN THE GAME
+//#############################
+
+//All game modes in the game, including riffs.
+$ENUM_START gameModes
+ tdm
+ cp
+ at
+ ctf
+ lts
+ ps
+ ffa
+ coliseum
+ aitdm
+ speedball
+ mfd
+ ttdm
+ fra
+ fd
+$ENUM_END
+
+// enum used for stats. mp_box and mp_test_engagement_range are
+// in this list so I can test stat tracking in those maps
+// add new maps to the bottom of this list...DO NOT CHANGE ORDER!!
+$ENUM_START maps
+ mp_box
+ mp_test_engagement_range
+
+ // R2
+ mp_forwardbase_kodai
+ mp_grave
+ mp_homestead
+ mp_thaw
+ mp_black_water_canal
+ mp_eden
+ mp_drydock
+ mp_crashsite3
+ mp_complex3
+ mp_coliseum
+
+ // R2 DLC
+ mp_angel_city
+ mp_colony02
+ mp_relic02
+ mp_glitch
+ mp_lf_stacks
+ mp_lf_meadow
+ mp_lf_deck
+ mp_lf_traffic
+ mp_lf_township
+ mp_lf_uma
+ mp_coliseum_column
+ mp_wargames
+ mp_rise
+$ENUM_END
+
+$ENUM_START loadoutWeaponsAndAbilities
+ NULL
+ melee_pilot_emptyhanded
+ melee_pilot_sword
+ melee_titan_sword
+ melee_titan_sword_aoe
+ mp_ability_cloak
+ mp_ability_grapple
+ mp_ability_heal
+ mp_ability_holopilot
+ mp_ability_phase_rewind
+ mp_ability_shifter
+ mp_titanability_ammo_swap
+ mp_titanability_basic_block
+ mp_titanability_gun_shield
+ mp_titanability_hover
+ mp_titanability_laser_trip
+ mp_titanability_particle_wall
+ mp_titanability_phase_dash
+ mp_titanability_power_shot
+ mp_titanability_slow_trap
+ mp_titanability_smoke
+ mp_titanability_sonar_pulse
+ mp_titanability_tether_trap
+ mp_titanability_rearm
+ mp_titancore_flame_wave
+ mp_titancore_flight_core
+ mp_titancore_laser_cannon
+ mp_titancore_salvo_core
+ mp_titancore_shift_core
+ mp_titancore_siege_mode
+ mp_titancore_upgrade
+ mp_titanweapon_40mm
+ mp_titanweapon_arc_wave
+ mp_titanweapon_flame_wall
+ mp_titanweapon_heat_shield
+ mp_titanweapon_homing_rockets
+ mp_titanweapon_dumbfire_rockets
+ mp_titanweapon_laser_lite
+ mp_titanweapon_leadwall
+ mp_titanweapon_meteor
+ mp_titanweapon_particle_accelerator
+ mp_titanweapon_predator_cannon
+ mp_titanweapon_rocket_launcher
+ mp_titanweapon_rocketeer_rocketstream
+ mp_titanweapon_salvo_rockets
+ mp_titanweapon_sniper
+ mp_titanweapon_sticky_40mm
+ mp_titanweapon_stun_laser
+ mp_titanweapon_tracker_rockets
+ mp_titanweapon_vortex_shield
+ mp_titanweapon_vortex_shield_ion
+ mp_titanweapon_xo16
+ mp_titanweapon_xo16_shorty
+ mp_titanweapon_xo16_vanguard
+ mp_weapon_alternator_smg
+ mp_weapon_arc_launcher
+ mp_weapon_autopistol
+ mp_weapon_car
+ mp_weapon_defender
+ mp_weapon_deployable_cover
+ mp_weapon_dmr
+ mp_weapon_doubletake
+ mp_weapon_epg
+ mp_weapon_esaw
+ mp_weapon_frag_drone
+ mp_weapon_frag_grenade
+ mp_weapon_g2
+ mp_weapon_grenade_electric_smoke
+ mp_weapon_grenade_emp
+ mp_weapon_grenade_gravity
+ mp_weapon_grenade_sonar
+ mp_weapon_hemlok
+ mp_weapon_hemlok_smg
+ mp_weapon_lmg
+ mp_weapon_lstar
+ mp_weapon_mastiff
+ mp_weapon_mgl
+ mp_weapon_pulse_lmg
+ mp_weapon_r97
+ mp_weapon_rocket_launcher
+ mp_weapon_rspn101
+ mp_weapon_rspn101_og
+ mp_weapon_satchel
+ mp_weapon_semipistol
+ mp_weapon_shotgun
+ mp_weapon_shotgun_pistol
+ mp_weapon_smart_pistol
+ mp_weapon_smr
+ mp_weapon_sniper
+ mp_weapon_softball
+ mp_weapon_thermite_grenade
+ mp_weapon_vinson
+ mp_weapon_wingman
+ mp_weapon_wingman_n
+ melee_titan_punch_ion
+ melee_titan_punch_legion
+ melee_titan_punch_northstar
+ melee_titan_punch_scorch
+ melee_titan_punch_tone
+ melee_titan_punch_vanguard
+$ENUM_END
+
+$ENUM_START pilotMod
+ NULL
+ aog
+ automatic_fire
+ burn_mod_rspn101
+ burn_mod_g2
+ burn_mod_hemlok
+ burn_mod_vinson
+ burn_mod_lstar
+ burn_mod_car
+ burn_mod_r97
+ burn_mod_alternator_smg
+ burn_mod_lmg
+ burn_mod_esaw
+ burn_mod_pulse_lmg
+ burn_mod_sniper
+ burn_mod_dmr
+ burn_mod_doubletake
+ burn_mod_mastiff
+ burn_mod_shotgun
+ burn_mod_softball
+ burn_mod_shotgun_pistol
+ burn_mod_autopistol
+ burn_mod_wingman
+ burn_mod_semipistol
+ burn_mod_smart_pistol
+ burn_mod_emp_grenade
+ burn_mod_frag_grenade
+ burn_mod_satchel
+ burn_mod_proximity_mine
+ burn_mod_grenade_electric_smoke
+ burn_mod_grenade_gravity
+ burn_mod_thermite_grenade
+ burn_mod_defender
+ burn_mod_rocket_launcher
+ burn_mod_arc_launcher
+ burn_mod_smr
+ burn_mod_mgl
+ burst
+ enhanced_targeting
+ extended_ammo
+ fast_lock
+ fast_reload
+ guided_missile
+ hcog
+ high_density
+ holosight
+ iron_sights
+ long_fuse
+ powered_magnets
+ scope_4x
+ scope_6x
+ scope_8x
+ scope_10x
+ scope_12x
+ silencer
+ sniper_assist
+ stabilizer
+ single_shot
+ slammer
+ stabilized_warhead
+ tank_buster
+ amped_wall
+ short_shift
+ burn_mod_epg
+ ricochet
+ ar_trajectory
+ redline_sight
+ threat_scope
+ smart_lock
+ pro_screen
+ delayed_shot
+ pas_run_and_gun
+ tactical_cdr_on_kill
+ pas_fast_ads
+ pas_fast_swap
+ pas_fast_reload
+ jump_kit
+ quick_charge
+ rocket_arena
+$ENUM_END
+
+$ENUM_START titanClasses
+ ion
+ scorch
+ ronin
+ tone
+ northstar
+ legion
+ vanguard
+$ENUM_END
+
+$ENUM_START titanMod
+ NULL
+ accelerator
+ afterburners
+ arc_triple_threat
+ burn_mod_titan_40mm
+ burn_mod_titan_arc_cannon
+ burn_mod_titan_sniper
+ burn_mod_titan_triple_threat
+ burn_mod_titan_xo16
+ burn_mod_titan_dumbfire_rockets
+ burn_mod_titan_homing_rockets
+ burn_mod_titan_salvo_rockets
+ burn_mod_titan_shoulder_rockets
+ burn_mod_titan_vortex_shield
+ burn_mod_titan_smoke
+ burn_mod_titan_particle_wall
+ burst
+ capacitor
+ extended_ammo
+ fast_lock
+ fast_reload
+ instant_shot
+ overcharge
+ quick_shot
+ rapid_fire_missiles
+ stryder_sniper
+$ENUM_END
+
+$ENUM_START pilotPassive
+ NULL
+ pas_stealth_movement
+ pas_ordnance_pack
+ pas_power_cell
+ pas_wallhang
+ pas_fast_health_regen
+ pas_minimap_ai
+ pas_longer_bubble
+ pas_run_and_gun
+ pas_dead_mans_trigger
+ pas_wall_runner
+ pas_fast_hack
+ pas_cloaked_wallrun
+ pas_cloaked_wallhang
+ pas_smoke_sight
+ pas_fast_embark
+ pas_cdr_on_kill
+ pas_at_hunter
+ pas_ordnance_beam
+ pas_fast_rodeo
+ pas_phase_eject
+ pas_ads_hover
+ pas_enemy_death_icons
+ pas_off_the_grid
+$ENUM_END
+
+$ENUM_START pilotSuit
+ medium
+ geist
+ stalker
+ light
+ heavy
+ grapple
+ nomad
+$ENUM_END
+
+$ENUM_START pilotRace
+ race_human_male
+ race_human_female
+$ENUM_END
+
+$ENUM_START pilotExecution
+ execution_neck_snap
+ execution_face_stab
+ execution_backshot
+ execution_combo
+ execution_knockout
+ execution_telefrag
+ execution_stim
+ execution_grapple
+ execution_pulseblade
+ execution_random
+ execution_cloak
+ execution_holopilot
+ execution_ampedwall
+$ENUM_END
+
+
+$ENUM_START titanExecution
+ execution_ion
+ execution_ion_prime
+ execution_tone
+ execution_tone_prime
+ execution_ronin
+ execution_ronin_prime
+ execution_northstar
+ execution_northstar_prime
+ execution_legion
+ execution_legion_prime
+ execution_vanguard
+ execution_scorch
+ execution_scorch_prime
+ execution_random_0
+ execution_random_1
+ execution_random_2
+ execution_random_3
+ execution_random_4
+ execution_random_5
+ execution_random_6
+$ENUM_END
+
+$ENUM_START titanPassive
+ NULL
+ pas_enhanced_titan_ai
+ pas_auto_eject
+ pas_dash_recharge
+ pas_defensive_core
+ pas_shield_regen
+ pas_assault_reactor
+ pas_hyper_core
+ pas_anti_rodeo
+ pas_build_up_nuclear_core
+ pas_offensive_autoload
+ pas_offensive_hitnrun
+ pas_offensive_regen
+ pas_defensive_tacload
+ pas_defensive_quickdash
+ pas_defensive_domeshield
+ pas_mobility_dash_capacity
+ pas_warpfall
+ pas_bubbleshield
+ pas_ronin_weapon
+ pas_northstar_weapon
+ pas_ion_weapon
+ pas_tone_weapon
+ pas_scorch_weapon
+ pas_legion_weapon
+ pas_ion_tripwire
+ pas_ion_vortex
+ pas_ion_lasercannon
+ pas_tone_rockets
+ pas_tone_sonar
+ pas_tone_wall
+ pas_ronin_arcwave
+ pas_ronin_phase
+ pas_ronin_swordcore
+ pas_northstar_cluster
+ pas_northstar_trap
+ pas_northstar_flightcore
+ pas_scorch_firewall
+ pas_scorch_shield
+ pas_scorch_selfdmg
+ pas_legion_spinup
+ pas_legion_gunshield
+ pas_legion_smartcore
+ pas_ion_weapon_ads
+ pas_tone_burst
+ pas_legion_chargeshot
+ pas_ronin_autoshift
+ pas_northstar_optics
+ pas_scorch_flamecore
+ pas_vanguard_coremeter
+ pas_vanguard_shield
+ pas_vanguard_rearm
+ pas_vanguard_doom
+ pas_vanguard_core1
+ pas_vanguard_core2
+ pas_vanguard_core3
+ pas_vanguard_core4
+ pas_vanguard_core5
+ pas_vanguard_core6
+ pas_vanguard_core7
+ pas_vanguard_core8
+ pas_vanguard_core9
+$ENUM_END
+
+$ENUM_START titanIsPrimeTitan //Really should be bool, but script for loadouts is not easily set up to handle bools unfortunately...
+ titan_is_not_prime
+ titan_is_prime
+$ENUM_END
+
+$ENUM_START faction
+ faction_apex
+ faction_64
+ faction_vinson
+ faction_marauder
+ faction_aces
+ faction_ares
+ faction_marvin
+$ENUM_END
+
+// This entire thing is legacy support for a DLC7 menu bug
+$ENUM_START ownedEntitlements
+ ET_DLC7_WEAPON_BUNDLE // BUNDLE MUST BE FIRST!!!!
+ ET_DLC7_R201_WARPAINT
+ ET_DLC7_G2A5_WARPAINT
+ ET_DLC7_FLATLINE_WARPAINT
+ ET_DLC7_CAR_WARPAINT
+ ET_DLC7_ALTERNATOR_WARPAINT
+ ET_DLC7_EVA8_WARPAINT
+ ET_DLC7_WINGMAN_WARPAINT
+ ET_DLC7_ARCHER_WARPAINT
+$ENUM_END
+
+//######################
+// LOADOUTS
+//######################
+
+$STRUCT_START spawnLoadout
+ int index
+$STRUCT_END
+
+$STRUCT_START pilotLoadout
+ string{42} name
+ pilotSuit suit
+ pilotRace race
+ pilotExecution execution
+ loadoutWeaponsAndAbilities primary
+ pilotMod primaryAttachment
+ pilotMod primaryMod1
+ pilotMod primaryMod2
+ pilotMod primaryMod3
+ loadoutWeaponsAndAbilities secondary
+ pilotMod secondaryMod1
+ pilotMod secondaryMod2
+ pilotMod secondaryMod3
+ loadoutWeaponsAndAbilities weapon3
+ pilotMod weapon3Mod1
+ pilotMod weapon3Mod2
+ pilotMod weapon3Mod3
+ loadoutWeaponsAndAbilities ordnance
+ pilotPassive passive1
+ pilotPassive passive2
+ int skinIndex
+ int camoIndex
+ int primarySkinIndex
+ int primaryCamoIndex
+ int secondarySkinIndex
+ int secondaryCamoIndex
+ int weapon3SkinIndex
+ int weapon3CamoIndex
+$STRUCT_END
+
+$STRUCT_START titanLoadout
+ string{42} name // TODO: No need for this in persistent data any more
+ titanClasses titanClass
+ titanMod primaryMod
+ loadoutWeaponsAndAbilities special
+ loadoutWeaponsAndAbilities antirodeo
+ titanPassive passive1
+ titanPassive passive2
+ titanPassive passive3
+ titanPassive passive4
+ titanPassive passive5
+ titanPassive passive6
+ titanExecution titanExecution
+ int skinIndex
+ int camoIndex
+ int decalIndex
+ int primarySkinIndex
+ int primaryCamoIndex
+ titanIsPrimeTitan isPrime //Really should be bool, but script for loadouts is not easily set up to handle bools unfortunately...
+ int primeSkinIndex
+ int primeCamoIndex
+ int primeDecalIndex
+ int showArmBadge
+$STRUCT_END
+
+$STRUCT_START recentUnlock
+ int refGuid
+ int parentRefGuid
+ int count
+$STRUCT_END
+
+int randomColiseumUnlocks
+int randomPlayerLevelUnlocks
+int randomTitanLevelUnlocks[titanClasses]
+int randomWeaponLevelUnlocks[loadoutWeaponsAndAbilities]
+int randomFactionLevelUnlocks[faction]
+
+int doubleXP
+int coliseumTickets
+int coliseumWinStreak
+int coliseumBestStreak
+int coliseumTotalWins
+int coliseumTotalLosses
+
+recentUnlock recentUnlocks[10]
+
+bool hasBeenIntroducedToComms
+int lastCommsUseDate
+int numTimesUsedComms
+bool custom_emoji_initialized
+int custom_emoji[4]
+
+int burnmeterSlot
+
+$STRUCT_START pveData
+ int version
+ int currency
+ int currencyInLatestMatch
+ int tacticalUnlocks[6]
+ int feathersForMap[maps]
+$STRUCT_END
+pveData pve
+
+faction factionChoice
+faction enemyFaction
+
+bool persistentRewards[32]
+int consumableRewards[32]
+
+spawnLoadout pilotSpawnLoadout
+spawnLoadout titanSpawnLoadout
+
+pilotLoadout activePilotLoadout
+titanLoadout activeTitanLoadout
+int activeTitanLoadoutIndex
+
+pilotLoadout pilotLoadouts[10]
+titanLoadout titanLoadouts[10]
+
+bool pinTrackedEntitlements[ownedEntitlements]
+bool newPinTrackedEntitlements[ownedEntitlements]
+
+$ENUM_START unlockRefs
+ edit_pilots // these two must come first
+ edit_titans
+
+ pilot_custom_loadout_1
+ pilot_custom_loadout_2
+ pilot_custom_loadout_3
+ pilot_custom_loadout_4
+ pilot_custom_loadout_5
+
+ titan_custom_loadout_1
+ titan_custom_loadout_2
+ titan_custom_loadout_3
+ titan_custom_loadout_4
+ titan_custom_loadout_5
+
+ burn_card_slot_1
+ burn_card_slot_2
+ burn_card_slot_3
+
+ burn_card_pack_1
+ burn_card_pack_2
+ burn_card_pack_3
+ burn_card_pack_4
+ burn_card_pack_5
+
+ challenges
+$ENUM_END
+
+//######################
+// BURN CARDS
+//######################
+
+$ENUM_START burnCard
+ NULL
+ bc_conscription
+ bc_double_xp
+ bc_free_xp
+ bc_fast_cooldown1
+ bc_fast_cooldown2
+ bc_super_stim
+ bc_super_cloak
+ bc_super_sonar
+ bc_summon_ogre
+ bc_cloak_forever
+ bc_stim_forever
+ bc_sonar_forever
+ bc_summon_stryder
+ bc_spectre_virus
+ bc_play_spectre
+ bc_double_agent
+ bc_minimap
+ bc_summon_atlas
+ bc_megaturrets
+ bc_summon_dogfighter
+ bc_wifi_spectre_hack
+ bc_nuclear_core
+ bc_core_charged
+ bc_smart_pistol_m2
+ bc_r97_m2
+ bc_rspn101_m2
+ bc_dmr_m2
+ bc_shotgun_m2
+ bc_lmg_m2
+ bc_g2_m2
+ bc_car_m2
+ bc_hemlok_m2
+ bc_sniper_m2
+ bc_smr_m2
+ bc_mgl_m2
+ bc_defender_m2
+ bc_rocket_launcher_m2
+ bc_semipistol_m2
+ bc_autopistol_m2
+ bc_wingman_m2
+ bc_satchel_m2
+ bc_frag_m2
+ bc_arc_m2
+ bc_prox_m2
+ bc_pilot_warning
+ bc_rematch
+ bc_minimap_scan
+ bc_free_build_time_1
+ bc_free_build_time_2
+ bc_fast_build_1
+ bc_fast_build_2
+ bc_hunt_soldier
+ bc_hunt_spectre
+ bc_hunt_titan
+ bc_hunt_pilot
+ bc_auto_sonar
+ bc_fast_movespeed
+ bc_auto_refill
+ bc_dice_ondeath
+ bc_titan_40mm_m2
+ bc_titan_arc_cannon_m2
+ bc_titan_rocket_launcher_m2
+ bc_titan_sniper_m2
+ bc_titan_triple_threat_m2
+ bc_titan_xo16_m2
+ bc_titan_dumbfire_missile_m2
+ bc_titan_homing_rockets_m2
+ bc_titan_salvo_rockets_m2
+ bc_titan_shoulder_rockets_m2
+ bc_titan_vortex_shield_m2
+ bc_titan_electric_smoke_m2
+ bc_titan_shield_wall_m2
+ bc_titan_melee_m2
+ bc_extra_dash
+ bc_lstar_m2
+ bc_mastiff_m2
+ bc_vinson_m2
+$ENUM_END
+
+
+$STRUCT_START struct_activeBurnCardData
+ burnCard cardRef
+ burnCard lastCardRef
+ bool clearOnStart // player has used this match long burn card
+$STRUCT_END
+
+$STRUCT_START struct_historyBurnCardData
+ int collected
+ int spent
+$STRUCT_END
+
+$STRUCT_START struct_blackMarketBurnCardUpgrades
+ burnCard cardRef
+$STRUCT_END
+
+int activeBCID
+
+int activeCallingCardIndex
+int activeCallsignIconIndex
+int activeCallsignIconStyleIndex
+
+int gen // as in, what generation player?
+
+
+//#########################
+// Faction XP
+//#########################
+
+int factionXP[faction]
+int previousFactionXP[faction]
+
+//#########################
+// Titan XP
+//#########################
+
+int titanXP[titanClasses]
+int previousTitanXP[titanClasses]
+
+int fdTitanXP[titanClasses]
+int fdPreviousTitanXP[titanClasses]
+
+int titanFDUnlockPoints[titanClasses]
+int previousFDUnlockPoints[titanClasses]
+
+int fd_match[20]
+int fd_count[20]
+int titanClassLockState[titanClasses]
+
+int fdTutorialBits
+int fdPlaylistBits
+
+//################################################
+// CHAD'S STAT TRACKING STUFF - DON'T MESS WIT IT
+//################################################
+
+
+$STRUCT_START sMapStats
+ int gamesJoined[gameModes]
+ int gamesCompleted[gameModes]
+ int gamesWon[gameModes]
+ int gamesLost[gameModes]
+ int topPlayerOnTeam[gameModes]
+ int top3OnTeam[gameModes]
+ float hoursPlayed[gameModes]
+ int timesScored100AttritionPoints_byMap
+ int winsByDifficulty[5]
+ int matchesByDifficulty[5]
+ int perfectMatchesByDifficulty[5]
+$STRUCT_END
+
+$STRUCT_START sGameStats
+ int modesPlayed[gameModes]
+ int previousModesPlayed[gameModes]
+ int modesWon[gameModes]
+ int mvp_total
+ int gamesCompletedTotal
+ int gamesWonTotal
+ int gamesWonAsIMC
+ int gamesWonAsMilitia
+ int gamesCompletedAsIMC
+ int gamesCompletedAsMilitia
+ int pvpKills[gameModes]
+ int timesKillDeathRatio2to1[gameModes]
+ int timesKillDeathRatio2to1_pvp[gameModes]
+ int timesScored100AttritionPoints_total
+$STRUCT_END
+
+$STRUCT_START sHoursPlayed
+ float total
+ float asTitan[titanClasses]
+ float asPilot
+ float asTitanTotal
+ float dead
+ float wallhanging
+ float wallrunning
+ float inAir
+$STRUCT_END
+
+$STRUCT_START sMilesTraveled
+ float total
+ float asTitan[titanClasses]
+ float asPilot
+ float asTitanTotal
+ float wallrunning
+ float inAir
+ float ziplining
+ float onFriendlyTitan
+ float onEnemyTitan
+$STRUCT_END
+
+$STRUCT_START sWeaponStats
+ float hoursUsed
+ float hoursEquipped
+ int shotsFired
+ int shotsHit
+ int headshots
+ int critHits
+ int titanDamage
+$STRUCT_END
+
+$STRUCT_START sWeaponKillStats
+ int total
+ int pilots
+ int ejecting_pilots
+ int spectres
+ int marvins
+ int grunts
+ int ai
+ int titansTotal
+ int titans[titanClasses]
+ int npcTitans[titanClasses]
+ int assistsTotal
+ int killingSprees
+$STRUCT_END
+
+$STRUCT_START sKillStats
+ int total
+ int totalWhileUsingBurnCard
+ int titansWhileTitanBCActive
+ int totalPVP
+ int pilots
+ int spectres
+ int marvins
+ int grunts
+ int totalTitans
+ int totalTitansWhileDoomed
+ int totalPilots
+ int totalNPC
+ int asPilot
+ int asTitan[titanClasses]
+ int firstStrikes
+ int ejectingPilots
+ int whileEjecting
+ int cloakedPilots
+ int whileCloaked
+ int wallrunningPilots
+ int whileWallrunning
+ int wallhangingPilots
+ int whileWallhanging
+ int pilotExecution
+ int pilotExecutePilot
+ int pilotExecutePilotByType[pilotExecution]
+ int pilotKickMelee
+ int pilotKickMeleePilot
+ int titanMelee
+ int titanMeleePilot
+ int titanStepCrush
+ int titanStepCrushPilot
+ int titanExocutionIon
+ int titanExocutionScorch
+ int titanExocutionNorthstar
+ int titanExocutionRonin
+ int titanExocutionTone
+ int titanExocutionLegion
+ int titanExocutionVanguard
+ int titanFallKill
+ int petTitanKillsFollowMode
+ int petTitanKillsGuardMode
+ int rodeo_total
+ int rodeo_stryder
+ int rodeo_buddy
+ int rodeo_atlas
+ int rodeo_ogre
+ int pilot_headshots_total
+ int evacShips
+ int flyers
+ int nuclearCore
+ int evacuatingEnemies
+ int exportTrapKills
+ int coopChallenge_NukeTitan_Kills
+ int coopChallenge_MortarTitan_Kills
+ int coopChallenge_EmpTitan_Kills
+ int coopChallenge_BubbleShieldGrunt_Kills
+ int coopChallenge_CloakDrone_Kills
+ int coopChallenge_Dropship_Kills
+ int coopChallenge_SuicideSpectre_Kills
+ int coopChallenge_Turret_Kills
+ int coopChallenge_Sniper_Kills
+ int ampedVortexKills
+ int meleeWhileCloaked
+ int pilotKillsWhileUsingActiveRadarPulse
+ int titanKillsAsPilot
+ int pilotKillsWhileStimActive
+ int pilotKillsAsTitan
+ int totalAssists
+ int killingSprees[titanClasses]
+ int pilotKillsAsPilot
+ int titanKillsAsTitan
+ int telefragKils
+ int grappleKills
+ int throughAWallKills
+ int distractedKills
+ int pilotExecutePilotWhileCloaked
+ int pilotKillsWithHoloPilotActive
+ int pilotKillsWithAmpedWallActive
+$STRUCT_END
+
+$STRUCT_START sDeathStats
+ int total
+ int totalPVP
+ int asPilot
+ int asTitan[titanClasses]
+ int byPilots
+ int bySpectres
+ int byGrunts
+ int byTitans[titanClasses]
+ int byNPCTitans[titanClasses]
+ int suicides
+ int whileEjecting
+$STRUCT_END
+
+$STRUCT_START sMiscStats
+ int titanFalls
+ int titanFallsFirst
+ int titanEmbarks
+ int rodeos
+ int rodeosFromEject
+ int timesEjected
+ int timesEjectedNuclear
+ int burnCardsEarned
+ int burnCardsSpent
+ int boostsActivated
+ int spectreLeeches
+ int spectreLeechesByMap[maps]
+ int evacsAttempted
+ int evacsSurvived
+ int flagsCaptured
+ int flagsReturned
+ int arcCannonMultiKills
+ int gruntsConscripted
+ int hardpointsCaptured
+ int challengeTiersCompleted
+ int challengesCompleted
+ int dailyChallengesCompleted
+ int timesLastTitanRemaining
+ int killingSprees
+ int coopChallengesCompleted
+ int forgedCertificationsUsed
+ int regenForgedCertificationsUsed
+$STRUCT_END
+
+
+$STRUCT_START sFDStats
+ int arcMinesPlaced
+ int turretsPlaced
+ int rodeos
+ int rodeoNukes
+ int arcMineZaps
+ int turretKills
+ int harvesterBoosts
+ int wavesComplete
+ int easyWins
+ int normalWins
+ int hardWins
+ int masterWins
+ int insaneWins
+ int highestTitanFDLevel
+$STRUCT_END
+
+
+$STRUCT_START sTitanStats
+ int pilots
+ int titansTotal
+ int ejections
+ int titansWhileDoomed
+ int titanDamage
+ int titansAsPrime
+ int pilotsAsPrime
+ int executionsAsPrime
+ int coresEarned
+ int matchesByDifficulty[5]
+ int perfectMatchesByDifficulty[5]
+$STRUCT_END
+
+sGameStats gameStats
+sMapStats mapStats[maps]
+sHoursPlayed timeStats
+sMilesTraveled distanceStats
+sWeaponStats weaponStats[loadoutWeaponsAndAbilities]
+sWeaponKillStats weaponKillStats[loadoutWeaponsAndAbilities]
+sKillStats killStats
+sDeathStats deathStats
+sMiscStats miscStats
+sFDStats fdStats
+sTitanStats titanStats[titanClasses]
+
+float kdratio_lifetime
+float kdratio_lifetime_pvp
+float kdratio_match[10]
+float kdratiopvp_match[10]
+
+int winStreak
+int highestWinStreakEver
+bool winStreakIsDraws
+int winLossHistory[10] // int instead of bool so we can have win, loss, and draw
+int winLossHistorySize
+
+int mostProjectilesCollectedInVortex
+int blackMarketItemsBought
+
+bool respawnKillInfected
+
+
+//#########################
+// WEAPONS
+//#########################
+
+$STRUCT_START weaponMain
+ sWeaponStats weaponStats
+ sWeaponKillStats weaponKillStats
+
+ int weaponXP
+ int previousWeaponXP
+
+ int proScreenKills
+ int previousProScreenKills
+
+ // bitfields
+ int newMods
+ int unlockedMods
+ int newWeaponSkins[5]
+ int unlockedWeaponSkins[5]
+ int newPrimeWeaponSkins[6]
+ int unlockedPrimeWeaponSkins[6]
+ int newFeatures
+ int unlockedFeatures
+$STRUCT_END
+
+$STRUCT_START weaponOffhand
+ sWeaponStats weaponStats
+ sWeaponKillStats weaponKillStats
+$STRUCT_END
+
+$STRUCT_START titanMain
+ // bitfields
+ int newPassives[2]
+ int unlockedPassives[2]
+ int newSkins[5]
+ int unlockedSkins[5]
+ int newPrimeSkins[2] //TODO: These are warpaints, not prime titan related and should be renamed next game! Too late since we shipped with it
+ int unlockedPrimeSkins[2] //TODO: These are warpaints, not prime titan related and should be renamed next game! Too late since we shipped with it
+ int newWeaponSkins[5]
+ int unlockedWeaponSkins[5]
+ int newPrimeWeaponSkins
+ int unlockedPrimeWeaponSkins
+ int newTitanDecals[3]
+ int unlockedTitanDecals[3]
+ int newPrimeTitanDecals
+ int unlockedPrimeTitanDecals
+ int unlockedFDUpgrades[2]
+ int newFDUpgrades[2]
+$STRUCT_END
+
+weaponMain pilotWeapons[35]
+weaponOffhand pilotOffhands[35]
+weaponMain titanWeapons[15]
+weaponOffhand titanOffhands[30]
+
+titanMain titanChassis[12]
+
+bool hasSeenStore
+
+// bitfields
+int newPilotSkins[5]
+int unlockedPilotSkins[5]
+int newPrimePilotSkins
+int unlockedPrimePilotSkins
+
+int newPilotWeapons[2]
+int unlockedPilotWeapons[2]
+int newPilotOffhands[2]
+int unlockedPilotOffhands[2]
+int newPilotPassives
+int unlockedPilotPassives
+
+int newTitanOffhands[2]
+int unlockedTitanOffhands[2]
+int newTitanPassives
+int unlockedTitanPassives
+int newTitanChassis
+int unlockedTitanChassis
+int newPrimeTitans
+int unlockedPrimeTitans
+int newPilotSuits
+int unlockedPilotSuits
+int newPilotExecutions
+int unlockedPilotExecutions
+
+int unlockedFeatures[2]
+int newFeatures[2]
+
+int unlockedBoosts
+int newBoosts
+
+int unlockedFactions
+int newFactions
+
+int unlockedCallingCards[16]
+int newCallingCards[16]
+
+int unlockedCallsignIcons[7]
+int newCallsignIcons[7]
+
+int unlockedCommsIcons[5]
+int newCommsIcons[5]
+
+int newTitanExecutions
+int unlockedTitanExecutions
+
+//#########################
+// CHALLENGES
+//#########################
+
+$ENUM_START challenge
+ NULL
+ // General
+
+ ch_games_played
+ ch_games_won
+ ch_games_mvp
+ ch_titan_falls
+ ch_rodeos
+ ch_times_ejected
+ ch_spectres_leeched
+
+ // Time
+
+ ch_hours_played
+ ch_hours_played_pilot
+ ch_hours_played_titan
+ ch_hours_wallhang
+
+ // Distance
+
+ ch_dist_total
+ ch_dist_pilot
+ ch_dist_titan
+ ch_dist_wallrun
+ ch_dist_inair
+ ch_dist_zipline
+ ch_dist_on_friendly_titan
+ ch_dist_on_enemy_titan
+
+ // Kills
+
+ ch_grunt_kills
+ ch_spectre_kills
+ ch_marvin_kills
+ ch_first_strikes
+ ch_ejecting_pilot_kills
+ ch_kills_while_ejecting
+ ch_cloaked_pilot_kills
+ ch_kills_while_cloaked
+ ch_wallrunning_pilot_kills
+ ch_wallhanging_pilot_kills
+ ch_kills_while_wallrunning
+ ch_kills_while_wallhanging
+ ch_pilotExecutePilot
+ ch_pilotKickMelee
+ ch_pilotKickMeleePilot
+ ch_titanMelee
+ ch_titanMeleePilot
+ ch_titanStepCrush
+ ch_titanStepCrushPilot
+ ch_titanExocutionStryder
+ ch_titanExocutionBuddy
+ ch_titanExocutionAtlas
+ ch_titanExocutionOgre
+ ch_titanFallKill
+ ch_petTitanKillsFollowMode
+ ch_petTitanKillsGuardMode
+ ch_rodeo_kills
+
+ // Titan Primary
+
+ ch_40mm_kills
+ ch_40mm_pilot_kills
+ ch_40mm_titan_kills
+ ch_40mm_spectre_kills
+ ch_40mm_grunt_kills
+ ch_40mm_hours_used
+ ch_40mm_crits
+
+ ch_xo16_kills
+ ch_xo16_pilot_kills
+ ch_xo16_titan_kills
+ ch_xo16_spectre_kills
+ ch_xo16_grunt_kills
+ ch_xo16_hours_used
+ ch_xo16_headshots
+ ch_xo16_crits
+
+ ch_titan_sniper_kills
+ ch_titan_sniper_pilot_kills
+ ch_titan_sniper_titan_kills
+ ch_titan_sniper_spectre_kills
+ ch_titan_sniper_grunt_kills
+ ch_titan_sniper_hours_used
+ ch_titan_sniper_crits
+
+ ch_rocket_launcher_kills
+ ch_rocket_launcher_pilot_kills
+ ch_rocket_launcher_titan_kills
+ ch_rocket_launcher_spectre_kills
+ ch_rocket_launcher_grunt_kills
+ ch_rocket_launcher_hours_used
+
+ ch_triple_threat_kills
+ ch_triple_threat_pilot_kills
+ ch_triple_threat_titan_kills
+ ch_triple_threat_spectre_kills
+ ch_triple_threat_grunt_kills
+ ch_triple_threat_hours_used
+
+ // Titan Ordnance
+
+ ch_salvo_rockets_kills
+ ch_salvo_rockets_pilot_kills
+ ch_salvo_rockets_titan_kills
+ ch_salvo_rockets_spectre_kills
+ ch_salvo_rockets_grunt_kills
+ ch_salvo_rockets_hours_used
+
+ ch_homing_rockets_titan_kills
+ ch_homing_rockets_hours_used
+
+ ch_dumbfire_rockets_kills
+ ch_dumbfire_rockets_pilot_kills
+ ch_dumbfire_rockets_titan_kills
+ ch_dumbfire_rockets_spectre_kills
+ ch_dumbfire_rockets_grunt_kills
+ ch_dumbfire_rockets_hours_used
+
+ ch_shoulder_rockets_titan_kills
+ ch_shoulder_rockets_hours_used
+
+ // Pilot Primary
+
+ ch_smart_pistol_kills
+ ch_smart_pistol_pilot_kills
+ ch_smart_pistol_spectre_kills
+ ch_smart_pistol_grunt_kills
+ ch_smart_pistol_hours_used
+
+ ch_shotgun_kills
+ ch_shotgun_pilot_kills
+ ch_shotgun_spectre_kills
+ ch_shotgun_grunt_kills
+ ch_shotgun_hours_used
+
+ ch_r97_kills
+ ch_r97_pilot_kills
+ ch_r97_spectre_kills
+ ch_r97_grunt_kills
+ ch_r97_hours_used
+ ch_r97_headshots
+
+ ch_car_kills
+ ch_car_pilot_kills
+ ch_car_spectre_kills
+ ch_car_grunt_kills
+ ch_car_hours_used
+ ch_car_headshots
+
+ ch_lmg_kills
+ ch_lmg_pilot_kills
+ ch_lmg_spectre_kills
+ ch_lmg_grunt_kills
+ ch_lmg_hours_used
+ ch_lmg_headshots
+
+ ch_rspn101_kills
+ ch_rspn101_pilot_kills
+ ch_rspn101_spectre_kills
+ ch_rspn101_grunt_kills
+ ch_rspn101_hours_used
+ ch_rspn101_headshots
+
+ ch_hemlok_kills
+ ch_hemlok_pilot_kills
+ ch_hemlok_spectre_kills
+ ch_hemlok_grunt_kills
+ ch_hemlok_hours_used
+ ch_hemlok_headshots
+
+ ch_g2_kills
+ ch_g2_pilot_kills
+ ch_g2_spectre_kills
+ ch_g2_grunt_kills
+ ch_g2_hours_used
+ ch_g2_headshots
+
+ ch_dmr_kills
+ ch_dmr_pilot_kills
+ ch_dmr_spectre_kills
+ ch_dmr_grunt_kills
+ ch_dmr_hours_used
+ ch_dmr_headshots
+
+ ch_sniper_kills
+ ch_sniper_pilot_kills
+ ch_sniper_spectre_kills
+ ch_sniper_grunt_kills
+ ch_sniper_hours_used
+
+ // Pilot Secondary
+
+ ch_smr_titan_kills
+ ch_smr_crits
+
+ ch_mgl_titan_kills
+
+ ch_archer_titan_kills
+
+ ch_defender_titan_kills
+ ch_defender_crits
+
+ // Pilot Ordnance
+
+ ch_frag_grenade_throws
+ ch_frag_grenade_kills
+ ch_frag_grenade_pilot_kills
+ ch_frag_grenade_grunt_kills
+
+ ch_emp_grenade_throws
+ ch_emp_grenade_kills
+ ch_emp_grenade_pilot_kills
+ ch_emp_grenade_grunt_kills
+ ch_emp_grenade_spectre_kills
+
+ ch_proximity_mine_throws
+ ch_proximity_mine_kills
+ ch_proximity_mine_pilot_kills
+ ch_proximity_mine_grunt_kills
+
+ ch_satchel_throws
+ ch_satchel_kills
+ ch_satchel_pilot_kills
+ ch_satchel_grunt_kills
+$ENUM_END
+
+$ENUM_START dailychallenge
+ NULL
+ // Dailies
+
+ ch_daily_xo16_pilot_kills
+ ch_daily_emp_grenade_kills
+ ch_daily_kills_nuclear_core
+$ENUM_END
+
+$STRUCT_START eChallenge
+ float progress
+ float previousProgress
+$STRUCT_END
+
+eChallenge challenges[challenge]
+eChallenge dailychallenges[dailychallenge]
+
+$STRUCT_START activeDailyChallenge
+ dailychallenge ref
+ int day
+$STRUCT_END
+
+activeDailyChallenge activeDailyChallenges[9] // holds the players daily challenge refs
+
+int trackedChallenges[3]
+int EOGTrackedChallenges[3]
+string{64} trackedChallengeRefs[3]
+string{64} EOGTrackedChallengeRefs[3]
+int dailyChallengeDayIndex
+bool newDailyChallenges
+
+//#########################
+// Post Game
+//#########################
+
+bool isPostGameScoreboardValid
+
+$STRUCT_START ePostGamePlayer
+ string{32} name
+ string{22} xuid
+ int level
+ int gen
+ int team
+ int scores[4]
+ bool playingRanked
+ int rank
+ int callsignIconIndex
+ float matchPerformance
+$STRUCT_END
+
+$STRUCT_START ePostGameData
+ int gameMode
+ int map
+ string{22} myXuid
+ int myTeam
+ int maxTeamSize
+ faction factionIMC
+ faction factionMCOR
+ int scoreIMC
+ int scoreMCOR
+ bool teams
+ bool privateMatch
+ bool ranked
+ bool hadMatchLossProtection
+ recentUnlock challengeUnlocks[6]
+ ePostGamePlayer players[16]
+$STRUCT_END
+
+ePostGameData postGameData
+
+//#########################
+// FD Awards
+//#########################
+
+bool isFDPostGameScoreboardValid
+
+$STRUCT_START eFDPostGamePlayer
+ string{32} name
+ string{22} xuid
+ int awardId
+ float awardValue
+ int suitIndex
+$STRUCT_END
+
+$STRUCT_START eFDPostGameData
+ int gameMode
+ int map
+ int myIndex
+ int numPlayers
+ eFDPostGamePlayer players[4]
+$STRUCT_END
+
+eFDPostGameData postGameDataFD
+
+// Track Gooser progress ( ejecting pilot kills ) before requirements changed so we can reward these players later if they did it the hard way
+int previousGooserProgress
+
+
+//#########################
+// GAME HISTORY
+//#########################
+
+// If these are size adjusted, re-initialize with InitPlayerMapHistory() and InitPlayerModeHistory()
+int mapHistory[24]
+int modeHistory[10]
+string{32} lastPlaylist
+
+//#########################
+// Dailies
+//#########################
+
+int lastDailyMatchVictory
+int lastTimePlayed
+int lastTimeLoggedIn
+
+$STRUCT_START struct_ranked
+ bool isPlayingRanked
+ int currentRank // deprecated but still used by code - need to fix
+$STRUCT_END
+
+int abandonCountForMode[gameModes]
+gameModes lastAbandonedMode
+int lastAbandonTime
+
+struct_ranked ranked
diff --git a/Northstar.CustomServers/mod.json b/Northstar.CustomServers/mod.json
new file mode 100644
index 000000000..a9e4f36d3
--- /dev/null
+++ b/Northstar.CustomServers/mod.json
@@ -0,0 +1,56 @@
+{
+ "ApiId" : "Northstar.CustomServers",
+ "Name" : "Northstar.CustomServers",
+ "Description" : "Various script patches to fix and reimplement functionality for custom multiplayer servers",
+ "Authors" : [
+ "BobTheBob"
+ ],
+ "Contacts" : [
+ "BobTheBob#1150"
+ ],
+ "Version" : "0.1",
+ "CustomScripts": [
+ {
+ "Path": "_misc.gnut",
+ "RunOn": "SERVER && MP",
+ },
+ {
+ "Path": "_store.gnut",
+ "RunOn": "SERVER && MP",
+ },
+ {
+ "Path": "_script_movers.gnut",
+ "RunOn": "SERVER && MP",
+ },
+
+ {
+ "Path": "mp/levels/_lf_maps_shared.gnut",
+ "RunOn": "SERVER && MP"
+ },
+
+ {
+ "Path": "gamemodes/sh_gamemodes_custom.gnut",
+ "RunOn": "(CLIENT || SERVER) && MP"
+ },
+
+ {
+ "Path": "sh_remote_functions_mp_custom.gnut",
+ "RunOn": "(CLIENT || SERVER) && MP"
+ },
+
+ {
+ "Path": "mp/_classic_mp_dropship_intro.gnut",
+ "RunOn": "SERVER && MP",
+ },
+ {
+ "Path": "mp/_classic_mp_no_intro.gnut",
+ "RunOn": "SERVER && MP"
+ },
+
+ {
+ "Path": "_loadouts_mp.gnut",
+ "RunOn": "SERVER && MP",
+ "ServerCallback": "SvLoadoutsMP_Init"
+ },
+ ]
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_drone.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_drone.txt
new file mode 100644
index 000000000..64f151c78
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_drone.txt
@@ -0,0 +1,33 @@
+Selector Common
+
+ SelectSchedule_Flinch
+ SelectSchedule_LayeredBehavior
+
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_PatrolRun
+ SelectSchedule_IdleStand
+
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_PatrolRun
+ SelectSchedule_IdleStand
+
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_RangeAttack
+ SelectSchedule_IdleStand
+
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_dropship.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_dropship.txt
new file mode 100644
index 000000000..a3a712e92
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_dropship.txt
@@ -0,0 +1,21 @@
+Selector Common
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_IdleStand
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_IdleStand
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_IdleStand
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_flyer.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_flyer.txt
new file mode 100644
index 000000000..a7a995f5b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_flyer.txt
@@ -0,0 +1,33 @@
+Selector Common
+
+ SelectSchedule_Flinch
+ SelectSchedule_LayeredBehavior
+
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_PatrolRun
+ SelectSchedule_IdleStand
+
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_PatrolRun
+ SelectSchedule_IdleStand
+
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_FearEnemy
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_MeleeAttack
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_IdleStand
+
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_frag_drone.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_frag_drone.txt
new file mode 100644
index 000000000..c7459d714
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_frag_drone.txt
@@ -0,0 +1,27 @@
+Selector Common
+
+ SelectSchedule_LayeredBehavior
+
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_CombatFace
+
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_goliath.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_goliath.txt
new file mode 100644
index 000000000..c4dad6ab0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_goliath.txt
@@ -0,0 +1,31 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_LayeredBehavior
+
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_FearEnemy
+ SelectSchedule_NewEnemySignal
+ SelectSchedule_MeleeAttack
+ SelectSchedule_ChaseEnemy
+
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_gunship.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_gunship.txt
new file mode 100644
index 000000000..b62c07933
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_gunship.txt
@@ -0,0 +1,30 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_PatrolRun
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_PatrolRun
+Selector_End
+
+
+Selector Combat
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_StrafeDodge
+ SelectSchedule_AttackRun
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttack
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_MoveToWeaponRange
+ SelectSchedule_ChaseEnemy
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_marvin.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_marvin.txt
new file mode 100644
index 000000000..351b9ff7c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_marvin.txt
@@ -0,0 +1,22 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_LayeredBehavior
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Idle
+
+Selector_End
+
+
+Selector Alert
+
+Selector_End
+
+
+Selector Combat
+
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan.txt
new file mode 100644
index 000000000..74742e99b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan.txt
@@ -0,0 +1,58 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+// SelectSchedule_StrafeDodge
+// SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+// SelectSchedule_EvasiveLowAmmo
+// SelectSchedule_ChaseEnemy
+// SelectSchedule_EngagementDistMax
+ SelectSchedule_StationaryLowAmmo
+// SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+// SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+// SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_MeleeAttack
+// SelectSchedule_BackwardDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_enhanced.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_enhanced.txt
new file mode 100644
index 000000000..8e1bc7c1b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_enhanced.txt
@@ -0,0 +1,59 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+// SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_ChaseEnemy
+// SelectSchedule_EngagementDistMax
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+// SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+// SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_enhanced_guard.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_enhanced_guard.txt
new file mode 100644
index 000000000..e68c6ab5c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_enhanced_guard.txt
@@ -0,0 +1,38 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlockedByFriend
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_guard.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_guard.txt
new file mode 100644
index 000000000..07dacfaad
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_mp_auto_titan_guard.txt
@@ -0,0 +1,43 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ //SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+// SelectSchedule_StrafeDodge
+// SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+// SelectSchedule_EvasiveLowAmmo
+// SelectSchedule_ChaseEnemy
+// SelectSchedule_EngagementDistMax
+ SelectSchedule_StationaryLowAmmo
+// SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+// SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlockedByFriend
+// SelectSchedule_CantSeeEnemy
+// SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite.txt
new file mode 100644
index 000000000..e68f7ddbd
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite.txt
@@ -0,0 +1,63 @@
+Selector Common
+
+ SelectSchedule_RodeoAttack
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ //SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ //SelectSchedule_StrafeDodge
+ //SelectSchedule_ForwardDodge
+
+ SelectSchedule_MeleeAttack
+
+ //CAI_Combatant::SelectSchedule_CombatNewEnemy
+ SelectSchedule_SwitchWeapon
+ SelectSchedule_EvasiveLowAmmo
+
+ SelectSchedule_CircleStrafe
+ CAI_Combatant::SelectSchedule_ShootingCover
+ //SelectSchedule_Taunt health > 0.8
+
+ SelectSchedule_ThrowGrenade
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttackTwitch
+ SelectSchedule_RangeAttack
+ //SelectSchedule_ChaseEnemy health > 0.4
+
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatEvasive
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_ChaseEnemy health > 0.4
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CircleStrafe
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin.txt
new file mode 100644
index 000000000..278d48732
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin.txt
@@ -0,0 +1,57 @@
+Selector Common
+
+ SelectSchedule_RodeoAttack
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ SelectSchedule_Flinch
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_LongJump
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ //SelectSchedule_Taunt health > 0.8
+ SelectSchedule_ChaseEnemy health > 0.4
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CircleStrafe
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_ChaseEnemy health > 0.4
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CircleStrafe
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin_cqb.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin_cqb.txt
new file mode 100644
index 000000000..282e9b9b7
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin_cqb.txt
@@ -0,0 +1,64 @@
+Selector Common
+
+ SelectSchedule_RodeoAttack
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ SelectSchedule_Flinch
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_LongJump
+ SelectSchedule_StrafeDodge
+// SelectSchedule_DisengageAfterMeleeAttack
+ SelectSchedule_ForwardDodge
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_Taunt
+ SelectSchedule_MoveToWeaponRange
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CircleStrafe
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ SelectSchedule_LongJump
+ SelectSchedule_StrafeDodge
+ SelectSchedule_DisengageAfterMeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_RangeAttack2
+ //SelectSchedule_RangeAttack
+ //SelectSchedule_Taunt
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CircleStrafe
+ SelectSchedule_CombatFace
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin_sniper.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin_sniper.txt
new file mode 100644
index 000000000..144654ab0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_pilot_elite_assassin_sniper.txt
@@ -0,0 +1,64 @@
+Selector Common
+
+ SelectSchedule_RodeoAttack
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ SelectSchedule_Flinch
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_LongJump
+ SelectSchedule_StrafeDodge
+ SelectSchedule_DisengageAfterMeleeAttack
+ CAI_Combatant::SelectSchedule_Snipe
+ SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_RangeAttack
+ //SelectSchedule_Taunt
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CircleStrafe
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ SelectSchedule_LongJump
+ SelectSchedule_StrafeDodge
+ SelectSchedule_DisengageAfterMeleeAttack
+ CAI_Combatant::SelectSchedule_Snipe
+ SelectSchedule_BackwardDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_RangeAttack2
+ //SelectSchedule_RangeAttack
+ //SelectSchedule_Taunt
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CircleStrafe
+ SelectSchedule_CombatFace
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_prowler.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_prowler.txt
new file mode 100644
index 000000000..f5feb8c76
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_prowler.txt
@@ -0,0 +1,42 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ //SelectSchedule_ReactJumpedOver
+ SelectSchedule_ReactCloak
+ SelectSchedule_ReactBullet
+ SelectSchedule_ReactSurprised
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_FearEnemy
+ SelectSchedule_NewEnemySignal
+ SelectSchedule_MeleeAttack
+ SelectSchedule_MeleeAttackJumpUp
+ SelectSchedule_CircleStrafe
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_TakeCoverFromUnreachableEnemy
+ SelectSchedule_MeleeAttackWait
+
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_prowler_cqb.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_prowler_cqb.txt
new file mode 100644
index 000000000..bff35f0ba
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_prowler_cqb.txt
@@ -0,0 +1,38 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ReactCloak
+ SelectSchedule_ReactBullet
+ SelectSchedule_ReactSurprised
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_MeleeAttackJumpUp
+ SelectSchedule_NewEnemySignal
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_MeleeAttackWait
+
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_soldier.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_soldier.txt
new file mode 100644
index 000000000..106014c3a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_soldier.txt
@@ -0,0 +1,53 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ReactJumpedOver
+ SelectSchedule_ReactCloak
+ SelectSchedule_ReactSurprised
+ SelectSchedule_ReactBullet
+ SelectSchedule_ReactFriendlyPlayer
+ SelectSchedule_FearEnemy
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ CAI_Combatant::SelectSchedule_ShootingCover
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_AntiMassiveEnemy
+
+ SelectSchedule_MeleeAttack
+ CAI_Combatant::SelectSchedule_ShootingCover
+ SelectSchedule_ChaseEnemy
+
+ CAI_Combatant::SelectSchedule_CombatNewEnemy
+ SelectSchedule_EvasiveLowAmmo
+
+ CAI_Combatant::SelectSchedule_MoveToSquadAssignedNode
+ //SelectSchedule_ThrowGrenade
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttackTwitch
+ SelectSchedule_RangeAttack
+
+ CAI_Combatant::SelectSchedule_CombatOccludedEnemy
+ CAI_Combatant::SelectSchedule_EnemyNotAttackable
+
+ SelectSchedule_CombatFace
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_sp_auto_titan.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_sp_auto_titan.txt
new file mode 100644
index 000000000..be309949d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_sp_auto_titan.txt
@@ -0,0 +1,79 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_ChangeToDesiredStance
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_BackwardDodge
+ SelectSchedule_MeleeAttack
+// SelectSchedule_EvasiveLowAmmo
+// SelectSchedule_ChaseEnemy
+// SelectSchedule_EngagementDistMax
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+// SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.5
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_sp_npc_titan_proto_stasisgun.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_sp_npc_titan_proto_stasisgun.txt
new file mode 100644
index 000000000..b93d22497
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_sp_npc_titan_proto_stasisgun.txt
@@ -0,0 +1,60 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_ChangeToDesiredStance
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+// SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_EngagementDistMin
+// SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_ChaseEnemy
+// SelectSchedule_EngagementDistMax
+ SelectSchedule_StationaryLowAmmo
+// SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+// SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+// SelectSchedule_FearEnemy
+ SelectSchedule_StrafeDodge
+ SelectSchedule_MeleeAttack
+// SelectSchedule_BackwardDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_sp_soldier.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_sp_soldier.txt
new file mode 100644
index 000000000..38ed82e52
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_sp_soldier.txt
@@ -0,0 +1,57 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ReactJumpedOver
+ SelectSchedule_ReactCloak
+ SelectSchedule_ReactSurprised
+ SelectSchedule_ReactBullet
+ SelectSchedule_ReactFriendlyPlayer
+ SelectSchedule_FearEnemy
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ CAI_Combatant::SelectSchedule_ShootingCover
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_AntiMassiveEnemy
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_SwitchWeapon
+ CAI_Combatant::SelectSchedule_ShootingCover
+ SelectSchedule_ChaseEnemy
+
+ CAI_Combatant::SelectSchedule_CombatNewEnemy
+ CAI_Combatant::SelectSchedule_MoveToSquadAssignedNode
+
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_BreakEnemyLosIfOutsideEngageDist
+
+ SelectSchedule_ThrowGrenade
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttackTwitch
+ SelectSchedule_RangeAttack
+
+ CAI_Combatant::SelectSchedule_CombatOccludedEnemy
+ CAI_Combatant::SelectSchedule_EnemyNotAttackable
+
+ SelectSchedule_CombatFace
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_spectre.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_spectre.txt
new file mode 100644
index 000000000..66937c70b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_spectre.txt
@@ -0,0 +1,47 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_ReactJumpedOver
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ CAI_Combatant::SelectSchedule_ShootingCover
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_AntiMassiveEnemy
+
+ SelectSchedule_MeleeAttack
+ CAI_Combatant::SelectSchedule_ShootingCover
+ SelectSchedule_EvasiveLowAmmo
+
+ CAI_Combatant::SelectSchedule_MoveToSquadAssignedNode
+ //SelectSchedule_ThrowGrenade
+ //SelectSchedule_RangeAttackEvasive
+ //SelectSchedule_LongJump
+ SelectSchedule_RangeAttackTwitch
+ SelectSchedule_RangeAttack
+
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_MoveToWeaponRange
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_AtEnemyLKP
+ SelectSchedule_CombatFace
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_stalker.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_stalker.txt
new file mode 100644
index 000000000..9bc1782a8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_stalker.txt
@@ -0,0 +1,39 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_ReactJumpedOver
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_ChaseEnemy
+
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+
+ SelectSchedule_EnemyTooClose
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_MoveToWeaponRange
+ SelectSchedule_AtEnemyLKP
+ SelectSchedule_CombatFace
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_stalker_crawling.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_stalker_crawling.txt
new file mode 100644
index 000000000..fac61cf58
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_stalker_crawling.txt
@@ -0,0 +1,32 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_MeleeAttack
+ SelectSchedule_ChaseEnemy
+
+ SelectSchedule_LayeredBehavior
+
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_IdleStand
+
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_CombatFace
+
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_CombatFace
+
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_super_spectre.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_super_spectre.txt
new file mode 100644
index 000000000..341dd67a7
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_super_spectre.txt
@@ -0,0 +1,58 @@
+Selector Common
+
+ //SelectSchedule_FallToGround
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ReactSurprised
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_MissileDodge
+ SelectSchedule_LongJump
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_SpecialAttack
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_ChargeAttack
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_EngagementDistMax
+ //SelectSchedule_EngagementDistMin
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ SelectSchedule_MissileDodge
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_EnemyAimDodge
+ //SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_SpecialAttack
+ SelectSchedule_RangeAttack
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_EnemyTooClose
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_EngagementDistMax
+ //SelectSchedule_EngagementDistMin
+ SelectSchedule_CombatFace
+Selector_End \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan.txt
new file mode 100644
index 000000000..6070c68eb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan.txt
@@ -0,0 +1,81 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ //SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ //SelectSchedule_EvasiveLowAmmo
+ //SelectSchedule_ChaseEnemy
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ //SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ //SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_buddy.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_buddy.txt
new file mode 100644
index 000000000..5636f2677
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_buddy.txt
@@ -0,0 +1,81 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ //CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ //SelectSchedule_EvasiveLowAmmo
+ //SelectSchedule_ChaseEnemy
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ //SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ //SelectSchedule_ChaseEnemy
+ //SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_long_range.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_long_range.txt
new file mode 100644
index 000000000..0c378fe94
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_long_range.txt
@@ -0,0 +1,79 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ //SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ //SelectSchedule_EvasiveLowAmmo
+ //SelectSchedule_ChaseEnemy
+ SelectSchedule_BackwardDodge
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo health < 0.5
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_melee.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_melee.txt
new file mode 100644
index 000000000..d1f1b2677
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_melee.txt
@@ -0,0 +1,80 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ //SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ //SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ //SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_melee_core.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_melee_core.txt
new file mode 100644
index 000000000..821938301
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_melee_core.txt
@@ -0,0 +1,38 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_meteor.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_meteor.txt
new file mode 100644
index 000000000..e189a512b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_meteor.txt
@@ -0,0 +1,81 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ //SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ //SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ //SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ //SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_minigun.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_minigun.txt
new file mode 100644
index 000000000..8b13b689f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_minigun.txt
@@ -0,0 +1,77 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ //SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ //SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_EngagementDistMax
+ //SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ //SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ //SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_minigun_nuke.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_minigun_nuke.txt
new file mode 100644
index 000000000..037f15054
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_ogre_minigun_nuke.txt
@@ -0,0 +1,77 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ //CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ //SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ //SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_EngagementDistMax
+ //SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ //SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ //SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ //SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_rocketeer.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_rocketeer.txt
new file mode 100644
index 000000000..c9a25afec
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_rocketeer.txt
@@ -0,0 +1,83 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ShootJump
+ SelectSchedule_MeleeAttack
+ //SelectSchedule_EvasiveLowAmmo
+ //SelectSchedule_ChaseEnemy
+ SelectSchedule_EngagementDistMax
+ SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ //SelectSchedule_ForwardDodge
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ //SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ShootJump
+ SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_CircleStrafe
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ShootJump
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_shotgun.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_shotgun.txt
new file mode 100644
index 000000000..fe78babd4
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_shotgun.txt
@@ -0,0 +1,82 @@
+Selector Common
+
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ SelectSchedule_PressToInitiateSyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_MeleeAttack
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_EngagementDistMax
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ //SelectSchedule_MoveToWeaponRange
+ SelectSchedule_CombatFace
+Selector_End
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ //SelectSchedule_CircleStrafe
+ //SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ //SelectSchedule_FearEnemy
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behavior_titan_sniper.txt b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_sniper.txt
new file mode 100644
index 000000000..87fa003f8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behavior_titan_sniper.txt
@@ -0,0 +1,82 @@
+Selector Common
+
+ SelectSchedule_FallToGround
+ SelectSchedule_MissileDodge
+ CNPC_Titan::SelectSchedule_TitanFlightCore
+ CNPC_Titan::SelectSchedule_PhysEntKnock
+ SelectSchedule_Flinch
+ SelectSchedule_DisplaceFromDangerousArea
+ SelectSchedule_ChangeToDesiredStance
+ SelectSchedule_SyncedMeleeAttack
+ CNPC_Titan::SelectSchedule_GotoDefensivePlacement
+ SelectSchedule_LayeredBehavior
+Selector_End
+
+
+Selector Idle
+
+ SelectSchedule_Idle
+Selector_End
+
+
+Selector Alert
+
+ SelectSchedule_Alert
+Selector_End
+
+
+Selector Combat
+
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ShootJump
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+// SelectSchedule_EngagementDistMax
+ SelectSchedule_EngagementDistMin
+// SelectSchedule_ChaseEnemy
+// SelectSchedule_ForwardDodge
+ SelectSchedule_EvasiveLowAmmo health < 0.5
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatAggressive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ShootJump
+ SelectSchedule_ForwardDodge
+ SelectSchedule_ChaseEnemy
+ SelectSchedule_StationaryLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
+
+
+Selector CombatEvasive
+
+ SelectSchedule_MeleeAttack
+ SelectSchedule_BackwardDodge
+ SelectSchedule_StrafeDodge
+ SelectSchedule_ShootJump
+ SelectSchedule_TakeCoverFromEnemy health > 0.1
+ SelectSchedule_EngagementDistMin
+ SelectSchedule_CircleStrafe
+ SelectSchedule_EvasiveLowAmmo
+ SelectSchedule_RangeAttackEvasive
+ SelectSchedule_RangeAttack2
+ SelectSchedule_RangeAttack
+ SelectSchedule_WeaponBlocked
+ SelectSchedule_CantSeeEnemy
+ SelectSchedule_CombatFace
+Selector_End
diff --git a/Northstar.CustomServers/scripts/aibehavior/behaviors.txt b/Northstar.CustomServers/scripts/aibehavior/behaviors.txt
new file mode 100644
index 000000000..5fac79770
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/behaviors.txt
@@ -0,0 +1,36 @@
+behavior_drone
+behavior_dropship
+behavior_flyer
+behavior_frag_drone
+behavior_goliath
+behavior_gunship
+behavior_marvin
+behavior_mp_auto_titan
+behavior_mp_auto_titan_enhanced
+behavior_mp_auto_titan_guard
+behavior_mp_auto_titan_enhanced_guard
+behavior_pilot_elite
+behavior_pilot_elite_assassin
+behavior_pilot_elite_assassin_cqb
+behavior_pilot_elite_assassin_sniper
+behavior_prowler
+behavior_prowler_cqb
+behavior_soldier
+behavior_sp_auto_titan
+behavior_sp_npc_titan_proto_stasisgun
+behavior_sp_soldier
+behavior_spectre
+behavior_stalker
+behavior_stalker_crawling
+behavior_super_spectre
+behavior_titan
+behavior_titan_buddy
+behavior_titan_long_range
+behavior_titan_rocketeer
+behavior_titan_shotgun
+behavior_titan_sniper
+behavior_titan_melee
+behavior_titan_melee_core
+behavior_titan_ogre_minigun
+behavior_titan_ogre_minigun_nuke
+behavior_titan_ogre_meteor
diff --git a/Northstar.CustomServers/scripts/aibehavior/common_schedules.txt b/Northstar.CustomServers/scripts/aibehavior/common_schedules.txt
new file mode 100644
index 000000000..8ef03ec42
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aibehavior/common_schedules.txt
@@ -0,0 +1,2681 @@
+//=========================================================
+// > Fail
+// This schedule itself can fail because the NPC may
+// be unable to finish the stop moving. If so, fall back
+// the a fail schedule that has no stop moving in it.
+//=========================================================
+Schedule
+
+ SCHED_FAIL
+
+ Tasks
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FAIL_NOSTOP
+ TASK_STOP_MOVING 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 0.5
+ TASK_WAIT_PVS 0
+
+ Interrupts
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_GIVE_WAY
+End_Schedule
+
+//=========================================================
+// > Fail without stop moving, which can fail.
+//=========================================================
+Schedule
+
+ SCHED_FAIL_NOSTOP
+
+ Tasks
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 0.5
+ TASK_WAIT_PVS 0
+
+ Interrupts
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_GIVE_WAY
+ End_Schedule
+
+//===============================================
+// > Idle_Stand
+//===============================================
+Schedule
+
+ SCHED_IDLE_STAND
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY 5
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 1
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_FEAR
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+ COND_GIVE_WAY
+ COND_HEAR_PLAYER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_COMBAT
+ COND_HEAR_BULLET_IMPACT
+ COND_IDLE_INTERRUPT
+ COND_REACT_SURPRISED
+ COND_REACT_FRIENDLY_PLAYER
+End_Schedule
+
+//===============================================
+// > IdleWalk
+//===============================================
+Schedule
+
+ SCHED_IDLE_WALK
+
+ Tasks
+ TASK_WALK_PATH 9999
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_WAIT_PVS 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY // in deference to scripted schedule where the enemy was slammed, thus no COND_NEW_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+ COND_HEAR_COMBAT
+ COND_HEAR_BULLET_IMPACT
+ COND_REACT_SURPRISED
+End_Schedule
+
+
+//===============================================
+// > SCHED_SWITCH_TO_PENDING_WEAPON
+//===============================================
+Schedule
+
+ SCHED_SWITCH_TO_PENDING_WEAPON
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SWITCH_WEAPON
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+
+//===============================================
+// > RangeAttackWait
+//===============================================
+Schedule
+
+ SCHED_RANGE_ATTACK_WAIT
+
+ Tasks
+ TASK_WAIT_BETWEEN_BURSTS 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_HEAVY_DAMAGE
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_GIVE_WAY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_SWITCH_WEAPON_REQUEST
+ COND_ANY_MELEE_ATTACK
+ COND_NOT_FACING_ENEMY
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+
+//===============================================
+Schedule
+
+ SCHED_RANGE_ATTACK1_EVASIVE
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_RANGE_ATTACK1_EVASIVE 0
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+ COND_NO_PRIMARY_CLIP_AMMO
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_GIVE_WAY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_ANY_MELEE_ATTACK
+ COND_NOT_FACING_ENEMY
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+
+//===============================================
+// > RangeAttack1
+//===============================================
+Schedule
+
+ SCHED_RANGE_ATTACK1
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_AIM_AT_ENEMY 0
+ TASK_ANNOUNCE_ATTACK 1 // 1 = primary attack
+ TASK_RANGE_ATTACK1 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_HEAVY_DAMAGE
+ COND_NO_PRIMARY_CLIP_AMMO
+ COND_WEAPON_BLOCKED_BY_FRIEND
+ COND_WEAPON_SIGHT_OCCLUDED
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_GIVE_WAY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_SWITCH_WEAPON_REQUEST
+ COND_ANY_MELEE_ATTACK
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+
+//===============================================
+// > RangeAttack2
+//===============================================
+Schedule
+
+ SCHED_RANGE_ATTACK2
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY_STRICT 3
+ TASK_AIM_AT_ENEMY 0
+ TASK_RANGE_ATTACK2 0
+
+ Interrupts
+ COND_WEAPON_SIGHT_OCCLUDED
+End_Schedule
+
+//===============================================
+Schedule
+
+ SCHED_RANGE_ATTACK_TWITCH
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK_TWITCH
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_HEAVY_DAMAGE
+ COND_NO_PRIMARY_CLIP_AMMO
+ COND_WEAPON_BLOCKED_BY_FRIEND
+ COND_WEAPON_SIGHT_OCCLUDED
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_GIVE_WAY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_SWITCH_WEAPON_REQUEST
+ COND_ANY_MELEE_ATTACK
+ COND_NOT_FACING_ENEMY
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+
+//===============================================
+// > SpecialAttack
+//===============================================
+Schedule
+
+ SCHED_SPECIAL_ATTACK
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY_STRICT 3
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SPECIAL_ATTACK_START
+ TASK_SPECIAL_ATTACK 0
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SPECIAL_ATTACK_END
+
+ Interrupts
+ COND_GIVE_WAY
+End_Schedule
+
+
+//===============================================
+Schedule
+
+ SCHED_THROW_GRENADE
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY_STRICT 4
+ TASK_SCRIPTED_DIALOGUE 10 // eCodeDialogueID.GRENADE_OUT
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_THROW_GRENADE
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+
+//===============================================
+Schedule
+
+ SCHED_RODEO_ATTACK
+
+ Tasks
+ TASK_RANGE_ATTACK1 0
+ TASK_WAIT_BETWEEN_BURSTS 0
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+// > Ambush - monster stands in place and waits for a new
+// enemy or chance to attack an existing enemy.
+//=========================================================
+Schedule
+
+ SCHED_AMBUSH
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT_INDEFINITE 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+End_Schedule
+
+
+//=========================================================
+// > AlertFace
+//=========================================================
+Schedule
+
+ SCHED_ALERT_FACE
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_IDEAL 0
+ TASK_SET_IDLE_ACTIVITY 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_PROVOKED
+End_Schedule
+
+//=========================================================
+// > AlertFace best sound
+//=========================================================
+Schedule
+
+ SCHED_ALERT_FACE_BESTSOUND
+
+ Tasks
+ TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0
+ TASK_STOP_MOVING 2
+ TASK_FACE_SAVEPOSITION 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 1.5
+ TASK_FACE_REASONABLE 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_FEAR
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+End_Schedule
+
+
+//=========================================================
+// > Alert_Scan
+//=========================================================
+Schedule
+
+ SCHED_ALERT_SCAN
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE_SCAN
+ TASK_WAIT 1
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_SQUAD_SEARCH
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_HEAR_COMBAT
+ COND_HEAR_PLAYER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_BULLET_IMPACT
+ COND_REACT_SURPRISED
+ COND_IDLE_INTERRUPT
+
+End_Schedule
+
+//=========================================================
+// > Alert_Area_Clear
+//=========================================================
+Schedule
+
+ SCHED_ALERT_AREA_CLEAR
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_NOTIFY_ENEMY_ELUDED 0
+ TASK_RESERVE_NEXT_SEARCH_POINT 0
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_IDLE_SCAN
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_SQUAD_SEARCH
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_HEAR_COMBAT
+ COND_HEAR_PLAYER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_BULLET_IMPACT
+ COND_REACT_SURPRISED
+ COND_IDLE_INTERRUPT
+
+End_Schedule
+
+
+//=========================================================
+// > AlertStand
+//=========================================================
+Schedule
+
+ SCHED_ALERT_STAND
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_FACE_ENEMY 5
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 1
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_LIGHT_DAMAGE
+ COND_PROVOKED
+ COND_HEAR_COMBAT
+ COND_HEAR_PLAYER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_BULLET_IMPACT
+ COND_IDLE_INTERRUPT
+ COND_GIVE_WAY
+ COND_REACT_SURPRISED
+ COND_REACT_FRIENDLY_PLAYER
+ COND_IDLE_INTERRUPT
+
+End_Schedule
+
+
+
+//=========================================================
+// > AlertWAlk
+//=========================================================
+Schedule
+
+ SCHED_ALERT_WALK
+
+ Tasks
+ TASK_WALK_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_WAIT_PVS 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_ENEMY_DEAD
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_REACT_SURPRISED
+End_Schedule
+
+//=========================================================
+// > InvestigateSound
+//
+// sends a monster to the location of the
+// sound that was just heard to check things out.
+//=========================================================
+Schedule
+
+ SCHED_INVESTIGATE_SOUND
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_STORE_BESTSOUND_IN_SAVEPOSITION 0
+ TASK_GET_PATH_TO_SAVEPOSITION_LOS 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_STOP_MOVING 0
+ TASK_FACE_SAVEPOSITION 0
+ TASK_LOOKAT_SAVEPOSITION 1.5
+ TASK_SET_ACTIVITY ACTIVITY:ACT_SEARCH_LOOK_AROUND
+ TASK_WAIT 0.3
+ TASK_NOTIFY_ENEMY_ELUDED 0
+ TASK_WAIT 1.7
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_FEAR
+ COND_SEE_ENEMY
+ COND_HEAVY_DAMAGE
+ COND_REACT_SURPRISED
+ COND_REACT_CORPSE
+ COND_NEW_INVESTIGATE_SOUND
+End_Schedule
+
+//=========================================================
+// > InvestigateCorpse
+//=========================================================
+Schedule
+
+ SCHED_INVESTIGATE_CORPSE
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_SCRIPTED_DIALOGUE 0 // eCodeDialogueID.MAN_DOWN
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SIGNAL_GROUP
+ TASK_SET_TOLERANCE_DISTANCE 100
+ TASK_STORE_CORPSE_POSITION_IN_SAVEPOSITION 0
+ TASK_GET_PATH_TO_SAVEPOSITION 0
+ TASK_NOTIFY_ENEMY_ELUDED 0
+ TASK_RESERVE_NEXT_SEARCH_POINT 0 // must be done after path find or will be cleared
+ TASK_DISABLE_ARRIVAL_ONCE 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SEARCH_CORPSE
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SIGNAL_GROUP
+ TASK_INIT_SEARCH_PATH 0
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_PATROL_PATH
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_FEAR
+ COND_SEE_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_HEAR_COMBAT
+ COND_REACT_SURPRISED
+End_Schedule
+
+
+//=========================================================
+// > Stand up
+//=========================================================
+Schedule
+
+ SCHED_STAND_UP
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_STAND
+
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+// > Crouch down
+//=========================================================
+Schedule
+
+ SCHED_CROUCH_DOWN
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCH
+
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+// > CombatStand
+//=========================================================
+Schedule
+
+ SCHED_COMBAT_STAND
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT_INDEFINITE 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_SEE_ENEMY
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_IDLE_INTERRUPT
+ COND_REACT_SURPRISED
+End_Schedule
+
+//=========================================================
+// > CombatWalk
+//=========================================================
+Schedule
+
+ SCHED_COMBAT_WALK
+
+ Tasks
+ TASK_WALK_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_WAIT_PVS 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_REACT_SURPRISED
+End_Schedule
+
+//=========================================================
+// > CombatFace
+//=========================================================
+Schedule
+
+ SCHED_COMBAT_FACE
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY 3
+ TASK_WAIT_UNTIL_ATTACK_READY 0.5
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_SQUAD_SEARCH
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_BULLET
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+//=========================================================
+// COMBAT_SWEEP
+//
+// Do a small sweep of the area
+//=========================================================
+Schedule
+
+ SCHED_COMBAT_SWEEP
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SEARCH_LOOK_AROUND
+
+ Interrupts
+ COND_SQUAD_SEARCH
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+ COND_HEAR_COMBAT // sound flags
+ COND_HEAR_PLAYER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_BULLET_IMPACT
+ COND_GIVE_WAY
+ COND_REACT_SURPRISED
+ COND_REACT_BULLET
+ COND_REACT_CLOAK
+End_Schedule
+
+
+//=========================================================
+// > Combat Wait facing current, reload if necessary
+//=========================================================
+Schedule
+
+ SCHED_COMBAT_WAIT
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_RELOAD 0.5
+ TASK_SCRIPTED_DIALOGUE 4 // script enum eCodeDialogueID.RELOADING
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT_FACE_ENEMY 1
+
+ Interrupts
+ COND_IN_DANGER
+ COND_SEE_ENEMY
+ COND_LOS_TO_ENEMY_LKP
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_BULLET
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+
+ //=========================================================
+// > Combat Wait facing enemy
+//=========================================================
+Schedule
+
+ SCHED_WAIT_FACING_ENEMY
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_RELOAD 0.5
+ TASK_FACE_REASONABLE 0
+ TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE_SCAN
+ TASK_WAIT 1
+
+ Interrupts
+ COND_IN_DANGER
+ COND_ENEMY_DEAD
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_REACT_SURPRISED
+ COND_REACT_BULLET
+ COND_REACT_CLOAK
+ End_Schedule
+
+//=========================================================
+// SCHED_HIDE_AND_RELOAD
+//=========================================================
+Schedule
+
+ SCHED_HIDE_AND_RELOAD
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RELOAD
+ TASK_FIND_COVER_FROM_ENEMY_WITHIN_RADIUS 0
+ TASK_SPRINT_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_REMEMBER MEMORY:INCOVER
+ TASK_FACE_ENEMY 3
+ TASK_RELOAD 0
+ TASK_SCRIPTED_DIALOGUE 4 // script enum eCodeDialogueID.RELOADING
+
+ Interrupts
+ COND_IN_DANGER
+ COND_ANY_MELEE_ATTACK
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+End_Schedule
+
+//=========================================================
+// > Reload
+//=========================================================
+Schedule
+
+ SCHED_RELOAD
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_LOOKAT_ENEMY 1.0
+ TASK_RELOAD 0
+ TASK_SCRIPTED_DIALOGUE 4 // script enum eCodeDialogueID.RELOADING
+
+ Interrupts
+ COND_IN_DANGER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_ANY_MELEE_ATTACK
+ COND_REACT_SURPRISED
+End_Schedule
+
+//=========================================================
+// > Directional melee
+//=========================================================
+Schedule
+
+ SCHED_DIRECTIONAL_MELEE
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_MELEE_DIRECTIONAL_ATTACK 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+ COND_HEAVY_DAMAGE
+ COND_ENEMY_OCCLUDED
+End_Schedule
+
+
+//=========================================================
+// > Melee_Attack1
+//=========================================================
+Schedule
+
+ SCHED_MELEE_ATTACK1
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_FACE_ENEMY_STRICT 1.5
+ TASK_ANNOUNCE_ATTACK 1 // 1 = primary attack
+ TASK_MELEE_ATTACK1 0
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > Melee_Charge
+//=========================================================
+Schedule
+
+ SCHED_MELEE_CHARGE
+
+ Tasks
+ TASK_FACE_ENEMY 1.5
+ TASK_ANNOUNCE_ATTACK 2 // 2 = secondary attack
+ TASK_MELEE_CHARGE 0
+
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+// > Melee_Attack_Wait
+//=========================================================
+Schedule
+
+ SCHED_MELEE_ATTACK_WAIT
+
+ Tasks
+ TASK_FACE_ENEMY_STRICT 3
+ TASK_SET_ACTIVITY ACTIVITY:ACT_MELEE_ATTACK_WAIT
+ TASK_WAIT 0.5
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+End_Schedule
+
+
+ //=========================================================
+ //=========================================================
+Schedule
+
+ SCHED_PRESS_ATTACK
+
+ Tasks
+ TASK_SET_TOLERANCE_DISTANCE 0
+ TASK_SET_PATH_CLUSTER_EXCLUDE 1
+ TASK_GET_PATH_TO_ENEMY_LKP 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_FACE_TARGET_AT_END 0
+
+ Interrupts
+ COND_IN_DANGER
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SQUAD_SEARCH
+ COND_ENEMY_DEAD
+ COND_ENEMY_UNREACHABLE
+ COND_NO_PRIMARY_AMMO
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_ANY_MELEE_ATTACK
+ COND_ATTACK_SLOT_AVAILABLE
+ COND_INSIDE_DANGEROUS_AREA
+End_Schedule
+
+
+//=========================================================
+// > ChaseEnemy
+//=========================================================
+Schedule
+
+ SCHED_CHASE_ENEMY
+
+ Tasks
+ TASK_SET_PATH_CLUSTER_EXCLUDE 1
+ TASK_GET_PATH_TO_ENEMY 0
+ TASK_RUN_PATH 0
+ TASK_SCRIPTED_DIALOGUE 9 // script enum eCodeDialogueID.CHASE_ENEMY
+ TASK_WAIT_FOR_MOVEMENT_CHASE 0
+ TASK_FACE_ENEMY 3
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+ COND_ENEMY_UNREACHABLE
+ COND_ANY_MELEE_ATTACK
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_LOST_ENEMY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_IN_DANGER
+ COND_GIVE_WAY
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+
+//=========================================================
+// > ChaseEnemyRangeAttacking
+//=========================================================
+Schedule
+
+ SCHED_CHASE_ENEMY_RANGE_ATTACKING
+
+ Tasks
+ TASK_SET_PATH_CLUSTER_EXCLUDE 1
+ TASK_GET_PATH_TO_ENEMY 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_CHASE 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+ COND_ENEMY_UNREACHABLE
+ COND_ANY_MELEE_ATTACK
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_LOST_ENEMY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_IN_DANGER
+ COND_GIVE_WAY
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+//=========================================================
+// > ChargeAttackRun
+//=========================================================
+Schedule
+
+ SCHED_CHARGE_ATTACK_RUN
+
+ Tasks
+ TASK_GET_PATH_TO_ENEMY 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_CHASE 0
+ TASK_FACE_ENEMY 1.5
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+ COND_ENEMY_UNREACHABLE
+ COND_ANY_MELEE_ATTACK
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_LOST_ENEMY
+ COND_INSIDE_DANGEROUS_AREA
+End_Schedule
+
+//=========================================================
+// > SCHED_BACK_AWAY_FROM_SAVE_POSITION
+//=========================================================
+Schedule
+
+ SCHED_BACK_AWAY_FROM_SAVE_POSITION
+
+ Tasks
+ TASK_FIND_BACKAWAY_FROM_SAVEPOSITION 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > BackAwayFromEnemy
+//=========================================================
+Schedule
+
+ SCHED_BACK_AWAY_FROM_ENEMY
+
+ Tasks
+ TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION 0
+ TASK_FIND_BACKAWAY_FROM_SAVEPOSITION 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+ COND_IN_DANGER
+ COND_NEW_ENEMY
+ COND_ANY_MELEE_ATTACK
+End_Schedule
+
+//=========================================================
+// > BigFlinch
+// played when heavy damage is taken for the first time in a while
+//=========================================================
+Schedule
+
+ SCHED_BIG_FLINCH
+
+ Tasks
+ TASK_BIG_FLINCH 0
+ TASK_REFRESH_REACT_TO_SOUND 0.5
+
+ Interrupts
+ COND_INTERRUPT_DAMAGE
+End_Schedule
+
+//=========================================================
+// > Die
+//=========================================================
+Schedule
+
+ SCHED_DIE
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_DIE 0
+
+ Interrupts
+ COND_NO_CUSTOM_INTERRUPTS
+End_Schedule
+
+//=========================================================
+// > Die
+//=========================================================
+Schedule
+
+ SCHED_DIE_RAGDOLL
+
+ Tasks
+ TASK_STOP_MOVING 0
+
+ Interrupts
+ COND_NO_CUSTOM_INTERRUPTS
+End_Schedule
+
+//=========================================================
+// > VictoryDance
+//=========================================================
+Schedule
+
+ SCHED_VICTORY_DANCE
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > Wait for Anim
+//=========================================================
+Schedule
+
+ SCHED_WAIT_FOR_SCRIPT_ANIM_END
+
+ Tasks
+ TASK_WAIT_FOR_SCRIPT_ANIM_END 0
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > Synced melee anim
+//=========================================================
+Schedule
+
+ SCHED_WAIT_FOR_SYNCED_MELEE_ANIM_END
+
+ Tasks
+ TASK_WAIT_FOR_SYNCED_MELEE_ANIM_END 0
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > Path towards a place to start a Synced melee
+//=========================================================
+Schedule
+
+ SCHED_PRESS_TO_INITIATE_SYNCED_MELEE_ATTACK
+
+ Tasks
+ TASK_GET_PATH_TO_INITIATE_SYNCED_MELEE 0
+ TASK_RUN_PATH 0
+ TASK_DISABLE_ARRIVAL_ONCE 0
+ TASK_WAIT_FOR_MOVEMENT 3
+
+ Interrupts
+ COND_CAN_SYNCED_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_ENEMY_DEAD
+End_Schedule
+
+//=========================================================
+// > Cower
+//
+// This is what is usually done when attempts
+// to escape danger fail.
+//=========================================================
+Schedule
+
+ SCHED_COWER
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY 2
+ TASK_SET_NPC_FLAG FLAG:NPC_DIE_ON_ANY_DAMAGE
+ TASK_SET_NPC_FLAG FLAG:NPC_RAGDOLL_IMMEDIATE
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_COWER
+ TASK_CLEAR_NPC_FLAG FLAG:NPC_ALLOW_FLEE
+ TASK_CLEAR_NPC_FLAG FLAG:NPC_DIE_ON_ANY_DAMAGE
+ TASK_CLEAR_NPC_FLAG FLAG:NPC_RAGDOLL_IMMEDIATE
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > TakeCoverFromBestSound
+//
+// hide from the loudest sound source
+//=========================================================
+Schedule
+
+ SCHED_TAKE_COVER_FROM_BEST_SOUND
+
+ Tasks
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FLEE_FROM_BEST_SOUND
+ TASK_STOP_MOVING 0
+ TASK_FIND_COVER_FROM_BEST_SOUND 0
+ TASK_SPRINT_RUNAWAY_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_REMEMBER MEMORY:INCOVER
+ TASK_FACE_REASONABLE 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 3
+
+ Interrupts
+ COND_NEW_ENEMY
+End_Schedule
+
+
+//=========================================================
+//
+//=========================================================
+Schedule
+
+ SCHED_FLEE_FROM_BEST_SOUND
+
+ Tasks
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COWER
+ TASK_GET_PATH_AWAY_FROM_BEST_SOUND 600
+ TASK_SPRINT_PATH_TIMED 2
+ TASK_STOP_MOVING 0
+
+ Interrupts
+ COND_NEW_ENEMY
+End_Schedule
+
+
+//=========================================================
+//
+//=========================================================
+Schedule
+
+ SCHED_TAKE_COVER_FROM_ENEMY
+
+ Tasks
+ TASK_SCRIPTED_DIALOGUE 8 // script enum eCodeDialogueID.TAKE_COVER_FROM_ENEMY
+ TASK_STOP_MOVING 0
+ TASK_FIND_COVER_FROM_ENEMY 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_REMEMBER MEMORY:INCOVER
+
+ Interrupts
+ COND_REACT_SURPRISED
+ COND_REACT_JUMPED_OVER
+ COND_DEFENSIVE_PLACEMENT
+ COND_INSIDE_DANGEROUS_AREA
+End_Schedule
+
+//=========================================================
+//
+//=========================================================
+Schedule
+
+ SCHED_DISPLACE_FROM_DANGEROUS_AREA
+
+ Tasks
+ TASK_SCRIPTED_DIALOGUE 11 // script enum eCodeDialogueID.DANGEROUS_AREA_DISPLACE
+ TASK_DISPLACE_FROM_DANGEROUS_AREA 0
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+
+//=========================================================
+//
+//=========================================================
+Schedule
+
+ SCHED_MELEE_DISENGAGE
+
+ Tasks
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_DISENGAGE
+
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+// > RunFromEnemy
+//
+// Run to cover, but don't turn to face enemy and upon
+// fail run around randomly
+//=========================================================
+Schedule
+
+ SCHED_RUN_FROM_ENEMY
+
+ Tasks
+ TASK_SCRIPTED_DIALOGUE 3 // script enum eCodeDialogueID.RUN_FROM_ENEMY
+ TASK_SET_RUNNING_FROM_ENEMY 0
+ TASK_STOP_MOVING 0
+ TASK_FIND_SAFE_HINT_FROM_ENEMY 1500
+ TASK_FIND_COVER_FROM_ENEMY_LIMITLESS 300
+ TASK_SPRINT_RUNAWAY_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_RUN_FROM_ENEMY_FALLBACK_COMPLETE
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+End_Schedule
+
+
+//=========================================================
+Schedule
+
+ SCHED_RUN_FROM_ENEMY_FALLBACK
+
+ Tasks
+ TASK_SET_RUNNING_FROM_ENEMY 0
+ TASK_STOP_MOVING 0
+ TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION 0
+ TASK_FIND_BACKAWAY_FROM_SAVEPOSITION 0
+ TASK_SPRINT_RUNAWAY_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_RUN_FROM_ENEMY_FALLBACK_COMPLETE
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+End_Schedule
+
+//=========================================================
+Schedule
+
+ SCHED_RUN_FROM_ENEMY_FALLBACK_COMPLETE
+
+ Tasks
+ TASK_WAIT_RANDOM 1
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SIGNAL_ACKNOWLEDGE
+ TASK_RELOAD 0.5
+ TASK_SCRIPTED_DIALOGUE 4 // script enum eCodeDialogueID.RELOADING
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_FACE_ENEMY_IF_HAS_LOS 3
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SIGNAL_ACKNOWLEDGE
+
+ Interrupts
+ COND_LOS_TO_ENEMY_LKP
+ COND_SEE_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+End_Schedule
+
+//=========================================================
+// > Forced_Go (Used for debug only)
+//=========================================================
+Schedule
+
+ SCHED_FORCED_GO
+
+ Tasks
+ TASK_SET_TOLERANCE_DISTANCE 0
+ TASK_GET_PATH_TO_SAVEPOSITION 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+
+End_Schedule
+
+//=========================================================
+// > Forced_Go (Used for debug only)
+//=========================================================
+Schedule
+
+ SCHED_FORCED_GO_RUN
+
+ Tasks
+ TASK_SET_TOLERANCE_DISTANCE 0
+ TASK_GET_PATH_TO_SAVEPOSITION 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+
+End_Schedule
+
+
+//=========================================================
+// SCHED_MOVE_TO_ENEMY
+// different from SCHED_CHASE_ENEMY, uses LKP
+//=========================================================
+Schedule
+
+ SCHED_MOVE_TO_ENEMY
+
+ Tasks
+ TASK_SET_TOLERANCE_DISTANCE 72
+ TASK_SET_PATH_CLUSTER_EXCLUDE 1
+ TASK_GET_PATH_TO_ENEMY_LKP 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_FACE_TARGET_AT_END 0
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SQUAD_SEARCH
+ COND_ENEMY_DEAD
+ COND_LOST_ENEMY
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+End_Schedule
+
+
+//=========================================================
+// SCHED_MOVE_TO_ENGAGEMENT_RANGE
+//
+//=========================================================
+Schedule
+
+ SCHED_MOVE_TO_ENGAGEMENT_RANGE
+
+ Tasks
+ TASK_SET_PATH_CLUSTER_EXCLUDE 1
+ TASK_GET_PATH_TO_ENEMY_LKP_LOS 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_FACE_TARGET_AT_END 0
+
+ Interrupts
+ COND_IN_DANGER
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SQUAD_SEARCH
+ COND_ENEMY_DEAD
+ COND_LOST_ENEMY
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+End_Schedule
+
+
+//=========================================================
+// SCHED_MOVE_TO_WEAPON_RANGE
+//
+//=========================================================
+Schedule
+
+ SCHED_MOVE_TO_WEAPON_RANGE
+
+ Tasks
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY
+ TASK_GET_PATH_TO_RANGE_ENEMY_LKP_LOS 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE
+
+ Interrupts
+ COND_IN_DANGER
+ COND_NEW_ENEMY
+ COND_ENEMY_DEAD
+ COND_LOST_ENEMY
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+End_Schedule
+
+//=========================================================
+// SCHED_MOVE_TO_SQUAD_RANGE
+//
+//=========================================================
+Schedule
+
+ SCHED_MOVE_TO_SQUAD_RANGE
+
+ Tasks
+ TASK_SET_PATH_CLUSTER_EXCLUDE 1
+ TASK_GET_PATH_TO_ENEMY_LKP 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_FACE_TARGET_AT_END 0
+
+ Interrupts
+ COND_IN_DANGER
+ COND_NEW_ENEMY
+ COND_SQUAD_SEARCH
+ COND_ENEMY_DEAD
+ COND_LOST_ENEMY
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+End_Schedule
+
+
+//=========================================================
+Schedule
+
+ SCHED_MOVE_TO_SQUAD_LEADER_GOAL
+
+ Tasks
+ TASK_GET_PATH_TO_SQUAD_LEADER_GOAL 0
+ TASK_SCRIPTED_DIALOGUE 6 // script enum eCodeDialogueID.MOVE_TO_SQUAD_LEADER
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_STOP_MOVING 0
+
+ Interrupts
+ COND_IN_DANGER
+ COND_SQUAD_SEARCH
+ COND_SQUAD_TAKING_POSITION
+ COND_ANY_MELEE_ATTACK
+ COND_SEE_ENEMY_VERY_CLOSE
+End_Schedule
+
+//=========================================================
+Schedule
+
+ SCHED_MOVE_TO_SQUAD_ASSIGNED_NODE
+
+ Tasks
+ TASK_GET_PATH_TO_SQUAD_ASSIGNED_NODE 0
+ TASK_SCRIPTED_DIALOGUE 7 // script enum eCodeDialogueID.FAN_OUT
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_STOP_MOVING 0
+
+ Interrupts
+ COND_IN_DANGER
+ COND_SQUAD_SEARCH
+ COND_ANY_MELEE_ATTACK
+ COND_SEE_ENEMY_VERY_CLOSE
+End_Schedule
+
+
+//=========================================================
+// ESTABLISH_LINE_OF_FIRE
+//
+// Go to a location from which I can shoot my enemy
+//=========================================================
+Schedule
+
+ SCHED_ESTABLISH_LINE_OF_FIRE
+
+ Tasks
+ TASK_SET_TOLERANCE_DISTANCE 0
+ TASK_GET_PATH_TO_ENEMY_LKP_LOS 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_FACE_TARGET_AT_END 0
+
+ Interrupts
+ COND_IN_DANGER
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SQUAD_SEARCH
+ COND_ENEMY_DEAD
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+End_Schedule
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Schedule
+
+ SCHED_MOVE_TO_HIGH_GROUND
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_FIND_HIGH_GROUND_NODE_FROM_ORIGIN 150
+ TASK_SPRINT_RUNAWAY_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_SET_TOLERANCE_DISTANCE 0
+
+ Interrupts
+ COND_IN_DANGER
+ COND_CAN_MELEE_ATTACK1
+End_Schedule
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Schedule
+
+ SCHED_PREPARE_TO_SNIPE
+
+ Tasks
+ TASK_GET_PATH_TO_ENEMY_LKP_LOS 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_FACE_TARGET_AT_END 0
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY 0
+ TASK_SET_WEAPON_BLOCKED_TIMER 5
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_ARM
+
+ Interrupts
+ COND_IN_DANGER
+ COND_ANY_MELEE_ATTACK
+End_Schedule
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Schedule
+
+ SCHED_SNIPE_WAIT
+
+ Tasks
+ TASK_AIM_AT_ENEMY 0
+ TASK_WAIT_BETWEEN_BURSTS 0
+ TASK_WAIT 0.2
+
+ Interrupts
+ COND_IN_DANGER
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_WEAPON_BLOCKED_TIMER
+End_Schedule
+
+//=========================================================
+// > PATROL_PATH
+//
+// patrol a path, with option running scripts at each patrol point
+//=========================================================
+
+Schedule
+
+ SCHED_PATROL_PATH
+
+ Tasks
+ TASK_GET_PATH_TO_NEXT_SEARCH_POINT 1500
+ TASK_WALK_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_RUN_VSCRIPT 0
+ TASK_RESERVE_NEXT_SEARCH_POINT 0
+
+ Interrupts
+ COND_ENEMY_DEAD
+ COND_LOST_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_HEAR_COMBAT
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_BULLET_IMPACT
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_REACT_SURPRISED
+ COND_REACT_BULLET
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+ COND_REACT_CORPSE
+ COND_REACT_FRIENDLY_PLAYER
+ COND_SQUAD_ENGAGE
+
+ End_Schedule
+
+
+//=========================================================
+// > PATROL_RUN
+//
+// Run around randomly until we detect an enemy
+//=========================================================
+Schedule
+
+ SCHED_PATROL_RUN
+
+ Tasks
+ TASK_SET_TOLERANCE_DISTANCE 0
+ TASK_SET_ROUTE_SEARCH_TIME 5 // Spend 5 seconds trying to build a path if stuck
+ TASK_GET_PATH_TO_RANDOM_NODE 0
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+ COND_GIVE_WAY
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_HEAR_COMBAT
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_PLAYER
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+End_Schedule
+
+//=========================================================
+// > IDLE_WANDER
+//
+// Walk around randomly
+//=========================================================
+Schedule
+
+ SCHED_IDLE_WANDER
+
+ Tasks
+ TASK_SET_ROUTE_SEARCH_TIME 5 // Spend 5 seconds trying to build a path if stuck
+ TASK_WANDER 0
+ TASK_WALK_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+ COND_GIVE_WAY
+ COND_HEAR_COMBAT
+ COND_INSIDE_DANGEROUS_AREA
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_IDLE_INTERRUPT
+End_Schedule
+
+//=========================================================
+// > FALL_TO_GROUND
+//=========================================================
+Schedule
+
+ SCHED_FALL_TO_GROUND
+
+ Tasks
+ TASK_FALL_TO_GROUND 0
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > SCHED_MOVE_AWAY_FROM_ENEMY
+//=========================================================
+Schedule
+
+ SCHED_MOVE_AWAY_FROM_ENEMY
+
+ Tasks
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MOVE_AWAY_FAIL
+ TASK_FACE_ENEMY 3
+ TASK_MOVE_AWAY_PATH 120
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_MOVE_AWAY_END
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+End_Schedule
+
+//=========================================================
+// > SCHED_MOVE_AWAY
+//=========================================================
+Schedule
+
+ SCHED_MOVE_AWAY
+
+ Tasks
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MOVE_AWAY_FAIL
+ TASK_MOVE_AWAY_PATH 120
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_MOVE_AWAY_END
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > SCHED_MOVE_AWAY_FAIL
+//=========================================================
+Schedule
+
+ SCHED_MOVE_AWAY_FAIL
+
+ Tasks
+ TASK_STOP_MOVING 0
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > SCHED_MOVE_AWAY_END (allows derived class to translate to appropriate behavior)
+//=========================================================
+Schedule
+
+ SCHED_MOVE_AWAY_END
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_FACE_REASONABLE 0
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+ COND_HEAR_COMBAT
+ COND_HEAR_PLAYER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_BULLET_IMPACT
+ COND_IDLE_INTERRUPT
+End_Schedule
+
+//=========================================================
+// > SCHED_DODGE_USE_DANGEROUS_AREA
+//=========================================================
+Schedule
+
+ SCHED_DODGE_USE_DANGEROUS_AREA
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_DODGE_FORWARD
+ TASK_WAIT_FOR_ACTIVITY_TO_FINISH 5
+ TASK_WAIT_RANDOM 0.35
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_DODGE_BACKWARD
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+//=========================================================
+// > SCHED_DODGE_FORWARD
+//=========================================================
+Schedule
+
+ SCHED_DODGE_FORWARD
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_DODGE_FORWARD
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+//=========================================================
+// > SCHED_DODGE_BACKWARD
+//=========================================================
+Schedule
+
+ SCHED_DODGE_BACKWARD
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_DODGE_BACKWARD
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+//=========================================================
+// > SCHED_DODGE
+//=========================================================
+Schedule
+
+ SCHED_DODGE
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_DODGE 0
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+
+//=========================================================
+// > SCHED_STRAFE_DODGE
+//=========================================================
+Schedule
+
+ SCHED_STRAFE_DODGE
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_STRAFE_DODGE 0
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+//=========================================================
+// > SCHED_DODGE_PATH
+//=========================================================
+Schedule
+
+ SCHED_DODGE_PATH
+
+ Tasks
+ TASK_DODGE_PATH 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+
+//=========================================================
+// > SCHED_STRAFE_DODGE_PATH
+//=========================================================
+Schedule
+
+ SCHED_STRAFE_DODGE_PATH
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_DODGE_PATH 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+// > SCHED_CIRCLE_STRAFE
+//=========================================================
+Schedule
+
+ SCHED_CIRCLE_STRAFE
+
+ Tasks
+ TASK_CIRCLE_STRAFE_PATH 0
+ TASK_LOOKAT_ENEMY 0.5
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT_FACE_TARGET_AT_END 0
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_IN_DANGER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_JUMPED_OVER
+End_Schedule
+
+
+//=========================================================
+// > SCHED_ATTACK_RUN
+//=========================================================
+Schedule
+
+ SCHED_ATTACK_RUN
+
+ Tasks
+ TASK_WAIT 0.5
+ TASK_ATTACK_RUN_PATH 0
+ TASK_SET_STOP_AT_GOAL 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_WAIT 0.5
+
+ Interrupts
+ COND_IN_DANGER
+End_Schedule
+
+
+//=========================================================
+// > SCHED_SLEEP
+//=========================================================
+Schedule
+
+ SCHED_SLEEP
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_WAIT 0.2
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// SCHED_TITAN_MOVE_TO_DEFENSIVE_PLACMENT
+//=========================================================
+Schedule
+
+ SCHED_TITAN_MOVE_TO_DEFENSIVE_PLACMENT
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_GET_PATH_TO_SAVEPOSITION 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_REMEMBER MEMORY:INCOVER
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_WAIT_FACING_ENEMY
+
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+// > SCHED_REACT_JUMPED_OVER
+//=========================================================
+Schedule
+
+ SCHED_REACT_JUMPED_OVER
+
+ Tasks
+ TASK_PLAY_REACTION_SEQUENCE ACTIVITY:ACT_REACT_JUMPED_OVER
+
+ Interrupts
+ COND_IN_DANGER
+End_Schedule
+
+//=========================================================
+// > SCHED_REACT_CLOAK_APPEAR
+//=========================================================
+Schedule
+
+ SCHED_REACT_CLOAK_APPEAR
+
+ Tasks
+ TASK_FACE_ENEMY 1
+ TASK_PLAY_REACTION_SEQUENCE ACTIVITY:ACT_REACT_CLOAK_APPEAR_NEAR
+
+ Interrupts
+ COND_IN_DANGER
+End_Schedule
+
+//=========================================================
+// > SCHED_REACT_CLOAK_DISAPPEAR
+//=========================================================
+Schedule
+
+ SCHED_REACT_CLOAK_DISAPPEAR
+
+ Tasks
+ TASK_FACE_ENEMY 1
+ TASK_PLAY_REACTION_SEQUENCE ACTIVITY:ACT_REACT_CLOAK_DISAPPEAR_NEAR
+
+ Interrupts
+ COND_SEE_ENEMY
+ COND_IN_DANGER
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_CLOAK
+End_Schedule
+
+
+//=========================================================
+// > SCHED_REACT_SURPRISED
+//=========================================================
+Schedule
+
+ SCHED_REACT_SURPRISED
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_SCRIPTED_DIALOGUE 12 // script enum eCodeDialogueID.REACT_SURPRISED
+ TASK_PLAY_REACTION_SEQUENCE ACTIVITY:ACT_REACT_SURPRISED
+
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+// > SCHED_REACT_BULLET
+//=========================================================
+Schedule
+
+ SCHED_REACT_BULLET
+
+ Tasks
+ TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0
+ TASK_STOP_MOVING 0
+ TASK_PLAY_REACTION_SEQUENCE ACTIVITY:ACT_REACT_BULLET
+ TASK_REFRESH_REACT_TO_SOUND 0.5
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+
+//=========================================================
+// > SCHED_SIGNAL_ACKNOWLEDGE
+//=========================================================
+Schedule
+
+ SCHED_SIGNAL_ACKNOWLEDGE
+
+ Tasks
+ TASK_WAIT_RANDOM 0.7
+ TASK_STOP_MOVING 0
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SIGNAL_ACKNOWLEDGE
+
+ Interrupts
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+End_Schedule
+
+//=========================================================
+// > SCHED_SIGNAL_SALUTE
+//=========================================================
+Schedule
+
+ SCHED_SIGNAL_SALUTE
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_PLAYER 0.5
+ TASK_SCRIPTED_DIALOGUE 1 // script enum eCodeDialogueID.SALUTE
+ TASK_PLAY_SEQUENCE ACTIVITY:ACT_SIGNAL_SALUTE
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+
+//=========================================================
+// > SCHED_SIGNAL_FORWARD
+//=========================================================
+ Schedule
+
+ SCHED_SIGNAL_FORWARD
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_IDEAL 0
+ TASK_SCRIPTED_DIALOGUE 2 // script enum eCodeDialogueID.ENEMY_CONTACT
+ TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_SIGNAL_FORWARD
+
+ Interrupts
+ COND_IN_DANGER
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ End_Schedule
+
+
+//=========================================================
+// > SCHED_SIGNAL_TAUNT
+//=========================================================
+ Schedule
+
+ SCHED_SIGNAL_TAUNT
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY 3
+ TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_SIGNAL_TAUNT
+
+ Interrupts
+ COND_IN_DANGER
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ End_Schedule
+
+
+//=========================================================
+// > SCHED_KNOCK_AWAY_PHYS_ENTS
+//=========================================================
+Schedule
+
+ SCHED_KNOCK_AWAY_PHYS_ENTS
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_FACE_BLOCKING_PHYS_ENT 0
+ TASK_KNOCK_AWAY_PHYS_ENTS 0
+
+ Interrupts
+
+End_Schedule
+
+//=========================================================
+// > SCHED_DANGEROUS_CLUSTER_STANDOFF
+//=========================================================
+Schedule
+
+ SCHED_DANGEROUS_CLUSTER_STANDOFF
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_FACE_ENEMY 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 7.5
+
+ Interrupts
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SEE_ENEMY
+
+End_Schedule
+
+//=========================================================
+// > SCHED_MOVE_TO_SHOOTING_COVER
+//=========================================================
+Schedule
+
+ SCHED_MOVE_TO_SHOOTING_COVER
+
+ Tasks
+ TASK_GET_PATH_TO_SHOOTING_COVER 0
+ TASK_RUN_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_ALIGN_WITH_SHOOTING_COVER_IDEAL_YAW 0
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+ COND_SHOOTING_COVER_HINT_COMPROMISED
+ COND_TOO_CLOSE_FOR_SHOOTING_COVER
+
+End_Schedule
+
+//=========================================================
+// > SCHED_IDLE_INSIDE_SHOOTING_COVER
+//=========================================================
+Schedule
+
+ SCHED_IDLE_INSIDE_SHOOTING_COVER
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_ALIGN_WITH_SHOOTING_COVER_IDEAL_YAW 0
+ TASK_RELOAD 1
+ TASK_SET_SHOOTING_COVER_ACTIVITY ACTIVITY:ACT_SHOOTING_COVER_IDLE
+ TASK_WAIT 0.5
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+ COND_SHOOTING_COVER_HINT_COMPROMISED
+
+End_Schedule
+
+//=========================================================
+// > SCHED_CHECK_INSIDE_SHOOTING_COVER
+//=========================================================
+Schedule
+
+ SCHED_CHECK_INSIDE_SHOOTING_COVER
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_ALIGN_WITH_SHOOTING_COVER_IDEAL_YAW 0
+ TASK_SET_SHOOTING_COVER_ACTIVITY ACTIVITY:ACT_SHOOTING_COVER_CHECK
+ TASK_WAIT_FOR_ACTIVITY_TO_FINISH 5
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+ COND_SHOOTING_COVER_HINT_COMPROMISED
+
+End_Schedule
+
+//=========================================================
+// > SCHED_ADVANCE_FROM_SHOOTING_COVER
+//=========================================================
+Schedule
+
+ SCHED_ADVANCE_FROM_SHOOTING_COVER
+
+ Tasks
+ TASK_SET_ADVANCE_FROM_SHOOTING_COVER_ACTIVITY 0
+ TASK_WAIT_FOR_ACTIVITY_TO_FINISH 5
+ TASK_BEGIN_SHOOTING_COVER_BURST_FIRE 0
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+ COND_SHOOTING_COVER_HINT_COMPROMISED
+
+End_Schedule
+
+//=========================================================
+// > SCHED_RETREAT_TO_SHOOTING_COVER
+//=========================================================
+Schedule
+
+ SCHED_RETREAT_TO_SHOOTING_COVER
+
+ Tasks
+ TASK_SET_RETREAT_TO_SHOOTING_COVER_ACTIVITY 0
+ TASK_WAIT_FOR_ACTIVITY_TO_FINISH 3
+ TASK_ALIGN_WITH_SHOOTING_COVER_IDEAL_YAW 0
+ TASK_RELOAD 0.3
+ TASK_SET_SHOOTING_COVER_ACTIVITY ACTIVITY:ACT_SHOOTING_COVER_IDLE
+ TASK_WAIT 0.5
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_JUMPED_OVER
+ COND_SHOOTING_COVER_HINT_COMPROMISED
+
+End_Schedule
+
+//=========================================================
+// > SCHED_GRENADE_FROM_SHOOTING_COVER
+//=========================================================
+Schedule
+
+ SCHED_GRENADE_FROM_SHOOTING_COVER
+
+ Tasks
+ TASK_SCRIPTED_DIALOGUE 10 // eCodeDialogueID.GRENADE_OUT
+ TASK_SET_SHOOTING_COVER_ACTIVITY ACTIVITY:ACT_SHOOTING_COVER_GRENADE
+ TASK_WAIT_FOR_ACTIVITY_TO_FINISH 5
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_REACT_SURPRISED
+ COND_REACT_JUMPED_OVER
+ COND_SHOOTING_COVER_HINT_COMPROMISED
+
+End_Schedule
+
+//=========================================================
+// > SCHED_GRENADE_ADVANCE_FROM_SHOOTING_COVER
+//=========================================================
+Schedule
+
+ SCHED_GRENADE_ADVANCE_FROM_SHOOTING_COVER
+
+ Tasks
+ TASK_SCRIPTED_DIALOGUE 10 // eCodeDialogueID.GRENADE_OUT
+ TASK_SET_SHOOTING_COVER_ACTIVITY ACTIVITY:ACT_SHOOTING_COVER_GRENADE_ADVANCE
+ TASK_WAIT_FOR_ACTIVITY_TO_FINISH 5
+ TASK_BEGIN_SHOOTING_COVER_BURST_FIRE 0
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_JUMPED_OVER
+ COND_SHOOTING_COVER_HINT_COMPROMISED
+
+End_Schedule
+
+//=========================================================
+// > SCHED_WAIT_WHILE_ADVANCED_FROM_SHOOTING_COVER
+//=========================================================
+Schedule
+
+ SCHED_WAIT_WHILE_ADVANCED_FROM_SHOOTING_COVER
+
+ Tasks
+ TASK_WAIT_UNTIL_ATTACK_READY 0.5
+
+ Interrupts
+ COND_ANY_MELEE_ATTACK
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_REACT_CLOAK
+ COND_REACT_JUMPED_OVER
+ COND_SHOOTING_COVER_HINT_COMPROMISED
+
+End_Schedule
+
+//=========================================================
+// > SCHED_GRAPPLED
+//=========================================================
+Schedule
+
+ SCHED_GRAPPLED
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_BIG_FLINCH 0
+
+ Interrupts
+
+End_Schedule
+
+
+//=========================================================
+// > SCHED_FOLLOWER_MOVE_AWAY_END
+//=========================================================
+Schedule
+
+ SCHED_FOLLOWER_MOVE_AWAY_END
+
+ Tasks
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FOLLOWER_MOVE_AWAY_FAIL
+ TASK_STOP_MOVING 0
+ TASK_FACE_FOLLOW_TARGET 0
+ TASK_SET_FOLLOW_DELAY 2
+
+ Interrupts
+ COND_PLAYER_PUSHING
+End_Schedule
+
+//=========================================================
+// > SCHED_FOLLOWER_MOVE_AWAY_FAIL
+//=========================================================
+Schedule
+
+ SCHED_FOLLOWER_MOVE_AWAY_FAIL
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_FACE_FOLLOW_TARGET 0
+ TASK_SET_FOLLOW_DELAY 2
+
+ Interrupts
+ COND_PLAYER_PUSHING
+End_Schedule
+
+//=========================================================
+// > SCHED_FOLLOW
+//=========================================================
+Schedule
+
+ SCHED_FOLLOW
+
+ Tasks
+ TASK_GET_PATH_TO_FOLLOW_POSITION 0
+ TASK_MOVE_PATH 0
+ TASK_MOVE_TO_FOLLOW_POSITION 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_ANY_MELEE_ATTACK
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_PROVOKED
+End_Schedule
+
+//=========================================================
+// > SCHED_FOLLOW_NO_INTERRUPT
+//=========================================================
+Schedule
+
+ SCHED_FOLLOW_NO_INTERRUPT
+
+ Tasks
+ TASK_GET_PATH_TO_FOLLOW_POSITION 0
+ TASK_MOVE_PATH 0
+ TASK_MOVE_TO_FOLLOW_POSITION 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+ COND_HEAVY_DAMAGE
+End_Schedule
+
+//=========================================================
+// > SCHED_FACE_FOLLOW_TARGET
+//=========================================================
+Schedule
+
+ SCHED_FACE_FOLLOW_TARGET
+
+ Tasks
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_FACE_FOLLOW_TARGET 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_FOLLOWER_IDLE_STAND
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_PROVOKED
+ COND_GIVE_WAY
+End_Schedule
+
+//=========================================================
+// > SCHED_FOLLOWER_GO_TO_WAIT_POINT
+//=========================================================
+Schedule
+
+ SCHED_FOLLOWER_GO_TO_WAIT_POINT
+
+ Tasks
+ TASK_LOCK_HINTNODE 0
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL
+ TASK_SET_TOLERANCE_DISTANCE 4
+ TASK_GET_PATH_TO_FOLLOW_POINT 0
+ TASK_SET_FOLLOW_TARGET_MARK 0
+ TASK_WALK_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_ARRIVE_AT_FOLLOW_POINT 0
+ TASK_SET_FOLLOW_POINT_STAND_SCHEDULE 0
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_PROVOKED
+ COND_PLAYER_PUSHING
+ COND_TARGET_MOVED_FROM_MARK
+End_Schedule
+
+//=========================================================
+// > SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL
+//=========================================================
+Schedule
+
+ SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL
+
+ Tasks
+ TASK_CLEAR_HINTNODE .5
+ TASK_SET_FOLLOW_DELAY 1
+
+ Interrupts
+End_Schedule
+
+//=========================================================
+// > SCHED_FOLLOWER_STAND_AT_WAIT_POINT
+//=========================================================
+Schedule
+
+ SCHED_FOLLOWER_STAND_AT_WAIT_POINT
+
+ Tasks
+ TASK_BEGIN_STAND_AT_WAIT_POINT 0
+ TASK_PLAY_HINT_ACTIVITY 0
+ TASK_SET_SCHEDULE SCHEDULE:SCHED_FOLLOWER_STAND_AT_WAIT_POINT
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_PROVOKED
+ COND_PLAYER_PUSHING
+ COND_TARGET_MOVED_FROM_MARK
+ COND_GIVE_WAY
+ COND_FOLLOW_WAIT_POINT_INVALID
+End_Schedule
+
+Schedule
+
+ SCHED_FOLLOWER_IDLE_STAND
+
+ Tasks
+ TASK_SET_FOLLOW_DELAY 0
+ TASK_STOP_MOVING 2
+ TASK_FACE_FOLLOW_TARGET 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 3
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SEE_FEAR
+ COND_CAN_RANGE_ATTACK1
+ COND_NO_PRIMARY_AMMO
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+ COND_GIVE_WAY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_COMBAT
+ COND_HEAR_BULLET_IMPACT
+ COND_REACT_SURPRISED
+ COND_PLAYER_PUSHING
+ COND_TARGET_MOVED_FROM_MARK
+ COND_FOLLOW_DELAY_EXPIRED
+ COND_FOUND_WAIT_POINT
+ COND_IDLE_INTERRUPT
+End_Schedule
+
+Schedule
+
+ SCHED_FOLLOWER_COMBAT_FACE
+
+ Tasks
+ TASK_STOP_MOVING 2
+ TASK_FACE_ENEMY 3
+ TASK_WAIT_UNTIL_ATTACK_READY 0.5
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SEE_FEAR
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_NO_PRIMARY_AMMO
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_PROVOKED
+ COND_GIVE_WAY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_HEAR_BULLET_IMPACT
+ COND_PLAYER_PUSHING
+ COND_TARGET_MOVED_FROM_MARK
+ COND_FOLLOW_DELAY_EXPIRED
+ COND_FOUND_WAIT_POINT
+End_Schedule
+
+Schedule
+
+ SCHED_FOLLOWER_COVER_IDLE
+
+ Tasks
+ TASK_STOP_MOVING 0
+ TASK_FACE_HINTNODE 0
+ TASK_SET_ACTIVITY ACTIVITY:ACT_COVER
+ TASK_WAIT 3
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_SEE_FEAR
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_INSIDE_DANGEROUS_AREA
+ COND_REACT_SURPRISED
+ COND_PROVOKED
+ COND_PLAYER_PUSHING
+ COND_FOLLOW_DELAY_EXPIRED
+End_Schedule
+
+//=========================================================
+//=========================================================
+Schedule
+
+ SCHED_ASSAULT_FAILED_TO_MOVE
+
+ Tasks
+ TASK_ASSAULT_DEFER_SCHEDULE_SELECTION 1
+
+ Interrupts
+End_Schedule
+
+
+//=========================================================
+//=========================================================
+Schedule
+
+ SCHED_HOLD_ASSAULT_POINT
+
+ Tasks
+ TASK_FACE_ASSAULT_POINT 0
+ TASK_SET_IDLE_ACTIVITY 0
+ TASK_WAIT 3
+
+ Interrupts
+ COND_VERY_DIFFERENT_NEW_ENEMY
+ COND_ENEMY_DEAD
+ COND_CAN_RANGE_ATTACK1
+ COND_CAN_RANGE_ATTACK2
+ COND_ANY_MELEE_ATTACK
+ COND_TOO_CLOSE_TO_ATTACK
+ COND_LOST_ENEMY
+ COND_INSIDE_DANGEROUS_AREA
+ COND_IN_DANGER
+ COND_HEAR_BULLET_IMPACT
+ COND_NO_PRIMARY_AMMO
+ COND_REACT_SURPRISED
+ COND_REACT_FRIENDLY_PLAYER
+End_Schedule
+
+//=========================================================
+//=========================================================
+Schedule
+
+ SCHED_MOVE_TO_ASSAULT_POINT
+
+ Tasks
+ TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSAULT_FAILED_TO_MOVE
+ TASK_GATHER_CONDITIONS 0
+ TASK_GET_PATH_TO_ASSAULT_POINT 0
+ TASK_SET_ADJUST_MOVE_SPEED_TO_SQUAD 1
+ TASK_SCRIPTED_DIALOGUE 5 // eCodeDialogueID.MOVE_TO_ASSAULT
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_FACE_ASSAULT_POINT 0
+
+ Interrupts
+ COND_PROVOKED
+ COND_SQUAD_TAKING_POSITION
+ COND_INSIDE_DANGEROUS_AREA
+ COND_IN_DANGER
+ COND_REACT_SURPRISED
+ COND_REACT_FRIENDLY_PLAYER
+ COND_ANY_MELEE_ATTACK
+ COND_PATH_INVOLVES_DANGEROUS_CLUSTER
+End_Schedule
+
+//=========================================================
+//=========================================================
+Schedule
+
+ SCHED_WALK_TO_ASSAULT_POINT
+
+ Tasks
+ TASK_GET_PATH_TO_ASSAULT_POINT 0
+ TASK_WALK_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+ TASK_FACE_ASSAULT_POINT 0
+
+ Interrupts
+ COND_GIVE_WAY
+ COND_HEAR_COMBAT
+ COND_INSIDE_DANGEROUS_AREA
+ COND_NEW_ENEMY
+ COND_SEE_ENEMY
+ COND_SEE_FEAR
+ COND_LIGHT_DAMAGE
+ COND_HEAVY_DAMAGE
+ COND_IDLE_INTERRUPT
+ COND_SQUAD_TAKING_POSITION
+ COND_IN_DANGER
+ COND_REACT_SURPRISED
+ COND_REACT_FRIENDLY_PLAYER
+ COND_ANY_MELEE_ATTACK
+ COND_PATH_INVOLVES_DANGEROUS_CLUSTER
+End_Schedule
+
+//=========================================================
+//=========================================================
+Schedule
+
+ SCHED_AT_ASSAULT_POINT
+
+ Tasks
+ TASK_FACE_ASSAULT_POINT 0
+
+ Interrupts
+ COND_NO_PRIMARY_AMMO
+ COND_INSIDE_DANGEROUS_AREA
+ COND_IN_DANGER
+End_Schedule
+
+
+//=========================================================
+//=========================================================
+Schedule
+
+ SCHED_ASSAULT_MOVE_AWAY
+
+ Tasks
+ TASK_MOVE_AWAY_PATH 120
+ TASK_MOVE_PATH 0
+ TASK_WAIT_FOR_MOVEMENT 0
+
+ Interrupts
+End_Schedule \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/base_vehicle.txt b/Northstar.CustomServers/scripts/aisettings/base_vehicle.txt
new file mode 100644
index 000000000..a13b30665
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/base_vehicle.txt
@@ -0,0 +1,35 @@
+base_vehicle
+{
+ leechAnimSet vehicle
+ leechAnimTag HIJACK
+ leechDataKnifeTag KNIFE
+
+ mechanical 1
+
+ DefaultModelName "models/vehicle/goblin_dropship/goblin_dropship.mdl"
+
+ FOV_Vert_Offset -20
+ FOV_Near_Dist 180 // use near values up to this distance
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 180
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 180
+ FOV_Idle_Far_Vert 180
+
+ FOV_Alert_Near_Horz 180
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 180
+ FOV_Alert_Far_Vert 180
+
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 180
+ FOV_Combat_Far_Vert 180
+
+ YawSpeed 25
+ MoveYawSpeed 15
+ AimAngularSpeed 25
+
+ PainOnHeavyDamageThreshold 0 // no heavy damage
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/classes.txt b/Northstar.CustomServers/scripts/aisettings/classes.txt
new file mode 100644
index 000000000..d250e2df9
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/classes.txt
@@ -0,0 +1,149 @@
+classes
+{
+ import base_vehicle.txt
+
+ import npc_bullseye.txt
+ import npc_drone.txt
+ import npc_drone_beam.txt
+ import npc_drone_cloaked.txt
+ import npc_drone_plasma.txt
+ import npc_drone_plasma_fd.txt
+ import npc_drone_plasma_fast.txt
+ import npc_drone_rocket.txt
+ import npc_drone_rocket_fast.txt
+ import npc_drone_shield.txt
+ import npc_drone_worker.txt
+ import npc_drone_worker_fast.txt
+ import npc_dropship.txt
+ import npc_dropship_dogfighter.txt
+ import npc_dropship_hero.txt
+ import npc_frag_drone.txt
+ import npc_frag_drone_fd.txt
+ import npc_frag_drone_throwable.txt
+ import npc_gunship.txt
+ import npc_gunship_scripted.txt
+ import npc_marvin.txt
+ import npc_pilot_elite.txt
+ import npc_pilot_elite_assassin.txt
+ import npc_pilot_elite_assassin_cqb.txt
+ import npc_pilot_elite_assassin_sniper.txt
+ import npc_pilot_elite_s2s.txt
+ import npc_prowler.txt
+ import npc_soldier.txt
+ import npc_soldier_bish.txt
+ import npc_soldier_blisk.txt
+ import npc_soldier_drone_summoner.txt
+ import npc_soldier_hero_bear.txt
+ import npc_soldier_hero_sarah.txt
+ import npc_soldier_shield_captain.txt
+ import npc_soldier_sidearm.txt
+ import npc_soldier_specialist.txt
+ import npc_soldier_specialist_militia.txt
+ import npc_soldier_spyglass.txt
+ import npc_soldier_training_sentry.txt
+ import npc_soldier_pve_sandbox.txt
+ import npc_soldier_pve_specialist.txt
+ import npc_soldier_pve_eliteguard.txt
+ import npc_spectre.txt
+ import npc_spectre_mortar.txt
+ import npc_stalker.txt
+ import npc_stalker_fd.txt
+ import npc_stalker_crawling.txt
+ import npc_stalker_crawling_fd.txt
+ import npc_stalker_crawling_mossy.txt
+ import npc_stalker_zombie.txt
+ import npc_stalker_zombie_mossy.txt
+ import npc_super_spectre.txt
+ import npc_super_spectre_burnmeter.txt
+ import npc_super_spectre_aitdm.txt
+ import npc_super_spectre_fd.txt
+ import npc_super_spectre_calmer.txt
+ import npc_titan.txt
+ import npc_titan_arc.txt
+ import npc_titan_atlas.txt
+ import npc_titan_atlas_stickybomb.txt
+ import npc_titan_atlas_stickybomb_boss_fd.txt
+ import npc_titan_atlas_tracker.txt
+ import npc_titan_atlas_tracker_fd_sniper.txt
+ import npc_titan_atlas_tracker_boss_fd.txt
+ import npc_titan_atlas_tracker_mortar.txt
+ import npc_titan_atlas_vanguard.txt
+ import npc_titan_atlas_vanguard_boss_fd.txt
+ import npc_titan_auto.txt
+ import npc_titan_auto_atlas.txt
+ import npc_titan_auto_atlas_rocketeer.txt
+ import npc_titan_auto_atlas_stickybomb.txt
+ import npc_titan_auto_atlas_ion_prime.txt
+ import npc_titan_auto_atlas_tracker.txt
+ import npc_titan_auto_atlas_tone_prime.txt
+ import npc_titan_auto_atlas_vanguard.txt
+ import npc_titan_auto_ogre.txt
+ import npc_titan_auto_ogre_fighter.txt
+ import npc_titan_auto_ogre_meteor.txt
+ import npc_titan_auto_ogre_scorch_prime.txt
+ import npc_titan_auto_ogre_minigun.txt
+ import npc_titan_auto_ogre_legion_prime.txt
+ import npc_titan_auto_stryder.txt
+ import npc_titan_auto_stryder_arc.txt
+ import npc_titan_auto_stryder_leadwall.txt
+ import npc_titan_auto_stryder_ronin_prime.txt
+ import npc_titan_auto_stryder_sniper.txt
+ import npc_titan_auto_stryder_northstar_prime.txt
+ import npc_titan_buddy.txt
+ import npc_titan_buddy_skyway.txt
+ import npc_titan_buddy_s2s.txt
+ import npc_titan_mortar.txt
+ import npc_titan_nuke.txt
+ import npc_titan_ogre.txt
+ import npc_titan_ogre_fighter.txt
+ import npc_titan_ogre_fighter_berserker_core.txt
+ import npc_titan_ogre_meteor.txt
+ import npc_titan_ogre_meteor_boss_fd.txt
+ import npc_titan_ogre_minigun.txt
+ import npc_titan_ogre_minigun_boss_fd.txt
+ import npc_titan_ogre_minigun_nuke.txt
+ import npc_titan_proto_stasisgun.txt
+ import npc_titan_sarah.txt
+ import npc_titan_vanguard.txt
+ import npc_titan_sniper.txt
+ import npc_titan_stryder.txt
+ import npc_titan_stryder_arc.txt
+ import npc_titan_stryder_leadwall.txt
+ import npc_titan_stryder_leadwall_boss_fd.txt
+ import npc_titan_stryder_leadwall_shift_core.txt
+ import npc_titan_stryder_leadwall_arc.txt
+ import npc_titan_stryder_rocketeer.txt
+ import npc_titan_stryder_rocketeer_dash_core.txt
+ import npc_titan_stryder_sniper.txt
+ import npc_titan_stryder_sniper_fd.txt
+ import npc_titan_stryder_sniper_boss_fd.txt
+ import npc_turret_mega.txt
+ import npc_turret_mega_nowindup.txt
+ import npc_turret_mega_old.txt
+ import npc_turret_mega_windup.txt
+ import npc_turret_mega_attrition.txt
+ import npc_turret_mega_fortwar.txt
+ import npc_turret_mega_frontierdefense.txt
+ import npc_turret_sentry.txt
+ import npc_turret_sentry_plasma.txt
+ import npc_turret_sentry_plasma_skyway.txt
+ import npc_turret_sentry_burn_card_at.txt
+ import npc_turret_sentry_burn_card_ap.txt
+ import npc_turret_sentry_burn_card_at_fd.txt
+ import npc_turret_sentry_burn_card_ap_fd.txt
+ import npc_turret_sentry_tactical_ability.txt
+ import npc_turret_sentry_tday.txt
+ import npc_turret_sentry_windup.txt
+
+ import npc_titan_atlas_stickybomb_bounty.txt
+ import npc_titan_atlas_ion_prime_bounty.txt
+ import npc_titan_atlas_tracker_bounty.txt
+ import npc_titan_atlas_vanguard_bounty.txt
+ import npc_titan_stryder_leadwall_bounty.txt
+ import npc_titan_stryder_sniper_bounty.txt
+ import npc_titan_stryder_northstar_prime_bounty.txt
+ import npc_titan_ogre_meteor_bounty.txt
+ import npc_titan_ogre_scorch_prime_bounty.txt
+ import npc_titan_ogre_minigun_bounty.txt
+ import npc_titan_ogre_legion_prime_bounty.txt
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_bullseye.txt b/Northstar.CustomServers/scripts/aisettings/npc_bullseye.txt
new file mode 100644
index 000000000..13a22de6a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_bullseye.txt
@@ -0,0 +1,8 @@
+npc_bullseye
+{
+ BaseClass "npc_bullseye"
+ title #NPC_BULLSEYE
+
+ noModel 1
+ meleeable 0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone.txt
new file mode 100644
index 000000000..e2b658354
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone.txt
@@ -0,0 +1,119 @@
+npc_drone
+{
+ HullType "HULL_SMALL"
+ AIClass flyingdrone
+ BodyType flyingdrone
+ SmartAmmoLockType any
+ drone_type "drone_type_basic"
+
+ BaseClass "npc_drone"
+
+ BehaviorSelector "behavior_drone"
+
+ mechanical 1
+
+ Health 90 [$sp]
+ Health 100 [$mp]
+
+ title #NPC_DRONE
+ ui_targetinfo "ui/targetinfo_npc_basic" [$mp]
+ ui_targetinfo "" [$sp]
+
+ //leechAnimSet flyingdrone
+ //onLeechFuncName DroneOnLeeched
+ //leechAnimTag HIJACK
+ //leechDataKnifeTag KNIFE
+ leechMaxDist 0
+
+ DefaultModelName "models/robots/drone_air_attack/drone_air_attack_rockets.mdl"
+
+ FOV_Vert_Offset 0
+ FOV_Near_Dist 500 // use near values up to this distance
+ FOV_Far_Dist 1700 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 360
+ FOV_Idle_Near_Vert 360
+ FOV_Idle_Far_Horz 400
+ FOV_Idle_Far_Vert 400
+
+ FOV_Alert_Near_Horz 150
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 110
+ FOV_Alert_Far_Vert 110
+
+ FOV_Combat_Near_Horz 160
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 130
+ FOV_Combat_Far_Vert 60
+
+ aimassist_bounds_override 40.0
+ aimassist_use_short_inner_bounds 1
+ aimassist_adspull_centerAttachmentName "HEADSHOT"
+ aimassist_adspull_centerRadius 11.0
+ aimassist_adspull_headshotAttachmentName ""
+ aimassist_adspull_headshotRadius 0.0
+
+ YawSpeed 35
+ MoveYawSpeed 15
+ AimAngularSpeed 10
+
+ // TEMP for now, make them basically blind unless you're right in front of them
+ NoticeDistNear_Idle 200
+ NoticeDistNear_Alert 300
+ NoticeDistNear_Combat 1000
+
+ NoticeDistFar_Idle 2000
+ NoticeDistFar_Alert 2000
+ NoticeDistFar_Combat 2000
+
+ NoticeTimeNear_Idle 0.1
+ NoticeTimeNear_Alert 0.1
+ NoticeTimeNear_Combat 0.0
+
+ NoticeTimeFar_Idle 1.0
+ NoticeTimeFar_Alert 1.0
+ NoticeTimeFar_Combat 1.0
+
+ NoticeForgetPreTime 0.3
+ NoticeForgetPostTime 5.0
+
+ rodeoMaxDist 0 // set to > 0 to make rodeoable
+ chaseStopDist 800
+ chaseStopDistHeavyArmor 1200
+
+ faceEnemyToleranceAngle 2
+ faceEnemyStrictToleranceAngle 1
+ PainOnHeavyDamageThreshold 0 // no heavy damage
+
+ waitBetweenWeaponBurst 1
+
+ magneticRange 125
+
+ waypointTolerance 16
+ randomHoverAmount 3
+ flyingStoppingTime 0.5
+ maxFlyingSpeed 600
+ maxFlyingSpeedNonCombat 150
+
+ maxPitch 15
+ maxRoll 30
+
+ patrolRangeMin 300
+ patrolRangeMax 600
+
+ hoverHeight 100 // if has target, offset above target's height
+
+ closestToCameraSound Drone_Mvmt_Hover_Hero // only works on drones for now
+ moveLoopingSound Drone_Mvmt_Hover
+ yawChangeSound Drone_Mvmt_Turn
+ yawChangeSoundAngle 100
+
+ //sound0 Drone.Ambient.Searching.Loop // scan loop (no enemy)
+ //sound1 Drone.Ambient.Has.Enemy.loop // scan loop (has enemy)
+ //sound2 Drone.Target.Locking // target locking on
+ //sound3 Drone_Beam_TargetPlayer // Target locked, plays at end of Drone.Target.Locking
+ //sound4 Drone.Target.Lost // target lost
+
+ showTitle 1 [$mp]
+ showTitle 0 [$sp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_beam.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_beam.txt
new file mode 100644
index 000000000..388ee3d69
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_beam.txt
@@ -0,0 +1,9 @@
+#base "npc_drone.txt"
+npc_drone_beam
+{
+ DefaultWeapon "mp_weapon_dronebeam"
+ ForceAutoPrecacheDefaultWeapon 1
+ title #NPC_DRONE_BEAM
+ drone_type "drone_type_beam"
+ DefaultModelName "models/robots/drone_air_attack/drone_air_attack_plasma.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_cloaked.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_cloaked.txt
new file mode 100644
index 000000000..1f1fe0793
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_cloaked.txt
@@ -0,0 +1,15 @@
+#base "npc_drone.txt"
+npc_drone_cloaked
+{
+ DefaultWeapon ""
+ title #NPC_DRONE_CLOAKED
+ drone_type "drone_type_cloaked"
+
+ YawSpeed 60
+ MoveYawSpeed 30
+ AimAngularSpeed 30
+
+ maxFlyingSpeed 1500
+ maxFlyingSpeedNonCombat 1500
+ hoverHeight 300 // if has target, offset above target's height
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma.txt
new file mode 100644
index 000000000..dd2665a4c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma.txt
@@ -0,0 +1,9 @@
+#base "npc_drone.txt"
+npc_drone_plasma
+{
+ DefaultWeapon "mp_weapon_droneplasma"
+ ForceAutoPrecacheDefaultWeapon 1
+ title #NPC_DRONE_PLASMA
+ drone_type "drone_type_plasma"
+ DefaultModelName "models/robots/drone_air_attack/drone_air_attack_plasma.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma_fast.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma_fast.txt
new file mode 100644
index 000000000..c05037c25
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma_fast.txt
@@ -0,0 +1,5 @@
+#base "npc_drone_plasma.txt"
+npc_drone_plasma_fast
+{
+ maxFlyingSpeedNonCombat 600 //120
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma_fd.txt
new file mode 100644
index 000000000..661403673
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_plasma_fd.txt
@@ -0,0 +1,20 @@
+#base "npc_drone_plasma.txt"
+npc_drone_plasma_fd
+{
+ DefaultWeapon "mp_weapon_droneplasma"
+ ForceAutoPrecacheDefaultWeapon 1
+ title #NPC_DRONE_PLASMA
+ drone_type "drone_type_plasma"
+ DefaultModelName "models/robots/drone_air_attack/drone_air_attack_plasma.mdl"
+
+ waypointTolerance 400
+ randomHoverAmount 1
+ flyingStoppingTime 0.0
+ maxFlyingSpeed 400
+ maxFlyingSpeedNonCombat 300
+
+ YawSpeed 75
+ MoveYawSpeed 65
+
+ SmartAmmoLockType small
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_rocket.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_rocket.txt
new file mode 100644
index 000000000..d330a3d25
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_rocket.txt
@@ -0,0 +1,8 @@
+#base "npc_drone.txt"
+npc_drone_rocket
+{
+ DefaultWeapon "mp_weapon_dronerocket"
+ title #NPC_DRONE_ROCKET
+ drone_type "drone_type_rocket"
+ DefaultModelName "models/robots/drone_air_attack/drone_air_attack_rockets.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_rocket_fast.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_rocket_fast.txt
new file mode 100644
index 000000000..7bd1a3e4c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_rocket_fast.txt
@@ -0,0 +1,5 @@
+#base "npc_drone_rocket.txt"
+npc_drone_rocket_fast
+{
+ maxFlyingSpeedNonCombat 600 //120
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_shield.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_shield.txt
new file mode 100644
index 000000000..e4e56d124
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_shield.txt
@@ -0,0 +1,9 @@
+#base "npc_drone.txt"
+npc_drone_shield
+{
+ title #NPC_DRONE_SHIELD
+ drone_type "drone_type_shield"
+ Health 350
+
+ nonCombatAI 1
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_worker.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_worker.txt
new file mode 100644
index 000000000..f400d2c5f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_worker.txt
@@ -0,0 +1,99 @@
+npc_drone_worker
+{
+ HullType "HULL_SMALL"
+ AIClass flyingdrone
+ BodyType flyingdrone
+ SmartAmmoLockType any
+
+ marvin_type "marvin_type_drone"
+ drone_type "drone_type_marvin"
+ BaseClass "npc_drone"
+ nonCombatAI 1
+
+ BehaviorSelector "behavior_drone"
+ title #NPC_DRONE_WORKER
+
+ Health 20 [$sp]
+ Health 20 [$mp]
+
+ //leechAnimSet flyingdrone
+ //onLeechFuncName DroneOnLeeched
+ //leechAnimTag HIJACK
+ //leechDataKnifeTag KNIFE
+ leechMaxDist 0
+
+ DefaultModelName "models/robots/aerial_unmanned_worker/aerial_unmanned_worker.mdl"
+
+ FOV_Vert_Offset 0
+ FOV_Near_Dist 500 // use near values up to this distance
+ FOV_Far_Dist 2000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 60
+ FOV_Idle_Far_Vert 60
+
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 60
+ FOV_Alert_Far_Vert 60
+
+ FOV_Combat_Near_Horz 160
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 130
+ FOV_Combat_Far_Vert 60
+
+ aimassist_adspull_centerAttachmentName "CHESTFOCUS"
+ aimassist_adspull_centerRadius 11.0
+ aimassist_adspull_headshotAttachmentName ""
+ aimassist_adspull_headshotRadius 0.0
+
+ YawSpeed 30
+ MoveYawSpeed 15
+ AimAngularSpeed 10
+
+ // TEMP for now, make them basically blind unless you're right in front of them
+ NoticeDistNear_Idle 200
+ NoticeDistNear_Alert 300
+ NoticeDistNear_Combat 1000
+
+ NoticeDistFar_Idle 2000
+ NoticeDistFar_Alert 2000
+ NoticeDistFar_Combat 2000
+
+ NoticeTimeNear_Idle 0.1
+ NoticeTimeNear_Alert 0.1
+ NoticeTimeNear_Combat 0.0
+
+ NoticeTimeFar_Idle 1.0
+ NoticeTimeFar_Alert 1.0
+ NoticeTimeFar_Combat 1.0
+
+ NoticeForgetPreTime 0.3
+ NoticeForgetPostTime 5.0
+
+ rodeoMaxDist 0 // set to > 0 to make rodeoable
+ chaseStopDist 700
+ chaseStopDistHeavyArmor 1000
+
+ faceEnemyToleranceAngle 2
+ faceEnemyStrictToleranceAngle 1
+ PainOnHeavyDamageThreshold 0 // no heavy damage
+
+ waitBetweenWeaponBurst 1
+
+ magneticRange 30
+ randomHoverAmount 1
+ flyingStoppingTime 0.75
+ maxFlyingSpeed 200
+ maxFlyingSpeedNonCombat 120
+
+ minGoalRadius 16
+
+ maxPitch 45
+ maxRoll 45
+
+ moveLoopingSound WorkerDrone_Mvmt_Hover
+
+ hoverHeight 100 // if has target, offset above target's height
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_drone_worker_fast.txt b/Northstar.CustomServers/scripts/aisettings/npc_drone_worker_fast.txt
new file mode 100644
index 000000000..e9e470bdd
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_drone_worker_fast.txt
@@ -0,0 +1,6 @@
+#base "npc_drone_worker.txt"
+npc_drone_worker_fast
+{
+ maxFlyingSpeed 400 //400
+ maxFlyingSpeedNonCombat 400 //120
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_dropship.txt b/Northstar.CustomServers/scripts/aisettings/npc_dropship.txt
new file mode 100644
index 000000000..851b8f644
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_dropship.txt
@@ -0,0 +1,49 @@
+#base "base_vehicle.txt"
+npc_dropship
+{
+ BaseClass "npc_dropship"
+ title #NPC_DROPSHIP
+ titleIMC #NPC_GOBLIN
+ titleMIL #NPC_CROW
+ ui_targetinfo "ui/targetinfo_titan" [$mp]
+ ui_targetinfo "" [$sp]
+
+ HullType "HULL_FLYING_VEHICLE"
+ AIClass vehicle
+ BodyType vehicle
+
+ collideWithPlayer 1
+ ArmorType heavy
+ SmartAmmoLockType large
+ DrawEnemyHealthBar 1
+ DrawTargetHealthBar 1
+
+ BehaviorSelector "behavior_dropship"
+
+ DefaultModelName "models/vehicle/goblin_dropship/goblin_dropship.mdl"
+ DefaultModelName_IMC "models/vehicle/goblin_dropship/goblin_dropship.mdl"
+ DefaultModelName_MIL "models/vehicle/crow_dropship/crow_dropship.mdl"
+
+ DefaultWeapon ""
+
+ Health 10000 [$sp]
+ Health 10000 [$mp]
+
+ meleeable false
+
+ magneticRange 190
+
+ YawSpeed 45
+ goalTolerance 400
+ waypointTolerance 1000
+ flyingStoppingTime 1.0
+ tiltLookaheadTime 1.0
+ yawLookaheadTime 2.0
+ randomHoverAmount 15
+ maxFlyingAccel 2000
+ maxFlyingSpeed 3000
+ maxFlyingSpeedNonCombat 3000
+
+ maxPitch 10
+ maxRoll 30
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_dropship_dogfighter.txt b/Northstar.CustomServers/scripts/aisettings/npc_dropship_dogfighter.txt
new file mode 100644
index 000000000..a2c6c88e2
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_dropship_dogfighter.txt
@@ -0,0 +1,8 @@
+#base "npc_dropship.txt"
+npc_dropship_dogfighter
+{
+ title #NPC_DROPSHIP_DOGFIGHTER
+ DefaultModelName "models/vehicle/straton/straton_imc_gunship_01.mdl"
+ DefaultModelName_IMC "models/vehicle/straton/straton_imc_gunship_01.mdl"
+ DefaultModelName_MIL "models/vehicle/hornet/hornet_fighter.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_dropship_hero.txt b/Northstar.CustomServers/scripts/aisettings/npc_dropship_hero.txt
new file mode 100644
index 000000000..a7b9214cf
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_dropship_hero.txt
@@ -0,0 +1,9 @@
+#base "npc_dropship.txt"
+npc_dropship_hero
+{
+ ui_targetinfo "ui/targetinfo_titan" [$mp]
+
+ DefaultModelName "models/vehicle/goblin_dropship/goblin_dropship_hero.mdl"
+ DefaultModelName_IMC "models/vehicle/goblin_dropship/goblin_dropship_hero.mdl"
+ DefaultModelName_MIL "models/vehicle/crow_dropship/crow_dropship_hero.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_frag_drone.txt b/Northstar.CustomServers/scripts/aisettings/npc_frag_drone.txt
new file mode 100644
index 000000000..9b435cdb7
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_frag_drone.txt
@@ -0,0 +1,92 @@
+npc_frag_drone
+{
+ HullType "HULL_HUMAN"
+ orientToGround 1
+ BodyType human
+ ArmorType normal
+ smartAmmoLockAttachmentName0 CHESTFOCUS
+ title #NPC_SPECTRE_SUICIDE
+
+ footstep_type "Tick"
+
+ minSpeedScale 0.8
+ maxSpeedScale 1.5
+
+ BaseClass npc_frag_drone
+ AIClass frag_drone
+ TraverseAnimType frag_drone
+ BehaviorSelector "behavior_frag_drone"
+
+ mechanical 1
+
+ DefaultModelName "models/robots/drone_frag/drone_frag.mdl"
+ DefaultModelName_IMC "models/robots/drone_frag/drone_frag.mdl"
+ DefaultModelName_MIL "models/robots/drone_frag/drone_frag.mdl"
+ DefaultWeapon ""
+ WeaponCapacity "Locked"
+
+ Health 45 [$sp]
+ Health 110 [$mp]
+
+ LookDistDefault_Idle 2000
+ LookDistDefault_Alert 2000
+ LookDistDefault_Combat 2000
+
+ FOV_Idle_Near_Horz 360
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 360
+ FOV_Idle_Far_Vert 180
+
+ FOV_Alert_Near_Horz 360
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 360
+ FOV_Alert_Far_Vert 180
+
+ FOV_Combat_Near_Horz 360
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 360
+ FOV_Combat_Far_Vert 180
+
+ faceEnemyToleranceAngle 180
+ faceEnemyStrictToleranceAngle 180
+ shouldConsiderFacingDir 0
+ moveYawSpeed 30
+
+ aimassist_bounds_override 25
+ aimassist_adspull_centerAttachmentName "aimassist_center"
+ aimassist_adspull_centerRadius 13.0
+ aimassist_adspull_headshotAttachmentName ""
+ aimassist_adspull_headshotRadius 0.0
+
+ MeleeDamageMin 120
+ MeleeDamageMax 120
+ MeleeDamageRadius 32
+ MeleeRange 16
+ meleeImpactEffectTable "melee_spectre"
+
+ meleeable 0
+ leechMaxDist 0
+
+ MinStartMoveDist 100
+ MaxArrivalDist 150
+ MinForceWalkDist 0
+ moveDeflectionLookAheadTime 0.5
+
+ allowPatrol 1
+ allowInvestigate 1
+
+ chaseStopDist 16
+ chaseStopDistHeavyArmor 16
+
+ shootableByFriendlyPlayer 1
+ showFriendlyIcon 1
+ ui_targetinfo "ui/targetinfo_npc_basic" [$mp]
+ ui_targetinfo "" [$sp]
+
+ aiEnemy_usePriorityDist 1300
+
+ magneticRange 125
+
+ suicideExplosionDelay 2.1
+ suicideExplosionDistance 200.0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_frag_drone_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_frag_drone_fd.txt
new file mode 100644
index 000000000..ce679d194
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_frag_drone_fd.txt
@@ -0,0 +1,23 @@
+#base "npc_frag_drone.txt"
+npc_frag_drone_fd
+{
+ SuicideChaseTime 3.0 [$sp]
+ SuicideChaseTime 10.0 [$mp]
+
+ Health 45 [$sp]
+ Health 100 [$mp]
+
+ DefaultModelName "models/weapons/sentry_frag/sentry_frag.mdl"
+ DefaultModelName_IMC "models/weapons/sentry_frag/sentry_frag.mdl"
+ DefaultModelName_MIL "models/weapons/sentry_frag/sentry_frag.mdl"
+
+
+ suicideExplosionDistance 200.0
+
+// OverrideOverloadAnim "sp_suicide_spectre_explode_stand_short"
+ JumpAtTitans 0
+ damageDefOverride "damagedef_frag_drone_explode_FD"
+
+ minSpeedScale 2.0
+ maxSpeedScale 2.5
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_frag_drone_throwable.txt b/Northstar.CustomServers/scripts/aisettings/npc_frag_drone_throwable.txt
new file mode 100644
index 000000000..53458c3cc
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_frag_drone_throwable.txt
@@ -0,0 +1,19 @@
+#base "npc_frag_drone.txt"
+npc_frag_drone_throwable
+{
+ title "#WPN_FRAG_DRONE"
+ suicideExplosionDelay 1.25 [$mp]
+ suicideExplosionDistance 100.0 [$mp]
+
+ Health 60 [$sp]
+ Health 60 [$mp]
+
+ DefaultModelName "models/weapons/sentry_frag/sentry_frag.mdl"
+ DefaultModelName_IMC "models/weapons/sentry_frag/sentry_frag.mdl"
+ DefaultModelName_MIL "models/weapons/sentry_frag/sentry_frag.mdl"
+
+ footstep_type "sentryfragdrone"
+
+ SuicideChaseTime 3.0 [$sp]
+ SuicideChaseTime 0.0 [$mp]
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_gunship.txt b/Northstar.CustomServers/scripts/aisettings/npc_gunship.txt
new file mode 100644
index 000000000..e4df1fe1f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_gunship.txt
@@ -0,0 +1,83 @@
+#base "base_vehicle.txt"
+npc_gunship
+{
+ HullType "HULL_FLYING_VEHICLE"
+ AIClass vehicle
+ BodyType vehicle
+ collideWithPlayer 1
+
+ ArmorType heavy
+ SmartAmmoLockType large
+
+ title #NPC_GUNSHIP
+ titleIMC #NPC_PHANTOM
+ titleMIL #NPC_HORNET
+ ui_targetinfo "ui/targetinfo_titan" [$mp]
+ ui_targetinfo "" [$sp]
+
+ BaseClass "npc_gunship"
+
+ DefaultModelName "models/vehicle/straton/straton_imc_gunship_01.mdl"
+ DefaultWeapon "mp_weapon_gunship_missile"
+ //DefaultWeapon "mp_weapon_gunship_turret"
+ BehaviorSelector "behavior_gunship"
+ chaseStopDist 700
+ chaseStopDistHeavyArmor 1000
+ DrawEnemyHealthBar 1
+ DrawTargetHealthBar 1
+
+ Health 10000 [$sp]
+ Health 10000 [$mp]
+
+ aiEnemy_priority 50
+
+ faceEnemyToleranceAngle 2
+ faceEnemyStrictToleranceAngle 1
+ aimConeCos 0.906 // 25 degree
+ aimConeCloseCos 0.906 // 25 degree
+
+ meleeable false
+ circleStrafeDist 1300
+
+ magneticRange 190
+ leechMaxDist 150
+ rodeoMaxDist 200
+
+ waitBetweenWeaponBurst 0
+
+ YawSpeed 30
+
+ minGoalRadius 300
+ patrolRangeMin 2000
+ patrolRangeMax 3000
+
+ goalTolerance 400
+ waypointTolerance 350
+ flyingStoppingTime 1.0
+ tiltLookaheadTime 2.0
+ yawLookaheadTime 2.0
+ randomHoverAmount 15
+ maxFlyingAccel 1500
+ maxFlyingSpeed 2200
+ maxFlyingSpeedNonCombat 1000
+
+ attackRunHeight 300
+ attackRunDist 2500
+
+ maxPitch 30
+ maxRoll 30
+
+ missileAwarenessCos -1
+
+ dodgePeriod 10 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 3 // Don't dodge more than this many times in dodgePeriod
+ minConsecutiveDodgeTime 2
+ StrafeDodgeDamage 400
+ flyingDodgeDist 800
+ enemyAimAtMeWidthHeavyArmor 150
+
+ chasecamDistanceMax 320
+ chasecamMaxOrbitDepth 90
+ chasecamOffsetUp 150
+ chasecamOffsetRight 110
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_gunship_scripted.txt b/Northstar.CustomServers/scripts/aisettings/npc_gunship_scripted.txt
new file mode 100644
index 000000000..e387551f3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_gunship_scripted.txt
@@ -0,0 +1,6 @@
+#base "npc_gunship.txt"
+npc_gunship_scripted
+{
+ BaseClass "npc_gunship_scripted"
+ title #NPC_GUNSHIP_SCRIPTED
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_marvin.txt b/Northstar.CustomServers/scripts/aisettings/npc_marvin.txt
new file mode 100644
index 000000000..495f3cceb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_marvin.txt
@@ -0,0 +1,73 @@
+npc_marvin
+{
+ AIClass marvin
+
+ title #NPC_MARVIN
+ ui_targetinfo "ui/targetinfo_npc_basic" [$mp]
+ ui_targetinfo "" [$sp]
+
+ HullType "HULL_HUMAN"
+ BodyType marvin
+ SmartAmmoLockType none
+ smartAmmoLockAttachmentName0 HEADFOCUS
+
+ marvin_type "marvin_type_walker"
+
+ BehaviorSelector "behavior_marvin"
+
+ mechanical 1
+ nonCombatAI 1
+
+ BaseClass "npc_marvin"
+
+ footstep_type "robot"
+
+ leechAnimSet marvin
+
+ DefaultModelName "models/robots/marvin/marvin.mdl"
+ DefaultWeapon ""
+
+ Health 10 [$sp]
+ Health 10 [$mp]
+
+ FOV_Vert_Offset -10
+ FOV_Near_Dist 150 // distance at which we transition between near and far FOV values
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 180
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 180
+ FOV_Idle_Far_Vert 180
+
+ FOV_Alert_Near_Horz 180
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 180
+ FOV_Alert_Far_Vert 180
+
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 180
+ FOV_Combat_Far_Vert 180
+
+ YawSpeed 30
+ MoveYawSpeed 12
+ AimAngularSpeed 7
+
+ MeleeDamageMin 10
+ MeleeDamageMax 20
+ MeleeDamageRadius 64
+
+ fallDeathHeight 300
+
+ PainOnHeavyDamageThreshold 0
+ PainOnRepeatDamageThreshold 0
+
+ MinStartMoveDist 100
+ MaxArrivalDist 120
+ MinForceWalkDist 150
+
+ magneticRange 60
+
+ moveDeflectionLookAheadTime 1.5
+ reactChanceDefault 100
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite.txt b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite.txt
new file mode 100644
index 000000000..8bd840296
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite.txt
@@ -0,0 +1,107 @@
+npc_pilot_elite
+{
+ HullType "HULL_HUMAN"
+ AIClass pilot_elite
+ BodyType human
+ ArmorType normal
+ TraverseAnimType human
+
+ smartAmmoLockAttachmentName0 CHESTFOCUS
+
+ footstep_type "grunt"
+
+ title #NPC_PILOT
+ ui_targetinfo "ui/targetinfo_npc_basic" [$mp]
+ ui_targetinfo "ui/targetinfo_npc_basic" [$sp]
+
+ BaseClass "npc_pilot_elite"
+
+ DefaultModelName "models/humans/pilots/sp_medium_reaper_m.mdl"
+ DefaultWeapon "mp_weapon_r97"
+ BehaviorSelector "behavior_pilot_elite"
+
+ Health 250 [$sp]
+ Health 250 [$mp]
+
+ PainOnHeavyDamageThreshold 250
+ breakOutOfPainDamageThreshold 150
+ PainOnRepeatDamageThreshold 300
+
+ allowUseCover 1
+
+ FOV_Vert_Offset 0
+ FOV_Near_Dist 150 // distance at which we transition between near and far FOV values
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 60
+ FOV_Idle_Far_Vert 60
+
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 60
+ FOV_Alert_Far_Vert 60
+
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 130
+ FOV_Combat_Far_Vert 100
+
+ AimAngularSpeed 7
+
+ YawSpeed 30
+ MoveYawSpeed 30
+
+ faceEnemyWhileMovingDist 1000
+
+ aimassist_adspull_centerAttachmentName "CHESTFOCUS"
+ aimassist_adspull_centerRadius 11.0
+ aimassist_adspull_headshotAttachmentName "HEADSHOT"
+ aimassist_adspull_headshotRadius 13.0
+
+ MeleeDamageMin 70
+ MeleeDamageMax 80
+ MeleeDamageRadius 32
+ MeleeRange 80
+ MeleeCosAngleRange 0.2
+ MeleeChargeRange 220
+ MeleeChargeDamageMin 180
+ MeleeChargeDamageMax 200
+ MeleeChargeDamageRadius 70
+ meleeInterval 0
+ meleeChargeInterval 3
+ meleeImpactEffectTable "melee_spectre"
+
+ blockPeriod 7
+ maxBlockPerPeriod 3
+
+ dodgePeriod 4 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 // Don't dodge more than this many times in dodgePeriod
+ minConsecutiveDodgeTime 1.5
+ dodgeForwardThreshold 800
+ dodgeBackwardThreshold 0
+ StrafeDodgeDamage 300
+
+ circleStrafeDist 500
+ circleStrafeAngleIncrement 30
+
+ chaseStopDist 16
+ chaseStopDistHeavyArmor 250
+
+ tauntInterval 60
+
+ MinStartMoveDist 100
+ MaxArrivalDist 150
+ MinForceWalkDist 0 // 150
+
+ leechMaxDist 0
+ showFriendlyIcon 1
+
+ meleeable 1
+
+ magneticRange 125
+
+ evasiveCombatTotalHealthDiffPct 0.8
+ aggressiveCombatTotalHealthDiffPct 0.95
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin.txt b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin.txt
new file mode 100644
index 000000000..144d4df19
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin.txt
@@ -0,0 +1,89 @@
+npc_pilot_elite_assassin
+{
+ HullType "HULL_HUMAN"
+ AIClass pilot_assassin
+ BodyType human
+ ArmorType normal
+ smartAmmoLockAttachmentName0 CHESTFOCUS
+ TraverseAnimType spectre
+
+ title #NPC_PILOT_ELITE_ASSASSIN
+
+ BaseClass "npc_pilot_elite"
+
+ DefaultModelName "models/Robots/spectre/spectre_ninja.mdl"
+ DefaultWeapon "mp_weapon_r97"
+ BehaviorSelector "behavior_pilot_elite"
+
+ Health 1500
+
+ FOV_Vert_Offset 0
+ FOV_Near_Dist 150 // distance at which we transition between near and far FOV values
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 60
+ FOV_Idle_Far_Vert 60
+
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 60
+ FOV_Alert_Far_Vert 60
+
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 130
+ FOV_Combat_Far_Vert 100
+
+ YawSpeed 30
+ MoveYawSpeed 30
+ AimAngularSpeed 20
+
+ aimassist_adspull_centerAttachmentName "CHESTFOCUS"
+ aimassist_adspull_centerRadius 11.0
+ aimassist_adspull_headshotAttachmentName "HEADSHOT"
+ aimassist_adspull_headshotRadius 13.0
+
+ MeleeDamageMin 70
+ MeleeDamageMax 80
+ MeleeDamageRadius 32
+ MeleeRange 80
+ MeleeCosAngleRange 0.2
+ MeleeChargeRange 220
+ MeleeChargeDamageMin 180
+ MeleeChargeDamageMax 200
+ MeleeChargeDamageRadius 70
+ meleeInterval 0
+ meleeChargeInterval 3
+ meleeImpactEffectTable "melee_spectre"
+
+ blockPeriod 7
+ maxBlockPerPeriod 3
+
+ dodgePeriod 4 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 // Don't dodge more than this many times in dodgePeriod
+ minConsecutiveDodgeTime 1.5
+ dodgeForwardThreshold 800
+ dodgeBackwardThreshold 0
+ StrafeDodgeDamage 300
+ circleStrafeDist 400
+
+ chaseStopDist 16
+ chaseStopDistHeavyArmor 250
+
+ tauntInterval 60
+
+ MinStartMoveDist 100
+ MaxArrivalDist 150
+ MinForceWalkDist 150
+
+ leechMaxDist 0
+ showFriendlyIcon 1
+
+ decloakOnShoot 1
+
+ meleeable 1
+
+ magneticRange 125
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin_cqb.txt b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin_cqb.txt
new file mode 100644
index 000000000..74bf638ef
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin_cqb.txt
@@ -0,0 +1,16 @@
+#base "npc_pilot_elite_assassin.txt"
+npc_pilot_elite_assassin_cqb
+{
+ title #NPC_PILOT_ELITE_ASSASSIN_CQB
+
+ BehaviorSelector "behavior_pilot_elite_assassin_cqb"
+
+ canCloak 1
+
+// blockPeriod 7
+// maxBlockPerPeriod 3
+
+ dodgePeriod 5 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 3 // Don't dodge more than this many times in dodgePeriod
+ minConsecutiveDodgeTime 1.0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin_sniper.txt b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin_sniper.txt
new file mode 100644
index 000000000..f9885b1d5
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_assassin_sniper.txt
@@ -0,0 +1,14 @@
+#base "npc_pilot_elite_assassin.txt"
+npc_pilot_elite_assassin_sniper
+{
+ title #NPC_PILOT_ELITE_ASSASSIN_SNIPE
+
+ canCloak 1
+
+ DefaultWeapon "mp_weapon_dmr"
+ BehaviorSelector "behavior_pilot_elite_assassin_sniper"
+
+ Health 1500 //hack for testing
+
+ decloakOnShoot 1
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_s2s.txt b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_s2s.txt
new file mode 100644
index 000000000..2a497d2ea
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_pilot_elite_s2s.txt
@@ -0,0 +1,6 @@
+#base "npc_pilot_elite.txt"
+
+npc_pilot_elite_s2s
+{
+ LookDistDefault_Combat 15000 //so the 6-4 can shoot at distant ships in the intro
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_prowler.txt b/Northstar.CustomServers/scripts/aisettings/npc_prowler.txt
new file mode 100644
index 000000000..1df9538f0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_prowler.txt
@@ -0,0 +1,148 @@
+npc_prowler
+{
+ HullType "HULL_PROWLER"
+ useSequenceBounds 1
+ orientToGround 1
+ AIClass prowler
+ TraverseAnimType prowler
+ BodyType human
+ ArmorType normal
+ collideWithPlayer 1
+
+ title #NPC_PROWLER
+ ui_targetinfo "ui/targetinfo_npc_basic" [$mp]
+ ui_targetinfo "" [$sp]
+
+ BaseClass "npc_prowler"
+
+ footstep_type "prowler"
+ quadruped 1
+
+ DefaultModelName "models/creatures/prowler/r2_prowler.mdl"
+ DefaultWeapon ""
+ BehaviorSelector "behavior_prowler"
+
+ GibModel0 "models/gibs/human_gibs.mdl"
+
+ melee_charge_set prowler
+
+ Health 280
+
+ painOnHeavyDamageThreshold 50
+ PainOnRepeatDamageThreshold 210
+ heavyPainMinInterval 4
+ PainOnSurpriseHit 0
+ PainWhileRunning 1
+
+ FOV_Vert_Offset 0
+ FOV_Near_Dist 150 // distance at which we transition between near and far FOV values
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 160
+ FOV_Idle_Near_Vert 160
+ FOV_Idle_Far_Horz 120
+ FOV_Idle_Far_Vert 120
+
+ FOV_Alert_Near_Horz 160
+ FOV_Alert_Near_Vert 160
+ FOV_Alert_Far_Horz 150
+ FOV_Alert_Far_Vert 120
+
+ FOV_Combat_Near_Horz 160
+ FOV_Combat_Near_Vert 160
+ FOV_Combat_Far_Horz 150
+ FOV_Combat_Far_Vert 120
+
+ faceEnemyToleranceAngle 45
+ faceEnemyStrictToleranceAngle 45
+ MoveYawSpeed 15
+ AimAngularSpeed 20
+ allowTurn45Anims 0
+
+ aimassist_adspull_centerAttachmentName "aimassist_center"
+ aimassist_adspull_centerRadius 18.0
+ aimassist_adspull_headshotAttachmentName "HEADSHOT"
+ aimassist_adspull_headshotRadius 13.0
+
+ MeleeDamageMin 60 [$mp]
+ MeleeDamageMax 80 [$mp]
+ MeleeDamageMinHeavyArmor 60 [$mp]
+ MeleeDamageMaxHeavyArmor 80 [$mp]
+ MeleeDamageRadius 60 [$mp]
+ MeleeRange 80 [$mp]
+ MeleeInterval 1.5 [$mp]
+ MeleeMaxCombo 2 [$mp]
+ MeleeChargeDamageMin 100 [$mp]
+ MeleeChargeDamageMax 100 [$mp]
+ MeleeChargeDamageMinHeavyArmor 100 [$mp]
+ MeleeChargeDamageMaxHeavyArmor 100 [$mp]
+ MeleeChargeDamageRadius 60 [$mp]
+ MeleeChargeRange 240 [$mp]
+ meleeChargeInterval 3 [$mp]
+
+ MeleeDamageMin 15 [$sp]
+ MeleeDamageMax 20 [$sp]
+ MeleeDamageMinHeavyArmor 150 [$sp]
+ MeleeDamageMaxHeavyArmor 150 [$sp]
+ MeleeDamageRadius 60 [$sp]
+ MeleeRange 80 [$sp]
+ MeleeInterval 1.5 [$sp]
+ MeleeMaxCombo 2 [$sp]
+ MeleeChargeDamageMin 25 [$sp]
+ MeleeChargeDamageMax 25 [$sp]
+ MeleeChargeDamageMinHeavyArmor 300 [$sp]
+ MeleeChargeDamageMaxHeavyArmor 300 [$sp]
+ MeleeChargeDamageRadius 60 [$sp]
+ MeleeChargeRange 240 [$sp]
+ meleeChargeInterval 3 [$sp]
+
+ MeleeCosAngleRange 0.707 // Matches faceEnemyStrictToleranceAngle
+ MeleeEnemyArmorType any
+ MeleeChargeEnemyArmorType any
+
+ meleeHighOffset 70
+
+ meleeImpactEffectTable "melee_prowler"
+ syncedMeleeEngageDist 40
+ meleeable 1
+
+ canBeAlertedByEnemiesOutsideOfMaxDist 0
+ returnToIdleTime 15
+ maxEnemyDist 950
+ maxEnemyDistHeavyArmor 1200
+ aiEnemy_usePriorityDist 0
+
+ pathMaxDetourBase 1000
+ pathMaxDetourMultiplier 2.0
+ minGoalRadius 256
+
+ MinStartMoveDist 100
+ MaxArrivalDist 230
+ MinForceWalkDist 0
+ circleStrafeDist 420
+ enemyAimAtMeWidth 100
+
+ chaseStopDist 100
+ chaseStopDistHeavyArmor 200
+ chaseStopVerticalDist 300
+ chaseTryRunningDodgeWhenAimedAtMinDist 300 // This should be greater than MeleeChargeRange so that he prioritizes leap attacking over dodging
+
+ dodgePeriod 4 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 // Don't dodge more than this many times in dodgePeriod
+
+ traverseCostFactor 0.5
+
+ showFriendlyIcon 1
+
+ allowFlee 1
+ allowSignals 1
+ allowPatrol 1
+ allowInvestigate 0 // no investigate
+
+ patrolRangeMin 600
+ patrolRangeMax 800
+
+ moveDeflectionLookAheadTime 0.5
+ reactChanceDefault 100
+ reactBulletChanceDefault 90
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier.txt
new file mode 100644
index 000000000..e5dcfeb17
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier.txt
@@ -0,0 +1,201 @@
+
+npc_soldier
+{
+ AIClass human
+ ArmorType normal
+ BaseClass "npc_soldier"
+ BodyType human
+ HullType "HULL_HUMAN"
+ SmartAmmoLockType small // defaults to small
+ TraverseAnimType human
+ traverseCostFactor 5.0
+
+ difficultTraverseFlags 256 // 1 << 8 (TRAVERSE_JUMP_UP_DOWN_128)
+
+ BehaviorSelector "behavior_soldier" [$mp]
+ BehaviorSelector "behavior_sp_soldier" [$sp]
+
+ footstep_type "grunt"
+ footstepSprintSpeedThreshold 100
+
+ title #NPC_SOLDIER
+ title_IMC #NPC_GRUNT_IMC
+ title_MIL #NPC_GRUNT_MILITIA
+ ui_targetinfo "ui/targetinfo_soldier_bounty" [$mp]
+ ui_targetinfo "ui/targetinfo_cockpit_name" [$sp]
+
+ IsGenericGrunt 1
+
+ GrenadeWeaponName mp_weapon_frag_grenade
+
+ AimAngularSpeed 7
+ allowFlee 1
+ allowInvestigate 1 [$sp]
+ allowPatrol 1 [$sp]
+ allowSignals 1
+ allowUseCover 1
+ DefaultModelName "models/humans/grunts/imc_grunt_rifle.mdl"
+ DefaultModelName_IMC "models/humans/grunts/imc_grunt_rifle.mdl"
+ DefaultModelName_MIL "models/humans/grunts/mlt_grunt_rifle.mdl"
+
+ DefaultWeapon "mp_weapon_rspn101"
+ WeaponCapacity "PilotMainWeapons"
+ GibModel0 "models/gibs/human_gibs.mdl"
+
+ headshotFX "P_headshot_human"
+
+ faceEnemyStrictToleranceAngle 30
+ faceEnemyWhileMovingDist 400 [$sp]
+ faceEnemyWhileMovingDist 600 [$mp]
+
+ Health 90 [$sp]
+ Health 50 [$mp]
+
+ PainOnRepeatDamageThreshold 39 [$sp]
+ PainOnRepeatDamageThreshold 15 [$mp]
+ RepeatDamageTimeInterval 8
+
+ PainOnHeavyDamageThreshold 80 // more than health so never happens for base soldier but will happen for higher health ones
+ PainOnSurpriseHit 1
+ PainWhileRunning 1
+
+ fallDeathHeight 300
+
+ aimassist_adspull_centerAttachmentName "CHESTFOCUS"
+ aimassist_adspull_centerRadius 11.0
+ aimassist_adspull_headshotAttachmentName "HEADSHOT"
+ aimassist_adspull_headshotRadius 8.0
+
+ MeleeDamageMax 30 [$mp]
+ MeleeDamageMin 30 [$mp]
+ MeleeDamageMax 53 [$sp]
+ MeleeDamageMin 53 [$sp]
+ MeleeDamageRadius 40 [$mp]
+ MeleeRange 40 [$mp]
+ MeleeDamageRadius 60 [$sp]
+ MeleeRange 60 [$sp]
+ MeleeDamageForce 15000
+
+ MeleeChargeDamageMin 20
+ MeleeChargeDamageMax 20
+ MeleeChargeDamageRadius 35
+ MeleeChargeRange 0
+ MeleeChargeCosAngleRange 0.866 // 30 degrees
+ MeleeChargeInterval 3
+ MeleeChargeEnemyArmorType normal
+ MeleeChargeOnlyPlayers 1
+
+ MaxArrivalDist 170
+ MinForceWalkDist 150
+ MinStartMoveDist 100
+ MoveYawSpeed 12
+
+ moveDeflectionLookAheadTime 0.5 [$sp]
+ moveDeflectionLookAheadTime 0.5 [$mp]
+
+ LookDistDefault_Alert 3000
+ LookDistDefault_Combat 5000
+ LookDistDefault_Idle 1500
+
+ "mp" [$mp]
+ {
+ crouchCombatDistInner 350
+ crouchCombatDistOuter 450
+
+ FOV_Alert_Far_Horz 100
+ FOV_Alert_Far_Vert 60
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 180
+ FOV_Combat_Far_Horz 100
+ FOV_Combat_Far_Vert 60
+ FOV_Combat_Near_Horz 130
+ FOV_Combat_Near_Vert 180
+ FOV_Idle_Far_Horz 100
+ FOV_Idle_Far_Vert 60
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 180
+
+ NoticeForgetPostTime 5.0
+ NoticeForgetPreTime 1.0
+
+ NoticeDistNear_Alert 300
+ NoticeDistNear_Combat 300
+ NoticeDistNear_Idle 150
+ NoticeDistFar_Alert 2000
+ NoticeDistFar_Combat 2000
+ NoticeDistFar_Idle 2000
+ NoticeTimeNear_Alert 0.1
+ NoticeTimeNear_Combat 0.0
+ NoticeTimeNear_Idle 0.1
+ NoticeTimeFar_Alert 1.0
+ NoticeTimeFar_Combat 1.0
+ NoticeTimeFar_Idle 1.0
+ NoticeTimePeripheral_Alert 0.7
+ NoticeTimePeripheral_Combat 0.7
+ NoticeTimePeripheral_Idle 0.7
+ }
+
+ "sp" [$sp]
+ {
+ crouchCombatDistInner 500
+ crouchCombatDistOuter 650
+
+ FOV_Alert_Far_Horz 100
+ FOV_Alert_Far_Vert 60
+ FOV_Alert_Near_Horz 180
+ FOV_Alert_Near_Vert 100
+ FOV_Combat_Far_Horz 100
+ FOV_Combat_Far_Vert 60
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 80
+ FOV_Idle_Far_Horz 80
+ FOV_Idle_Far_Vert 60
+ FOV_Idle_Near_Horz 170
+ FOV_Idle_Near_Vert 80
+
+ NoticeForgetPostTime 5.0
+ NoticeForgetPreTime 1.0
+
+ NoticeDistNear_Alert 300
+ NoticeDistNear_Combat 300
+ NoticeDistNear_Idle 150
+ NoticeDistFar_Alert 1800
+ NoticeDistFar_Combat 2500
+ NoticeDistFar_Idle 1500
+ NoticeTimeNear_Alert 0.2
+ NoticeTimeNear_Combat 0.1
+ NoticeTimeNear_Idle 0.5
+ NoticeTimeFar_Alert 0.75
+ NoticeTimeFar_Combat 0.5
+ NoticeTimeFar_Idle 2.0
+ NoticeTimePeripheral_Alert 1.0
+ NoticeTimePeripheral_Combat 0.75
+ NoticeTimePeripheral_Idle 2.0
+ }
+
+ FOV_Near_Dist 150 // distance at which we transition between near and far FOV values
+ FOV_Far_Dist 2000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+ FOV_Vert_Offset -20 // looking down instead of up... better for pilots on high ground feeling
+
+ showTitle 1
+
+ resetBurstOnStopShootOverlay 1
+ restrictAimGunToValidSequences 1
+ showFriendlyIcon 1
+ waitBetweenWeaponBurst 1
+ suppressLSP_duration 10 [$sp]
+ suppressLSP_duration 10 [$mp]
+ enemyAimAtMeWidth 100
+
+ aiEnemy_usePriorityDist 2000 [$sp]
+ aiEnemy_immediateThreatDist 230 [$sp]
+ aiEnemy_immediateThreatDist 94 [$mp]
+
+ braceWhenDangerousAreaDisplacementFails 1
+
+ reactChanceDefault 90 [$sp]
+ reactChanceDefault 60 [$mp]
+ reactBulletChanceDefault 90 [$sp]
+ reactBulletChanceDefault 60 [$mp]
+ reactFriendlyChanceDefault 100
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_bish.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_bish.txt
new file mode 100644
index 000000000..b4e6eee54
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_bish.txt
@@ -0,0 +1,8 @@
+#base "npc_soldier.txt"
+npc_soldier_bish
+{
+ SettingsTitle "Bish"
+ DefaultModelName_IMC "models/humans/mcor_hero/bish/mcor_hero_bish.mdl"
+ DefaultModelName_MIL "models/humans/mcor_hero/bish/mcor_hero_bish.mdl"
+}
+
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_blisk.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_blisk.txt
new file mode 100644
index 000000000..82548c97e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_blisk.txt
@@ -0,0 +1,8 @@
+#base "npc_soldier.txt"
+npc_soldier_blisk
+{
+ SettingsTitle "Blisk"
+ DefaultModelName_IMC "models/humans/imc_villain/blisk/imc_villain_blisk.mdl"
+ DefaultModelName_MIL "models/humans/imc_villain/blisk/imc_villain_blisk.mdl"
+}
+
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_drone_summoner.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_drone_summoner.txt
new file mode 100644
index 000000000..2ebedae21
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_drone_summoner.txt
@@ -0,0 +1,4 @@
+#base "npc_soldier_shield_captain.txt"
+npc_soldier_drone_summoner
+{
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_hero_bear.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_hero_bear.txt
new file mode 100644
index 000000000..d10daea62
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_hero_bear.txt
@@ -0,0 +1,14 @@
+#base "npc_soldier.txt"
+npc_soldier_hero_bear
+{
+ title #NPC_BEAR_NAME
+ SettingsTitle #NPC_BEAR_NAME
+ IsGenericGrunt 0
+
+ DefaultModelName "models/humans/pilots/sp_heavy_roog_m.mdl"
+ DefaultModelName_IMC "models/humans/pilots/sp_heavy_roog_m.mdl"
+ DefaultModelName_MIL "models/humans/pilots/sp_heavy_roog_m.mdl"
+
+ DefaultWeapon "mp_weapon_mastiff"
+}
+
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_hero_sarah.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_hero_sarah.txt
new file mode 100644
index 000000000..fcc3901b6
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_hero_sarah.txt
@@ -0,0 +1,12 @@
+#base "npc_soldier.txt"
+npc_soldier_hero_sarah
+{
+ title #NPC_SARAH_NAME
+ SettingsTitle #NPC_SARAH_NAME
+ IsGenericGrunt 0
+
+ DefaultModelName "models/humans/heroes/mlt_hero_sarah.mdl"
+ DefaultModelName_IMC "models/humans/heroes/mlt_hero_sarah.mdl"
+ DefaultModelName_MIL "models/humans/heroes/mlt_hero_sarah.mdl"
+}
+
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_eliteguard.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_eliteguard.txt
new file mode 100644
index 000000000..f09efadad
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_eliteguard.txt
@@ -0,0 +1,12 @@
+#base "npc_soldier.txt"
+
+npc_soldier_pve_eliteguard
+{
+ title "Elite Guard"
+
+ DefaultModelName "models/humans/grunts/imc_grunt_rifle_pve.mdl"
+ DefaultModelName_IMC "models/humans/grunts/imc_grunt_rifle_pve.mdl"
+ DefaultModelName_MIL "models/humans/grunts/imc_grunt_rifle_pve.mdl"
+
+ returnToIdleTime 3
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_sandbox.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_sandbox.txt
new file mode 100644
index 000000000..cb4e5784e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_sandbox.txt
@@ -0,0 +1,7 @@
+#base "npc_soldier.txt"
+
+npc_soldier_pve_sandbox
+{
+ title "Grunt"
+ returnToIdleTime 3
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_specialist.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_specialist.txt
new file mode 100644
index 000000000..d3abdef29
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_pve_specialist.txt
@@ -0,0 +1,45 @@
+#base "npc_soldier.txt"
+npc_soldier_pve_specialist
+{
+ SummonDrone npc_drone_plasma
+ title #NPC_SPECIALIST
+
+ IsGenericGrunt 0
+
+ mechanical 1
+ magneticRange 125
+
+ DefaultWeapon "mp_weapon_lstar"
+ GrenadeWeaponName "mp_weapon_frag_drone"
+
+ Health 120
+ PainOnRepeatDamageThreshold 90
+
+ DefaultModelName "models/humans/grunts/imc_grunt_shield_captain.mdl"
+ DefaultModelName_IMC "models/humans/grunts/imc_grunt_shield_captain.mdl"
+ DefaultModelName_MIL "models/humans/grunts/imc_grunt_shield_captain.mdl"
+
+ GibModel0 "models/robots/spectre/spectre_assault_d_gib_leg_l.mdl"
+ GibModel1 "models/robots/spectre/spectre_assault_d_gib_leg_r.mdl"
+ GibModel2 "models/robots/spectre/spectre_assault_d_gib_arm_l.mdl"
+ GibModel3 "models/robots/spectre/spectre_assault_d_gib_arm_r.mdl"
+ GibModelSoftened0 "models/robots/spectre/spectre_assault_d_gib_leg_l.mdl"
+ GibModelSoftened1 "models/robots/spectre/spectre_assault_d_gib_leg_r.mdl"
+ GibModelSoftened2 "models/robots/spectre/spectre_assault_d_gib_arm_l.mdl"
+ GibModelSoftened3 "models/robots/spectre/spectre_assault_d_gib_arm_r.mdl"
+ GibAttachment0 "left_leg"
+ GibAttachment1 "right_leg"
+ GibAttachment2 "left_arm"
+ GibAttachment3 "right_arm"
+ GibSpeed 200
+ GibAngularSpeed 20
+ GibMaxDist 1600
+ GibFX "P_exp_spectre_death"
+ GibSound "Explo_Spectre"
+
+ headshotFX "P_headshot_pilot_robot"
+
+ footstep_type "robopilot"
+
+ returnToIdleTime 3
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_shield_captain.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_shield_captain.txt
new file mode 100644
index 000000000..96516c659
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_shield_captain.txt
@@ -0,0 +1,27 @@
+#base "npc_soldier.txt"
+npc_soldier_shield_captain
+{
+ title #NPC_SOLDIER_SHIELD_CAPTAIN
+ PersonalShield 1
+
+ DefaultWeapon "mp_weapon_lmg"
+
+ Health 350 [$mp]
+ Health 150 [$sp]
+
+ PainOnRepeatDamageThreshold 260 [$mp]
+ PainOnRepeatDamageThreshold 110 [$sp]
+
+ AimAngularSpeed 3
+ faceEnemyToleranceAngle 35
+
+ IsGenericGrunt 0
+
+ DefaultModelName "models/humans/grunts/imc_grunt_shield_captain.mdl"
+ DefaultModelName_IMC "models/humans/grunts/imc_grunt_shield_captain.mdl"
+ DefaultModelName_MIL "models/humans/grunts/imc_grunt_shield_captain.mdl"
+
+ headshotFX "P_headshot_pilot"
+
+ braceWhenDangerousAreaDisplacementFails 0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_sidearm.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_sidearm.txt
new file mode 100644
index 000000000..e5062df0d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_sidearm.txt
@@ -0,0 +1,6 @@
+#base "npc_soldier.txt"
+npc_soldier_sidearm
+{
+ title "Pistol Grunt"
+ DefaultWeapon "mp_weapon_semipistol"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_specialist.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_specialist.txt
new file mode 100644
index 000000000..fc7a77a0f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_specialist.txt
@@ -0,0 +1,43 @@
+#base "npc_soldier.txt"
+npc_soldier_specialist
+{
+ SummonDrone npc_drone_plasma
+ title #NPC_SPECIALIST
+
+ IsGenericGrunt 0
+
+ mechanical 1
+ magneticRange 125
+
+ DefaultWeapon "mp_weapon_lstar"
+ GrenadeWeaponName "mp_weapon_frag_drone"
+
+ Health 120
+ PainOnRepeatDamageThreshold 90
+
+ DefaultModelName "models/humans/pilots/sp_light_ged_m.mdl"
+ DefaultModelName_IMC "models/humans/pilots/sp_light_ged_m.mdl"
+ DefaultModelName_MIL "models/humans/pilots/sp_light_ged_m.mdl"
+
+ GibModel0 "models/robots/spectre/spectre_assault_d_gib_leg_l.mdl"
+ GibModel1 "models/robots/spectre/spectre_assault_d_gib_leg_r.mdl"
+ GibModel2 "models/robots/spectre/spectre_assault_d_gib_arm_l.mdl"
+ GibModel3 "models/robots/spectre/spectre_assault_d_gib_arm_r.mdl"
+ GibModelSoftened0 "models/robots/spectre/spectre_assault_d_gib_leg_l.mdl"
+ GibModelSoftened1 "models/robots/spectre/spectre_assault_d_gib_leg_r.mdl"
+ GibModelSoftened2 "models/robots/spectre/spectre_assault_d_gib_arm_l.mdl"
+ GibModelSoftened3 "models/robots/spectre/spectre_assault_d_gib_arm_r.mdl"
+ GibAttachment0 "left_leg"
+ GibAttachment1 "right_leg"
+ GibAttachment2 "left_arm"
+ GibAttachment3 "right_arm"
+ GibSpeed 200
+ GibAngularSpeed 20
+ GibMaxDist 1600
+ GibFX "P_exp_spectre_death"
+ GibSound "Explo_Spectre"
+
+ headshotFX "P_headshot_pilot_robot"
+
+ footstep_type "robopilot"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_specialist_militia.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_specialist_militia.txt
new file mode 100644
index 000000000..f08a13469
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_specialist_militia.txt
@@ -0,0 +1,44 @@
+#base "npc_soldier.txt"
+npc_soldier_specialist_militia
+{
+ SummonDrone npc_drone_plasma
+ title #NPC_SPECIALIST_MILITIA
+
+ IsGenericGrunt 0
+
+ mechanical 1
+ magneticRange 125
+
+ DefaultWeapon "mp_weapon_lstar"
+ GrenadeWeaponName "mp_weapon_frag_drone"
+
+ Health 120
+ PainOnRepeatDamageThreshold 90
+
+ DefaultModelName "models/humans/grunts/grunt_specialist.mdl"
+ DefaultModelName_IMC "models/humans/grunts/grunt_specialist.mdl"
+ DefaultModelName_MIL "models/humans/grunts/grunt_specialist.mdl"
+
+ GibModel0 "models/robots/spectre/spectre_assault_d_gib_leg_l.mdl"
+ GibModel1 "models/robots/spectre/spectre_assault_d_gib_leg_r.mdl"
+ GibModel2 "models/robots/spectre/spectre_assault_d_gib_arm_l.mdl"
+ GibModel3 "models/robots/spectre/spectre_assault_d_gib_arm_r.mdl"
+ GibModelSoftened0 "models/robots/spectre/spectre_assault_d_gib_leg_l.mdl"
+ GibModelSoftened1 "models/robots/spectre/spectre_assault_d_gib_leg_r.mdl"
+ GibModelSoftened2 "models/robots/spectre/spectre_assault_d_gib_arm_l.mdl"
+ GibModelSoftened3 "models/robots/spectre/spectre_assault_d_gib_arm_r.mdl"
+ GibAttachment0 "left_leg"
+ GibAttachment1 "right_leg"
+ GibAttachment2 "left_arm"
+ GibAttachment3 "right_arm"
+ GibSpeed 200
+ GibAngularSpeed 20
+ GibMaxDist 1600
+ GibFX "P_exp_spectre_death"
+ GibSound "Explo_Spectre"
+
+ headshotFX "P_headshot_pilot_robot"
+
+ footstep_type "robopilot"
+}
+
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_spyglass.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_spyglass.txt
new file mode 100644
index 000000000..6fd6c86d5
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_spyglass.txt
@@ -0,0 +1,8 @@
+#base "npc_soldier.txt"
+npc_soldier_spyglass
+{
+ SettingsTitle "Spyglass"
+ DefaultModelName_IMC "models/humans/imc_villain/spyglass/imc_villain_spyglass.mdl"
+ DefaultModelName_MIL "models/humans/imc_villain/spyglass/imc_villain_spyglass.mdl"
+}
+
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_soldier_training_sentry.txt b/Northstar.CustomServers/scripts/aisettings/npc_soldier_training_sentry.txt
new file mode 100644
index 000000000..37634d9a4
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_soldier_training_sentry.txt
@@ -0,0 +1,24 @@
+#base "npc_soldier.txt"
+npc_soldier_training_sentry
+{
+ title #NPC_SOLDIER_TRAINING_SENTRY
+
+ FOV_Vert_Offset 10
+ FOV_Near_Dist 150 // distance at which we transition between near and far FOV values
+ FOV_Far_Dist 2000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 180
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 180
+ FOV_Idle_Far_Vert 80
+
+ FOV_Alert_Near_Horz 180
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 180
+ FOV_Alert_Far_Vert 80
+
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 180
+ FOV_Combat_Far_Vert 80
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_spectre.txt b/Northstar.CustomServers/scripts/aisettings/npc_spectre.txt
new file mode 100644
index 000000000..27efebad2
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_spectre.txt
@@ -0,0 +1,216 @@
+npc_spectre
+{
+ AIClass spectre
+ TraverseAnimType spectre
+
+ title #NPC_SPECTRE
+ useSequenceBounds 1
+
+ aiEnemy_usePriorityDist 1300
+ aiEnemy_immediateThreatDist 200 [$sp]
+ aiEnemy_immediateThreatDist 94 [$mp]
+
+ AimAngularSpeed 7
+
+ ArmorType normal
+
+ BaseClass "npc_spectre"
+ BodyType human
+
+ footstep_type "robot"
+ mechanical 1
+
+ BehaviorSelector "behavior_spectre"
+
+ chaseStopDist 300
+ chaseStopDistHeavyArmor 800
+
+ allowCower 0
+ allowUseCover 1
+
+ traverseCostFactor 2.0
+ minGoalRadius 256
+
+ DefaultModelName "models/robots/spectre/imc_spectre.mdl"
+ DefaultModelName_IMC "models/robots/spectre/imc_spectre.mdl"
+ DefaultModelName_MIL "models/robots/spectre/imc_spectre.mdl"
+
+ DefaultWeapon "mp_weapon_rspn101"
+ WeaponCapacity "PilotMainWeapons"
+
+ longJumpCheckMinInterval 15
+ longJumpCheckMaxInterval 30
+ longJumpMinDist 400
+ longJumpMaxDist 700
+ longJumpHeight 300
+
+ GibModel0 "models/robots/spectre/spectre_assault_d_gib_leg_l.mdl"
+ GibModel1 "models/robots/spectre/spectre_assault_d_gib_leg_r.mdl"
+ GibModel2 "models/robots/spectre/spectre_assault_d_gib_arm_l.mdl"
+ GibModel3 "models/robots/spectre/spectre_assault_d_gib_arm_r.mdl"
+ GibModelSoftened0 "models/robots/spectre/spectre_assault_d_gib_leg_l.mdl"
+ GibModelSoftened1 "models/robots/spectre/spectre_assault_d_gib_leg_r.mdl"
+ GibModelSoftened2 "models/robots/spectre/spectre_assault_d_gib_arm_l.mdl"
+ GibModelSoftened3 "models/robots/spectre/spectre_assault_d_gib_arm_r.mdl"
+ GibAttachment0 "left_leg"
+ GibAttachment1 "right_leg"
+ GibAttachment2 "left_arm"
+ GibAttachment3 "right_arm"
+ GibSpeed 200
+ GibAngularSpeed 20
+ GibMaxDist 1600
+ GibFX "P_exp_spectre_death"
+ GibSound "Explo_Spectre"
+
+ headshotFX "P_headshot_pilot_robot"
+ landingImpactTable "pilot_landing"
+ footstepImpactTable "pilot_foostep"
+
+ faceEnemyStrictToleranceAngle 30
+ faceEnemyWhileMovingDist 600
+
+ "mp" [$mp]
+ {
+ crouchCombatDistInner 300
+ crouchCombatDistOuter 350
+
+ FOV_Alert_Far_Horz 100
+ FOV_Alert_Far_Vert 60
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 180
+ FOV_Combat_Far_Horz 100
+ FOV_Combat_Far_Vert 60
+ FOV_Combat_Near_Horz 130
+ FOV_Combat_Near_Vert 180
+ FOV_Idle_Far_Horz 100
+ FOV_Idle_Far_Vert 60
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 180
+
+ NoticeForgetPostTime 5.0
+ NoticeForgetPreTime 1.0
+
+ NoticeDistNear_Alert 300
+ NoticeDistNear_Combat 300
+ NoticeDistNear_Idle 150
+ NoticeDistFar_Alert 2000
+ NoticeDistFar_Combat 2000
+ NoticeDistFar_Idle 2000
+ NoticeTimeNear_Alert 0.1
+ NoticeTimeNear_Combat 0.0
+ NoticeTimeNear_Idle 0.1
+ NoticeTimeFar_Alert 1.0
+ NoticeTimeFar_Combat 1.0
+ NoticeTimeFar_Idle 1.0
+ NoticeTimePeripheral_Alert 0.7
+ NoticeTimePeripheral_Combat 0.7
+ NoticeTimePeripheral_Idle 0.7
+ }
+
+ "sp" [$sp]
+ {
+ crouchCombatDistInner 400
+ crouchCombatDistOuter 500
+
+ FOV_Alert_Far_Horz 100
+ FOV_Alert_Far_Vert 60
+ FOV_Alert_Near_Horz 180
+ FOV_Alert_Near_Vert 100
+ FOV_Combat_Far_Horz 100
+ FOV_Combat_Far_Vert 60
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 80
+ FOV_Idle_Far_Horz 100
+ FOV_Idle_Far_Vert 60
+ FOV_Idle_Near_Horz 170
+ FOV_Idle_Near_Vert 80
+
+ NoticeForgetPostTime 5.0
+ NoticeForgetPreTime 1.0
+
+ NoticeDistNear_Alert 300
+ NoticeDistNear_Combat 300
+ NoticeDistNear_Idle 150
+ NoticeDistFar_Alert 1800
+ NoticeDistFar_Combat 2500
+ NoticeDistFar_Idle 1500
+ NoticeTimeNear_Alert 0.2
+ NoticeTimeNear_Combat 0.1
+ NoticeTimeNear_Idle 0.5
+ NoticeTimeFar_Alert 0.75
+ NoticeTimeFar_Combat 0.5
+ NoticeTimeFar_Idle 2.0
+ NoticeTimePeripheral_Alert 1.0
+ NoticeTimePeripheral_Combat 0.75
+ NoticeTimePeripheral_Idle 2.0
+ }
+
+ FOV_Near_Dist 150 // distance at which we transition between near and far FOV values
+ FOV_Far_Dist 2000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+ FOV_Vert_Offset -20 // looking down instead of up... better for pilots on high ground feeling
+
+ HullType "HULL_HUMAN"
+ leechAnimSet spectre
+ leechMaxDist 150
+ magneticRange 125
+ MaxArrivalDist 170
+
+ aimassist_adspull_centerAttachmentName "CHESTFOCUS"
+ aimassist_adspull_centerRadius 11.0
+ aimassist_adspull_headshotAttachmentName "HEADSHOT"
+ aimassist_adspull_headshotRadius 7.0
+
+ MeleeDamageMax 20 [$mp]
+ MeleeDamageMin 20 [$mp]
+
+ MeleeDamageMax 53 [$sp]
+ MeleeDamageMin 53 [$sp]
+
+ Health 130 [$sp]
+ Health 100 [$mp]
+
+ PainOnHeavyDamageThreshold 32
+ PainOnRepeatDamageThreshold 70
+
+ MeleeDamageRadius 40 [$mp]
+ MeleeRange 65 [$mp]
+
+ MeleeDamageRadius 60 [$sp]
+ MeleeRange 60 [$sp]
+
+ MeleeChargeDamageMin 80
+ MeleeChargeDamageMax 80
+ MeleeChargeDamageMinHeavyArmor 80
+ MeleeChargeDamageMaxHeavyArmor 80
+ MeleeChargeDamageRadius 25
+ MeleeChargeRange 135
+ meleeChargeInterval 1
+ MeleeChargeEnemyArmorType any
+
+ meleeImpactEffectTable "melee_spectre"
+
+ MinForceWalkDist 0
+ MinStartMoveDist 100
+ MoveYawSpeed 12
+ moveDeflectionLookAheadTime 0.5 [$sp]
+
+ shootingCoverFightRadius 600 [$sp]
+
+ DrawEnemyHealthBar 0
+ DrawTargetHealthBar 0
+
+ resetBurstOnStopShootOverlay 1
+ restrictAimGunToValidSequences 1
+
+ showFriendlyIcon 1
+ showTitle 1 [$mp]
+ showTitle 1 [$sp]
+
+ ui_targetinfo "ui/targetinfo_spectre_bounty" [$mp]
+ ui_targetinfo "ui/targetinfo_npc_hackable" [$sp]
+
+ smartAmmoLockAttachmentName0 HEADFOCUS
+ waitBetweenWeaponBurst 1
+ suppressLSP_duration 1.5
+ reactChanceDefault 90
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_spectre_mortar.txt b/Northstar.CustomServers/scripts/aisettings/npc_spectre_mortar.txt
new file mode 100644
index 000000000..6c2151ebd
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_spectre_mortar.txt
@@ -0,0 +1,45 @@
+#base "npc_spectre.txt"
+npc_spectre_mortar
+{
+ title #NPC_SPECTRE_MORTAR
+
+ minGoalRadius 8
+ assaultHarvester 0
+
+ "mp" [$mp]
+ {
+ crouchCombatDistInner 300
+ crouchCombatDistOuter 350
+
+ FOV_Alert_Far_Horz 100
+ FOV_Alert_Far_Vert 180
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 120
+ FOV_Combat_Far_Horz 100
+ FOV_Combat_Far_Vert 120
+ FOV_Combat_Near_Horz 130
+ FOV_Combat_Near_Vert 180
+ FOV_Idle_Far_Horz 100
+ FOV_Idle_Far_Vert 60
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 180
+ }
+ "sp" [$sp]
+ {
+ crouchCombatDistInner 300
+ crouchCombatDistOuter 350
+
+ FOV_Alert_Far_Horz 100
+ FOV_Alert_Far_Vert 60
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 180
+ FOV_Combat_Far_Horz 100
+ FOV_Combat_Far_Vert 60
+ FOV_Combat_Near_Horz 130
+ FOV_Combat_Near_Vert 180
+ FOV_Idle_Far_Horz 100
+ FOV_Idle_Far_Vert 60
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 180
+ }
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_stalker.txt b/Northstar.CustomServers/scripts/aisettings/npc_stalker.txt
new file mode 100644
index 000000000..ae3347e9d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_stalker.txt
@@ -0,0 +1,162 @@
+npc_stalker
+{
+ AIClass stalker
+ TraverseAnimType stalker
+
+ title #NPC_STALKER
+ ui_targetinfo "ui/targetinfo_stalker_bounty" [$mp]
+ ui_targetinfo "ui/targetinfo_npc_hackable" [$sp]
+ ui_targetinfo_offset_z 12
+
+ useSequenceBounds 1
+
+ aiEnemy_usePriorityDist 1300
+ aiEnemy_immediateThreatDist 200
+
+ AimAngularSpeed 7
+
+ ArmorType normal
+
+ BaseClass "npc_stalker"
+ BodyType human
+
+ footstep_type "stalker"
+ footstepSprintSpeedThreshold 85
+ mechanical 1
+
+ BehaviorSelector "behavior_stalker"
+ crawlingSettingsWrapper "npc_stalker_crawling"
+
+ chaseStopDist 75 [$sp]
+ chaseStopDistHeavyArmor 200 [$sp]
+
+ chaseStopDist 500 [$mp]
+ chaseStopDistHeavyArmor 800 [$mp]
+
+ traverseCostFactor 5.0 // need to take into account slow move speed
+ minGoalRadius 1600
+
+ DefaultModelName "models/robots/stalker/robot_stalker.mdl" [$sp]
+ DefaultModelName_IMC "models/robots/stalker/robot_stalker.mdl" [$sp]
+ DefaultModelName_MIL "models/robots/stalker/robot_stalker.mdl" [$sp]
+
+ DefaultModelName "models/robots/stalker/robot_stalker_red.mdl" [$mp]
+ DefaultModelName_IMC "models/robots/stalker/robot_stalker_red.mdl" [$mp]
+ DefaultModelName_MIL "models/robots/stalker/robot_stalker_red.mdl" [$mp]
+
+ DefaultWeapon "mp_weapon_lstar"
+ WeaponCapacity "PilotMainWeapons"
+
+ longJumpHeight 2000
+
+ GibModel0 "models/robots/stalker/robot_stalker_l_leg_gib.mdl" [$sp]
+ GibModel1 "models/robots/stalker/robot_stalker_r_leg_gib.mdl" [$sp]
+ GibModel2 "models/robots/stalker/robot_stalker_l_arm_gib.mdl" [$sp]
+ GibModel3 "models/robots/stalker/robot_stalker_r_arm_gib.mdl" [$sp]
+ GibModelSoftened0 "models/robots/stalker/robot_stalker_l_leg_gib.mdl" [$sp]
+ GibModelSoftened1 "models/robots/stalker/robot_stalker_r_leg_gib.mdl" [$sp]
+ GibModelSoftened2 "models/robots/stalker/robot_stalker_l_arm_gib.mdl" [$sp]
+ GibModelSoftened3 "models/robots/stalker/robot_stalker_r_arm_gib.mdl" [$sp]
+ GibModel0 "models/robots/stalker/robot_stalker_l_leg_red_gib.mdl" [$mp]
+ GibModel1 "models/robots/stalker/robot_stalker_r_leg_red_gib.mdl" [$mp]
+ GibModel2 "models/robots/stalker/robot_stalker_l_arm_red_gib.mdl" [$mp]
+ GibModel3 "models/robots/stalker/robot_stalker_r_arm_red_gib.mdl" [$mp]
+ GibModelSoftened0 "models/robots/stalker/robot_stalker_l_leg_red_gib.mdl" [$mp]
+ GibModelSoftened1 "models/robots/stalker/robot_stalker_r_leg_red_gib.mdl" [$mp]
+ GibModelSoftened2 "models/robots/stalker/robot_stalker_l_arm_red_gib.mdl" [$mp]
+ GibModelSoftened3 "models/robots/stalker/robot_stalker_r_arm_red_gib.mdl" [$mp]
+ GibAttachment0 "left_leg"
+ GibAttachment1 "right_leg"
+ GibAttachment2 "left_arm"
+ GibAttachment3 "right_arm"
+ GibSpeed 100
+ GibAngularSpeed 5
+ GibMaxDist 1600
+ GibFX "P_exp_spectre_death"
+ GibSound "Explo_Spectre"
+
+ headshotFX "P_headshot_pilot_robot"
+ landingImpactTable "pilot_landing"
+ footstepImpactTable "pilot_foostep"
+
+ faceEnemyStrictToleranceAngle 25
+ faceEnemyWhileMovingDist 60
+
+ FOV_Idle_Far_Horz 60
+ FOV_Idle_Far_Vert 50
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 70
+
+ FOV_Alert_Far_Horz 60
+ FOV_Alert_Far_Vert 50
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 70
+
+ FOV_Combat_Far_Horz 60
+ FOV_Combat_Far_Vert 50
+ FOV_Combat_Near_Horz 130
+ FOV_Combat_Near_Vert 70
+
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+ FOV_Near_Dist 350 // distance at which we transition between near and far FOV values
+ FOV_Vert_Offset 0
+
+ HullType "HULL_HUMAN"
+ magneticRange 125
+ MaxArrivalDist 170
+
+ aimassist_adspull_centerAttachmentName "CHESTFOCUS"
+ aimassist_adspull_centerRadius 11.0
+ aimassist_adspull_headshotAttachmentName "HEADSHOT"
+ aimassist_adspull_headshotRadius 8.0
+ aimassist_adspull_noPitchUp 1 // don't want players to get sucked to chest when they're trying to chop the legs
+
+ MeleeDamageMin 20 [$mp]
+ MeleeDamageMax 20 [$mp]
+ Health 150 [$mp]
+
+ PainOnHeavyDamageThreshold 80 [$mp] // turned off for mp
+ PainOnRepeatDamageThreshold 50 [$mp]
+
+ MeleeDamageMin 53 [$sp]
+ MeleeDamageMax 53 [$sp]
+ Health 250 [$sp]
+
+ PainOnHeavyDamageThreshold 59 [$sp]
+ PainOnRepeatDamageThreshold 75 [$sp]
+
+ MeleeDamageRadius 30 [$mp]
+ MeleeRange 50 [$mp]
+
+ MeleeDamageRadius 60 [$sp]
+ MeleeRange 60 [$sp]
+
+ MeleeDamageForce 40000
+
+ meleeImpactEffectTable "melee_spectre"
+
+ MinForceWalkDist 0
+ MinStartMoveDist 100
+ MoveYawSpeed 12
+ allowTurn45Anims 0
+ moveDeflectionLookAheadTime 1.5
+
+ DrawEnemyHealthBar 0
+ DrawTargetHealthBar 0
+
+ resetBurstOnStopShootOverlay 1
+ restrictAimGunToValidSequences 1
+
+ showFriendlyIcon 1
+ showTitle 1 [$mp]
+ showTitle 1 [$sp]
+
+ smartAmmoLockAttachmentName0 HEADFOCUS
+ smartAmmoLockAttachmentName1 left_arm
+ smartAmmoLockAttachmentName2 right_arm
+ smartAmmoLockAttachmentName3 foot_L_sole
+ smartAmmoLockAttachmentName4 foot_R_sole
+ waitBetweenWeaponBurst 1
+ suppressLSP_duration 8
+ reactChanceDefault 90
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling.txt b/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling.txt
new file mode 100644
index 000000000..3a2516717
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling.txt
@@ -0,0 +1,33 @@
+#base "npc_stalker.txt"
+npc_stalker_crawling
+{
+ ScriptSpawnAsCrawler 1
+
+ AIClass stalker_crawling
+
+ useSequenceBounds 1
+ orientToGround 1
+
+ DefaultWeapon ""
+ BehaviorSelector "behavior_stalker_crawling"
+
+ faceEnemyToleranceAngle 45
+
+ eyeOffsetOverride_enabled 1
+ eyeOffsetOverride "5 0 20"
+
+ chaseStopDist 45
+
+ allowWalkAnims 0
+
+ MeleeDamageMax 35
+ MeleeDamageMin 30
+
+ MeleeDamageRadius 40
+ MeleeRange 45
+ MeleeInterval 0
+
+ aimassist_adspull_centerRadius 11.0
+ aimassist_adspull_headshotRadius 7.0
+ aimassist_adspull_noPitchUp 0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling_fd.txt
new file mode 100644
index 000000000..62a9d7ebd
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling_fd.txt
@@ -0,0 +1,5 @@
+#base "npc_stalker_crawling.txt"
+npc_stalker_crawling_fd
+{
+ minGoalRadius 16
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling_mossy.txt b/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling_mossy.txt
new file mode 100644
index 000000000..48fc894dd
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_stalker_crawling_mossy.txt
@@ -0,0 +1,11 @@
+#base "npc_stalker_crawling.txt"
+npc_stalker_crawling_mossy
+{
+ title "#NPC_STALKER_ZOMBIE_MOSSY" [$sp]
+
+ DefaultModelName "models/robots/stalker/robot_stalker_mossy.mdl"
+ DefaultModelName_IMC "models/robots/stalker/robot_stalker_mossy.mdl"
+ DefaultModelName_MIL "models/robots/stalker/robot_stalker_mossy.mdl"
+
+ Health 150
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_stalker_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_stalker_fd.txt
new file mode 100644
index 000000000..cc21b4418
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_stalker_fd.txt
@@ -0,0 +1,6 @@
+#base "npc_stalker.txt"
+npc_stalker_fd
+{
+ crawlingSettingsWrapper "npc_stalker_crawling_fd"
+ minGoalRadius 16
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_stalker_zombie.txt b/Northstar.CustomServers/scripts/aisettings/npc_stalker_zombie.txt
new file mode 100644
index 000000000..fddee0fbf
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_stalker_zombie.txt
@@ -0,0 +1,15 @@
+#base "npc_stalker.txt"
+npc_stalker_zombie
+{
+ SpawnLimping 1
+
+ title "#NPC_STALKER_ZOMBIE" [$sp]
+
+ DefaultWeapon ""
+ Health 150
+
+ allowWalkAnims 0
+
+ leechAnimSet spectre
+ leechMaxDist 150
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_stalker_zombie_mossy.txt b/Northstar.CustomServers/scripts/aisettings/npc_stalker_zombie_mossy.txt
new file mode 100644
index 000000000..8dccbf6f1
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_stalker_zombie_mossy.txt
@@ -0,0 +1,9 @@
+#base "npc_stalker_zombie.txt"
+npc_stalker_zombie_mossy
+{
+ title "#NPC_STALKER_ZOMBIE_MOSSY" [$sp]
+
+ DefaultModelName "models/robots/stalker/robot_stalker_mossy.mdl"
+ DefaultModelName_IMC "models/robots/stalker/robot_stalker_mossy.mdl"
+ DefaultModelName_MIL "models/robots/stalker/robot_stalker_mossy.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_super_spectre.txt b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre.txt
new file mode 100644
index 000000000..f5d30aa5a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre.txt
@@ -0,0 +1,210 @@
+npc_super_spectre
+{
+ HullType "HULL_MEDIUM"
+ AIClass super_spectre
+ TraverseAnimType super_spectre
+ collideWithPlayer 1
+
+ ragdollOnTraverseDeath 0
+ mechanical 1
+ BodyType super_spectre
+ ArmorType heavy [$sp]
+ ArmorType heavy [$mp]
+ SmartAmmoLockType large
+ smartAmmoLockAttachmentName0 CHESTFOCUS
+ smartAmmoLockAttachmentName1 HEADFOCUS
+ smartAmmoLockAttachmentName2 exp_torso_main
+ smartAmmoLockAttachmentName3 shell
+ smartAmmoLockAttachmentName4 shell2
+
+ suppressLSP_duration 5
+
+ title #NPC_SUPER_SPECTRE
+ ui_targetinfo "ui/targetinfo_titan" [$mp]
+ ui_targetinfo "" [$sp]
+
+ DrawEnemyHealthBar 1 [$mp]
+ DrawTargetHealthBar 1 [$mp]
+
+ DrawEnemyHealthBar 0 [$sp]
+ DrawTargetHealthBar 0 [$sp]
+
+ footstep_type "superspectre"
+ footstepSprintSpeedThreshold 500
+
+ BaseClass "npc_super_spectre"
+
+ DefaultModelName "models/robots/super_spectre/super_spectre_v1.mdl"
+ DefaultWeapon "mp_weapon_super_spectre"
+ ForceAutoPrecacheDefaultWeapon 1
+ AdditionalScriptWeapon "mp_weapon_spectre_spawner"
+ AdditionalAISettings "npc_frag_drone"
+ BehaviorSelector "behavior_super_spectre"
+
+ GibModel0 "models/robots/super_spectre/super_spectre_left_arm.mdl"
+ GibModel1 "models/robots/super_spectre/super_spectre_right_arm.mdl"
+ GibModel2 "models/robots/super_spectre/super_spectre_left_leg.mdl"
+ GibModel3 "models/robots/super_spectre/super_spectre_right_leg.mdl"
+ GibModelSoftened0 "models/robots/super_spectre/super_spectre_left_arm.mdl"
+ GibModelSoftened1 "models/robots/super_spectre/super_spectre_right_arm.mdl"
+ GibModelSoftened2 "models/robots/super_spectre/super_spectre_left_leg.mdl"
+ GibModelSoftened3 "models/robots/super_spectre/super_spectre_right_leg.mdl"
+ GibAttachment0 "FX_DAM_VENT_RL"
+ GibAttachment1 "FX_DAM_VENT_RR"
+ GibAttachment2 "THRUST_L_FOOT"
+ GibAttachment3 "THRUST_R_FOOT"
+ GibSpeed 450
+ GibAngularSpeed 30
+ GibMaxDist 16000
+ GibFX "P_sup_spectre_death"
+ //GibSound "ai_reaper_explo_3p"
+
+ //leechAnimSet super_spectre
+ //onLeechFuncName SuperSpectreOnLeeched
+ //leechAnimTag REF
+ //leechDataKnifeTag KNIFE
+ leechMaxDist 0
+
+ Health 3500 [$mp]
+ Health 3000 [$sp]
+
+ PainOnHeavyDamageThreshold 450
+ PainOnRepeatDamageThreshold 2400
+ breakOutOfPainDamageThreshold 500
+ PainOnSurpriseHit 0
+ PainWhileRunning 0
+
+ FOV_Vert_Offset 0
+ FOV_Near_Dist 500 // distance at which we transition between near and far FOV values
+ FOV_Far_Dist 2000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 130
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 60
+ FOV_Idle_Far_Vert 60
+
+ FOV_Alert_Near_Horz 130
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 60
+ FOV_Alert_Far_Vert 60
+
+ FOV_Combat_Near_Horz 160
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 130
+ FOV_Combat_Far_Vert 60
+
+ MoveYawSpeed 15
+ AimAngularSpeed 20
+
+ moveDeflectionSmallObstacleRadius 25
+
+ aimassist_adspull_centerAttachmentName "exp_torso_core_fx"
+ aimassist_adspull_centerRadius 18.0
+ aimassist_adspull_headshotAttachmentName "HEADFOCUS"
+ aimassist_adspull_headshotRadius 17.0
+
+ // The common ground slam melee attack he does is actually defined by damagedef_reaper_groundslam
+ MeleeCosAngleRange -1 // 360
+ MeleeDamageForce 50000
+ MeleeChargeDamageForce 50000
+
+ // MP melee settings
+ MeleeDamageMin 50 [$mp]
+ MeleeDamageMax 70 [$mp]
+ MeleeDamageMinHeavyArmor 1000 [$mp]
+ MeleeDamageMaxHeavyArmor 2000 [$mp]
+ MeleeChargeDamageMin 60 [$mp]
+ MeleeChargeDamageMax 80 [$mp]
+ MeleeChargeDamageMinHeavyArmor 1000 [$mp]
+ MeleeChargeDamageMaxHeavyArmor 2000 [$mp]
+
+ // SP melee settings
+ MeleeDamageMin 20 [$sp]
+ MeleeDamageMax 30 [$sp]
+ MeleeDamageMinHeavyArmor 1000 [$sp]
+ MeleeDamageMaxHeavyArmor 2000 [$sp]
+ MeleeChargeDamageMin 40 [$sp]
+ MeleeChargeDamageMax 50 [$sp]
+ MeleeChargeDamageMinHeavyArmor 1000 [$sp]
+ MeleeChargeDamageMaxHeavyArmor 2000 [$sp]
+
+ MeleeDamageRadius 120
+ MeleeRange 150
+ MeleeChargeDamageRadius 100
+ MeleeChargeDamageHeight 120
+ MeleeChargeRange 150
+
+ MeleeEnemyArmorType any
+ MeleeChargeEnemyArmorType normal
+ MeleeChargeCosAngleRange 0.5
+ MeleeDamageFlags "DF_MELEE | DF_KNOCK_BACK"
+ MeleeChargeDamageFlags "DF_MELEE | DF_KNOCK_BACK"
+
+ meleeCameraShakeDuration 1.0
+ meleeImpactEffectTable "melee_superSpectre"
+ //landingImpactTable "titan_landing"
+ //footstepImpactTable "titan_footstep"
+ dodgeImpactTable "titan_dodge"
+
+ specialRangeAttackMinDist 700
+ specialRangeAttackMaxDist 1500
+
+ faceEnemyToleranceAngle 45
+ faceEnemyStrictToleranceAngle 45
+
+ waitBetweenWeaponBurst 0
+
+ MinStartMoveDist 100
+ MaxArrivalDist 250
+ MinForceWalkDist 1500
+ MinForceWalkDistVsHeavyArmor 150
+
+ titanStompable 0
+ rodeoMaxDist 0
+ showFriendlyIcon 1
+
+ aiEnemy_usePriorityDist 1500
+ aiEnemy_priority 20
+
+ magneticRange 125
+ chaseStopDist 150
+ chaseStopDistHeavyArmor 1200
+ chaseOnlyReachable 1
+
+ traverseCostFactor 2.0
+ minGoalRadius 256
+
+ dodgePeriod 6 [$sp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 [$sp] // Don't dodge more than this many times in dodgePeriod
+
+ dodgePeriod 6 [$mp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 [$mp] // Don't dodge more than this many times in dodgePeriod
+
+ minConsecutiveDodgeTime 0
+ missileAwarenessCos -0.1
+ dodgeForwardThreshold 800
+ dodgeBackwardThreshold 400
+ StrafeDodgeDamage 400
+ lockOnDodgeHealthThreshold 0.8
+ enemyAimAtMeWidthHeavyArmor 75
+
+ longJumpCheckMinInterval 8
+ longJumpCheckMaxInterval 15
+ longJumpMinDist 600
+ longJumpMaxDist 1000
+ longJumpHeight 300
+
+ chasecamDistanceMax 320
+ chasecamMaxOrbitDepth 90
+ chasecamOffsetUp 0
+ chasecamOffsetRight 110
+
+ enable_frag_drones 1 [$sp]
+ enable_frag_drones 0 [$mp]
+
+ reactChanceDefault 100
+ reactSurprised_maxRadius 2000
+ reactSurprised_distDelta 600
+
+ nuke_on_death 1
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_aitdm.txt b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_aitdm.txt
new file mode 100644
index 000000000..1a9e1cf11
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_aitdm.txt
@@ -0,0 +1,17 @@
+#base "npc_super_spectre.txt"
+npc_super_spectre_aitdm
+{
+ enable_frag_drones 0
+ enable_harvester_assault 1
+
+ MinForceWalkDist 300
+ MinForceWalkDistVsHeavyArmor 150
+
+ Health 3500 [$mp]
+ Health 3000 [$sp]
+
+ ui_targetinfo "ui/targetinfo_reaper_bounty" [$mp]
+ ui_targetinfo "" [$sp]
+
+ PainOnHeavyDamageThreshold 1000
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_burnmeter.txt b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_burnmeter.txt
new file mode 100644
index 000000000..fc52becda
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_burnmeter.txt
@@ -0,0 +1,6 @@
+#base "npc_super_spectre.txt"
+npc_super_spectre_burnmeter
+{
+ enable_frag_drones 0
+ ui_targetinfo "ui/targetinfo_reaper_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_calmer.txt b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_calmer.txt
new file mode 100644
index 000000000..0acb249fc
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_calmer.txt
@@ -0,0 +1,5 @@
+#base "npc_super_spectre.txt"
+npc_super_spectre_calmer
+{
+ nuke_on_death 0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_fd.txt
new file mode 100644
index 000000000..3a6a9da77
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_super_spectre_fd.txt
@@ -0,0 +1,17 @@
+#base "npc_super_spectre.txt"
+npc_super_spectre_fd
+{
+ enable_frag_drones 0
+ assaultHarvester 0
+
+ minGoalRadius 64
+
+ MinForceWalkDist 0
+ MinForceWalkDistVsHeavyArmor 0
+
+ Health 3500 [$mp]
+ Health 3000 [$sp]
+
+ ui_targetinfo "ui/targetinfo_reaper_bounty" [$mp]
+ ui_targetinfo "" [$sp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan.txt
new file mode 100644
index 000000000..b2d7f38fb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan.txt
@@ -0,0 +1,172 @@
+npc_titan
+{
+ HullType "HULL_TITAN"
+ useSequenceBounds 1
+ collideWithPlayer 1
+ AIClass titan
+ BodyType titan
+ ArmorType heavy
+ SmartAmmoLockType large
+ TraverseAnimType titan
+ title #NPC_TITAN
+ ui_targetinfo "ui/targetinfo_titan" [$mp]
+ ui_targetinfo "ui/targetinfo_titan_sp" [$sp]
+
+ smartAmmoLockAttachmentName0 SMART_AMMO_HEAD
+ smartAmmoLockAttachmentName1 SMART_AMMO_TORSO_FRONT
+ smartAmmoLockAttachmentName2 SMART_AMMO_TORSO_BASE
+ smartAmmoLockAttachmentName3 SMART_AMMO_ARM_LEFT
+ smartAmmoLockAttachmentName4 SMART_AMMO_ARM_RIGHT
+
+ BaseClass "npc_titan"
+
+ DefaultModelName "models/titans/buddy/titan_buddy.mdl"
+ DefaultWeapon "mp_titanweapon_particle_accelerator"
+ BehaviorSelector "behavior_titan"
+ WeaponCapacity "TitanMainWeapons"
+ npc_titan_player_settings "titan_atlas"
+
+ mechanical 1
+
+ showHolsteredWeapons 1
+
+ Health 8000 [$sp]
+ Health 8000 [$mp]
+
+ PainOnHeavyDamageThreshold 1000
+ PainOnRepeatDamageThreshold 3000
+ RepeatDamageTimeInterval 3
+ breakOutOfPainDamageThreshold 300
+ lightPainMinInterval 1.5
+ heavyPainMinInterval 8
+
+ FOV_Vert_Offset -20
+ FOV_Near_Dist 180 // use near values up to this distance
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 140
+ FOV_Idle_Near_Vert 120
+ FOV_Idle_Far_Horz 100
+ FOV_Idle_Far_Vert 70
+
+ FOV_Alert_Near_Horz 180
+ FOV_Alert_Near_Vert 90
+ FOV_Alert_Far_Horz 170
+ FOV_Alert_Far_Vert 70
+
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 120
+ FOV_Combat_Far_Horz 170
+ FOV_Combat_Far_Vert 90
+
+ YawSpeed 25
+ MoveYawSpeed 15
+ AimAngularSpeed 10
+
+ moveDeflectionSmallObstacleRadius 25
+
+ MeleeDamageMin 150
+ MeleeDamageMax 300
+ MeleeDamageMinHeavyArmor 500
+ MeleeDamageMaxHeavyArmor 600
+ MeleeDamageRadius 120
+ MeleeDamageForce 2500000
+ MeleeChargeDamageForce 2800000
+ MeleeDamageUpwardBias 0.5
+ MeleeRange 200
+ MeleeChargeRange 270
+ MeleeChargeDamageRadius 150
+ MeleeChargeDamageHeight 150
+ MeleeChargeDamageMin 500
+ MeleeChargeDamageMax 700
+ MeleeChargeDamageMinHeavyArmor 700
+ MeleeChargeDamageMaxHeavyArmor 1000
+ meleeInterval 2
+ meleeChargeInterval 3
+ meleeMaxCombo 2
+ meleeCameraShakeDuration 1.0
+ MeleeEnemyArmorType any
+ MeleeChargeEnemyArmorType any
+ meleeImpactEffectTable "melee_titan"
+ syncedMeleeEngageDist 80
+ syncedMeleePressToInitiateDist 300
+ landingImpactTable "titan_landing"
+ footstepImpactTable "titan_footstep"
+ dodgeImpactTable "titan_dodge"
+
+ circleStrafeDist 1000
+ circleStrafeAngleIncrement 20
+
+ faceEnemyToleranceAngle 85
+ faceEnemyWhileMovingDist 3500
+ faceEnemyWhileMovingDuration 3
+ StrafeDodgeDamage 800
+
+ takeCoverFromEnemyRadius 700
+ chaseStopDist 200
+ chaseStopDistHeavyArmor 1000
+
+ MinStartMoveDist 300
+ MaxArrivalDist 320
+ MinForceWalkDist 300
+
+ showFriendlyIcon 1
+ restrictAimGunToValidSequences 1
+ waitBetweenWeaponBurst 1
+ shootableByFriendlyPlayer 1
+
+ allowFlee 0
+ allowSignals 1
+ allowPatrol 1
+ allowInvestigate 1
+ allowCrouchAnims 0
+
+ patrolRangeMin 1000
+ patrolRangeMax 2500
+
+ minGoalRadius 256
+
+ aiEnemy_immediateThreatDist 800 [$sp]
+ aiEnemy_usePriorityDist 2200
+ aiEnemy_priority 50
+
+ ai_passThroughThickness 128
+
+ titan_footstep_damage_height_check_ratio 0.15
+ titan_footstep_damage_min_speed 50
+ titan_footstep_damage_interval 1.0
+ titan_footstep_damage_dist_interval 150
+
+ chasecamDistanceMax 320
+ chasecamMaxOrbitDepth 90
+ chasecamOffsetUp 0
+ chasecamOffsetRight 110
+
+ magneticRange 190
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 // Don't dodge more than this many times in dodgePeriod
+ dodgeForwardThreshold 1000
+ dodgeBackwardThreshold 1600
+ enemyAimAtMeWidthHeavyArmor 50
+ reactChanceDefault 100
+
+ evasiveCombatShieldPct 0.8 // if my shield is above 80% don't be evasive
+
+ evasiveCombatHealthSegmentPct 0.0
+ aggressiveCombatHealthSegmentPct 0.0
+
+ aggressiveCombatTotalHealthDiffPct 0.3 // if my health+shield - enemy heatlh+shield is > 30% of my max health+shield
+
+ evasiveCombatTotalHealthDiffPct 1.0 [$mp] // if enemy health+shield - my heatlh+shield is > 100% of my max health+shield
+ evasiveCombatTotalHealthDiffPct 0.2 [$sp] // if enemy health+shield - my heatlh+shield is > 20% of my max health+shield
+
+ evasiveCombatHealthChangeRateDiff -200
+ aggresiveCombatHealthChangeRateDiff 100
+
+ sharedEnergyTotal 1000
+ sharedEnergyRegenDelay 0.2
+ sharedEnergyRegenRate 100.0
+
+ DrawEnemyHealthBar 1
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_arc.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_arc.txt
new file mode 100644
index 000000000..5cd5c2a3e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_arc.txt
@@ -0,0 +1,10 @@
+#base "npc_titan.txt"
+npc_titan_arc
+{
+ title #NPC_TITAN_ARC
+ WeaponCapacity "Locked"
+ npc_titan_player_settings "titan_stryder"
+ footstep_type "stryder"
+ is_arc_titan 1
+ faceEnemyWhileMovingDist 1024
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas.txt
new file mode 100644
index 000000000..8ac0d2eab
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas.txt
@@ -0,0 +1,21 @@
+#base "npc_titan.txt"
+
+npc_titan_atlas
+{
+ footstep_type "atlas"
+ title #NPC_TITAN_ATLAS
+
+ StrafeDodgeDamage 800
+ PainOnRepeatDamageThreshold 3400
+
+ dodgePeriod 8 [$sp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 [$sp] // Don't dodge more than this many times in dodgePeriod
+
+ dodgePeriod 12 [$mp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 [$mp] // Don't dodge more than this many times in dodgePeriod
+
+ DefaultModelName "models/titans/medium/titan_medium_wraith.mdl" [$mp]
+ DefaultModelName "models/titans/medium/sp_titan_medium_wraith.mdl" [$sp]
+
+ npc_titan_player_settings "titan_atlas"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_ion_prime_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_ion_prime_bounty.txt
new file mode 100644
index 000000000..c042f71d6
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_ion_prime_bounty.txt
@@ -0,0 +1,5 @@
+#base "npc_titan_atlas_stickybomb_bounty.txt"
+npc_titan_atlas_ion_prime_bounty
+{
+ npc_titan_player_settings "titan_atlas_ion_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb.txt
new file mode 100644
index 000000000..0ce4acb3b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb.txt
@@ -0,0 +1,19 @@
+#base "npc_titan_atlas.txt"
+npc_titan_atlas_stickybomb
+{
+ title "#NPC_TITAN_ATLAS_STICKYBOMB"
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_atlas_stickybomb"
+
+ sharedEnergyTotal 1000
+ sharedEnergyRegenDelay 0.2
+ sharedEnergyRegenRate 100.0
+
+ evasiveCombatHealthChangeRateDiff -100
+ aggresiveCombatHealthChangeRateDiff 100
+
+ DefaultModelName "models/titans/medium/titan_medium_ajax.mdl" [$mp]
+ DefaultModelName "models/titans/medium/sp_titan_medium_ajax.mdl" [$sp]
+
+ footstep_type "ion"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb_boss_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb_boss_fd.txt
new file mode 100644
index 000000000..0da91a358
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb_boss_fd.txt
@@ -0,0 +1,23 @@
+#base "npc_titan_atlas_stickybomb.txt"
+npc_titan_atlas_stickybomb_boss_fd
+{
+ title "#NPC_TITAN_ATLAS_STICKYBOMB"
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_atlas_stickybomb"
+
+ sharedEnergyTotal 1000
+ sharedEnergyRegenDelay 0.2
+ sharedEnergyRegenRate 100.0
+
+ evasiveCombatHealthChangeRateDiff -100
+ aggresiveCombatHealthChangeRateDiff 100
+
+ DefaultModelName "models/titans/medium/titan_medium_ajax.mdl" [$mp]
+ DefaultModelName "models/titans/medium/sp_titan_medium_ajax.mdl" [$sp]
+
+ footstep_type "ion"
+
+ titanCamoIndex -1
+ titanDecalIndex 12
+ titanSkinIndex 8
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb_bounty.txt
new file mode 100644
index 000000000..8fcda68e9
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_stickybomb_bounty.txt
@@ -0,0 +1,5 @@
+#base "npc_titan_atlas_stickybomb.txt"
+npc_titan_atlas_stickybomb_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker.txt
new file mode 100644
index 000000000..5a57b9ee7
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker.txt
@@ -0,0 +1,19 @@
+#base "npc_titan_atlas.txt"
+npc_titan_atlas_tracker
+{
+ title #NPC_TITAN_ATLAS_TRACKER
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_atlas_tracker"
+
+ BehaviorSelector "behavior_titan_long_range"
+
+ circleStrafeDist 1500
+ circleStrafeAngleIncrement 15
+
+ aggresiveCombatHealthChangeRateDiff 300
+
+ DefaultModelName "models/titans/medium/titan_medium_wraith.mdl" [$mp]
+ DefaultModelName "models/titans/medium/sp_titan_medium_wraith.mdl" [$sp]
+
+ footstep_type "tone"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_boss_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_boss_fd.txt
new file mode 100644
index 000000000..07a653c11
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_boss_fd.txt
@@ -0,0 +1,23 @@
+#base "npc_titan_atlas_tracker.txt"
+npc_titan_atlas_tracker_boss_fd
+{
+ title #NPC_TITAN_ATLAS_TRACKER
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_atlas_tracker"
+
+ BehaviorSelector "behavior_titan_long_range"
+
+ circleStrafeDist 1500
+ circleStrafeAngleIncrement 15
+
+ aggresiveCombatHealthChangeRateDiff 300
+
+ DefaultModelName "models/titans/medium/titan_medium_wraith.mdl" [$mp]
+ DefaultModelName "models/titans/medium/sp_titan_medium_wraith.mdl" [$sp]
+
+ footstep_type "tone"
+
+ titanCamoIndex 30
+ titanDecalIndex 12
+ titanSkinIndex 2
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_bounty.txt
new file mode 100644
index 000000000..dc413e82b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_bounty.txt
@@ -0,0 +1,5 @@
+#base "npc_titan_atlas_tracker.txt"
+npc_titan_atlas_tracker_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_fd_sniper.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_fd_sniper.txt
new file mode 100644
index 000000000..1703dd9f8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_fd_sniper.txt
@@ -0,0 +1,7 @@
+#base "npc_titan_atlas_tracker.txt"
+npc_titan_atlas_tracker_fd_sniper
+{
+ title #NPC_TITAN_SNIPER_FD
+ minGoalRadius 100
+ assaultHarvester 0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_mortar.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_mortar.txt
new file mode 100644
index 000000000..8dfc4baae
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_tracker_mortar.txt
@@ -0,0 +1,10 @@
+#base "npc_titan_atlas_tracker.txt"
+npc_titan_atlas_tracker_mortar
+{
+ title #NPC_TITAN_MORTAR
+
+ npc_titan_player_settings "titan_atlas_tracker_mortar"
+ minGoalRadius 32
+ assaultHarvester 0
+ allowCrouchAnims 1
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard.txt
new file mode 100644
index 000000000..ec63b128d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard.txt
@@ -0,0 +1,19 @@
+#base "npc_titan_atlas.txt"
+npc_titan_atlas_vanguard
+{
+ title #NPC_TITAN_ATLAS_VANGUARD
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_atlas_vanguard"
+
+ BehaviorSelector "behavior_titan_long_range"
+
+ circleStrafeDist 1500
+ circleStrafeAngleIncrement 15
+
+ aggresiveCombatHealthChangeRateDiff 300
+
+ DefaultModelName "models/titans/medium/titan_medium_vanguard.mdl" [$mp]
+ DefaultModelName "models/titans/medium/titan_medium_vanguard.mdl" [$sp]
+
+ footstep_type "buddy"
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard_boss_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard_boss_fd.txt
new file mode 100644
index 000000000..335eee599
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard_boss_fd.txt
@@ -0,0 +1,23 @@
+#base "npc_titan_atlas_vanguard.txt"
+npc_titan_atlas_vanguard_boss_fd
+{
+ title #NPC_TITAN_ATLAS_VANGUARD
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_atlas_vanguard"
+
+ BehaviorSelector "behavior_titan_long_range"
+
+ circleStrafeDist 1500
+ circleStrafeAngleIncrement 15
+
+ aggresiveCombatHealthChangeRateDiff 300
+
+ DefaultModelName "models/titans/medium/titan_medium_vanguard.mdl" [$mp]
+ DefaultModelName "models/titans/medium/titan_medium_vanguard.mdl" [$sp]
+
+ footstep_type "buddy"
+
+ titanCamoIndex 133
+ titanDecalIndex 0
+ titanSkinIndex 2
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard_bounty.txt
new file mode 100644
index 000000000..eebad86a3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_atlas_vanguard_bounty.txt
@@ -0,0 +1,5 @@
+#base "npc_titan_atlas_vanguard.txt"
+npc_titan_atlas_vanguard_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto.txt
new file mode 100644
index 000000000..14f3c2456
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto.txt
@@ -0,0 +1,43 @@
+#base "npc_titan.txt"
+
+npc_titan_auto
+{
+ title #NPC_TITAN_AUTO
+
+ BaseClass "npc_titan"
+
+ footstep_type "atlas"
+
+ aggressiveCombatTotalHealthDiffPct 0.5
+
+ "general" [$mp]
+ {
+ BehaviorSelector "behavior_mp_auto_titan"
+ chaseStopDistHeavyArmor 1000
+ dodgeBackwardThreshold 1600
+ dodgeForwardThreshold 800
+ faceEnemyWhileMovingDist 2500
+ faceEnemyWhileMovingDist 3500
+ Health 8000
+ lockOnDodgeHealthThreshold 0.6
+ MeleeChargeDamageMaxHeavyArmor 2000
+ MeleeChargeDamageMinHeavyArmor 2000
+ MeleeDamageMaxHeavyArmor 500
+ MeleeDamageMinHeavyArmor 400
+ StrafeDodgeDamage 800
+ }
+
+ "general" [$sp]
+ {
+ BehaviorSelector "behavior_sp_auto_titan"
+ chaseStopDistHeavyArmor 600
+ dodgeBackwardThreshold 2000
+ dodgeForwardThreshold 1500
+ Health 7500
+ MeleeChargeDamageMaxHeavyArmor 1500
+ MeleeChargeDamageMinHeavyArmor 1000
+ MeleeDamageMaxHeavyArmor 1000
+ MeleeDamageMinHeavyArmor 750
+ StrafeDodgeDamage 800
+ }
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas.txt
new file mode 100644
index 000000000..17a65f129
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas.txt
@@ -0,0 +1,15 @@
+#base "npc_titan_auto.txt"
+
+npc_titan_auto_atlas
+{
+ footstep_type "atlas"
+ title #NPC_TITAN_AUTO_ATLAS
+
+ StrafeDodgeDamage 800
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 // Don't dodge more than this many times in dodgePeriod
+
+ ai_passThroughThickness 128
+ DefaultModelName "models/titans/medium/titan_medium_ajax.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_ion_prime.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_ion_prime.txt
new file mode 100644
index 000000000..5545e3d7f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_ion_prime.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_auto_atlas_stickybomb.txt"
+
+npc_titan_auto_atlas_ion_prime
+{
+ npc_titan_player_settings "titan_atlas_ion_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_rocketeer.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_rocketeer.txt
new file mode 100644
index 000000000..48fa9b994
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_rocketeer.txt
@@ -0,0 +1,10 @@
+#base "npc_titan_auto_atlas.txt"
+
+npc_titan_auto_atlas_rocketeer
+{
+ title "#NPC_TITAN_AUTO_ATLAS_ROCKETEER"
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_atlas_rocketeer"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_stickybomb.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_stickybomb.txt
new file mode 100644
index 000000000..72307ee8b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_stickybomb.txt
@@ -0,0 +1,12 @@
+#base "npc_titan_auto_atlas.txt"
+
+npc_titan_auto_atlas_stickybomb
+{
+ title "#NPC_TITAN_AUTO_ATLAS_LASER"
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_atlas_stickybomb"
+
+ footstep_type "ion"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_tone_prime.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_tone_prime.txt
new file mode 100644
index 000000000..a475466f3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_tone_prime.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_auto_atlas_tracker.txt"
+
+npc_titan_auto_atlas_tone_prime
+{
+ npc_titan_player_settings "titan_atlas_tone_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_tracker.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_tracker.txt
new file mode 100644
index 000000000..55bc4b0a3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_tracker.txt
@@ -0,0 +1,12 @@
+#base "npc_titan_auto_atlas.txt"
+
+npc_titan_auto_atlas_tracker
+{
+ title "#NPC_TITAN_AUTO_ATLAS_TRACKER"
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_atlas_tracker"
+
+ footstep_type "tone"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_vanguard.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_vanguard.txt
new file mode 100644
index 000000000..9e0028889
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_atlas_vanguard.txt
@@ -0,0 +1,12 @@
+#base "npc_titan_auto_atlas.txt"
+
+npc_titan_auto_atlas_vanguard
+{
+ title "#NPC_TITAN_AUTO_ATLAS_VANGUARD"
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_atlas_vanguard"
+
+ footstep_type "buddy"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre.txt
new file mode 100644
index 000000000..e291df60f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre.txt
@@ -0,0 +1,13 @@
+#base "npc_titan_auto.txt"
+
+npc_titan_auto_ogre
+{
+ footstep_type "ogre"
+ title #NPC_TITAN_AUTO_OGRE
+
+ StrafeDodgeDamage 1000
+
+ dodgePeriod 7 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+ DefaultModelName "models/titans/heavy/titan_heavy_ogre.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_fighter.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_fighter.txt
new file mode 100644
index 000000000..921055c00
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_fighter.txt
@@ -0,0 +1,10 @@
+#base "npc_titan_auto_ogre.txt"
+
+npc_titan_auto_ogre_fighter
+{
+ title "#NPC_TITAN_AUTO_OGRE_FIGHTER"
+
+ dodgePeriod 7 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_ogre_fighter"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_legion_prime.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_legion_prime.txt
new file mode 100644
index 000000000..16afd2268
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_legion_prime.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_auto_ogre_minigun.txt"
+
+npc_titan_auto_ogre_legion_prime
+{
+ npc_titan_player_settings "titan_ogre_legion_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_meteor.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_meteor.txt
new file mode 100644
index 000000000..385a401b8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_meteor.txt
@@ -0,0 +1,11 @@
+#base "npc_titan_auto_ogre.txt"
+
+npc_titan_auto_ogre_meteor
+{
+ title "#NPC_TITAN_AUTO_OGRE_METEOR"
+
+ maxDodgePerPeriod 0 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_ogre_meteor"
+
+ footstep_type "scorch"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_minigun.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_minigun.txt
new file mode 100644
index 000000000..33e9b2632
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_minigun.txt
@@ -0,0 +1,17 @@
+#base "npc_titan_auto_ogre.txt"
+
+npc_titan_auto_ogre_minigun
+{
+ title "#NPC_TITAN_AUTO_OGRE_MINIGUN"
+
+ maxDodgePerPeriod 0 // Don't dodge more than this many times in dodgePeriod
+
+ aimAngularSpeed 2
+ aimConeCos 0.5 // 60 degree
+ aimConeCloseCos 0.5 // 60 degree
+
+ npc_titan_player_settings "titan_ogre_minigun"
+ windup_time 1.0
+
+ footstep_type "legion"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_scorch_prime.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_scorch_prime.txt
new file mode 100644
index 000000000..8f0928b0a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_ogre_scorch_prime.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_auto_ogre_meteor.txt"
+
+npc_titan_auto_ogre_scorch_prime
+{
+ npc_titan_player_settings "titan_ogre_scorch_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder.txt
new file mode 100644
index 000000000..a1a6fbe97
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder.txt
@@ -0,0 +1,11 @@
+#base "npc_titan_auto.txt"
+
+npc_titan_auto_stryder
+{
+ footstep_type "northstar"
+ title #NPC_TITAN_AUTO_STRYDER
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 3 // Don't dodge more than this many times in dodgePeriod
+ DefaultModelName "models/titans/light/titan_light_locust.mdl"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_arc.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_arc.txt
new file mode 100644
index 000000000..82e384e84
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_arc.txt
@@ -0,0 +1,10 @@
+#base "npc_titan_auto_stryder.txt"
+
+npc_titan_auto_stryder_arc
+{
+ title "#NPC_TITAN_AUTO_STRYDER_ARC"
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_stryder_arc"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_leadwall.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_leadwall.txt
new file mode 100644
index 000000000..81896b86f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_leadwall.txt
@@ -0,0 +1,12 @@
+#base "npc_titan_auto_stryder.txt"
+
+npc_titan_auto_stryder_leadwall
+{
+ title "#NPC_TITAN_AUTO_STRYDER_SWORD"
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_stryder_leadwall"
+
+ footstep_type "ronin"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_northstar_prime.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_northstar_prime.txt
new file mode 100644
index 000000000..0ce6d33e7
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_northstar_prime.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_auto_stryder_sniper.txt"
+
+npc_titan_auto_stryder_northstar_prime
+{
+ npc_titan_player_settings "titan_stryder_northstar_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_ronin_prime.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_ronin_prime.txt
new file mode 100644
index 000000000..0d00942fe
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_ronin_prime.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_auto_stryder_leadwall.txt"
+
+npc_titan_auto_stryder_ronin_prime
+{
+ npc_titan_player_settings "titan_stryder_ronin_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_sniper.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_sniper.txt
new file mode 100644
index 000000000..b43a5a0c6
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_auto_stryder_sniper.txt
@@ -0,0 +1,12 @@
+#base "npc_titan_auto_stryder.txt"
+
+npc_titan_auto_stryder_sniper
+{
+ title "#NPC_TITAN_AUTO_STRYDER_SNIPER"
+
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+ npc_titan_player_settings "titan_stryder_sniper"
+
+ footstep_type "northstar"
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy.txt
new file mode 100644
index 000000000..453f0d2cb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy.txt
@@ -0,0 +1,25 @@
+#base "npc_titan.txt"
+
+npc_titan_buddy
+{
+ AIClass titan_buddy
+ footstep_type "buddy"
+ title #NPC_BT_NAME
+
+ ui_targetinfo "" [$mp]
+ ui_targetinfo "ui/targetinfo_cockpit_name" [$sp]
+
+ DefaultModelName "models/titans/buddy/titan_buddy.mdl"
+ npc_titan_player_settings "titan_buddy"
+ BehaviorSelector "behavior_titan_buddy"
+
+ dodgeForwardThreshold 2500
+
+ nonCombatTurnAnimsOnlyForIdle 1
+ allowPatrol 0
+
+ MeleeChargeRange 100
+
+ returnToIdleTime 3
+ can_traverse 1
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy_s2s.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy_s2s.txt
new file mode 100644
index 000000000..83b687ca7
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy_s2s.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_buddy.txt"
+
+npc_titan_buddy_s2s
+{
+ LookDistDefault_Combat 15000 //so he and sarah can fire on ships at the intro
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy_skyway.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy_skyway.txt
new file mode 100644
index 000000000..12403bef0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_buddy_skyway.txt
@@ -0,0 +1,7 @@
+#base "npc_titan_buddy.txt"
+
+npc_titan_buddy_skyway
+{
+ DefaultModelName "models/titans/buddy/titan_buddy_skyway.mdl"
+ npc_titan_player_settings "titan_buddy_spare"
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_mortar.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_mortar.txt
new file mode 100644
index 000000000..7f80721db
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_mortar.txt
@@ -0,0 +1,8 @@
+#base "npc_titan.txt"
+npc_titan_mortar
+{
+ title #NPC_TITAN_MORTAR
+ WeaponCapacity "Locked"
+ npc_titan_player_settings "titan_atlas"
+ is_mortar_titan 1
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_nuke.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_nuke.txt
new file mode 100644
index 000000000..3cd1eabb4
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_nuke.txt
@@ -0,0 +1,9 @@
+#base "npc_titan.txt"
+npc_titan_nuke
+{
+ title #NPC_TITAN_NUKE
+ WeaponCapacity "Locked"
+ npc_titan_player_settings "titan_ogre"
+ is_nuke_titan 1
+ faceEnemyWhileMovingDist 500
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre.txt
new file mode 100644
index 000000000..7a284ae8f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre.txt
@@ -0,0 +1,22 @@
+#base "npc_titan.txt"
+
+npc_titan_ogre
+{
+ footstep_type "ogre"
+ title #NPC_TITAN_OGRE
+
+ StrafeDodgeDamage 1000
+ PainOnRepeatDamageThreshold 3500
+
+ MeleeChargeRange 300
+
+ dodgePeriod 10 [$sp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 [$sp] // Don't dodge more than this many times in dodgePeriod
+
+ dodgePeriod 12 [$mp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 0 [$mp] // Don't dodge more than this many times in dodgePeriod
+
+ DefaultModelName "models/titans/heavy/titan_heavy_ogre.mdl" [$mp]
+ DefaultModelName "models/titans/heavy/sp_titan_heavy_deadbolt.mdl" [$sp]
+ npc_titan_player_settings "titan_ogre"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_fighter.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_fighter.txt
new file mode 100644
index 000000000..d1f504777
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_fighter.txt
@@ -0,0 +1,26 @@
+#base "npc_titan_ogre.txt"
+npc_titan_ogre_fighter
+{
+ title #NPC_TITAN_OGRE_FIGHTER
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_ogre_fighter"
+
+ BehaviorSelector "behavior_titan_melee"
+
+ PainOnHeavyDamageThreshold 10000
+
+ chaseStopDist 200
+ chaseStopDistHeavyArmor 200
+
+ meleeInterval 1
+ meleeChargeInterval 2
+ meleeMaxCombo 4
+
+ //evasiveCombatHealthSegmentPct 0.0 [$mp] // if my segment is down to 20% be evasive
+ //evasiveCombatHealthSegmentPct 0.2 [$sp] // if my segment is down to 20% be evasive
+ //aggressiveCombatHealthSegmentPct 0.0 [$mp] // if enemy segement is down to 40% be aggressive
+ //aggressiveCombatHealthSegmentPct 0.4 [$sp] // if enemy segement is down to 40% be aggressive
+
+ evasiveCombatHealthChangeRateDiff -600
+ aggresiveCombatHealthChangeRateDiff -400
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_fighter_berserker_core.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_fighter_berserker_core.txt
new file mode 100644
index 000000000..c3058416b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_fighter_berserker_core.txt
@@ -0,0 +1,34 @@
+#base "npc_titan_ogre_fighter.txt"
+npc_titan_ogre_fighter_berserker_core
+{
+ BehaviorSelector "behavior_titan_melee_core"
+
+ chaseStopDist 100
+ chaseStopDistHeavyArmor 100
+
+ dodgePeriod 1.2 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+
+ PainOnHeavyDamageThreshold 0 // no heavy damage
+ PainOnRepeatDamageThreshold 1
+
+ meleeInterval 0.5
+ meleeChargeInterval 0.5
+ meleeMaxCombo 8
+
+ MeleeDamageMin 2000
+ MeleeDamageMax 3000
+ MeleeDamageMinHeavyArmor 2000
+ MeleeDamageMaxHeavyArmor 3000
+ MeleeDamageRadius 150
+ MeleeDamageForce 3000000
+ MeleeChargeDamageForce 3200000
+ MeleeRange 200
+ MeleeChargeRange 300
+ MeleeChargeDamageRadius 100
+ MeleeChargeDamageHeight 100
+ MeleeChargeDamageMin 500
+ MeleeChargeDamageMax 500
+ MeleeChargeDamageMinHeavyArmor 3000
+ MeleeChargeDamageMaxHeavyArmor 4000
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_legion_prime_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_legion_prime_bounty.txt
new file mode 100644
index 000000000..9c1028391
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_legion_prime_bounty.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_ogre_minigun_bounty.txt"
+npc_titan_ogre_legion_prime_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+ npc_titan_player_settings "titan_ogre_legion_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor.txt
new file mode 100644
index 000000000..b935f34a2
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor.txt
@@ -0,0 +1,24 @@
+#base "npc_titan_ogre.txt"
+npc_titan_ogre_meteor
+{
+ title #NPC_TITAN_OGRE_METEOR
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_ogre_meteor"
+
+ BehaviorSelector "behavior_titan_ogre_meteor"
+
+ chaseStopDist 500
+ chaseStopDistHeavyArmor 700
+ circleStrafeDist 600
+
+ evasiveCombatTotalHealthDiffPct 0.1
+
+ evasiveCombatHealthChangeRateDiff -300
+ aggresiveCombatHealthChangeRateDiff 50
+
+ footstep_type "scorch"
+
+ DefaultModelName "models/titans/heavy/titan_heavy_ogre.mdl" [$mp]
+ DefaultModelName "models/titans/heavy/sp_titan_heavy_ogre.mdl" [$sp]
+
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_boss_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_boss_fd.txt
new file mode 100644
index 000000000..ce863d299
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_boss_fd.txt
@@ -0,0 +1,27 @@
+#base "npc_titan_ogre_meteor.txt"
+npc_titan_ogre_meteor_boss_fd
+{
+ title #NPC_TITAN_OGRE_METEOR
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_ogre_meteor"
+
+ BehaviorSelector "behavior_titan_ogre_meteor"
+
+ chaseStopDist 500
+ chaseStopDistHeavyArmor 700
+ circleStrafeDist 600
+
+ evasiveCombatTotalHealthDiffPct 0.1
+
+ evasiveCombatHealthChangeRateDiff -300
+ aggresiveCombatHealthChangeRateDiff 50
+
+ footstep_type "scorch"
+
+ DefaultModelName "models/titans/heavy/titan_heavy_ogre.mdl" [$mp]
+ DefaultModelName "models/titans/heavy/sp_titan_heavy_ogre.mdl" [$sp]
+
+ titanCamoIndex -1
+ titanDecalIndex 0
+ titanSkinIndex 7
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_bounty.txt
new file mode 100644
index 000000000..62a8afad8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_bounty.txt
@@ -0,0 +1,5 @@
+#base "npc_titan_ogre_meteor.txt"
+npc_titan_ogre_meteor_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_nuke.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_nuke.txt
new file mode 100644
index 000000000..5a73c6f0e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_meteor_nuke.txt
@@ -0,0 +1,11 @@
+#base "npc_titan_ogre_meteor.txt"
+npc_titan_ogre_meteor_nuke
+{
+ title #NPC_TITAN_AUTO_NUKE
+ npc_titan_player_settings "titan_ogre_meteor_nuke"
+ minGoalRadius 32
+
+ titanCamoIndex -1
+ titanDecalIndex 11
+ titanSkinIndex 4
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun.txt
new file mode 100644
index 000000000..16055d80f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun.txt
@@ -0,0 +1,27 @@
+#base "npc_titan_ogre.txt"
+npc_titan_ogre_minigun
+{
+ title #NPC_TITAN_OGRE_MINIGUN
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_ogre_minigun"
+
+ BehaviorSelector "behavior_titan_ogre_minigun"
+
+ lightPainMinInterval 4
+
+ StrafeDodgeDamage 1200
+
+ chaseStopDist 2000
+ chaseStopDistHeavyArmor 2000
+
+ aimAngularSpeed 2
+ aimConeCos 0.5 // 60 degree
+ aimConeCloseCos 0.5 // 60 degree
+
+ footstep_type "legion"
+
+ DefaultModelName "models/titans/heavy/titan_heavy_deadbolt.mdl" [$mp]
+ DefaultModelName "models/titans/heavy/sp_titan_heavy_deadbolt.mdl" [$sp]
+
+ windup_time 1.0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_boss_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_boss_fd.txt
new file mode 100644
index 000000000..2587a3315
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_boss_fd.txt
@@ -0,0 +1,31 @@
+#base "npc_titan_ogre_minigun.txt"
+npc_titan_ogre_minigun_boss_fd
+{
+ title #NPC_TITAN_OGRE_MINIGUN
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_ogre_minigun"
+
+ BehaviorSelector "behavior_titan_ogre_minigun"
+
+ lightPainMinInterval 4
+
+ StrafeDodgeDamage 1200
+
+ chaseStopDist 2000
+ chaseStopDistHeavyArmor 2000
+
+ aimAngularSpeed 2
+ aimConeCos 0.5 // 60 degree
+ aimConeCloseCos 0.5 // 60 degree
+
+ footstep_type "legion"
+
+ DefaultModelName "models/titans/heavy/titan_heavy_deadbolt.mdl" [$mp]
+ DefaultModelName "models/titans/heavy/sp_titan_heavy_deadbolt.mdl" [$sp]
+
+ windup_time 1.0
+
+ titanCamoIndex 30
+ titanDecalIndex 12
+ titanSkinIndex 2
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_bounty.txt
new file mode 100644
index 000000000..aa4ab86fe
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_bounty.txt
@@ -0,0 +1,5 @@
+#base "npc_titan_ogre_minigun.txt"
+npc_titan_ogre_minigun_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_nuke.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_nuke.txt
new file mode 100644
index 000000000..fba50cbaf
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_minigun_nuke.txt
@@ -0,0 +1,13 @@
+#base "npc_titan_ogre_minigun.txt"
+npc_titan_ogre_minigun_nuke
+{
+ title #NPC_TITAN_AUTO_NUKE
+ npc_titan_player_settings "titan_ogre_minigun_nuke"
+ minGoalRadius 32
+
+ BehaviorSelector "behavior_titan_ogre_minigun_nuke"
+
+ titanCamoIndex 133
+ titanDecalIndex 15
+ titanSkinIndex 2
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_scorch_prime_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_scorch_prime_bounty.txt
new file mode 100644
index 000000000..d8a8510d3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_ogre_scorch_prime_bounty.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_ogre_meteor_bounty.txt"
+npc_titan_ogre_scorch_prime_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+ npc_titan_player_settings "titan_ogre_scorch_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_proto_stasisgun.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_proto_stasisgun.txt
new file mode 100644
index 000000000..57bd78644
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_proto_stasisgun.txt
@@ -0,0 +1,8 @@
+#base "npc_titan.txt"
+
+npc_titan_proto_stasisgun
+{
+ title #NPC_TITAN_PROTO_STASISGUN
+
+ BehaviorSelector "behavior_sp_npc_titan_proto_stasisgun"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_sarah.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_sarah.txt
new file mode 100644
index 000000000..1030ad4ec
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_sarah.txt
@@ -0,0 +1,21 @@
+#base "npc_titan_buddy.txt"
+
+npc_titan_sarah
+{
+ AIClass titan_buddy
+ footstep_type "buddy"
+ title #NPC_SARAH_NAME
+
+ ui_targetinfo "ui/targetinfo_titan" [$mp]
+ ui_targetinfo "ui/targetinfo_titan_sp" [$sp]
+
+ DefaultModelName "models/titans/buddy/titan_buddy.mdl"
+ npc_titan_player_settings "titan_buddy"
+ BehaviorSelector "behavior_titan_buddy"
+
+ MeleeChargeRange 250
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 // Don't dodge more than this many times in dodgePeriod
+
+ aiEnemy_priority 20 // lower than other titans
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_sniper.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_sniper.txt
new file mode 100644
index 000000000..c4be82ae3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_sniper.txt
@@ -0,0 +1,20 @@
+#base "npc_titan.txt"
+
+npc_titan_sniper
+{
+ HullType "HULL_TITAN"
+ title #NPC_TITAN_SNIPER
+
+ BehaviorSelector "behavior_titan_sniper"
+ WeaponCapacity "Locked"
+ npc_titan_player_settings "titan_atlas"
+
+ circleStrafeDist 2500
+ circleStrafeAngleIncrement 5
+
+ aiEnemy_usePriorityDist 3500
+
+ LookDistDefault_Combat 9500 //DO NOT set less than 9k without talking to mo first - will break s2s
+
+ aggressiveCombatTotalHealthDiffPct 0.8
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder.txt
new file mode 100644
index 000000000..6257f3770
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder.txt
@@ -0,0 +1,20 @@
+#base "npc_titan.txt"
+
+npc_titan_stryder
+{
+ footstep_type "northstar"
+ title #NPC_TITAN_STRYDER
+
+ dodgePeriod 8 [$sp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 3 [$sp] // Don't dodge more than this many times in dodgePeriod
+
+ dodgePeriod 12 [$mp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 [$mp] // Don't dodge more than this many times in dodgePeriod
+
+ DefaultModelName "models/titans/light/titan_light_locust.mdl"
+ npc_titan_player_settings "titan_stryder"
+
+ takeCoverFromEnemyRadius 1500
+
+ evasiveCombatHealthChangeRateDiff -150
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_arc.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_arc.txt
new file mode 100644
index 000000000..37aec44e8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_arc.txt
@@ -0,0 +1,7 @@
+#base "npc_titan_stryder.txt"
+npc_titan_stryder_arc
+{
+ title #NPC_TITAN_STRYDER_ARC
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_stryder_arc"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall.txt
new file mode 100644
index 000000000..fc2111c1a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall.txt
@@ -0,0 +1,41 @@
+#base "npc_titan_stryder.txt"
+npc_titan_stryder_leadwall
+{
+ title #NPC_TITAN_STRYDER_LEADWALL
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_stryder_leadwall"
+
+ BehaviorSelector "behavior_titan_shotgun"
+
+ MeleeDamageMinHeavyArmor 750
+ MeleeDamageMaxHeavyArmor 750
+
+ chaseStopDist 200
+ chaseStopDistHeavyArmor 750
+ chaseTryRunningDodgeWhenAimedAtMinDist 700
+
+ circleStrafeDist 700
+ circleStrafeAngleIncrement 35
+
+ dodgePeriod 12 [$sp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 [$sp] // Don't dodge more than this many times in dodgePeriod
+
+ dodgePeriod 12 [$mp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 [$mp] // Don't dodge more than this many times in dodgePeriod
+
+ evasiveCombatHealthChangeRateDiff -350
+ aggresiveCombatHealthChangeRateDiff 50
+
+ //evasiveCombatHealthSegmentPct 0.0 [$mp] // no segment health regen in mp
+ //evasiveCombatHealthSegmentPct 0.4 [$sp] // if my segment is down to 40% be evasive
+
+ //aggressiveCombatHealthSegmentPct 0.0 [$mp] // no segment health regen in mp
+ //aggressiveCombatHealthSegmentPct 0.5 [$sp] // if enemy segement is down to 50% be aggressive
+
+ DefaultModelName "models/titans/light/titan_light_locust.mdl" [$mp]
+ DefaultModelName "models/titans/light/sp_titan_light_locust.mdl" [$sp]
+
+ aggressiveCombatTotalHealthDiffPct 0.2 // if my health+shield - enemy heatlh+shield is > 40% of my max health+shield
+
+ footstep_type "ronin"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_arc.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_arc.txt
new file mode 100644
index 000000000..bdbfb50d2
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_arc.txt
@@ -0,0 +1,11 @@
+#base "npc_titan_stryder_leadwall.txt"
+npc_titan_stryder_leadwall_arc
+{
+ title #NPC_TITAN_ARC
+
+ npc_titan_player_settings "titan_stryder_leadwall_arc"
+
+ titanCamoIndex 2
+ titanDecalIndex 0
+ titanSkinIndex 52
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_boss_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_boss_fd.txt
new file mode 100644
index 000000000..17f702ce6
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_boss_fd.txt
@@ -0,0 +1,45 @@
+#base "npc_titan_stryder_leadwall.txt"
+npc_titan_stryder_leadwall_boss_fd
+{
+ title #NPC_TITAN_STRYDER_LEADWALL
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_stryder_leadwall"
+
+ BehaviorSelector "behavior_titan_shotgun"
+
+ MeleeDamageMinHeavyArmor 750
+ MeleeDamageMaxHeavyArmor 750
+
+ chaseStopDist 200
+ chaseStopDistHeavyArmor 750
+ chaseTryRunningDodgeWhenAimedAtMinDist 700
+
+ circleStrafeDist 700
+ circleStrafeAngleIncrement 35
+
+ dodgePeriod 12 [$sp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 [$sp] // Don't dodge more than this many times in dodgePeriod
+
+ dodgePeriod 12 [$mp] // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 [$mp] // Don't dodge more than this many times in dodgePeriod
+
+ evasiveCombatHealthChangeRateDiff -350
+ aggresiveCombatHealthChangeRateDiff 50
+
+ //evasiveCombatHealthSegmentPct 0.0 [$mp] // no segment health regen in mp
+ //evasiveCombatHealthSegmentPct 0.4 [$sp] // if my segment is down to 40% be evasive
+
+ //aggressiveCombatHealthSegmentPct 0.0 [$mp] // no segment health regen in mp
+ //aggressiveCombatHealthSegmentPct 0.5 [$sp] // if enemy segement is down to 50% be aggressive
+
+ DefaultModelName "models/titans/light/titan_light_locust.mdl" [$mp]
+ DefaultModelName "models/titans/light/sp_titan_light_locust.mdl" [$sp]
+
+ aggressiveCombatTotalHealthDiffPct 0.2 // if my health+shield - enemy heatlh+shield is > 40% of my max health+shield
+
+ footstep_type "ronin"
+
+ titanCamoIndex -1
+ titanDecalIndex 12
+ titanSkinIndex 6
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_bounty.txt
new file mode 100644
index 000000000..c5892a673
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_bounty.txt
@@ -0,0 +1,5 @@
+#base "npc_titan_stryder_leadwall.txt"
+npc_titan_stryder_leadwall_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_shift_core.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_shift_core.txt
new file mode 100644
index 000000000..9729cb7e6
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_leadwall_shift_core.txt
@@ -0,0 +1,38 @@
+#base "npc_titan_stryder_leadwall.txt"
+npc_titan_stryder_leadwall_shift_core
+{
+ BehaviorSelector "behavior_titan_melee_core"
+
+ PainOnHeavyDamageThreshold 10000
+
+ chaseStopDist 100
+ chaseStopDistHeavyArmor 100
+
+ circleStrafeDist 150
+
+ dodgePeriod 5 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 1 // Don't dodge more than this many times in dodgePeriod
+
+ meleeInterval 0.2
+ meleeChargeInterval 0.2
+ meleeMaxCombo 8
+
+ MeleeDamageMin 500
+ MeleeDamageMax 500
+ MeleeDamageMinHeavyArmor 2000
+ MeleeDamageMaxHeavyArmor 2000
+ MeleeDamageRadius 150
+ MeleeDamageForce 3000000
+ MeleeChargeDamageForce 3200000
+ MeleeRange 200
+ MeleeChargeRange 300
+ MeleeChargeDamageRadius 150
+ MeleeChargeDamageHeight 150
+ MeleeChargeDamageMin 600
+ MeleeChargeDamageMax 600
+ MeleeChargeDamageMinHeavyArmor 2000
+ MeleeChargeDamageMaxHeavyArmor 2000
+
+ evasiveCombatHealthChangeRateDiff -600
+ aggresiveCombatHealthChangeRateDiff -400
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_northstar_prime_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_northstar_prime_bounty.txt
new file mode 100644
index 000000000..131a10854
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_northstar_prime_bounty.txt
@@ -0,0 +1,6 @@
+#base "npc_titan_stryder_sniper_bounty.txt"
+npc_titan_stryder_northstar_prime_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+ npc_titan_player_settings "titan_stryder_northstar_prime"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_rocketeer.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_rocketeer.txt
new file mode 100644
index 000000000..2fa07fa78
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_rocketeer.txt
@@ -0,0 +1,24 @@
+#base "npc_titan_stryder.txt"
+npc_titan_stryder_rocketeer
+{
+ title #NPC_TITAN_STRYDER_ROCKET
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_stryder_rocketeer"
+
+ BehaviorSelector "behavior_titan_rocketeer"
+
+ circleStrafeDist 1500
+ circleStrafeAngleIncrement 30
+
+ dodgeForwardThreshold 1200
+ dodgeBackwardThreshold 1600
+
+ longJumpCheckMinInterval 10
+ longJumpCheckMaxInterval 20
+ longJumpMinDist 1000
+ longJumpMaxDist 8000
+ longJumpHeight 360
+
+ DefaultModelName "models/titans/light/titan_light_raptor.mdl" [$mp]
+ DefaultModelName "models/titans/light/sp_titan_light_raptor.mdl" [$sp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_rocketeer_dash_core.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_rocketeer_dash_core.txt
new file mode 100644
index 000000000..b96916425
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_rocketeer_dash_core.txt
@@ -0,0 +1,11 @@
+#base "npc_titan_stryder_rocketeer.txt"
+npc_titan_stryder_rocketeer_dash_core
+{
+ circleStrafeDist 80
+
+ dodgePeriod 0.1 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 3 // Don't dodge more than this many times in dodgePeriod
+
+ evasiveCombatHealthChangeRateDiff -600
+ aggresiveCombatHealthChangeRateDiff -400
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper.txt
new file mode 100644
index 000000000..b23e493e6
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper.txt
@@ -0,0 +1,22 @@
+#base "npc_titan_stryder.txt"
+npc_titan_stryder_sniper
+{
+ title #NPC_TITAN_STRYDER_SNIPER
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_stryder_sniper"
+
+ BehaviorSelector "behavior_titan_sniper"
+
+ LookDistDefault_Combat 12000 // long range for S2S
+
+ longJumpCheckMinInterval 10
+ longJumpCheckMaxInterval 20
+ longJumpMinDist 500 // not to far so sniper can get use it as defense
+ longJumpMaxDist 8000
+ longJumpHeight 360
+
+ DefaultModelName "models/titans/light/titan_light_raptor.mdl" [$mp]
+ DefaultModelName "models/titans/light/sp_titan_light_raptor.mdl" [$sp]
+
+ footstep_type "northstar"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_boss_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_boss_fd.txt
new file mode 100644
index 000000000..90493d793
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_boss_fd.txt
@@ -0,0 +1,26 @@
+#base "npc_titan_stryder_sniper.txt"
+npc_titan_stryder_sniper_boss_fd
+{
+ title #NPC_TITAN_STRYDER_SNIPER
+ WeaponCapacity "FromLoadout"
+ npc_titan_player_settings "titan_stryder_sniper"
+
+ BehaviorSelector "behavior_titan_sniper"
+
+ LookDistDefault_Combat 12000 // long range for S2S
+
+ longJumpCheckMinInterval 10
+ longJumpCheckMaxInterval 20
+ longJumpMinDist 500 // not to far so sniper can get use it as defense
+ longJumpMaxDist 8000
+ longJumpHeight 360
+
+ DefaultModelName "models/titans/light/titan_light_raptor.mdl" [$mp]
+ DefaultModelName "models/titans/light/sp_titan_light_raptor.mdl" [$sp]
+
+ footstep_type "northstar"
+
+ titanCamoIndex 30
+ titanDecalIndex 10
+ titanSkinIndex 2
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_bounty.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_bounty.txt
new file mode 100644
index 000000000..26c2ddc8d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_bounty.txt
@@ -0,0 +1,5 @@
+#base "npc_titan_stryder_sniper.txt"
+npc_titan_stryder_sniper_bounty
+{
+ ui_targetinfo "ui/targetinfo_titan_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_fd.txt
new file mode 100644
index 000000000..2a156b9a6
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_stryder_sniper_fd.txt
@@ -0,0 +1,7 @@
+#base "npc_titan_stryder_sniper.txt"
+npc_titan_stryder_sniper_fd
+{
+ title #NPC_TITAN_SNIPER_FD
+ minGoalRadius 100
+ assaultHarvester 0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_titan_vanguard.txt b/Northstar.CustomServers/scripts/aisettings/npc_titan_vanguard.txt
new file mode 100644
index 000000000..09daba770
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_titan_vanguard.txt
@@ -0,0 +1,24 @@
+#base "npc_titan_buddy.txt"
+
+npc_titan_vanguard
+{
+ AIClass titan_buddy
+ footstep_type "buddy"
+ title "#NPC_TITAN_VANGUARD" [$mp]
+ title "#NPC_TITAN_VANGUARD_SP" [$sp]
+
+
+ ui_targetinfo "ui/targetinfo_titan" [$mp]
+ ui_targetinfo "ui/targetinfo_titan_sp" [$sp]
+
+ DefaultModelName "models/titans/buddy/titan_buddy.mdl"
+ Skin 1
+ npc_titan_player_settings "titan_buddy"
+ BehaviorSelector "behavior_titan_buddy"
+
+ MeleeChargeRange 250
+ dodgePeriod 8 // Don't dodge more than maxDodgePerPeriod within this time
+ maxDodgePerPeriod 2 // Don't dodge more than this many times in dodgePeriod
+
+ aiEnemy_priority 20 // lower than other titans
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_mega.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega.txt
new file mode 100644
index 000000000..f44fa5c0b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega.txt
@@ -0,0 +1,69 @@
+npc_turret_mega
+{
+ HullType "HULL_CUSTOM" // use OBB as hull size
+ AIClass turret
+ collideWithPlayer 1
+
+ BodyType other
+ ArmorType heavy [$sp]
+ ArmorType heavy [$mp]
+ SmartAmmoLockType large
+
+ titanStompable 0
+
+ title #NPC_TURRET_HEAVY
+ ui_targetinfo "ui/targetinfo_titan" [$mp]
+ ui_targetinfo "" [$sp]
+
+ mechanical 1
+
+ BaseClass "npc_turret_mega"
+ DrawEnemyHealthBar 1
+ DrawTargetHealthBar 1
+
+ DefaultModelName "models/Robots/turret_heavy/turret_heavy.mdl"
+ DefaultWeapon "mp_weapon_turretlaser_mega"
+
+ maxEnemyDist 0
+ maxEnemyDistHeavyArmor 5000
+ maxEnemyDistForAttacker 16000
+ maxEnemyDistHeavyArmorForAttacker 16000
+
+ FOV_Vert_Offset -20
+ FOV_Near_Dist 180 // use near values up to this distance
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 180
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 180
+ FOV_Idle_Far_Vert 180
+
+ FOV_Alert_Near_Horz 180
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 180
+ FOV_Alert_Far_Vert 180
+
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 180
+ FOV_Combat_Far_Vert 180
+
+ YawSpeed 25
+ AimAngularSpeed 30
+
+ meleeable 0
+
+ chasecamDistanceMax 500
+ chasecamMaxOrbitDepth 90
+ chasecamOffsetUp 75
+ chasecamOffsetRight 0
+
+ magneticRange 190
+
+ sound0 "MegaTurret_Laser_ClunkStart" // start sound
+ sound1 "MegaTurret_Laser_Servo" // pitch sound
+ sound2 "MegaTurret_Laser_Servo" // yaw sound
+
+ windup_time 0.0
+ start_active 0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_attrition.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_attrition.txt
new file mode 100644
index 000000000..8ea04829f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_attrition.txt
@@ -0,0 +1,18 @@
+#base "npc_turret_mega.txt"
+npc_turret_mega_attrition
+{
+ windup_time 0.0
+ Health 15000 [$mp]
+
+ maxEnemyDist 0
+ maxEnemyDistHeavyArmor 5000
+ maxEnemyDistForAttacker 16000
+ maxEnemyDistHeavyArmorForAttacker 16000
+
+ Health 5000 [$sp]
+ start_active 1
+ regen_rate 100
+ explode_on_death 1
+
+ ui_targetinfo "ui/targetinfo_megaturret_bounty" [$mp]
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_fortwar.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_fortwar.txt
new file mode 100644
index 000000000..4c06f5d6a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_fortwar.txt
@@ -0,0 +1,42 @@
+#base "npc_turret_mega.txt"
+npc_turret_mega_fortwar
+{
+ windup_time 0.0
+ Health 10000 //15000
+
+ maxEnemyDist 0
+ maxEnemyDistHeavyArmor 8000
+ //maxEnemyDistForAttacker 16000
+ //maxEnemyDistHeavyArmorForAttacker 16000
+
+ FOV_Vert_Offset -20
+ FOV_Near_Dist 360 // use near values up to this distance
+ FOV_Far_Dist 4028 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ YawSpeed 70 //50 //75
+ AimAngularSpeed 70 //60 //90
+
+ waitBetweenWeaponBurst 3
+
+ //faceEnemyToleranceAngle 5
+ //faceEnemyStrictToleranceAngle 5
+
+ onlyAttackableByTitans 1 // hack for fortwar mode
+
+ suppressLSP_duration 10 [$sp]
+ suppressLSP_duration 10 [$mp]
+
+ NoticeForgetPostTime 5.0 [$sp]
+ NoticeForgetPreTime 1.0 [$sp]
+
+ NoticeForgetPostTime 5.0 [$mp]
+ NoticeForgetPreTime 1.0 [$mp]
+
+ start_active 1
+ regen_rate 100
+ explode_on_death 1
+
+ ui_targetinfo "ui/targetinfo_megaturret_fw" [$mp]
+
+ smartAmmoLockFromTitansOnly 1
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_frontierdefense.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_frontierdefense.txt
new file mode 100644
index 000000000..1cb89fe6a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_frontierdefense.txt
@@ -0,0 +1,42 @@
+#base "npc_turret_mega.txt"
+npc_turret_mega_frontierdefense
+{
+ windup_time 0.0
+ Health 10000 //15000
+
+ maxEnemyDist 3000
+ maxEnemyDistHeavyArmor 8000
+ //maxEnemyDistForAttacker 16000
+ //maxEnemyDistHeavyArmorForAttacker 16000
+
+ FOV_Vert_Offset -20
+ FOV_Near_Dist 360 // use near values up to this distance
+ FOV_Far_Dist 4028 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ YawSpeed 70 //50 //75
+ AimAngularSpeed 70 //60 //90
+
+ waitBetweenWeaponBurst 3
+
+ //faceEnemyToleranceAngle 5
+ //faceEnemyStrictToleranceAngle 5
+
+ //onlyAttackableByTitans 1 // hack for fortwar mode
+
+ suppressLSP_duration 10 [$sp]
+ suppressLSP_duration 10 [$mp]
+
+ NoticeForgetPostTime 5.0 [$sp]
+ NoticeForgetPreTime 1.0 [$sp]
+
+ NoticeForgetPostTime 5.0 [$mp]
+ NoticeForgetPreTime 1.0 [$mp]
+
+ start_active 1
+ regen_rate 100
+ explode_on_death 1
+
+ ui_targetinfo "ui/targetinfo_megaturret_fw" [$mp]
+
+ smartAmmoLockFromTitansOnly 0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_nowindup.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_nowindup.txt
new file mode 100644
index 000000000..91fc60a65
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_nowindup.txt
@@ -0,0 +1,5 @@
+#base "npc_turret_mega.txt"
+npc_turret_mega_nowindup
+{
+ windup_time 0.0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_old.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_old.txt
new file mode 100644
index 000000000..ba3cff002
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_old.txt
@@ -0,0 +1,8 @@
+#base "npc_turret_mega.txt"
+npc_turret_mega_old
+{
+ DefaultModelName "models/turrets/turret_imc_lrg.mdl"
+ DefaultWeapon "mp_weapon_mega_turret"
+
+ windup_time 0.0
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_windup.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_windup.txt
new file mode 100644
index 000000000..adbd3e885
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_mega_windup.txt
@@ -0,0 +1,13 @@
+#base "npc_turret_mega.txt"
+npc_turret_mega_windup
+{
+ windup_time 0.0
+ Health 8000 [$mp]
+
+ maxEnemyDist 0
+
+ Health 5000 [$sp]
+ start_active 1
+ regen_rate 100
+ explode_on_death 1
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry.txt
new file mode 100644
index 000000000..f5dfa5f74
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry.txt
@@ -0,0 +1,72 @@
+npc_turret_sentry
+{
+ HullType "HULL_CUSTOM"
+ AIClass small_turret
+ collideWithPlayer 1 [$sp]
+ collideWithPlayer 0 [$mp]
+
+ BodyType other
+ ArmorType heavy
+ title #NPC_TURRET_SENTRY
+ ui_targetinfo "ui/targetinfo_remote_turret" [$mp]
+ ui_targetinfo "" [$sp]
+
+ Health 600 [$sp]
+ Health 1200 [$mp]
+
+ mechanical 1
+
+ turret_explode_on_death 1
+
+ SmartAmmoLockType any
+ smartAmmoLockAttachmentName0 turret_player_use
+
+ BaseClass "npc_turret_sentry"
+
+ DefaultModelName "models/robots/turret_plasma/turret_plasma.mdl"
+ DefaultWeapon "mp_weapon_yh803_bullet"
+ DrawEnemyHealthBar 0
+ DrawTargetHealthBar 0
+
+ maxEnemyDist 2000
+ maxEnemyDistHeavyArmor 2000
+ maxEnemyDistForAttacker 8000
+ maxEnemyDistHeavyArmorForAttacker 8000
+
+ FOV_Vert_Offset -20
+ FOV_Near_Dist 180 // use near values up to this distance
+ FOV_Far_Dist 1000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ LookDistDefault_Idle 2500
+ LookDistDefault_Alert 3000
+ LookDistDefault_Combat 4000
+
+ FOV_Idle_Near_Horz 360
+ FOV_Idle_Near_Vert 270
+ FOV_Idle_Far_Horz 270
+ FOV_Idle_Far_Vert 180
+
+ FOV_Alert_Near_Horz 360
+ FOV_Alert_Near_Vert 270
+ FOV_Alert_Far_Horz 270
+ FOV_Alert_Far_Vert 180
+
+ FOV_Combat_Near_Horz 360
+ FOV_Combat_Near_Vert 270
+ FOV_Combat_Far_Horz 270
+ FOV_Combat_Far_Vert 180
+
+ MaxTurretYaw 90
+ YawSpeed 45
+ AimAngularSpeed 45
+
+ meleeable 1
+
+ suppressLSP_duration 10
+
+ magneticRange 90
+
+ sound0 "Boost_Card_SentryTurret_Scanning_Start_3P" // start up
+ sound1 "MegaTurret_Servos_Tilt_LP" // silent by design
+ sound2 "Boost_Card_SentryTurret_Scanning_Loop_3P" // yaw
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_ap.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_ap.txt
new file mode 100644
index 000000000..9ec586110
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_ap.txt
@@ -0,0 +1,16 @@
+#base "npc_turret_sentry.txt"
+npc_turret_sentry_burn_card_ap
+{
+ DefaultModelName "models/Robots/turret_hitscan/turret_hitscan.mdl"
+ DefaultWeapon "mp_weapon_yh803_bullet"
+ title "#NPC_TURRET_SENTRY_AP"
+
+ maxEnemyDistHeavyArmor 0
+
+ Health 600 [$sp]
+ Health 1250 [$mp]
+
+ turret_lifetime 60.0 //Make sure to change #BURNMETER_AP_TURRETWEAPON_DESC
+
+ //MaxTurretYaw 360
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_ap_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_ap_fd.txt
new file mode 100644
index 000000000..16cce927a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_ap_fd.txt
@@ -0,0 +1,23 @@
+#base "npc_turret_sentry.txt"
+npc_turret_sentry_burn_card_ap_fd
+{
+ DefaultModelName "models/Robots/turret_hitscan/turret_hitscan.mdl"
+ DefaultWeapon "mp_weapon_yh803_bullet"
+ title "#NPC_TURRET_SENTRY"
+
+ maxEnemyDist 2000
+ maxEnemyDistHeavyArmor 1200
+ maxEnemyDistForAttacker 8000
+ maxEnemyDistHeavyArmorForAttacker 2000
+
+ YawSpeed 35
+ AimAngularSpeed 35
+
+ Health 600 [$sp]
+ Health 300 [$mp]
+
+ turret_lifetime 0.0
+ cleanup_between_rounds 0
+
+ MaxTurretYaw 360
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_at.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_at.txt
new file mode 100644
index 000000000..58a5c49ce
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_at.txt
@@ -0,0 +1,16 @@
+#base "npc_turret_sentry.txt"
+npc_turret_sentry_burn_card_at
+{
+ DefaultModelName "models/robots/turret_plasma/turret_plasma.mdl"
+ DefaultWeapon "mp_weapon_turretplasma"
+ title "#NPC_TURRET_SENTRY_AT"
+
+ maxEnemyDist 0
+
+ Health 600 [$sp]
+ Health 2500 [$mp]
+
+ turret_lifetime 60.0 //Make sure to change #BURNMETER_AT_TURRETWEAPON_DESC and #BURNMETER_AT_TURRETWEAPON_DESC_BOOST_ACTIVATION_TEXT
+
+ //MaxTurretYaw 360
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_at_fd.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_at_fd.txt
new file mode 100644
index 000000000..f120c79ba
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_burn_card_at_fd.txt
@@ -0,0 +1,17 @@
+#base "npc_turret_sentry.txt"
+npc_turret_sentry_burn_card_at_fd
+{
+ DefaultModelName "models/robots/turret_plasma/turret_plasma.mdl"
+ DefaultWeapon "mp_weapon_turretplasma"
+ title "#NPC_TURRET_SENTRY_PLASMA"
+
+ maxEnemyDist 2000
+
+ Health 600 [$sp]
+ Health 400 [$mp]
+
+ turret_lifetime 0.0 //Make sure to change #BURNMETER_AT_TURRETWEAPON_DESC and #BURNMETER_AT_TURRETWEAPON_DESC_BOOST_ACTIVATION_TEXT
+ cleanup_between_rounds 0
+
+ MaxTurretYaw 360
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_plasma.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_plasma.txt
new file mode 100644
index 000000000..bad53ace5
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_plasma.txt
@@ -0,0 +1,9 @@
+#base "npc_turret_sentry.txt"
+npc_turret_sentry_plasma
+{
+ DefaultModelName "models/robots/turret_plasma/turret_plasma.mdl"
+ DefaultWeapon "mp_weapon_turretplasma"
+ title "#NPC_TURRET_SENTRY_PLASMA"
+
+ //MaxTurretYaw 360
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_plasma_skyway.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_plasma_skyway.txt
new file mode 100644
index 000000000..1244c353c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_plasma_skyway.txt
@@ -0,0 +1,14 @@
+#base "npc_turret_sentry.txt"
+npc_turret_sentry_plasma_skyway
+{
+ DefaultModelName "models/robots/turret_plasma/turret_plasma.mdl"
+ DefaultWeapon "mp_weapon_turretplasma"
+ title "#NPC_TURRET_SENTRY_PLASMA"
+
+ //MaxTurretYaw 360
+
+ maxEnemyDist 8000
+ maxEnemyDistHeavyArmor 8000
+ maxEnemyDistForAttacker 8000
+ maxEnemyDistHeavyArmorForAttacker 8000
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_tactical_ability.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_tactical_ability.txt
new file mode 100644
index 000000000..ad6a15c0d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_tactical_ability.txt
@@ -0,0 +1,7 @@
+#base "npc_turret_sentry.txt"
+npc_turret_sentry_tactical_ability
+{
+ Health 600
+ title #NPC_TURRET_SENTRY_TACTICAL_ABILITY
+ //SecondaryWeapon "mp_weapon_engineer_turret_rocket"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_tday.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_tday.txt
new file mode 100644
index 000000000..c94ada19d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_tday.txt
@@ -0,0 +1,70 @@
+npc_turret_sentry_tday
+{
+ HullType "HULL_CUSTOM"
+ AIClass small_turret
+ collideWithPlayer 1
+
+ BodyType other
+ ArmorType heavy
+ title #NPC_TURRET_HEAVY
+ ui_targetinfo "ui/targetinfo_npc_basic" [$mp]
+ ui_targetinfo "" [$sp]
+
+ Health 600 [$sp]
+ Health 1200 [$mp]
+
+ mechanical 1
+ titanStompable 0
+
+ turret_explode_on_death 0
+
+ SmartAmmoLockType any
+
+ BaseClass "npc_turret_sentry"
+
+ DefaultModelName "models/Robots/turret_heavy/turret_heavy.mdl"
+ DefaultWeapon "mp_weapon_turret_tday"
+ DrawEnemyHealthBar 0
+ DrawTargetHealthBar 0
+
+ LookDistDefault_Idle 10000
+ LookDistDefault_Alert 10000
+ LookDistDefault_Combat 10000
+
+ FOV_Vert_Offset -20
+ FOV_Near_Dist 180 // use near values up to this distance
+ FOV_Far_Dist 10000 // use far values after this distance, interpolate horizontal in between, use far for vertical
+
+ FOV_Idle_Near_Horz 180
+ FOV_Idle_Near_Vert 180
+ FOV_Idle_Far_Horz 180
+ FOV_Idle_Far_Vert 180
+
+ FOV_Alert_Near_Horz 180
+ FOV_Alert_Near_Vert 180
+ FOV_Alert_Far_Horz 180
+ FOV_Alert_Far_Vert 180
+
+ FOV_Combat_Near_Horz 180
+ FOV_Combat_Near_Vert 180
+ FOV_Combat_Far_Horz 180
+ FOV_Combat_Far_Vert 180
+
+ MaxTurretYaw 90
+ YawSpeed 40
+ AimAngularSpeed 55
+
+ meleeable 0
+
+ suppressLSP_duration 10
+
+ magneticRange 90
+ aim_laser_disabled 1
+
+ sound0 "MegaTurret_Servos_Clunk" // start sound
+ sound1 "MegaTurret_Servos_Tilt_LP" // pitch sound
+ sound2 "MegaTurret_Servos_Rotate_LP" // yaw sound
+
+ windup_time 0.0
+ start_active 0
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_windup.txt b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_windup.txt
new file mode 100644
index 000000000..4d92d825e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/npc_turret_sentry_windup.txt
@@ -0,0 +1,8 @@
+#base "npc_turret_sentry.txt"
+npc_turret_sentry_windup
+{
+ health 600
+ MaxTurretYaw 180
+ windup_time 2.5
+ DefaultWeapon "mp_weapon_yh803"
+}
diff --git a/Northstar.CustomServers/scripts/aisettings/synced_melee_data.rson b/Northstar.CustomServers/scripts/aisettings/synced_melee_data.rson
new file mode 100644
index 000000000..f07e21c46
--- /dev/null
+++ b/Northstar.CustomServers/scripts/aisettings/synced_melee_data.rson
@@ -0,0 +1,628 @@
+/*
+Synced Melee between AI data.
+This list should be sorted by attacker/defender pairs.
+*/
+
+/*
+engageAngleThreshold - How much turntable blending the defender is willing to do.
+Larger values increase the likelihood that the melee occurs, but at the cost of more blending.
+*/
+
+
+
+
+
+
+// ----- grunt VS grunt ----- //
+// front
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_F_punch_A"
+ defenderSeq: "pt_synced_melee_F_punch_D"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_F_throw_A"
+ defenderSeq: "pt_synced_melee_F_throw_D"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_knife_kick_A"
+ defenderSeq: "pt_synced_melee_knife_kick_D"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_knife_struggle_A"
+ defenderSeq: "pt_synced_melee_knife_struggle_D"
+ engageAngleThreshold: 40
+}
+
+
+// side
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_R_disarm_A"
+ defenderSeq: "pt_synced_melee_R_disarm_D"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_L_tackle_A"
+ defenderSeq: "pt_synced_melee_L_tackle_D"
+ engageAngleThreshold: 40
+}
+
+
+// back
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_back_backstab_A"
+ defenderSeq: "pt_synced_melee_back_backstab_D"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_back_cheststab_A"
+ defenderSeq: "pt_synced_melee_back_cheststab_D"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_back_neckstab_A"
+ defenderSeq: "pt_synced_melee_back_neckstab_D"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "human"
+ attackerSeq: "pt_synced_melee_back_throw_A"
+ defenderSeq: "pt_synced_melee_back_throw_D"
+ engageAngleThreshold: 40
+}
+
+
+
+
+
+
+// ----- spectre VS spectre ----- //
+{
+ attackerClass: "spectre"
+ defenderClass: "spectre"
+ attackerSeq: "sp_stand_melee_A"
+ defenderSeq: "sp_stand_melee_V"
+ engageAngleThreshold: 70
+}
+
+{
+ attackerClass: "spectre"
+ defenderClass: "spectre"
+ attackerSeq: "sp_stand_melee_headrip_A"
+ defenderSeq: "sp_stand_melee_headrip_V"
+ engageAngleThreshold: 70
+}
+
+{
+ attackerClass: "spectre"
+ defenderClass: "spectre"
+ attackerSeq: "sp_stand_melee_behind_A"
+ defenderSeq: "sp_stand_melee_behind_V"
+ engageAngleThreshold: 70
+}
+
+{
+ attackerClass: "spectre"
+ defenderClass: "spectre"
+ attackerSeq: "sp_stand_melee_left_A"
+ defenderSeq: "sp_stand_melee_left_V"
+ engageAngleThreshold: 40
+}
+
+
+
+
+
+
+// ----- crawling stalker VS grunt ----- //
+{
+ attackerClass: "stalker_crawling"
+ defenderClass: "human"
+ attackerSeq: "st_synced_melee_F_grunt_crawl"
+ defenderSeq: "pt_synced_melee_F_grunt_crawl"
+ engageAngleThreshold: 70
+}
+
+
+
+
+
+
+// ----- stalker VS grunt ----- //
+{
+ attackerClass: "stalker"
+ defenderClass: "human"
+ attackerSeq: "st_synced_melee_F_chestpunch_A"
+ defenderSeq: "pt_synced_melee_F_chestpunch_D"
+ engageAngleThreshold: 70
+}
+
+{
+ attackerClass: "stalker"
+ defenderClass: "human"
+ attackerSeq: "st_synced_melee_F_facepunch"
+ defenderSeq: "pt_synced_melee_F_facepunch"
+ engageAngleThreshold: 90
+}
+
+{
+ attackerClass: "stalker"
+ defenderClass: "human"
+ attackerSeq: "st_synced_melee_B_chestpunch_A"
+ defenderSeq: "pt_synced_melee_B_chestpunch_D"
+ engageAngleThreshold: 90
+}
+
+{
+ attackerClass: "stalker"
+ defenderClass: "human"
+ attackerSeq: "st_synced_melee_F_spinepunch"
+ defenderSeq: "pt_synced_melee_B_spinepunch"
+ engageAngleThreshold: 40
+}
+
+
+
+
+
+
+// ----- spectre VS grunt ----- //
+{
+ attackerClass: "spectre"
+ defenderClass: "human"
+ attackerSeq: "sp_stand_melee_A"
+ defenderSeq: "pt_stand_melee_V"
+ engageAngleThreshold: 70
+}
+
+{
+ attackerClass: "spectre"
+ defenderClass: "human"
+ attackerSeq: "sp_stand_melee_headrip_A"
+ defenderSeq: "pt_stand_melee_headrip_V"
+ engageAngleThreshold: 70
+}
+
+{
+ attackerClass: "spectre"
+ defenderClass: "human"
+ attackerSeq: "sp_stand_melee_behind_A"
+ defenderSeq: "pt_stand_melee_behind_V"
+ engageAngleThreshold: 70
+}
+
+{
+ attackerClass: "spectre"
+ defenderClass: "human"
+ attackerSeq: "sp_stand_melee_left_A"
+ defenderSeq: "pt_stand_melee_left_V"
+ engageAngleThreshold: 40
+}
+
+
+
+
+
+
+// ----- prowler VS grunt ----- //
+{
+ attackerClass: "prowler"
+ defenderClass: "human"
+ attackerSeq: "pr_grunt_attack_F"
+ defenderSeq: "pt_prowler_attack_F"
+ engageAngleThreshold: 70
+}
+
+
+
+
+
+
+// ----- grunt VS pilot ----- //
+{
+ attackerClass: "human"
+ defenderClass: "pilot_assassin"
+ attackerSeq: "pt_synced_melee_B_neckstab_V"
+ defenderSeq: "ninja_synced_melee_B_neckstab_A"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "pilot_assassin"
+ attackerSeq: "pt_synced_melee_R_chestleap_V"
+ defenderSeq: "ninja_synced_melee_R_chestleap_A"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "pilot_assassin"
+ attackerSeq: "pt_synced_melee_F_chestslam_V"
+ defenderSeq: "ninja_synced_melee_F_chestslam_A"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "pilot_assassin"
+ attackerSeq: "pt_synced_melee_F_chestkick_V"
+ defenderSeq: "ninja_synced_melee_F_chestkick_A"
+ engageAngleThreshold: 40
+}
+
+{
+ attackerClass: "human"
+ defenderClass: "pilot_assassin"
+ attackerSeq: "pt_synced_melee_L_headcut_V"
+ defenderSeq: "ninja_synced_melee_L_headcut_A"
+ engageAngleThreshold: 40
+}
+
+
+
+
+
+
+// ----- titan VS grunt ----- //
+{
+ attackerClass: "titan"
+ defenderClass: "human"
+ attackerSeq: "at_synced_melee_F_toss_A"
+ defenderSeq: "pt_synced_melee_F_toss_V"
+ engageAngleThreshold: 60
+}
+
+{
+ attackerClass: "titan"
+ defenderClass: "human"
+ attackerSeq: "at_synced_melee_B_toss_A"
+ defenderSeq: "pt_synced_melee_B_toss_V"
+ engageAngleThreshold: 60
+}
+
+
+
+
+
+
+// ----- titan VS spectre ----- //
+{
+ attackerClass: "titan"
+ defenderClass: "spectre"
+ attackerSeq: "at_synced_melee_F_toss_A"
+ defenderSeq: "pt_synced_melee_F_toss_V"
+ engageAngleThreshold: 60
+}
+
+{
+ attackerClass: "titan"
+ defenderClass: "spectre"
+ attackerSeq: "at_synced_melee_B_toss_A"
+ defenderSeq: "pt_synced_melee_B_toss_V"
+ engageAngleThreshold: 60
+}
+
+
+
+
+
+
+// ----- titan VS titan ----- //
+{
+ attackerClass: "titan"
+ defenderClass: "titan"
+ attackerSeq: "mt_ai_sync_melee_kick_knockback_A"
+ defenderSeq: "mt_ai_sync_melee_kick_knockback_V"
+ engageAngleThreshold: 60
+ attackerSeq_sparselyAvailable: true
+ //defenderHealthThreshold_above: 0.3
+}
+
+{
+ attackerClass: "titan"
+ defenderClass: "titan"
+ attackerSeq: "mt_ai_synced_melee_kick_finisher_A"
+ defenderSeq: "mt_ai_synced_melee_kick_finisher_V"
+ engageAngleThreshold: 90
+ attackerSeq_sparselyAvailable: true
+ defenderHealthThreshold_below: 0.3
+}
+
+/*
+{
+ attackerClass: "titan"
+ defenderClass: "titan"
+ attackerSeq: "at_synced_melee_F_bash_A"
+ defenderSeq: "at_synced_melee_F_bash_V"
+ engageAngleThreshold: 60
+}
+
+{
+ attackerClass: "titan"
+ defenderClass: "titan"
+ attackerSeq: "at_synced_melee_F_punch_A"
+ defenderSeq: "at_synced_melee_F_punch_V"
+ engageAngleThreshold: 60
+}
+*/
+{
+ attackerClass: "titan"
+ defenderClass: "titan"
+ attackerSeq: "mt_ai_synced_finisher_B_armrip_A"
+ defenderSeq: "mt_ai_synced_finisher_B_armrip_V"
+ engageAngleThreshold: 60
+ defenderHealthThreshold_below: 0.3
+}
+
+{
+ attackerClass: "titan"
+ defenderClass: "titan"
+ attackerSeq: "mt_ai_synced_knockback_B_slam_A"
+ defenderSeq: "mt_ai_synced_knockback_B_slam_V"
+ engageAngleThreshold: 60
+}
+
+
+
+// ----- BT VS titan (melee) ----- //
+{
+ attackerClass: "titan_buddy"
+ defenderClass: "titan"
+ attackerSeq: "bt_synced_titan_uppercut_A"
+ defenderSeq: "titan_synced_bt_uppercut_D"
+ engageAngleThreshold: 60
+ defenderHealthThreshold_above: 0.3
+}
+
+{
+ attackerClass: "titan_buddy"
+ defenderClass: "titan"
+ attackerSeq: "bt_synced_titan_kickshoot_A"
+ defenderSeq: "titan_synced_bt_kickshoot_D"
+ engageAngleThreshold: 60
+ defenderHealthThreshold_above: 0.3
+}
+
+{
+ attackerClass: "titan_buddy"
+ defenderClass: "titan"
+ attackerSeq: "bt_synced_titan_2hitcombo_A"
+ defenderSeq: "titan_synced_bt_2hitcombo_D"
+ engageAngleThreshold: 60
+ defenderHealthThreshold_above: 0.3
+}
+
+
+
+
+
+
+// ----- BT VS titan (executions) ----- //
+{
+ attackerClass: "titan_buddy"
+ defenderClass: "titan"
+ attackerSeq: "bt_synced_titan_execute_kickshoot_AI_A"
+ defenderSeq: "titan_synced_bt_execute_kickshoot_AI_V"
+ engageAngleThreshold: 60
+ defenderHealthThreshold_below: 0.3
+}
+
+{
+ attackerClass: "titan_buddy"
+ defenderClass: "titan"
+ attackerSeq: "bt_synced_titan_execute_flip_takedown_AI_A"
+ defenderSeq: "titan_synced_bt_execute_flip_takedown_AI_V"
+ engageAngleThreshold: 60
+ defenderHealthThreshold_below: 0.3
+}
+
+{
+ attackerClass: "titan_buddy"
+ defenderClass: "titan"
+ attackerSeq: "bt_synced_titan_execute_pilot_rip_A"
+ defenderSeq: "titan_synced_bt_execute_pilot_rip_V"
+ engageAngleThreshold: 60
+ defenderHealthThreshold_below: 0.3
+}
+
+
+
+
+
+
+// ----- titan VS BT (melee) ----- //
+// generic titan
+{
+ attackerClass: "titan"
+ defenderClass: "titan_buddy"
+ attackerSeq: "at_synced_melee_F_punch_A"
+ defenderSeq: "at_synced_melee_F_punch_V"
+ engageAngleThreshold: 60
+}
+
+// medium titan only
+{
+ attackerClass: "titan"
+ defenderClass: "titan_buddy"
+ attackerSeq: "mt_ai_sync_melee_kick_knockback_A"
+ defenderSeq: "bt_ai_sync_melee_kick_knockback_V"
+ engageAngleThreshold: 60
+ attackerSeq_sparselyAvailable: true
+ defenderHealthThreshold_above: 0.3
+}
+
+
+
+
+
+
+// ----- titan VS BT (executions) ----- //
+// light titan only
+{
+ attackerClass: "titan"
+ defenderClass: "titan_buddy"
+ attackerSeq: "lt_execution_attacker_sword_01"
+ defenderSeq: "bt_execution_victim_sword_01"
+ engageAngleThreshold: 60
+ attackerSeq_sparselyAvailable: true
+ defenderHealthThreshold_below: 0.3
+}
+
+// medium titan only
+{
+ attackerClass: "titan"
+ defenderClass: "titan_buddy"
+ attackerSeq: "mt_ai_synced_melee_kick_finisher_A"
+ defenderSeq: "bt_ai_synced_melee_kick_finisher_V"
+ engageAngleThreshold: 60
+ attackerSeq_sparselyAvailable: true
+ defenderHealthThreshold_below: 0.3
+}
+
+{
+ attackerClass: "titan"
+ defenderClass: "titan_buddy"
+ attackerSeq: "mt_execution_attacker_laser"
+ defenderSeq: "bt_execution_victim_laser"
+ engageAngleThreshold: 60
+ attackerSeq_sparselyAvailable: true
+ defenderHealthThreshold_below: 0.3
+}
+
+{
+ attackerClass: "titan"
+ defenderClass: "titan_buddy"
+ attackerSeq: "mt_execution_attacker_tone"
+ defenderSeq: "bt_execution_victim_tone"
+ engageAngleThreshold: 60
+ attackerSeq_sparselyAvailable: true
+ defenderHealthThreshold_below: 0.3
+}
+
+// heavy titan only
+{
+ attackerClass: "titan"
+ defenderClass: "titan_buddy"
+ attackerSeq: "htPRED_MP_Sync_Execution_attacker"
+ defenderSeq: "t_MeleeExecuted_By_htPred"
+ engageAngleThreshold: 60
+ attackerSeq_sparselyAvailable: true
+ defenderHealthThreshold_below: 0.3
+}
+
+{
+ attackerClass: "titan"
+ defenderClass: "titan_buddy"
+ attackerSeq: "htThermite_MP_Sync_Execution_attacker"
+ defenderSeq: "t_MeleeExecuted_By_htThermite"
+ engageAngleThreshold: 60
+ attackerSeq_sparselyAvailable: true
+ defenderHealthThreshold_below: 0.3
+}
+
+
+
+
+
+
+// ----- BT VS prowler (executions) ----- //
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_front_01_A"
+ defenderSeq: "bt_synced_prowler_front_01_D"
+ engageAngleThreshold: 45
+}
+
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_front_02_A"
+ defenderSeq: "bt_synced_prowler_front_02_D"
+ engageAngleThreshold: 45
+}
+
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_45_left_A"
+ defenderSeq: "bt_synced_prowler_45_left_D"
+ engageAngleThreshold: 45
+}
+
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_45_right_A"
+ defenderSeq: "bt_synced_prowler_45_right_D"
+ engageAngleThreshold: 45
+}
+
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_90_left_A"
+ defenderSeq: "bt_synced_prowler_90_left_D"
+ engageAngleThreshold: 45
+}
+
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_90_right_A"
+ defenderSeq: "bt_synced_prowler_90_right_D"
+ engageAngleThreshold: 45
+}
+
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_135_left_A"
+ defenderSeq: "bt_synced_prowler_135_left_D"
+ engageAngleThreshold: 45
+}
+
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_135_right_A"
+ defenderSeq: "bt_synced_prowler_135_right_D"
+ engageAngleThreshold: 45
+}
+
+{
+ attackerClass: "prowler"
+ defenderClass: "titan_buddy"
+ attackerSeq: "prowler_synced_bt_back_01_A"
+ defenderSeq: "bt_synced_prowler_back_01_D"
+ engageAngleThreshold: 45
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_anim.gnut b/Northstar.CustomServers/scripts/vscripts/_anim.gnut
new file mode 100644
index 000000000..2ead1d306
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_anim.gnut
@@ -0,0 +1,1395 @@
+untyped
+
+global function Anim_Init
+
+global function FirstPersonSequence
+global function GetAnim
+global function HasAnim
+global function SetAnim
+global function PlayAnimTeleport
+global function GetAnimStartInfo
+
+global function PlayFPSAnim
+global function PlayFPSAnimShowProxy
+global function PlayFPSAnimTeleport
+global function PlayFPSAnimTeleportShowProxy
+
+global function PlayAnim
+global function PlayAnimGravity
+global function PlayAnimGravityClientSyncing
+global function PlayAnimRunGravity
+global function PlayAnimRun
+
+global function RunToAnimStart_Deprecated
+global function RunToAnimStartForced_Deprecated
+
+global function RunToAnimStartPos
+global function RunToAndPlayAnim
+global function RunToAndPlayAnimAndWait
+global function RunToAndPlayAnimGravity
+global function RunToAndPlayAnimGravityForced
+
+function Anim_Init()
+{
+ RegisterSignal( "NewViewAnim" )
+ RegisterSignal( "NewFirstPersonSequence" )
+ RegisterSignal( "ScriptAnimStop" )
+ RegisterSignal( "AnimEventKill" )
+
+ AddGlobalAnimEvent( "enable_weapon", GlobalAnimEvent_EnableWeapon )
+ AddGlobalAnimEvent( "disable_weapon", GlobalAnimEvent_DisableWeapon )
+ AddGlobalAnimEvent( "clear_parent", GlobalAnimEvent_ClearParent )
+ AddGlobalAnimEvent( "hide", GlobalAnimEvent_Hide )
+ AddGlobalAnimEvent( "show", GlobalAnimEvent_Show )
+ AddGlobalAnimEvent( "RecordOrigin", GlobalAnimEvent_RecordOrigin )
+ AddGlobalAnimEvent( "ShowFPSProxy", GlobalAnimEvent_ShowFPSProxy )
+ AddGlobalAnimEvent( "clear_anim_view_ent",GlobalAnimEvent_ClearAnimViewEntity )
+ AddGlobalAnimEvent( "scripted_death_to_ragdoll", GlobalAnimEvent_ScriptedDeathToRagdoll )
+ AddGlobalAnimEvent( "SetVelocity", GlobalAnimEvent_SetVelocity )
+ AddGlobalAnimEvent( "stance_kneel", GlobalAnimEvent_StanceKneel )
+ AddGlobalAnimEvent( "stance_kneeling", GlobalAnimEvent_StanceKneeling )
+ AddGlobalAnimEvent( "stance_stand", GlobalAnimEvent_StanceStand )
+ AddGlobalAnimEvent( "stance_standing", GlobalAnimEvent_StanceStanding )
+ AddGlobalAnimEvent( "enable_planting", GlobalAnimEvent_EnablePlanting )
+ AddGlobalAnimEvent( "kill", GlobalAnimEvent_Kill )
+ AddGlobalAnimEvent( "gib", GlobalAnimEvent_Gib )
+ AddGlobalAnimEvent( "titan_gib", GlobalAnimEvent_TitanGib )
+ AddGlobalAnimEvent( "EnableAimAssist", GlobalAnimEvent_EnableAimAssist )
+ AddGlobalAnimEvent( "DisableAimAssist", GlobalAnimEvent_DisableAimAssist )
+ AddGlobalAnimEvent( "give_ammo", GlobalAnimEvent_GiveAmmo )
+
+ #if SP
+ PrecacheWeapon( "mp_titanweapon_salvo_rockets" ) // used by bt_pod_fire_left/bt_pod_fire_right anim events. Only BT has these anim events.
+ AddGlobalAnimEvent( "bt_pod_fire_left", GlobalAnimEvent_BT_Pod_Left )
+ AddGlobalAnimEvent( "bt_pod_fire_right", GlobalAnimEvent_BT_Pod_Right )
+ #endif
+}
+
+void function GlobalAnimEvent_BT_Pod_Left( entity guy )
+{
+ BT_Pod( guy, "POD_L" )
+}
+
+void function GlobalAnimEvent_BT_Pod_Right( entity guy )
+{
+ BT_Pod( guy, "POD_R" )
+}
+
+void function BT_Pod( entity guy, string tag )
+{
+ entity oldOffhandWeapon = guy.GetOffhandWeapon( 0 )
+ guy.TakeOffhandWeapon( 0 )
+ guy.GiveOffhandWeapon( "mp_titanweapon_salvo_rockets", 0, [ "scripted_no_damage" ] )
+
+ //printt( tag )
+ entity newOffhandWeapon = guy.GetOffhandWeapon( 0 )
+ int attachID = guy.LookupAttachment( tag )
+ vector angles = guy.GetAttachmentAngles( attachID )
+ WeaponPrimaryAttackParams params
+ params.pos = guy.GetAttachmentOrigin( attachID )
+ params.dir = AnglesToForward( angles )
+ StartParticleEffectOnEntity( guy, GetParticleSystemIndex( $"P_muzzleflash_predator" ), FX_PATTACH_POINT_FOLLOW, attachID )
+
+ thread OnWeaponPrimaryAttack_titanweapon_salvo_rockets( newOffhandWeapon, params )
+
+ guy.TakeOffhandWeapon( 0 )
+
+ if ( oldOffhandWeapon )
+ guy.GiveOffhandWeapon( oldOffhandWeapon.GetWeaponClassName(), 0, oldOffhandWeapon.GetMods() )
+}
+
+void function GlobalAnimEvent_EnableWeapon( entity guy )
+{
+ if ( guy.IsPlayer() )
+ {
+ guy.EnableWeapon()
+ guy.EnableWeaponViewModel()
+ }
+ else
+ printt( "Warning: Tried to enable weapon on non player: " + guy )
+}
+
+void function GlobalAnimEvent_DisableWeapon( entity guy )
+{
+ if ( guy.IsPlayer() )
+ {
+ guy.DisableWeapon()
+ }
+ else
+ printt( "Warning: Tried to disable weapon on non player: " + guy )
+}
+
+void function GlobalAnimEvent_ClearParent( entity guy )
+{
+ guy.ClearParent()
+}
+
+void function GlobalAnimEvent_Hide( entity guy )
+{
+ guy.Hide()
+}
+
+void function GlobalAnimEvent_Show( entity guy )
+{
+ guy.Show()
+}
+
+void function GlobalAnimEvent_RecordOrigin( entity actor )
+{
+ if ( !actor.IsPlayer() )
+ return
+ if ( !( "recordedOrigin" in actor.s ) )
+ actor.s.recordedOrigin <- []
+
+ table record = {}
+ record.origin <- actor.GetOrigin()
+ record.time <- Time()
+
+ actor.s.recordedOrigin.append( record )
+}
+
+void function GlobalAnimEvent_ShowFPSProxy( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ local viewmodel = player.GetFirstPersonProxy()
+ viewmodel.ShowFirstPersonProxy()
+}
+
+void function GlobalAnimEvent_ClearAnimViewEntity( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ClearPlayerAnimViewEntity( player, 1.0 )
+}
+
+void function GlobalAnimEvent_ScriptedDeathToRagdoll( entity ent )
+{
+ ent.Die()
+ ent.SetContinueAnimatingAfterRagdoll( true )
+ ent.BecomeRagdoll( Vector(0,0,0), false )
+}
+
+void function GlobalAnimEvent_SetVelocity( entity actor )
+{
+ if ( !actor.IsPlayer() )
+ return
+ local record = null
+
+ if ( ( "recordedOrigin" in actor.s ) && actor.s.recordedOrigin.len() )
+ record = actor.s.recordedOrigin[ actor.s.recordedOrigin.len() - 1 ]
+
+ Assert( record, "anim had AE_SV_VSCRIPT_CALLBACK: SetVelocity, but no AE_SV_VSCRIPT_CALLBACK:RecordOrigin" )
+
+ local dir = Normalize( actor.GetOrigin() - record.origin )
+ local distance = Distance( actor.GetOrigin(), record.origin )
+ local time = Time() - record.time
+ if ( time <= 0 )
+ time = 0.001 // timescale bug?
+ local speed = distance / time
+
+ actor.SetVelocity( dir * speed )
+}
+
+void function GlobalAnimEvent_StanceKneel( entity guy )
+{
+ Assert( guy.IsTitan() )
+ Assert( guy.IsNPC() )
+ SetStanceKneel( guy.GetTitanSoul() )
+}
+
+void function GlobalAnimEvent_StanceKneeling( entity guy )
+{
+ Assert( guy.IsTitan() )
+ Assert( guy.IsNPC() )
+ SetStanceKneeling( guy.GetTitanSoul() )
+}
+
+void function GlobalAnimEvent_StanceStand( entity guy )
+{
+ Assert( guy.IsTitan() )
+ Assert( guy.IsNPC() )
+ SetStanceStand( guy.GetTitanSoul() )
+}
+
+void function GlobalAnimEvent_StanceStanding( entity guy )
+{
+ Assert( guy.IsTitan() )
+ Assert( guy.IsNPC() )
+ SetStanceStanding( guy.GetTitanSoul() )
+}
+
+void function GlobalAnimEvent_EnablePlanting( entity guy )
+{
+ if ( guy.IsNPC() || guy.IsPlayer() )
+ guy.Anim_EnablePlanting()
+ else
+ printt( "Warning: Tried to enable planting on " + guy )
+}
+
+void function GlobalAnimEvent_Kill( entity guy )
+{
+ if ( IsAlive( guy ) )
+ {
+ Signal( guy, "AnimEventKill" )
+ guy.TakeDamage( guy.GetMaxHealth() + 1, null, null, { damageSourceId=damagedef_suicide } )
+ guy.BecomeRagdoll( Vector( 0, 0, 0 ), false )
+ }
+}
+
+void function GlobalAnimEvent_Gib( entity guy )
+{
+ if ( IsAlive( guy ) )
+ {
+ Signal( guy, "AnimEventKill" )
+ guy.Gib( <0,0,100> )
+ }
+}
+
+void function GlobalAnimEvent_TitanGib( entity guy )
+{
+ if ( IsAlive( guy ) )
+ {
+ PlayTitanDeathFxUp( guy )
+
+ local entKVs = guy.CreateTableFromModelKeyValues()
+ local hitData = entKVs["hit_data"]
+
+ foreach ( bodyGroupName, bodyGroupData in hitData )
+ {
+ if ( !("blank" in bodyGroupData) )
+ continue
+
+ local bodyGroupIndex = guy.FindBodyGroup( bodyGroupName )
+ local stateCount = guy.GetBodyGroupModelCount( bodyGroupIndex )
+ guy.SetBodygroup( bodyGroupIndex, stateCount - 1 )
+ }
+ }
+}
+
+
+void function GlobalAnimEvent_EnableAimAssist( entity guy )
+{
+ if ( IsAlive( guy ) )
+ {
+ guy.SetAimAssistAllowed( true )
+ }
+}
+
+void function GlobalAnimEvent_DisableAimAssist( entity guy )
+{
+ if ( IsAlive( guy ) )
+ {
+ guy.SetAimAssistAllowed( false )
+ }
+}
+
+void function GlobalAnimEvent_GiveAmmo( entity guy )
+{
+ if ( IsAlive( guy ) )
+ {
+ array<entity> weapons = guy.GetMainWeapons()
+ if ( weapons.len() > 0 )
+ {
+ entity weapon = weapons[0]
+ if ( IsValid( weapon ) )
+ {
+ weapon.SetWeaponPrimaryClipCount( weapon.GetWeaponPrimaryClipCountMax() )
+ }
+ }
+ }
+}
+
+
+function GetAnim( guy, animation )
+{
+ if ( !( "anims" in guy.s ) )
+ return animation
+
+ if ( !( animation in guy.s.anims ) )
+ return animation
+
+ return guy.s.anims[ animation ]
+}
+
+function HasAnim( guy, animation )
+{
+ if ( !( "anims" in guy.s ) )
+ return false
+
+ return animation in guy.s.anims
+}
+
+function SetAnim( guy, name, animation )
+{
+ if ( !( "anims" in guy.s ) )
+ guy.s.anims <- {}
+
+ Assert( !( name in guy.s.anims ), guy + " already has set anim " + name )
+
+ guy.s.anims[ name ] <- animation
+}
+
+AnimRefPoint function GetAnimStartInfo( entity ent, string animAlias, animref )
+{
+ string animData = expect string( GetAnim( ent, animAlias ) )
+ AnimRefPoint animStartInfo = ent.Anim_GetStartForRefPoint( animData, animref.GetOrigin(), animref.GetAngles() )
+
+ return animStartInfo
+}
+
+
+function GetRefPosition( reference )
+{
+ Assert( reference.HasKey( "model" ) && reference.GetValueForModelKey() != $"", "Tried to play an anim relative to " + reference + " but it has no model/ref attachment." )
+
+ local position = {}
+ local attach_id
+ attach_id = reference.LookupAttachment( "REF" )
+
+ if ( attach_id )
+ {
+ position.origin <- reference.GetAttachmentOrigin( attach_id )
+ position.angles <- reference.GetAttachmentAngles( attach_id )
+ }
+
+ return position
+}
+
+// play the anim
+function __PlayAnim( guy, animation_name, reference = null, optionalTag = null, blendTime = DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+{
+ expect entity( guy )
+
+ Assert( IsValid_ThisFrame( guy ), "Invalid ent sent to PlayAnim " + animation_name )
+ local animation = GetAnim( guy, animation_name )
+
+ guy.SetNextThinkNow()
+
+ #if !DEV
+ if ( guy.IsNPC() && !guy.IsInterruptable() )
+ {
+ // better than nothing failsafe
+ guy.Signal( "OnAnimationInterrupted" )
+ guy.Signal( "OnAnimationDone" )
+ return
+ }
+ #endif
+
+ if ( guy.IsNPC() )
+ {
+ guy.EndSignal( "OnDeath" )
+ Assert( IsAlive( guy ), "Guy " + guy + " tried to play an anim, but it is not alive." )
+ }
+
+ if ( reference )
+ {
+ if ( reference == guy )
+ {
+ local position = GetRefPosition( reference )
+ local origin = position.origin
+ local angles = position.angles
+
+ if ( guy.IsNPC() )
+ guy.Anim_ScriptedPlayWithRefPoint( animation, origin, angles, blendTime )
+ else
+ guy.Anim_PlayWithRefPoint( animation, origin, angles, blendTime )
+
+ return
+ }
+
+ if ( optionalTag )
+ {
+ if ( typeof( reference ) == "vector" )
+ {
+ Assert( typeof( optionalTag ) == "vector", "Expected angles but got " + optionalTag )
+ if ( guy.IsNPC() )
+ guy.Anim_ScriptedPlayWithRefPoint( animation, reference, optionalTag, blendTime )
+ else
+ guy.Anim_PlayWithRefPoint( animation, reference, optionalTag, blendTime )
+ return
+ }
+
+ Assert( typeof( optionalTag ) == "string", "Passed invalid optional tag " + optionalTag )
+
+ if ( guy.GetParent() == reference )
+ {
+ if ( guy.IsNPC() )
+ guy.Anim_ScriptedPlay( animation )
+ else
+ guy.Anim_Play( animation )
+ }
+ else
+ {
+ local attachIndex = reference.LookupAttachment( optionalTag )
+ local origin = reference.GetAttachmentOrigin( attachIndex )
+ local angles = reference.GetAttachmentAngles( attachIndex )
+ if ( guy.IsNPC() )
+ {
+ //local origin = reference.GetOrigin()
+ //local angles = reference.GetAngles()
+ guy.Anim_ScriptedPlayWithRefPoint( animation, origin, angles, blendTime )
+ }
+ else
+ {
+ //local animStartPos = guy.Anim_GetStartForRefEntity_Old( animation, reference, optionalTag )
+ //local origin = animStartPos.origin
+ //local angles = animStartPos.angles
+ guy.Anim_PlayWithRefPoint( animation, origin, angles, blendTime )
+ }
+ }
+ return
+ }
+ }
+ else
+ {
+ Assert( optionalTag == null, "Reference was null, but optionalTag was not. Did you mean to set the tag?" )
+ }
+
+ if ( reference != null && guy.GetParent() == reference )
+ {
+ if ( guy.IsNPC() )
+ guy.Anim_ScriptedPlay( animation )
+ else
+ guy.Anim_Play( animation )
+
+ return
+ }
+
+ if ( !reference )
+ reference = guy
+
+ local origin = reference.GetOrigin()
+ local angles = reference.GetAngles()
+
+ if ( guy.IsNPC() )
+ guy.Anim_ScriptedPlayWithRefPoint( animation, origin, angles, blendTime )
+ else
+ guy.Anim_PlayWithRefPoint( animation, origin, angles, blendTime )
+
+}
+
+function TeleportToAnimStart( _guy, animation_name, reference, optionalTag = null, smooth = false )
+{
+ entity guy = expect entity( _guy )
+
+ Assert( reference, "NO reference" )
+ string animation = expect string( GetAnim( guy, animation_name ) )
+ AnimRefPoint animStartPos
+
+ if ( optionalTag )
+ {
+ if ( typeof( reference ) == "vector" )
+ {
+ Assert( typeof( optionalTag ) == "vector", "Expected angles but got " + optionalTag )
+ animStartPos = guy.Anim_GetStartForRefPoint( animation, reference, optionalTag )
+ }
+ else
+ {
+ animStartPos = guy.Anim_GetStartForRefEntity( animation, reference, optionalTag )
+ }
+ }
+ else
+ {
+ //printt( "Reference is " + reference )
+ //printt( "guy is " + guy )
+ //printt( "animation is " + animation )
+ local origin = reference.GetOrigin()
+ local angles = reference.GetAngles()
+ animStartPos = guy.Anim_GetStartForRefPoint( animation, origin, angles )
+ }
+ //Assert( animStartPos, "No animStartPos for " + animation + " on " + guy )
+
+ // hack! shouldn't need to do this
+ animStartPos.origin = ClampToWorldspace( animStartPos.origin )
+
+ if ( guy.GetParent() )
+ {
+ if ( smooth )
+ {
+ guy.SetAbsOriginSmooth( animStartPos.origin )
+ guy.SetAbsAnglesSmooth( animStartPos.angles )
+ }
+ else
+ {
+ guy.SetAbsOrigin( animStartPos.origin )
+ guy.SetAbsAngles( animStartPos.angles )
+ }
+ }
+ else
+ {
+ guy.SetOrigin( animStartPos.origin )
+ guy.SetAngles( animStartPos.angles )
+ }
+}
+
+// wait till arrive at goal and animation is done
+function RunToAndPlayAnimAndWait( entity guy, string animation_name, reference, bool doArrival = false, optionalTag = null )
+{
+ bool savedEnableFriendlyFollower = guy.ai.enableFriendlyFollower
+ guy.ai.enableFriendlyFollower = false
+
+ local allowFlee = guy.GetNPCFlag( NPC_ALLOW_FLEE )
+ local allowHandSignal = guy.GetNPCFlag( NPC_ALLOW_HAND_SIGNALS )
+ guy.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+
+ __RunToAndPlayAnim( guy, animation_name, reference, doArrival, optionalTag )
+
+ guy.WaitSignal( "OnFinishedAssault" )
+ WaittillAnimDone( guy )
+
+ guy.SetNPCFlag( NPC_ALLOW_FLEE, allowFlee )
+ guy.ai.enableFriendlyFollower = savedEnableFriendlyFollower
+}
+
+// wait till arrive at goal and start animation but don't wait until animation is done
+function RunToAndPlayAnim( entity guy, string animation_name, reference, bool doArrival = false, optionalTag = null )
+{
+ bool savedEnableFriendlyFollower = guy.ai.enableFriendlyFollower
+ guy.ai.enableFriendlyFollower = false
+
+ local allowFlee = guy.GetNPCFlag( NPC_ALLOW_FLEE )
+ local allowHandSignal = guy.GetNPCFlag( NPC_ALLOW_HAND_SIGNALS )
+ guy.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+
+ __RunToAndPlayAnim( guy, animation_name, reference, doArrival, optionalTag )
+
+ guy.WaitSignal( "OnFinishedAssault" )
+
+ guy.SetNPCFlag( NPC_ALLOW_FLEE, allowFlee )
+ guy.ai.enableFriendlyFollower = savedEnableFriendlyFollower
+}
+
+function RunToAndPlayAnimGravity( entity guy, string animation_name, reference, bool doArrival = false, optionalTag = null )
+{
+ local allowFlee = guy.GetNPCFlag( NPC_ALLOW_FLEE )
+ local allowHandSignal = guy.GetNPCFlag( NPC_ALLOW_HAND_SIGNALS )
+ guy.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+
+ float arrivalTolerance = guy.AssaultGetArrivalTolerance()
+ __RunToAndPlayAnim( guy, animation_name, reference, doArrival, optionalTag )
+
+ guy.WaitSignal( "OnFinishedAssault" )
+ guy.Anim_EnablePlanting()
+ WaittillAnimDone( guy )
+
+ guy.AssaultSetArrivalTolerance( arrivalTolerance )
+
+ guy.SetNPCFlag( NPC_ALLOW_HAND_SIGNALS, allowHandSignal )
+}
+
+
+function RunToAndPlayAnimGravityForced( entity guy, string animation_name, reference, bool doArrival = false, optionalTag = null )
+{
+ bool savedEnableFriendlyFollower = guy.ai.enableFriendlyFollower
+ guy.ai.enableFriendlyFollower = false
+
+ local allowFlee = guy.GetNPCFlag( NPC_ALLOW_FLEE )
+ local allowHandSignal = guy.GetNPCFlag( NPC_ALLOW_HAND_SIGNALS )
+ guy.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+
+ float arrivalTolerance = guy.AssaultGetArrivalTolerance()
+ __RunToAndPlayAnim( guy, animation_name, reference, doArrival, optionalTag )
+
+ guy.WaitSignal( "OnFinishedAssault" )
+ guy.Anim_EnablePlanting()
+ WaittillAnimDone( guy )
+
+ guy.AssaultSetArrivalTolerance( arrivalTolerance )
+
+ guy.SetNPCFlag( NPC_ALLOW_HAND_SIGNALS, allowHandSignal )
+ guy.ai.enableFriendlyFollower = savedEnableFriendlyFollower
+}
+
+function __RunToAndPlayAnim( entity guy, string animation_name, reference, bool doArrival, optionalTag )
+{
+ Assert( IsAlive( guy ) )
+ guy.Anim_Stop() // in case we were doing an anim already
+ guy.EndSignal( "OnDeath" )
+
+ string animation = expect string( GetAnim( guy, animation_name ) )
+ local origin, angles
+
+ if ( optionalTag )
+ {
+ if ( typeof( reference ) == "vector" )
+ {
+ Assert( typeof( optionalTag ) == "vector", "Expected angles but got " + optionalTag )
+ origin = reference
+ angles = optionalTag
+ }
+ else
+ {
+ local attach_id = reference.LookupAttachment( optionalTag )
+ origin = reference.GetAttachmentOrigin( attach_id )
+ angles = reference.GetAttachmentAngles( attach_id )
+ }
+ }
+ else
+ {
+ Assert( typeof( reference ) != "vector", "Expected an entity, but got an origin with no angles" )
+ origin = reference.GetOrigin()
+ angles = reference.GetAngles()
+ }
+
+ guy.AssaultPointToAnim( origin, angles, animation, doArrival, 4.0 )
+}
+
+// run to the place to start the anim, then play it
+function RunToAnimStart_Deprecated( guy, animation_name, reference = null, optionalTag = null )
+{
+ expect entity( guy )
+
+ Assert( IsAlive( guy ) )
+ guy.Anim_Stop() // in case we were doing an anim already
+ guy.EndSignal( "OnDeath" )
+
+ local allowFlee = guy.GetNPCFlag( NPC_ALLOW_FLEE )
+ local allowHandSignal = guy.GetNPCFlag( NPC_ALLOW_HAND_SIGNALS )
+
+ guy.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+
+ local animation = GetAnim( guy, animation_name )
+ local animStartPos
+
+ if ( optionalTag )
+ {
+ if ( typeof( reference ) == "vector" )
+ {
+ Assert( typeof( optionalTag ) == "vector", "Expected angles but got " + optionalTag )
+ animStartPos = guy.Anim_GetStartForRefPoint_Old( animation, reference, optionalTag )
+ }
+ else
+ {
+ animStartPos = guy.Anim_GetStartForRefEntity_Old( animation, reference, optionalTag )
+ }
+ }
+ else
+ {
+ local origin = reference.GetOrigin()
+ local angles = reference.GetAngles()
+ animStartPos = guy.Anim_GetStartForRefPoint_Old( animation, origin, angles )
+ }
+
+ guy.AssaultPoint( animStartPos.origin )
+ guy.WaitSignal( "OnFinishedAssault" )
+
+ guy.SetNPCFlag( NPC_ALLOW_FLEE, allowFlee )
+ guy.SetNPCFlag( NPC_ALLOW_HAND_SIGNALS, allowHandSignal )
+
+ local dist = Distance( animStartPos.origin, guy.GetOrigin() )
+ if ( dist > 8 )
+ {
+ //DebugDrawLine( animStartPos.origin, guy.GetOrigin(), 255, 150, 0, true, 60 )
+ printt( guy, " was ", dist, " units away from where he wanted to end his scripted sequence" )
+ }
+// printt( guy + " finished assault at dist ", Distance( animStartPos.origin, guy.GetOrigin() ) )
+// Assert( Distance( animStartPos.origin, guy.GetOrigin() ) < 32, guy + " finished assault but was " + ( Distance( animStartPos.origin, guy.GetOrigin() ) ) + " away from where he should have ended up." )
+}
+
+// only use this if you are OK with a frame pause before the start of the animation
+void function RunToAnimStartPos( entity guy, string animation_name, reference = null, bool doArrival = false, optionalTag = null )
+{
+ Assert( IsAlive( guy ) )
+ guy.Anim_Stop() // in case we were doing an anim already
+ guy.EndSignal( "OnDeath" )
+
+ local allowFlee = guy.GetNPCFlag( NPC_ALLOW_FLEE )
+ local allowHandSignal = guy.GetNPCFlag( NPC_ALLOW_HAND_SIGNALS )
+ local allowArrivals = guy.GetNPCMoveFlag( NPCMF_DISABLE_ARRIVALS )
+
+ if ( !doArrival )
+ {
+ // guy.DisableArrivalOnce( true )
+ guy.EnableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS )
+ }
+
+ guy.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+
+ local animation = GetAnim( guy, animation_name )
+ local animStartPos
+
+ if ( optionalTag )
+ {
+ if ( typeof( reference ) == "vector" )
+ {
+ Assert( typeof( optionalTag ) == "vector", "Expected angles but got " + optionalTag )
+ animStartPos = guy.Anim_GetStartForRefPoint_Old( animation, reference, optionalTag )
+ }
+ else
+ {
+ animStartPos = guy.Anim_GetStartForRefEntity_Old( animation, reference, optionalTag )
+ vector ornull clampedPos = NavMesh_ClampPointForAI( animStartPos.origin, guy )
+ if ( clampedPos != null )
+ animStartPos.origin = clampedPos
+ }
+ }
+ else
+ {
+ local origin = reference.GetOrigin()
+ local angles = reference.GetAngles()
+ animStartPos = guy.Anim_GetStartForRefPoint_Old( animation, origin, angles )
+ }
+
+ var fightRadius = guy.AssaultGetFightRadius()
+ var arrivalTolerance = guy.AssaultGetArrivalTolerance()
+ float runtoRadius = 61.16
+ guy.AssaultSetFightRadius( runtoRadius )
+ guy.AssaultSetArrivalTolerance( runtoRadius )
+
+ bool savedEnableFriendlyFollower = guy.ai.enableFriendlyFollower
+ guy.ai.enableFriendlyFollower = false
+
+ guy.AssaultPoint( animStartPos.origin )
+
+ //DebugDrawLine( guy.GetOrigin(), animStartPos.origin, 255, 0, 0, true, 20.0 )
+ //DebugDrawAngles( animStartPos.origin, animStartPos.angles )
+ //thread DebugAssaultEnt( guy, assaultEnt )
+ WaitSignal( guy, "OnFinishedAssault" )
+
+ //in case the scripter reset during run, we want to honor the intended change
+ if ( guy.AssaultGetFightRadius() == runtoRadius )
+ guy.AssaultSetFightRadius( fightRadius )
+
+ if ( guy.AssaultGetArrivalTolerance() == runtoRadius )
+ guy.AssaultSetArrivalTolerance( arrivalTolerance )
+
+ guy.SetNPCFlag( NPC_ALLOW_FLEE, allowFlee )
+ guy.SetNPCFlag( NPC_ALLOW_HAND_SIGNALS, allowHandSignal )
+ guy.SetNPCMoveFlag( NPCMF_DISABLE_ARRIVALS, allowArrivals )
+
+ guy.ai.enableFriendlyFollower = savedEnableFriendlyFollower
+}
+
+///////////////////////////////////////////////////////////////////
+// Deprecated, use RunToAndPlayAnim, otherwise there will be a gap between arriving at position and playing the animation
+function RunToAnimStartForced_Deprecated( entity guy, string animation_name, reference = null, optionalTag = null, bool disableArrival = true, disableAssaultAngles = false )
+{
+ Assert( IsAlive( guy ) )
+ guy.Anim_Stop() // in case we were doing an anim already
+ guy.EndSignal( "OnDeath" )
+
+ local allowFlee = guy.GetNPCFlag( NPC_ALLOW_FLEE )
+ local allowHandSignal = guy.GetNPCFlag( NPC_ALLOW_HAND_SIGNALS )
+ local allowArrivals = guy.GetNPCMoveFlag( NPCMF_DISABLE_ARRIVALS )
+
+ if ( disableArrival )
+ {
+ // guy.DisableArrivalOnce( true )
+ guy.EnableNPCMoveFlag( NPCMF_DISABLE_ARRIVALS )
+ }
+
+ guy.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+
+ local animation = GetAnim( guy, animation_name )
+ local animStartPos
+
+ if ( optionalTag )
+ {
+ if ( typeof( reference ) == "vector" )
+ {
+ Assert( typeof( optionalTag ) == "vector", "Expected angles but got " + optionalTag )
+ animStartPos = guy.Anim_GetStartForRefPoint_Old( animation, reference, optionalTag )
+ }
+ else
+ {
+ animStartPos = guy.Anim_GetStartForRefEntity_Old( animation, reference, optionalTag )
+ vector ornull clampedPos = NavMesh_ClampPointForAI( animStartPos.origin, guy )
+ if ( clampedPos != null )
+ animStartPos.origin = clampedPos
+ }
+ }
+ else
+ {
+ local origin = reference.GetOrigin()
+ local angles = reference.GetAngles()
+ animStartPos = guy.Anim_GetStartForRefPoint_Old( animation, origin, angles )
+ }
+
+ var fightRadius = guy.AssaultGetFightRadius()
+ var arrivalTolerance = guy.AssaultGetArrivalTolerance()
+ float runtoRadius = 61.16
+ guy.AssaultSetFightRadius( runtoRadius )
+ guy.AssaultSetArrivalTolerance( runtoRadius )
+
+ bool savedEnableFriendlyFollower = guy.ai.enableFriendlyFollower
+ guy.ai.enableFriendlyFollower = false
+
+ guy.AssaultPoint( animStartPos.origin )
+ if ( !disableAssaultAngles )
+ guy.AssaultSetAngles( animStartPos.angles, true )
+
+ //DebugDrawLine( guy.GetOrigin(), animStartPos.origin, 255, 0, 0, true, 20.0 )
+ //DebugDrawAngles( animStartPos.origin, animStartPos.angles )
+ //thread DebugAssaultEnt( guy, assaultEnt )
+ WaitSignal( guy, "OnFinishedAssault" )
+
+/*
+ if ( !disableAssaultAngles )
+ guy.AssaultSetAngles( animStartPos.angles, true )
+
+ guy.AssaultPointToAnim( animStartPos.origin, animation, 4.0 )
+ WaittillAnimDone( guy )
+// guy.WaitSignal( "OnFinishedAssault" )
+
+*/
+ //in case the scripter reset during run, we want to honor the intended change
+ if ( guy.AssaultGetFightRadius() == runtoRadius )
+ guy.AssaultSetFightRadius( fightRadius )
+
+ if ( guy.AssaultGetArrivalTolerance() == runtoRadius )
+ guy.AssaultSetArrivalTolerance( arrivalTolerance )
+
+ guy.SetNPCFlag( NPC_ALLOW_FLEE, allowFlee )
+ guy.SetNPCFlag( NPC_ALLOW_HAND_SIGNALS, allowHandSignal )
+ guy.SetNPCMoveFlag( NPCMF_DISABLE_ARRIVALS, allowArrivals )
+
+ guy.ai.enableFriendlyFollower = savedEnableFriendlyFollower
+}
+
+void function ShowEnt( entity viewmodel )
+{
+ if ( IsValid_ThisFrame( viewmodel ) )
+ viewmodel.ShowFirstPersonProxy()
+}
+
+// anim teleport
+function PlayAnimTeleport( guy, animation_name, reference = null, optionalTag = null, initialTime = -1.0, smooth = false )
+{
+ if ( type( guy ) == "array" || type( guy ) == "table" )
+ {
+ Assert( reference, "NO reference" )
+ local firstEnt = null
+ foreach ( ent in guy )
+ {
+ if ( !firstEnt )
+ firstEnt = ent
+
+ TeleportToAnimStart( ent, animation_name, reference, optionalTag, smooth )
+ __PlayAnim( ent, animation_name, reference, optionalTag, 0 )
+ if ( initialTime > 0.0 )
+ guy.Anim_SetInitialTime( initialTime )
+ }
+
+ WaittillAnimDone( expect entity( firstEnt ) )
+ }
+ else
+ {
+ if ( !reference )
+ reference = guy
+
+ TeleportToAnimStart( guy, animation_name, reference, optionalTag, smooth )
+ __PlayAnim( guy, animation_name, reference, optionalTag, 0 )
+ if ( initialTime > 0.0 )
+ guy.Anim_SetInitialTime( initialTime )
+ WaittillAnimDone( expect entity( guy ) )
+ }
+}
+
+// play the anim
+function PlayAnim( guy, animation_name, reference = null, optionalTag = null, blendTime = DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, initialTime = -1.0 )
+{
+ if ( type( guy ) == "array" )
+ {
+ foreach ( ent in guy )
+ {
+ __PlayAnim( ent, animation_name, reference, optionalTag, blendTime )
+ if ( initialTime > 0.0 )
+ guy.Anim_SetInitialTime( initialTime )
+ }
+
+ WaittillAnimDone( expect entity( guy[0] ) )
+ }
+ else
+ {
+ __PlayAnim( guy, animation_name, reference, optionalTag, blendTime )
+ if ( initialTime > 0.0 )
+ guy.Anim_SetInitialTime( initialTime )
+ WaittillAnimDone( expect entity( guy ) )
+ }
+}
+
+// play the anim
+function PlayAnimRun( entity guy, string animation_name, reference, bool doArrival, optionalTag = null, blendTime = DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+{
+ RunToAndPlayAnim( guy, animation_name, reference, doArrival, optionalTag )
+ WaitSignal( guy, "OnFinishedAssault" )
+ WaittillAnimDone( guy )
+}
+
+function PlayAnimRunGravity( entity guy, string animation_name, reference, bool doArrival, optionalTag = null, blendTime = DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+{
+ RunToAndPlayAnim( guy, animation_name, reference, doArrival, optionalTag )
+ WaitSignal( guy, "OnFinishedAssault" )
+ guy.Anim_EnablePlanting()
+ WaittillAnimDone( guy )
+}
+
+function PlayAnimGravityClientSyncing( guy, animation_name, reference = null, optionalTag = null, blendTime = DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+{
+ __PlayAnim( guy, animation_name, reference, optionalTag, blendTime )
+ guy.Anim_EnablePlanting()
+ WaittillAnimDone( expect entity( guy ) )
+}
+
+function PlayAnimGravity( guy, animation_name, reference = null, optionalTag = null, blendTime = DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+{
+ __PlayAnim( guy, animation_name, reference, optionalTag, blendTime )
+ guy.Anim_EnablePlanting()
+ WaittillAnimDone( expect entity( guy ) )
+}
+
+
+function CalcSequenceBlendTime( FirstPersonSequenceStruct sequence, entity player, entity ent = null )
+{
+ if ( sequence.blendTime != CALCULATE_SEQUENCE_BLEND_TIME )
+ return
+
+ sequence.blendTime = 0
+ if ( ent && sequence.thirdPersonAnim != "" )
+ {
+ local start
+ if ( sequence.attachment != "" )
+ {
+ start = player.Anim_GetStartForRefEntity_Old( sequence.thirdPersonAnim, ent, sequence.attachment )
+ }
+ else
+ {
+ start = {}
+ start.origin <- ent.GetOrigin()
+ start.angles <- ent.GetAngles()
+ }
+
+ if ( sequence.teleport )
+ {
+ player.SetAbsOrigin( start.origin )
+ player.SetAbsAngles( start.angles )
+ }
+ else
+ {
+ local dist = Distance( player.GetOrigin(), start.origin )
+ sequence.blendTime = GraphCapped( dist, 0, 350, 0.25, 0.9 )
+ }
+ }
+}
+
+void function PlayFPSAnim( entity player, string anim3rd, string anim1st = "", entity ref = null, string optionalTag = "", void functionref(entity) animView = null, float blendTime = DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, float initialTime = 0.0 )
+{
+ bool teleport = false
+ bool hideProxy = true
+ __PlayFPSAnimInternal( player, anim3rd, anim1st, ref, optionalTag, animView, blendTime, initialTime, teleport, hideProxy )
+}
+
+void function PlayFPSAnimShowProxy( entity player, string anim3rd, string anim1st = "", entity ref = null, string optionalTag = "", void functionref(entity) animView = null, float blendTime = DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME, float initialTime = 0.0 )
+{
+ bool teleport = false
+ bool hideProxy = false
+ __PlayFPSAnimInternal( player, anim3rd, anim1st, ref, optionalTag, animView, blendTime, initialTime, teleport, hideProxy )
+}
+
+void function PlayFPSAnimTeleport( entity player, string anim3rd, string anim1st = "", entity ref = null, string optionalTag = "", void functionref(entity) animView = null, float initialTime = 0.0 )
+{
+ bool teleport = true
+ bool hideProxy = true
+ float blendTime = 0.0
+ __PlayFPSAnimInternal( player, anim3rd, anim1st, ref, optionalTag, animView, blendTime, initialTime, teleport, hideProxy )
+}
+
+void function PlayFPSAnimTeleportShowProxy( entity player, string anim3rd, string anim1st = "", entity ref = null, string optionalTag = "", void functionref(entity) animView = null, float initialTime = 0.0 )
+{
+ bool teleport = true
+ bool hideProxy = false
+ float blendTime = 0.0
+ __PlayFPSAnimInternal( player, anim3rd, anim1st, ref, optionalTag, animView, blendTime, initialTime, teleport, hideProxy )
+}
+
+void function __PlayFPSAnimInternal( entity player, string anim3rd, string anim1st, entity ref, string optionalTag, void functionref(entity) animView, float blendTime, float initialTime, bool teleport, bool hideProxy )
+{
+ if ( animView == null )
+ animView = ViewConeRampFree
+
+ FirstPersonSequenceStruct sequence
+
+ sequence.firstPersonAnim = anim1st
+ sequence.thirdPersonAnim = anim3rd
+ sequence.attachment = optionalTag
+ sequence.viewConeFunction = animView
+ sequence.setInitialTime = initialTime
+ sequence.blendTime = blendTime
+ sequence.teleport = teleport
+ sequence.hideProxy = hideProxy
+
+ //hard coded in this function
+ sequence.noParent = true
+
+ FirstPersonSequence( sequence, player, ref )
+}
+
+void function FirstPersonSequence( FirstPersonSequenceStruct sequence, entity player, entity ent = null )
+{
+ player.Signal( "NewFirstPersonSequence" )
+ player.EndSignal( "NewFirstPersonSequence" )
+ player.EndSignal( "ScriptAnimStop" )
+
+ player.SetVelocity( <0,0,0> ) // fix this
+ if ( player.IsPlayer() && sequence.snapPlayerFeetToEyes )
+ {
+ player.SnapFeetToEyes()
+ }
+
+ //figure out if we have/should do a first person sequence and handle Spawn slots.
+ bool doFirstPersonAnim = sequence.firstPersonAnim != ""
+
+ entity firstPersonProxy
+ if ( doFirstPersonAnim )
+ {
+ Assert( player.IsPlayer(), player + " is not a player" )
+ firstPersonProxy = player.GetFirstPersonProxy()
+ if ( !IsValid( firstPersonProxy ) || !EntHasModelSet( firstPersonProxy ) )
+ {
+ doFirstPersonAnim = false;
+ }
+ }
+
+ if ( doFirstPersonAnim )
+ {
+ firstPersonProxy.ShowFirstPersonProxy()
+
+ if ( sequence.renderWithViewModels )
+ firstPersonProxy.RenderWithViewModels( true )
+ else
+ firstPersonProxy.RenderWithViewModels( false )
+
+ firstPersonProxy.ClearParent()
+ firstPersonProxy.SetAbsOrigin( player.GetOrigin() )
+ firstPersonProxy.SetAbsAngles( player.GetAngles() )
+
+ // Set anim view entity *after* setting the proxy's origin so that we calculate our initial view offset correctly (for lerping)
+ SetPlayerAnimViewEntity( player, firstPersonProxy )
+ firstPersonProxy.SetNextThinkNow()
+ SetForceDrawWhileParented( firstPersonProxy, true )
+ }
+ else if ( sequence.thirdPersonCameraAttachments.len() > 0 )
+ {
+ if ( player.IsPlayer() )
+ {
+ entity fpProxy = player.GetFirstPersonProxy() //Shouldn't ever need to show the first person proxy when doing thirdPersonCameraAttachments; hide it explicitly here to stop it from showing up when chaining animations
+ if ( IsValid( fpProxy ) )
+ fpProxy.HideFirstPersonProxy()
+
+ if ( sequence.thirdPersonCameraEntity )
+ {
+ SetPlayerAnimViewEntity( player, sequence.thirdPersonCameraEntity )
+ }
+ else
+ {
+ SetPlayerAnimViewEntity( player, player )
+ }
+
+ player.AnimViewEntity_SetThirdPersonCameraAttachments( sequence.thirdPersonCameraAttachments )
+ if ( sequence.thirdPersonCameraVisibilityChecks )
+ {
+ player.AnimViewEntity_EnableThirdPersonCameraVisibilityChecks()
+ }
+ }
+ }
+ else
+ {
+ if ( player.IsPlayer() )
+ {
+ ClearPlayerAnimViewEntity( player )
+ }
+ }
+
+ entity soul
+
+ if ( ent )
+ {
+ // the entity we are animating relative to may change during the animation
+ if ( IsSoul( ent ) )
+ {
+ soul = ent
+ ent = soul.GetTitan()
+ if ( !IsValid( ent ) )
+ return
+ }
+ else if ( HasSoul( ent ) )
+ {
+ soul = ent.GetTitanSoul()
+ }
+ }
+
+ CalcSequenceBlendTime( sequence, player, ent )
+
+ if ( player.IsPlayer() )
+ {
+ if ( sequence.teleport )
+ {
+ player.AnimViewEntity_SetLerpInTime( 0.0 )
+ player.PlayerCone_SetLerpTime( 0.0 )
+ player.SnapToAbsOrigin( player.GetOrigin() )
+ }
+ else
+ {
+ if ( sequence.noViewLerp || (sequence.blendTime <= 0.0) )
+ {
+ player.AnimViewEntity_SetLerpInTime( 0.0 )
+ player.PlayerCone_SetLerpTime( 0.0 )
+ }
+ else
+ {
+ player.AnimViewEntity_SetLerpInTime( sequence.blendTime )
+ player.PlayerCone_SetLerpTime( sequence.blendTime )
+ }
+ }
+
+ if ( sequence.thirdPersonCameraAttachments.len() == 0 )
+ {
+ if ( sequence.firstPersonBlendOutTime >= 0.0 )
+ {
+ player.AnimViewEntity_SetLerpOutTime( sequence.firstPersonBlendOutTime )
+ }
+ else
+ {
+ player.AnimViewEntity_SetLerpOutTime( 0.4 )
+ }
+ }
+
+ if ( !doFirstPersonAnim || !sequence.playerPushable )
+ {
+ player.SnapFeetToEyes()
+ }
+ }
+
+ if ( ent && !sequence.noParent )
+ {
+ local optionalTag
+ if ( sequence.attachment != "" )
+ {
+ optionalTag = sequence.attachment
+ }
+ else
+ {
+ optionalTag = ""
+ }
+
+ if ( player.GetParent() != ent )
+ {
+ // you could be parenting from one tag to another but we don't do
+ // that anywhere currently, and if we want to do it we can do some
+ // special stuff
+ player.SetParent( ent, optionalTag, false, sequence.blendTime )
+ }
+ }
+
+ if ( doFirstPersonAnim )
+ {
+ if ( sequence.teleport )
+ {
+ firstPersonProxy.SnapToAbsOrigin( player.GetOrigin() )
+ }
+
+ if ( sequence.playerPushable )
+ {
+ firstPersonProxy.SetParent( player, "", false )
+ }
+ else
+ {
+ firstPersonProxy.SetToSameParentAs( player )
+ }
+ }
+
+ if ( sequence.relativeAnim != "" )
+ {
+ if ( sequence.teleport )
+ {
+ thread PlayAnimGravityClientSyncing( ent, sequence.relativeAnim, null, null, 0.0 )
+ }
+ else
+ {
+ thread PlayAnimGravityClientSyncing( ent, sequence.relativeAnim )
+ }
+
+ if ( sequence.setInitialTime != 0.0 )
+ ent.Anim_SetInitialTime( sequence.setInitialTime )
+ }
+
+ if ( doFirstPersonAnim )
+ {
+ if ( ent )
+ {
+ thread PlayAnim( firstPersonProxy, sequence.firstPersonAnim, ent, sequence.attachment, sequence.blendTime )
+ }
+ else if ( sequence.playerPushable )
+ {
+ firstPersonProxy.Anim_Play( sequence.firstPersonAnim )
+ firstPersonProxy.Anim_DisableUpdatePosition()
+ }
+ else if ( sequence.gravity )
+ {
+ thread PlayAnimGravityClientSyncing( firstPersonProxy, sequence.firstPersonAnim, sequence.origin, sequence.angles, sequence.blendTime )
+ }
+ else
+ {
+ thread PlayAnim( firstPersonProxy, sequence.firstPersonAnim, sequence.origin, sequence.angles, sequence.blendTime )
+ }
+
+ // BROKEN - Anim_EnablePlanting() only works on players and NPCs
+ // if ( sequence.enablePlanting )
+ // {
+ // viewmodel.Anim_EnablePlanting()
+ // }
+
+ if ( sequence.setInitialTime != 0.0 )
+ {
+ firstPersonProxy.Anim_SetInitialTime( sequence.setInitialTime )
+ }
+
+ if ( sequence.useAnimatedRefAttachment )
+ {
+ firstPersonProxy.Anim_EnableUseAnimatedRefAttachmentInsteadOfRootMotion()
+ }
+
+ if ( sequence.hideProxy )
+ {
+ firstPersonProxy.HideFirstPersonProxy()
+ }
+ }
+
+ if ( sequence.thirdPersonAnim != "" )
+ {
+ if ( ent )
+ {
+ thread PlayAnim( player, sequence.thirdPersonAnim, ent, sequence.attachment, sequence.blendTime )
+ }
+ else if ( player.IsPlayer() && sequence.playerPushable )
+ {
+ player.Anim_Play( sequence.thirdPersonAnim )
+ player.Anim_DisableUpdatePosition()
+ }
+ else if ( sequence.gravity )
+ {
+ thread PlayAnimGravityClientSyncing( player, sequence.thirdPersonAnim, sequence.origin, sequence.angles, sequence.blendTime )
+ }
+ else
+ {
+ thread PlayAnim( player, sequence.thirdPersonAnim, sequence.origin, sequence.angles, sequence.blendTime )
+ }
+
+ if ( sequence.enablePlanting )
+ player.Anim_EnablePlanting()
+
+ if ( sequence.viewConeFunction != null )
+ {
+ if ( sequence.thirdPersonCameraAttachments.len() == 0 )
+ {
+ sequence.viewConeFunction( player )
+ }
+ }
+
+ if ( sequence.setInitialTime != 0.0 )
+ player.Anim_SetInitialTime( sequence.setInitialTime )
+
+ if ( sequence.useAnimatedRefAttachment )
+ player.Anim_EnableUseAnimatedRefAttachmentInsteadOfRootMotion()
+
+ WaittillAnimDone( player )
+ }
+
+ if ( doFirstPersonAnim && IsValid( firstPersonProxy ) && firstPersonProxy.Anim_IsActive() && !firstPersonProxy.IsSequenceFinished() )
+ {
+ WaittillAnimDone( firstPersonProxy )
+ }
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( player.IsPlayer() )
+ {
+ if ( !IsAlive( player ) )
+ return
+
+ if ( IsDisconnected( player ) )
+ return
+ }
+ else
+ if ( player.IsNPC() )
+ {
+ if ( !IsAlive( player ) )
+ return
+ }
+
+ // time passed
+ if ( soul )
+ {
+ if ( !IsValid( soul ) )
+ return
+
+ ent = soul.GetTitan()
+ if ( !IsAlive( ent ) )
+ return
+ }
+
+ if ( sequence.thirdPersonAnimIdle != "" )
+ {
+ //thread PlayAnim( player, sequence.thirdPersonAnimIdle, ent, sequence.attachment, 0 )
+ if ( ent )
+ {
+ thread PlayAnim( player, sequence.thirdPersonAnimIdle, ent, sequence.attachment, sequence.blendTime )
+ }
+ else if ( player.IsPlayer() && sequence.playerPushable )
+ {
+ player.Anim_Play( sequence.thirdPersonAnimIdle )
+ player.Anim_DisableUpdatePosition()
+ }
+ else
+ {
+ thread PlayAnim( player, sequence.thirdPersonAnimIdle, sequence.origin, sequence.angles, sequence.blendTime )
+ }
+ }
+
+ if ( sequence.firstPersonAnimIdle != "" )
+ {
+ firstPersonProxy = player.GetFirstPersonProxy()
+ firstPersonProxy.ShowFirstPersonProxy()
+
+ if ( IsValid( firstPersonProxy ) && EntHasModelSet( firstPersonProxy ) ) //JFS: Defensive fix for player not having view models sometimes
+ {
+ if ( sequence.renderWithViewModels )
+ firstPersonProxy.RenderWithViewModels( true )
+ else
+ firstPersonProxy.RenderWithViewModels( false )
+
+ SetPlayerAnimViewEntity( player, firstPersonProxy )
+ firstPersonProxy.SetNextThinkNow()
+
+ firstPersonProxy.SetAbsOrigin( player.GetOrigin() )
+ firstPersonProxy.SetAbsAngles( player.GetAngles() )
+
+ firstPersonProxy.Anim_Play( sequence.firstPersonAnimIdle )
+
+ if ( sequence.playerPushable )
+ {
+ firstPersonProxy.SetParent( player, "", false )
+ firstPersonProxy.Anim_DisableUpdatePosition()
+ }
+ else
+ {
+ firstPersonProxy.SetToSameParentAs( player )
+ }
+ }
+ }
+
+ if ( sequence.thirdPersonAnimIdle != "" && sequence.firstPersonAnimIdle != "" )
+ {
+ if ( sequence.viewConeFunction != null )
+ sequence.viewConeFunction( player )
+ }
+}
+
+
+function ClampPlayerViewCone( player )
+{
+ player.EndSignal( "OnDeath" )
+ player.PlayerCone_SetLerpTime( 0.0 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_auto_precache.gnut b/Northstar.CustomServers/scripts/vscripts/_auto_precache.gnut
new file mode 100644
index 000000000..75c7873ef
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_auto_precache.gnut
@@ -0,0 +1,771 @@
+#if DEV
+global function AutoPrecache_Init
+global function SetAutoPrecacheVersion
+global function MarkNPCForAutoPrecache
+global function AP_NPCSpawnerFound
+global function AP_PrecacheWeapon
+global function AP_PrecacheModel
+
+const int AUTO_PRECACHE_VERSION = 5
+
+global struct AutoPrecacheList
+{
+ array<string> weapons
+ table<string,int> weaponCount
+ table<string,array<entity> > npcSpawners
+ table<asset,string> autoPrecacheScript
+
+ array<asset> models
+}
+
+struct
+{
+ int autoPrecacheVersion
+ array<string> forceAutoPrecacheAiSettings
+
+ table<string,int> autoPrecacheFound_weapons
+ table<asset,bool> autoPrecacheFound_models
+ table<string,int> autoPrecacheFound_npcs
+
+} file
+
+void function AutoPrecache_Init()
+{
+ thread VerifyAutoPrecaches()
+
+ AddCallback_OnClientConnecting( AutoPrecache_OnPlayerConnect )
+}
+
+void function AutoPrecache_OnPlayerConnect( entity player )
+{
+ if ( Dev_CommandLineHasParm( "-autoprecache_all" ) )
+ {
+ switch ( GetMapName() )
+ {
+ case "sp_training":
+ ClientCommand( player, "map sp_crashsite" )
+ return
+
+ case "sp_crashsite":
+ ClientCommand( player, "map sp_sewers1" )
+ return
+
+ case "sp_sewers1":
+ ClientCommand( player, "map sp_boomtown" )
+ return
+
+ case "sp_boomtown":
+ ClientCommand( player, "map sp_boomtown_end" )
+ return
+
+ case "sp_boomtown_end":
+ ClientCommand( player, "map sp_boomtown_start" )
+ return
+
+ case "sp_boomtown_start":
+ ClientCommand( player, "map sp_hub_timeshift" )
+ return
+
+ case "sp_hub_timeshift":
+ ClientCommand( player, "map sp_timeshift_spoke02" )
+ return
+
+ case "sp_timeshift_spoke02":
+ ClientCommand( player, "map sp_beacon" )
+ return
+
+ case "sp_beacon":
+ ClientCommand( player, "map sp_beacon_spoke0" )
+ return
+
+ case "sp_beacon_spoke0":
+ ClientCommand( player, "map sp_tday" )
+ return
+
+ case "sp_tday":
+ ClientCommand( player, "map sp_s2s" )
+ return
+
+ case "sp_s2s":
+ ClientCommand( player, "map sp_skyway_v1" )
+ return
+
+ case "sp_skyway_v1":
+ ClientCommand( player, "map mp_grave" )
+ return
+
+ case "mp_grave":
+ ClientCommand( player, "quit" )
+ return
+
+ default:
+ ClientCommand( player, "map sp_training" )
+ return
+ }
+
+ }
+}
+
+void function VerifyAutoPrecaches()
+{
+ WaitEndFrame()
+ if ( !IsTestMap() )
+ Autoprecache_Verify()
+}
+
+void function Autoprecache_Verify()
+{
+ AutoPrecacheList autoPrecacheList = GenerateAutoPrecacheListForLevel()
+ if ( AutoPrecacheUpToDate( autoPrecacheList ) )
+ return
+
+ if ( !Dev_CommandLineHasParm( "-autoprecache_all" ) && !Dev_CommandLineHasParm( "-autoprecache" ) )
+ {
+ // dont really want mp generating auto precache if one map has an npc randomly placed in it, or an mp dedi going rogue
+ CodeWarning( "Entities have changed. Re-export auto precache script or run game with -autoprecache." )
+ return
+ }
+
+ ExportAutoPrecacheList( autoPrecacheList )
+
+ if ( Dev_CommandLineHasParm( "-autoprecache" ) )
+ {
+ Dev_CommandLineRemoveParm( "-autoprecache" )
+ ServerCommand( "reload" )
+ return
+ }
+
+ LevelTransitionStruct ornull trans = GetLevelTransitionStruct()
+ if ( trans != null )
+ {
+ expect LevelTransitionStruct( trans )
+ ChangeLevel( GetMapName(), trans )
+ return
+ }
+
+ LevelTransitionStruct trans2
+ ChangeLevel( GetMapName(), trans2 )
+
+}
+
+bool function IsTitanAISettings( string aiSettings )
+{
+ return Dev_GetAISettingByKeyField_Global( aiSettings, "aiclass" ) == "titan"
+}
+
+void function AddAutoPrecacheWeapon( AutoPrecacheList autoPrecacheList, string weapon )
+{
+ if ( weapon == "" )
+ return
+ autoPrecacheList.weapons.append( weapon )
+}
+
+void function FillAISettingsPrecaches( string aiSettings, AutoPrecacheList autoPrecacheList )
+{
+ if ( Dev_GetAISettingByKeyField_Global( aiSettings, "ForceAutoPrecacheDefaultWeapon" ) == 1 )
+ {
+ string weapon = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "DefaultWeapon" ) )
+ Assert( weapon != "", "Expected a weapon because ForceAutoPrecacheDefaultWeapon 1" )
+ AddAutoPrecacheWeapon( autoPrecacheList, weapon )
+ }
+
+ var grenadeWeapon = Dev_GetAISettingByKeyField_Global( aiSettings, "GrenadeWeaponName" )
+
+ if ( grenadeWeapon != "" )
+ {
+ expect string( grenadeWeapon )
+ AddAutoPrecacheWeapon( autoPrecacheList, grenadeWeapon )
+ }
+
+ var AdditionalScriptWeapon = Dev_GetAISettingByKeyField_Global( aiSettings, "AdditionalScriptWeapon" )
+ if ( AdditionalScriptWeapon != null )
+ {
+ expect string( AdditionalScriptWeapon )
+ AddAutoPrecacheWeapon( autoPrecacheList, AdditionalScriptWeapon )
+ }
+
+ var AdditionalAISettings = Dev_GetAISettingByKeyField_Global( aiSettings, "AdditionalAISettings" )
+ if ( AdditionalAISettings != null )
+ {
+ expect string( AdditionalAISettings )
+ FillAISettingsPrecaches( AdditionalAISettings, autoPrecacheList )
+ }
+
+ for ( int i = 0;; i++ )
+ {
+ asset gibModel = Dev_GetAISettingAssetByKeyField_Global( aiSettings, "GibModel" + i )
+ if ( gibModel == $"" )
+ break
+ autoPrecacheList.models.append( gibModel )
+ }
+
+ if ( IsTitanAISettings( aiSettings ) )
+ {
+ var titanSettings = Dev_GetAISettingByKeyField_Global( aiSettings, "npc_titan_player_settings" )
+ // is it a titan?
+ Assert( titanSettings != null, "No npc_titan_player_settings field in titan settings " + titanSettings )
+
+ // titans get their model from player model
+ expect string( titanSettings )
+ TitanLoadoutDef ornull titanLoadout = GetTitanLoadoutForColumn( "setFile", titanSettings )
+ if ( titanLoadout == null )
+ return
+
+ expect TitanLoadoutDef( titanLoadout )
+ AddTitanLoadoutToAutoPrecache( titanLoadout, autoPrecacheList )
+ }
+ else
+ {
+ // non-titan npcs get their model from their set file
+ string baseClass = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "BaseClass" ) )
+ array<string> keys = [ "DefaultModelName", "DefaultModelName_IMC", "DefaultModelName_MIL" ]
+
+ foreach ( key in keys )
+ {
+ var model = Dev_GetAISettingAssetByKeyField_Global( aiSettings, key )
+ if ( model == null )
+ continue
+
+ if ( model == $"" )
+ continue
+
+ expect asset( model )
+ autoPrecacheList.models.append( model )
+ }
+ }
+}
+
+void function AddTitanLoadoutToAutoPrecache( TitanLoadoutDef titanLoadout, AutoPrecacheList autoPrecacheList )
+{
+ array<string> weapons = GetWeaponsFromTitanLoadout( titanLoadout )
+ foreach ( weapon in weapons )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, weapon )
+ }
+
+ #if MP
+ //Precache both the prime and non-prime versions
+ string primeSetFile
+ string nonPrimeSetFile
+ string titanClass = titanLoadout.titanClass
+ Assert( titanClass != "" )
+ nonPrimeSetFile = GetSetFileForTitanClassAndPrimeStatus( titanClass, false )
+ AddTitanSetFileToAutoPrecache( nonPrimeSetFile, autoPrecacheList )
+
+ if( TitanClassHasPrimeTitan( titanClass ) )
+ {
+ primeSetFile = GetSetFileForTitanClassAndPrimeStatus( titanClass, true )
+ AddTitanSetFileToAutoPrecache( primeSetFile, autoPrecacheList )
+ }
+ #elseif SP
+ string nonPrimeSetFile = titanLoadout.setFile
+ //printt( "nonPrimeSetFile: " + nonPrimeSetFile )
+ AddTitanSetFileToAutoPrecache( nonPrimeSetFile, autoPrecacheList )
+ #endif
+}
+
+void function AddTitanSetFileToAutoPrecache( string setFile, AutoPrecacheList autoPrecacheList )
+{
+ asset model = GetPlayerSettingsAssetForClassName( setFile, "bodymodel" )
+ autoPrecacheList.models.append( model )
+
+ autoPrecacheList.models.extend( GetModelsFromSetFile_3rdPerson( setFile ) )
+
+ asset hatchmodel = Dev_GetPlayerSettingAssetByKeyField_Global( setFile, "hatchmodel" )
+ if ( hatchmodel != $"" )
+ {
+ autoPrecacheList.models.append( hatchmodel )
+ }
+
+ AddAutoPrecacheScript( autoPrecacheList, setFile )
+
+ #if MP
+ autoPrecacheList.models.extend( GetModelsFromSetFile( setFile ) )
+ #endif
+}
+
+void function MarkNPCForAutoPrecache( string aiSettings )
+{
+ Assert( !file.forceAutoPrecacheAiSettings.contains( aiSettings ), "Already marked " + aiSettings + " for auto precache" )
+ file.forceAutoPrecacheAiSettings.append( aiSettings )
+}
+
+bool function AutoPrecacheUpToDate( AutoPrecacheList autoPrecacheList )
+{
+ foreach ( weapon in autoPrecacheList.weapons )
+ {
+ if ( !( weapon in file.autoPrecacheFound_weapons ) )
+ {
+ CodeWarning( "Auto Precache Failed: Weapon " + weapon + " not found." )
+ return false
+ }
+
+ if ( file.autoPrecacheFound_weapons[ weapon ] != autoPrecacheList.weaponCount[ weapon ] )
+ {
+ CodeWarning( "Auto Precache Failed: Weapon " + weapon + " count changed from " + file.autoPrecacheFound_weapons[ weapon ] + " to " + autoPrecacheList.weaponCount[ weapon ] )
+ return false
+ }
+
+ if ( !WeaponIsPrecached( weapon ) )
+ {
+ CodeWarning( "Auto Precache Failed: Weapon " + weapon + " is not precached." )
+ return false
+ }
+ }
+
+ foreach ( model in autoPrecacheList.models )
+ {
+ if ( !( model in file.autoPrecacheFound_models ) )
+ {
+ CodeWarning( "Auto Precache Failed: Model " + model + " not found." )
+ return false
+ }
+
+ if ( !ModelIsPrecached( model ) )
+ {
+ CodeWarning( "Auto Precache Failed: Model " + model + " is not precached." )
+ return false
+ }
+
+ //TODO: I think this is correct but it would make SP's autoprecache stuff need to get updated. Not worth the risk for R2.
+ /*if ( file.autoPrecacheFound_models.len() != autoPrecacheList.models.len() )
+ {
+ CodeWarning( "Auto Precache Failed: autoPrecacheFound_models.len() is not the same as autoPrecacheList.models.len()" )
+ return false
+ }*/
+ }
+
+ foreach ( settings, spawners in autoPrecacheList.npcSpawners )
+ {
+ if ( !( settings in file.autoPrecacheFound_npcs ) )
+ {
+ CodeWarning( "Auto Precache Failed: NPC " + settings + " not found." )
+ return false
+ }
+
+ if ( file.autoPrecacheFound_npcs[ settings ] != spawners.len() )
+ {
+ CodeWarning( "Auto Precache Failed: NPC spawner " + settings + " count changed from " + file.autoPrecacheFound_npcs[ settings ] + " to " + spawners.len() )
+ return false
+ }
+ }
+
+ // verify up to date autoprecache
+ return file.autoPrecacheVersion == AUTO_PRECACHE_VERSION
+}
+
+
+void function SetAutoPrecacheVersion( int ver )
+{
+ file.autoPrecacheVersion = ver
+}
+
+
+void function FillFromNPCSettings( array<string> npcAiSettings, AutoPrecacheList autoPrecacheList )
+{
+ table<string,bool> filledAiSettings
+
+ foreach ( aiSettings in file.forceAutoPrecacheAiSettings )
+ {
+ FillAISettingsPrecaches( aiSettings, autoPrecacheList )
+ filledAiSettings[ aiSettings ] <- true
+ }
+
+ // precache weapons from the AI
+ foreach ( aiSettings in npcAiSettings )
+ {
+ // any of these spawned in the level?
+ string baseClass = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "BaseClass" ) )
+ array<entity> spawners = GetSpawnerArrayByClassName( baseClass )
+
+ bool titanSettings = IsTitanAISettings( aiSettings )
+
+ foreach ( spawner in spawners )
+ {
+ // this may be set on the entity in leveled
+ table kvs = spawner.GetSpawnEntityKeyValues()
+
+ string leveledAISettings
+ if ( "leveled_aisettings" in kvs )
+ {
+ leveledAISettings = expect string( kvs.leveled_aisettings )
+ }
+
+ // this finds all spawners with the same baseclass, so only check the spawners that match ai settings.
+ if ( leveledAISettings == "" )
+ {
+ if ( baseClass != aiSettings )
+ continue
+ }
+ else
+ {
+ if ( leveledAISettings != aiSettings )
+ continue
+ }
+
+ if ( !( aiSettings in filledAiSettings ) )
+ {
+ // found a spawner with these leveled AI settings
+ FillAISettingsPrecaches( aiSettings, autoPrecacheList )
+ filledAiSettings[ aiSettings ] <- true
+ }
+
+ if ( !( aiSettings in autoPrecacheList.npcSpawners ) )
+ autoPrecacheList.npcSpawners[ aiSettings ] <- []
+ autoPrecacheList.npcSpawners[ aiSettings ].append( spawner )
+
+ if ( "script_drone_type" in kvs )
+ {
+ string script_drone_type = expect string( kvs.script_drone_type )
+ if ( !( script_drone_type in filledAiSettings ) )
+ {
+ filledAiSettings[ script_drone_type ] <- true
+ FillAISettingsPrecaches( script_drone_type, autoPrecacheList )
+ }
+ }
+
+ if ( "additionalequipment" in kvs )
+ {
+ string additionalequipment = expect string( kvs.additionalequipment )
+ if ( LegalWeaponString( additionalequipment ) && additionalequipment.find( "auto_" ) != 0 )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, additionalequipment )
+ }
+ }
+
+ if ( "grenadeWeaponName" in kvs )
+ {
+ string grenadeWeaponName = expect string( kvs.grenadeWeaponName )
+ if ( LegalWeaponString( grenadeWeaponName ) )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, grenadeWeaponName )
+ }
+ }
+
+ if ( titanSettings )
+ {
+ int titanType = int( expect string( kvs.TitanType ) )
+ string leveledTitanLoadout = expect string( kvs.leveled_titan_loadout )
+
+ TitanLoadoutDef loadout = GetTitanLoadoutFromPlayerSetFile( leveledTitanLoadout )
+
+ array<string> weapons = GetWeaponsFromTitanLoadout( loadout )
+ foreach ( weapon in weapons )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, weapon )
+ }
+
+ #if SP
+ if ( titanType == TITAN_MERC )
+ {
+ // we have a boss!
+ string titanSettings = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "npc_titan_player_settings" ) )
+ string bossName = GetMercCharacterForSetFile( titanSettings )
+ BossTitanData bossTitanData = GetBossTitanData( bossName )
+ autoPrecacheList.models.append( bossTitanData.characterModel )
+ }
+ #endif
+ }
+ }
+ }
+}
+
+AutoPrecacheList function GenerateAutoPrecacheListForLevel()
+{
+ AutoPrecacheList autoPrecacheList
+
+ FillFromNPCSettings( GetAllNPCSettings(), autoPrecacheList )
+ array<string> deprecatedNPCs = GetAllDeprecatedNPCSettings()
+ FillFromNPCSettings( deprecatedNPCs, autoPrecacheList )
+
+ foreach ( aiSettings in deprecatedNPCs )
+ {
+ if ( !( aiSettings in autoPrecacheList.npcSpawners ) )
+ continue
+ foreach ( spawner in autoPrecacheList.npcSpawners[ aiSettings ] )
+ {
+ CodeWarning( "Found deprecated NPC " + aiSettings + " at " + spawner.GetSpawnEntityKeyValues().origin )
+ }
+ }
+
+ foreach ( npc in GetNPCArray() )
+ {
+ if ( !IsValid( npc ) )
+ continue
+
+ string weapon = expect string( npc.kv.additionalequipment )
+ if ( LegalWeaponString( weapon ) )
+ AddAutoPrecacheWeapon( autoPrecacheList, weapon )
+// string weapon = npc.AISetting_GetDefaultWeapon()
+// if ( LegalWeaponString( weapon ) )
+// weapons.append( weapon )
+
+ if ( npc.HasKey( "grenadeWeaponName" ) )
+ {
+ string grenadeWeaponName = expect string( npc.kv.grenadeWeaponName )
+ if ( LegalWeaponString( grenadeWeaponName ) )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, grenadeWeaponName )
+ }
+ }
+
+ string grenadeWeapon = npc.AISetting_GetGrenadeWeapon()
+ if ( grenadeWeapon != "" )
+ AddAutoPrecacheWeapon( autoPrecacheList, grenadeWeapon )
+
+ var AdditionalScriptWeapon = npc.Dev_GetAISettingByKeyField( "AdditionalScriptWeapon" )
+ if ( AdditionalScriptWeapon != null )
+ {
+ expect string( AdditionalScriptWeapon )
+ AddAutoPrecacheWeapon( autoPrecacheList, AdditionalScriptWeapon )
+ }
+ }
+
+ #if SP
+ LeveledScriptedWeapons leveledScriptedWeapons = GetAllLeveledScriptWeapons()
+ foreach ( weaponClass, _ in leveledScriptedWeapons.foundScriptWeapons )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, weaponClass )
+ }
+
+ array<string> weapons
+
+ weapons = GetNPCDefaultWeapons()
+ foreach ( weapon in weapons )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, weapon )
+ }
+
+ PilotLoadoutDef loadout = GetPilotLoadoutForCurrentMapSP()
+ weapons = GetWeaponsFromPilotLoadout( loadout )
+ foreach ( weapon in weapons )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, weapon )
+ }
+
+ autoPrecacheList.models.extend( GetModelsFromSetFile( loadout.setFile ) )
+ AddAutoPrecacheScript( autoPrecacheList, loadout.setFile )
+
+ TitanLoadoutDef titanLoadout = GetTitanLoadoutForCurrentMap()
+ autoPrecacheList.models.extend( GetModelsFromSetFile( titanLoadout.setFile ) )
+ AddAutoPrecacheScript( autoPrecacheList, titanLoadout.setFile )
+ #endif
+
+
+ #if MP
+ array<string> pilotTypes = GetAllItemRefsOfType( eItemTypes.PILOT_SUIT )
+
+ foreach ( suit in pilotTypes )
+ {
+ string suitMale = GetSuitAndGenderBasedSetFile( suit, "race_human_male" )
+ autoPrecacheList.models.extend( GetModelsFromSetFile( suitMale ) )
+ AddAutoPrecacheScript( autoPrecacheList, suitMale )
+
+ string suitFemale = GetSuitAndGenderBasedSetFile( suit, "race_human_female" )
+ autoPrecacheList.models.extend( GetModelsFromSetFile( suitFemale ) )
+ AddAutoPrecacheScript( autoPrecacheList, suitFemale )
+ }
+ #endif
+
+ array<TitanLoadoutDef> titanLoadouts = GetAllowedTitanLoadouts()
+
+ foreach ( loadout in titanLoadouts )
+ {
+ #if MP
+ // in sp we dont want all the extra cockpit models and whatnot
+ AddTitanLoadoutToAutoPrecache( loadout, autoPrecacheList )
+ #endif
+
+ #if SP
+ // in sp it would be good to get away from giving all weapons on all levels
+ weapons = GetWeaponsFromTitanLoadout( loadout )
+ foreach ( weapon in weapons )
+ {
+ AddAutoPrecacheWeapon( autoPrecacheList, weapon )
+ }
+ #endif
+ }
+
+ AutoPrecache_InitFlightpathShared( autoPrecacheList )
+
+ autoPrecacheList.weapons.sort( SortStringAlphabetize )
+
+ table<string,int> weaponCount
+ foreach ( weapon in autoPrecacheList.weapons )
+ {
+ if ( !( weapon in weaponCount ) )
+ weaponCount[ weapon ] <- 0
+ weaponCount[ weapon ]++
+ }
+
+ autoPrecacheList.weaponCount = weaponCount
+
+ RemoveDupesFromSorted_String( autoPrecacheList.weapons )
+
+ autoPrecacheList.models.sort( SortAssetAlphabetize )
+ RemoveDupesFromSorted_Asset( autoPrecacheList.models )
+
+ return autoPrecacheList
+}
+
+void function AddAutoPrecacheScript( AutoPrecacheList autoPrecacheList, string settings )
+{
+ var autoprecache = Dev_GetPlayerSettingByKeyField_Global( settings, "autoprecache_script" )
+ if ( autoprecache == null )
+ return
+
+ expect string( autoprecache )
+ Assert( autoprecache != "" )
+
+ asset bodyModel = GetPlayerSettingsAssetForClassName( settings, "bodymodel" )
+ autoPrecacheList.autoPrecacheScript[ bodyModel ] <- autoprecache
+}
+
+void function AP_NPCSpawnerFound( string settings, int count )
+{
+ file.autoPrecacheFound_npcs[ settings ] <- count
+}
+
+void function AP_PrecacheWeapon( string weapon, int count )
+{
+ file.autoPrecacheFound_weapons[ weapon ] <- count
+
+ PrecacheWeapon( weapon )
+}
+
+void function AP_PrecacheModel( asset model )
+{
+ file.autoPrecacheFound_models[ model ] <- true
+
+ PrecacheModel( model )
+}
+
+void function ExportAutoPrecacheList( AutoPrecacheList autoPrecacheList )
+{
+ string mapName
+ #if SP
+ mapName = GetMapName().toupper()
+ #endif
+
+ #if MP
+ mapName = "MP"
+ #endif
+
+ // Write function open
+ DevTextBufferClear()
+ // Write verification call
+
+ DevTextBufferWrite( "global function " + mapName + "_AutoPrecache\n\n" )
+ DevTextBufferWrite( "void function " + mapName + "_AutoPrecache()\n" )
+ DevTextBufferWrite( "{\n" )
+
+ DevTextBufferWrite( "#if DEV\n" )
+
+ DevTextBufferWrite( " #if SERVER\n" )
+ DevTextBufferWrite( " SetAutoPrecacheVersion( " + AUTO_PRECACHE_VERSION + " )\n" )
+
+ DevTextBufferWrite( " // NPC spawners found:\n" )
+ array<string> spawnerNames
+ foreach ( aiSettings, spawnerArray in autoPrecacheList.npcSpawners )
+ {
+ spawnerNames.append( aiSettings )
+ }
+ spawnerNames.sort( SortStringAlphabetize )
+
+ foreach ( aiSettings in spawnerNames )
+ {
+ array<entity> spawnerArray = autoPrecacheList.npcSpawners[ aiSettings ]
+ DevTextBufferWrite( " AP_NPCSpawnerFound( \"" + aiSettings + "\", " + spawnerArray.len() + " )\n" )
+ }
+ DevTextBufferWrite( " #endif\n" )
+ DevTextBufferWrite( "\n" )
+
+ foreach ( weapon in autoPrecacheList.weapons )
+ {
+ int count = autoPrecacheList.weaponCount[ weapon ]
+ DevTextBufferWrite( " AP_PrecacheWeapon( \"" + weapon + "\", " + count + " )\n" )
+ }
+
+ foreach ( model in autoPrecacheList.models )
+ {
+ DevTextBufferWrite( " AP_PrecacheModel( " + model + " )\n" )
+ }
+
+ DevTextBufferWrite( "#endif\n\n" )
+
+ DevTextBufferWrite( "#if !DEV\n" )
+
+ DevTextBufferWrite( "\n" )
+
+ foreach ( weapon in autoPrecacheList.weapons )
+ {
+ int count = autoPrecacheList.weaponCount[ weapon ]
+ DevTextBufferWrite( " PrecacheWeapon( \"" + weapon + "\" )\n" )
+ }
+
+ foreach ( model in autoPrecacheList.models )
+ {
+ DevTextBufferWrite( " PrecacheModel( " + model + " )\n" )
+ }
+
+ DevTextBufferWrite( "#endif\n\n" )
+
+ DevTextBufferWrite( "#if CLIENT\n" )
+
+ array<string>[4] titanModelAssets
+ foreach ( model, script in autoPrecacheList.autoPrecacheScript )
+ {
+ switch ( script )
+ {
+ case "atlas":
+ titanModelAssets[ 0 ].append( " ClTitanAtlas_Init( " + model + " )\n" )
+ break
+
+ case "ogre":
+
+ titanModelAssets[ 1 ].append( " ClTitanOgre_Init( " + model + " )\n" )
+ break
+
+ case "stryder":
+ titanModelAssets[ 2 ].append( " ClTitanStryder_Init( " + model + " )\n" )
+ break
+
+ case "buddy":
+ titanModelAssets[ 2 ].append( " ClTitanBuddy_Init( " + model + " )\n" )
+ break
+
+ default:
+ Assert( 0, "Unknown autoprecache_script key " + script )
+ break
+ }
+ }
+
+ foreach( arrayOfAsset in titanModelAssets ) //Sort output so exported precache file can be diffed easily
+ {
+ arrayOfAsset.sort( SortStringAlphabetize )
+ foreach( assetElement in arrayOfAsset )
+ {
+ DevTextBufferWrite( assetElement )
+ }
+ }
+
+ DevTextBufferWrite( "#endif\n" )
+
+ // Write function close
+ DevTextBufferWrite( "}\n\n" )
+
+
+ #if SP
+ string filename = "scripts/vscripts/sp/autoprecache/" + mapName + "_autoprecache.nut"
+ #endif
+
+ #if MP
+ string filename = "scripts/vscripts/mp/" + mapName + "_autoprecache.nut"
+ #endif
+ DevP4Checkout( filename )
+ DevTextBufferDumpToFile( filename )
+ DevP4Add( filename )
+ printt( "Wrote " + filename )
+}
+
+#endif \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut b/Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut
new file mode 100644
index 000000000..30758becd
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_bubble_shield.gnut
@@ -0,0 +1,524 @@
+global function BubbleShield_Init
+
+global function CreateBubbleShield
+global function IsTitanWithinBubbleShield
+global function TitanHasBubbleShieldWeapon
+global function LetTitanPlayerShootThroughBubbleShield
+global function CreateGenericBubbleShield
+global function CreateParentedBubbleShield
+
+global function WaitUntilTitanStandsOrDies
+global function DestroyBubbleShield
+global function CreateBubbleShieldWithSettings
+
+const float SHIELD_TITAN_DAMAGE_FLOOR = 250.0
+const float SHIELD_TITAN_DAMAGE_CEILING = 16000 //Some arbitrarily large number really
+const float SHIELD_PILOT_DAMAGE_FLOOR = 30.0
+const float SHIELD_PILOT_DAMAGE_CEILING = 60.0
+const float SHIELD_NPC_DAMAGE_FLOOR = 30.0
+
+const float SHIELD_FADE_ARBITRARY_DELAY = 3.0
+const float SHIELD_FADE_ENDCAP_DELAY = 1.0
+
+const float SHIELD_DISTANCE_TO_DESTROY = 40
+
+struct BubbleShieldDamageStruct
+{
+ float damageFloor
+ float damageCeiling
+ array<float> quadraticPolynomialCoefficients //Should actually be float[3], but because float[ 3 ] and array<float> are different types and this needs to be fed into EvaluatePolynomial make it an array<float> instead
+}
+
+struct
+{
+ BubbleShieldDamageStruct titanDamageStruct
+ BubbleShieldDamageStruct pilotDamageStruct
+ BubbleShieldDamageStruct aiDamageStruct
+
+}file
+
+
+void function BubbleShield_Init()
+{
+ RegisterSignal( "TitanBrokeBubbleShield" )
+ RegisterSignal( "NewBubbleShield" )
+ RegisterSignal( "StopBubbleShieldDamage" )
+
+ InitBubbleShieldDamageStructValues( file.titanDamageStruct, SHIELD_TITAN_DAMAGE_FLOOR, SHIELD_TITAN_DAMAGE_CEILING, [ 12.0, 5.0, 2.0 ] )
+ InitBubbleShieldDamageStructValues( file.pilotDamageStruct, SHIELD_PILOT_DAMAGE_FLOOR, SHIELD_PILOT_DAMAGE_CEILING, [ 2.0, 1.0, 1.0 ] )
+ InitBubbleShieldDamageStructValues( file.aiDamageStruct, SHIELD_PILOT_DAMAGE_FLOOR, SHIELD_PILOT_DAMAGE_CEILING, [ 2.0, 1.0, 1.0 ] )
+}
+
+void function InitBubbleShieldDamageStructValues( BubbleShieldDamageStruct damageStruct, float damageFloor, float damageCeiling, array<float> quadPolynomialCoeffs )
+{
+ damageStruct.damageFloor = damageFloor
+ damageStruct.damageCeiling = damageCeiling
+ damageStruct.quadraticPolynomialCoefficients = quadPolynomialCoeffs
+}
+
+void function CreateBubbleShield( entity titan, vector origin, vector angles )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ titan.Signal( "ClearDisableTitanfall" )
+
+ entity soul = titan.GetTitanSoul()
+ entity player = soul.GetBossPlayer()
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( !svGlobal.bubbleShieldEnabled )
+ return
+
+ player.EndSignal( "OnDestroy" )
+
+ float embarkTime = GetBubbleShieldDuration( player )
+ float bubTime = embarkTime + SHIELD_FADE_ARBITRARY_DELAY + SHIELD_FADE_ENDCAP_DELAY
+
+ soul.Signal( "NewBubbleShield" )
+ entity bubbleShield = CreateBubbleShieldWithSettings( titan.GetTeam(), origin, angles, player, bubTime )
+ bubbleShield.SetBossPlayer( player ) // so code knows AI should try to shoot at titan inside shield
+ soul.soul.bubbleShield = bubbleShield
+
+ player.SetTitanBubbleShieldTime( Time() + GetBubbleShieldDuration( player ) ) //This sets the time to display "Titan Shielded" on the HUD
+
+ AI_CreateDangerousArea_Static( bubbleShield, null, TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE, titan.GetTeam(), true, true, origin )
+
+ //titan.SetNPCPriorityOverride( 1 )
+
+ OnThreadEnd(
+ function () : ( titan, soul, player, bubbleShield )
+ {
+ if ( IsValid( player ) )
+ player.SetTitanBubbleShieldTime( 0 ) //This sets the time to display "Titan Shielded" on the HUD
+
+ CleanupTitanBubbleShieldVars( titan, soul, bubbleShield )
+
+ }
+ )
+
+ waitthread WaitUntilShieldFades( player, titan, bubbleShield, bubTime + 4.0 )
+}
+
+void function MonitorTitanMovement( entity soul, entity bubbleShield )
+{
+ entity titan = soul.GetTitan()
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+ bubbleShield.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDestroy" )
+
+ vector startPos = titan.GetOrigin()
+ float endTime = Time() + SHIELD_FADE_ARBITRARY_DELAY
+ while( endTime >= Time() )
+ {
+ if ( Distance( titan.GetOrigin(), startPos ) > SHIELD_DISTANCE_TO_DESTROY )
+ break
+
+ wait 0.1
+ }
+
+ soul.Signal( "TitanBrokeBubbleShield" )
+}
+
+void function CreateGenericBubbleShield( entity titan, vector origin, vector angles, float duration = 9999.0 )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ entity soul = titan.GetTitanSoul()
+ soul.Signal( "NewBubbleShield" )
+ entity bubbleShield = CreateBubbleShieldWithSettings( titan.GetTeam(), origin, angles, titan, 9999 )
+ soul.soul.bubbleShield = bubbleShield
+
+ titan.SetNPCPriorityOverride( 10 )
+
+ OnThreadEnd(
+ function () : ( titan, soul, bubbleShield )
+ {
+ CleanupTitanBubbleShieldVars( titan, soul, bubbleShield )
+ }
+ )
+
+ waitthread WaitUntilShieldFades( null, titan, bubbleShield, duration )
+}
+
+void function CreateParentedBubbleShield( entity titan, vector origin, vector angles, float duration = 9999.0 )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ entity soul = titan.GetTitanSoul()
+ soul.Signal( "NewBubbleShield" )
+ entity bubbleShield = CreateBubbleShieldWithSettings( titan.GetTeam(), origin, angles, titan, 9999 )
+ soul.soul.bubbleShield = bubbleShield
+
+ titan.SetNPCPriorityOverride( 10 )
+
+ OnThreadEnd(
+ function () : ( titan, soul, bubbleShield )
+ {
+ CleanupTitanBubbleShieldVars( titan, soul, bubbleShield )
+ }
+ )
+
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "OnDestroy" )
+
+ soul.soul.bubbleShield.SetParent( titan, "ORIGIN" )
+ table bubleshieldDotS = expect table( soul.soul.bubbleShield.s )
+ entity friendlyColoredFX = expect entity (bubleshieldDotS.friendlyColoredFX )
+ entity enemyColoredFX = expect entity (bubleshieldDotS.enemyColoredFX )
+ friendlyColoredFX.SetParent( soul.soul.bubbleShield )
+ enemyColoredFX.SetParent( soul.soul.bubbleShield )
+
+ wait duration
+}
+
+void function CleanupTitanBubbleShieldVars( entity titan, entity soul, entity bubbleShield )
+{
+ DestroyBubbleShield( bubbleShield )
+
+ if ( IsValid( soul ) ){
+ soul.soul.bubbleShield = null
+ }
+
+ if ( IsAlive( titan ) )
+ titan.ClearNPCPriorityOverride()
+}
+
+void function DestroyBubbleShield( entity bubbleShield )
+{
+ if ( IsValid( bubbleShield ) )
+ {
+ ClearChildren( bubbleShield )
+ bubbleShield.Destroy()
+ }
+}
+
+entity function CreateBubbleShieldWithSettings( int team, vector origin, vector angles, entity owner = null, float duration = 9999 )
+{
+ entity bubbleShield = CreateEntity( "prop_dynamic" )
+ bubbleShield.SetValueForModelKey( $"models/fx/xo_shield.mdl" )
+ bubbleShield.kv.solid = SOLID_VPHYSICS
+ bubbleShield.kv.rendercolor = "81 130 151"
+ bubbleShield.kv.contents = (int(bubbleShield.kv.contents) | CONTENTS_NOGRAPPLE)
+ bubbleShield.SetOrigin( origin )
+ bubbleShield.SetAngles( angles )
+ // Blocks bullets, projectiles but not players and not AI
+ bubbleShield.kv.CollisionGroup = TRACE_COLLISION_GROUP_BLOCK_WEAPONS
+ bubbleShield.SetBlocksRadiusDamage( true )
+ DispatchSpawn( bubbleShield )
+ bubbleShield.Hide()
+
+ SetTeam( bubbleShield, team )
+ array<entity> bubbleShieldFXs
+
+ vector coloredFXOrigin = origin + Vector( 0, 0, 25 )
+ table bubbleShieldDotS = expect table( bubbleShield.s )
+ if ( team == TEAM_UNASSIGNED )
+ {
+ entity neutralColoredFX = StartParticleEffectInWorld_ReturnEntity( BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX, coloredFXOrigin, <0, 0, 0> )
+ SetTeam( neutralColoredFX, team )
+ bubbleShieldDotS.neutralColoredFX <- neutralColoredFX
+ bubbleShieldFXs.append( neutralColoredFX )
+ }
+ else
+ {
+ //Create friendly and enemy colored particle systems
+ entity friendlyColoredFX = StartParticleEffectInWorld_ReturnEntity( BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX, coloredFXOrigin, <0, 0, 0> )
+ SetTeam( friendlyColoredFX, team )
+ friendlyColoredFX.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
+ EffectSetControlPointVector( friendlyColoredFX, 1, FRIENDLY_COLOR_FX )
+
+ entity enemyColoredFX = StartParticleEffectInWorld_ReturnEntity( BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX, coloredFXOrigin, <0, 0, 0> )
+ SetTeam( enemyColoredFX, team )
+ enemyColoredFX.kv.VisibilityFlags = ENTITY_VISIBLE_TO_ENEMY
+ EffectSetControlPointVector( enemyColoredFX, 1, ENEMY_COLOR_FX )
+
+ bubbleShieldDotS.friendlyColoredFX <- friendlyColoredFX
+ bubbleShieldDotS.enemyColoredFX <- enemyColoredFX
+ bubbleShieldFXs.append( friendlyColoredFX )
+ bubbleShieldFXs.append( enemyColoredFX )
+ }
+
+ #if MP
+ DisableTitanfallForLifetimeOfEntityNearOrigin( bubbleShield, origin, TITANHOTDROP_DISABLE_ENEMY_TITANFALL_RADIUS )
+ #endif
+
+ EmitSoundOnEntity( bubbleShield, "BubbleShield_Sustain_Loop" )
+
+ thread CleanupBubbleShield( bubbleShield, bubbleShieldFXs, duration )
+ thread BubbleShieldDamageEnemies( bubbleShield, owner )
+
+ return bubbleShield
+}
+
+void function CleanupBubbleShield( entity bubbleShield, array<entity> bubbleShieldFXs, float fadeTime )
+{
+ bubbleShield.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function () : ( bubbleShield, bubbleShieldFXs )
+ {
+ if ( IsValid_ThisFrame( bubbleShield ) )
+ {
+ StopSoundOnEntity( bubbleShield, "BubbleShield_Sustain_Loop" )
+ EmitSoundOnEntity( bubbleShield, "BubbleShield_End" )
+ DestroyBubbleShield( bubbleShield )
+ }
+
+ foreach ( fx in bubbleShieldFXs )
+ {
+ if ( IsValid_ThisFrame( fx ) )
+ {
+ EffectStop( fx )
+ }
+ }
+ }
+ )
+
+ wait fadeTime
+}
+
+void function WaitUntilShieldFades( entity player, entity titan, entity bubbleShield, float failTime )
+{
+ bubbleShield.EndSignal( "OnDestroy" )
+ entity soul = titan.GetTitanSoul()
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "NewBubbleShield" )
+
+ soul.EndSignal( "TitanBrokeBubbleShield" )
+
+ if ( player != null )
+ waitthread WaitUntilPlayerTitanStandsOrDies( player, titan, failTime )
+ else
+ waitthread WaitUntilTitanStandsOrDies( titan, failTime )
+
+ // have to add this since OnTitanDeath is somewhat unreliable, especially in the middle of titan transfer
+ if ( !IsAlive( soul.GetTitan() ) )
+ return
+
+ thread MonitorTitanMovement( soul, bubbleShield )
+ wait SHIELD_FADE_ARBITRARY_DELAY
+}
+
+void function WaitUntilPlayerTitanStandsOrDies( entity player, entity titan, float failTime )
+{
+ waitthread WaitUntilTitanStandsOrDies( titan, failTime )
+
+ if ( !IsAlive( player ) )
+ return
+
+ if ( IsPlayerEmbarking( player ) && player.Anim_IsActive() )
+ WaittillAnimDone( player )
+}
+
+void function WaitUntilTitanStandsOrDies( entity titan, float timeout = -1.0 )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "ChangedTitanMode" )
+ float endTime = Time() + timeout
+
+ for ( ;; )
+ {
+ if ( titan.GetTitanSoul().GetStance() == STANCE_STAND )
+ return
+
+ if ( Time() > endTime && timeout != -1 )
+ break
+
+ wait 0.2
+ }
+}
+
+void function BubbleShieldDamageEnemies( entity bubbleShield, entity bubbleShieldPlayer )
+{
+ bubbleShield.EndSignal( "OnDestroy" )
+ if ( IsValid( bubbleShieldPlayer ) )
+ bubbleShieldPlayer.EndSignal( "OnDestroy" )
+
+ bubbleShield.EndSignal( "StopBubbleShieldDamage" )
+
+ entity trigger = CreateEntity( "trigger_cylinder" )
+ trigger.SetRadius( TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE )
+ trigger.SetAboveHeight( TITAN_BUBBLE_SHIELD_CYLINDER_TRIGGER_HEIGHT ) //Still not quite a sphere, will see if close enough
+ trigger.SetBelowHeight( 0 )
+ trigger.SetOrigin( bubbleShield.GetOrigin() )
+ trigger.SetParent( bubbleShield )
+ DispatchSpawn( trigger )
+
+ trigger.SearchForNewTouchingEntity() //JFS: trigger.GetTouchingEntities() will not return entities already in the trigger unless this is called. See bug 202843
+
+ /*DebugDrawCylinder( trigger.GetOrigin(), <270,0,0>, TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE, TITAN_BUBBLE_SHIELD_CYLINDER_TRIGGER_HEIGHT, 255, 255, 255, true, 20.0 )
+ DebugDrawSphere( bubbleShield.GetOrigin(), TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE, 255, 0, 0, true, 20 )*/
+ OnThreadEnd(
+ function() : ( trigger )
+ {
+ trigger.Destroy()
+ }
+ )
+
+ float refreshLowerBound = 0.5
+ float refreshUpperBound = 0.8
+
+ table<entity, int> soulTable = {}
+ table<entity, int> npcTable = {}
+ table<entity, int> pilotTable = {}
+
+ table<entity, int> countTable
+
+ while ( true )
+ {
+ array<entity> touchingEnts = trigger.GetTouchingEntities()
+
+ foreach( touchingEnt in touchingEnts )
+ {
+ if ( touchingEnt.IsTitan() )
+ countTable = soulTable
+ else if( touchingEnt.IsPlayer() )
+ countTable = pilotTable
+ else
+ countTable = npcTable
+
+ DamageEntWithinBubbleShield( bubbleShield, bubbleShieldPlayer, touchingEnt, countTable )
+ }
+
+ wait RandomFloatRange( refreshLowerBound, refreshUpperBound )
+ }
+}
+
+void function LetTitanPlayerShootThroughBubbleShield( entity titanPlayer )
+{
+ Assert( titanPlayer.IsTitan() )
+
+ entity soul = titanPlayer.GetTitanSoul()
+ entity bubbleShield = soul.soul.bubbleShield
+
+ if ( !IsValid( bubbleShield ) )
+ return
+
+ bubbleShield.SetOwner( titanPlayer ) //After this, player is able to fire out from shield. WATCH OUT FOR POTENTIAL COLLISION BUGS!
+
+ thread MonitorLastFireTime( titanPlayer )
+ thread StopPlayerShootThroughBubbleShield( titanPlayer, bubbleShield )
+}
+
+void function StopPlayerShootThroughBubbleShield( entity player, entity bubbleShield )
+{
+ player.EndSignal( "OnDeath" )
+ player.WaitSignal( "OnChangedPlayerClass" ) //Kill this thread once player gets out of the Titan
+
+ if ( !IsValid( bubbleShield ) )
+ return
+
+ bubbleShield.SetOwner( null )
+}
+
+void function MonitorLastFireTime( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnChangedPlayerClass" ) //Kill this thread once player gets out of the Titan
+
+ player.WaitSignal( "OnPrimaryAttack" ) //Sent when player fires his weapon
+ //printt( "Player fired weapon! in MonitorLastFireTime" )
+
+ entity soul = player.GetTitanSoul()
+
+ if ( !IsValid( soul ) )
+ return
+
+ soul.Signal( "TitanBrokeBubbleShield" ) //WaitUntilShieldFades will end when this signal is sent
+}
+
+void function DamageEntWithinBubbleShield( entity bubbleShield, entity bubbleShieldPlayer, entity touchingEnt, table<entity, int> countTable, )
+{
+ int ownerTeam = IsValid( bubbleShieldPlayer ) ? bubbleShieldPlayer.GetTeam() : bubbleShield.GetTeam()
+ if ( !BubbleShieldShouldDamage( bubbleShield, ownerTeam, touchingEnt ) )
+ return
+
+ entity entInCountTable = null
+
+ if ( touchingEnt.IsTitan() )
+ {
+ entity soul = touchingEnt.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return
+
+ entInCountTable = soul
+ }
+ else
+ {
+ entInCountTable = touchingEnt
+ }
+
+ if ( IsValid( entInCountTable ) && !( entInCountTable in countTable ) )
+ countTable[ entInCountTable ] <- 0
+
+ int timesTouched = ++countTable[ entInCountTable ]
+
+ BubbleShieldDamageStruct damageStruct
+
+ if ( touchingEnt.IsTitan() )
+ damageStruct = file.titanDamageStruct
+ else if ( touchingEnt.IsPlayer() )
+ damageStruct = file.pilotDamageStruct
+ else
+ damageStruct = file.aiDamageStruct
+
+ float damageAmount = damageStruct.damageFloor + EvaluatePolynomial( float ( countTable[ entInCountTable ] ), damageStruct.quadraticPolynomialCoefficients )
+
+ //printt( "Damage amount: " + damageAmount + ", touchingEnt: " + touchingEnt )
+
+ touchingEnt.TakeDamage( damageAmount, bubbleShieldPlayer, bubbleShield, { origin = bubbleShield.GetOrigin(), damageSourceId=eDamageSourceId.bubble_shield } )
+ StatusEffect_AddTimed( touchingEnt, eStatusEffect.emp, 0.1, 1.0, 0.2 )
+
+ EmitSoundOnEntity( bubbleShield, "titan_energyshield_damage" )
+}
+
+bool function BubbleShieldShouldDamage( entity bubbleShield, int ownerTeam, entity ent )
+{
+ if ( !IsAlive( ent ) )
+ return false
+
+ if ( ownerTeam == ent.GetTeam() )
+ return false
+
+ /*if ( ent.IsTitan() && IsTitanWithinBubbleShield( ent ) )
+ return false*/
+
+ if ( ! ( ent instanceof CBaseCombatCharacter ) ) //Projectiles etc won't get damaged
+ return false
+
+ float distSqr = DistanceSqr( bubbleShield.GetOrigin(), ent.GetOrigin() )
+
+ return distSqr <= TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE_SQUARED
+}
+
+bool function IsTitanWithinBubbleShield( entity titan )
+{
+ if ( !IsAlive( titan ) )
+ return false
+
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) ) //Bug 152438. Defensive coding, but there's a small window after embarking where the npc Titan doesn't have a soul anymore but can be damaged
+ return false
+
+ if ( !IsValid( soul.soul.bubbleShield ) )
+ return false
+
+ return DistanceSqr( soul.soul.bubbleShield.GetOrigin(), titan.GetOrigin() ) < TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE * TITAN_BUBBLE_SHIELD_INVULNERABILITY_RANGE
+}
+
+bool function TitanHasBubbleShieldWeapon( entity titan )
+{
+ entity weapon = titan.GetActiveWeapon()
+ if ( IsValid( weapon ) && IsValid( weapon.w.bubbleShield ) )
+ return true
+
+ return false
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_codecallbacks_common.gnut b/Northstar.CustomServers/scripts/vscripts/_codecallbacks_common.gnut
new file mode 100644
index 000000000..b08fdcf1a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_codecallbacks_common.gnut
@@ -0,0 +1,853 @@
+
+global function CodeCallback_DamageEntity
+global function HandleFootstepDamage
+global function CodeCallback_OnEntityKilled
+
+global function AddDamageCallback
+global function RemoveDamageCallback
+global function RunClassDamageCallbacks
+global function AddDamageFinalCallback
+global function RunClassDamageFinalCallbacks
+global function AddPostDamageCallback
+global function RunClassPostDamageCallbacks
+
+global function AddDamageByCallback
+global function AddDamageCallbackSourceID
+global function AddDeathCallback
+global function RemoveDeathCallback
+global function AddSoulDeathCallback
+global function AddCallback_OnPlayerRespawned
+global function AddCallback_OnPlayerKilled
+global function AddCallback_OnNPCKilled
+global function AddCallback_OnTitanDoomed
+global function AddCallback_OnTitanHealthSegmentLost
+global function AddCallback_OnClientConnecting
+global function AddCallback_OnClientConnected
+global function AddCallback_OnClientDisconnected
+global function AddCallback_OnPilotBecomesTitan
+global function AddCallback_OnTitanBecomesPilot
+global function AddCallback_EntityChangedTeam
+global function AddCallback_OnTouchHealthKit
+global function AddCallback_OnPlayerAssist
+global function AddCallback_OnPlayerGetsNewPilotLoadout
+global function AddCallback_OnTitanGetsNewTitanLoadout
+global function AddCallback_OnUpdateDerivedPilotLoadout
+global function AddCallback_OnUpdateDerivedTitanLoadout
+global function AddCallback_OnUpdateDerivedPlayerTitanLoadout
+global function AddClientCommandCallback
+global function AddPlayerDropScriptedItemsCallback
+global function AddCallback_OnPlayerInventoryChanged
+
+// Register functions are called when an entity spawns.
+global function RegisterForDamageDeathCallbacks
+global function CodeCallback_OnInventoryChanged
+global function CodeCallback_OnEntityChangedTeam
+
+global function AddEntityCallback_OnDamaged
+global function RemoveEntityCallback_OnDamaged
+global function AddEntityCallback_OnPostDamaged
+global function RemoveEntityCallback_OnPostDamaged
+global function AddEntityCallback_OnKilled
+global function RemoveEntityCallback_OnKilled
+global function AddEntityCallback_OnPostShieldDamage
+global function RemoveEntityCallback_OnPostShieldDamage
+
+global function AddTitanCallback_OnHealthSegmentLost
+global function RemoveTitanCallback_OnHealthSegmentLost
+
+// Player movement callbacks
+global function AddPlayerMovementEventCallback
+global function RemovePlayerMovementEventCallback
+global function CodeCallback_OnPlayerJump
+global function CodeCallback_OnPlayerDoubleJump
+global function CodeCallback_OnPlayerDodge
+global function CodeCallback_OnPlayerLeaveGround
+global function CodeCallback_OnPlayerTouchGround
+global function CodeCallback_OnPlayerMantle
+global function CodeCallback_OnPlayerBeginWallrun
+global function CodeCallback_OnPlayerEndWallrun
+global function CodeCallback_OnPlayerBeginWallhang
+global function CodeCallback_OnPlayerEndWallhang
+
+struct
+{
+ table<string, array< void functionref( entity, var ) > > classDamageCallbacks
+ table<string, array< void functionref( entity, var ) > > classDamageFinalCallbacks
+ table<string, array< void functionref( entity, var ) > > classPostDamageCallbacks
+ array< void functionref( entity ) > playerInventoryChangedCallbacks
+} file
+
+void function CodeCallback_DamageEntity( entity ent, var damageInfo )
+{
+ // gametype script decides if ent should take damage
+ if ( !ScriptCallback_ShouldEntTakeDamage( ent, damageInfo ) )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( "CodeCallback_DamageEntity() top:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == damagedef_titan_step )
+ HandleFootstepDamage( ent, damageInfo )
+
+ RunClassDamageCallbacks( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after class damage callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ // Added via AddEntityCallback_OnDamaged
+ foreach ( callbackFunc in ent.e.entDamageCallbacks )
+ callbackFunc( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after AddEntityCallback_OnDamaged() callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( damageSourceId in shGlobal.damageSourceIdCallbacks )
+ {
+ foreach ( callbackFunc in shGlobal.damageSourceIdCallbacks[ damageSourceId ] )
+ callbackFunc( ent, damageInfo )
+ }
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after damageSourceId callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ RunClassDamageFinalCallbacks( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after class damage final callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ // make destructible vehicles take more damage from DF_EXPLOSION damage type
+ if ( "isDestructibleVehicle" in ent.s && DamageInfo_GetCustomDamageType( damageInfo ) & DF_EXPLOSION )
+ {
+ DamageInfo_ScaleDamage( damageInfo, 2.0 )
+ }
+
+ if ( ent.GetShieldHealth() > 0 )
+ {
+ DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )
+ ShieldModifyDamage( ent, damageInfo )
+ }
+
+ // Added via AddEntityCallback_OnPostDamaged
+ foreach ( callbackFunc in ent.e.entPostDamageCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( "CodeCallback_DamageEntity() bottom:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
+ UpdateLastDamageTime( ent )
+
+ AddFlinch( ent, damageInfo )
+
+ UpdateAttackerInfo( ent, DamageInfo_GetAttacker( damageInfo ), DamageInfo_GetDamage( damageInfo ) )
+}
+
+bool function TrySpectreVirus( entity victim, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsSpectre( victim ) )
+ return false
+
+ if ( !IsAlive( attacker ) )
+ return false
+
+ if ( !attacker.IsTitan() )
+ return false
+
+ if ( !attacker.IsPlayer() )
+ return false
+
+ if ( !PlayerHasPassive( attacker, ePassives.PAS_WIFI_SPECTRE ) )
+ return false
+
+ thread LeechPropagate( victim, attacker )
+ return true
+}
+
+
+void function HandleFootstepDamage( entity victim, var damageInfo )
+{
+ if ( TrySpectreVirus( victim, damageInfo ) )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+}
+
+void function CodeCallback_OnEntityKilled( entity ent, var damageInfo )
+{
+ // npcs and player do death package in their own killed callbacks which are always called (even if deathNotifications is false)
+ if ( !ent.IsNPC() && !ent.IsPlayer() )
+ HandleDeathPackage( ent, damageInfo )
+
+ string className = ent.GetClassName()
+ if ( className in shGlobal.deathCallbacks )
+ {
+ foreach ( callbackFunc in shGlobal.deathCallbacks[className] )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+ }
+
+
+ // Added via AddEntityCallback_OnKilled
+ foreach ( callbackFunc in ent.e.entKilledCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+
+ SendEntityKilledEvent( ent, damageInfo )
+}
+
+
+void function SendEntityKilledEvent( entity ent, var damageInfo )
+{
+ array<entity> players = GetPlayerArray()
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ // trigger_hurt is no longer networked, so the "attacker" fails to display obituaries
+ if ( attacker )
+ {
+ string attackerClassname = attacker.GetClassName()
+
+ if ( attackerClassname == "trigger_hurt" || attackerClassname == "trigger_multiple" )
+ attacker = GetEntByIndex( 0 ) // worldspawn
+ }
+
+ int attackerEHandle = attacker ? attacker.GetEncodedEHandle() : -1
+
+ int victimEHandle = ent.GetEncodedEHandle()
+ int scriptDamageType = DamageInfo_GetCustomDamageType( damageInfo )
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ if ( scriptDamageType & DF_VORTEX_REFIRE )
+ damageSourceId = eDamageSourceId.mp_titanweapon_vortex_shield
+
+ if ( IsValidHeadShot( damageInfo, ent ) )
+ scriptDamageType = scriptDamageType | DF_HEADSHOT
+ else
+ scriptDamageType = scriptDamageType & (~DF_HEADSHOT)
+
+ foreach ( player in players )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_OnEntityKilled", attackerEHandle, victimEHandle, scriptDamageType, damageSourceId )
+ }
+}
+
+//=====================================================================================
+// Utility functions
+//=====================================================================================
+
+void function AddDamageCallback( string className, void functionref( entity, var ) callbackFunc )
+{
+ if ( !( className in file.classDamageCallbacks ) )
+ file.classDamageCallbacks[className] <- []
+
+ file.classDamageCallbacks[className].append( callbackFunc )
+}
+
+void function RemoveDamageCallback( string className, void functionref( entity, var ) callbackFunc )
+{
+ Assert( className in file.classDamageCallbacks, "Tried to remove damage callback that isn't added" )
+ Assert( file.classDamageCallbacks[className].contains( callbackFunc ), "Tried to remove damage callback that isn't added" )
+ file.classDamageCallbacks[className].fastremovebyvalue( callbackFunc )
+}
+
+void function RunClassDamageCallbacks( entity ent, var damageInfo )
+{
+ string className = ent.GetClassName()
+ if ( !( className in file.classDamageCallbacks ) )
+ return
+
+ foreach ( callbackFunc in file.classDamageCallbacks[className] )
+ {
+ callbackFunc( ent, damageInfo )
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+ }
+}
+
+void function AddDamageFinalCallback( string className, void functionref( entity, var ) callbackFunc )
+{
+ if ( !( className in file.classDamageFinalCallbacks ) )
+ file.classDamageFinalCallbacks[className] <- []
+
+ file.classDamageFinalCallbacks[className].append( callbackFunc )
+}
+void function RunClassDamageFinalCallbacks( entity ent, var damageInfo )
+{
+ string className = ent.GetClassName()
+ if ( !( className in file.classDamageFinalCallbacks ) )
+ return
+
+ foreach ( callbackFunc in file.classDamageFinalCallbacks[className] )
+ {
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+ callbackFunc( ent, damageInfo )
+ }
+}
+
+
+void function AddPostDamageCallback( string className, void functionref( entity, var ) callbackFunc )
+{
+ if ( !( className in file.classPostDamageCallbacks ) )
+ file.classPostDamageCallbacks[className] <- []
+
+ file.classPostDamageCallbacks[className].append( callbackFunc )
+}
+
+void function RunClassPostDamageCallbacks( entity ent, var damageInfo )
+{
+ string className = ent.GetClassName()
+ if ( !( className in file.classPostDamageCallbacks ) )
+ return
+
+ foreach ( callbackFunc in file.classPostDamageCallbacks[className] )
+ {
+ #if DEV
+ float damage = DamageInfo_GetDamage( damageInfo )
+ #endif
+ callbackFunc( ent, damageInfo )
+
+ #if DEV
+ Assert( damage == DamageInfo_GetDamage( damageInfo ), "Damage changed in a post damage callback" )
+ #endif
+ }
+}
+
+void function AddDamageByCallback( string className, void functionref( entity, var ) callbackFunc )
+{
+ if ( !( className in svGlobal.damageByCallbacks ) )
+ svGlobal.damageByCallbacks[className] <- []
+
+ svGlobal.damageByCallbacks[className].append( callbackFunc )
+}
+
+void function AddDamageCallbackSourceID( int id, void functionref(entity, var) callbackFunc )
+{
+ if ( !( id in shGlobal.damageSourceIdCallbacks ) )
+ shGlobal.damageSourceIdCallbacks[id] <- []
+
+ shGlobal.damageSourceIdCallbacks[id].append( callbackFunc )
+}
+
+void function AddDeathCallback( string className, void functionref( entity, var ) callbackFunc )
+{
+ if ( !( className in shGlobal.deathCallbacks ) )
+ shGlobal.deathCallbacks[className] <- []
+
+ shGlobal.deathCallbacks[className].append( callbackFunc )
+}
+
+void function RemoveDeathCallback( string className, void functionref( entity, var ) callbackFunc )
+{
+ Assert( className in shGlobal.deathCallbacks, "Tried to remove death callback that isn't added" )
+ Assert( shGlobal.deathCallbacks[className].contains( callbackFunc ), "Tried to remove death callback that isn't added" )
+ shGlobal.deathCallbacks[className].fastremovebyvalue( callbackFunc )
+}
+
+void function AddSoulDeathCallback( void functionref( entity, var ) callbackFunc )
+{
+ #if DEV
+ foreach ( func in svGlobal.soulDeathFuncs )
+ {
+ Assert( func != callbackFunc , "Already added " + string( callbackFunc ) + " with AddSoulDeathCallback" )
+ }
+ #endif
+
+ svGlobal.soulDeathFuncs.append( callbackFunc )
+}
+
+void function AddCallback_OnTouchHealthKit( string className, bool functionref( entity player, entity healthpack ) callbackFunc )
+{
+ if ( ! (className in svGlobal.onTouchHealthKitCallbacks ) )
+ {
+ svGlobal.onTouchHealthKitCallbacks[ className ] <- [ callbackFunc ]
+ return
+ }
+ else
+ {
+ Assert( !svGlobal.onTouchHealthKitCallbacks[className].contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnTouchHealthKit to class " + className )
+ svGlobal.onTouchHealthKitCallbacks[className].append( callbackFunc )
+ }
+
+}
+
+void function AddCallback_OnPlayerRespawned( void functionref( entity ) callbackFunc )
+{
+ Assert( !svGlobal.onPlayerRespawnedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerRespawned" )
+ svGlobal.onPlayerRespawnedCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnPlayerKilled( void functionref( entity victim, entity attacker, var damageInfo ) callbackFunc )
+{
+ Assert( !svGlobal.onPlayerKilledCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerKilled" )
+ svGlobal.onPlayerKilledCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnNPCKilled( void functionref( entity victim, entity attacker, var damageInfo ) callbackFunc )
+{
+ Assert( !svGlobal.onNPCKilledCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerKilled" )
+ svGlobal.onNPCKilledCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnTitanDoomed( void functionref( entity victim, var damageInfo ) callbackFunc )
+{
+ Assert( !svGlobal.onTitanDoomedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnTitanDoomed" )
+ svGlobal.onTitanDoomedCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnTitanHealthSegmentLost( void functionref( entity victim, entity attacker ) callbackFunc )
+{
+ Assert( !svGlobal.onTitanHealthSegmentLostCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnTitanHealthSegmentLost" )
+ svGlobal.onTitanHealthSegmentLostCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnClientConnecting( void functionref( entity player ) callbackFunc )
+{
+ Assert( !svGlobal.onClientConnectingCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnClientConnecting" )
+ svGlobal.onClientConnectingCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnClientConnected( void functionref( entity player ) callbackFunc )
+{
+ Assert( !svGlobal.onClientConnectedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnClientConnected" )
+ svGlobal.onClientConnectedCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnClientDisconnected( void functionref( entity player ) callbackFunc )
+{
+ Assert( !svGlobal.onClientDisconnectedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnClientDisconnected" )
+ svGlobal.onClientDisconnectedCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnPilotBecomesTitan( void functionref( entity pilot, entity npc_titan ) callbackFunc )
+{
+ Assert( !svGlobal.onPilotBecomesTitanCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPilotBecomesTitan" )
+ svGlobal.onPilotBecomesTitanCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnTitanBecomesPilot( void functionref( entity pilot, entity npc_titan ) callbackFunc )
+{
+ Assert( !svGlobal.onTitanBecomesPilotCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnTitanBecomesPilot" )
+ svGlobal.onTitanBecomesPilotCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnPlayerAssist( void functionref( entity attacker, entity victim ) callbackFunc )
+{
+ Assert( !svGlobal.onPlayerAssistCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerAssist" )
+ svGlobal.onPlayerAssistCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_EntityChangedTeam( string className, void functionref( entity ent ) callbackFunc )
+{
+ if ( !( className in svGlobal.onEntityChangedTeamCallbacks ) )
+ {
+ svGlobal.onEntityChangedTeamCallbacks[ className ] <- [ callbackFunc ]
+ return
+ }
+ else
+ {
+ Assert( !svGlobal.onEntityChangedTeamCallbacks[ className ].contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_EntityChangedTeam" )
+ svGlobal.onEntityChangedTeamCallbacks[ className ].append( callbackFunc )
+ }
+}
+
+void function AddCallback_OnTitanGetsNewTitanLoadout( void functionref( entity titan, TitanLoadoutDef newTitanLoadout ) callbackFunc )
+{
+ Assert( !svGlobal.onTitanGetsNewLoadoutCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnTitanGetsNewTitanLoadout" )
+ svGlobal.onTitanGetsNewLoadoutCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnPlayerGetsNewPilotLoadout( void functionref( entity player, PilotLoadoutDef newTitanLoadout ) callbackFunc )
+{
+ Assert( !svGlobal.onPlayerGetsNewPilotLoadoutCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerGetsNewPilotLoadout" )
+ svGlobal.onPlayerGetsNewPilotLoadoutCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnUpdateDerivedTitanLoadout( void functionref( TitanLoadoutDef newTitanLoadout ) callbackFunc )
+{
+ Assert( !svGlobal.onUpdateDerivedTitanLoadoutCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnUpdateDerivedTitanLoadout" )
+ svGlobal.onUpdateDerivedTitanLoadoutCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnUpdateDerivedPlayerTitanLoadout( void functionref( entity player, TitanLoadoutDef newTitanLoadout ) callbackFunc )
+{
+ Assert( !svGlobal.onUpdateDerivedPlayerTitanLoadoutCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnUpdateDerivedTitanLoadout" )
+ svGlobal.onUpdateDerivedPlayerTitanLoadoutCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_OnUpdateDerivedPilotLoadout( void functionref( PilotLoadoutDef newPilotLoadout ) callbackFunc )
+{
+ Assert( !svGlobal.onUpdateDerivedPilotLoadoutCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnUpdateDerivedPilotLoadout" )
+ svGlobal.onUpdateDerivedPilotLoadoutCallbacks.append( callbackFunc )
+}
+
+void function AddClientCommandCallback( string commandString, bool functionref( entity player, array<string> args ) callbackFunc )
+{
+ Assert( !( commandString in svGlobal.clientCommandCallbacks ), "Already added " + commandString + " with AddClientCommandCallback" )
+ svGlobal.clientCommandCallbacks[ commandString ] <- callbackFunc
+}
+
+void function AddPlayerDropScriptedItemsCallback( void functionref(entity player) callbackFunc )
+{
+ Assert( !( svGlobal.onPlayerDropsScriptedItemsCallbacks.contains( callbackFunc ) ), "Already added " + string( callbackFunc ) + " with AddPlayerDropScriptedItemsCallback" )
+ svGlobal.onPlayerDropsScriptedItemsCallbacks.append( callbackFunc )
+}
+
+//=====================================================================================
+// Register functions are called when an entity spawns.
+//=====================================================================================
+
+void function RegisterForDamageDeathCallbacks( entity ent )
+{
+ string className = ent.GetClassName()
+
+ if ( (className in file.classDamageCallbacks) || (className in file.classDamageFinalCallbacks) )
+ ent.SetDamageNotifications( true )
+
+ if ( className in shGlobal.deathCallbacks )
+ ent.SetDeathNotifications( true )
+}
+
+void function AddTitanCallback_OnHealthSegmentLost( entity ent, void functionref( entity titan, entity victim ) callbackFunc )
+{
+ Assert( !ent.e.entSegmentLostCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " to entity" )
+
+ ent.e.entSegmentLostCallbacks.append( callbackFunc )
+}
+
+void function RemoveTitanCallback_OnHealthSegmentLost( entity ent, void functionref( entity titan, entity victim ) callbackFunc )
+{
+ int index = ent.e.entSegmentLostCallbacks.find( callbackFunc )
+
+ Assert( index != -1, "Requested DamageCallback " + string( callbackFunc ) + " to be removed not found! " )
+ ent.e.entSegmentLostCallbacks.fastremove( index )
+}
+
+void function AddEntityCallback_OnDamaged( entity ent, void functionref( entity ent, var damageInfo ) callbackFunc )
+{
+ Assert( !ent.e.entDamageCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " to entity" )
+
+ ent.SetDamageNotifications( true )
+ ent.e.entDamageCallbacks.append( callbackFunc )
+}
+
+void function RemoveEntityCallback_OnDamaged( entity ent, void functionref( entity ent, var damageInfo ) callbackFunc )
+{
+ int index = ent.e.entDamageCallbacks.find( callbackFunc )
+
+ Assert( index != -1, "Requested DamageCallback " + string( callbackFunc ) + " to be removed not found! " )
+ ent.e.entDamageCallbacks.fastremove( index )
+
+ if ( ent.e.entDamageCallbacks.len() == 0 && ent.e.entPostDamageCallbacks.len() == 0 )
+ ent.SetDamageNotifications( false )
+}
+
+void function AddEntityCallback_OnPostDamaged( entity ent, void functionref( entity ent, var damageInfo ) callbackFunc )
+{
+ Assert( !ent.e.entPostDamageCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " to entity" )
+
+ ent.SetDamageNotifications( true )
+ ent.e.entPostDamageCallbacks.append( callbackFunc )
+}
+
+void function RemoveEntityCallback_OnPostDamaged( entity ent, void functionref( entity ent, var damageInfo ) callbackFunc )
+{
+ int index = ent.e.entPostDamageCallbacks.find( callbackFunc )
+
+ Assert( index != -1, "Requested PostDamageCallback " + string( callbackFunc ) + " to be removed not found! " )
+ ent.e.entPostDamageCallbacks.fastremove( index )
+
+ if ( ent.e.entPostDamageCallbacks.len() == 0 && ent.e.entDamageCallbacks.len() == 0 )
+ ent.SetDamageNotifications( false )
+}
+
+void function AddEntityCallback_OnKilled( entity ent, void functionref( entity, var ) callbackFunc )
+{
+ #if DEV
+ foreach ( func in ent.e.entKilledCallbacks )
+ {
+ Assert( func != callbackFunc , "Already added " + string( callbackFunc ) + " to entity" )
+ }
+ #endif
+
+ ent.SetDeathNotifications( true )
+ ent.e.entKilledCallbacks.append( callbackFunc )
+}
+
+void function RemoveEntityCallback_OnKilled( entity ent, void functionref( entity, var ) callbackFunc )
+{
+ int index = ent.e.entKilledCallbacks.find( callbackFunc )
+
+ Assert( index != -1, "Requested KilledCallback " + string( callbackFunc ) + " to be removed not found! " )
+ ent.e.entKilledCallbacks.fastremove( index )
+
+ if ( ent.e.entKilledCallbacks.len() == 0 )
+ ent.SetDeathNotifications( false )
+}
+
+void function AddEntityCallback_OnPostShieldDamage( entity ent, void functionref( entity, var, float ) callbackFunc )
+{
+ #if DEV
+ foreach ( func in ent.e.entPostShieldDamageCallbacks )
+ {
+ Assert( func != callbackFunc , "Already added " + string( callbackFunc ) + " to entity" )
+ }
+ #endif
+
+ ent.e.entPostShieldDamageCallbacks.append( callbackFunc )
+}
+
+void function RemoveEntityCallback_OnPostShieldDamage( entity ent, void functionref( entity, var, float ) callbackFunc )
+{
+ int index = ent.e.entPostShieldDamageCallbacks.find( callbackFunc )
+
+ Assert( index != -1, "Requested OnPostShieldDamage " + string( callbackFunc ) + " to be removed not found! " )
+ ent.e.entPostShieldDamageCallbacks.fastremove( index )
+}
+
+void function CodeCallback_OnInventoryChanged( entity player )
+{
+ player.Signal( "InventoryChanged" )
+
+ if ( !IsAlive( player ) )
+ return
+
+#if HAS_TITAN_WEAPON_SWAPPING
+ if ( player.IsTitan() )
+ {
+ array<entity> weapons = GetPrimaryWeapons( player )
+ bool weaponSwap = true
+ foreach ( weapon in weapons )
+ {
+ if ( weapon == player.p.lastPrimaryWeaponEnt )
+ weaponSwap = false
+ player.p.lastPrimaryWeaponEnt = weapon
+ }
+
+ if ( weaponSwap )
+ {
+ table<int,float> cooldowns = GetWeaponCooldownsForTitanLoadoutSwitch( player )
+
+ ResetTitanLoadoutFromPrimary( player )
+
+ bool foundNewWeapon
+
+ foreach ( weapon in weapons )
+ {
+ int loadoutIndex = GetSPTitanLoadoutIndexForWeapon( weapon.GetWeaponClassName() )
+ if ( loadoutIndex >= 0 )
+ {
+ if ( GetSPTitanLoadoutHasEverBeenSelected( loadoutIndex ) )
+ continue
+
+ foundNewWeapon = true
+ SetSPTitanLoadoutHasEverBeenSelected( loadoutIndex )
+ }
+ }
+
+ if ( !JustLoadedFromCheckpoint() && !foundNewWeapon )
+ SetWeaponCooldownsForTitanLoadoutSwitch( player, cooldowns )
+ }
+
+ Assert( player.GetOffhandWeapon( OFFHAND_SPECIAL ) == null || !player.GetOffhandWeapon( OFFHAND_SPECIAL ).HasMod( "npc_normal_difficulty" ), "Player should never have mod npc_normal_difficulty" )
+ }
+#endif // #if HAS_TITAN_WEAPON_SWAPPING
+
+ foreach ( callbackFunc in file.playerInventoryChangedCallbacks )
+ {
+ callbackFunc( player )
+ }
+}
+
+void function CodeCallback_OnEntityChangedTeam( entity ent )
+{
+ string className = ent.GetClassName()
+ if ( !( className in svGlobal.onEntityChangedTeamCallbacks ) )
+ return
+
+ // Added via AddCallback_EntityChangedTeam
+ foreach ( callbackFunc in svGlobal.onEntityChangedTeamCallbacks[ className ] )
+ {
+ callbackFunc( ent )
+ }
+}
+
+//=============================
+// Player movement callbacks
+//=============================
+
+void function AddPlayerMovementEventCallback( entity player, int playerMovementEvent, void functionref( entity player ) callbackFunc )
+{
+ if ( !player.GetSendMovementCallbacks() )
+ player.SetSendMovementCallbacks( true )
+
+ table<int, array<void functionref( entity )> > callbackTable = player.p.playerMovementEventCallbacks
+
+ if ( ! ( playerMovementEvent in callbackTable ) )
+ callbackTable[ playerMovementEvent ] <- []
+
+ Assert( !callbackTable[ playerMovementEvent ].contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddPlayerMovementEventCallback for player " + player.GetPlayerName() )
+ callbackTable[ playerMovementEvent ].append( callbackFunc )
+}
+
+void function RemovePlayerMovementEventCallback( entity player, int playerMovementEvent, void functionref( entity player ) callbackFunc )
+{
+ table<int, array<void functionref( entity )> > callbackTable = player.p.playerMovementEventCallbacks
+
+ Assert( playerMovementEvent in callbackTable )
+
+ callbackTable[ playerMovementEvent ].fastremovebyvalue( callbackFunc )
+
+ if ( callbackTable[ playerMovementEvent ].len() == 0 )
+ {
+ //printt( "No more callbacks for playerMovementEvent: " + playerMovementEvent + ", removing array of functions" )
+ delete callbackTable[ playerMovementEvent ]
+ }
+
+ if ( callbackTable.len() == 0 )
+ {
+ //printt( "No more playerMovementEventCallbacks for player : " + player + ", make player not get movementcallbacks anymore." )
+ player.SetSendMovementCallbacks( false )
+ }
+}
+
+void function CodeCallback_OnPlayerJump( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.JUMP in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Jump")
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.JUMP ] )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerDoubleJump( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.DOUBLE_JUMP in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Double Jump")
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.DOUBLE_JUMP ] )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerDodge( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.DODGE in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Dodge" )
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.DODGE ] )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerLeaveGround( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.LEAVE_GROUND in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Left Ground")
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.LEAVE_GROUND ] )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerTouchGround( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.TOUCH_GROUND in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Touch Ground")
+
+ array<void functionref(entity)> callbacks = clone player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.TOUCH_GROUND ]
+
+ //Run actual functions
+ foreach( callbackFunc in callbacks )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerMantle( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.MANTLE in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Mantle")
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.MANTLE ] )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerBeginWallrun( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.BEGIN_WALLRUN in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Wallrun Begin")
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.BEGIN_WALLRUN ] )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerEndWallrun( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.END_WALLRUN in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Wallrun End")
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.END_WALLRUN ] )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerBeginWallhang( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.BEGIN_WALLHANG in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Wallhang Begin")
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.BEGIN_WALLHANG ] )
+ callbackFunc( player )
+}
+
+void function CodeCallback_OnPlayerEndWallhang( entity player )
+{
+ if ( ! ( ePlayerMovementEvents.END_WALLHANG in player.p.playerMovementEventCallbacks ) )
+ return
+
+ //printt( "Player Wallhang End")
+
+ //Run actual functions
+ foreach( callbackFunc in player.p.playerMovementEventCallbacks[ ePlayerMovementEvents.END_WALLHANG ] )
+ callbackFunc( player )
+}
+
+
+void function AddCallback_OnPlayerInventoryChanged( void functionref( entity ) callbackFunc )
+{
+ file.playerInventoryChangedCallbacks.append( callbackFunc )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_codecallbacks_player_input.gnut b/Northstar.CustomServers/scripts/vscripts/_codecallbacks_player_input.gnut
new file mode 100644
index 000000000..120620566
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_codecallbacks_player_input.gnut
@@ -0,0 +1,552 @@
+//TODO: Add "retrigger if held" functionality to stick
+
+
+// Player input callbacks
+global function AddButtonPressedPlayerInputCallback
+global function RemoveButtonPressedPlayerInputCallback
+global function AddButtonReleasedPlayerInputCallback
+global function RemoveButtonReleasedPlayerInputCallback
+global function AddPlayerHeldButtonEventCallback
+global function RemovePlayerHeldButtonEventCallback
+
+global function AddPlayerPressedForwardCallback
+global function RemovePlayerPressedForwardCallback
+global function AddPlayerPressedBackCallback
+global function RemovePlayerPressedBackCallback
+global function AddPlayerPressedLeftCallback
+global function RemovePlayerPressedLeftCallback
+global function AddPlayerPressedRightCallback
+global function RemovePlayerPressedRightCallback
+
+global function DEBUG_AddSprintJumpHeldMeleePressed //Only for reference on how to do more complicated input events
+
+global function CodeCallback_OnPlayerInputCommandChanged
+global function CodeCallback_OnPlayerInputAxisChanged
+
+void function CodeCallback_OnPlayerInputCommandChanged( entity player, int cmdsHeld, int cmdsPressed, int cmdsReleased )
+{
+ foreach( callbackStruct in player.p.playerInputEventCallbacks )
+ {
+ if ( PlayerInputsMatchCallbackInputs( cmdsHeld, cmdsPressed, cmdsReleased, callbackStruct ) )
+ callbackStruct.callbackFunc( player )
+ }
+
+ foreach( callbackStruct in player.p.playerHeldButtonEventCallbacks )
+ {
+ if ( cmdsPressed & callbackStruct.buttonHeld )
+ thread RunHeldCallbackAfterTimePasses( player, callbackStruct )
+ }
+
+ foreach( callbackStruct in player.p.playerHeldButtonEventCallbacks )
+ {
+ if ( cmdsReleased & callbackStruct.buttonHeld )
+ {
+ string endSignalName = GetEndSignalNameForHeldButtonCallback( callbackStruct )
+ player.Signal( endSignalName ) //Send signal to kill corresponding RunHeldCallbackAfterTimePasses
+ }
+ }
+}
+
+void function CodeCallback_OnPlayerInputAxisChanged( entity player, float horizAxis, float vertAxis )
+{
+ //printt( "Axis Changed: X: " + horizAxis + " Y: " + vertAxis )
+
+ foreach( callbackStruct in player.p.playerInputAxisEventCallbacks )
+ {
+ if ( ShouldRunPlayerInputAxisCallbackFunc( horizAxis, vertAxis, callbackStruct ) )
+ RunPlayerInputAxisCallbackFunc( player, callbackStruct )
+ }
+}
+
+void function AddPlayerInputEventCallback_Internal( entity player, PlayerInputEventCallbackStruct inputCallbackStruct ) //Not really meant to be used directly unless you know what you're doing! Use utility functions like AddButtonPressedPlayerInputCallback instead
+{
+ if ( !player.GetSendInputCallbacks() )
+ player.SetSendInputCallbacks( true )
+
+ Assert( !InputEventCallbackAlreadyExists( player, inputCallbackStruct ), " Adding the same inputEventCallback " + string ( inputCallbackStruct.callbackFunc ) + " with the same inputs!" )
+
+ player.p.playerInputEventCallbacks.append( inputCallbackStruct )
+}
+
+void function RemovePlayerInputEventCallback_Internal( entity player, PlayerInputEventCallbackStruct inputCallbackStruct ) //Not really meant to be used directly unless you know what you're doing! Use utility functions like RemoveButtonPressedPlayerInputCallback instead
+{
+ for( int i = player.p.playerInputEventCallbacks.len() - 1; i >= 0; --i ) //Removing from the end of an array, so it's fine to remove as we go along
+ {
+ if ( InputCallbackStructsAreTheSame( player.p.playerInputEventCallbacks[i], inputCallbackStruct ) )
+ {
+ player.p.playerInputEventCallbacks.remove( i )
+ break
+ }
+
+ }
+
+ TurnOffInputCallbacksIfNecessary( player )
+}
+
+bool function InputEventCallbackAlreadyExists( entity player, PlayerInputEventCallbackStruct inputCallbackStruct )
+{
+ foreach( existingCallbackStruct in player.p.playerInputEventCallbacks )
+ {
+ if ( InputCallbackStructsAreTheSame( existingCallbackStruct, inputCallbackStruct ) )
+ return true
+ }
+
+ return false
+}
+
+void function AddPlayerHeldButtonEventCallback( entity player, int buttonEnum, void functionref( entity player ) callbackFunc, float buttonHeldTime = 1.0 )
+{
+ if ( !player.GetSendInputCallbacks() )
+ player.SetSendInputCallbacks( true )
+
+ PlayerHeldButtonEventCallbackStruct callbackStruct
+ callbackStruct.buttonHeld = buttonEnum
+ callbackStruct.callbackFunc = callbackFunc
+ callbackStruct.timeHeld = buttonHeldTime
+
+ Assert( !HeldEventCallbackAlreadyExists( player, callbackStruct ), " Adding the same heldEventCallback " + string ( callbackStruct.callbackFunc ) + " with the same parameters!" )
+
+ string endSignalName = GetEndSignalNameForHeldButtonCallback( callbackStruct )
+ RegisterSignal( endSignalName )//Signal meant to kill the waiting thread if button is released. Note that registering the same signal multiple times seems to be ok.
+
+ player.p.playerHeldButtonEventCallbacks.append( callbackStruct )
+}
+
+
+void function RemovePlayerHeldButtonEventCallback( entity player, int buttonEnum, void functionref( entity player ) callbackFunc, float buttonHeldTime = 1.0 )
+{
+ PlayerHeldButtonEventCallbackStruct callbackStruct
+ callbackStruct.buttonHeld = buttonEnum
+ callbackStruct.callbackFunc = callbackFunc
+ callbackStruct.timeHeld = buttonHeldTime
+
+ for( int i = player.p.playerHeldButtonEventCallbacks.len() - 1; i >= 0; --i ) //Removing from the end of an array, so it's fine to remove as we go along
+ {
+ if ( HeldButtonCallbackStructsAreTheSame( player.p.playerHeldButtonEventCallbacks[i], callbackStruct ) )
+ player.p.playerHeldButtonEventCallbacks.remove( i )
+ }
+
+ TurnOffInputCallbacksIfNecessary( player )
+}
+
+bool function HeldEventCallbackAlreadyExists( entity player, PlayerHeldButtonEventCallbackStruct callbackStruct )
+{
+ foreach( existingCallbackStruct in player.p.playerHeldButtonEventCallbacks )
+ {
+ if ( HeldButtonCallbackStructsAreTheSame( existingCallbackStruct, callbackStruct ) )
+ return true
+ }
+
+ return false
+}
+
+void function DEBUG_PlayerHeldSprintJumpAndPressedMelee( entity player ) //Debug function, just an example on how to hook up more complicated InputEvents
+{
+ PrintFunc()
+}
+
+void function DEBUG_AddSprintJumpHeldMeleePressed() //Debug function, just an example on how to hook up more complicated InputEvents
+{
+ PlayerInputEventCallbackStruct callbackStruct
+ callbackStruct.cmdsHeldBitMask = IN_SPEED | IN_JUMP
+ callbackStruct.cmdsPressedBitMask = IN_MELEE
+ callbackStruct.callbackFunc = DEBUG_PlayerHeldSprintJumpAndPressedMelee
+
+ AddPlayerInputEventCallback_Internal( GetPlayerArray()[0], callbackStruct )
+}
+
+//List of valid inputs are in sh_constants for reference
+void function AddButtonPressedPlayerInputCallback( entity player, int buttonEnum, void functionref( entity player ) callbackFunc )
+{
+ PlayerInputEventCallbackStruct callbackStruct
+ callbackStruct.cmdsPressedBitMask = buttonEnum
+ callbackStruct.callbackFunc = callbackFunc
+
+ AddPlayerInputEventCallback_Internal( player, callbackStruct )
+}
+
+void function RemoveButtonPressedPlayerInputCallback( entity player, int buttonEnum, void functionref( entity player ) callbackFunc )
+{
+ PlayerInputEventCallbackStruct callbackStruct
+ callbackStruct.cmdsPressedBitMask = buttonEnum
+ callbackStruct.callbackFunc = callbackFunc
+
+ RemovePlayerInputEventCallback_Internal( player, callbackStruct )
+}
+
+void function AddButtonReleasedPlayerInputCallback( entity player, int buttonEnum, void functionref( entity player ) callbackFunc )
+{
+ PlayerInputEventCallbackStruct callbackStruct
+ callbackStruct.cmdsReleasedBitMask = buttonEnum
+ callbackStruct.callbackFunc = callbackFunc
+
+ AddPlayerInputEventCallback_Internal( player, callbackStruct )
+}
+
+void function RemoveButtonReleasedPlayerInputCallback( entity player, int buttonEnum, void functionref( entity player ) callbackFunc )
+{
+ PlayerInputEventCallbackStruct callbackStruct
+ callbackStruct.cmdsReleasedBitMask = buttonEnum
+ callbackStruct.callbackFunc = callbackFunc
+
+ RemovePlayerInputEventCallback_Internal( player, callbackStruct )
+}
+
+
+void function RunHeldCallbackAfterTimePasses( entity player, PlayerHeldButtonEventCallbackStruct callbackStruct )
+{
+ string endSignalName = GetEndSignalNameForHeldButtonCallback( callbackStruct )
+ player.EndSignal( endSignalName )
+ player.EndSignal( "OnDeath" )
+
+ /*OnThreadEnd(
+ function() : ( )
+ {
+ printt( "function ended at: " + Time() )
+
+ }
+ )
+
+ printt( "Pre wait time: " + Time() )*/
+ wait callbackStruct.timeHeld
+
+ //printt( "Post wait time: " + Time() )
+
+ if ( !IsValid( player ) )
+ return
+
+ callbackStruct.callbackFunc( player )
+}
+
+string function GetEndSignalNameForHeldButtonCallback( PlayerHeldButtonEventCallbackStruct callbackStruct )
+{
+ return ( "Button" + callbackStruct.buttonHeld + "Released_EndSignal" )
+}
+
+bool function InputCallbackStructsAreTheSame( PlayerInputEventCallbackStruct callbackStruct1, PlayerInputEventCallbackStruct callbackStruct2 ) //Really just a comparison function because == does a compare by reference, not a compare by value
+{
+ if ( callbackStruct1.cmdsPressedBitMask != callbackStruct2.cmdsPressedBitMask )
+ return false
+
+ if ( callbackStruct1.cmdsHeldBitMask != callbackStruct2.cmdsHeldBitMask )
+ return false
+
+ if ( callbackStruct1.cmdsReleasedBitMask != callbackStruct2.cmdsReleasedBitMask )
+ return false
+
+ if ( callbackStruct1.callbackFunc != callbackStruct2.callbackFunc )
+ return false
+
+ return true
+}
+
+bool function PlayerInputsMatchCallbackInputs( int cmdsHeld, int cmdsPressed, int cmdsReleased, PlayerInputEventCallbackStruct callbackStruct )
+{
+ if ( !HasBitMask( cmdsHeld, callbackStruct.cmdsHeldBitMask ) )
+ return false
+
+ if ( !HasBitMask( cmdsPressed, callbackStruct.cmdsPressedBitMask ) )
+ return false
+
+ if ( !HasBitMask( cmdsReleased, callbackStruct.cmdsReleasedBitMask ) )
+ return false
+
+ return true
+}
+
+bool function HeldButtonCallbackStructsAreTheSame( PlayerHeldButtonEventCallbackStruct struct1, PlayerHeldButtonEventCallbackStruct struct2 ) //Really just a comparison function because == does a compare by reference, not a compare by value
+{
+ if ( struct1.buttonHeld != struct2.buttonHeld )
+ return false
+
+ if ( struct1.callbackFunc != struct2.callbackFunc )
+ return false
+
+ if ( struct1.timeHeld != struct2.timeHeld )
+ return false
+
+ return true
+
+}
+
+void function TurnOffInputCallbacksIfNecessary( entity player )
+{
+ if ( player.p.playerInputEventCallbacks.len() > 0 )
+ return
+
+ if ( player.p.playerHeldButtonEventCallbacks.len() > 0 )
+ return
+
+ if ( player.p.playerInputAxisEventCallbacks.len() > 0 )
+ return
+
+ //printt( "No more input callbacks, SetInputCallbacks to false" )
+ player.SetSendInputCallbacks( false )
+}
+
+PlayerInputAxisEventCallbackStruct function MakePressedForwardCallbackStruct()
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct
+ callbackStruct.horizAxisMinThreshold = -1.0
+ callbackStruct.horizAxisMaxThreshold = 1.0
+ callbackStruct.vertAxisMinThreshold = 0.4
+ callbackStruct.vertAxisMaxThreshold = 1.0
+
+ return callbackStruct
+}
+
+void function AddPlayerPressedForwardCallback( entity player, bool functionref( entity player ) callbackFunc, float debounceTime = 2.0 )
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct = MakePressedForwardCallbackStruct()
+ callbackStruct.debounceTime = debounceTime
+ callbackStruct.callbackFunc = callbackFunc
+
+ AddPlayerInputAxisEventCallback_Internal( player, callbackStruct )
+}
+
+void function RemovePlayerPressedForwardCallback( entity player, bool functionref( entity player ) callbackFunc, float debounceTime = 2.0 )
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct = MakePressedForwardCallbackStruct()
+ callbackStruct.debounceTime = debounceTime
+ callbackStruct.callbackFunc = callbackFunc
+
+ RemovePlayerInputAxisEventCallback_Internal( player, callbackStruct )
+}
+
+PlayerInputAxisEventCallbackStruct function MakePressedBackCallbackStruct()
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct
+ callbackStruct.horizAxisMinThreshold = -1.0
+ callbackStruct.horizAxisMaxThreshold = 1.0
+ callbackStruct.vertAxisMinThreshold = -1.0
+ callbackStruct.vertAxisMaxThreshold = -0.4
+
+ return callbackStruct
+}
+
+void function AddPlayerPressedBackCallback( entity player, bool functionref( entity player ) callbackFunc, float debounceTime = 2.0 )
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct = MakePressedBackCallbackStruct()
+ callbackStruct.debounceTime = debounceTime
+ callbackStruct.callbackFunc = callbackFunc
+
+ AddPlayerInputAxisEventCallback_Internal( player, callbackStruct )
+
+}
+
+void function RemovePlayerPressedBackCallback( entity player, bool functionref( entity player ) callbackFunc, float debounceTime = 2.0 )
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct = MakePressedBackCallbackStruct()
+ callbackStruct.debounceTime = debounceTime
+ callbackStruct.callbackFunc = callbackFunc
+
+ RemovePlayerInputAxisEventCallback_Internal( player, callbackStruct )
+
+}
+
+PlayerInputAxisEventCallbackStruct function MakePressedLeftCallbackStruct()
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct
+ callbackStruct.horizAxisMinThreshold = -1.0
+ callbackStruct.horizAxisMaxThreshold = -0.4
+ callbackStruct.vertAxisMinThreshold = -1.0
+ callbackStruct.vertAxisMaxThreshold = 1.0
+
+ return callbackStruct
+}
+
+void function AddPlayerPressedLeftCallback( entity player, bool functionref( entity player ) callbackFunc, float debounceTime = 2.0 )
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct = MakePressedLeftCallbackStruct()
+ callbackStruct.debounceTime = debounceTime
+ callbackStruct.callbackFunc = callbackFunc
+
+ AddPlayerInputAxisEventCallback_Internal( player, callbackStruct )
+}
+
+void function RemovePlayerPressedLeftCallback( entity player, bool functionref( entity player ) callbackFunc, float debounceTime = 2.0 )
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct = MakePressedLeftCallbackStruct()
+ callbackStruct.debounceTime = debounceTime
+ callbackStruct.callbackFunc = callbackFunc
+
+ RemovePlayerInputAxisEventCallback_Internal( player, callbackStruct )
+}
+
+PlayerInputAxisEventCallbackStruct function MakePressedRightCallbackStruct()
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct
+ callbackStruct.horizAxisMinThreshold = 0.4
+ callbackStruct.horizAxisMaxThreshold = 1.0
+ callbackStruct.vertAxisMinThreshold = -1.0
+ callbackStruct.vertAxisMaxThreshold = 1.0
+
+ return callbackStruct
+}
+
+void function AddPlayerPressedRightCallback( entity player, bool functionref( entity player ) callbackFunc, float debounceTime = 2.0 )
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct = MakePressedRightCallbackStruct()
+ callbackStruct.debounceTime = debounceTime
+ callbackStruct.callbackFunc = callbackFunc
+
+ AddPlayerInputAxisEventCallback_Internal( player, callbackStruct )
+}
+
+void function RemovePlayerPressedRightCallback( entity player, bool functionref( entity player ) callbackFunc, float debounceTime = 2.0 )
+{
+ PlayerInputAxisEventCallbackStruct callbackStruct = MakePressedRightCallbackStruct()
+ callbackStruct.debounceTime = debounceTime
+ callbackStruct.callbackFunc = callbackFunc
+
+ RemovePlayerInputAxisEventCallback_Internal( player, callbackStruct )
+}
+
+
+void function AddPlayerInputAxisEventCallback_Internal( entity player, PlayerInputAxisEventCallbackStruct callbackStruct )
+{
+ if ( !player.GetSendInputCallbacks() )
+ player.SetSendInputCallbacks( true )
+
+ Assert( IsValidPlayerInputAxisEventCallbackStruct( callbackStruct ) )
+
+ Assert( !InputAxisEventCallbackAlreadyExists( player, callbackStruct ), " Adding the same inputEventCallback " + string ( callbackStruct.callbackFunc ) + " with the same inputs!" )
+
+ player.p.playerInputAxisEventCallbacks.append( callbackStruct )
+}
+
+void function RemovePlayerInputAxisEventCallback_Internal( entity player, PlayerInputAxisEventCallbackStruct callbackStruct )
+{
+ for( int i = player.p.playerInputAxisEventCallbacks.len() - 1; i >= 0; --i ) //Removing from the end of an array, so it's fine to remove as we go along
+ {
+ if ( InputAxisCallbackStructsAreTheSame( player.p.playerInputAxisEventCallbacks[i], callbackStruct ) )
+ {
+ player.p.playerInputAxisEventCallbacks.remove( i )
+ break //Can break since we shouldn't have more than one callbackstruct that's exactly the same
+ }
+ }
+
+ TurnOffInputCallbacksIfNecessary( player )
+}
+
+bool function InputAxisEventCallbackAlreadyExists( entity player, PlayerInputAxisEventCallbackStruct callbackStruct )
+{
+ foreach( existingStruct in player.p.playerInputAxisEventCallbacks )
+ {
+ if ( InputAxisCallbackStructsAreTheSame( existingStruct, callbackStruct ) )
+ return true
+ }
+
+ return false
+}
+
+bool function InputAxisCallbackStructsAreTheSame( PlayerInputAxisEventCallbackStruct callbackStruct1, PlayerInputAxisEventCallbackStruct callbackStruct2 ) //Really just a comparison function because == does a compare by reference, not a compare by value
+{
+ if ( callbackStruct1.horizAxisMinThreshold != callbackStruct2.horizAxisMinThreshold )
+ return false
+
+ if ( callbackStruct1.horizAxisMaxThreshold != callbackStruct2.horizAxisMaxThreshold )
+ return false
+
+ if ( callbackStruct1.vertAxisMinThreshold != callbackStruct2.vertAxisMinThreshold )
+ return false
+
+ if ( callbackStruct1.vertAxisMaxThreshold != callbackStruct2.vertAxisMaxThreshold )
+ return false
+
+ if ( callbackStruct1.debounceTime != callbackStruct2.debounceTime )
+ return false
+
+ if ( callbackStruct1.callbackFunc != callbackStruct2.callbackFunc )
+ return false
+
+ return true
+}
+
+bool function ShouldRunPlayerInputAxisCallbackFunc( float horizAxis, float vertAxis, PlayerInputAxisEventCallbackStruct callbackStruct )
+{
+ if ( horizAxis < callbackStruct.horizAxisMinThreshold )
+ return false
+
+ if ( horizAxis > callbackStruct.horizAxisMaxThreshold )
+ return false
+
+ if ( vertAxis < callbackStruct.vertAxisMinThreshold )
+ return false
+
+ if ( vertAxis > callbackStruct.vertAxisMaxThreshold )
+ return false
+
+ if ( Time() < callbackStruct.lastTriggeredTime + callbackStruct.debounceTime )
+ return false
+
+ return true
+
+}
+
+bool function IsValidPlayerInputAxisEventCallbackStruct( PlayerInputAxisEventCallbackStruct callbackStruct )
+{
+ //Make sure thresholds are within valid ranges
+ if ( callbackStruct.horizAxisMinThreshold < -1.0 )
+ return false
+
+ if ( callbackStruct.horizAxisMinThreshold > 1.0 )
+ return false
+
+ if ( callbackStruct.horizAxisMaxThreshold < -1.0 )
+ return false
+
+ if ( callbackStruct.horizAxisMaxThreshold > 1.0 )
+ return false
+
+ if ( callbackStruct.vertAxisMinThreshold < -1.0 )
+ return false
+
+ if ( callbackStruct.vertAxisMinThreshold > 1.0 )
+ return false
+
+ if ( callbackStruct.vertAxisMaxThreshold < 1.0 )
+ return false
+
+ if ( callbackStruct.vertAxisMaxThreshold > 1.0 )
+ return false
+
+ //Make sure min and maxes are correct relative to each other
+ if ( callbackStruct.horizAxisMinThreshold > callbackStruct.horizAxisMaxThreshold )
+ return false
+
+ if ( callbackStruct.vertAxisMinThreshold > callbackStruct.vertAxisMaxThreshold )
+ return false
+
+ return true
+}
+
+void function RunPlayerInputAxisCallbackFunc( entity player, PlayerInputAxisEventCallbackStruct callbackStruct )
+{
+ bool callbackResult = callbackStruct.callbackFunc( player )
+ if ( callbackResult )
+ {
+ callbackStruct.lastTriggeredTime = Time()
+
+ if ( callbackStruct.debounceTime > 0 )
+ thread RunInputAxisCallbackAfterTimePasses( player, callbackStruct ) //Note that this has the potential to call RunPlayerInputAxisCallbackFunc again
+ }
+}
+
+void function RunInputAxisCallbackAfterTimePasses( entity player, PlayerInputAxisEventCallbackStruct callbackStruct )
+{
+ player.EndSignal( "OnDeath" )
+
+ wait callbackStruct.debounceTime
+ WaitFrame() //Time to wait isn't exact due to floating point precision, so wait an extra frame.
+
+
+ if ( !IsValid( player ) )
+ return
+
+ float horizAxis = player.GetInputAxisRight()
+ float vertAxis = player.GetInputAxisForward()
+
+ if ( ShouldRunPlayerInputAxisCallbackFunc( horizAxis, vertAxis, callbackStruct ) )
+ RunPlayerInputAxisCallbackFunc( player, callbackStruct ) //Note that this has the potential to call RunInputAxisCallbackAfterTimePasses again
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_control_panel.gnut b/Northstar.CustomServers/scripts/vscripts/_control_panel.gnut
new file mode 100644
index 000000000..f9d7a4ff8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_control_panel.gnut
@@ -0,0 +1,727 @@
+untyped
+
+global function ControlPanel_Init
+
+global function InitControlPanelUseFuncTable
+global function AddControlPanelUseFuncTable
+global function SetControlPanelPrompts
+global function SetPanelUsableToEnemies
+global function PanelFlipsToPlayerTeamAndUsableByEnemies
+global function GetAllControlPanels
+global function CaptureAllAvailableControlPanels
+global function GetPanelUseEnts
+global function PlayIncomingFX
+global function SetControlPanelUseFunc
+global function ClearControlPanelUseFuncs
+
+const INCOMING_SPAWN_FX = $"P_ar_titan_droppoint"
+
+struct
+{
+ array<entity> controlPanels
+} file
+
+//=========================================================
+// Control Panels
+//
+//=========================================================
+
+//////////////////////////////////////////////////////////////////////
+function ControlPanel_Init()
+{
+ PrecacheModel( $"models/communication/terminal_usable_imc_01.mdl" )
+ PrecacheParticleSystem( INCOMING_SPAWN_FX )
+
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_disabled/console_disabled" )
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_f_deploy/console_f_deploy" )
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_f_search/console_f_search" )
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_f_active/console_f_active" )
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_f_repair/console_f_repair" )
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_e_deploy/console_e_deploy" )
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_e_search/console_e_search" )
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_e_active/console_e_active" )
+ //PrecacheMaterial( $"vgui/hud/control_panel/console_e_repair/console_e_repair" )
+
+ AddSpawnCallback( "prop_control_panel", OnPanelSpawn )
+
+
+ RegisterSignal( "PanelReprogrammed" )
+ RegisterSignal( "PanelReprogram_Success" )
+ RegisterSignal( "OnContinousUseStopped" )
+}
+
+//////////////////////////////////////////////////////////
+function GameModeRemovePanel( ent )
+{
+ local keepUndefined
+ string gameMode = GameRules_GetGameMode()
+
+ switch ( gameMode )
+ {
+ // if we are in this game mode, then don't keep undefined panels
+ default:
+ keepUndefined = true
+ gameMode = TEAM_DEATHMATCH
+ break
+ }
+
+ local gamemodeKey = "gamemode_" + gameMode
+
+ if ( ent.HasKey( gamemodeKey ) && ent.kv[gamemodeKey] == "1" )
+ {
+ // the key exists and it's true so keep it
+ return
+ }
+
+ if ( !ent.HasKey( gamemodeKey ) && keepUndefined )
+ {
+ // the key doesn't exist but keepUndefined is true so still keep it
+ return
+ }
+
+ ent.Destroy()
+}
+
+
+//////////////////////////////////////////////////////////////////////
+void function OnPanelSpawn( entity panel )
+{
+ Assert( panel.GetModelName() == $"models/communication/terminal_usable_imc_01.mdl" )
+
+ thread OnPanelSpawn_Internal( panel )
+}
+
+//////////////////////////////////////////////////////////////////////
+void function OnPanelSpawn_Internal( entity panel )
+{
+ panel.EndSignal( "OnDestroy" )
+ GameModeRemovePanel( panel )
+
+ panel.s.useFuncArray <- []
+
+ Assert( IsValid( panel ), "Invalid panel " + panel )
+ panel.EndSignal( "OnDestroy" )
+
+ file.controlPanels.append( panel )
+
+ thread PanelUpdateUsability( panel )
+
+ panel.useFunction = ControlPanel_CanUseFunction
+
+ panel.s.leechTimeNormal <- 3.0
+ panel.s.leechTimeFast <- 1.1
+
+ panel.kv.forceVisibleInPhaseShift = true
+
+ panel.s.onPlayerFinishesUsing_func <- null
+ panel.s.hackedOnce <- false
+ //Used in Frontier Mode for knowing if NPCs are hacking the panel.
+ panel.s.hackingEntity <- null
+
+ panel.s.remoteTurret <- null
+ panel.s.remoteTurretStartFunc <- null
+
+ #if HAS_PANEL_HIGHLIGHT
+ int contextId = 0
+ panel.Highlight_SetFunctions( contextId, 0, true, HIGHLIGHT_OUTLINE_INTERACT_BUTTON, 1, 0, false )
+ panel.Highlight_SetParam( contextId, 0, HIGHLIGHT_COLOR_INTERACT )
+ panel.Highlight_SetCurrentContext( contextId )
+ panel.Highlight_ShowInside( 0.0 )
+ panel.Highlight_ShowOutline( 0.0 )
+ #endif
+
+ string flag
+ if ( panel.HasKey( "scr_flag_set" ) )
+ {
+ string editorVal = expect string( panel.kv.scr_flag_set )
+ if ( editorVal != "" )
+ {
+ flag = editorVal
+ FlagInit( flag )
+ }
+ }
+
+ string hackFlag
+ if ( panel.HasKey( "scr_flag_hack_started" ) )
+ {
+ string editorVal = expect string( panel.kv.scr_flag_hack_started )
+ if ( editorVal != "" )
+ {
+ hackFlag = editorVal
+ FlagInit( hackFlag )
+ }
+ }
+
+ bool toggleFlag = false
+ if ( panel.HasKey( "toggleFlagWhenHacked" ) )
+ toggleFlag = panel.kv.toggleFlagWhenHacked == "1"
+
+ bool singleUse = false
+ if ( panel.HasKey( "singleUse" ) )
+ singleUse = panel.kv.singleUse.tointeger() > 0
+
+ string requiredFlag = ""
+ if ( panel.HasKey( "scr_flagRequired" ) && panel.GetValueForKey( "scr_flagRequired" ) != "" )
+ requiredFlag = panel.GetValueForKey( "scr_flagRequired" )
+
+ for ( ;; )
+ {
+ var player = panel.WaitSignal( "OnPlayerUse" ).player
+ Assert( player.IsPlayer() )
+ expect entity( player )
+
+ if ( !IsAlive( player ) || player.IsTitan() )
+ continue
+
+ // Panel might be disabled with a flag, so don't allow a hack. We don't disable usability though, because we want use prompts still, with custom hint text
+ if ( (requiredFlag != "") && !Flag( requiredFlag ) )
+ continue
+
+ // already a user?
+ if ( IsAlive( panel.GetBossPlayer() ) )
+ continue
+
+ if ( !panel.useFunction( player, panel ) )
+ {
+ //play buzzer sound
+ //EmitSoundOnEntity( panel, "Operator.Ability_offline" )
+ wait 1
+ continue
+ }
+
+ waitthread PlayerUsesControlPanel( player, panel, flag, toggleFlag, hackFlag )
+
+ if ( singleUse && (panel.s.hackedOnce == true) )
+ break
+ }
+
+ // control panel no longer usable
+ panel.UnsetUsable()
+ panel.SetUsePrompts( "", "" )
+ #if HAS_PANEL_HIGHLIGHT
+ panel.Highlight_HideInside( 1.0 )
+ panel.Highlight_HideOutline( 1.0 )
+ #endif
+}
+
+void function PanelUpdateUsability( entity panel )
+{
+ panel.EndSignal( "OnDestroy" )
+
+ //Default, set it usable by everyone
+ panel.SetUsableByGroup( "pilot" )
+ panel.SetUsePrompts( "#DEFAULT_HACK_HOLD_PROMPT", "#DEFAULT_HACK_PRESS_PROMPT" )
+
+ if ( !panel.HasKey( "scr_flagRequired" ) )
+ return
+
+ string flag = panel.GetValueForKey( "scr_flagRequired" )
+
+ if ( flag == "" )
+ return
+
+ FlagInit( flag )
+
+ string disabledUsePrompt = ""
+ if ( panel.HasKey( "disabledHintString" ) )
+ disabledUsePrompt = panel.GetValueForKey( "disabledHintString" )
+
+ while(true)
+ {
+ panel.SetUsePrompts( disabledUsePrompt, disabledUsePrompt )
+ FlagWait( flag )
+ panel.SetUsePrompts( "#DEFAULT_HACK_HOLD_PROMPT", "#DEFAULT_HACK_PRESS_PROMPT" )
+ FlagWaitClear( flag )
+ }
+}
+
+void function PlayIncomingFX( vector origin, int teamNum )
+{
+ wait 1.50
+ EmitSoundAtPosition( teamNum, origin, "Titan_1P_Warpfall_Start" )
+
+ local colorVec = Vector( 0, 255, 0 )
+ entity cpoint = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpoint, UniqueString( "pickup_controlpoint" ) )
+ DispatchSpawn( cpoint )
+ cpoint.SetOrigin( colorVec )
+ entity glowFX = PlayFXWithControlPoint( INCOMING_SPAWN_FX, origin, cpoint, -1, null, null, C_PLAYFX_LOOP )
+
+ OnThreadEnd(
+ function() : ( glowFX, cpoint )
+ {
+ if ( IsValid( glowFX ) )
+ glowFX.Destroy()
+ if ( IsValid( cpoint ) )
+ cpoint.Destroy()
+ }
+ )
+
+ wait 1.25
+}
+
+void function PlayerUsesControlPanel( entity player, entity panel, string flag, bool toggleFlag, string hackFlag )
+{
+ thread PlayerProgramsControlPanel( panel, player, hackFlag )
+
+ local result = panel.WaitSignal( "PanelReprogrammed" )
+
+ if ( result.success )
+ {
+ local panelEHandle = IsValid( panel ) ? panel.GetEncodedEHandle() : null
+ array<entity> players = GetPlayerArray()
+ foreach( player in players )
+ {
+ Remote_CallFunction_Replay( player, "ServerCallback_ControlPanelRefresh", panelEHandle )
+ }
+
+ RunPanelUseFunctions( panel, player )
+ panel.Signal( "PanelReprogram_Success" )
+ if ( flag != "" )
+ {
+ if ( toggleFlag && Flag( flag ) )
+ FlagClear( flag )
+ else
+ {
+ FlagSet( flag )
+ }
+ }
+
+ panel.s.hackedOnce = true
+ }
+ else
+ {
+ //play buzzer sound
+ //EmitSoundOnEntity( panel, "Operator.Ability_offline" )
+ WaitFrame() // arbitrary delay so that you can't restart the leech instantly after failing
+ if ( hackFlag != "" )
+ FlagClear( hackFlag )
+ }
+}
+
+function RunPanelUseFunctions( panel, player )
+{
+ if ( panel.s.useFuncArray.len() <= 0 )
+ return
+
+ foreach ( useFuncTable in clone panel.s.useFuncArray )
+ {
+ if ( useFuncTable.useEnt == null )
+ useFuncTable.useFunc( panel, player )
+ else
+ useFuncTable.useFunc( panel, player, useFuncTable.useEnt )
+ }
+}
+
+function SetControlPanelUseFunc( panel, func, ent = null )
+{
+ local Table = InitControlPanelUseFuncTable()
+ Table.useFunc <- func
+ Table.useEnt <- ent
+ AddControlPanelUseFuncTable( panel, Table )
+}
+
+function ClearControlPanelUseFuncs( panel )
+{
+ panel.s.useFuncArray.clear()
+}
+
+//////////////////////////////////////////////////////////////////////
+void function PlayerProgramsControlPanel( entity panel, entity player, string hackFlag )
+{
+ Assert( IsAlive( player ) )
+
+ // need to wait here so that the panel script can start waiting for the PanelReprogrammed signal.
+ WaitFrame()
+
+ local action =
+ {
+ playerAnimation1pStart = "ptpov_data_knife_console_leech_start"
+ playerAnimation1pIdle = "ptpov_data_knife_console_leech_idle"
+ playerAnimation1pEnd = "ptpov_data_knife_console_leech_end"
+
+ playerAnimation3pStart = "pt_data_knife_console_leech_start"
+ playerAnimation3pIdle = "pt_data_knife_console_leech_idle"
+ playerAnimation3pEnd = "pt_data_knife_console_leech_end"
+
+ panelAnimation3pStart = "tm_data_knife_console_leech_start"
+ panelAnimation3pIdle = "tm_data_knife_console_leech_idle"
+ panelAnimation3pEnd = "tm_data_knife_console_leech_end"
+
+ direction = Vector( -1, 0, 0 )
+ }
+
+ #if HAS_PANEL_HIGHLIGHT
+ panel.Highlight_HideInside( 1.0 )
+ panel.Highlight_HideOutline( 1.0 )
+ #endif
+
+ local e = {}
+ e.success <- false
+ e.knives <- []
+
+ e.panelUsableValueToRestore <- panel.GetUsableValue()
+ e.startOrigin <- player.GetOrigin()
+ panel.SetBossPlayer( player )
+ panel.SetUsableValue( USABLE_BY_OWNER )
+
+ e.setIntruder <- false
+
+ e.finishedPanelOpen <- false
+ e.animViewLerpoutTime <- 0.3
+ e.doRequireUseButtonHeld <- true
+
+ player.ForceStand()
+ HolsterAndDisableWeapons( player ) //Do here instead of after doRequireUseButtonHeld check since DisableOffhandWeapons() is counter based, i.e. a call to DisableOffhandWeapons() must be matched with a call to EnableOffhandWeapons()
+
+ //
+ if ( panel.s.remoteTurret )
+ {
+ action.playerAnimation1pStart = "ptpov_data_knife_console_leech_remoteturret_start"
+ action.playerAnimation3pStart = "pt_data_knife_console_leech_remoteturret_start"
+ action.panelAnimation3pStart = "tm_data_knife_console_leech_remoteturret_start"
+
+ e.animViewLerpoutTime = 0.0
+ e.doRequireUseButtonHeld = false
+
+ panel.SetUsePrompts( "", "" )
+ }
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "ScriptAnimStop" )
+
+ OnThreadEnd
+ (
+ function() : ( e, player, panel )
+ {
+ if ( e.setIntruder )
+ level.nv.panelIntruder = null
+
+ if ( IsValid( player ) )
+ {
+ player.ClearAnimNearZ()
+ player.ClearParent()
+
+ // stop any running first person sequences
+ player.Anim_Stop()
+
+ if ( IsAlive( player ) )
+ PutEntityInSafeSpot( player, panel, null, expect vector( e.startOrigin ), player.GetOrigin() )
+
+ // done with first person anims
+ ClearPlayerAnimViewEntity( player, expect float( e.animViewLerpoutTime ) )
+ DeployAndEnableWeapons( player )
+ player.UnforceStand()
+
+ if ( player.ContextAction_IsLeeching() )
+ player.Event_LeechEnd()
+ }
+
+ if ( IsValid( panel ) )
+ {
+ // stop any running first person sequences
+ panel.Anim_Stop()
+ panel.Anim_Play( "ref" ) // close the hatch
+
+ // reset default usability
+ if ( !panel.s.remoteTurret || !e.finishedPanelOpen )
+ {
+ panel.ClearBossPlayer()
+ panel.SetUsableValue( e.panelUsableValueToRestore )
+ }
+
+ if ( !e.success )
+ {
+ #if HAS_PANEL_HIGHLIGHT
+ panel.Highlight_ShowInside( 1.0 )
+ panel.Highlight_ShowOutline( 1.0 )
+ #endif
+
+ panel.Signal( "PanelReprogrammed", { success = e.success } )
+ #if MP
+ local turret = GetMegaTurretLinkedToPanel( panel ) //CHIN: Control panels shouldn't need to know about turrets
+ if ( IsValid( turret ) && IsTurret( turret ) )
+ {
+ local usableValue = MegaTurretUsabilityFunc( turret, panel )
+ panel.SetUsableByGroup( usableValue )
+ SetUsePromptForPanel( panel, turret )
+ }
+ else
+ {
+ // Turret got destoyed while hacking.
+ // Usability state has been set by ReleaseTurret( ... ) in ai_turret.nut
+ // Changing it to the previous usable value would put us in a bad state.
+
+
+ // we should change how this works for R2
+ //
+ // HACK remove s.scriptedPanel when these are refactored
+ if ( "scriptedPanel" in panel.s )
+ panel.SetUsableValue( e.panelUsableValueToRestore )
+ }
+ #endif
+
+ #if SP
+ if ( "scriptedPanel" in panel.s )
+ panel.SetUsableValue( e.panelUsableValueToRestore )
+ #endif
+ }
+
+ if ( panel.s.remoteTurret && e.finishedPanelOpen )
+ thread panel.s.remoteTurretStartFunc( panel, player, e.panelUsableValueToRestore )
+
+ if ( panel.s.onPlayerFinishesUsing_func )
+ thread panel.s.onPlayerFinishesUsing_func( panel, player, e.success )
+ }
+
+ foreach ( knife in e.knives )
+ {
+ if ( IsValid( knife ) )
+ knife.Destroy()
+ }
+ }
+ )
+
+ if ( e.doRequireUseButtonHeld && !player.UseButtonPressed() )
+ return // it's possible to get here and no longer be holding the use button. If that is the case lets not continue.
+
+ if ( player.ContextAction_IsActive() )
+ return
+
+ if ( player.IsPhaseShifted() )
+ return
+
+ player.SetAnimNearZ( 1 )
+
+ player.Event_LeechStart()
+
+ local leechTime = panel.s.leechTimeNormal
+
+ if ( PlayerHasPassive( player, ePassives.PAS_FAST_HACK ) )
+ leechTime = panel.s.leechTimeFast
+
+ local totalTime = leechTime + player.GetSequenceDuration( action.playerAnimation3pStart )
+
+ thread TrackContinuousUse( player, totalTime, e.doRequireUseButtonHeld )
+
+ waitthread ControlPanelFlipAnimation( panel, player, action, e )
+
+ if ( e.doRequireUseButtonHeld && !player.UseButtonPressed() )
+ return // we might have returned from the flip anim because we released the use button.
+
+ if ( hackFlag != "" )
+ FlagSet( hackFlag )
+
+ e.finishedPanelOpen = true
+ if ( panel.s.remoteTurret )
+ {
+ // Called on thread end above.
+ return
+ }
+
+ Remote_CallFunction_Replay( player, "ServerCallback_DataKnifeStartLeech", leechTime )
+
+ waitthread WaitForEndLeechOrStoppedUse( player, leechTime, e, panel )
+
+ if ( e.success )
+ {
+ thread DataKnifeSuccessSounds( player )
+ }
+ else
+ {
+ DataKnifeCanceledSounds( player )
+ Remote_CallFunction_Replay( player, "ServerCallback_DataKnifeCancelLeech" )
+ }
+
+ waitthread ControlPanelFlipExitAnimation( player, panel, action, e )
+}
+
+function WaitForEndLeechOrStoppedUse( player, leechTime, e, panel )
+{
+ player.EndSignal( "OnContinousUseStopped" )
+ wait leechTime
+ e.success = true
+ panel.Signal( "PanelReprogrammed", { success = e.success } )
+}
+
+
+//////////////////////////////////////////////////////////////////////
+function ControlPanelFlipAnimation( entity panel, entity player, action, e )
+{
+// OnThreadEnd
+// (
+// function() : ( panel )
+// {
+// if ( IsValid( panel ) )
+// DeleteAnimEvent( panel, "knife_popout" )
+// }
+// )
+ player.EndSignal( "OnContinousUseStopped" )
+
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.attachment = "ref"
+ playerSequence.thirdPersonAnim = expect string ( action.playerAnimation3pStart )
+ playerSequence.thirdPersonAnimIdle = expect string ( action.playerAnimation3pIdle )
+ playerSequence.firstPersonAnim = expect string ( action.playerAnimation1pStart )
+ playerSequence.firstPersonAnimIdle = expect string ( action.playerAnimation1pIdle )
+ if ( IntroPreviewOn() )
+ playerSequence.viewConeFunction = ControlPanelFlipViewCone
+
+ FirstPersonSequenceStruct panelSequence
+ panelSequence.thirdPersonAnim = expect string ( action.panelAnimation3pStart )
+ panelSequence.thirdPersonAnimIdle = expect string ( action.panelAnimation3pIdle )
+
+
+ asset model = DATA_KNIFE_MODEL
+
+ entity knife = CreatePropDynamic( model )
+ SetTargetName( knife, "dataKnife" )
+ knife.SetParent( player, "PROPGUN", false, 0.0 )
+ e.knives.append( knife )
+
+ thread PanelFirstPersonSequence( panelSequence, panel, player )
+ waitthread FirstPersonSequence( playerSequence, player, panel )
+}
+
+
+void function ControlPanelFlipViewCone( entity player )
+{
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -80 )
+ player.PlayerCone_SetMaxYaw( 80 )
+ player.PlayerCone_SetMinPitch( -80 )
+ player.PlayerCone_SetMaxPitch( 10 )
+}
+
+
+//////////////////////////////////////////////////////////////////////
+function PanelFirstPersonSequence( FirstPersonSequenceStruct panelSequence, entity panel, entity player )
+{
+ player.EndSignal( "OnDeath" )
+ panel.EndSignal( "OnDestroy" )
+
+ waitthread FirstPersonSequence( panelSequence, panel )
+}
+
+
+//////////////////////////////////////////////////////////////////////
+function ControlPanelFlipExitAnimation( entity player, entity panel, action, e )
+{
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.blendTime = 0.0
+ playerSequence.attachment = "ref"
+ playerSequence.teleport = true
+
+ FirstPersonSequenceStruct panelSequence
+ panelSequence.blendTime = 0.0
+
+ playerSequence.thirdPersonAnim = expect string ( action.playerAnimation3pEnd )
+ playerSequence.firstPersonAnim = expect string ( action.playerAnimation1pEnd )
+ panelSequence.thirdPersonAnim = expect string ( action.panelAnimation3pEnd )
+
+ thread FirstPersonSequence( panelSequence, panel )
+ waitthread FirstPersonSequence( playerSequence, player, panel )
+}
+
+
+//////////////////////////////////////////////////////////////////////
+function TrackContinuousUse( player, leechTime, doRequireUseButtonHeld )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "ScriptAnimStop" )
+
+ local result = {}
+ result.success <- false
+
+ OnThreadEnd
+ (
+ function() : ( player, result )
+ {
+ if ( !result.success )
+ {
+ player.Signal( "OnContinousUseStopped" )
+ }
+ }
+ )
+
+ float startTime = Time()
+ while ( Time() < startTime + leechTime && (!doRequireUseButtonHeld || player.UseButtonPressed()) && !player.IsPhaseShifted() )
+ WaitFrame()
+
+ if ( !doRequireUseButtonHeld || player.UseButtonPressed() )
+ result.success = true
+}
+
+function InitControlPanelUseFuncTable()
+{
+ local Table = {}
+ Table.useEnt <- null
+ Table.useFunc <- null
+ return Table
+}
+
+function AddControlPanelUseFuncTable( panel, Table )
+{
+ // a table that contains
+ //1. a function to be called when the control panel is used
+ //2. an entity that the function refers to, e.g. the turret to be created
+ panel.s.useFuncArray.append( Table )
+}
+
+function SetControlPanelPrompts( ent, func )
+{
+ ent.s.prompts <- func( ent )
+}
+
+function SetPanelUsableToEnemies( panel )
+{
+ if ( panel.GetTeam() == TEAM_IMC || panel.GetTeam() == TEAM_MILITIA )
+ {
+ panel.SetUsableByGroup( "enemies pilot" )
+ return
+ }
+
+ //Not on either player team, just set usable to everyone
+ panel.SetUsableByGroup( "pilot" )
+}
+
+function PanelFlipsToPlayerTeamAndUsableByEnemies( panel, entity player )
+{
+ expect entity( panel )
+
+ SetTeam( panel, player.GetTeam() )
+ SetPanelUsableToEnemies( panel )
+}
+
+function GetPanelUseEnts( panel )
+{
+ local useEntsArray = []
+ foreach( useFuncTable in panel.s.useFuncArray )
+ {
+ if ( useFuncTable.useEnt )
+ useEntsArray.append( useFuncTable.useEnt )
+ }
+
+ return useEntsArray
+
+}
+
+array<entity> function GetAllControlPanels()
+{
+ //Defensively remove control panels that are invalid.
+ //This is because we can have control panels in levels for some game modes
+ //but not in others, e.g. refuel mode vs tdm
+
+ ArrayRemoveInvalid( file.controlPanels )
+ return file.controlPanels
+}
+
+function CaptureAllAvailableControlPanels( player )
+{
+ array<entity> panels = GetAllControlPanels()
+ foreach ( panel in panels )
+ {
+ printt( "panel team " + panel.GetTeam() )
+ RunPanelUseFunctions( panel, player )
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_dogfighter.gnut b/Northstar.CustomServers/scripts/vscripts/_dogfighter.gnut
new file mode 100644
index 000000000..db6161738
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_dogfighter.gnut
@@ -0,0 +1,343 @@
+untyped
+
+global function Dogfighter_Init
+
+global function CreateDogFighterAttack
+global function LaunchRandomDogFighterAttacks
+global function CreateDogFighter
+global function CreateDogFighterAssist
+global function GetGunshipModel
+const TURRET_WEAPON_BULLETS = "mp_weapon_yh803_bullet"
+
+function Dogfighter_Init()
+{
+
+ AddDeathCallback( "npc_dropship", OnDogFighterDeath )
+
+ RegisterSignal( "new_attack_thread" )
+ RegisterSignal( "GunshipForceLeave" )
+}
+
+function LaunchRandomDogFighterAttacks( int team )
+{
+ svGlobal.levelEnt.Signal( "new_attack_thread" )
+ svGlobal.levelEnt.EndSignal( "new_attack_thread" )
+
+ for ( ;; )
+ {
+ thread CreateDogFighterAttack( team )
+ wait RandomFloatRange( 3, 9 )
+ }
+}
+
+function CreateDogFighterAttack( int team )
+{
+ FlightPath flightPath = GetAnalysisForModel( GetFlightPathModel( "fp_straton_model" ), STRATON_ATTACK_FULL )
+
+ CallinData drop
+ InitCallinData( drop )
+ SetCallinStyle( drop, eDropStyle.RANDOM_FROM_YAW )
+ SetCallinYaw( drop, RandomFloat( 360 ) )
+
+
+ SpawnPointFP spawnPoint = GetSpawnPointForStyle( flightPath, drop )
+ if ( !spawnPoint.valid )
+ return
+
+ entity ship = CreateDogFighter( Vector(0,0,0), Vector(0,0,0), team )
+
+ int hornet_health = 2000
+ ship.SetHealth( hornet_health )
+ ship.SetMaxHealth( hornet_health )
+
+ ship.EndSignal( "OnDeath" )
+
+ //AddBulletTurrets( ship, team )
+
+ waitthread PlayAnimTeleport( ship, "st_AngelCity_IMC_Win_ComeIn", spawnPoint.origin, spawnPoint.angles )
+
+ thread PlayAnim( ship, "st_AngelCity_IMC_Win_Idle", spawnPoint.origin, spawnPoint.angles )
+ waitthread DogFighterWaitsUntilLeave( ship )
+
+ waitthread PlayAnim( ship, "st_AngelCity_IMC_Win_Leave", spawnPoint.origin, spawnPoint.angles, 0.5 )
+
+ ship.Kill_Deprecated_UseDestroyInstead()
+}
+
+
+function DogFighterWaitsUntilLeave( ship, idleMin = 10, idleMax = 15 )
+{
+ local duration = ship.GetSequenceDuration( "st_AngelCity_IMC_Win_Idle" )
+
+ // make it play full increments of the idle anim
+ local maxHealth = ship.GetMaxHealth().tofloat()
+ local idleTime = RandomFloatRange( idleMin, idleMax )
+ local reps = ( duration / idleTime ).tointeger()
+ local totalTime = reps * duration
+ local endTime = Time() + totalTime
+
+ for ( ;; )
+ {
+ if ( ship.GetHealth().tofloat() / maxHealth < 0.2 )
+ return
+ if ( Time() >= endTime )
+ return
+ wait 0.1
+ }
+}
+
+entity function CreateDogFighter( vector origin, vector angles, int team )
+{
+ entity hornet
+ if ( GetBugReproNum() == 81765 )
+ {
+ hornet = CreateEntity( "npc_dropship" )
+ }
+ else
+ {
+ // HACK: using a prop script for now since NPC dropships are still buggy
+ // Jiesang told me to do it!
+ hornet = CreatePropScript( GetGunshipModel( team ), origin, angles, 8 )
+ hornet.EnableAttackableByAI( 50, 0, AI_AP_FLAG_NONE )
+ SetTeam( hornet, team )
+ hornet.SetOrigin( origin )
+ hornet.SetAngles( angles )
+ }
+
+ hornet.s.dogfighter <- true
+ hornet.kv.teamnumber = team
+
+ local title
+ switch ( team )
+ {
+ case TEAM_MILITIA:
+ hornet.SetModel( GetFlightPathModel( "fp_hornet_model" ) )
+ title = "Militia Hornet"
+ break
+
+ case TEAM_IMC:
+ hornet.SetModel( GetFlightPathModel( "fp_straton_model" ) )
+ title = "IMC Phantom"
+ break
+ }
+
+ hornet.SetTitle( title )
+
+ hornet.SetOrigin( origin )
+ hornet.SetAngles( angles )
+ // DispatchSpawn( hornet )
+
+ //hornet.EnableRenderAlways()
+ //hornet.SetAimAssistAllowed( false )
+
+ return hornet
+}
+
+void function OnDogFighterDeath( entity ent, var damageInfo )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ if ( !( "dogfighter" in ent.s ) )
+ return
+
+ if ( ent.GetHealth() <= 0 )
+ FighterExplodes( ent )
+}
+
+
+
+
+function AddRocketTurrets( entity ship, int team, int prof = eWeaponProficiency.VERYGOOD )
+{
+ entity turret = AddTurret( ship, team, "mp_weapon_yh803", "l_exhaust_front_1" )
+ turret.kv.WeaponProficiency = prof
+ turret.NotSolid()
+ turret.Show()
+ entity weapon = turret.GetActiveWeapon()
+ weapon.Show()
+
+ turret = AddTurret( ship, team, "mp_weapon_yh803", "r_exhaust_front_1" )
+ turret.kv.WeaponProficiency = prof
+ turret.NotSolid()
+ turret.Show()
+ weapon = turret.GetActiveWeapon()
+ weapon.Show()
+}
+
+void function AddBulletTurrets( entity ship, int team, int prof = eWeaponProficiency.VERYGOOD )
+{
+ entity turret = AddTurret( ship, team, TURRET_WEAPON_BULLETS, "l_exhaust_front_1" )
+ turret.kv.WeaponProficiency = prof
+ turret.NotSolid()
+ turret = AddTurret( ship, team, TURRET_WEAPON_BULLETS, "r_exhaust_front_1" )
+ turret.kv.WeaponProficiency = prof
+ turret.NotSolid()
+}
+
+asset function GetGunshipModel( int team )
+{
+ switch ( team )
+ {
+ case TEAM_MILITIA:
+ return GetFlightPathModel( "fp_hornet_model" )
+
+ case TEAM_IMC:
+ return GetFlightPathModel( "fp_straton_model" )
+ }
+
+ unreachable
+}
+
+void function CreateDogFighterAssist( int team, vector origin, vector angles, float duration = 10.0, entity ship = null, float dropHeight = 1500 )
+{
+ angles += <0,90,0>
+
+ angles = < 0, angles.y%360, 0 >
+
+ // DebugDrawSphere( origin, 256, 255, 0, 0, true, 10.0 )
+
+ // warp in effect before
+ Point start = GetWarpinPosition( GetFighterModelForTeam( team ), "st_AngelCity_IMC_Win_ComeIn_fast", origin, angles )
+
+ if ( !IsValid( ship ) )
+ {
+ ship = CreateDogFighter( start.origin, start.angles, team )
+ }
+ else
+ {
+ ship.SetOrigin( start.origin )
+ ship.SetAngles( start.angles )
+ }
+
+ waitthread __WarpInEffectShared( start.origin, start.angles, "", 0.0 )
+
+
+ ship.SetHealth( 10000 )
+ ship.SetMaxHealth( 10000 )
+ ship.EndSignal( "OnDeath" )
+
+ #if R1_VGUI_MINIMAP
+ ship.Minimap_SetDefaultMaterial( GetMinimapMaterial( "VIP_friendly" ) )
+ ship.Minimap_SetFriendlyMaterial( GetMinimapMaterial( "VIP_friendly" ) )
+ ship.Minimap_SetEnemyMaterial( GetMinimapMaterial( "VIP_enemy" ) )
+ ship.Minimap_SetBossPlayerMaterial( GetMinimapMaterial( "VIP_friendly" ) )
+ #endif
+ ship.Minimap_SetObjectScale( 0.11 )
+ ship.Minimap_SetZOrder( MINIMAP_Z_NPC )
+
+ entity mover = CreateScriptMover( origin, angles )
+
+ OnThreadEnd(
+ function () : ( ship, mover )
+ {
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ if ( IsValid( ship ) )
+ ship.Destroy()
+ }
+ )
+
+ float SHIP_SIZE = 530
+ vector SHIP_MIN = Vector( -SHIP_SIZE/2, -SHIP_SIZE/2, -250 )
+ vector SHIP_MAX = Vector( SHIP_SIZE/2, SHIP_SIZE/2, 100 )
+
+ entity turret
+
+ turret = CreateEntity( "npc_turret_sentry" )
+ SetSpawnOption_AISettings( turret, "npc_turret_sentry_rockets_dropship" )
+ SetSpawnOption_Weapon( turret, "mp_turretweapon_blaster" )
+ SetSpawnOption_Alert( turret )
+ SetTeam( turret, team )
+ DispatchSpawn( turret )
+ turret.SetInvulnerable()
+ turret.SetParent( ship, "r_turret_attach", false, 0.0 )
+ turret.SetTitle( ship.GetTitle() )
+ NPC_NoTarget( turret )
+
+ turret = CreateEntity( "npc_turret_sentry" )
+ SetSpawnOption_AISettings( turret, "npc_turret_sentry_rockets_dropship" )
+ SetSpawnOption_Weapon( turret, "mp_turretweapon_blaster" )
+ SetSpawnOption_Alert( turret )
+ SetTeam( turret, team )
+ DispatchSpawn( turret )
+ turret.SetInvulnerable()
+ turret.SetParent( ship, "l_turret_attach", false, 0.0 )
+ turret.SetTitle( ship.GetTitle() )
+ NPC_NoTarget( turret )
+
+ // DrawArrow( origin, angles, 10, 50 )
+
+ waitthread PlayAnimTeleport( ship, "st_AngelCity_IMC_Win_ComeIn_fast", origin, angles )
+
+ // -------------------------------------------------
+ // now we want to drop the ship close to the ground
+ // -------------------------------------------------
+
+ vector shipOrigin = ship.GetOrigin()
+ vector newOrigin = shipOrigin
+
+ if ( dropHeight > 0 )
+ {
+ // TRACE to find the floor
+ float traceFrac = TraceLineSimple( shipOrigin, shipOrigin - Vector(0,0,dropHeight), ship )
+ vector floorPos = shipOrigin - Vector(0,0,dropHeight * traceFrac)
+ floorPos += Vector( 0,0,400 ) //we don't want the ship to land!
+
+ // TRACE to see if anything is in the way
+ float result = TraceHullSimple( shipOrigin, floorPos, SHIP_MIN, SHIP_MAX, ship )
+ vector offset = ( shipOrigin - floorPos ) * result
+
+ // This is where we will move the spawnpoint
+ newOrigin = origin - offset
+ }
+
+ // float duration = ship.GetSequenceDuration( "st_AngelCity_IMC_Win_Idle" )
+
+ ship.SetParent( mover, "REF" )
+ ship.Anim_EnableUseAnimatedRefAttachmentInsteadOfRootMotion()
+
+ // Ship comes in...
+ thread PlayAnim( ship, "st_AngelCity_IMC_Win_Idle", mover, "REF" )
+
+ // Ship goes down...
+ float dropDuration = 5.0
+ if ( dropHeight > 0 )
+ {
+ mover.NonPhysicsMoveTo( newOrigin, dropDuration, dropDuration*0.4, dropDuration*0.4 )
+ wait dropDuration
+ }
+
+ // Ship hangs out for a while...
+ waitthread GunshipWaitLeave( ship, duration )
+
+ // Ship raises before it leaves...
+ if ( dropHeight > 0 )
+ {
+ mover.NonPhysicsMoveTo( origin, dropDuration, dropDuration*0.4, dropDuration*0.4 )
+ wait dropDuration
+ }
+ ship.ClearParent()
+
+ // Ship leaves...
+ waitthread PlayAnim( ship, "st_AngelCity_IMC_Win_Leave", origin, angles, 0.5 )
+}
+
+void function GunshipWaitLeave( entity ship, float duration )
+{
+ ship.EndSignal( "GunshipForceLeave" )
+ wait duration
+}
+
+asset function GetFighterModelForTeam( int team )
+{
+ switch ( team )
+ {
+ case TEAM_MILITIA:
+ return GetFlightPathModel( "fp_hornet_model" )
+
+ case TEAM_IMC:
+ return GetFlightPathModel( "fp_straton_model" )
+ }
+ unreachable
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_entitystructs.gnut b/Northstar.CustomServers/scripts/vscripts/_entitystructs.gnut
new file mode 100644
index 000000000..378ceae38
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_entitystructs.gnut
@@ -0,0 +1,692 @@
+global struct BeamEffect
+{
+ entity effect
+ entity cpoint
+}
+
+global struct SpawnPointData
+{
+ table lastRatingData = {}
+}
+
+global struct BallLightningData
+{
+ asset zapFx = BALL_LIGHTNING_ZAP_FX
+ float zapLifetime = BALL_LIGHTNING_ZAP_LIFETIME
+ string zapSound = BALL_LIGHTNING_ZAP_SOUND
+ string zapImpactTable = BALL_LIGHTNING_FX_TABLE
+ float radius = BALL_LIGHTNING_ZAP_RADIUS
+ float humanRadius = BALL_LIGHTNING_ZAP_HUMANSIZE_RADIUS
+ float height = BALL_LIGHTNING_ZAP_HEIGHT
+ float minDot = -1.0
+ float damageToPilots = BALL_LIGHTNING_DAMAGE_TO_PILOTS
+ float damage = BALL_LIGHTNING_DAMAGE
+ bool zapPylons = false
+ int deathPackage = ( DF_DISSOLVE | DF_GIB | DF_ELECTRICAL | DF_STOPS_TITAN_REGEN )
+ bool fatalToDoomedTitans = false
+}
+
+global struct PhaseRewindData
+{
+ vector origin
+ vector angles
+ vector velocity
+ bool wasInContextAction
+ bool wasCrouched
+}
+
+global struct PlayerInputEventCallbackStruct
+{
+ int cmdsPressedBitMask = 0
+ int cmdsHeldBitMask = 0
+ int cmdsReleasedBitMask = 0
+ void functionref( entity player ) callbackFunc
+}
+
+global struct PlayerHeldButtonEventCallbackStruct
+{
+ int buttonHeld = 0
+ void functionref( entity player ) callbackFunc
+ float timeHeld = 1.0
+}
+
+global struct PlayerInputAxisEventCallbackStruct
+{
+ float horizAxisMinThreshold = -1.0
+ float horizAxisMaxThreshold = 1.0
+ float vertAxisMinThreshold= -1.0
+ float vertAxisMaxThreshold= 1.0
+
+ bool functionref( entity player ) callbackFunc
+ float debounceTime = 0.0
+ float lastTriggeredTime = 0.0
+}
+
+global enum eStatUpdateTime
+{
+ DISTANCE,
+ TIME_PLAYED,
+ WEAPON_USAGE,
+ COUNT
+}
+
+global enum eStoredWeaponType
+{
+ main,
+ offhand
+}
+
+global struct TitanDamage
+{
+ int shieldDamage
+
+ bool doomedNow
+ int doomedDamage
+}
+
+global struct RecentUnlock
+{
+ int refGuid = 0
+ int parentRefGuid = 0
+ int count = 0
+}
+
+global struct StoredWeapon
+{
+ string name
+ int weaponType = eStoredWeaponType.main
+ bool activeWeapon = false
+ int inventoryIndex
+ array<string> mods
+ int modBitfield
+ int ammoCount
+ int clipCount
+ float nextAttackTime
+ int skinIndex
+ int camoIndex
+ bool isProScreenOwner
+ #if MP
+ string burnReward
+ #endif
+ int scriptFlags0
+ int scriptTime0
+}
+
+global struct ScriptTriggerData
+{
+ bool enabled
+ float radius
+ table<entity> entities
+ array<void functionref(entity, entity)> enterCallbacks
+ array<void functionref(entity, entity)> leaveCallbacks
+ float top
+ float bottom
+ int flags
+ int managedEntArrayHandle
+}
+
+global struct BurnCardPhaseRewindStruct
+{
+ array<PhaseRewindData> phaseRetreatSavedPositions
+ bool phaseRetreatShouldSave = true
+}
+
+// This struct is hooked up to entity.e in code
+global struct ServerEntityStruct
+{
+ entity repairSoul // repair drones
+ array<void functionref( entity, var )> entKilledCallbacks
+ array<entity> fxArray
+ entity cpoint1
+ bool moverPathPrecached
+ bool blockActive = false
+ int embarkCount = 0 // For the Titan soul to know how many times a player has embarked.
+
+ entity syncedMeleeAttacker
+ entity lastSyncedMeleeAttacker
+ bool markedForExecutionDeath
+
+ bool proto_weakToPilotWeapons
+
+ bool isHotDropping
+
+ bool spawnPointInUse
+ entity lastAttacker
+
+ // sticky props
+ float spawnTime = 0.0
+ bool isStickyCrit = false
+ int stickyRoundsArrayId = -1 //script managed ent array index
+
+ table<string,AnimEventData> animEventDataForEntity
+
+ array<DamageHistoryStruct> recentDamageHistory
+
+ float lastTakeDamageTime_thermite // meteor thermite does an extra burst of damage on first contact
+ float lastTakeDamageTime_laser_cannon
+
+ // tracker rounds
+ int myTrackerRoundsIdx = -1 //script managed ent array index
+ int myReservedTrackerRoundsIdx = -1 //script managed ent array index
+ int trackerRoundsOnMeIdx = -1 //script managed ent array index
+ bool allowLifetimeDeath = true
+
+ // entities
+ float nextAllowStickyExplodeTime = 0.0
+ float stickyClearTime = 0.0
+
+ entity shieldWallFX
+ entity cpoint
+
+ // vortex rules
+ var functionref( entity, var ) BulletHitRules
+ bool functionref( entity, entity, bool ) ProjectileHitRules
+
+ // soul shield
+ float nextShieldDecayTime = 0.0
+ float forcedRegenTime = 0.0
+
+ // ball lightning
+ BallLightningData ballLightningData
+ int ballLightningTargetsIdx = -1
+ int arcPylonArrayIdx = -1 //script managed ent array index
+ float lastArcTime = 0.0
+
+ // laser tripwire
+ int laserPylonArrayIdx = -1 //script managed ent array index
+
+ //Survivor
+ int crateType
+
+ //Bomb
+ // TODO: remove hasBomb and replace with deterministic checks
+ bool hasBomb = false
+ bool destroyOutOfBounds = false
+ bool destroyTriggerHurt = false
+
+ //Rodeo
+ entity lastRodeoAttacker
+
+ array<int> smokeScreenSlowdownIdx
+
+ // number of shield beacons affecting me
+ int shieldBeaconCount
+ array<BeamEffect> shieldBeaconFXArray
+
+ //Ping
+ entity lastPlayerToSpot
+ float lastSpotTime = -9999.0
+
+ bool forceRagdollDeath = false
+
+ int projectileID
+
+ bool windPushEnabled = true
+ bool inWindTunnel
+ float windTunnelStartTime
+ float windTunnelStrength
+ vector windTunnelDirection
+
+ bool forceGibDeath = false
+
+ SpawnPointData spawnPointData
+
+ array<void functionref( entity ent, var damageInfo )> entDamageCallbacks
+ array<void functionref( entity ent, var damageInfo )> entPostDamageCallbacks
+ array<void functionref( entity titan, entity attacker )> entSegmentLostCallbacks
+ array<void functionref( entity ent, var damageInfo, float actualShieldDamage )> entPostShieldDamageCallbacks
+
+ // Used for weapons and abilities that have multiple ticks, but we only want a single tick to hit each player
+ array<entity> damagedEntities
+ bool onlyDamageEntitiesOnce = false
+ bool onlyDamageEntitiesOncePerTick = false
+ float lastDamageTickTime
+
+ array<entity> attachedEnts
+
+ //Scorch Variables
+ table< int, array<entity> > waveLinkFXTable //Wave FX Link - Used for the wide projectile attacks so we can link FX across the rows.
+ table< int, vector > fireTrapEndPositions //Used to spawn 1 particle per arm instead of many.
+ table< int, entity > fireTrapMovingGeo //Used track which piece of moving geo the fire trap arms are on
+
+ //Legion Variables
+ bool ammoSwapPlaying = false
+ bool gunShieldActive = false
+
+ int fxType
+ array<entity> fxControlPoints
+
+ int totalEntsStoredID = 0
+ int AT_BossID
+
+ ScriptTriggerData scriptTriggerData
+
+ bool noOwnerFriendlyFire = false
+
+ bool hasDefaultEnemyHighlight
+
+ bool isDisabled = false
+
+ int gameModeId
+
+ // PVE //
+ int roamerSpawnType = -1
+ int pveSpawnType = -1
+ int pveSpawnFlags = 0
+ bool roamerIsAggro = false
+ //
+ int objectiveGoalVersion = 0
+ int objectiveGoalFlags = 0 // eObjectiveTracking.*
+ /////////
+
+ array<entity> sonarTriggers
+
+ string enemyHighlight = ""
+ string burnReward = ""
+ int fd_roundDeployed = -1
+}
+
+global struct MeritData
+{
+ int scoreMeritState
+ int completionMeritState
+ int winMeritState
+ int evacMeritState
+
+ int weaponMerits
+ int titanMerits
+
+ int happyHourMeritState
+}
+
+// This struct is hooked up to entity.p in code
+global struct ServerPlayerStruct
+{
+ float connectTime
+ bool clientScriptInitialized = false
+
+ bool hasMatchLossProtection = false
+
+ bool usingLoadoutCrate
+ int activePilotLoadoutIndex = -1
+ int activeTitanLoadoutIndex = -1
+ int npcFollowersArrayID
+ entity lastFriendlyTriggerTouched
+ float titanDamageDealt // Does not include shield damage
+ float titanDamageDealt_Stat // Does not include shield damage
+ string spectreSquad
+ bool fastballActivatePressed
+ float nextATShieldRegenTime
+ bool demigod
+ bool partyMember
+
+ bool[OFFHAND_COUNT] offhandSlotLocked = [ false, false, false, false, false, false ]
+ float[OFFHAND_COUNT] lastPilotOffhandUseTime = [ -99.0, -99.0, -99.0, -99.0, -99.0, -99.0 ]
+ float[OFFHAND_COUNT] lastPilotOffhandChargeFrac = [ -1.0, -1.0, -1.0, -1.0, -1.0, -1.0 ]
+ float[OFFHAND_COUNT] lastPilotClipFrac = [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
+ float[OFFHAND_COUNT] lastTitanOffhandUseTime = [ -99.0, -99.0, -99.0, -99.0, -99.0, -99.0 ]
+ float[OFFHAND_COUNT] lastTitanOffhandChargeFrac = [ -1.0, -1.0, -1.0, -1.0, -1.0, -1.0 ]
+ float[OFFHAND_COUNT] lastTitanClipFrac = [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
+ float lastSuitPower = -1.0
+
+ entity currentTargetPlayerOrSoul_Ent
+ float currentTargetPlayerOrSoul_LastHitTime
+
+ entity lastPrimaryWeaponEnt // track when your primary changes
+
+ void functionref( entity, entity ) followPlayerOverride
+
+ float postDeathThreadStartTime
+ float lastSelectSPTitanLoadoutTime
+
+ float lastNpcSyncedMeleeVsPlayerTime = -99
+
+ bool watchingPetTitanKillReplay
+
+ bool hasSniperWeapon = false
+
+ int controllableProjectiles_scriptManagedID = -1
+
+ float lastDroneShieldStunPushTime
+
+ float lastRespawnTime
+ float lastDamageTime
+ float lastDeathTime
+ vector deathOrigin
+ vector deathAngles
+ vector rematchOrigin
+
+ entity lastKiller
+ array<float> recentPlayerKilledTimes
+ array<float> recentAllKilledTimes
+ bool seekingRevenge
+ table<entity, int> playerKillStreaks
+ int playerOrTitanKillsSinceLastDeath //This used to only be player Kills, changed primarily for challenge unlock. See 207007
+ float lastOnslaughtTime = -ONSLAUGHT_REQUIREMENT_TIME
+ float lastMayhemTime = -MAYHEM_REQUIREMENT_TIME
+
+ entity lastSpawnPoint
+
+ string lastExecutionUsed
+
+ MeritData meritData
+
+ void functionref( entity ) currViewConeFunction = null
+ RodeoPackageStruct& rodeoPackage //To assign into a nested struct, reference is needed. Less efficient than directly changing values of struct
+ bool rodeoReadyForAction = true
+ float lastClamberFailedTime = 0.0
+
+ table<int, array<void functionref( entity )> > playerMovementEventCallbacks
+ array<PlayerInputEventCallbackStruct> playerInputEventCallbacks
+ array<PlayerHeldButtonEventCallbackStruct> playerHeldButtonEventCallbacks
+ array<PlayerInputAxisEventCallbackStruct> playerInputAxisEventCallbacks
+
+ // for titan zipline
+ entity activeZiplineBolt
+ table<entity> activeZiplineEnts
+ int activeZiplineTargetID
+ string ogTitanOffhandWeaponName
+
+ array<PlayerSlowDownEffect> slowEffects
+
+ //AT Turrets
+ float PROTO_UseDebounceEndTime = 0.0 //Working around hacky implementation.
+
+ entity deployableAmmoBeacon
+
+ bool pilotLoadoutChanged
+ bool titanLoadoutChanged
+ int pilotModelNeedsUpdate = -1
+ int titanModelNeedsUpdate = -1
+
+ int lastActivatedSpreeRewardsWeaponReward
+
+ float empEndTime
+
+ int disableOffhandWeaponsStackCount
+
+ float timeTitanUpgradesStartCountingDown = -1.0
+ float timeTitanUpgradesAccumulatedPauseTime = -1.0
+
+ bool isEmbarking = false
+ bool isDisembarking = false
+ bool isCustomDisembark = false
+
+ vector ornull quickDeathOrigin = null
+ vector ornull quickDeathAngles = null
+ bool doingQuickDeath = false
+ bool quickDeathRealDeathFadesToBlack = false
+
+ int numberOfDeaths = 0
+ int numberOfDeathsSinceLastKill = 0
+
+ float lastGrappledTime
+
+ bool isReviving = false
+
+ float lastEjectTime = 0
+
+ bool showingMobilityGhost
+ float timeNearMobilityGhostHint
+
+ array<StoredWeapon> storedWeapons
+
+ bool rodeoShouldAdjustJumpOffVelocity = false
+ float rodeoRequestBatteryHintLastShownTime = 0.0
+ float batteryLastTouchedNotificationTime = 0.0
+
+ entity leechTarget = null
+ float lastLeechTypeSoundTime = -1
+ table<entity, entity> leechedEnts = {}
+
+ float lastFullHealthTime
+
+ bool isDisconnected = false
+ array<int> empStatusEffectsToClearForPhaseShift //Not great, done to avoid needing code work to get a separate empSlow/empSTurnEffects
+
+ float stats_wallrunTime = 0
+ float stats_wallhangTime = 0
+ float stats_airTime = 0
+ float[ eStatUpdateTime.COUNT ] statUpdateTimes
+ table<string, float> lastPlayerDidDamageTimes
+ bool rewardedMatchCredit = false
+
+ bool lastPosForDistanceStatValid = false
+ vector lastPosForDistanceStat
+
+ bool pilotEjecting = false
+ float pilotEjectStartTime
+ float pilotEjectEndTime
+
+ array<int> deathHintViews
+
+ float earnMeterOwnedFrac
+ float earnMeterOverdriveFrac
+ float earnMeterRewardFrac
+
+ BurnCardPhaseRewindStruct burnCardPhaseRewindStruct
+ array<entity> rodeoAnimTempProps
+
+ bool controllingTurret = false
+
+ int pveTacticalType = -1
+ int pveTacticalLevel = -1
+
+ array<RecentUnlock> challengeUnlocks
+
+ float lastDpadSayTime = -999
+ int consecutiveDpadMessages = 0
+ float replacementTitanETATimer = 0
+ float replacementTitanReady_lastNagTime = 0
+
+ int turretArrayId = -1
+
+ float hotStreakTime = 0.0
+}
+
+global struct TitanSettings
+{
+ string titanSetFile = ""
+ array<string> titanSetFileMods
+}
+
+global struct NPCDefaultWeapon
+{
+ string wep
+ array<string> mods
+}
+
+// This struct is hooked up to entity.ai in code
+global struct ServerAIStruct
+{
+ TitanSettings titanSettings
+
+ TitanLoadoutDef& titanSpawnLoadout
+
+ vector spawnOrigin
+
+ NPCDefaultWeapon ornull mySpawnOptions_weapon
+
+ string droneSpawnAISettings
+
+ float startCrawlingTime
+ bool crawling = false
+ bool transitioningToCrawl = false
+ bool preventOwnerDamage
+ bool invulnerableToNPC = false
+ bool buddhaMode
+ bool killShotSound = true
+
+ table<int,int> stalkerHitgroupDamageAccumulated // used to decide when to blow off limbs
+ table<int,float> stalkerHitgroupLastHitTime // used to decide when to blow off limbs
+
+ bool fragDroneArmed = true
+ entity suicideSpectreExplodingAttacker
+ float suicideSpectreDefaultExplosionDelay
+ float suicideSpectreExplosionDelay
+ float suicideSpectreExplosionDistance
+ float suicideSpectreExplosionTraceTime
+
+ bool superSpectreEnableFragDrones = true
+ int fragDroneMin = 0
+ int fragDroneMax = 0
+ int fragDroneBatch = 0
+ int activeMinionEntArrayID = -1
+
+ bool readyToFire = true
+
+ float nextRegenTime
+ float nextAllowAnnounceTime
+
+ bool enableFriendlyFollower = true
+ entity lastFriendlyTrigger
+
+
+ bool leechInProgress = false
+ float leechStartTime = -1
+
+ //Marvins
+ entity carryBarrel
+ entity mortarTarget
+
+ int bossTitanType
+ bool bossTitanVDUEnabled = true
+ bool bossTitanPlayIntro = true
+ int mercCharacterID
+ string bossCharacterName
+
+ int killCount
+ int scoreCount
+
+ bool shouldDropBattery = true
+ int nukeCore = 0
+
+ int playerDoomedProficiency
+ int defaultProficiency
+
+ string dropshipSpawnStyle = ""
+ float spawnTime
+}
+
+
+// hooked up to entity.w in code
+global struct ServerWeaponStruct
+{
+ float startChargeTime = 0.0
+ bool wasCharged = false
+ bool initialized = false
+ entity lastProjectileFired
+ array vortexImpactData
+
+ array<PhaseRewindData> phaseRetreatSavedPositions
+ bool phaseRetreatShouldSave = true
+
+ entity laserWorldModel
+ entity guidedMissileTarget = null
+ array<entity> salvoMissileArray
+
+ entity weaponOwner
+ entity bubbleShield
+ array<int> statusEffects
+ array<entity> fxHandles
+ float lastFireTime
+
+ table< entity, int> targetLockEntityStatusEffectID
+ void functionref(entity, entity) missileFiredCallback
+ int savedKillCount
+}
+
+
+global struct RemoteTurretSettings
+{
+ vector turretOrigin
+ vector turretAngles
+ vector panelOrigin
+ vector panelAngles
+
+ asset turretModel
+ asset panelModel
+
+ string turretSettingsName
+ string weaponName
+
+ bool viewClampEnabled
+ float viewClampRangeYaw
+ float viewClampRangePitch
+ float viewStartPitch
+}
+
+// hooked up to entity.remoteturret in code
+global struct ServerRemoteTurretStruct
+{
+ RemoteTurretSettings ornull settings
+ entity controlPanel
+ int statusEffectID
+}
+
+
+// hooked up to entity.proj in code
+global struct ServerProjectileStruct
+{
+ bool isChargedShot = false
+ float damageScale = 1.0
+ bool onlyAllowSmartPistolDamage = false
+ bool selfPropelled = true
+ bool startPlanting = false
+ entity trackedEnt
+ int projectileBounceCount = 0
+ array<entity> projectileGroup
+ int projectileID
+ bool tetherAttached
+ vector savedOrigin
+ vector savedRelativeDelta
+ entity savedMovingGeo
+ vector savedAngles
+ entity inflictorOverride
+ bool hasBouncedOffVortex = false
+ bool isPlanted = false
+}
+
+// hooked up to entity.soul in code
+global struct ServerTitanSoulStruct
+{
+ bool rebooting = false
+ float lastSegmentLossTime = 0.0
+ float batteryTime
+ entity bubbleShield
+ NPCPilotStruct seatedNpcPilot
+ bool skipDoomState
+ bool regensHealth = true
+ bool diesOnEject = true
+ float doomedStartTime = 0.0
+ entity batteryContainer = null
+ entity armBadge = null
+ bool batteryContainerBeingUsed = false
+ bool batteryContainerPastPointOfNoReturn = false
+ entity lastOwner
+ int upgradeCount = 0
+ TitanLoadoutDef& titanLoadout
+ entity nukeAttacker = null
+ bool batteryMovedDown = false
+}
+
+// hooked up to entity.decoy in code
+global struct ServerPlayerDecoyStruct
+{
+ array< entity > fxHandles
+ array< string > loopingSounds
+}
+
+// hooked up to entity.sp in code
+global struct ServerSpawnpointStruct
+{
+ bool enabled
+ float lastUsedTime
+ array< int > zones
+ array< entity > visibleToTurret
+}
+
+global struct ServerFirstPersonProxyStruct
+{
+ entity battery
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_global_entities.gnut b/Northstar.CustomServers/scripts/vscripts/_global_entities.gnut
new file mode 100644
index 000000000..767436d9c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_global_entities.gnut
@@ -0,0 +1,343 @@
+untyped
+
+globalize_all_functions
+
+//=========================================================
+// _global_entities
+// Create/initialize various global entities
+//=========================================================
+
+
+// if you set this to zero, various things that spam developer 2 won't run
+const SPAMS_DEVELOPER2 = 1
+
+void function MP_PlayerPostInit( entity self )
+{
+ entity player = self
+ Assert( !player.hasSpawned )
+
+ player.InitMPClasses()
+
+ player.hasSpawned = true
+}
+
+array _PlayerDidSpawnCallbacks = []
+
+function __PlayerDidSpawn( entity player )
+{
+ if ( player.GetPlayerName() == "Replay" )
+ return
+
+ foreach( callback in _PlayerDidSpawnCallbacks )
+ {
+ thread callback()
+ }
+
+ svGlobal.levelEnt.Signal( "PlayerDidSpawn", { player=player } )
+ FlagSet( "PlayerDidSpawn" )
+}
+
+function AddCallback_PlayerDidSpawn( callback )
+{
+ _PlayerDidSpawnCallbacks.append( callback )
+}
+
+function ClientCommand( client, command, delay = 0 )
+{
+ EntFireByHandle( _cc, "Command", command, delay, client, null )
+}
+
+function ServerCommand( command, delay = 0 )
+{
+ EntFireByHandle( _sc, "Command", command, delay, null, null )
+}
+
+table __trackedRefs = {}
+
+function AddTrackRef( ref )
+{
+ //printl( "Adding ref for: " + string( ref ) )
+ __trackedRefs[string( ref )] <- ref.weakref()
+}
+
+function RefTrackerThink()
+{
+ foreach ( refName, entity refObj in __trackedRefs )
+ {
+ if ( !refObj || !refObj.ref() )
+ {
+ delete __trackedRefs[refName]
+ if ( SPAMS_DEVELOPER2 )
+ svGlobal.levelEnt.Fire( "CallScriptFunction", "RefTrackerThink", 0.033 )
+ return
+ }
+
+ if ( IsValid_ThisFrame( refObj ) )
+ continue
+
+ printl( "UNFREED REFERENCE (use weakref for entities): " + refName )
+ __trackedRefs[ refName ] = null
+ }
+
+ if ( SPAMS_DEVELOPER2 )
+ svGlobal.levelEnt.Fire( "CallScriptFunction", "RefTrackerThink", 2.0 )
+}
+
+function DumpTrackRefs()
+{
+ foreach ( refName, refObj in __trackedRefs )
+ {
+ if ( !refObj || !refObj.ref() )
+ continue
+
+ printl( "TRACKREF: " + refName + " " + refObj.ref() )
+
+ }
+}
+
+function MapRequiresFullFlightpathSupport()
+{
+ return false //GetMapName().find( "mp_" ) == 0
+}
+
+function AINFileIsUpToDate_Wrapper()
+{
+ if ( GetAINScriptVersion() != AIN_REV )
+ return false
+
+ return AINFileIsUpToDate()
+}
+
+
+function Hud_Hide( __t__, __tt__ )
+{
+}
+
+function Hud_Show( __t__, __tt__ )
+{
+}
+
+function PathsOutOfDate( player )
+{
+ SendHudMessage( player, "Paths Out of Date. Type buildainfile at console.", -1, 0.4, 255, 255, 0, 255, 0.0, 0.5, 0.0 )
+ // , x_pos, y_pos, R, G, B, A, fade_in_time, hold_time, fade_out_time )
+}
+
+function NavmeshOutOfDate( player )
+{
+ SendHudMessage( player, "Navmesh Out of Date. Build in LevelEd", -1, 0.6, 192, 255, 0, 255, 0.0, 0.5, 0.0 )
+ // , x_pos, y_pos, R, G, B, A, fade_in_time, hold_time, fade_out_time )
+}
+
+
+function NavmeshUpToDateCheck()
+{
+ FlagWait( "PlayerDidSpawn" )
+
+ if ( NavMesh_IsUpToDate() )
+ return
+
+ for ( int i = 0; i < 5; i++ )
+ {
+ wait 1
+
+ array<entity> players = GetPlayerArray()
+ // let's not spam the whole server
+ if ( players.len() )
+ NavmeshOutOfDate( players[0] )
+ }
+}
+
+
+// paths out of date
+function AINFileIsUpToDateCheck()
+{
+ FlagWait( "PlayerDidSpawn" )
+
+ if ( AINFileIsUpToDate_Wrapper() )
+ return
+
+ if ( !AINExists() )
+ return
+
+ for ( int i = 0; i < 5; i++ )
+ {
+ wait 1
+
+ array<entity> players = GetPlayerArray()
+ // let's not spam the whole server
+ if ( players.len() )
+ PathsOutOfDate( players[0] )
+ }
+}
+
+function PlayerSeesGraphWarning( player )
+{
+ player.EndSignal( "OnDestroy" )
+ local i
+ float minWait = 0.03
+ float maxWait = 0.7
+ local result
+ int max = 15
+ for ( i = 0; i < max; i++ )
+ {
+ result = Graph( i, 0, max, maxWait, minWait )
+
+ wait result
+ if ( !IsValid( player ) )
+ return
+
+ if ( GetNPCArray().len() == 0 )
+ continue
+
+ wait result * 0.5
+ if ( !IsValid( player ) )
+ return
+
+ PathsOutOfDate( player )
+ }
+
+ for ( ;; )
+ {
+ wait 0.5
+ if ( !IsValid( player ) )
+ return
+
+ if ( GetNPCArray().len() == 0 )
+ continue
+
+ wait 0.3
+ if ( !IsValid( player ) )
+ return
+
+ PathsOutOfDate( player )
+ }
+}
+
+
+
+// Look up and set damageSourceIds for environmental damage triggers
+// This works this way so maps don't have to be recompiled if any damageSourceIds change
+void function InitDamageTriggers( entity self )
+{
+ if ( !self.HasKey( "damageSourceName" ) )
+ return
+
+ switch ( self.GetValueForKey( "damageSourceName" ) )
+ {
+ case "fall":
+ self.kv.damageSourceId = eDamageSourceId.fall
+ break
+
+ case "splat":
+ self.kv.damageSourceId = eDamageSourceId.splat
+ break
+
+ case "burn":
+ self.kv.damageSourceId = eDamageSourceId.burn
+ break
+
+ case "submerged":
+ self.kv.damageSourceId = eDamageSourceId.submerged
+ break
+
+ case "electric_conduit":
+ self.kv.damageSourceId = eDamageSourceId.electric_conduit
+ break
+
+ case "turbine":
+ self.kv.damageSourceId = eDamageSourceId.turbine
+ break
+
+ case "lasergrid":
+ self.kv.damageSourceId = eDamageSourceId.lasergrid
+ break
+
+ case "crush":
+ self.kv.damageSourceId = eDamageSourceId.damagedef_crush
+ break
+
+ case "toxic_sludge":
+ self.kv.damageSourceId = eDamageSourceId.toxic_sludge
+ break
+
+ default:
+ Assert( false, "Unsupported damage source name on trigger_hurt: " + self.GetValueForKey( "damageSourceName" ) )
+ }
+}
+
+bool function IsDamageFromDamageTrigger( damageInfo )
+{
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ case eDamageSourceId.fall:
+ case eDamageSourceId.splat:
+ case eDamageSourceId.burn:
+ case eDamageSourceId.submerged:
+ return true
+ }
+
+ return false
+}
+
+function ScriptLeakDetector()
+{
+ svGlobal.levelEnt.Signal( "GameEnd" )
+ OnThreadEnd(
+ function() : ()
+ {
+ TotalEnts()
+ }
+ )
+
+ WaitFrame()
+ TotalEnts()
+
+ for ( ;; )
+ {
+ wait 60
+ TotalEnts()
+ }
+}
+
+void function NavmeshSeparatorThink( entity separator )
+{
+ bool connected = true
+ if ( separator.HasKey( "startDisconnected" ) && separator.kv.startDisconnected == "1" )
+ connected = false
+
+ ToggleNPCPathsForEntity( separator, connected )
+ if ( connected )
+ separator.NotSolid()
+ else
+ separator.Solid()
+
+ if ( separator.HasKey( "script_flag" ) && separator.kv.script_flag != "" )
+ {
+ string flag = string( separator.kv.script_flag )
+ FlagInit( flag, connected )
+
+ while( IsValid( separator ) )
+ {
+ if ( connected )
+ FlagWaitClear( flag )
+ else
+ FlagWait( flag )
+
+ connected = !connected
+ ToggleNPCPathsForEntity( separator, connected )
+ if ( connected )
+ separator.NotSolid()
+ else
+ separator.Solid()
+ }
+ }
+}
+
+void function DevDebugText( entity node )
+{
+ Assert( node.HasKey( "text" ) && node.kv.text != "", "info_debug_text at " + node.GetOrigin() + " doesn't have text set on it." )
+ // Debug text doesn't work right away becuase of code, this delay makes them show up
+ wait 3.0
+ DebugDrawText( node.GetOrigin(), string( node.kv.text ), true, 999999.9 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_harvester.gnut b/Northstar.CustomServers/scripts/vscripts/_harvester.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_harvester.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_health_regen.gnut b/Northstar.CustomServers/scripts/vscripts/_health_regen.gnut
new file mode 100644
index 000000000..ded25dc3d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_health_regen.gnut
@@ -0,0 +1,172 @@
+
+global function HealthRegen_Init
+
+global function PilotHealthRegenThinkSP
+global function PilotShieldHealthUpdate
+
+struct
+{
+ float healthRegenRate
+} file
+
+void function HealthRegen_Init()
+{
+ if ( IsSingleplayer() )
+ {
+ file.healthRegenRate = 1.0
+ }
+ else
+ {
+ file.healthRegenRate = 6.0
+ AddCallback_PlayerClassChanged( HealthRegen_OnPlayerClassChangedMP )
+ RegisterSignal( "PilotHealthRegenThink" )
+ }
+}
+
+void function PilotHealthRegenThinkSP( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ while ( IsValid( player ) )
+ {
+ wait( HEALTH_REGEN_TICK_TIME )
+
+ if ( !IsAlive( player ) )
+ continue
+
+ if ( !IsPilot( player ) )
+ continue
+
+ if ( shGlobal.proto_pilotHealthRegenDisabled )
+ continue
+
+ //Assert( IsTestMap() || player.GetPlayerSettings() == DEFAULT_PILOT_SETTINGS, "for now, we should all be pilot_solo at all times, or in a test map." )
+
+ if ( player.GetHealth() == player.GetMaxHealth() )
+ continue
+
+ float healthRegenRate = 4.0
+ float healthRegenStartDelay = GraphCapped( player.GetHealth(), 0, player.GetMaxHealth(), 3.0, 0.8 )
+
+ //printt( "recentDamage " + recentDamage + " delay " + healthRegenStartDelay + " rate " + healthRegenRate )
+
+ if ( Time() - player.p.lastDamageTime < healthRegenStartDelay )
+ {
+ continue
+ }
+
+ player.SetHealth( min( player.GetMaxHealth(), player.GetHealth() + healthRegenRate ) )
+ }
+}
+
+bool function IsHealActive( entity player )
+{
+ return StatusEffect_Get( player, eStatusEffect.stim_visual_effect ) > 0.0
+}
+
+void function PilotHealthRegenThinkMP( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.Signal( "PilotHealthRegenThink" )
+ player.EndSignal( "PilotHealthRegenThink" )
+
+ //float healthRegenStartDelay = player.GetPlayerSettingsField( "powerRegenRateOp" ) // seconds after we take damager to start regen
+ float healthRegenStartDelay = 5.0 //Needs to use GetPlayerSettingsField() instead of hard coding, waiting on Bug 129567
+ if ( PlayerHasPassive( player, ePassives.PAS_FAST_HEALTH_REGEN ) )
+ healthRegenStartDelay = 2.5
+
+ while ( IsValid( player ) )
+ {
+ wait( HEALTH_REGEN_TICK_TIME )
+
+ if ( !IsAlive( player ) )
+ continue
+
+ if ( !IsPilot( player ) )
+ continue
+
+ if ( shGlobal.proto_pilotHealthRegenDisabled )
+ continue
+
+ float healthRegenRate = file.healthRegenRate // health regen per tick
+
+ if ( player.GetHealth() == player.GetMaxHealth() )
+ continue
+
+ // No regen during phase shift
+ if ( player.IsPhaseShifted() )
+ continue
+
+ if ( IsHealActive( player ) )
+ {
+ if ( Time() - player.p.lastDamageTime < min( ABILITY_STIM_REGEN_DELAY, healthRegenStartDelay ) )
+ continue
+ else
+ healthRegenRate = healthRegenRate * ABILITY_STIM_REGEN_MOD
+ }
+ else if ( Time() - player.p.lastDamageTime < healthRegenStartDelay )
+ {
+ continue
+ }
+
+ player.SetHealth( min( player.GetMaxHealth(), player.GetHealth() + healthRegenRate ) )
+ if ( player.GetHealth() == player.GetMaxHealth() )
+ {
+ ClearRecentDamageHistory( player )
+ ClearLastAttacker( player )
+ }
+ }
+}
+
+void function HealthRegen_OnPlayerClassChangedMP( entity player )
+{
+ thread PilotHealthRegenThinkMP( player )
+}
+
+float function PilotShieldHealthUpdate( entity player, var damageInfo )
+{
+ if ( DamageInfo_GetForceKill( damageInfo ) )
+ {
+ player.SetShieldHealth( 0 )
+ return 0.0
+ }
+
+ int shieldHealth = player.GetShieldHealth()
+
+ float shieldDamage = 0
+
+ if ( shieldHealth )
+ {
+ DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )
+
+ shieldDamage = PilotShieldModifyDamage( player, damageInfo )
+
+ if ( shieldDamage )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+
+ return shieldDamage
+}
+
+float function PilotShieldModifyDamage( entity player, var damageInfo )
+{
+ float shieldHealth = float( player.GetShieldHealth() )
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ float newShieldHealth = shieldHealth - damage
+ float permanentDamage = 0.0
+
+ if ( newShieldHealth < 0 )
+ permanentDamage = fabs( newShieldHealth )
+
+ player.SetShieldHealth( maxint( 0, int( newShieldHealth ) ) )
+
+ if ( shieldHealth && newShieldHealth <= 0 )
+ {
+ EmitSoundOnEntity( player, "titan_energyshield_down" )
+ }
+
+ DamageInfo_SetDamage( damageInfo, permanentDamage )
+
+ return min( shieldHealth, damage )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_init.gnut b/Northstar.CustomServers/scripts/vscripts/_init.gnut
new file mode 100644
index 000000000..fc9fe2b98
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_init.gnut
@@ -0,0 +1,40 @@
+#if DEV
+untyped
+#endif
+
+//=========================================================
+// _init
+// Called on newgame or transitions, AFTER entities have been created and initialized
+//=========================================================
+
+global function CodeCallback_PostEntityInit
+
+bool _initialized = false
+
+void function CodeCallback_PostEntityInit()
+{
+ printl( "Code Script: _init" )
+
+ // prevent save/load code from running global scripts again
+ Assert( !_initialized )
+ _initialized = true
+
+ RunCallbacks_EntitiesDidLoad()
+
+ FlagInit( "EntitiesDidLoad" )
+ FlagSet( "EntitiesDidLoad" )
+
+ array<entity> exfilPanels = GetEntArrayByClass_Expensive( "prop_exfil_panel" )
+ foreach ( panel in exfilPanels )
+ panel.Destroy()
+
+ // regexp unit tests
+ Assert( regexp( "^foo.*bar$" ).match( "foobar" ) )
+ Assert( !regexp( "^foo.+bar$" ).match( "foobar" ) )
+ Assert( regexp( "^foo.*bar$" ).match( "fooxbar" ) )
+ Assert( regexp( "^foo.+bar$" ).match( "fooxbar" ) )
+ Assert( regexp( "^foo.*$" ).match( "foo" ) )
+ Assert( !regexp( "^foo.+$" ).match( "foo" ) )
+ Assert( regexp( "^foo.*$" ).match( "foon" ) )
+ Assert( regexp( "^foo.+$" ).match( "foon" ) )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_loadouts_mp.gnut b/Northstar.CustomServers/scripts/vscripts/_loadouts_mp.gnut
new file mode 100644
index 000000000..fdba12da9
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_loadouts_mp.gnut
@@ -0,0 +1,248 @@
+untyped
+global function SvLoadoutsMP_Init
+
+global function SetLoadoutGracePeriodEnabled
+global function SetWeaponDropsEnabled
+global function GetTitanLoadoutForPlayer
+
+struct {
+ bool loadoutGracePeriodEnabled = true
+ bool weaponDropsEnabled = true
+ array<entity> dirtyLoadouts
+} file
+
+void function SvLoadoutsMP_Init()
+{
+ InitDefaultLoadouts() // titan loadout code relies on this, not called on server by default
+
+ // most of these are fairly insecure right now, could break pdata if called maliciously, need fixing eventually
+ RegisterSignal( "EndUpdateCachedLoadouts" )
+ RegisterSignal( "GracePeriodDone" ) // temp to get weapons\_weapon_utility.nut:2271 to behave
+
+ AddCallback_OnClientConnected( UpdateCallsignOnConnect )
+
+ AddClientCommandCallback( "RequestPilotLoadout", ClientCommandCallback_RequestPilotLoadout )
+ AddClientCommandCallback( "RequestTitanLoadout", ClientCommandCallback_RequestTitanLoadout )
+ AddClientCommandCallback( "SetPersistentLoadoutValue", ClientCommandCallback_SetPersistentLoadoutValue )
+ AddClientCommandCallback( "SwapSecondaryAndWeapon3PersistentLoadoutData", ClientCommandCallback_SwapSecondaryAndWeapon3PersistentLoadoutData )
+ AddClientCommandCallback( "SetBurnCardPersistenceSlot", ClientCommandCallback_SetBurnCardPersistenceSlot )
+
+ if ( IsLobby() ) // can't usually set these in real games
+ {
+ AddClientCommandCallback( "SetCallsignIcon", ClientCommandCallback_SetCallsignIcon )
+ AddClientCommandCallback( "SetCallsignCard", ClientCommandCallback_SetCallsignCard )
+ AddClientCommandCallback( "SetFactionChoicePersistenceSlot", ClientCommandCallback_SetFactionChoicePersistenceSlot )
+ }
+ else
+ AddClientCommandCallback( "InGameMPMenuClosed", ClientCommandCallback_InGameMPMenuClosed )
+
+ AddCallback_OnPlayerKilled( DestroyDroppedWeapon )
+}
+
+void function SetLoadoutGracePeriodEnabled( bool enabled )
+{
+ file.loadoutGracePeriodEnabled = enabled
+}
+
+void function SetWeaponDropsEnabled( bool enabled )
+{
+ file.weaponDropsEnabled = enabled
+}
+
+void function DestroyDroppedWeapon( entity victim, entity attacker, var damageInfo )
+{
+ if ( !file.weaponDropsEnabled )
+ victim.GetActiveWeapon().Destroy()
+}
+
+TitanLoadoutDef function GetTitanLoadoutForPlayer( entity player )
+{
+ SetActiveTitanLoadout( player ) // set right loadout
+
+ // fix bug with titan weapons having null mods
+ // null mods aren't valid and crash if we try to give them to npc
+ TitanLoadoutDef def = GetActiveTitanLoadout( player )
+ def.primaryMods.removebyvalue( "null" )
+
+ return def
+}
+
+void function UpdateCallsignOnConnect( entity player )
+{
+ // these netints are required for callsigns and such to display correctly on other clients
+ player.SetPlayerNetInt( "activeCallingCardIndex", player.GetPersistentVarAsInt( "activeCallingCardIndex" ) )
+ player.SetPlayerNetInt( "activeCallsignIconIndex", player.GetPersistentVarAsInt( "activeCallsignIconIndex" ) )
+}
+
+// loadout clientcommands
+bool function ClientCommandCallback_RequestPilotLoadout( entity player, array<string> args )
+{
+ if ( args.len() != 1 )
+ return true
+
+ print( player + " RequestPilotLoadout " + args[0] )
+
+ // insecure, could be used to set invalid spawnloadout index potentially
+ SetPersistentSpawnLoadoutIndex( player, "pilot", args[0].tointeger() )
+
+ SetPlayerLoadoutDirty( player )
+
+ return true
+}
+
+bool function ClientCommandCallback_RequestTitanLoadout( entity player, array<string> args )
+{
+ if ( args.len() != 1 )
+ return true
+
+ print( player + " RequestTitanLoadoutLoadout " + args[0] )
+
+ // insecure, could be used to set invalid spawnloadout index potentially
+ SetPersistentSpawnLoadoutIndex( player, "titan", args[0].tointeger() )
+
+ if ( !IsLobby() )
+ EarnMeterMP_SetTitanLoadout( player )
+
+ return true
+}
+
+bool function ClientCommandCallback_SetPersistentLoadoutValue( entity player, array<string> args )
+{
+ //if ( args.len() != 4 )
+ // return true
+
+ if ( args.len() < 4 )
+ return true
+
+ string val = args[ 3 ]
+ if ( args.len() > 4 ) // concat args after 3 into last arg so we can do strings with spaces and such
+ for ( int i = 4; i < args.len(); i++ )
+ val += " " + args[ i ]
+
+ val = strip( val ) // remove any tailing whitespace
+
+ print( player + " SetPersistentLoadoutValue " + args[0] + " " + args[1] + " " + args[2] + " " + val )
+
+ // VERY temp and insecure
+ SetPersistentLoadoutValue( player, args[0], args[1].tointeger(), args[2], val )
+
+ if ( args[0] == "pilot" )
+ SetPlayerLoadoutDirty( player )
+
+ return true
+}
+
+bool function ClientCommandCallback_SwapSecondaryAndWeapon3PersistentLoadoutData( entity player, array<string> args )
+{
+ if ( args.len() != 1 )
+ return true
+
+ print( "SwapSecondaryAndWeapon3PersistentLoadoutData " + args[0] )
+
+ // get loadout
+ int index = args[0].tointeger()
+ PilotLoadoutDef loadout = GetPilotLoadoutFromPersistentData( player, index )
+
+ // swap loadouts
+ // is this a good way of doing it? idk i think this is the best way of doing it
+ // can't use validation because when you swap, you'll have a secondary/weapon3 in 2 slots at once at one point, which fails validation
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "secondary", loadout.weapon3 )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "secondaryMod1", loadout.weapon3Mod1 )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "secondaryMod2", loadout.weapon3Mod2 )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "secondaryMod3", loadout.weapon3Mod3 )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "secondarySkinIndex", loadout.weapon3SkinIndex.tostring() )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "secondaryCamoIndex", loadout.weapon3CamoIndex.tostring() )
+
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "weapon3", loadout.secondary )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "weapon3Mod1", loadout.secondaryMod1 )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "weapon3Mod2", loadout.secondaryMod2 )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "weapon3Mod3", loadout.secondaryMod3 )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "weapon3SkinIndex", loadout.secondarySkinIndex.tostring() )
+ SetPlayerPersistentVarWithoutValidation( player, "pilot", index, "weapon3CamoIndex", loadout.secondaryCamoIndex.tostring() )
+
+ SetPlayerLoadoutDirty( player )
+
+ return true
+}
+
+bool function ClientCommandCallback_SetBurnCardPersistenceSlot( entity player, array<string> args )
+{
+ if ( args.len() != 1 )
+ return true
+
+ print( player + " SetBurnCardPersistenceSlot " + args[0] )
+
+ // insecure, could be used to set invalid burnmeterslot potentially
+ if ( IsRefValidAndOfType( args[0], eItemTypes.BURN_METER_REWARD ) )
+ player.SetPersistentVar( "burnmeterSlot", BurnReward_GetByRef( args[0] ).id )
+ else
+ print( player + " invalid ref " + args[0] )
+
+ return true
+}
+
+// lobby clientcommands
+bool function ClientCommandCallback_SetCallsignIcon( entity player, array<string> args )
+{
+ print( player + " SetCallsignIcon " + args[0] )
+
+ if ( IsRefValidAndOfType( args[0], eItemTypes.CALLSIGN_ICON ) )
+ PlayerCallsignIcon_SetActiveByRef( player, args[0] )
+ else
+ print( player + " invalid ref " + args[0] )
+
+ return true
+}
+
+bool function ClientCommandCallback_SetCallsignCard( entity player, array<string> args )
+{
+ print( player + " SetCallsignIcon " + args[0] )
+
+ if ( IsRefValidAndOfType( args[0], eItemTypes.CALLING_CARD ) )
+ PlayerCallingCard_SetActiveByRef( player, args[0] )
+ else
+ print( player + " invalid ref " + args[0] )
+
+ return true
+}
+
+bool function ClientCommandCallback_SetFactionChoicePersistenceSlot( entity player, array<string> args )
+{
+ print( player + " SetFactionChoicePersistenceSlot " + args[0] )
+
+ if ( IsRefValidAndOfType( args[0], eItemTypes.FACTION ) )
+ player.SetPersistentVar( "factionChoice", args[0] ) // no function for this so gotta set directly lol
+
+ return true
+}
+
+bool function ClientCommandCallback_InGameMPMenuClosed( entity player, array<string> args )
+{
+ SavePdataForEntityIndex( player.GetPlayerIndex() )
+ TryGivePilotLoadoutForGracePeriod( player )
+ return true
+}
+
+bool function IsRefValidAndOfType( string ref, int itemType )
+{
+ return IsRefValid( ref ) && GetItemType( ref ) == itemType
+}
+
+void function SetPlayerLoadoutDirty( entity player )
+{
+ if ( file.loadoutGracePeriodEnabled )
+ file.dirtyLoadouts.append( player )
+}
+
+void function TryGivePilotLoadoutForGracePeriod( entity player )
+{
+ if ( !IsLobby() && file.dirtyLoadouts.contains( player ) )
+ {
+ file.dirtyLoadouts.remove( file.dirtyLoadouts.find( player ) )
+
+ if ( Time() - player.s.respawnTime <= CLASS_CHANGE_GRACE_PERIOD )
+ Loadouts_TryGivePilotLoadout( player )
+ else
+ SendHudMessage( player, "#LOADOUT_CHANGE_NEXT_BOTH", -1, 0.4, 255, 255, 255, 255, 0.15, 3.0, 0.5 ) // like 90% sure this is innacurate lol
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_mapspawn.gnut b/Northstar.CustomServers/scripts/vscripts/_mapspawn.gnut
new file mode 100644
index 000000000..3efee093e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_mapspawn.gnut
@@ -0,0 +1,217 @@
+//=========================================================
+// _mapspawn.nut
+// Called on newgame or transitions, BEFORE entities have been created and initialized
+//=========================================================
+
+global function CodeCallback_MapSpawn
+global function CodeCallback_ClientCommand
+global table _ClientCommandCallbacks = {}
+global entity _cc = null
+global entity _sc = null
+
+global struct spawnCallbackFuncArray
+{
+ array<void functionref( entity )> callbackArray
+ string entityClassname
+}
+
+global struct spawnCallbackFuncArray_scriptNoteworthy
+{
+ array<void functionref( entity )> callbackArray
+ string scriptNoteworthy
+}
+
+global struct spawnCallbackEditorClassFuncArray
+{
+ array<void functionref( entity )> callbackArray
+ string entityClassname
+ string entityEditorClassname
+}
+
+global typedef pilotEliminationDialogueCallbackType void functionref( int, array<entity>, int, array<entity> )
+
+global struct SvGlobals
+{
+ entity worldspawn
+
+ array<spawnCallbackFuncArray> spawnCallbackFuncs
+ array<spawnCallbackEditorClassFuncArray> spawnCallbackEditorClassFuncs
+ array<spawnCallbackFuncArray_scriptNoteworthy> spawnCallbackFuncs_scriptNoteworthy
+
+ table<string, array<void functionref( entity )> > spawnCallbacks_scriptName
+
+ array<pilotEliminationDialogueCallbackType> pilotEliminationDialogueCallbacks
+ table<string, array<bool functionref( entity player, entity healthpack)> > onTouchHealthKitCallbacks
+ array<void functionref( entity )> onClientConnectedCallbacks
+ array<void functionref(entity)> onPlayerRespawnedCallbacks
+ array<void functionref( entity player, entity npc_titan )> onPilotBecomesTitanCallbacks
+ array<void functionref( entity player, entity npc_titan )> onTitanBecomesPilotCallbacks
+ array<void functionref( entity, entity, entity) > soulTransferFuncs
+ array<void functionref( entity titanSoul )> soulSettingsChangeFuncs
+ array<void functionref( entity titanSoul )> soulInitFuncs
+ table<string, array<void functionref( entity, var )> > damageByCallbacks
+
+ bool functionref( entity ) gameModeAbandonPenaltyApplies
+
+ bool functionref() timelimitCompleteFunc
+ bool functionref( entity ) titanAvailabilityCheck
+ bool cloakBreaksOnMelee = true //Reexamine if still needed if we have same behavior for cloak in MP/SP.
+ float defaultPilotLeechTime = 2.8
+ int winReason
+ string winReasonText
+ string lossReasonText
+ string gameWonAnnouncement
+ string gameLostAnnouncement
+
+ table< int, int > npcsSpawnedThisFrame_scriptManagedArray
+
+ float pilotRespawnDelay = 0.0
+
+ array<void functionref( entity, var )> soulDeathFuncs
+
+ table<string, void functionref(entity)> globalAnimEventCallbacks
+
+ array<void functionref( entity titan, TitanLoadoutDef newTitanLoadout )> onTitanGetsNewLoadoutCallbacks
+ array<void functionref( entity player, PilotLoadoutDef newTitanLoadout )> onPlayerGetsNewPilotLoadoutCallbacks
+ array<void functionref( TitanLoadoutDef newTitanLoadout )> onUpdateDerivedTitanLoadoutCallbacks
+ array<void functionref( entity player, TitanLoadoutDef newTitanLoadout )> onUpdateDerivedPlayerTitanLoadoutCallbacks
+ array<void functionref( PilotLoadoutDef newPilotLoadout )> onUpdateDerivedPilotLoadoutCallbacks
+
+ array<void functionref( entity victim, entity attacker, var damageInfo )> onPlayerKilledCallbacks
+ array<void functionref( entity victim, entity attacker, var damageInfo )> onNPCKilledCallbacks
+
+ array<void functionref( entity victim, var damageInfo )> onTitanDoomedCallbacks
+ array<void functionref( entity victim, entity attacker )> onTitanHealthSegmentLostCallbacks
+ array<void functionref( entity player )> onClientConnectingCallbacks
+ array<void functionref( entity player )> onClientDisconnectedCallbacks
+ array<void functionref( entity attacker, entity victim )> onPlayerAssistCallbacks
+
+ array<void functionref( entity player )> onPlayerDropsScriptedItemsCallbacks
+ array<void functionref( entity player )> onPlayerClassChangedCallbacks
+
+ array<void functionref( entity ship, string anim )> onWaveSpawnDropshipSpawned
+
+ table<string, array<void functionref( entity ent )> >onEntityChangedTeamCallbacks
+
+ table<string, bool functionref( entity player, array<string>args )> clientCommandCallbacks
+ array<void functionref()>[ eGameState._count_ ] gameStateEnterCallbacks
+
+ bool allowPointsOverLimit = false
+
+ bool bubbleShieldEnabled = true
+
+ entity levelEnt
+
+ //TODO: Get rid of these and use the new StartParticleEffectInWorld_ReturnEntity etc functions
+ entity fx_CP_color_enemy //Used for setting control points on FX
+ entity fx_CP_color_friendly //Used for setting control points on FX
+ entity fx_CP_color_neutral //Used for setting control points on FX
+
+ array<entity>[ TEAM_COUNT ] classicMPDropships
+ bool evacEnabled = false
+
+ void functionref( entity player ) observerFunc
+ array<void functionref()> playingThinkFuncTable
+ array<void functionref()> thirtySecondsLeftFuncTable
+ void functionref( int progress ) matchProgressAnnounceFunc
+
+ void functionref( entity player ) cp_VO_NagFunc
+ void functionref( entity player, entity hardpoint, float distance ) cp_VO_ApproachFunc
+ void functionref( entity touchEnt, entity hardpoint ) cp_VO_LeftTriggerWithoutCappingFunc
+
+ table<int, string> hardpointStringIDs
+
+ entity[ TEAM_COUNT ] flagSpawnPoints
+
+ vector distCheckTestPoint
+
+ void functionref() scoreEventOverrideFunc
+
+ array<void functionref( entity, entity )> onLeechedCustomCallbackFunc
+
+ bool forceSpawnAsTitan = false
+ bool forceSpawnIntoTitan = false
+ bool forceDisableTitanfalls = false
+ bool titanfallEnabled = true
+
+ //RoundWinningKillReplay related
+ entity roundWinningKillReplayViewEnt = null
+ entity roundWinningKillReplayVictim = null
+ int roundWinningKillReplayInflictorEHandle = -1
+ bool watchingRoundWinningKillReplay = false
+
+ bool forceNoFinalRoundDraws = false //Setting this to true will force a round based mode to keep playing rounds until a winner is determined. Game will not end on draw.
+
+ bool roundBasedTeamScore_RoundReset = true //if true, reset team scores at the start of each round.
+ bool isInPilotGracePeriod = false // if true, all players will be allowed to switch loadouts
+}
+
+global SvGlobals svGlobal
+
+void function CodeCallback_MapSpawn() // original script entry point
+{
+ ScriptCompilerTest()
+ LoadDiamond()
+
+ _cc = CreateEntity( "point_clientcommand" )
+ _sc = CreateEntity( "point_servercommand" )
+ PrecacheEntity( "env_entity_dissolver" )
+
+ LevelVarInit()
+
+ svGlobal.worldspawn = GetEnt( "worldspawn" )
+ svGlobal.worldspawn.kv.startdark = true
+
+ PrecacheModel( $"models/dev/editor_ref.mdl" )
+ PrecacheModel( $"models/dev/empty_model.mdl" )
+ PrecacheModel( $"models/test/brad/store_card.mdl" )
+ PrecacheModel( $"models/test/brad/store_card_angel_city.mdl" )
+ PrecacheModel( $"models/test/brad/store_card_colony.mdl" )
+ PrecacheModel( $"models/test/brad/store_card_relic.mdl" )
+ PrecacheModel( $"models/test/brad/store_card_prime_bundle.mdl" )
+ PrecacheModel( $"models/test/brad/store_titan_warpaint_bundle.mdl" )
+ PrecacheModel( $"models/test/brad/store_weapon_warpaint_bundle.mdl" )
+ PrecacheModel( $"models/test/brad/jump_start.mdl" )
+ PrecacheModel( $"models/weapons/shoulder_rocket_SRAM/ptpov_law_menu.mdl" ) // HACK
+ PrecacheModel( $"models/weapons/lstar/ptpov_lstar_menu.mdl" ) // HACK
+ PrecacheModel( $"models/weapons/softball_at/ptpov_softball_at_menu.mdl" ) // HACK
+ PrecacheModel( $"models/weapons/mastiff_stgn/ptpov_mastiff_menu.mdl" ) // HACK
+ PrecacheModel( $"models/error.mdl" ) // model used when no model is provided
+ if ( DREW_MODE == 2 ) // TEMPHACK
+ PrecacheModel( GREEN_SCREEN_MODEL )
+
+ printl( "Code Script: _mapspawn" )
+
+ // This will end up in either SP or MP
+ SPMP_MapSpawn_Init()
+}
+
+
+var function CodeCallback_ClientCommand( entity player, array<string> args )
+{
+ /*printl( "############################" )
+ printl( "CodeCallback_ClientCommand() before" )
+ printl( "player = " + player )
+ printl( "args:" )
+ foreach( key, value in args )
+ printl( key + " : " + value )
+ printl( "############################" )*/
+
+ string commandString = args.remove( 0 )
+
+ //TODO: Track down Why VModEnable is being called from code?
+
+ //Assert( commandString in svGlobal.clientCommandCallbacks )
+ if ( commandString in svGlobal.clientCommandCallbacks )
+ {
+ return svGlobal.clientCommandCallbacks[ commandString ]( player, args )
+ }
+ else
+ {
+ printl( "############################" )
+ printl( "CommandString: " + commandString + " was not added via AddClientCommandCallback but is being called in CodeCallback_ClientCommand" )
+ printl( "############################" )
+ return false
+ }
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/_menu_callbacks.gnut b/Northstar.CustomServers/scripts/vscripts/_menu_callbacks.gnut
new file mode 100644
index 000000000..5b2c2c531
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_menu_callbacks.gnut
@@ -0,0 +1,14 @@
+global function MenuCallbacks_Init
+
+void function MenuCallbacks_Init()
+{
+ AddClientCommandCallback( "LeaveMatch", ClientCommandCallback_LeaveMatch )
+}
+
+bool function ClientCommandCallback_LeaveMatch( entity player, array<string> args )
+{
+ SavePdataForEntityIndex( player.GetPlayerIndex() )
+ ClientCommand( player, "disconnect" )
+
+ return true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_misc.gnut b/Northstar.CustomServers/scripts/vscripts/_misc.gnut
new file mode 100644
index 000000000..4384f8dfe
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_misc.gnut
@@ -0,0 +1,45 @@
+//todo figure out where these should be and move them to those places
+global function Spotting_Init
+global function FW_Border_GlobalInit
+global function IsVDUTitan
+global function PIN_PlayerRodeoedEnemyTitanToCompletion
+global function PlayerProgressionAllowed
+
+void function Spotting_Init()
+{
+
+}
+
+void function FW_Border_GlobalInit()
+{
+ AddCallback_EntitiesDidLoad( FW_Border_EntitiesDidLoad )
+}
+
+void function FW_Border_EntitiesDidLoad()
+{
+ if ( GetConVarString( "mp_gamemode" ) != "fw" )
+ {
+ // remove fw borders if we're not playing fw
+ array<entity> brushes = GetEntArrayByClass_Expensive( "func_brush" )
+ foreach ( entity brush in brushes )
+ if ( GetEditorClass( brush ) == "func_brush_fw_territory_border" && GameModeRemove( brush ) )
+ brush.Destroy()
+
+ return
+ }
+}
+
+bool function IsVDUTitan(entity titan)
+{
+ return false
+}
+
+void function PIN_PlayerRodeoedEnemyTitanToCompletion( entity player, entity titan, bool playerHadBattery )
+{
+
+}
+
+bool function PlayerProgressionAllowed( entity player )
+{
+ return true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_networkvars.gnut b/Northstar.CustomServers/scripts/vscripts/_networkvars.gnut
new file mode 100644
index 000000000..14990a152
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_networkvars.gnut
@@ -0,0 +1,169 @@
+untyped
+
+
+global function SetEntityVar
+global function SetServerVar
+global function SetNetworkVar
+global function SyncServerVars
+global function SyncEntityVars
+
+
+function SetEntityVar( entity ent, varName, value )
+{
+ Assert( IsServer() )
+ Assert( varName in _entityClassVars[ent.GetClassName()], "Entity " + ent + " does not have remote var " + varName )
+ Assert( varName in _entityClassVarsIsEnts[ent.GetClassName()] )
+ Assert( varName in _entityClassVarsSyncToAllClients[ent.GetClassName()] )
+ Assert( typeof value != "string" )
+
+ Assert( "_entityVars" in ent )
+
+ if ( ent._entityVars[varName] == value )
+ return
+
+ ent._entityVars[varName] = value
+
+ if ( _entityClassVarsIsEnts[ent.GetClassName()][varName] && value != null )
+ {
+ //printl( "SET NETWORK ENTITY VAR TO AN ENTITY. GETTING EHANDLE" )
+ value = value.GetEncodedEHandle()
+ }
+
+ local syncToAllPlayers = _entityClassVarsSyncToAllClients[ent.GetClassName()][varName]
+
+ // only sync "player" variables to that player
+ if ( ent.IsPlayer() && !ent.IsBot() && !syncToAllPlayers )
+ {
+ if ( !ent.p.clientScriptInitialized )
+ return
+
+ Remote_CallFunction_NonReplay( ent, "ServerCallback_SetEntityVar", ent.GetEncodedEHandle(), _entityClassVarHandles[varName], value )
+ }
+ else
+ {
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ if ( player.IsBot() )
+ continue
+
+ if ( !player.p.clientScriptInitialized )
+ continue
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetEntityVar", ent.GetEncodedEHandle(), _entityClassVarHandles[varName], value )
+ }
+ }
+}
+
+function SetServerVar( varName, value )
+{
+ Assert( IsServer() )
+ Assert( varName in _serverVars )
+ Assert( typeof value != "string" )
+ expect string( varName )
+
+ if ( _serverVars[varName] == value )
+ return
+
+ _serverVars[varName] = value
+
+ if ( varName in _serverEntityVars && value != null )
+ {
+ if ( IsValid( value ) )
+ value = value.GetEncodedEHandle()
+ else
+ value = null
+ }
+
+ // Run server script change callback if one exists
+ thread ServerVarChangedCallbacks( varName )
+
+ // Update the var on all clients
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ if ( !player.p.clientScriptInitialized )
+ continue
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetServerVar", _serverVarHandles[varName], value )
+ }
+}
+
+function SetNetworkVar( obj, varName, value )
+{
+ if ( obj == level )
+ {
+ return SetServerVar( varName, value )
+ }
+ else
+ {
+ expect entity( obj )
+ return SetEntityVar( obj, varName, value )
+ }
+}
+
+function SyncServerVars( entity player )
+{
+ Assert( IsServer() )
+
+ foreach ( varName, value in _serverVars )
+ {
+ if ( varName in _serverEntityVars && value != null )
+ {
+ if ( IsValid( value ) )
+ value = value.GetEncodedEHandle()
+ else
+ value = null
+ }
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetServerVar", _serverVarHandles[varName], value )
+ }
+}
+
+function SyncEntityVars( entity player )
+{
+ Assert( IsServer() )
+
+ foreach ( className, _ in _entityClassVars )
+ {
+ array<entity> entities
+ if ( className == "player" )
+ entities = GetPlayerArray()
+ else
+ entities = GetNPCArrayByClass( className )
+
+ foreach ( ent in entities )
+ {
+ if ( !IsValid( ent ) )
+ continue
+
+ foreach( varName, value in _entityClassVars[className] )
+ {
+ local entValue = ent._entityVars[varName]
+ if ( entValue == value )
+ continue
+
+ if ( !_entityClassVarsSyncToAllClients[className][varName] && ent != player )
+ {
+ Assert( className == "player" )
+ continue
+ }
+ //if ( className == "player" && !_entityClassVarsSyncToAllClients[className][varName] )
+ // continue
+ //
+ if ( _entityClassVarsIsEnts[className][varName] )
+ {
+ if ( !IsValid( entValue ) )
+ continue
+ // if this is an entity var, change over to e-handle
+ entValue = entValue.GetEncodedEHandle()
+ }
+
+ Assert( player.p.clientScriptInitialized )
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetEntityVar", ent.GetEncodedEHandle(), _entityClassVarHandles[varName], entValue )
+ }
+ }
+ }
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/_objective.gnut b/Northstar.CustomServers/scripts/vscripts/_objective.gnut
new file mode 100644
index 000000000..893861bf0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_objective.gnut
@@ -0,0 +1,108 @@
+untyped
+
+global function Objective_Init
+
+global function RegisterObjective
+global function SetCurrentTeamObjectiveForPlayer
+global function SetTeamActiveObjective
+global function ClearTeamActiveObjective
+
+global function SetPlayerActiveObjective
+global function ClearPlayerActiveObjective
+
+int convIndex = 0 //Note that objectiveIndex 0 is reserved by code to mean no objective active!
+
+//Split this out into _objective_shared, _objective and cl_objective once QA gets a chance to hammer at it.
+function Objective_Init()
+{
+ level.objToIndex <- {}
+ level.teamActiveObjective <- { [TEAM_IMC] = null, [TEAM_MILITIA] = null }
+
+}
+
+function RegisterObjective( objectiveName )
+{
+ convIndex++
+ level.objToIndex[ objectiveName ] <- convIndex
+}
+
+function CreateTeamActiveObjectiveTable( objectiveName, objectiveTimer = 0, objectiveEntity = null )
+{
+ local Table = {}
+ Table.objectiveName <- objectiveName
+ Table.objectiveTimer <- objectiveTimer
+ Table.objectiveEntity <- objectiveEntity
+
+ return Table
+}
+
+function SetCurrentTeamObjectiveForPlayer( entity player )
+{
+ int team = player.GetTeam()
+ local objectiveTable = GetTeamActiveObjective( team )
+
+ if ( objectiveTable )
+ {
+ local objectiveName = objectiveTable.objectiveName
+ local objectiveTimer = objectiveTable.objectiveTimer
+ local objectiveEntity = objectiveTable.objectiveEntity
+ SetPlayerActiveObjective( player, objectiveName, objectiveTimer, objectiveEntity )
+ }
+}
+
+function GetTeamActiveObjective( team )
+{
+ if ( (team != TEAM_IMC) && (team != TEAM_MILITIA) )
+ return null
+ return level.teamActiveObjective[team]
+}
+
+function SetTeamActiveObjective( team, objectiveName, objectiveTimer = 0, objectiveEntity = null )
+{
+ Assert( team == TEAM_IMC || team == TEAM_MILITIA )
+ array<entity> players = GetPlayerArrayOfTeam( team )
+
+ local objectiveIndex = level.objToIndex[ objectiveName ]
+
+ foreach ( player in players )
+ {
+ SetPlayerActiveObjective_Internal( player, objectiveIndex, objectiveTimer, objectiveEntity )
+ }
+
+ level.teamActiveObjective[ team ] = CreateTeamActiveObjectiveTable( objectiveName, objectiveTimer, objectiveEntity )
+}
+
+function ClearTeamActiveObjective( team )
+{
+ Assert( team == TEAM_IMC || team == TEAM_MILITIA )
+ array<entity> players = GetPlayerArrayOfTeam( team )
+ foreach ( player in players )
+ {
+ ClearPlayerActiveObjective( player )
+ }
+
+ level.teamActiveObjective[ team ] = null
+
+}
+
+function SetPlayerActiveObjective( player, objectiveName, objectiveTimer = 0, objectiveEntity = null )
+{
+ local objectiveIndex = level.objToIndex[ objectiveName ]
+
+ SetPlayerActiveObjective_Internal( player, objectiveIndex, objectiveTimer, objectiveEntity )
+}
+
+function SetPlayerActiveObjective_Internal( player, objectiveIndex, objectiveTimer, objectiveEntity )
+{
+ player.SetObjectiveEndTime( objectiveTimer )
+ player.SetObjectiveEntity( objectiveEntity )
+ player.SetObjectiveIndex( objectiveIndex )
+}
+
+function ClearPlayerActiveObjective( player )
+{
+ player.SetObjectiveEndTime( 0 )
+ player.SetObjectiveEntity( null )
+ player.SetObjectiveIndex( 0 )
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/_on_spawned.gnut b/Northstar.CustomServers/scripts/vscripts/_on_spawned.gnut
new file mode 100644
index 000000000..d1935d629
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_on_spawned.gnut
@@ -0,0 +1,508 @@
+untyped
+
+global function CodeCallback_PreSpawn
+global function CodeCallback_OnSpawned
+global function RunMySpawnFunctions
+global function AddScriptNoteworthySpawnCallback
+global function SpawnFromSpawnerArray
+global function AddSpawnCallback
+global function AddSpawnCallbackEditorClass
+global function AddSpawnCallback_ScriptName
+global function GetLeveledAISettings
+global function GetSpawnAISettings
+global function GetDefaultAISetting
+
+void function CodeCallback_PreSpawn( entity npc )
+{
+ /*
+ SCRIPTERS READ THIS
+
+ The purpose of this function is to fixup npc fields coming from all the many places they can come from, so that code doesn't break them during DispatchSpawn.
+ If you want to fix an AI field on spawned, you should do it in ai_spawn_content, unless the field change is related to code functionality and needs
+ to change before code spawns the AI. Then, it should be here.
+
+ Thanks
+ -Mackey
+ */
+ Assert( npc.IsNPC() )
+
+ if ( !npc.HasAISettings() )
+ {
+ // this ai has no ai settings file
+ string aisettings = GetDefaultAISetting( npc )
+ Assert( aisettings != "" )
+ SetAISettingsWrapper( npc, aisettings )
+ }
+
+ if ( npc.IsTitan() )
+ {
+ if ( npc.ai.titanSettings.titanSetFile == "" )
+ {
+ if ( npc.HasKey( "leveled_titan_settings" ) )
+ {
+ SetTitanSettings( npc.ai.titanSettings, expect string( npc.kv.leveled_titan_settings ) )
+ }
+ }
+
+ var builtInLoadout
+
+ if ( npc.HasKey( "leveled_titan_loadout" ) )
+ {
+ builtInLoadout = npc.GetValueForKey( "leveled_titan_loadout" )
+ }
+ else
+ {
+ if ( npc.Dev_GetAISettingByKeyField( "WeaponCapacity" ) == "FromLoadout" )
+ builtInLoadout = npc.Dev_GetAISettingByKeyField( "npc_titan_player_settings" )
+ }
+
+ if ( builtInLoadout != null )
+ {
+ // derive loadout from built in loadout in this case
+ expect string( builtInLoadout )
+ if ( npc.ai.titanSettings.titanSetFile != builtInLoadout )
+ SetTitanSettings( npc.ai.titanSettings, builtInLoadout )
+
+ npc.ai.titanSpawnLoadout.setFile = builtInLoadout
+ // OverwriteLoadoutWithDefaultsForSetFile_ExceptSpecialAndAntiRodeo( npc.ai.titanSpawnLoadout )
+ OverwriteLoadoutWithDefaultsForSetFile( npc.ai.titanSpawnLoadout ) // get the entire loadout, including defensive and tactical
+
+ //Set camo, decal, and skin indices from npc settings.
+ //Using Dev_GetAISettingsByKeyField_Global because titan has not spawned yet, so the non-global version of this function does not work.
+ var camoIndex = Dev_GetAISettingByKeyField_Global( npc.GetAISettingsName(), "titanCamoIndex" )
+ var decalIndex = Dev_GetAISettingByKeyField_Global( npc.GetAISettingsName(), "titanDecalIndex" )
+ var skinIndex = Dev_GetAISettingByKeyField_Global( npc.GetAISettingsName(), "titanSkinIndex" )
+ if ( camoIndex != null )
+ npc.ai.titanSpawnLoadout.camoIndex = expect int ( camoIndex )
+ if ( decalIndex != null )
+ npc.ai.titanSpawnLoadout.decalIndex = expect int ( decalIndex )
+ if ( skinIndex != null )
+ npc.ai.titanSpawnLoadout.skinIndex = expect int ( skinIndex )
+ }
+
+ AssignSpawnOptionsFromLeveled( npc, SetSpawnOption_Weapon, "additionalequipment", "primaryWeapon_mods" )
+ npc.kv.additionalequipment = ""
+ AssignSpawnOptionsFromLeveled( npc, SetSpawnOption_Ordnance, "titanOrdnance", "titanOrdnance_mods" )
+ AssignSpawnOptionsFromLeveled( npc, SetSpawnOption_Special, "titanSpecial", "titanSpecial_mods" )
+ AssignSpawnOptionsFromLeveled( npc, SetSpawnOption_Antirodeo, "titanAntiRodeo", "titanAntiRodeo_mods" )
+
+ // temp fix for npc_create npc_titan, probably should refactor away npc.ai.titanSettings
+ if ( npc.ai.titanSettings.titanSetFile == "" )
+ SetTitanSettings( npc.ai.titanSettings, expect string( npc.Dev_GetAISettingByKeyField( "npc_titan_player_settings" ) ) )
+
+ CreateTitanModelAndSkinSetup( npc )
+
+ if ( npc.GetAIClass() == AIC_TITAN_BUDDY )
+ npc.kv.squadname = "bt"
+ }
+ else
+ {
+ npc.SetValueForModelKey( npc.GetSettingModelName() )
+ }
+
+ if ( !IsTurret( npc ) && IsSingleplayer() && npc.kv.squadname == "" && npc.GetTeam() >= FIRST_GAME_TEAM )
+ npc.SetAutoSquad()
+ //AutoSquadnameAssignment( npc )
+
+ if ( !npc.IsTitan() )
+ {
+ AssignGrenadeWeaponFromAISettings( npc )
+
+ AssignDroneSpawnAISettings( npc )
+
+ if ( npc.ai.mySpawnOptions_weapon == null )
+ {
+ if ( !IsTurret( npc ) )
+ {
+ NPCDefaultWeapon ornull defaultWeapon = GetNPCDefaultWeaponForLevel( npc )
+ if ( defaultWeapon != null )
+ {
+ expect NPCDefaultWeapon( defaultWeapon )
+ SetSpawnOption_Weapon( npc, defaultWeapon.wep, defaultWeapon.mods )
+ npc.kv.additionalequipment = ""
+ return
+ }
+ }
+
+ switch ( npc.kv.additionalequipment )
+ {
+ case "":
+ case "auto_weapon":
+ case "auto_weapon_antititan":
+ case "auto_weapon_sidearm":
+ case "auto_weapon_rifle":
+ case "auto_weapon_lmg":
+ case "auto_weapon_shield_captain":
+ case "auto_weapon_shotgun":
+ case "auto_weapon_smg":
+ case "auto_weapon_sniper":
+ case "auto_weapon_specialist":
+
+ // fill weapon in from ai settings file
+ npc.kv.additionalequipment = ""
+ string aiSettingsWeapon = npc.AISetting_GetDefaultWeapon()
+ if ( aiSettingsWeapon != "" )
+ SetSpawnOption_Weapon( npc, aiSettingsWeapon )
+ break
+
+ case "none":
+ npc.kv.additionalequipment = ""
+ break
+ }
+ }
+ }
+}
+
+void function AssignDroneSpawnAISettings( entity npc )
+{
+ if ( npc.HasKey( "script_drone_type" ) )
+ {
+ var droneType = npc.kv.script_drone_type
+ if ( droneType != null )
+ {
+ expect string( droneType )
+ if ( droneType.tolower() == "none" )
+ droneType = ""
+ npc.ai.droneSpawnAISettings = droneType
+ }
+ return
+ }
+
+ npc.ai.droneSpawnAISettings = npc.AISetting_SummonDrone()
+}
+
+
+void function AssignGrenadeWeaponFromAISettings( entity npc )
+{
+ if( npc.kv.grenadeWeaponName == "none" )
+ {
+ npc.kv.grenadeWeaponName = ""
+ return
+ }
+
+ if ( npc.kv.GrenadeWeaponName != "" )
+ return
+
+ string grenadeWeaponName = npc.AISetting_GetGrenadeWeapon()
+ if ( grenadeWeaponName == "" )
+ return
+
+ npc.kv.grenadeWeaponName = grenadeWeaponName
+}
+
+void function AssignSpawnOptionsFromLeveled( entity npc, void functionref( entity, string, array<string> = 0 ) spawnSettingsFunc, string kvWeapon, string kvWeaponMods )
+{
+ if ( !npc.HasKey( kvWeapon ) )
+ return
+ string weapon = npc.GetValueForKey( kvWeapon )
+ if ( weapon == "" )
+ return
+
+ array<string> mods
+ if ( npc.HasKey( kvWeaponMods ) )
+ {
+ mods = split( npc.GetValueForKey( kvWeaponMods ), " " )
+ }
+
+ spawnSettingsFunc( npc, weapon, mods )
+}
+
+string function GetDefaultAISetting( entity npc )
+{
+// change this to map directly by file name from subClass, and error if its not there.
+// This insures consistent settings file naming and makes settings files less of a mix-and-match concept.
+// subclasses should also sub-name off their class (except for craaaaaazy soldier/grunt guy)
+
+ if ( npc.mySpawnOptions_aiSettings != null )
+ {
+ // you have to include base if you use SpawnOption_AISettings
+ return string( npc.mySpawnOptions_aiSettings )
+ }
+
+ if ( npc.HasKey( "leveled_aisettings" ) )
+ {
+ return GetLeveledAISettings( npc )
+ }
+
+ if ( npc.IsTitan() && npc.ai.titanSettings.titanSetFile != "" )
+ {
+ // from titan player set file
+ string settingsKey = GetAISettingsStringForMode()
+
+ var aiSettings = Dev_GetPlayerSettingByKeyField_Global( npc.ai.titanSettings.titanSetFile, settingsKey )
+ if ( aiSettings != null )
+ return expect string( aiSettings )
+ }
+
+ return npc.GetClassName()
+}
+
+string function GetLeveledAISettings( entity npc )
+{
+ Assert( npc.IsNPC() )
+ Assert( npc.HasKey( "leveled_aisettings" ) )
+ string settings = expect string( npc.kv.leveled_aisettings )
+ switch ( settings )
+ {
+ // remap deprecated substrings for awhile
+ case "npc_soldier_drone_summoner_shield":
+ return "npc_soldier_drone_summoner"
+ }
+ return settings
+}
+
+string function GetSpawnAISettings( entity npc )
+{
+ if ( npc.mySpawnOptions_aiSettings != null)
+ return expect string( npc.mySpawnOptions_aiSettings )
+ else if ( npc.HasKey( "leveled_aisettings" ) )
+ return expect string( npc.kv.leveled_aisettings )
+
+ return ""
+}
+
+void function CodeCallback_OnSpawned( entity ent )
+{
+ if ( IsSpawner( ent ) )
+ {
+ var spawnerKVs = ent.GetSpawnEntityKeyValues()
+ if ( "script_flag_killed" in spawnerKVs )
+ thread SetupFlagKilledForNPC( ent )
+ return
+ }
+
+ string classname = ent.GetClassName()
+
+ if ( classname in _entityClassVars )
+ {
+ if ( !ent._entityVars )
+ InitEntityVars( ent )
+
+ //ent.ConnectOutput( "OnDestroy", "_RemoveFromEntityList" )
+ }
+
+ int teamNum = int( expect string( ent.kv.teamnumber ) )
+ if ( teamNum != 0 )
+ SetTeam( ent, teamNum )
+
+ SetModelSkinFromLeveled( ent )
+
+ if ( IsLobby() )
+ {
+ RunMySpawnFunctions( ent )
+ return
+ }
+
+ if ( ent.IsNPC() )
+ {
+ CommonNPCOnSpawned( ent )
+ }
+
+ if ( ent instanceof CBaseCombatCharacter && ent.GetModelName() != $"" )
+ InitDamageStates( ent )
+
+ if ( ent instanceof CProjectile || ent instanceof CBaseGrenade )
+ thread PROTO_InitTrackedProjectile( ent )
+
+ /*
+ if ( !( "totalSpawned" in level ) )
+ {
+ level.totalSpawned <- {}
+ level.totalSpawned.total <- 0
+ }
+
+ if ( !( "classname" in level.totalSpawned ) )
+ {
+ level.totalSpawned[ classname ] <- {}
+ }
+
+ level.totalSpawned[ classname ][ ent ] <- ent
+ level.totalSpawned.total++
+ */
+
+ RegisterForDamageDeathCallbacks( ent )
+
+ RunMySpawnFunctions( ent )
+}
+
+function RunMySpawnFunctions( entity self )
+{
+ if ( !IsValid( self ) )
+ {
+ // entity was deleted already
+ return
+ }
+
+ RunSpawnCallbacks( self )
+ RunEditorClassCallbacks( self )
+ RunScriptNoteworthyCallbacks( self )
+ RunScriptNameCallbacks( self )
+}
+
+void function AddSpawnCallback( string classname, void functionref( entity ) func )
+{
+ foreach ( spawnCallbackFuncArray funcArray in svGlobal.spawnCallbackFuncs )
+ {
+ if ( funcArray.entityClassname == classname )
+ {
+ funcArray.callbackArray.append( func )
+ return
+ }
+ }
+
+ spawnCallbackFuncArray funcArray
+ funcArray.entityClassname = classname
+ funcArray.callbackArray.append( func )
+ svGlobal.spawnCallbackFuncs.append( funcArray )
+}
+
+void function AddSpawnCallbackEditorClass( string classname, string editorClassname, void functionref( entity ) func )
+{
+ foreach ( spawnCallbackEditorClassFuncArray funcArray in svGlobal.spawnCallbackEditorClassFuncs )
+ {
+ if ( funcArray.entityClassname == classname && funcArray.entityEditorClassname == editorClassname )
+ {
+ funcArray.callbackArray.append( func )
+ return
+ }
+ }
+
+ spawnCallbackEditorClassFuncArray funcArray
+ funcArray.entityClassname = classname
+ funcArray.entityEditorClassname = editorClassname
+ funcArray.callbackArray.append( func )
+ svGlobal.spawnCallbackEditorClassFuncs.append( funcArray )
+}
+
+function RunSpawnCallbacks( entity self )
+{
+ string classname = self.GetClassName()
+
+ foreach ( spawnCallbackFuncArray funcArray in svGlobal.spawnCallbackFuncs )
+ {
+ if ( funcArray.entityClassname == classname )
+ {
+ foreach ( func in funcArray.callbackArray )
+ {
+ func( self )
+ }
+ }
+ }
+}
+
+function RunEditorClassCallbacks( entity self )
+{
+ string editorClassname = GetEditorClass( self )
+ if ( editorClassname == "" )
+ return
+
+ string classname = self.GetClassName()
+
+ foreach ( spawnCallbackEditorClassFuncArray funcArray in svGlobal.spawnCallbackEditorClassFuncs )
+ {
+ if ( funcArray.entityEditorClassname == editorClassname )
+ {
+ //Assert( funcArray.entityClassname == classname, "Editor classname callback was set on entity with wrong base classname type" )
+ if ( funcArray.entityClassname != classname )
+ CodeWarning( "Entity " + editorClassname + " is expecting alias of " + funcArray.entityClassname + " but found a " + classname + ". You may just need to reexport from LevelEd and recompile the map to fix this." )
+
+ foreach ( func in funcArray.callbackArray )
+ {
+ thread func( self )
+ }
+ }
+ }
+}
+
+array<entity> function SpawnFromSpawnerArray( array<entity> spawners, void functionref( entity ) ornull spawnSettingsFunc = null )
+{
+ array<entity> spawned
+ if ( spawnSettingsFunc == null )
+ {
+ foreach ( entity spawner in spawners )
+ {
+ entity ent = spawner.SpawnEntity()
+ DispatchSpawn( ent )
+ spawned.append( ent )
+ }
+ }
+ else
+ {
+ expect void functionref( entity )( spawnSettingsFunc )
+ foreach ( entity spawner in spawners )
+ {
+ entity ent = spawner.SpawnEntity()
+ spawnSettingsFunc( ent )
+ DispatchSpawn( ent )
+ spawned.append( ent )
+ }
+ }
+
+ return spawned
+}
+
+void function RunScriptNameCallbacks( entity ent )
+{
+ string name = ent.GetScriptName()
+ if ( !( name in svGlobal.spawnCallbacks_scriptName ) )
+ return
+
+ foreach ( callback in svGlobal.spawnCallbacks_scriptName[ name ] )
+ {
+ thread callback( ent )
+ }
+}
+
+void function AddSpawnCallback_ScriptName( string scriptName, void functionref( entity ) func )
+{
+ if ( !( scriptName in svGlobal.spawnCallbacks_scriptName ) )
+ svGlobal.spawnCallbacks_scriptName[ scriptName ] <- []
+ svGlobal.spawnCallbacks_scriptName[ scriptName ].append( func )
+}
+
+void function RunScriptNoteworthyCallbacks( entity ent )
+{
+ if ( !( ent.HasKey( "script_noteworthy" ) ) )
+ return
+
+ foreach ( noteworthyCallback in svGlobal.spawnCallbackFuncs_scriptNoteworthy )
+ {
+ if ( ent.kv.script_noteworthy != noteworthyCallback.scriptNoteworthy )
+ continue
+
+ foreach ( func in noteworthyCallback.callbackArray )
+ {
+ func( ent )
+ }
+
+ break // ??? break?
+ }
+}
+
+void function AddScriptNoteworthySpawnCallback( string script_noteworthy, void functionref( entity ) func )
+{
+ foreach ( noteworthyCallback in svGlobal.spawnCallbackFuncs_scriptNoteworthy )
+ {
+ if ( script_noteworthy != noteworthyCallback.scriptNoteworthy )
+ continue
+
+ noteworthyCallback.callbackArray.append( func )
+ return
+ }
+
+ spawnCallbackFuncArray_scriptNoteworthy newNoteworthyCallback
+ newNoteworthyCallback.scriptNoteworthy = script_noteworthy
+ newNoteworthyCallback.callbackArray.append( func )
+ svGlobal.spawnCallbackFuncs_scriptNoteworthy.append( newNoteworthyCallback )
+}
+
+void function SetModelSkinFromLeveled( entity ent )
+{
+ // Hack that we have to wait a frame for it to work. Code should just do this for us anyways.
+ if ( !ent.HasKey( "modelskin" ) )
+ return
+
+ int skin = expect int( ent.kv.modelskin.tointeger() )
+ if ( skin > 0 )
+ ent.SetSkin( skin )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_pain_death_sounds.gnut b/Northstar.CustomServers/scripts/vscripts/_pain_death_sounds.gnut
new file mode 100644
index 000000000..10d2b6166
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_pain_death_sounds.gnut
@@ -0,0 +1,455 @@
+global function PainDeathSounds_Init
+global function PlayDeathSounds
+global function PlayPainSounds
+global function TogglePainDeathDebug
+
+struct PainOrDeathSound
+{
+ bool functionref( entity, entity, bool, int, int ) isSoundTypeFunc
+ string alias_1p_victim_only
+ string alias_3p_except_victim
+ string alias_3p_attacker_only
+ string alias_3p_except_attacker
+ bool blocksPriority
+ int priority
+}
+
+struct
+{
+ array< array<PainOrDeathSound> > painSounds
+ array< array<PainOrDeathSound> > deathSounds
+
+ bool painDeathDebug
+} file
+
+
+enum eBodyTypes
+{
+ NPC_ANDROID
+ NPC_GRUNT
+ NPC_MARVIN
+ NPC_PROWLER
+ NPC_SPECIALIST
+ NPC_SPECTRE
+ NPC_STALKER
+ NPC_SUPER_SPECTRE
+ PLAYER_ANDROID_FEMALE
+ PLAYER_ANDROID_MALE
+ PLAYER_HUMAN_FEMALE
+ PLAYER_HUMAN_MALE
+ TITAN
+ total
+}
+
+int function GetBodyTypeIndexFromVictim( entity victim )
+{
+ // can add hologram support if needed
+ if ( victim.IsHologram() )
+ return -1
+
+ if ( victim.IsTitan() )
+ return eBodyTypes.TITAN
+
+ if ( victim.IsPlayer() )
+ {
+ if ( victim.IsMechanical() )
+ {
+ if ( IsPlayerFemale( victim ) )
+ return eBodyTypes.PLAYER_ANDROID_FEMALE
+
+ return eBodyTypes.PLAYER_ANDROID_MALE
+ }
+ else
+ {
+ if ( IsPlayerFemale( victim ) )
+ return eBodyTypes.PLAYER_HUMAN_FEMALE
+
+ return eBodyTypes.PLAYER_HUMAN_MALE
+ }
+ }
+
+ if ( IsSpecialist( victim ) )
+ return eBodyTypes.NPC_SPECIALIST
+
+ if ( IsGrunt( victim ) )
+ return eBodyTypes.NPC_GRUNT
+
+ if ( IsProwler( victim ) )
+ return eBodyTypes.NPC_PROWLER
+
+ if ( IsSuperSpectre( victim ) )
+ return eBodyTypes.NPC_SUPER_SPECTRE
+
+ if ( IsSpectre( victim ) )
+ return eBodyTypes.NPC_SPECTRE
+
+ if ( IsStalker( victim ) )
+ return eBodyTypes.NPC_STALKER
+
+ if ( IsMarvin( victim ) )
+ return eBodyTypes.NPC_MARVIN
+
+ return -1
+}
+
+void function PainDeathSounds_Init()
+{
+ file.painSounds.resize( eBodyTypes.total )
+ file.deathSounds.resize( eBodyTypes.total )
+
+ var dataTable = GetDataTable( $"datatable/pain_death_sounds.rpak" )
+ int numRows = GetDatatableRowCount( dataTable )
+
+ int eventColumn = GetDataTableColumnByName( dataTable, "event" )
+ int blocksPriorityColumn = GetDataTableColumnByName( dataTable, "blocksNextPriority" )
+ int methodColumn = GetDataTableColumnByName( dataTable, "method" )
+ int priorityColumn = GetDataTableColumnByName( dataTable, "priority" )
+ int bodyTypeColumn = GetDataTableColumnByName( dataTable, "bodyType" )
+ int alias_1p_victim_only_column = GetDataTableColumnByName( dataTable, "alias_1p_victim_only" )
+ int alias_3p_except_victim_column = GetDataTableColumnByName( dataTable, "alias_3p_except_victim" )
+ int alias_3p_attacker_only_column = GetDataTableColumnByName( dataTable, "alias_3p_attacker_only" )
+ int alias_3p_except_attacker_column = GetDataTableColumnByName( dataTable, "alias_3p_except_attacker" )
+ int visibleColumn = GetDataTableColumnByName( dataTable, "spmp" )
+
+ table<string,bool> visibleMask
+ visibleMask[ "spmp" ] <- true
+ if ( IsMultiplayer() )
+ visibleMask[ "mp" ] <- true
+ else if ( IsSingleplayer() )
+ visibleMask[ "sp" ] <- true
+
+ for ( int i = 0; i < numRows; i++ )
+ {
+ string visible = GetDataTableString( dataTable, i, visibleColumn )
+ if ( !( visible in visibleMask ) )
+ continue
+
+ int priority = GetDataTableInt( dataTable, i, priorityColumn )
+ bool blocksPriority = GetDataTableBool( dataTable, i, blocksPriorityColumn )
+ string event = GetDataTableString( dataTable, i, eventColumn )
+ string method = GetDataTableString( dataTable, i, methodColumn )
+ string bodyTypeName = GetDataTableString( dataTable, i, bodyTypeColumn )
+ string alias_1p_victim_only = GetDataTableString( dataTable, i, alias_1p_victim_only_column )
+ string alias_3p_except_victim = GetDataTableString( dataTable, i, alias_3p_except_victim_column )
+ string alias_3p_attacker_only = GetDataTableString( dataTable, i, alias_3p_attacker_only_column )
+ string alias_3p_except_attacker = GetDataTableString( dataTable, i, alias_3p_except_attacker_column )
+ int bodyType = eBodyTypes[ bodyTypeName ]
+
+ PainOrDeathSound painOrDeathSound
+ painOrDeathSound.isSoundTypeFunc = GetSoundTypeFuncFromName( method )
+ painOrDeathSound.alias_1p_victim_only = alias_1p_victim_only
+ painOrDeathSound.alias_3p_except_victim = alias_3p_except_victim
+ painOrDeathSound.alias_3p_attacker_only = alias_3p_attacker_only
+ painOrDeathSound.alias_3p_except_attacker = alias_3p_except_attacker
+ painOrDeathSound.blocksPriority = blocksPriority
+ painOrDeathSound.priority = priority
+
+ #if DEV
+ if ( priority < 100 || priority > 500 )
+ CodeWarning( "PainDeathSound event priority must be between 100 and 500. See " + event + " " + method )
+ #endif
+
+ switch ( event )
+ {
+ case "pain":
+ file.painSounds[ bodyType ].append( painOrDeathSound )
+ break
+
+ case "death":
+ file.deathSounds[ bodyType ].append( painOrDeathSound )
+ break
+
+ default:
+ CodeWarning( "Couldn't find pain/death event type " + event )
+ break
+ }
+ }
+
+ for ( int i = 0; i < eBodyTypes.total; i++ )
+ {
+ file.painSounds[ i ].sort( PainOrDeathSort )
+ file.deathSounds[ i ].sort( PainOrDeathSort )
+ }
+}
+
+int function PainOrDeathSort( PainOrDeathSound a, PainOrDeathSound b )
+{
+ if ( a.priority < b.priority )
+ return -1
+ if ( b.priority < a.priority )
+ return 1
+ return 0
+}
+
+
+bool functionref( entity, entity, bool, int, int ) function GetSoundTypeFuncFromName( string method )
+{
+ switch ( method )
+ {
+ case "SE_ANY":
+ return SE_ANY
+
+ case "SE_GIB":
+ return SE_GIB
+
+ case "SE_BULLET":
+ return SE_BULLET
+
+ case "SE_DISSOLVE":
+ return SE_DISSOLVE
+
+ case "SE_ELECTRICAL":
+ return SE_ELECTRICAL
+
+ case "SE_EXPLOSION":
+ return SE_EXPLOSION
+
+ case "SE_FALL":
+ return SE_FALL
+
+ case "SE_HEADSHOT_BULLET":
+ return SE_HEADSHOT_BULLET
+
+ case "SE_HEADSHOT_SHOTGUN":
+ return SE_HEADSHOT_SHOTGUN
+
+ case "SE_HEADSHOT_TITAN":
+ return SE_HEADSHOT_TITAN
+
+ case "SE_NECK_SNAP":
+ return SE_NECK_SNAP
+
+ case "SE_THERMITE_GRENADE":
+ return SE_THERMITE_GRENADE
+
+ case "SE_PROWLER":
+ return SE_PROWLER
+
+ case "SE_SMOKE":
+ return SE_SMOKE
+
+ case "SE_TITAN_STEP":
+ return SE_TITAN_STEP
+ }
+}
+
+bool function SE_ANY( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return true
+}
+
+bool function SE_GIB( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return bool( damageTypes & DF_GIB )
+}
+
+bool function SE_BULLET( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return bool( damageTypes & DF_BULLET )
+}
+
+bool function SE_DISSOLVE( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return bool( damageTypes & DF_DISSOLVE )
+}
+
+bool function SE_ELECTRICAL( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return bool( damageTypes & DF_ELECTRICAL )
+}
+
+bool function SE_EXPLOSION( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return bool( damageTypes & DF_EXPLOSION )
+}
+
+bool function SE_FALL( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return damageSourceID == eDamageSourceId.fall
+}
+
+bool function SE_HEADSHOT_BULLET( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ if ( !isValidHeadshot )
+ return false
+
+ return bool( damageTypes & DF_BULLET )
+}
+
+bool function SE_HEADSHOT_SHOTGUN( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ if ( !isValidHeadshot )
+ return false
+
+ return bool( damageTypes & DF_SHOTGUN )
+}
+
+bool function SE_HEADSHOT_TITAN( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ if ( !attacker.IsTitan() )
+ return false
+
+ return isValidHeadshot
+}
+
+bool function SE_NECK_SNAP( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return damageSourceID == eDamageSourceId.human_execution
+}
+
+bool function SE_THERMITE_GRENADE( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return damageSourceID == eDamageSourceId.mp_weapon_thermite_grenade
+}
+
+bool function SE_PROWLER( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ if ( !IsValid( attacker ) )
+ return false
+
+ return IsProwler( attacker )
+}
+
+bool function SE_SMOKE( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return damageSourceID == eDamageSourceId.mp_weapon_grenade_electric_smoke
+}
+
+bool function SE_TITAN_STEP( entity victim, entity attacker, bool isValidHeadshot, int damageTypes, int damageSourceID )
+{
+ return bool( damageTypes & DF_TITAN_STEP )
+}
+
+void function PlayPainSounds( entity victim, var damageInfo )
+{
+ int bodyType = GetBodyTypeIndexFromVictim( victim )
+ if ( bodyType >= 0 )
+ PlayPainOrDeathSounds( file.painSounds[ bodyType ], victim, damageInfo )
+}
+
+void function PlayDeathSounds( entity victim, var damageInfo )
+{
+ int bodyType = GetBodyTypeIndexFromVictim( victim )
+ if ( bodyType >= 0 )
+ PlayPainOrDeathSounds( file.deathSounds[ bodyType ], victim, damageInfo )
+}
+
+void function PlayPainOrDeathSounds( array<PainOrDeathSound> soundEvents, entity victim, var damageInfo )
+{
+ array<string> alias_1p_victim_only
+ array<string> alias_3p_except_victim
+ array<string> alias_3p_attacker_only
+ array<string> alias_3p_except_attacker
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ bool isValidHeadshot = IsValidHeadShot( damageInfo, victim )
+ int damageTypes = DamageInfo_GetCustomDamageType( damageInfo )
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ int lastPriority = 0
+ bool blockingPriority
+
+ foreach ( painOrDeathSound in soundEvents )
+ {
+ Assert( painOrDeathSound.priority >= lastPriority )
+
+ if ( blockingPriority )
+ {
+ if ( painOrDeathSound.priority > lastPriority )
+ break
+ }
+
+ if ( painOrDeathSound.isSoundTypeFunc( victim, attacker, isValidHeadshot, damageTypes, damageSourceID ) )
+ {
+ if ( painOrDeathSound.alias_1p_victim_only != "" )
+ alias_1p_victim_only.append( painOrDeathSound.alias_1p_victim_only )
+ if ( painOrDeathSound.alias_3p_except_victim != "" )
+ alias_3p_except_victim.append( painOrDeathSound.alias_3p_except_victim )
+ if ( painOrDeathSound.alias_3p_attacker_only != "" )
+ alias_3p_attacker_only.append( painOrDeathSound.alias_3p_attacker_only )
+ if ( painOrDeathSound.alias_3p_except_attacker != "" )
+ alias_3p_except_attacker.append( painOrDeathSound.alias_3p_except_attacker )
+
+ blockingPriority = painOrDeathSound.blocksPriority || blockingPriority
+ }
+
+ lastPriority = painOrDeathSound.priority
+ }
+
+ foreach ( sound in alias_3p_except_victim )
+ {
+ EmitSoundOnEntity( victim, sound )
+ }
+
+ if ( victim.IsPlayer() )
+ {
+ foreach ( sound in alias_1p_victim_only )
+ {
+ EmitSoundOnEntityOnlyToPlayer( victim, victim, sound )
+ }
+ }
+
+ if ( attacker.IsPlayer() )
+ {
+ foreach ( sound in alias_3p_except_attacker )
+ {
+ EmitSoundOnEntityExceptToPlayer( victim, attacker, sound )
+ }
+
+ foreach ( sound in alias_3p_attacker_only )
+ {
+ EmitSoundOnEntityOnlyToPlayer( victim, attacker, sound )
+ }
+ }
+ else
+ {
+ foreach ( sound in alias_3p_except_attacker )
+ {
+ EmitSoundOnEntity( victim, sound )
+ }
+ }
+
+ #if DEV
+ if ( !file.painDeathDebug )
+ return
+
+ foreach ( sound in alias_3p_except_victim )
+ {
+ printt( "PAIN_DEATH_DEBUG: EmitSoundOnEntity - " + sound )
+ }
+
+ if ( victim.IsPlayer() )
+ {
+ foreach ( sound in alias_1p_victim_only )
+ {
+ printt( "PAIN_DEATH_DEBUG: EmitSoundOnEntityOnlyToPlayer - " + sound )
+ }
+ }
+
+ if ( attacker.IsPlayer() )
+ {
+ foreach ( sound in alias_3p_except_attacker )
+ {
+ printt( "PAIN_DEATH_DEBUG: EmitSoundOnEntityExceptToPlayer - " + sound )
+ }
+
+ foreach ( sound in alias_3p_attacker_only )
+ {
+ printt( "PAIN_DEATH_DEBUG: EmitSoundOnEntityOnlyToPlayer - " + sound )
+ }
+ }
+ else
+ {
+ foreach ( sound in alias_3p_except_attacker )
+ {
+ printt( "PAIN_DEATH_DEBUG: EmitSoundOnEntity - " + sound )
+ }
+ }
+ #endif
+}
+
+void function TogglePainDeathDebug()
+{
+ file.painDeathDebug = !file.painDeathDebug
+ printt( "PainDeathDebug is " + file.painDeathDebug )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_passives.gnut b/Northstar.CustomServers/scripts/vscripts/_passives.gnut
new file mode 100644
index 000000000..1264686ec
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_passives.gnut
@@ -0,0 +1,1657 @@
+untyped
+
+global function Passives_Init
+
+global function InitPassives
+global function GivePassive
+global function GivePassiveLifeLong
+global function GiveTitanPassiveLifeLong
+global function TakePassive
+global function TakeAllPassives
+global function ScanMinimap
+global function MinimapPlayerConnected
+global function SoulHasPassive
+global function ScanMinimapUntilDeath
+global function GivePlayerPassivesFromSoul
+global function PrintAllPassives
+
+global function IsConscript
+
+global function UpdateMinimapStatusToOtherPlayers
+global function UpdateTitanMinimapStatusToOtherPlayers
+global function UpdateAIMinimapStatusToOtherPlayers
+global function UpdateMinimapStatus // moves to minimap script eventually?
+global function ApplyTitanWeaponPassives
+global function UpdateScorchHotStreakCoreMeter
+#if MP
+global function ApplyFDUpgradeWeaponPassives
+global function ApplyFDDerviedUpgrades
+#endif
+
+const FD_HOT_STREAK_DAMAGE_MAX = 10000
+const FD_HOT_STREAK_DECAY_TIME = 30.0 //1/2 this value until it the hotstreak falls off. 1/2 the value until it goes from full to empty.
+//const FD_HOT_STREAK_CORE_MULTIPLIER_MAX = 0.5
+
+function Passives_Init()
+{
+ RegisterSignal( "EndCloakedWallHangs" )
+ RegisterSignal( "EndCloakedWallruns" )
+
+ AddSpawnCallback( "npc_spectre", MinimapNPCSpawned )
+ AddSpawnCallback( "npc_soldier", MinimapNPCSpawned )
+ AddDeathCallback( "player", PassiveDeathCallback )
+
+ AddCallback_OnTitanGetsNewTitanLoadout( ApplyTitanWeaponPassives )
+
+ #if MP
+ if ( GetCurrentPlaylistVarInt( "aegis_upgrades", 0 ) == 1 )
+ AddCallback_OnUpdateDerivedPlayerTitanLoadout( ApplyFDDerviedUpgrades ) //Half of the meta functions, the other half lives in passives.gnut This is used for class mods.
+ #endif
+
+ level.wifiLeachInterval <- 2.5
+}
+
+function InitPassives( entity player )
+{
+ player.s.removePassiveOnDeath <- {}
+}
+
+#if MP
+void function ApplyFDDerviedUpgrades( entity player, TitanLoadoutDef loadout )
+{
+ array<ItemData> fdUpgrades = GetAllItemsOfType( eItemTypes.TITAN_FD_UPGRADE )
+ array<string> upgradeRefs
+ foreach ( ItemData upgrade in fdUpgrades )
+ {
+ if ( loadout.titanClass == upgrade.parentRef && !IsSubItemLocked( player, upgrade.ref, upgrade.parentRef ) )
+ {
+ upgradeRefs.append( upgrade.ref )
+ }
+ }
+ if ( loadout.titanClass == "ronin" )
+ ApplyDerivedRoninFDUpgrades( upgradeRefs, loadout )
+ else if ( loadout.titanClass == "northstar" )
+ ApplyDerivedNorthstarFDUpgrades( upgradeRefs, loadout )
+ else if ( loadout.titanClass == "vanguard" )
+ ApplyDerivedVanguardFDUpgrades( upgradeRefs, loadout )
+ else if ( loadout.titanClass == "ion" )
+ ApplyDerivedIonFDUpgrades( upgradeRefs, loadout )
+ else if ( loadout.titanClass == "tone" )
+ ApplyDerivedToneFDUpgrades( upgradeRefs, loadout )
+ else if ( loadout.titanClass == "scorch" )
+ ApplyDerivedScorchFDUpgrades( upgradeRefs, loadout )
+ else if ( loadout.titanClass == "legion" )
+ ApplyDerivedLegionFDUpgrades( upgradeRefs, loadout )
+}
+
+void function ApplyFDUpgradeWeaponPassives( entity titan, TitanLoadoutDef loadout )
+{
+ entity player
+ if ( titan.IsPlayer() )
+ player = titan
+ else if ( IsValid( titan.mySpawnOptions_ownerPlayer ) )
+ player = expect entity( titan.mySpawnOptions_ownerPlayer )
+ else
+ player = titan.GetBossPlayer()
+
+ if ( !IsValid( player ) )
+ return
+
+ array<ItemData> fdUpgrades = GetAllItemsOfType( eItemTypes.TITAN_FD_UPGRADE )
+ array<string> upgradeRefs
+ foreach ( ItemData upgrade in fdUpgrades )
+ {
+ if ( loadout.titanClass == upgrade.parentRef && !IsSubItemLocked( player, upgrade.ref, upgrade.parentRef ) )
+ upgradeRefs.append( upgrade.ref )
+ }
+
+ if ( loadout.titanClass == "ronin" )
+ ApplyRoninFDUpgrades( upgradeRefs, titan, loadout )
+ else if ( loadout.titanClass == "northstar" )
+ ApplyNorthstarFDUpgrades( upgradeRefs, titan, loadout )
+ else if ( loadout.titanClass == "vanguard" )
+ ApplyVanguardFDUpgrades( upgradeRefs, titan, loadout )
+ else if ( loadout.titanClass == "ion" )
+ ApplyIonFDUpgrades( upgradeRefs, titan, loadout )
+ else if ( loadout.titanClass == "tone" )
+ ApplyToneFDUpgrades( upgradeRefs, titan, loadout )
+ else if ( loadout.titanClass == "scorch" )
+ ApplyScorchFDUpgrades( upgradeRefs, titan, loadout )
+ else if ( loadout.titanClass == "legion" )
+ ApplyLegionFDUpgrades( upgradeRefs, titan, loadout )
+}
+
+void function ApplyDerivedRoninFDUpgrades( array<string> upgradeRefs, TitanLoadoutDef loadout )
+{
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_ronin_defense_tier_1":
+ loadout.setFileMods.append( "fd_health_upgrade" )
+ break
+ }
+ }
+}
+
+void function ApplyRoninFDUpgrades( array<string> upgradeRefs, entity titan, TitanLoadoutDef loadout )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Ejecting
+ return
+
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_ronin_utility_tier_1":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_phase_charges" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_ronin_utility_tier_2":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_phase_distance" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_ronin_defense_tier_2":
+ float titanShieldHealth = GetTitanSoulShieldHealth( soul )
+ soul.SetShieldHealthMax( int( titanShieldHealth * 1.5 ) )
+ break
+ case "fd_upgrade_ronin_weapon_tier_1":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_MELEE )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_sword_upgrade" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_ronin_weapon_tier_2":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_sword_block" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_ronin_ultimate":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_duration" )
+ weapon.SetMods( mods )
+ break
+ }
+ }
+}
+
+void function ApplyDerivedNorthstarFDUpgrades( array<string> upgradeRefs, TitanLoadoutDef loadout )
+{
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_northstar_defense_tier_1":
+ loadout.setFileMods.append( "fd_health_upgrade" )
+ break
+ }
+ }
+}
+
+void function ApplyNorthstarFDUpgrades( array<string> upgradeRefs, entity titan, TitanLoadoutDef loadout )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Ejecting
+ return
+
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_northstar_utility_tier_1":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_explosive_trap" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_northstar_utility_tier_2":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_trap_charges" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_northstar_defense_tier_2":
+ float titanShieldHealth = GetTitanSoulShieldHealth( soul )
+ soul.SetShieldHealthMax( int( titanShieldHealth * 1.5 ) )
+ break
+ case "fd_upgrade_northstar_weapon_tier_1":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_upgrade_charge" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_northstar_weapon_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_upgrade_crit" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_northstar_ultimate":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_twin_cluster" )
+ weapon.SetMods( mods )
+ break
+ }
+ }
+}
+
+void function ApplyDerivedVanguardFDUpgrades( array<string> upgradeRefs, TitanLoadoutDef loadout )
+{
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_vanguard_defense_tier_1":
+ loadout.setFileMods.append( "fd_health_upgrade" )
+ break
+ }
+ }
+}
+
+void function ApplyVanguardFDUpgrades( array<string> upgradeRefs, entity titan, TitanLoadoutDef loadout )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Ejecting
+ return
+
+ entity primaryWeapon = titan.GetMainWeapons()[0]
+ array<string> primaryMods = primaryWeapon.GetMods()
+ primaryMods.append( "fd_balance" )
+ primaryWeapon.SetMods( primaryMods )
+
+ entity ordanance = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ array<string> ordananceMods = ordanance.GetMods()
+ ordananceMods.append( "fd_balance" )
+ ordanance.SetMods( ordananceMods )
+
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_vanguard_utility_tier_1":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_vanguard_utility_1" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_vanguard_utility_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_vanguard_utility_2" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_vanguard_defense_tier_2":
+ float titanShieldHealth = GetTitanSoulShieldHealth( soul )
+ soul.SetShieldHealthMax( int( titanShieldHealth * 1.5 ) )
+ break
+ case "fd_upgrade_vanguard_weapon_tier_1":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_vanguard_weapon_1" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_vanguard_weapon_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_vanguard_weapon_2" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_vanguard_ultimate":
+ if ( SoulHasPassive( soul, ePassives.PAS_VANGUARD_CORE1 ) ) //Has Arc Rounds, Choose Energy Transfer or Missile Racks
+ {
+ if ( RandomIntRange( 1, 100 ) <= 50 )
+ {
+ entity offhandWeapon = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ if ( IsValid( offhandWeapon ) )
+ {
+ array<string> mods = offhandWeapon.GetMods()
+ mods.append( "missile_racks" )
+ offhandWeapon.SetMods( mods )
+ }
+ }
+ else
+ {
+ entity offhandWeapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ if ( IsValid( offhandWeapon ) )
+ {
+ array<string> mods = offhandWeapon.GetMods()
+ mods.append( "energy_transfer" )
+ offhandWeapon.SetMods( mods )
+ }
+ }
+ }
+ else if ( SoulHasPassive( soul, ePassives.PAS_VANGUARD_CORE2 ) ) //Has Missile Racks, Choose Energy Transfer or Arc Rounds
+ {
+ if ( RandomIntRange( 1, 100 ) <= 50 )
+ {
+ array<entity> weapons = GetPrimaryWeapons( titan )
+ if ( weapons.len() > 0 )
+ {
+ entity primaryWeapon = weapons[0]
+ if ( IsValid( primaryWeapon ) )
+ {
+ array<string> mods = primaryWeapon.GetMods()
+ mods.append( "arc_rounds" )
+ primaryWeapon.SetMods( mods )
+ primaryWeapon.SetWeaponPrimaryClipCount( primaryWeapon.GetWeaponPrimaryClipCountMax() )
+ }
+ }
+ }
+ else
+ {
+ entity offhandWeapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ if ( IsValid( offhandWeapon ) )
+ {
+ array<string> mods = offhandWeapon.GetMods()
+ mods.append( "energy_transfer" )
+ offhandWeapon.SetMods( mods )
+ }
+ }
+ }
+ else if ( SoulHasPassive( soul, ePassives.PAS_VANGUARD_CORE3 ) ) //Has Energy Transfer, Choose Arc Rounds or Missile Racks
+ {
+ if ( RandomIntRange( 1, 100 ) <= 50 )
+ {
+ array<entity> weapons = GetPrimaryWeapons( titan )
+ if ( weapons.len() > 0 )
+ {
+ entity primaryWeapon = weapons[0]
+ if ( IsValid( primaryWeapon ) )
+ {
+ array<string> mods = primaryWeapon.GetMods()
+ mods.append( "arc_rounds" )
+ primaryWeapon.SetMods( mods )
+ primaryWeapon.SetWeaponPrimaryClipCount( primaryWeapon.GetWeaponPrimaryClipCountMax() )
+ }
+ }
+ }
+ else
+ {
+ entity offhandWeapon = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ if ( IsValid( offhandWeapon ) )
+ {
+ array<string> mods = offhandWeapon.GetMods()
+ mods.append( "missile_racks" )
+ offhandWeapon.SetMods( mods )
+ }
+ }
+ }
+ break
+ }
+ }
+}
+
+void function ApplyDerivedIonFDUpgrades( array<string> upgradeRefs, TitanLoadoutDef loadout )
+{
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_ion_utility_tier_1":
+ loadout.setFileMods.append( "fd_energy_regen" )
+ break
+ case "fd_upgrade_ion_utility_tier_2":
+ loadout.setFileMods.append( "fd_energy_max" )
+ break
+ case "fd_upgrade_ion_defense_tier_1":
+ loadout.setFileMods.append( "fd_health_upgrade" )
+ break
+ }
+ }
+}
+
+void function ApplyIonFDUpgrades( array<string> upgradeRefs, entity titan, TitanLoadoutDef loadout )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Ejecting
+ return
+
+ entity primaryWeapon = titan.GetMainWeapons()[0]
+ array<string> primaryMods = primaryWeapon.GetMods()
+ primaryMods.append( "fd_balance" )
+ primaryWeapon.SetMods( primaryMods )
+
+ entity ordanance = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ array<string> ordananceMods = ordanance.GetMods()
+ ordananceMods.append( "fd_balance" )
+ ordanance.SetMods( ordananceMods )
+
+ //entity utilityWeapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ //array<string> utilityMods = utilityWeapon.GetMods()
+ //utilityMods.append( "fd_balance" )
+ //utilityWeapon.SetMods( utilityMods )
+
+ entity coreWeapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+ array<string> coreMods = coreWeapon.GetMods()
+ coreMods.append( "fd_balance" )
+ coreWeapon.SetMods( coreMods )
+
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_ion_defense_tier_2":
+ float titanShieldHealth = GetTitanSoulShieldHealth( soul )
+ soul.SetShieldHealthMax( int( titanShieldHealth * 1.5 ) )
+ break
+ case "fd_upgrade_ion_weapon_tier_1":
+ entity ordanance = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ array<string> ordananceMods = ordanance.GetMods()
+ ordananceMods.append( "fd_laser_upgrade" )
+ ordanance.SetMods( ordananceMods )
+ break
+ case "fd_upgrade_ion_weapon_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_split_shot_cost" )
+ weapon.SetMods( mods )
+ break
+
+ case "fd_upgrade_ion_ultimate":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_laser_cannon" )
+ weapon.SetMods( mods )
+ break
+ }
+ }
+}
+
+void function ApplyDerivedToneFDUpgrades( array<string> upgradeRefs, TitanLoadoutDef loadout )
+{
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_tone_defense_tier_1":
+ loadout.setFileMods.append( "fd_health_upgrade" )
+ break
+ }
+ }
+}
+
+void function ApplyToneFDUpgrades( array<string> upgradeRefs, entity titan, TitanLoadoutDef loadout )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Ejecting
+ return
+
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_tone_utility_tier_1":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_sonar_duration" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_tone_utility_tier_2":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_sonar_damage_amp" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_tone_defense_tier_2":
+ float titanShieldHealth = GetTitanSoulShieldHealth( soul )
+ soul.SetShieldHealthMax( int( titanShieldHealth * 1.5 ) )
+ break
+ case "fd_upgrade_tone_weapon_tier_1":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_splasher_rounds" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_tone_weapon_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_tone_weapon_2" )
+ weapon.SetMods( mods )
+ weapon.SetWeaponPrimaryClipCount( weapon.GetWeaponPrimaryClipCountMax() )
+ break
+ case "fd_upgrade_tone_ultimate":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_salvo_core" )
+ weapon.SetMods( mods )
+ break
+ }
+ }
+}
+
+void function ApplyDerivedScorchFDUpgrades( array<string> upgradeRefs, TitanLoadoutDef loadout )
+{
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_scorch_defense_tier_1":
+ loadout.setFileMods.append( "fd_health_upgrade" )
+ break
+ }
+ }
+}
+
+void function ApplyScorchFDUpgrades( array<string> upgradeRefs, entity titan, TitanLoadoutDef loadout )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Ejecting
+ return
+
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_scorch_utility_tier_1":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_hot_streak" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_scorch_utility_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_fire_damage_upgrade" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_scorch_defense_tier_2":
+ float titanShieldHealth = GetTitanSoulShieldHealth( soul )
+ soul.SetShieldHealthMax( int( titanShieldHealth * 1.5 ) )
+ break
+ case "fd_upgrade_scorch_weapon_tier_1":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ if ( !mods.contains( "fd_wpn_upgrade_2" ) )
+ {
+ mods.append( "fd_wpn_upgrade_1" )
+ weapon.SetMods( mods )
+ weapon.SetWeaponPrimaryClipCount( weapon.GetWeaponPrimaryClipCountMax() )
+ }
+ break
+ case "fd_upgrade_scorch_weapon_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.fastremovebyvalue( "fd_wpn_upgrade_1" )
+ mods.append( "fd_wpn_upgrade_2" )
+ weapon.SetMods( mods )
+ weapon.SetWeaponPrimaryClipCount( weapon.GetWeaponPrimaryClipCountMax() )
+ break
+ case "fd_upgrade_scorch_ultimate":
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_explosive_barrel" )
+ weapon.SetMods( mods )
+ break
+ }
+ }
+}
+
+void function ApplyDerivedLegionFDUpgrades( array<string> upgradeRefs, TitanLoadoutDef loadout )
+{
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_legion_defense_tier_1":
+ loadout.setFileMods.append( "fd_health_upgrade" )
+ break
+ }
+ }
+}
+
+void function ApplyLegionFDUpgrades( array<string> upgradeRefs, entity titan, TitanLoadoutDef loadout )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Ejecting
+ return
+
+ foreach ( upgrade in upgradeRefs )
+ {
+ switch ( upgrade )
+ {
+ case "fd_upgrade_legion_utility_tier_1":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_closerange_helper" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_legion_utility_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_longrange_helper" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_legion_defense_tier_2":
+ float titanShieldHealth = GetTitanSoulShieldHealth( soul )
+ soul.SetShieldHealthMax( int( titanShieldHealth * 1.5 ) )
+ break
+ case "fd_upgrade_legion_weapon_tier_1":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_piercing_shots" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_legion_weapon_tier_2":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "fd_gun_shield_redirect" )
+ weapon.SetMods( mods )
+ break
+ case "fd_upgrade_legion_ultimate":
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ if ( !mods.contains( "pas_legion_weapon" ) )
+ {
+ mods.append( "pas_legion_weapon" )
+ weapon.SetMods( mods )
+ weapon.SetWeaponPrimaryClipCount( weapon.GetWeaponPrimaryClipCountMax() )
+ }
+ if ( !mods.contains( "pas_legion_spinup" ) )
+ {
+ mods.append( "pas_legion_spinup" )
+ weapon.SetMods( mods )
+ }
+ weapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+ mods = weapon.GetMods()
+ if ( !mods.contains( "pas_legion_smartcore" ) )
+ {
+ mods.append( "pas_legion_smartcore" )
+ }
+ weapon.SetMods( mods )
+ weapon = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ mods = weapon.GetMods()
+ if ( !mods.contains( "pas_legion_chargeshot" ) )
+ {
+ mods.append( "pas_legion_chargeshot" )
+ }
+ weapon.SetMods( mods )
+ weapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ mods = weapon.GetMods()
+ mods.append( "fd_gun_shield" )
+ weapon.SetMods( mods )
+ break
+ }
+ }
+}
+#endif
+
+void function ApplyTitanWeaponPassives( entity titan, TitanLoadoutDef loadout )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Ejecting
+ return
+
+ if ( loadout.titanClass == "ronin" && loadout.isPrime == "titan_is_prime" )
+ {
+ array<int> offhandSlots = [ OFFHAND_MELEE, OFFHAND_LEFT, OFFHAND_RIGHT ]
+
+ foreach ( slot in offhandSlots )
+ {
+ entity weapon = titan.GetOffhandWeapon( slot )
+ array<string> mods = weapon.GetMods()
+ mods.append( "modelset_prime" )
+ weapon.SetMods( mods )
+ }
+ }
+
+ foreach ( passive, value in soul.passives )
+ {
+ if ( !value )
+ continue
+
+ switch ( passive )
+ {
+ case ePassives.PAS_ION_TRIPWIRE:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_ion_tripwire" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_ION_VORTEX:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_ion_vortex" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_ION_LASERCANNON:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_ion_lasercannon" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_ION_WEAPON_ADS:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_ion_weapon_ads" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_NORTHSTAR_WEAPON:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_northstar_weapon" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_NORTHSTAR_TRAP:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_northstar_trap" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_NORTHSTAR_CLUSTER:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_northstar_cluster" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_NORTHSTAR_OPTICS:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_northstar_optics" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_SCORCH_SELFDMG:
+ soul.SetPreventCrits( true )
+ break
+
+ case ePassives.PAS_SCORCH_WEAPON:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_scorch_weapon" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_SCORCH_SHIELD:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_LEFT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_scorch_shield" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_SCORCH_FIREWALL:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_scorch_firewall" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_SCORCH_FLAMECORE:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_scorch_flamecore" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_LEGION_WEAPON:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_legion_weapon" )
+ weapon.SetMods( mods )
+ int max = weapon.GetWeaponPrimaryClipCountMax()
+ weapon.SetWeaponPrimaryClipCount( max )
+ break
+
+ case ePassives.PAS_LEGION_SPINUP:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_legion_spinup" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_LEGION_SMARTCORE:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_legion_smartcore" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_LEGION_CHARGESHOT:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ORDNANCE )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_legion_chargeshot" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_TONE_WEAPON:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_tone_weapon" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_TONE_BURST:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_tone_burst" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_TONE_ROCKETS:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_tone_rockets" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_TONE_SONAR:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_tone_sonar" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_RONIN_WEAPON:
+ entity weapon = titan.GetMainWeapons()[0]
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_ronin_weapon" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_RONIN_ARCWAVE:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_RIGHT )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_ronin_arcwave" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_RONIN_PHASE:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_ronin_phase" )
+ weapon.SetMods( mods )
+ break
+
+ case ePassives.PAS_VANGUARD_REARM:
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ array<string> mods = weapon.GetMods()
+ mods.append( "pas_vanguard_rearm" )
+ weapon.SetMods( mods )
+ break
+ }
+ }
+
+ #if MP
+ if ( GetCurrentPlaylistVarInt( "aegis_upgrades", 0 ) == 1 ) //Necessary to occur after normal weapon mods are assigned from passives.
+ ApplyFDUpgradeWeaponPassives( titan, loadout )
+ #endif
+}
+
+function GivePassive( entity player, int passive )
+{
+ if ( IsSoul( player ) )
+ {
+ entity soul = player
+ Assert( passive in level.titanPassives, "This is not a titan passive" )
+ soul.passives[ passive ] = true
+
+ entity titan = soul.GetTitan()
+ if ( IsValid( titan ) && titan.IsPlayer() )
+ GiveTitanPassiveLifeLong( titan, passive ) //This actually loops back around to GivePassive
+ return
+ }
+
+ // printt( "give passive " + GetPassiveName( passive ), passive )
+ player.GivePassive( passive )
+
+ // enter/exit functions for specific passives
+ switch ( passive )
+ {
+ case ePassives.PAS_MINIMAP_AI:
+ case ePassives.PAS_MINIMAP_ALL:
+ case ePassives.PAS_MINIMAP_PLAYERS:
+ UpdateMinimapStatus( player )
+ break
+
+ case ePassives.PAS_CONSCRIPT:
+ thread PlayerConscription( player )
+ break
+
+ case ePassives.PAS_WIFI_SPECTRE:
+ thread PlayerSpectreWifi( player )
+ break
+
+ case ePassives.PAS_FAST_SWAP:
+ player.GiveExtraWeaponMod( "pas_fast_swap" )
+ break
+
+ case ePassives.PAS_POWER_CELL:
+ player.GiveExtraWeaponMod( "pas_power_cell" )
+ break
+
+ case ePassives.PAS_WALLHANG:
+ player.GiveExtraWeaponMod( "pas_wallhang" )
+ break
+
+ case ePassives.PAS_FAST_HEALTH_REGEN:
+ player.GiveExtraWeaponMod( "pas_fast_health_regen" )
+ break
+
+ case ePassives.PAS_DEFENSIVE_CORE:
+ player.GiveExtraWeaponMod( "pas_defensive_core" )
+ break
+
+ case ePassives.PAS_RUN_AND_GUN:
+ player.GiveExtraWeaponMod( "pas_run_and_gun" )
+ break
+
+ case ePassives.PAS_ORDNANCE_PACK:
+ player.GiveExtraWeaponMod( "pas_ordnance_pack" )
+ break
+
+ case ePassives.PAS_FAST_RELOAD:
+ player.GiveExtraWeaponMod( "pas_fast_reload" )
+ break
+
+ case ePassives.PAS_ASSAULT_REACTOR:
+ player.GiveExtraWeaponMod( "mod_ordnance_core" )
+ break
+
+ case ePassives.PAS_MARATHON_CORE:
+ player.GiveExtraWeaponMod( "mod_marathon_core" )
+ break
+
+ case ePassives.PAS_CLOAKED_WALLHANG:
+ thread CloakedWallHangs( player )
+ break
+
+ case ePassives.PAS_CLOAKED_WALLRUN:
+ thread CloakedWallruns( player )
+ break
+
+ case ePassives.PAS_SMOKE_SIGHT:
+ Remote_CallFunction_Replay( player, "ServerCallback_BeginSmokeSight" )
+ break
+ }
+}
+
+string function GetPassiveName( int passive )
+{
+ Assert( passive in level.passiveEnumFromPassive, "Passive bitfield: " + passive + " does not exist" )
+
+ return expect string( level.passiveEnumFromPassive[ passive ] )
+}
+
+function TakePassive( entity player, int passive )
+{
+ if ( IsSoul( player ) )
+ {
+ entity soul = player
+ Assert( passive in level.titanPassives, "This is not a titan passive" )
+ soul.passives[ passive ] = false
+ entity titan = soul.GetTitan()
+ if ( IsValid( titan ) && titan.IsPlayer() )
+ TakePassive( titan, passive )
+ return
+ }
+
+ //printt( "take passive " + PassiveEnumFromBitfield( passive ) )
+ player.RemovePassive( passive )
+
+ // enter/exit functions for specific passives
+ switch ( passive )
+ {
+ case ePassives.PAS_MINIMAP_AI:
+ case ePassives.PAS_MINIMAP_ALL:
+ case ePassives.PAS_MINIMAP_PLAYERS:
+ UpdateMinimapStatus( player )
+ break
+
+ case ePassives.PAS_FAST_SWAP:
+ player.TakeExtraWeaponMod( "pas_fast_swap" )
+ break
+
+ case ePassives.PAS_POWER_CELL:
+ player.TakeExtraWeaponMod( "pas_power_cell" )
+ break
+
+ case ePassives.PAS_WALLHANG:
+ player.TakeExtraWeaponMod( "pas_wallhang" )
+ break
+
+ case ePassives.PAS_FAST_HEALTH_REGEN:
+ player.TakeExtraWeaponMod( "pas_fast_health_regen" )
+ break
+
+ case ePassives.PAS_DEFENSIVE_CORE:
+ player.TakeExtraWeaponMod( "pas_defensive_core" )
+ break
+
+ case ePassives.PAS_RUN_AND_GUN:
+ player.TakeExtraWeaponMod( "pas_run_and_gun" )
+ break
+
+ case ePassives.PAS_ORDNANCE_PACK:
+ player.TakeExtraWeaponMod( "pas_ordnance_pack" )
+ break
+
+ case ePassives.PAS_FAST_RELOAD:
+ player.TakeExtraWeaponMod( "pas_fast_reload" )
+ break
+
+ case ePassives.PAS_ASSAULT_REACTOR:
+ player.TakeExtraWeaponMod( "mod_ordnance_core" )
+ break
+
+ case ePassives.PAS_MARATHON_CORE:
+ player.TakeExtraWeaponMod( "mod_marathon_core" )
+ break
+
+ case ePassives.PAS_CLOAKED_WALLHANG:
+ player.Signal( "EndCloakedWallHangs" )
+ break
+
+ case ePassives.PAS_CLOAKED_WALLRUN:
+ player.Signal( "EndCloakedWallruns" )
+ break
+
+ case ePassives.PAS_SMOKE_SIGHT:
+ Remote_CallFunction_Replay( player, "ServerCallback_EndSmokeSight" )
+ break
+ }
+}
+
+void function PassiveDeathCallback( entity player, var damageInfo )
+{
+ foreach ( int passive in player.s.removePassiveOnDeath )
+ {
+ TakePassive( player, passive )
+ }
+
+ player.s.removePassiveOnDeath = {}
+}
+
+function GivePassiveLifeLong( entity player, int passive )
+{
+ //Note: Badness happens if a burn card with passive tries to give a server flag!
+ Assert( !( passive in level.titanPassives ), "This is a titan passive" )
+
+ // give the passive for one life
+ player.s.removePassiveOnDeath[ passive ] <- passive
+ GivePassive( player, passive )
+}
+
+function GiveTitanPassiveLifeLong( entity player, int passive )
+{
+ Assert( passive in level.titanPassives, "This is a titan passive" )
+
+ // give the passive for one life
+ player.s.removePassiveOnDeath[ passive ] <- passive
+ GivePassive( player, passive )
+}
+
+function TakeAllPassives( entity player )
+{
+ foreach( passiveName, passive in _PassiveFromEnum )
+ {
+ if ( player.HasPassive( passive ) )
+ TakePassive( player, passive )
+ }
+
+ player.ClearExtraWeaponMods()
+ player.s.removePassiveOnDeath = {}
+}
+
+
+function GivePlayerPassivesFromSoul( entity player, entity soul )
+{
+ Assert( player == soul.GetTitan() )
+
+ foreach( passiveName, passive in _PassiveFromEnum ) //Since this is just a bitmask, we could just add all the soul's passives directly instead of trying to break it down to its components first like we do here. However, while it is less efficent, it's also easier to debug.
+ {
+ if ( soul.passives[ passive ] )
+ GiveTitanPassiveLifeLong( player, passive )
+ }
+}
+
+table function GetRevealParms( entity player )
+{
+ table Table = {}
+
+ if ( player.HasPassive( ePassives.PAS_MINIMAP_ALL ) )
+ {
+ Table.ai <- true
+ Table.players <- true
+ Table.titans <- true
+ }
+ else
+ {
+ Table.titans <- false
+
+ if ( player.HasPassive( ePassives.PAS_MINIMAP_PLAYERS ) )
+ Table.players <- true
+ else
+ Table.players <- false
+
+ if ( player.HasPassive( ePassives.PAS_MINIMAP_AI ) )
+ Table.ai <- true
+ else
+ Table.ai <- false
+ }
+
+ return Table
+}
+
+function UpdateMinimapStatusToOtherPlayers( entity player )
+{
+ int team = player.GetTeam()
+ array players = GetPlayerArray()
+ foreach ( otherPlayer in players )
+ {
+ // teammates are on minimap by default
+ if ( team == otherPlayer.GetTeam() )
+ continue
+
+ table reveal = GetRevealParms( expect entity( otherPlayer ) )
+ if ( reveal.players )
+ {
+ player.Minimap_AlwaysShow( 0, otherPlayer )
+ }
+ }
+}
+
+function UpdateTitanMinimapStatusToOtherPlayers( entity titan )
+{
+ int team = titan.GetTeam()
+ array players = GetPlayerArray()
+ foreach ( otherPlayer in players )
+ {
+ // teammates are on minimap by default
+ if ( team == otherPlayer.GetTeam() )
+ continue
+
+ table reveal = GetRevealParms( expect entity( otherPlayer ) )
+ if ( reveal.titans )
+ {
+ titan.Minimap_AlwaysShow( 0, otherPlayer )
+ }
+ }
+}
+
+function UpdateAIMinimapStatusToOtherPlayers( entity guy )
+{
+ int team = guy.GetTeam()
+ array players = GetPlayerArray()
+ foreach ( otherPlayer in players )
+ {
+ // teammates are on minimap by default
+ if ( team == otherPlayer.GetTeam() )
+ continue
+
+ table reveal = GetRevealParms( expect entity( otherPlayer ) )
+ if ( reveal.ai )
+ {
+ guy.Minimap_AlwaysShow( 0, otherPlayer )
+ }
+ }
+}
+
+function UpdateMinimapStatus( entity player )
+{
+ int team = player.GetTeam()
+ table reveal = GetRevealParms( player )
+
+ array players = GetPlayerArray()
+ if ( reveal.players )
+ {
+ foreach ( target in players )
+ {
+ if ( team != target.GetTeam() )
+ target.Minimap_AlwaysShow( 0, player )
+ }
+ }
+ else
+ {
+ foreach ( target in players )
+ {
+ if ( team != target.GetTeam() )
+ target.Minimap_DisplayDefault( 0, player )
+ }
+ }
+
+ array<entity> titans = GetNPCArrayByClass( "npc_titan" )
+ if ( reveal.titans )
+ {
+ foreach ( target in titans )
+ {
+ if ( team != target.GetTeam() )
+ target.Minimap_AlwaysShow( 0, player )
+ }
+ }
+ else
+ {
+ foreach ( target in titans )
+ {
+ if ( team != target.GetTeam() )
+ target.Minimap_DisplayDefault( 0, player )
+ }
+ }
+
+ array<entity> ai = GetNPCArrayByClass( "npc_soldier" )
+ ai.extend( GetNPCArrayByClass( "npc_spectre" ) )
+
+ if ( reveal.ai )
+ {
+ foreach ( target in ai )
+ {
+ if ( team != target.GetTeam() )
+ target.Minimap_AlwaysShow( 0, player )
+ }
+ }
+ else
+ {
+ foreach ( target in ai )
+ {
+ if ( team != target.GetTeam() )
+ target.Minimap_DisplayDefault( 0, player )
+ }
+ }
+
+// foreach ( target in ai )
+// {
+// if ( target.GetBossPlayer() == player )
+// target.Minimap_AlwaysShow( 0, player )
+// }
+}
+
+function ScanMinimapUntilDeath( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ for ( ;; )
+ {
+ thread ScanMinimap( player, true )
+ wait 10.0
+ }
+}
+
+function ScanMinimap( entity player, bool playSound, float displayTime = 3.0 )
+{
+ // already has the passive?
+ if ( PlayerHasPassive( player, ePassives.PAS_MINIMAP_ALL ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+
+ int handle = player.GetEncodedEHandle()
+ Remote_CallFunction_Replay( player, "ServerCallback_MinimapPulse", handle )
+
+ OnThreadEnd(
+ function () : ( player )
+ {
+ if ( IsValid( player ) )
+ TakePassive( player, ePassives.PAS_MINIMAP_ALL )
+ }
+ )
+
+ GivePassive( player, ePassives.PAS_MINIMAP_ALL )
+ if ( playSound )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Burn_Card_Map_Hack_Radar_Pulse_V1_1P" )
+ wait displayTime
+}
+
+void function MinimapNPCSpawned( entity guy )
+{
+ // show up on minimap for player that has the passive
+
+ int team = guy.GetTeam()
+ if ( IsIMCOrMilitiaTeam( team ) == false )
+ return
+ array<entity> players = GetPlayerArrayOfEnemies( team )
+ foreach ( player in players )
+ {
+ if ( !PlayerRevealsNPCs( player ) )
+ continue
+
+ guy.Minimap_AlwaysShow( 0, player )
+ }
+}
+
+function PlayerRevealsPlayers( entity player )
+{
+ return player.HasPassive( ePassives.PAS_MINIMAP_PLAYERS ) || player.HasPassive( ePassives.PAS_MINIMAP_ALL )
+}
+
+function MinimapPlayerConnected( entity guy )
+{
+ int team = guy.GetTeam()
+ array<entity> players = GetPlayerArrayOfEnemies( team )
+ foreach ( player in players )
+ {
+ if ( !PlayerRevealsPlayers( player ) )
+ continue
+
+ guy.Minimap_AlwaysShow( 0, player )
+ }
+}
+
+function PlayerSpectreWifi( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ for ( ;; )
+ {
+ if ( !PlayerHasPassive( player, ePassives.PAS_WIFI_SPECTRE ) )
+ return
+
+ LeechSurroundingSpectres( player.GetOrigin(), player )
+
+ wait level.wifiLeachInterval
+ }
+}
+
+const CLEAR_CONSCRIPTED_GRUNTS_ON_DEATH = false
+
+function PlayerConscription( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ if ( CLEAR_CONSCRIPTED_GRUNTS_ON_DEATH )
+ {
+ if ( "conscriptedGrunts" in player.s )
+ return
+
+ table conscriptedGrunts
+ if ( "conscriptedGrunts" in player.s )
+ {
+ conscriptedGrunts = expect table( player.s.conscriptedGrunts )
+ }
+ else
+ {
+ conscriptedGrunts = {}
+ player.s.conscriptedGrunts <- conscriptedGrunts
+ }
+
+ OnThreadEnd(
+ function () : ( player, conscriptedGrunts )
+ {
+ ClearConscriptedGrunts( player, conscriptedGrunts )
+ }
+ )
+ }
+
+ for ( ;; )
+ {
+ if ( !PlayerHasPassive( player, ePassives.PAS_CONSCRIPT ) )
+ return
+
+ if ( IsAlive( player ) )
+ {
+ ConscriptNearbyGrunts( player )
+ }
+ wait 1.5
+ }
+}
+
+function ConscriptNearbyGrunts( entity player )
+{
+ int team = player.GetTeam()
+ array<entity> grunts = GetNPCArrayEx( "npc_soldier", team, TEAM_ANY, player.GetOrigin(), 220 )
+
+ foreach ( grunt in grunts )
+ {
+ if ( !IsValid( grunt ) )
+ continue
+
+ if ( IsAlive( grunt.GetBossPlayer() ) )
+ continue
+
+ ConscriptGruntSquad( grunt, player, team )
+ WaitFrame()
+ }
+}
+
+function ConscriptGrunt( entity grunt, entity player, int team )
+{
+ EmitSoundOnEntity( grunt, "BurnCard_Conscription_TurnSoldier" )
+ grunt.Signal( "StopHardpointBehavior" )
+ SetTeam( grunt, team )
+ grunt.SetBossPlayer( player )
+ grunt.SetTitle( "#NPC_CONSCRIPT" )
+ grunt.ai.preventOwnerDamage = true
+
+ #if HAS_STATS
+ UpdatePlayerStat( player, "misc_stats", "gruntsConscripted", 1 )
+ #endif
+
+ if ( CLEAR_CONSCRIPTED_GRUNTS_ON_DEATH )
+ {
+ player.s.conscriptedGrunts[ grunt ] <- grunt
+ // printt( player + " Conscripted " + grunt + " to squadname " + grunt.kv.squadname )
+ }
+}
+
+function MakePlayerSquad( entity player )
+{
+ string squad = "player" + player.entindex() + "gruntSquad"
+ int index = 0
+ string squadName = squad + index
+ for ( ;; )
+ {
+ if ( GetNPCSquadSize( squadName ) == 0 )
+ return squadName
+
+ index++
+ squadName = squad + index
+ }
+}
+
+function ConscriptGruntSquad( entity grunt, entity player, int team )
+{
+ array<entity> grunts
+ string gruntSquad = expect string( grunt.Get( "squadname" ) )
+
+ if ( gruntSquad == "" )
+ grunts.append( grunt )
+ else
+ grunts = GetNPCArrayBySquad( gruntSquad )
+
+ foreach ( guy in grunts )
+ {
+ if ( IsAlive( guy.GetBossPlayer() ) )
+ continue
+ if ( guy.GetTeam() != team )
+ continue
+
+ ConscriptGrunt( guy, player, team )
+ }
+}
+
+function ClearConscriptedGrunts( entity player, table conscriptedGrunts )
+{
+ foreach ( grunt in conscriptedGrunts )
+ {
+ expect entity( grunt )
+ if ( !IsAlive( grunt ) )
+ continue
+
+ entity owner = grunt.GetBossPlayer()
+ if ( IsValid( owner ) && owner != player )
+ continue
+
+ grunt.ClearBossPlayer()
+ asset model = grunt.GetModelName()
+ int team
+ if ( model.find( "imc" ) != null )
+ {
+ team = TEAM_IMC
+ }
+ else
+ {
+ team = TEAM_MILITIA
+ }
+
+ var title = grunt.GetSettingTitle()
+ if ( title != null && title != "" )
+ {
+ grunt.SetTitle( title )
+ FixupTitle( grunt )
+ }
+
+ grunt.Signal( "StopHardpointBehavior" )
+ SetTeam( grunt, team )
+ }
+
+ delete player.s.conscriptedGrunts
+}
+
+bool function IsConscript( entity guy )
+{
+ return IsAlive( guy.GetBossPlayer() )
+}
+
+bool function SoulHasPassive( entity soul, int passive )
+{
+ return expect bool( soul.passives[ passive ] )
+}
+
+function PrintAllPassives( entity player )
+{
+ foreach( passiveName, passive in _PassiveFromEnum )
+ {
+ if ( player.HasPassive( passive ) )
+ printt( "Player " + player + " has passive: " + passiveName )
+ }
+}
+
+function CloakedWallHangs( entity player )
+{
+ player.Signal( "EndCloakedWallHangs" )
+ player.EndSignal( "EndCloakedWallHangs" )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+
+ float cloakStartTime
+ float rechargeStartTime
+
+ if ( !( "wallHangCloakDuration" in player.s ) )
+ player.s.wallHangCloakDuration <- WALLHANG_CLOAK_DURATION
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( !IsValid( player ) )
+ return
+
+ if ( IsCloaked( player ) )
+ DisableCloak( player, 0 )
+ }
+ )
+
+ while ( true )
+ {
+ while ( !player.IsWallHanging() )
+ {
+ rechargeStartTime = Time()
+ WaitFrame()
+ player.s.wallHangCloakDuration += Time() - rechargeStartTime
+
+ if ( player.s.wallHangCloakDuration > WALLHANG_CLOAK_DURATION )
+ player.s.wallHangCloakDuration = WALLHANG_CLOAK_DURATION
+ }
+
+ if ( !IsCloaked( player ) && player.s.wallHangCloakDuration )
+ EnableCloak( player, expect float( player.s.wallHangCloakDuration ), WALLHANG_CLOAK_TRANSITION_TIME )
+
+ while ( player.s.wallHangCloakDuration && player.IsWallHanging() )
+ {
+ cloakStartTime = Time()
+ WaitFrame()
+ player.s.wallHangCloakDuration -= Time() - cloakStartTime
+
+ if ( player.s.wallHangCloakDuration < 0 )
+ player.s.wallHangCloakDuration = 0
+ }
+
+ if ( IsCloaked( player ) )
+ DisableCloak( player, WALLHANG_CLOAK_TRANSITION_TIME )
+
+ if ( player.IsWallHanging() )
+ WaitFrame()
+ }
+}
+
+function CloakedWallruns( entity player )
+{
+ player.Signal( "EndCloakedWallruns" )
+ player.EndSignal( "EndCloakedWallruns" )
+ player.EndSignal( "OnDeath" )
+
+ float cloakStartTime
+ float rechargeStartTime
+
+ if ( !( "wallRunCloakDuration" in player.s ) )
+ player.s.wallRunCloakDuration <- WALLRUN_CLOAK_DURATION
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( !IsValid( player ) )
+ return
+
+ if ( IsCloaked( player ) )
+ DisableCloak( player, 0 )
+ }
+ )
+
+ while ( true )
+ {
+ while ( !player.IsWallRunning() )
+ {
+ rechargeStartTime = Time()
+ WaitFrame()
+ player.s.wallRunCloakDuration += Time() - rechargeStartTime
+
+ if ( player.s.wallRunCloakDuration > WALLRUN_CLOAK_DURATION )
+ player.s.wallRunCloakDuration = WALLRUN_CLOAK_DURATION
+ }
+
+ if ( !IsCloaked( player ) && player.s.wallRunCloakDuration )
+ EnableCloak( player, expect float( player.s.wallRunCloakDuration ), WALLRUN_CLOAK_TRANSITION_TIME )
+
+ while ( player.s.wallRunCloakDuration && player.IsWallRunning() )
+ {
+ cloakStartTime = Time()
+ WaitFrame()
+ player.s.wallRunCloakDuration -= Time() - cloakStartTime
+
+ if ( player.s.wallRunCloakDuration < 0 )
+ player.s.wallRunCloakDuration = 0
+ }
+
+ if ( IsCloaked( player ) )
+ DisableCloak( player, WALLRUN_CLOAK_TRANSITION_TIME )
+
+ if ( player.IsWallRunning() )
+ WaitFrame()
+ }
+}
+
+//To avoid threads and callbacks, this is using any netFloat > 0.5 to mean full CoreMeter scaling.
+void function UpdateScorchHotStreakCoreMeter( entity attacker, float damage )
+{
+ if ( !attacker.IsPlayer() )
+ return
+
+ float baseValue = attacker.GetPlayerNetFloat( "coreMeterModifier" )
+ float newValue = damage / FD_HOT_STREAK_DAMAGE_MAX * 0.5
+ float combinedValue = baseValue + newValue
+ if ( baseValue + newValue >= 0.5 )
+ combinedValue = 1.0
+
+ attacker.SetPlayerNetFloat( "coreMeterModifier", combinedValue )
+ attacker.SetPlayerNetFloatOverTime( "coreMeterModifier", 0.0, FD_HOT_STREAK_DECAY_TIME )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_ping.gnut b/Northstar.CustomServers/scripts/vscripts/_ping.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_ping.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_powerup.gnut b/Northstar.CustomServers/scripts/vscripts/_powerup.gnut
new file mode 100644
index 000000000..40984d0db
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_powerup.gnut
@@ -0,0 +1,102 @@
+untyped
+global function PowerUps_Init
+
+void function PowerUps_Init()
+{
+ SH_PowerUp_Init()
+
+ AddCallback_EntitiesDidLoad( EntitesDidLoad )
+}
+
+void function EntitesDidLoad()
+{
+ array<entity> scriptRefs = GetEntArrayByClass_Expensive( "script_ref" )
+ foreach ( entity ref in scriptRefs )
+ if ( ref.HasKey( "powerUpType" ) )
+ {
+ PowerUp powerup = GetPowerUpFromItemRef( expect string( ref.kv.powerUpType ) )
+
+ // CreatePickup is defined in mp/_pickups.gnut
+ // mp/_pickups.gnut is a sp-only script
+ // it's literally in the mp folder
+ // respawn PLEASE
+ //CreatePickup( ref, powerup.model, bool function( entity player ) { powerup.destroyFunc( player ); return true } )
+
+ if ( powerup.spawnFunc() )
+ {
+ CreatePropDynamic( powerup.baseModel, ref.GetOrigin(), ref.GetAngles(), 2 )
+ thread PowerUpThink( ref, powerup )
+ }
+ }
+
+ AddCallback_OnTouchHealthKit( "item_powerup", OnPowerUpCollected )
+}
+
+entity function CreatePowerUp( entity spawnpoint, PowerUp powerup )
+{
+ entity powerupEnt = CreateEntity( "item_powerup" )
+
+ powerupEnt.SetValueForModelKey( powerup.model )
+ powerupEnt.kv.fadedist = 10000
+ powerupEnt.kv.gravity = 0.000001 // really hacky, but gravity 0.0 is considered the same as 1.0, and i'm not sure how to enable/disable gravity on entities in script
+
+ DispatchSpawn( powerupEnt )
+
+ powerupEnt.SetModel( powerup.model )
+ powerupEnt.SetOrigin( spawnpoint.GetOrigin() + powerup.modelOffset )
+ powerupEnt.SetAngles( spawnpoint.GetAngles() + powerup.modelAngles )
+
+ powerupEnt.s.powerUpType <- powerup.itemRef
+
+ return powerupEnt
+}
+
+void function PowerUpThink( entity spawnpoint, PowerUp powerup )
+{
+ svGlobal.levelEnt.EndSignal( "RoundEnd" ) // should reset on round end
+
+ entity powerupEnt
+
+ while ( true )
+ {
+ powerupEnt = CreatePowerUp( spawnpoint, powerup )
+
+ OnThreadEnd( function() : ( powerupEnt, spawnpoint, powerup )
+ {
+ // should be called on round end
+ print( "resetting powerup..." )
+ if ( IsValid( powerupEnt ) )
+ powerupEnt.Destroy()
+
+ // recursively spawn new powerup
+ thread PowerUpThink( spawnpoint, powerup )
+ }
+ )
+
+ // handle the glow here so we can destroy it
+ PickupGlow glow = CreatePickupGlow( powerupEnt, powerup.glowColor.x.tointeger(), powerup.glowColor.y.tointeger(), powerup.glowColor.z.tointeger() )
+ glow.glowFX.SetOrigin( spawnpoint.GetOrigin() ) // want the glow to be parented to the powerup, but have the position of the spawnpoint
+
+ powerupEnt.WaitSignal( "OnDestroy" )
+
+ wait powerup.respawnDelay
+ }
+}
+
+bool function OnPowerUpCollected( entity player, entity healthpack )
+{
+ PowerUp powerup = GetPowerUpFromItemRef( expect string( healthpack.s.powerUpType ) )
+
+ if ( player.IsTitan() == powerup.titanPickup )
+ {
+ // hack because i couldn't figure out any other way to do this without modifying sh_powerup
+ // ensure we don't kill the powerup if it's a battery the player can't pickup
+ if ( ( powerup.index == ePowerUps.titanTimeReduction || powerup.index == ePowerUps.LTS_TitanTimeReduction ) && PlayerHasBattery( player ) )
+ return false
+
+ powerup.destroyFunc( player )
+ return true // destroys the powerup
+ }
+
+ return false // keeps powerup alive
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_remote_functions_mp.gnut b/Northstar.CustomServers/scripts/vscripts/_remote_functions_mp.gnut
new file mode 100644
index 000000000..567954b18
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_remote_functions_mp.gnut
@@ -0,0 +1,943 @@
+untyped
+
+global function RemoteFunctions_Init
+
+function RemoteFunctions_Init()
+{
+ Remote_BeginRegisteringFunctions()
+ _RegisteringFunctions = true
+
+ switch ( GetMapName() )
+ {
+ case "mp_colony":
+ RegisterServerVar( "ClientTiming", 0 )
+ Remote_RegisterFunction( "ServerCallback_CreateSpectrePaletteLighting" )
+ break
+
+ case "mp_wargames":
+ Remote_RegisterFunction( "ServerCallback_StopWargamesPodAmbienceSound" )
+ Remote_RegisterFunction( "ServerCallback_SpawnIMCFactionLeaderForIntro" )
+ Remote_RegisterFunction( "ServerCallback_SpawnMilitiaFactionLeaderForIntro" )
+ Remote_RegisterFunction( "ServerCallback_ClearFactionLeaderIntro" )
+ Remote_RegisterFunction( "ServerCallback_PlayPodTransitionScreenFX" )
+ break
+ }
+
+ Remote_RegisterFunction( "ServerCallback_DpadCommSay" )
+
+ Remote_RegisterFunction( "ServerCallback_CaptialShips" )
+
+ Remote_RegisterFunction( "ServerCallback_RewardReadyMessage" )
+ Remote_RegisterFunction( "ServerCallback_TitanReadyMessage" )
+
+ Remote_RegisterFunction( "ServerCallback_FPS_Test" )// This is for local FPS tests using myscripts for standardized optimization
+ Remote_RegisterFunction( "ServerCallback_FPS_Avg" )// general callback for more people to use - soupy
+ Remote_RegisterFunction( "DebugSetFrontline" )
+ Remote_RegisterFunction( "ServerCallback_StartCinematicNodeEditor" )
+ Remote_RegisterFunction( "ServerCallback_AISkitDebugMessage" ) //chad - temp to do debug lines on my client only during real MP matches
+ Remote_RegisterFunction( "ServerCallback_UpdateClientChallengeProgress" )
+ Remote_RegisterFunction( "ServerCallback_EventNotification" )
+
+ Remote_RegisterFunction( "SCB_RefreshBurnCardSelector" )
+ Remote_RegisterFunction( "ServerCallback_EjectConfirmed" )
+
+ Remote_RegisterFunction( "SCB_AddGrenadeIndicatorForEntity" )
+
+ Remote_RegisterFunction( "SCB_SetUserPerformance" )
+ Remote_RegisterFunction( "SCB_UpdateSponsorables" )
+ Remote_RegisterFunction( "SCB_ClientDebug" )
+
+ Remote_RegisterFunction( "ScriptCallback_UnlockAchievement" )
+ Remote_RegisterFunction( "ServerCallback_UpdateHeroStats" )
+
+ RegisterNetworkedVariable( "sentryTurretCount", SNDC_PLAYER_EXCLUSIVE, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "itemInventoryCount", SNDC_PLAYER_EXCLUSIVE, SNVT_INT, 0 )
+ Remote_RegisterFunction( "ServerCallback_GiveSentryTurret" )
+ Remote_RegisterFunction( "ServerCallback_TurretReport" )
+ Remote_RegisterFunction( "ServerCallback_TurretWorldIconShow" )
+ Remote_RegisterFunction( "ServerCallback_TurretWorldIconHide" )
+
+ // SIDE NOTIFICATION
+ Remote_RegisterFunction( "ServerCallback_LoadoutNotification" )
+ Remote_RegisterFunction( "ServerCallback_ItemNotification" )
+
+ Remote_RegisterFunction( "ServerCallback_AnnouncePathLevelUp" )
+
+ Remote_RegisterFunction( "ServerCallback_SonarPulseFromPosition" )
+
+ // Survival Start
+ Remote_RegisterFunction( "ServerCallback_OpenShopMenu" )
+ Remote_RegisterFunction( "ServerCallback_CloseShopMenu" )
+ RegisterServerVar( "survivorEventActive", false )
+ RegisterServerVar( "survivorEventEndTime", 0.0 )
+ RegisterServerVar( "survivorEventMilitiaScrap", 0 )
+ RegisterServerVar( "survivorEventIMCScrap", 0 )
+ // Survival End
+
+ // Shield core
+ Remote_RegisterFunction( "ServerCallback_StartShieldPlayer" )
+ Remote_RegisterFunction( "ServerCallback_StopShieldPlayer" )
+ Remote_RegisterFunction( "ServerCallback_AddShieldedPlayer" )
+ Remote_RegisterFunction( "ServerCallback_RemoveShieldedPlayer" )
+
+ //HACK: these nv's should eventually be code driven concepts
+ RegisterEntityVar_AllSynced( "player", "empEndTime", 0 )
+ RegisterEntityVar_AllSynced( "titan_soul", "PROTO_stickyExplosiveCount", 0 )
+ RegisterEntityVar_AllSynced( "titan_soul", "PROTO_trackerCount", 0 )
+
+ RegisterNetworkedVariable( "playerAllowedToMelee", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL, true )
+ RegisterNetworkedVariable( "playerAllowedToLeech", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL, true )
+ RegisterNetworkedVariable( "playerAllowedToSyncedMelee", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL, true )
+ RegisterNetworkedVariable( "rodeoBatteryCount", SNDC_TITAN_SOUL, SNVT_INT, 3 )
+
+ RegisterNetworkedVariable( "coreMeterModifier", SNDC_PLAYER_GLOBAL, SNVT_FLOAT_RANGE_OVER_TIME, 0.0, 0.0, 1.0 )
+
+ Remote_RegisterFunction( "SCB_SmartAmmoForceLockedOntoHudDraw" )
+
+ // we want to keep these as nv's because we want them to ignore kill replay
+ // -------------
+ RegisterEntityVar( "player", "nextRespawnTime", 0 )
+ // -------------
+ // end
+
+ RegisterEntityVar( "player", "titanQueueNum", NOT_IN_TITAN_QUEUE )
+
+ RegisterEntityVar_AllSynced( "player", "titanRequestNum", null )
+ RegisterEntityVar_AllSynced( "player", "titanRequestSkipped", 0 )
+ RegisterServerVar( "titanNextRequestEventTime", 0 )
+ RegisterServerVar( "titanNextRequestEventType", TITAN_REQUEST_WAITING_FOR_WAVE )
+
+ Remote_RegisterFunction( "ServerCallback_UpdateMarker" )
+ Remote_RegisterFunction( "DisablePrecacheErrors" )
+ Remote_RegisterFunction( "RestorePrecacheErrors" )
+
+
+ RegisterEntityVar_AllSynced( "player", "inSmoke", false )
+
+ Remote_RegisterFunction( "SCB_PlayTitanCockpitSounds" )
+ Remote_RegisterFunction( "SCB_StopTitanCockpitSounds" )
+
+ Remote_RegisterFunction( "ServerCallback_RewardUsed" )
+ Remote_RegisterFunction( "ServerCallback_VanguardUpgradeMessage" )
+
+ // SHOULD PROBABLY BE CODE
+ RegisterServerVar( "gameStateChangeTime", null )
+ RegisterServerVar( "gameState", -1 )
+ RegisterServerVar( "gameStartTime", null )
+ RegisterServerVar( "coopStartTime", null )
+ RegisterServerVar( "gameEndTime", 0.0 )
+ RegisterServerVar( "switchedSides", null )
+ RegisterServerVar( "replayDisabled", false )
+
+ //Round Winning Kill replay related
+ RegisterServerVar( "roundWinningKillReplayEnabled", false )
+ RegisterServerVar( "roundWinningKillReplayPlaying", false )
+ RegisterServerVar( "roundScoreLimitComplete", false )
+ RegisterServerVar( "roundWinningKillReplayEntHealthFrac", 0.0 ) //Using .nv because we need the non-rolled back value during round winning kill replay
+
+ RegisterServerVar( "badRepPresent", false )
+
+ RegisterServerVar( "nonStandardScoring", false )
+
+ RegisterServerVar( "roundBased", false )
+ RegisterServerVar( "roundStartTime", null )
+ RegisterServerVar( "roundEndTime", 0.0 )
+ RegisterServerVar( "roundsPlayed", 0 )
+
+ RegisterServerVar( "minPickLoadOutTime", null )
+ RegisterServerVar( "connectionTimeout", 0 )
+ RegisterServerVar( "winningTeam", null )
+ RegisterServerVar( "titanDropEnabledForTeam", TEAM_BOTH )
+ RegisterServerVar( "matchProgress", 0 )
+
+ // Linked Hardpoints
+ Remote_RegisterFunction( "ServerCallback_HardpointChanged" )
+
+ Remote_RegisterFunction( "ServerCallback_DisableHudForEvac" )
+
+ // Seconds
+ RegisterServerVar( "secondsTitanCheckTime", null )
+
+ // Attack/Defend based game modes
+ RegisterServerVar( "attackingTeam", null )
+
+ // Riffs
+ RegisterServerVar( "spawnAsTitan", null )
+ RegisterServerVar( "titanAvailability", null )
+ RegisterServerVar( "titanExitEnabled", null )
+ RegisterServerVar( "allowNPCs", null )
+ RegisterServerVar( "aiLethality", null )
+ RegisterServerVar( "minimapState", null )
+ RegisterServerVar( "ospState", null )
+ RegisterServerVar( "ammoLimit", null )
+ RegisterServerVar( "eliminationMode", null )
+ RegisterServerVar( "floorIsLava", null )
+ RegisterServerVar( "playerBleedout", null )
+ RegisterServerVar( "titanQueueLimit", 0 )
+ RegisterServerVar( "boostAvailability", 0 )
+ RegisterServerVar( "teamShareCoreMeter", 0 )
+ RegisterNetworkedVariable( "titanEjectEnabled", SNDC_GLOBAL, SNVT_BOOL, true )
+
+ // MFD
+ RegisterServerVar( "mfdOverheadPingDelay", 0 )
+
+ RegisterNetworkedVariable( "gameInfoStatusText", SNDC_PLAYER_EXCLUSIVE, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "indicatorId", SNDC_PLAYER_EXCLUSIVE, SNVT_INT, 0 )
+
+ switch ( GameRules_GetGameMode() )
+ {
+ case ATTRITION:
+ RegisterNetworkedVariable( "AT_currentWave", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "AT_bankStartTime", SNDC_GLOBAL, SNVT_TIME, 0.0 )
+ RegisterNetworkedVariable( "AT_bankEndTime", SNDC_GLOBAL, SNVT_TIME, 0.0 )
+ RegisterNetworkedVariable( "AT_supplyDropExpireTime", SNDC_GLOBAL, SNVT_TIME, 0.0 )
+ RegisterNetworkedVariable( "shouldDisplayBountyPortraits", SNDC_GLOBAL, SNVT_BOOL )
+
+ RegisterNetworkedVariable( "camp1Ent", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "camp2Ent", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "camp3Ent", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "AcampProgress", SNDC_GLOBAL, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
+ RegisterNetworkedVariable( "BcampProgress", SNDC_GLOBAL, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
+
+ RegisterNetworkedVariable( "1AcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "2AcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "3AcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "4AcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "5AcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+
+ RegisterNetworkedVariable( "1BcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "2BcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "3BcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "4BcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+ RegisterNetworkedVariable( "5BcampCount", SNDC_GLOBAL, SNVT_INT, -1 )
+
+ RegisterNetworkedVariable( "banksOpen", SNDC_GLOBAL, SNVT_BOOL, false )
+ RegisterNetworkedVariable( "preBankPhase", SNDC_GLOBAL, SNVT_BOOL, false )
+
+ Remote_RegisterFunction( "ServerCallback_AT_AnnouncePreParty" )
+ Remote_RegisterFunction( "ServerCallback_AT_AnnounceBoss" )
+ Remote_RegisterFunction( "ServerCallback_AT_AnnounceWaveOver" )
+ Remote_RegisterFunction( "ServerCallback_AT_YouKilledBoss" )
+ Remote_RegisterFunction( "ServerCallback_AT_YouCollectedBox" )
+ Remote_RegisterFunction( "ServerCallback_AT_WarnPlayerBounty" )
+ Remote_RegisterFunction( "ServerCallback_AT_YouSurvivedBounty" )
+ Remote_RegisterFunction( "ServerCallback_AT_TeammateSurvivedBounty" )
+ Remote_RegisterFunction( "ServerCallback_AT_PromptBossRodeo" )
+ Remote_RegisterFunction( "ServerCallback_AT_PromptBossExecute" )
+ Remote_RegisterFunction( "ServerCallback_AT_BossDoomed" )
+ Remote_RegisterFunction( "ServerCallback_AT_OnPlayerConnected" )
+ Remote_RegisterFunction( "ServerCallback_AT_UpdateMostWanted" )
+ Remote_RegisterFunction( "ServerCallback_AT_ScoreSplashStartMultTimer" )
+ Remote_RegisterFunction( "ServerCallback_AT_ShowRespawnBonusLoss" )
+ Remote_RegisterFunction( "ServerCallback_AT_BankOpen" )
+ Remote_RegisterFunction( "ServerCallback_AT_BankClose" )
+ Remote_RegisterFunction( "ServerCallback_AT_FinishDeposit" )
+ Remote_RegisterFunction( "ServerCallback_AT_ShowATScorePopup" )
+ Remote_RegisterFunction( "ServerCallback_AT_BossDamageScorePopup" )
+ Remote_RegisterFunction( "ServerCallback_AT_PlayerKillScorePopup" )
+ Remote_RegisterFunction( "ServerCallback_AT_ShowStolenBonus" )
+ Remote_RegisterFunction( "ServerCallback_AT_ClearCampAndBossPortraits" )
+ Remote_RegisterFunction( "ServerCallback_AT_PulseBankAntena" )
+ RegisterNetworkedVariable( "AT_bonusPoints", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_bonusPoints256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_bonusPointMult", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_bonusMultTimer", SNDC_PLAYER_GLOBAL, SNVT_TIME, 0.0 )
+ RegisterNetworkedVariable( "AT_earnedPoints", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_earnedPoints256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_totalPoints", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_totalPoints256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_playerUploading", SNDC_PLAYER_GLOBAL, SNVT_BOOL, false )
+
+ /*
+ RegisterNetworkedVariable( "milGoldPlayer", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "milSilverPlayer", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "milBronzePlayer", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "milGoldPlayerBonus", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "milSilverPlayerBonus", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "milBronzePlayerBonus", SNDC_GLOBAL, SNVT_INT, 0 )
+
+ RegisterNetworkedVariable( "imcGoldPlayer", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "imcSilverPlayer", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "imcBronzePlayer", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "imcGoldPlayerBonus", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "imcSilverPlayerBonus", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "imcBronzePlayerBonus", SNDC_GLOBAL, SNVT_INT, 0 )
+ */
+
+#if CLIENT
+ CLAttrition_RegisterNetworkFunctions()
+#endif
+ break
+
+ case AI_TDM:
+ RegisterNetworkedVariable( "AT_bonusPoints", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_bonusPoints256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_earnedPoints", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "AT_earnedPoints256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+
+ RegisterNetworkedVariable( "IMCdefcon", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "MILdefcon", SNDC_GLOBAL, SNVT_INT, 0 )
+ Remote_RegisterFunction( "ServerCallback_AITDM_OnPlayerConnected" )
+#if CLIENT
+ CLAITDM_RegisterNetworkFunctions()
+#endif
+ break
+
+ case CAPTURE_POINT:
+ printt( "registering gamemode network variables for CAPTURE_POINT" )
+ RegisterNetworkedVariable( "objectiveAEnt", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "objectiveBEnt", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "objectiveCEnt", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "objectiveAState", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "objectiveBState", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "objectiveCState", SNDC_GLOBAL, SNVT_INT )
+
+ RegisterNetworkedVariable( "objectiveACappingTeam", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "objectiveBCappingTeam", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "objectiveCCappingTeam", SNDC_GLOBAL, SNVT_INT )
+
+ RegisterNetworkedVariable( "objectiveAProgress", SNDC_GLOBAL, SNVT_FLOAT_RANGE_OVER_TIME, 0.0, 0.0, 2.0 )
+ RegisterNetworkedVariable( "objectiveBProgress", SNDC_GLOBAL, SNVT_FLOAT_RANGE_OVER_TIME, 0.0, 0.0, 2.0 )
+ RegisterNetworkedVariable( "objectiveCProgress", SNDC_GLOBAL, SNVT_FLOAT_RANGE_OVER_TIME, 0.0, 0.0, 2.0 )
+
+ RegisterNetworkedVariable( "imcChevronState", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "milChevronState", SNDC_GLOBAL, SNVT_INT )
+
+ Remote_RegisterFunction( "ServerCallback_CP_PlayMatchEndingMusic" )
+
+ /*
+ #if DEV
+ Remote_RegisterFunction( "ServerCallback_CP_PrintHardpointOccupants" )
+ #endif
+ */
+
+#if CLIENT
+ CLCapturePoint_RegisterNetworkFunctions()
+#endif
+ break
+
+ case CAPTURE_THE_FLAG:
+ RegisterNetworkedVariable( "imcFlag", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "milFlag", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "imcFlagHome", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "milFlagHome", SNDC_GLOBAL, SNVT_ENTITY )
+
+ RegisterNetworkedVariable( "imcFlagState", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "milFlagState", SNDC_GLOBAL, SNVT_INT, 0 )
+
+ RegisterNetworkedVariable( "flagReturnProgress", SNDC_GLOBAL, SNVT_FLOAT_RANGE_OVER_TIME, 0.0, 0.0, 1.0 )
+ RegisterNetworkedVariable( "returningFlag", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL, false )
+
+ Remote_RegisterFunction( "ServerCallback_CTF_PlayMatchNearEndMusic" )
+ Remote_RegisterFunction( "ServerCallback_CTF_StartReturnFlagProgressBar" )
+ Remote_RegisterFunction( "ServerCallback_CTF_StopReturnFlagProgressBar" )
+
+#if CLIENT
+ CLCaptureTheFlag_RegisterNetworkFunctions()
+#endif
+ break
+
+ case FORT_WAR:
+ {
+ Remote_RegisterFunction( "ServerCallback_FW_FriendlyBaseAttacked" )
+ Remote_RegisterFunction( "ServerCallback_FW_NotifyTitanRequired" )
+ Remote_RegisterFunction( "ServerCallback_FW_NotifyEnterFriendlyArea" )
+ Remote_RegisterFunction( "ServerCallback_FW_NotifyExitFriendlyArea" )
+ Remote_RegisterFunction( "ServerCallback_FW_NotifyEnterEnemyArea" )
+ Remote_RegisterFunction( "ServerCallback_FW_NotifyExitEnemyArea" )
+ Remote_RegisterFunction( "ServerCallback_FW_SetObjective" )
+ }
+ break
+
+ case MARKED_FOR_DEATH:
+ Remote_RegisterFunction( "ServerCallback_MFD_StartNewMarkCountdown" )
+ break
+
+ case LAST_TITAN_STANDING:
+ {
+ Remote_RegisterFunction( "ServerCallback_LTSThirtySecondWarning" )
+ }
+ break
+
+ case COLISEUM:
+ Remote_RegisterFunction( "ServerCallback_ColiseumDisplayTickets" )
+ Remote_RegisterFunction( "ServerCallback_ColiseumIntro" )
+ break
+
+ case SPEEDBALL:
+ RegisterNetworkedVariable( "flagCarrier", SNDC_GLOBAL, SNVT_ENTITY )
+ Remote_RegisterFunction( "ServerCallback_SPEEDBALL_LastPlayer" )
+ Remote_RegisterFunction( "ServerCallback_SPEEDBALL_LastFlagOwner" )
+#if CLIENT
+ CLSPEEDBALL_RegisterNetworkFunctions()
+#endif
+ break
+ case FD:
+ RegisterNetworkedVariable( "FD_waveState", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_waveActive", SNDC_GLOBAL, SNVT_BOOL, false )
+ RegisterNetworkedVariable( "FD_totalWaves", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_currentWave", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_activeHarvester", SNDC_GLOBAL, SNVT_ENTITY )
+ RegisterNetworkedVariable( "FD_restartsRemaining", SNDC_GLOBAL, SNVT_INT )
+
+ //AI Type counts
+ RegisterNetworkedVariable( "FD_AICount_Titan", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Titan_Nuke", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Titan_Mortar", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Titan_Arc", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Grunt", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Spectre", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Spectre_Mortar", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Stalker", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Reaper", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Ticks", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Drone", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Drone_Cloak", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Current", SNDC_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "FD_AICount_Total", SNDC_GLOBAL, SNVT_INT, 0 )
+
+ RegisterNetworkedVariable( "FD_wavePoints", SNDC_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "FD_wavePoints256", SNDC_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "FD_harvesterInvulTime", SNDC_GLOBAL, SNVT_TIME, 0 )
+ RegisterNetworkedVariable( "FD_nextWaveStartTime", SNDC_GLOBAL, SNVT_TIME, 0 )
+
+ RegisterNetworkedVariable( "FD_readyForNextWave", SNDC_PLAYER_GLOBAL, SNVT_BOOL, false )
+
+ Remote_RegisterFunction( "ServerCallback_FD_AnnouncePreParty" )
+ Remote_RegisterFunction( "ServerCallback_FD_ClearPreParty" )
+ Remote_RegisterFunction( "ServerCallback_FD_PingMinimap" )
+ Remote_RegisterFunction( "ServerCallback_FD_MoneyFly" )
+ Remote_RegisterFunction( "ServerCallback_FD_SayThanks" )
+ Remote_RegisterFunction( "ServerCallback_FD_DisplayHarvesterKiller" )
+ Remote_RegisterFunction( "ServerCallback_FD_NotifyStoreOpen" )
+
+ Remote_RegisterFunction( "ServerCallback_ShowCycleHint" )
+ Remote_RegisterFunction( "ServerCallback_OpenBoostStore" )
+ Remote_RegisterFunction( "ServerCallback_UpdateMoney" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTeamReserve" )
+ Remote_RegisterFunction( "ServerCallback_EnableDropshipBoostStore" )
+ Remote_RegisterFunction( "ServerCallback_DisableDropshipBoostStore" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTurretCount" )
+ Remote_RegisterFunction( "ServerCallback_UpdatePlayerHasBattery" )
+ Remote_RegisterFunction( "ServerCallback_UpdateAmpedWeaponState" )
+ Remote_RegisterFunction( "ServerCallback_BoostStoreTitanHint" )
+ Remote_RegisterFunction( "ServerCallback_UpdateGameStats" )
+ Remote_RegisterFunction( "ServerCallback_ShowGameStats" )
+ Remote_RegisterFunction( "ServerCallback_FD_UpdateWaveInfo" )
+ Remote_RegisterFunction( "ServerCallback_FD_NotifyMVP" )
+
+ RegisterNetworkedVariable( "boostStoreOpen", SNDC_GLOBAL, SNVT_BOOL, false )
+ RegisterNetworkedVariable( "playerHasBatteryBoost", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL, false )
+ RegisterNetworkedVariable( "FD_money", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "FD_money256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+
+ RegisterNetworkedVariable( "numSuperRodeoGrenades", SNDC_PLAYER_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "numHarvesterShieldBoost", SNDC_PLAYER_GLOBAL, SNVT_INT, 0 )
+
+ RegisterNetworkedVariable( "showOverheadIcon", SNDC_TITAN_SOUL, SNVT_BOOL, false )
+
+#if CLIENT
+ CLFD_RegisterNetworkFunctions()
+#endif
+ break
+ }
+
+ #if DEVSCRIPTS
+ Dev_RemoteFunctions_Init()
+ #endif
+ Remote_RegisterFunction( "ServerCallback_NukeGrenadeWindowOpen" )
+ Remote_RegisterFunction( "ServerCallback_NukeGrenadeWindowClosed" )
+
+ Remote_RegisterFunction( "ServerCallback_RegisterTeamTitanMenuButtons" )
+ Remote_RegisterFunction( "ServerCallback_OpenTeamTitanMenu" )
+ Remote_RegisterFunction( "ServerCallback_CloseTeamTitanMenu" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTeamTitanMenuTime" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTeamTitanSelectionMenu" )
+
+ RegisterNetworkedVariable( "playerHardpointID", SNDC_PLAYER_EXCLUSIVE, SNVT_UNSIGNED_INT, 255 )
+
+
+ //Bleedout mechanic
+ //Remote_RegisterFunction( "ServerCallback_BLEEDOUT_StartFirstAidProgressBar" )
+ //Remote_RegisterFunction( "ServerCallback_BLEEDOUT_StopFirstAidProgressBar" )
+ //Remote_RegisterFunction( "ServerCallback_BLEEDOUT_ShowWoundedMarker" )
+ //Remote_RegisterFunction( "ServerCallback_BLEEDOUT_HideWoundedMarker" )
+
+ // NEW INTRO SYSTEM ( _cl_spawnslot_system.nut )
+ Remote_RegisterFunction( "ServerCallback_ResetEntSkyScale" )
+ Remote_RegisterFunction( "ServerCallback_SetEntSkyScale" )
+ Remote_RegisterFunction( "ServerCallback_ResetMapSettings" )
+ Remote_RegisterFunction( "ServerCallback_SetMapSettings" )
+ Remote_RegisterFunction( "ServerCallback_ToneMapping" )
+ Remote_RegisterFunction( "ServerCallback_LaptopFX" )
+
+ Remote_RegisterFunction( "ServerCallback_YouDied" )
+ Remote_RegisterFunction( "ServerCallback_YouRespawned" )
+
+ Remote_RegisterFunction( "ServerCallback_ShowDeathHint" )
+
+ Remote_RegisterFunction( "ServerCallback_ShowNextSpawnMessage" )
+ Remote_RegisterFunction( "ServerCallback_HideNextSpawnMessage" )
+
+ Remote_RegisterFunction( "ServerCallback_AnnounceWinner" )
+ Remote_RegisterFunction( "ServerCallback_AnnounceRoundWinner" )
+
+ //Remote_RegisterFunction( "ServerCallback_ToggleRankedInGame" )
+ Remote_RegisterFunction( "ServerCallback_GuidedMissileDestroyed" )
+ Remote_RegisterFunction( "ServerCallback_DoClientSideCinematicMPMoment" ) // hard to say if this is safe as fire and forget
+ Remote_RegisterFunction( "ServerCallback_SetAssistInformation" )
+ Remote_RegisterFunction( "ServerCallback_TitanEMP" )
+ Remote_RegisterFunction( "ServerCallback_AirburstIconUpdate" )
+ Remote_RegisterFunction( "ServerCallback_TitanCockpitBoot" ) // all this does is reset the tone mapping
+ Remote_RegisterFunction( "ServerCallback_DataKnifeStartLeech" )
+ Remote_RegisterFunction( "ServerCallback_DataKnifeCancelLeech" )
+ Remote_RegisterFunction( "ServerCallback_ControlPanelRefresh" )
+ Remote_RegisterFunction( "ServerCallback_TurretRefresh" )
+ Remote_RegisterFunction( "ServerCallback_CreateEvacShipIcon" )
+ Remote_RegisterFunction( "ServerCallback_DestroyEvacShipIcon" )
+ Remote_RegisterFunction( "ServerCallback_AddCapturePoint" )
+ Remote_RegisterFunction( "ServerCallback_TitanDisembark" ) // plays a line of dialog and calls "cockpit.StartDisembark()", and does tonemapping update, hides crosshair and names
+ Remote_RegisterFunction( "ServerCallback_OnEntityKilled" ) // handles obit and death recap
+ Remote_RegisterFunction( "ServerCallback_OnTitanKilled" ) // handles obit for titans
+ Remote_RegisterFunction( "ServerCallback_PlayerConnectedOrDisconnected" )
+ Remote_RegisterFunction( "SCBUI_PlayerConnectedOrDisconnected" )
+ Remote_RegisterFunction( "ServerCallback_PlayerChangedTeams" )
+ Remote_RegisterFunction( "ServerCallback_AnnounceTitanReservation" )
+
+ // IMPORTANT BUT MAYBE FINE AS A REMOTE CALL
+ Remote_RegisterFunction( "ServerCallback_ReplacementTitanSpawnpoint" )
+ Remote_RegisterFunction( "ServerCallback_TitanTookDamage" ) // should be converted into a code callback... similar to NotifyDidDamage
+ Remote_RegisterFunction( "ServerCallback_PilotTookDamage" ) // should be converted into a code callback... similar to NotifyDidDamage
+ Remote_RegisterFunction( "ServerCallback_PlayerUsesBurnCard" ) // tell a player that somebody used a burn card he should know about
+ Remote_RegisterFunction( "ServerCallback_ScreenShake" )
+ Remote_RegisterFunction( "ServerCallback_MinimapPulse" ) // if burn card moves to weapon then we dont need this
+ Remote_RegisterFunction( "ServerCallback_UpdateOverheadIconForNPC" )
+ Remote_RegisterFunction( "ServerCallback_SetFlagHomeOrigin" )
+ //Remote_RegisterFunction( "ServerCallback_OpenBurnCardMenu" )
+ //Remote_RegisterFunction( "ServerCallback_OpenDifficultyMenu" )
+ //Remote_RegisterFunction( "ServerCallback_ExitBurnCardMenu" )
+
+ // TITAN SHIELD BATTERY
+ Remote_RegisterFunction( "ServerCallback_StartBatteryTimer" )
+ Remote_RegisterFunction( "ServerCallback_TitanBatteryDown" )
+
+ // Ping
+ Remote_RegisterFunction( "ServerCallback_SpottingHighlight" )
+ Remote_RegisterFunction( "ServerCallback_SpottingDeny" )
+
+ // XP
+ Remote_RegisterFunction( "ServerCallback_PlayerLeveledUp" )
+ Remote_RegisterFunction( "ServerCallback_TitanLeveledUp" )
+ Remote_RegisterFunction( "ServerCallback_TitanXPAdded" )
+ Remote_RegisterFunction( "ServerCallback_WeaponLeveledUp" )
+ Remote_RegisterFunction( "ServerCallback_WeaponXPAdded" )
+ Remote_RegisterFunction( "ServerCallback_WeaponChallengeCompleted" )
+ Remote_RegisterFunction( "ServerCallback_TitanChallengeCompleted" )
+ Remote_RegisterFunction( "ServerCallback_PlayerChallengeCompleted" )
+
+ // Rodeo Battery
+ RegisterNetworkedVariable( "batteryOnBack", SNDC_PLAYER_EXCLUSIVE, SNVT_ENTITY )
+ RegisterNetworkedVariable( "offerRodeoBatteryLastUsedTime", SNDC_PLAYER_EXCLUSIVE, SNVT_TIME )
+ RegisterNetworkedVariable( "requestRodeoBatteryLastUsedTime", SNDC_PLAYER_EXCLUSIVE, SNVT_TIME )
+
+ Remote_RegisterFunction( "ServerCallback_UpdateRodeoRiderHud" )
+
+ RegisterEntityVar( "player", "permanentEventNotification", -1 )
+
+ //Titan Selection Screen - Clients don't have access to other player's persistent vars.
+ Remote_RegisterFunction( "ServerCallback_UpdateTeamTitanSelection" )
+
+ //FFA
+ Remote_RegisterFunction( "ServerCallback_FFASuddenDeathAnnouncement" )
+
+ // Bomb Mode
+ //Remote_RegisterFunction( "ServerCallback_AnnounceBombPickup" )
+ //Remote_RegisterFunction( "ServerCallback_AnnounceBombDropped" )
+ //Remote_RegisterFunction( "ServerCallback_AnnounceBombArmed" )
+ //Remote_RegisterFunction( "ServerCallback_AnnounceBombDisarmed" )
+ //Remote_RegisterFunction( "ServerCallback_AnnounceBombRespawned" )
+ //Remote_RegisterFunction( "ServerCallback_AnnounceBombExploded" )
+ //Remote_RegisterFunction( "ServerCallback_IncomingBombSpawnpoint" )
+
+ //Air Drops
+ Remote_RegisterFunction( "ServerCallback_IncomingAirdrop" )
+
+ // DEV ONLY
+ Remote_RegisterFunction( "ServerCallback_TitanLostHealthSegment" )
+
+ // LESS ESSENTIAL, CAN SHIP AS REMOTE FUNCTIONS
+ Remote_RegisterFunction( "ServerCallback_PlayScreenFXWarpJump" )
+ Remote_RegisterFunction( "ServerCallback_Phantom_Scan" )
+ Remote_RegisterFunction( "ServerCallback_RodeoScreenShake" )
+ Remote_RegisterFunction( "ServerCallback_RodeoerEjectWarning" ) // play pre-eject fx on titan
+ Remote_RegisterFunction( "ServerCallback_TitanEmbark" ) // used purely to play a single line of dialog
+ Remote_RegisterFunction( "ServerCallback_DogFight" )
+ Remote_RegisterFunction( "ServerCallback_Announcement" )
+ Remote_RegisterFunction( "ServerCallback_GameModeAnnouncement" )
+
+ Remote_RegisterFunction( "ServerCallback_ScoreEvent" )
+ Remote_RegisterFunction( "ServerCallback_CallingCardEvent" )
+
+ Remote_RegisterFunction( "ServerCallback_PlayConversation" )
+ Remote_RegisterFunction( "ServerCallback_PlayTitanConversation" )
+ Remote_RegisterFunction( "ServerCallback_PlaySquadConversation" )
+ Remote_RegisterFunction( "ServerCallback_CreateDropShipIntLighting" )
+ Remote_RegisterFunction( "ServerCallback_EvacObit" )
+ Remote_RegisterFunction( "ServerCallback_ShowTurretHint" )
+ Remote_RegisterFunction( "ServerCallback_HideTurretHint" )
+ Remote_RegisterFunction( "ServerCallback_ShowTurretInUseHint" )
+ Remote_RegisterFunction( "ServerCallback_UpdateBurnCardTitle" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTitanModeHUD" )
+ Remote_RegisterFunction( "ServerCallback_GiveMatchLossProtection" )
+ Remote_RegisterFunction( "ServerCallback_SquadLeaderBonus" )
+ Remote_RegisterFunction( "ServerCallback_SquadLeaderDoubleXP" )
+
+ Remote_RegisterFunction( "ServerCallback_TitanFallWarning" )
+ Remote_RegisterFunction( "SCB_TitanDialogue" )
+
+ Remote_RegisterFunction( "ServerCallback_PlayLobbyScene" )
+
+ Remote_RegisterFunction( "ServerCallback_PilotCreatedGunShield" )
+
+ Remote_RegisterFunction( "ServerCallback_BeginSmokeSight" )
+ Remote_RegisterFunction( "ServerCallback_EndSmokeSight" )
+
+ Remote_RegisterFunction( "UpdateCachedPilotLoadout" )
+ Remote_RegisterFunction( "UpdateCachedTitanLoadout" )
+ Remote_RegisterFunction( "UpdateAllCachedPilotLoadouts" )
+ Remote_RegisterFunction( "UpdateAllCachedTitanLoadouts" )
+ Remote_RegisterFunction( "ServerCallback_UpdatePilotModel" )
+ Remote_RegisterFunction( "ServerCallback_UpdateTitanModel" )
+
+ // DEV ONLY
+ Remote_RegisterFunction( "ServerCallback_MVUpdateModelBounds" )
+ Remote_RegisterFunction( "ServerCallback_MVEnable" )
+ Remote_RegisterFunction( "ServerCallback_MVDisable" )
+ Remote_RegisterFunction( "ServerCallback_ModelViewerDisableConflicts" )
+
+ Remote_RegisterFunction( "ServerCallback_Test" )
+
+ // SHOULD BE REMOVED
+ Remote_RegisterFunction( "ServerCallback_SetClassicSkyScale" )
+ Remote_RegisterFunction( "ServerCallback_ResetClassicSkyScale" )
+
+ RegisterEntityVar( "player", "drawFastballHud", false )
+ RegisterEntityVar( "player", "reviveBleedingOut", 0.0, true )
+ RegisterEntityVar( "player", "reviveHealedTime", 0.0, true )
+
+ // SHOULD PROBABLY BE CODE
+ Remote_RegisterFunction( "ServerCallback_ClientInitComplete" )
+ RegisterServerVar( "forcedDialogueOnly", false )
+ //RegisterNetworkedVariable( "squadConversationEnabled", SNDC_GLOBAL, SNVT_BOOL, true ) //TEMP, remove when we do Miles meta data conversation controls
+ //RegisterNetworkedVariable( "titanOSDialogueEnabled", SNDC_GLOBAL, SNVT_BOOL, true ) //TEMP, remove when we do Miles meta data conversation controls
+ Remote_RegisterFunction( "SCB_LockCapturePointForTeam" )
+ Remote_RegisterFunction( "SCB_UnlockCapturePointForTeam" )
+
+ // SHOULD GO AWAY
+ Remote_RegisterFunction( "ServerCallback_SetEntityVar" )
+ Remote_RegisterFunction( "ServerCallback_SetServerVar" )
+
+
+ // POSSIBLY CAN STAY AS REMOTE FUNCTIONS
+ Remote_RegisterFunction( "ServerCallback_PlayTeamMusicEvent" )
+ Remote_RegisterFunction( "ServerCallback_PlayMusicToCompletion" )
+ Remote_RegisterFunction( "ServerCallback_PlayMusic" )
+ Remote_RegisterFunction( "ServerCallback_TitanCockpitEMP" )
+ Remote_RegisterFunction( "ServerCallback_PlayerEarnedBurnCard" )
+ Remote_RegisterFunction( "ServerCallback_PlayerStoppedBurnCard" )
+
+ // UI FUNCTIONS
+ Remote_RegisterFunction( "ServerCallback_SetUIVar" )
+ Remote_RegisterFunction( "ServerCallback_ShopPurchaseStatus" )
+ Remote_RegisterFunction( "ServerCallback_OpenPilotLoadoutMenu" )
+ Remote_RegisterFunction( "ServerCallback_GenericDialog" )
+
+ // Ghost Recorder
+ RegisterEntityVar( "player", "mobilityGhostAnalyzed", false )
+ RegisterEntityVar( "player", "displayMobilityGhostHint", 0.0 )
+ RegisterEntityVar( "player", "displayMobilityGhostAnim", false )
+
+ // Dev Only
+ Remote_RegisterFunction( "Dev_PrintClientMessage" )
+ Remote_RegisterFunction( "Dev_BuildClientMessage" )
+
+ // Class Functions
+ Remote_RegisterFunction( "ServerCallback_DeploymentDeath" )
+ Remote_RegisterFunction( "ServerCallback_AddArcConnectorToy" )
+ Remote_RegisterFunction( "ServerCallback_PlayDialogueOnEntity" )
+ Remote_RegisterFunction( "ServerCallback_PlayDialogueAtPosition" )
+ Remote_RegisterFunction( "ServerCallback_PlayerConversation" )
+
+ //Weapon Flyout
+ RegisterNetworkedVariable( "shouldShowWeaponFlyout", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL, true )
+
+ Remote_RegisterFunction( "SCB_SetDoubleXPStatus" )
+
+ Remote_RegisterFunction( "SCB_SetScoreMeritState" )
+ Remote_RegisterFunction( "SCB_SetCompleteMeritState" )
+ Remote_RegisterFunction( "SCB_SetWinMeritState" )
+ Remote_RegisterFunction( "SCB_SetEvacMeritState" )
+ Remote_RegisterFunction( "SCB_SetMeritCount" )
+ Remote_RegisterFunction( "SCB_SetWeaponMeritCount" )
+ Remote_RegisterFunction( "SCB_SetTitanMeritCount" )
+ Remote_RegisterFunction( "SCB_UpdateTitanLoadouts" )
+
+ Remote_RegisterFunction( "SCB_SetHighlightFlagDisableDeathFade" ) //Hack, just for PulseBladeExecution
+
+ if ( IsLobby() )
+ {
+ Remote_RegisterFunction( "SCB_UpdateRankedPlayMenu" )
+ Remote_RegisterFunction( "SCB_UpdateBC" )
+ Remote_RegisterFunction( "SCB_RefreshBlackMarket" )
+ Remote_RegisterFunction( "ServerCallback_ShopOpenBurnCardPack" )
+ Remote_RegisterFunction( "ServerCallback_ShopOpenGenericItem" )
+ Remote_RegisterFunction( "SCB_RefreshCards" )
+ Remote_RegisterFunction( "SCB_UpdateEmptySlots" )
+ Remote_RegisterFunction( "SCB_UpdateBCFooter" )
+ }
+
+ if ( !IsModelViewer() )
+ {
+ switch ( GameRules_GetGameMode() )
+ {
+ case MARKED_FOR_DEATH:
+ case MARKED_FOR_DEATH_PRO:
+ Remote_RegisterFunction( "SCB_MarkedChanged" )
+ break
+ }
+ }
+
+ RegisterString( "#GAMEMODE_NO_TITANS_REMAINING" )
+ RegisterString( "#GAMEMODE_ENEMY_TITANS_DESTROYED" )
+ RegisterString( "#GAMEMODE_FRIENDLY_TITANS_DESTROYED" )
+ RegisterString( "#GAMEMODE_ENEMY_PILOTS_ELIMINATED" )
+ RegisterString( "#GAMEMODE_FRIENDLY_PILOTS_ELIMINATED" )
+ RegisterString( "#GAMEMODE_ENEMY_PILOT_ELIMINATED" )
+ RegisterString( "#GAMEMODE_FRIENDLY_PILOT_ELIMINATED" )
+ RegisterString( "#GAMEMODE_WAVE_LIMIT_REACHED" )
+ RegisterString( "#GAMEMODE_TIME_LIMIT_REACHED" )
+ RegisterString( "#GAMEMODE_SCORE_LIMIT_REACHED" )
+ RegisterString( "#GAMEMODE_ROUND_LIMIT_REACHED" )
+ RegisterString( "#GAMEMODE_ROUND_LIMIT_REACHED_WON_MORE_ROUNDS" )
+ RegisterString( "#GAMEMODE_ROUND_LIMIT_REACHED_LOSS_MORE_ROUNDS" )
+ RegisterString( "#GAMEMODE_ROUND_LIMIT_REACHED_ROUND_SCORE_DRAW" )
+ RegisterString( "#GAMEMODE_PREPARE_FOR_EVAC" )
+ RegisterString( "#GAMEMODE_AWAIT_INSTRUCTIONS" )
+ RegisterString( "#GAMEMODE_TITAN_TIME_ADVANTAGE" )
+ RegisterString( "#GAMEMODE_TITAN_TIME_DISADVANTAGE" )
+ RegisterString( "#GAMEMODE_TITAN_DAMAGE_ADVANTAGE" )
+ RegisterString( "#GAMEMODE_TITAN_DAMAGE_DISADVANTAGE" )
+ RegisterString( "#GAMEMODE_TITAN_TITAN_ADVANTAGE" )
+ RegisterString( "#GAMEMODE_TITAN_TITAN_DISADVANTAGE" )
+ RegisterString( "#GAMEMODE_DEFENDERS_WIN" )
+ RegisterString( "#GAMEMODE_ATTACKERS_WIN" )
+ RegisterString( "#GAMEMODE_LTS_TIME_LIMIT_REACHED_WIN" )
+ RegisterString( "#GAMEMODE_LTS_TIME_LIMIT_REACHED_LOSS" )
+ RegisterString( "#GAMEMODE_LTS_BOMB_DEFUSED_WIN" )
+ RegisterString( "#GAMEMODE_LTS_BOMB_DEFUSED_LOSS" )
+ RegisterString( "#GAMEMODE_LTS_BOMB_DETONATED_WIN" )
+ RegisterString( "#GAMEMODE_LTS_BOMB_DETONATED_LOSS" )
+ RegisterString( "#GAMEMODE_MARKED_FOR_DEATH_PRO_WIN_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_MARKED_FOR_DEATH_PRO_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_MARKED_FOR_DEATH_PRO_DISCONNECT_WIN_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_MARKED_FOR_DEATH_PRO_DISCONNECT_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_COLISEUM_DISCONNECT_WIN_ANNOUNCEMENT" )
+
+ RegisterString( "#GAMEMODE_LH_WIN_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_LH_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_LH_TIME_OVER_WIN_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_LH_TIME_OVER_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_LH_TIME_OVER_DRAW_ANNOUNCEMENT" )
+
+ RegisterString( "#GAMEMODE_HUNTED_WIN_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_HUNTED_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_HUNTED_WIN_TIME_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_HUNTED_LOSS_TIME_ANNOUNCEMENT" )
+
+ RegisterString( "#GAMEMODE_SPEEDBALL_WIN_TIME_FLAG_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_SPEEDBALL_LOSS_TIME_FLAG_ANNOUNCEMENT" )
+
+ RegisterString( "#GAMEMODE_SPEEDBALL_WIN_TIME_FLAG_LAST" )
+ RegisterString( "#GAMEMODE_SPEEDBALL_LOSS_TIME_FLAG_LAST" )
+
+ RegisterString( "#GAMEMODE_SPEEDBALL_WIN_MORE_PILOTS_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_SPEEDBALL_LOSS_MORE_PILOTS_ANNOUNCEMENT" )
+
+ RegisterString( "#GAMEMODE_DON_WIN_MORE_KILLS_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_DON_LOSS_MORE_KILLS_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_VICTORY" )
+ RegisterString( "#GAMEMODE_DEFEATED" )
+
+ RegisterString( "#DEV_COMMAND_FORCED_WIN_ANNOUNCEMENT" )
+ RegisterString( "#DEV_COMMAND_FORCED_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#COOP_TOTAL_VICTORY_HINT" )
+ RegisterString( "#COOP_TOTAL_DEFEAT_HINT" )
+ RegisterString( "#GAMEMODE_SUR_WIN_ANNOUNCEMENT" )
+ RegisterString( "#GAMEMODE_SUR_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#ENEMY_TEAM_DISCONNECTED_WIN_ANNOUNCEMENT" )
+ RegisterString( "#ENEMY_TEAM_DISCONNECTED_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#SUDDEN_DEATH_WIN_ANNOUNCEMENT" )
+ RegisterString( "#SUDDEN_DEATH_LOSS_ANNOUNCEMENT" )
+ RegisterString( "#SUDDEN_DEATH_KILLED_NEXT_PLAYER_WIN_ANNOUNCEMENT" )
+ RegisterString( "#SUDDEN_DEATH_KILLED_NEXT_PLAYER_LOSS_ANNOUNCEMENT" )
+
+ RegisterString( "#CAPTURE_THE_FLAG_FLAG_ESCAPED" )
+ RegisterString( "#CAPTURE_THE_FLAG_FLAG_CAPTURE_STOPPED" )
+
+ RegisterString( "#GAMESTATE_SWITCHING_SIDES" )
+ RegisterString( "#GAMEMODE_HOST_ENDED_MATCH" )
+
+ RegisterString( "#GENERIC_DRAW_ANNOUNCEMENT" )
+
+ RegisterString( "#RODEO_MULTI_SPOT_MOVE_HINT" )
+ RegisterString( "#RODEO_RIP_BATTERY_HINT" )
+ RegisterString( "#RODEO_APPLY_BATTERY_HINT" )
+ RegisterString( "#RODEO_REQUEST_BATTERY_HINT" )
+ RegisterString( "#RODEO_ANTI_RODEO_SMOKE_HINT" )
+ RegisterString( "#RODEO_ANTI_RODEO_SMOKE_NO_CHARGES_HINT" )
+
+ RegisterString( "#GAMEMODE_FRONTIER_WIN_ALL_CAPTURED" )
+ RegisterString( "#GAMEMODE_FRONTIER_LOSS_ALL_CAPTURED" )
+
+ RegisterString( "#FW_TEAM_TOWER_UNDER_ATTACK" )
+ RegisterString( "#FW_TEAM_TOWER_UNDER_ATTACK_SUB" )
+ RegisterString( "#FW_SHIELD_UNDER_ATTACK")
+ RegisterString( "#FW_SHIELD_DOWN" )
+ RegisterString( "#FW_USE_GENERATOR_NO_BATTERY" )
+ RegisterString( "#FW_USE_TURRET_GENERATOR" )
+ RegisterString( "#FW_USE_TURRET_GENERATOR_PC" )
+ RegisterString( "#FW_TURRET_OWNER" )
+ RegisterString( "#FW_TURRET_DESTROYED" )
+ RegisterString( "#FW_TITAN_REQUIRED" )
+ RegisterString( "#FW_TITAN_REQUIRED_SUB" )
+ RegisterString( "#FW_FRIENDLY_TOWER" )
+ RegisterString( "#FW_ENEMY_TOWER" )
+ RegisterString( "#FW_FRIENDLY_AREA_ENTER" )
+ RegisterString( "#FW_FRIENDLY_AREA_EXIT" )
+ RegisterString( "#FW_ENEMY_AREA_ENTER" )
+ RegisterString( "#FW_ENEMY_AREA_EXIT" )
+ RegisterString( "#FW_USE_BATTERY" )
+
+ RegisterString( "#CP_CAPTURE_POINTS" )
+ RegisterString( "#CP_AMP_POINTS" )
+ RegisterString( "#CP_DEFEND_POINTS" )
+
+ RegisterString( "#FW_OBJECTIVE_EARN" )
+ RegisterString( "#FW_OBJECTIVE_TITANFALL" )
+ RegisterString( "#FW_OBJECTIVE_EMBARK" )
+ RegisterString( "#FW_OBJECTIVE_ATTACK" )
+
+ RegisterString( "#AT_OBJECTIVE_KILL_DZ" )
+ RegisterString( "#AT_OBJECTIVE_KILL_DZ_MULTI" )
+ RegisterString( "#AT_OBJECTIVE_KILL_BOSS" )
+ RegisterString( "#AT_OBJECTIVE_KILL_BOSS_MULTI" )
+ RegisterString( "#AT_BANK_OPEN")
+ RegisterString( "#AT_BANK_OPEN_OBJECTIVE" )
+
+ RegisterString( "#SPEEDBALL_OBJECTIVE_KILL_CAP" )
+ RegisterString( "#SPEEDBALL_OBJECTIVE_ENEMY_FLAG" )
+ RegisterString( "#SPEEDBALL_OBJECTIVE_FRIENDLY_FLAG" )
+ RegisterString( "#SPEEDBALL_OBJECTIVE_PLAYER_FLAG" )
+
+ RegisterString( "#FD_TOTAL_VICTORY_HINT" )
+ RegisterString( "#FD_TOTAL_DEFEAT_HINT" )
+
+#if DEVSCRIPTS
+ Dev_RemoteStrings_Init()
+#endif // DEVSCRIPTS
+
+ //Note: The following are all test variables, feel free to comment them out as we hit the limit
+ //Begin test variables
+ //RegisterNetworkedVariable( "b", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL )
+ //RegisterNetworkedVariable( "i", SNDC_GLOBAL, SNVT_INT )
+ //RegisterNetworkedVariable( "u", SNDC_PLAYER_EXCLUSIVE, SNVT_UNSIGNED_INT )
+ //RegisterNetworkedVariable( "r", SNDC_TITAN_SOUL, SNVT_FLOAT_RANGE, .2, -1, 1 )
+ //RegisterNetworkedVariable( "rot", SNDC_PLAYER_GLOBAL, SNVT_FLOAT_RANGE_OVER_TIME, .5, -1, 1 )
+ //RegisterNetworkedVariable( "t", SNDC_PLAYER_GLOBAL, SNVT_TIME, 500 )
+ //RegisterNetworkedVariable( "e", SNDC_TITAN_SOUL, SNVT_ENTITY )
+ //end test variables
+
+ RegisterServerVar( "titanAvailableBits", 0 ) // HACK; we need this information to be 100% accurate, even during kill replay
+ RegisterServerVar( "respawnAvailableBits", 0 ) // HACK; we need this information to be 100% accurate, even during kill replay
+
+ RegisterNetworkedVariable( "batteryCount", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "activeCallingCardIndex", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+ RegisterNetworkedVariable( "activeCallsignIconIndex", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 )
+
+ RegisterNetworkedVariable( "rewardState", SNDC_PLAYER_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "goalState", SNDC_PLAYER_GLOBAL, SNVT_INT, 0 )
+ RegisterNetworkedVariable( EARNMETER_OWNEDFRAC, SNDC_PLAYER_EXCLUSIVE, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
+ RegisterNetworkedVariable( EARNMETER_EARNEDFRAC, SNDC_PLAYER_EXCLUSIVE, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
+ RegisterNetworkedVariable( EARNMETER_REWARDFRAC, SNDC_PLAYER_EXCLUSIVE, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
+
+ RegisterNetworkedVariable( EARNMETER_GOALID, SNDC_PLAYER_EXCLUSIVE, SNVT_UNSIGNED_INT )
+ RegisterNetworkedVariable( EARNMETER_REWARDID, SNDC_PLAYER_EXCLUSIVE, SNVT_UNSIGNED_INT )
+ RegisterNetworkedVariable( EARNMETER_MODE, SNDC_PLAYER_EXCLUSIVE, SNVT_INT )
+
+ RegisterNetworkedVariable( TOP_INVENTORY_ITEM_BURN_CARD_ID, SNDC_PLAYER_EXCLUSIVE, SNVT_INT, -1 )
+
+ RegisterNetworkedVariable( "activePilotLoadoutIndex", SNDC_PLAYER_EXCLUSIVE, SNVT_INT, 0 )
+ RegisterNetworkedVariable( "activeTitanLoadoutIndex", SNDC_PLAYER_EXCLUSIVE, SNVT_INT, 0 )
+
+ RegisterNetworkedVariable( "coreAvailableFrac", SNDC_TITAN_SOUL, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
+ RegisterNetworkedVariable( "coreExpireFrac", SNDC_TITAN_SOUL, SNVT_FLOAT_RANGE_OVER_TIME, 0.0, 0.0, 1.0 )
+ RegisterNetworkedVariable( "upgradeCount", SNDC_TITAN_SOUL, SNVT_INT, 0 )
+
+ RegisterNetworkedVariable( "xpMultiplier", SNDC_PLAYER_EXCLUSIVE, SNVT_INT, 0 )
+
+ //Battle Chatter
+ Remote_RegisterFunction( "ServerCallback_PlayBattleChatter" )
+ RegisterNetworkedVariable( "battleChatterVoiceIndex", SNDC_PLAYER_GLOBAL, SNVT_INT, 0 )
+
+ //Faction Dialogue
+ Remote_RegisterFunction( "ServerCallback_PlayFactionDialogue" )
+ Remote_RegisterFunction( "ServerCallback_ForcePlayFactionDialogue" )
+ Remote_RegisterFunction( "ServerCallback_SpawnFactionCommanderInDropship" )
+
+ Remote_RegisterFunction( "ServerCallback_PlaySpectreChatterMP" )
+ Remote_RegisterFunction( "ServerCallback_PlayGruntChatterMP" )
+
+ Remote_RegisterFunction( "ServerCallback_EarnMeterAwarded" )
+
+ Remote_RegisterFunction( "ServerCallback_GetObjectiveReminderOnLoad" )
+ Remote_RegisterFunction( "ServerCallback_ClearObjectiveReminderOnLoad" )
+
+ Remote_RegisterFunction( "ServerCallback_PingMinimap" )
+
+ //Boosts
+ RegisterNetworkedVariable( "boostTimedEffectLastsTill", SNDC_PLAYER_EXCLUSIVE, SNVT_TIME )
+ RegisterNetworkedVariable( "burn_numTurrets", SNDC_PLAYER_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "burn_turretLimit", SNDC_GLOBAL, SNVT_INT, 5 )
+
+ #if CLIENT
+ //RegisterNetworkedVariableChangeCallback_time( "t", Changed )
+ RegisterNetworkedVariableChangeCallback_int( "upgradeCount", NetworkedVarChangedCallback_UpdateVanguardRUICoreStatus )
+ if ( !IsLobby() )
+ {
+ ClGameState_RegisterNetworkFunctions()
+
+ Cl_EarnMeter_RegisterNetworkFunctions()
+ ClRodeoTitan_RegisterNetworkFunctions()
+ ClSentryTurret_RegisterNetworkFunctions()
+
+ ClBurnMeter_RegisterNetworkFunctions()
+ }
+ #endif
+
+ InitCustomNetworkVars()
+
+ Remote_EndRegisteringFunctions()
+ _RegisteringFunctions = false
+}
+
+void function Changed( entity ent, float old, float new, bool actuallyChanged )
+{
+ printt( "Changed (" + ent + "): " + old + " -> " + new )
+}
+
+// script GetPlayerArray()[0].SetPlayerNetInt( "i", 0 )
diff --git a/Northstar.CustomServers/scripts/vscripts/_script_movers.gnut b/Northstar.CustomServers/scripts/vscripts/_script_movers.gnut
new file mode 100644
index 000000000..ca7b839b8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_script_movers.gnut
@@ -0,0 +1,1783 @@
+untyped
+
+global function ScriptMovers_Init
+global function ScriptedSwitchDeactivate
+global function ScriptToyChangeStatusLights
+global function SetSwitchUseFunc
+global function ScriptedRotatorRotate
+global function CodeCallback_PreBuildAINFile
+
+const FX_BARREL_EXPLOSION = $"P_spectre_suicide"
+const FX_GENERATOR_EXPLOSION = $"xo_exp_death"
+const FX_CONSOLE_EXPLOSION = $"xo_exp_death"
+const FX_PANEL_EXPLOSION = $"P_drone_exp_md"
+const FX_BARREL_FIRE_SMOKE = $"P_fire_small_FULL"
+const FX_GENERATOR_FIRE_SMOKE = $"P_fire_med_FULL"
+const FX_CONSOLE_FIRE_SMOKE = $"P_fire_med_FULL"
+const FX_PANEL_FIRE_SMOKE = $"P_fire_small_FULL"
+
+const SOUND_BARREL_EXPLODE = "corporate_spectre_death_explode"
+const SOUND_GENERATOR_EXPLODE = "Goblin_Dropship_Explode"
+const SOUND_PANEL_EXPLODE = "AngelCity_Scr_DroneExplodes"
+const SOUND_CONSOLE_EXPLODE = "corporate_spectre_death_explode"
+
+const FAN_PUSH_RAMP_TIME = 0.8
+const FAN_DEFAULT_PUSH_ACCEL = 25000 // units/sec^2
+const FAN_PUSH_ANTI_GRAVITY = 1000 // units/sec^2
+const FAN_PUSH_DECAY_SIDE_VELOCITY = false
+const FAN_DEBUG = false
+
+struct
+{
+ table switchCallbacks
+ //array<entity> entsInWindTunnel
+} file
+
+void function ScriptMovers_Init()
+{
+ AddSpawnCallbackEditorClass( "prop_dynamic", "script_door", ScriptedDoorInit )
+ AddSpawnCallbackEditorClass( "prop_dynamic", "script_switch", ScriptedSwitchInit )
+ AddSpawnCallbackEditorClass( "script_mover_lightweight", "script_rotator", ScriptedRotatorThink )
+ AddSpawnCallbackEditorClass( "script_mover_lightweight", "script_seesaw", SeeSawThink )
+ AddSpawnCallbackEditorClass( "prop_dynamic", "shootable_clasp", ClaspInit )
+
+ AddSpawnCallback_ScriptName( "FanPusher", FanPusherThink )
+
+ PrecacheParticleSystem( FX_BARREL_EXPLOSION )
+ PrecacheParticleSystem( FX_GENERATOR_EXPLOSION )
+ PrecacheParticleSystem( FX_PANEL_EXPLOSION )
+ PrecacheParticleSystem( FX_BARREL_FIRE_SMOKE )
+ PrecacheParticleSystem( FX_GENERATOR_FIRE_SMOKE )
+ PrecacheParticleSystem( FX_PANEL_FIRE_SMOKE )
+ PrecacheParticleSystem( FX_CONSOLE_EXPLOSION )
+ PrecacheParticleSystem( FX_CONSOLE_FIRE_SMOKE )
+
+ AddSpawnCallback( "script_mover", MoverInit )
+ AddSpawnCallback( "script_mover_lightweight", MoverInit )
+
+ RegisterSignal( "OpenDoor" )
+ RegisterSignal( "CloseDoor" )
+ RegisterSignal( "OnDeactivate")
+ RegisterSignal( "OnActivate")
+ RegisterSignal( "StopRotating" )
+}
+
+void function FlagControlsDoor( entity door, string flag )
+{
+ EndSignal( door, "OnDestroy" )
+ while ( true )
+ {
+ WaitSignal( level, flag )
+ if ( Flag( flag ) )
+ Signal( door, "OpenDoor" )
+ else
+ Signal( door, "CloseDoor" )
+ }
+}
+
+void function TriggerControlsDoor( entity door, entity trigger )
+{
+ EndSignal( door, "OpenDoor" )
+ EndSignal( door, "OnDestroy" )
+ EndSignal( trigger, "OnDestroy" )
+
+ WaitSignal( trigger, "OnTrigger" )
+
+ Signal( door, "OpenDoor" )
+}
+
+void function MotionActivatedDoor( entity door )
+{
+ bool doorOpen = false
+ if ( door.HasKey( "startOpen" ) )
+ doorOpen = (door.kv.startOpen == "1")
+
+ EndSignal( door, "OnDestroy" )
+
+ while ( true )
+ {
+ if ( doorOpen )
+ {
+ while ( ArrayEntityWithinDistance( GetPlayerArray(), door.GetOrigin(), 300 ) )
+ wait 0.2
+ Signal( door, "CloseDoor" )
+ }
+ else
+ {
+ while ( !ArrayEntityWithinDistance( GetPlayerArray(), door.GetOrigin(), 200 ) )
+ wait 0.2
+ Signal( door, "OpenDoor" )
+ }
+ doorOpen = !doorOpen
+ wait 1
+ }
+}
+
+void function CodeCallback_PreBuildAINFile()
+{
+ array<entity> doors = GetEntArrayByClass_Expensive( "prop_dynamic" )
+
+ foreach ( entity door in doors )
+ {
+ if ( GetEditorClass( door ) != "script_door" )
+ continue
+
+ door.SetBoneFollowersSolid( false )
+ array<entity> linkedEnts = door.GetLinkEntArray()
+ foreach ( entity ent in linkedEnts )
+ {
+ if ( ent.GetClassName() == "func_brush" )
+ {
+ ent.NotSolid()
+ break
+ }
+ }
+ }
+}
+
+
+void function ScriptedDoorInit( entity door )
+{
+ #if DEV
+ array validModels = [
+ $"models/door/door_imc_interior_03_128_animated.mdl",
+ $"models/door/pod_door_Hangar_IMC_01_animated.mdl",
+ $"models/door/door_512x512x16_elevatorstyle01_animated.mdl",
+ $"models/door/door_256x256x8_elevatorstyle01_animated.mdl",
+ $"models/door/door_128x104x8_rolldownstyle01_animated.mdl",
+ $"models/door/door_256x256x8_rolldownstyle01_animated.mdl",
+ $"models/door/door_256_02_beacon_metal_door_animated.mdl",
+ $"models/door/door_beacon_core_animated.mdl",
+ $"models/door/door_128x104x8_elevatorstyle01_animated.mdl"
+ $"models/door/door_marvin_animated.mdl"
+ ]
+ Assert( validModels.contains( door.GetModelName() ), "Door model at " + door.GetOrigin() + " is invalid: " + door.GetModelName() )
+ #endif
+
+ EndSignal( door, "OnDestroy" )
+ door.SetBlocksLOS( true )
+
+ bool doorOpen = false
+ if ( door.HasKey( "startOpen" ) )
+ doorOpen = (door.kv.startOpen == "1")
+ bool initializing = true
+
+ if ( door.HasKey( "script_flag" ) )
+ {
+ string flag = expect string( door.kv.script_flag )
+ FlagInit( flag )
+ if ( doorOpen )
+ FlagSet( flag )
+ thread FlagControlsDoor( door, flag )
+ }
+
+ string flagToggle
+ if ( door.HasKey( "scr_flagToggle" ) )
+ {
+ flagToggle = expect string( door.kv.scr_flagToggle )
+ FlagInit( flagToggle )
+ }
+
+ if ( door.HasKey( "motionActivated" ) && door.kv.motionActivated == "1" )
+ thread MotionActivatedDoor( door )
+
+ // The door can link to a func_brush that is the collision of the door. The collision will be enabled/disabled based on the door state
+ entity clipBrush
+ array<entity> linkedEnts = door.GetLinkEntArray()
+ foreach ( entity ent in linkedEnts )
+ {
+ if ( ent.GetClassName() == "func_brush" )
+ {
+ clipBrush = ent
+ break
+ }
+ }
+ if ( IsValid( clipBrush ) )
+ {
+ clipBrush.Hide()
+ clipBrush.NotSolid()
+ WaitFrame()
+ }
+
+ // A trigger_multiple can link to the door, causing the door to open. It will only open the door once.
+ entity trigger
+ array<entity> linkParents = door.GetLinkParentArray()
+ foreach ( entity ent in linkParents )
+ {
+ if ( ent.GetClassName() == "trigger_multiple" )
+ {
+ trigger = ent
+ break
+ }
+ }
+ if ( IsValid( trigger ) )
+ thread TriggerControlsDoor( door, trigger )
+
+ while ( IsValid( door ) )
+ {
+ // UPDATE THE DOOR STATE
+ if ( doorOpen )
+ door.Anim_Play("open")
+ else if ( !initializing )
+ door.Anim_Play("close")
+
+ if ( IsValid( clipBrush ) )
+ {
+ ToggleNPCPathsForEntity( clipBrush, doorOpen )
+ if ( doorOpen )
+ {
+ clipBrush.Hide()
+ clipBrush.NotSolid()
+ }
+ else
+ {
+ clipBrush.Show()
+ clipBrush.Solid()
+ }
+ }
+ else
+ {
+ // door must be setup with expensive bone_follower collision for this to work
+ ToggleNPCPathsForEntity( door, doorOpen )
+ }
+
+ if ( flagToggle != "" )
+ {
+ if ( doorOpen )
+ FlagSet( flagToggle )
+ else
+ FlagClear( flagToggle )
+ }
+ initializing = false
+
+ // WAIT FOR STATE CHANGE
+ if ( doorOpen )
+ WaitSignal( door, "CloseDoor" )
+ else
+ WaitSignal( door, "OpenDoor" )
+
+ CreateShakeRumbleOnly( door.GetOrigin(), 15, 150, 1 )
+
+ doorOpen = !doorOpen
+ }
+}
+
+
+void function ScriptedSwitchInit( entity button )
+{
+ #if DEV
+ array< asset > validModels = [
+ $"models/domestic/light_switch_touchscreen.mdl",
+ $"models/props/pressure_plates/pressure_plate_titan_industrial_01.mdl",
+ $"models/domestic/elevator_switch_01.mdl",
+ $"models/beacon/crane_room_monitor_console.mdl",
+ $"models/props/global_access_panel_button/global_access_panel_button_wall.mdl",
+ $"models/props/global_access_panel_button/global_access_panel_button_console.mdl"
+ ]
+ Assert( validModels.contains( button.GetModelName() ) )
+ #endif
+
+ bool usesSkins
+ int activeSkinID
+ int inactiveSkinID
+
+ switch( button.GetModelName() )
+ {
+ case $"models/props/global_access_panel_button/global_access_panel_button_wall.mdl":
+ case $"models/props/global_access_panel_button/global_access_panel_button_console.mdl":
+ usesSkins = true
+ activeSkinID = 0
+ inactiveSkinID = 1
+ break
+ case $"models/beacon/crane_room_monitor_console.mdl":
+ usesSkins = true
+ activeSkinID = 1
+ inactiveSkinID = 2
+ break
+ default:
+ usesSkins = false
+ break
+ }
+
+ int contextId = 0
+ button.Highlight_SetFunctions( contextId, 0, true, HIGHLIGHT_OUTLINE_INTERACT_BUTTON, 1, 0, false )
+ button.Highlight_SetParam( contextId, 0, HIGHLIGHT_COLOR_INTERACT )
+ button.Highlight_SetCurrentContext( contextId )
+
+ EndSignal( button, "OnDestroy" )
+ EndSignal( button, "OnDeactivate" )
+
+ OnThreadEnd(
+ function() : ( button )
+ {
+ // If we haven't destroyed the button, it must be inactive
+ if ( IsValid( button ) )
+ {
+ //ScriptToyChangeStatusLights( button, $"runway_light_red" )
+ Entity_StopFXArray( button )
+ button.UnsetUsable()
+ button.Highlight_HideInside( 1.0 )
+ button.Highlight_HideOutline( 1.0 )
+ }
+ }
+ )
+
+ bool buttonActivated = false
+ bool buttonIsSingleUse = false
+ bool initialized = false
+ bool buttonIsUsable = false
+ float multiUseDelay = 0.2
+
+ bool isPressurePlate = button.GetModelName() == $"models/props/pressure_plates/pressure_plate_titan_industrial_01.mdl"
+
+ if ( isPressurePlate )
+ button.kv.solid = 0 //hack until we can figure out why collision on this model kills titans when embarked
+
+ if ( button.HasKey( "singleUse" ) )
+ buttonIsSingleUse = (button.kv.singleUse == "1" )
+
+ if ( button.HasKey( "usable" ) )
+ buttonIsUsable = (button.kv.usable == "1" )
+
+ if ( button.HasKey( "multiUseDelay" ) )
+ {
+ multiUseDelay = float(button.kv.multiUseDelay)
+ if ( multiUseDelay > 0.0 )
+ Assert( !buttonIsSingleUse, "script_switch at " + button.GetOrigin() + "has multiUseDelay set and is single use" )
+ }
+
+ string flagToggle
+ if ( button.HasKey( "scr_flagToggle" ) )
+ {
+ flagToggle = expect string( button.kv.scr_flagToggle )
+ FlagInit( flagToggle )
+ }
+
+ string flagRequired
+ if ( button.HasKey( "scr_flagRequired" ) )
+ {
+ flagRequired = expect string( button.kv.scr_flagRequired )
+ FlagInit( flagRequired )
+ }
+
+ string hintString_hold = "#HOLD_TO_USE_GENERIC"
+ if ( button.HasKey( "hintString_hold" ) && button.kv.hintString_hold != "" )
+ hintString_hold = string( button.kv.hintString_hold )
+ string hintString_press = "#PRESS_TO_USE_GENERIC"
+ if ( button.HasKey( "hintString_press" ) && button.kv.hintString_press != "" )
+ hintString_press = string( button.kv.hintString_press )
+
+ entity trigger = GetLinkedTrigger( button )
+
+ //need a trigger for pressure plate unless it's just for show
+ if ( ( isPressurePlate ) && ( buttonIsUsable ) )
+ Assert( IsValid( trigger ), "script_switch pressure plate at " + button.GetOrigin() + " needs to link to a trigger_multiple" )
+
+ if ( isPressurePlate && buttonIsUsable )
+ {
+ Assert( IsValid( trigger ), "pressure plate switch at " + button.GetOrigin() + " requires a triggerTarget to activate" )
+ Assert( trigger.GetClassName() == "trigger_multiple", "pressure plate switch at " + button.GetOrigin() + " requires a trigger_multiple to activate" )
+ Assert( trigger.kv.spawnflags == "3", "Trigger for pressure plate at " + button.GetOrigin() + " needs spawnflags set to 3" )
+ }
+
+ if ( !isPressurePlate )
+ {
+ button.SetUsable()
+ button.SetUsableByGroup( "pilot" )
+ button.SetUsePrompts( hintString_hold, hintString_press )
+ button.Highlight_ShowInside( 1.0 )
+ button.Highlight_ShowOutline( 1.0 )
+ }
+
+ var player //hack: have to use "var" when waiting on a usable signal or trigger
+ bool buttonUsedOnce = false
+
+ while ( IsValid( button ) )
+ {
+ //-------------------------
+ // UPDATE EFFECTS
+ //-------------------------
+
+ if ( buttonActivated )
+ {
+ //ScriptToyChangeStatusLights( button, $"runway_light_red" )
+ Entity_StopFXArray( button )
+ if ( usesSkins )
+ button.SetSkin( inactiveSkinID )
+ }
+ else
+ {
+ ScriptToyChangeStatusLights( button, $"runway_light_green" )
+ if ( usesSkins )
+ button.SetSkin( activeSkinID )
+ }
+
+ if ( !buttonIsUsable )
+ break //exit loop if we just want the pretty lights, but no player usability
+
+ if ( buttonIsSingleUse && buttonUsedOnce )
+ break //exit loop if this is a single use button
+
+ if ( isPressurePlate )
+ {
+ //-------------------------
+ // WAIT FOR STATE CHANGE (PRESSURE PLATE)
+ //-------------------------
+
+ if ( buttonActivated )
+ waitthread PressurePlateWaitSignal( trigger, "OnEndTouchAll" )
+ else
+ waitthread PressurePlateWaitSignal( trigger, "OnTrigger" )
+ }
+ else
+ {
+ if ( flagRequired != "" && !Flag( flagRequired ) )
+ {
+ if ( !isPressurePlate )
+ {
+ if ( button.HasKey( "disabledHintString" ) )
+ button.SetUsePrompts( button.kv.disabledHintString, button.kv.disabledHintString )
+ else
+ button.UnsetUsable()
+ }
+
+ //ScriptToyChangeStatusLights( button, $"runway_light_red" )
+ Entity_StopFXArray( button )
+ if ( usesSkins )
+ button.SetSkin( inactiveSkinID )
+ FlagWait( flagRequired )
+ ScriptToyChangeStatusLights( button, $"runway_light_green" )
+ if ( usesSkins )
+ button.SetSkin( activeSkinID )
+ button.SetUsePrompts( hintString_hold , hintString_press )
+ button.SetUsable()
+ }
+
+ //-------------------------
+ // WAIT FOR STATE CHANGE (SIMPLE PUSH BUTTON)
+ //-------------------------
+
+ if ( buttonActivated )
+ {
+ wait multiUseDelay
+ }
+ else
+ {
+ player = button.WaitSignal( "OnPlayerUse" ).player
+ if ( !IsValid( player ) )
+ continue
+ if ( !player.IsPlayer() )
+ continue
+ }
+ }
+
+ //--------------------------------------
+ // Player activated, switch button state
+ //--------------------------------------
+
+ buttonUsedOnce = true
+ buttonActivated = !buttonActivated
+
+ EmitSoundOnEntity( button, "Switch_Activate" )
+
+ button.Signal( "OnActivate" )
+
+ if ( !isPressurePlate && buttonActivated )
+ {
+ button.UnsetUsable() //make the button unusable right after clicking so player doesn't double hit it
+ button.Highlight_HideInside( 1.0 )
+ button.Highlight_HideOutline( 1.0 )
+
+ // Run callbacks
+ if ( button in file.switchCallbacks )
+ {
+ foreach( table callbackTable in file.switchCallbacks[ button ] )
+ {
+ if ( callbackTable.useEnt == null )
+ callbackTable.useFunc( button, player )
+ else
+ callbackTable.useFunc( button, player, callbackTable.useEnt )
+ }
+ }
+ }
+
+ //------------
+ // SET FLAGS
+ //------------
+
+ // Button activated (green)
+ if ( flagToggle != "" && buttonActivated )
+ {
+ FlagSet( flagToggle )
+ }
+
+ if ( buttonActivated )
+ SpawnSpawnersLinkedToButton( button, expect entity( player ) )
+
+ //else if ( buttonActivated && isPressurePlate )
+ // wait 1.5 //wait a bit before re-enabling the usability
+
+ // Button deactivated (red)
+ if ( flagToggle != "" && !buttonActivated )
+ FlagClear( flagToggle )
+
+ if ( !buttonActivated )
+ {
+ button.SetUsable()
+ button.Highlight_ShowInside( 1.0 )
+ button.Highlight_ShowOutline( 1.0 )
+ }
+ }
+
+ if ( ( isPressurePlate ) && ( IsValid( trigger ) ) )
+ trigger.Destroy()
+ else
+ {
+ button.UnsetUsable()
+ button.Highlight_HideInside( 1.0 )
+ button.Highlight_HideOutline( 1.0 )
+ }
+}
+
+void function SpawnSpawnersLinkedToButton( entity button, entity activator )
+{
+ foreach ( entity linkedEnt in button.GetLinkEntArray() )
+ {
+ if ( IsStalkerRack( linkedEnt ) )
+ {
+ thread SpawnFromStalkerRack( linkedEnt, activator )
+ }
+ else if ( IsSpawner( linkedEnt ) )
+ {
+ entity spawned = linkedEnt.SpawnEntity()
+ DispatchSpawn( spawned )
+ }
+ else
+ {
+ Signal( linkedEnt, "OpenDoor" )
+ }
+ }
+}
+
+void function ScriptedSwitchDeactivate( entity button )
+{
+ Assert( IsValid( button ) )
+ button.Signal( "OnDeactivate" )
+}
+
+void function ScriptToyChangeStatusLights( entity button, asset fxName )
+{
+ //--------------------------
+ // Kill any previous effects
+ //--------------------------
+ Entity_StopFXArray( button )
+
+ //--------------------------
+ // Start new effects at tags
+ //--------------------------
+ array<entity> newFxLights
+ array<string> fxTags
+ entity newFx
+ int index = 0
+ string tagName
+
+ while (true)
+ {
+ tagName = "light" + index
+ local id = button.LookupAttachment( tagName )
+ if ( id == 0 )
+ break
+
+ newFx = PlayLoopFXOnEntity( fxName, button, tagName )
+ newFxLights.append( newFx )
+
+ index++
+ }
+
+ button.e.fxArray = newFxLights
+}
+
+void function PressurePlateWaitSignal( entity trigger, string waitSignal )
+{
+ //waitSignal is either "OnTrigger" or "OnEndTouchAll"
+
+ trigger.EndSignal( "OnDestroy" )
+ var result //hack. Result info from triggers
+
+ while ( IsValid( trigger ) )
+ {
+ result = trigger.WaitSignal( waitSignal )
+
+ if ( !IsValid( result.activator ) )
+ continue
+ if ( !result.activator.IsTitan() )
+ continue
+ if ( ( result.activator.IsPlayer() ) || ( IsPetTitan( result.activator ) ) )
+ break
+ }
+}
+
+void function ScriptedRotatorThink( entity rotator )
+{
+ rotator.Hide()
+
+ if ( rotator.HasKey( "use_local_rotation" ) && rotator.kv.use_local_rotation == "1" )
+ rotator.NonPhysicsSetRotateModeLocal( true )
+
+ EndSignal( rotator, "OnDestroy" )
+
+ vector baseAngles = rotator.GetAngles()
+
+ // Linked entities get parented
+ array<entity> linkedEnts = rotator.GetLinkEntArray()
+ foreach ( entity ent in linkedEnts )
+ {
+ ent.SetParent( rotator, "", true )
+ }
+
+ if ( rotator.HasKey( "player_collides" ) && rotator.kv.player_collides == "1" )
+ rotator.SetPusher( true )
+
+ if ( rotator.HasKey( "change_navmesh" ) && rotator.kv.change_navmesh == "1" )
+ rotator.ChangeNPCPathsOnMove( true )
+
+ // script will custom rotate this one
+ if ( rotator.HasKey( "scripted_rotator" ) && expect string( rotator.kv.scripted_rotator ) == "true" )
+ return
+
+ if ( rotator.HasKey( "script_flag" ) )
+ {
+ string flag = expect string( rotator.kv.script_flag )
+ FlagInit( flag )
+ while ( true )
+ {
+ bool returnToBaseAngle = false
+ if ( rotator.HasKey( "flag_clear_resets" ) )
+ returnToBaseAngle = rotator.kv.flag_clear_resets == "1"
+
+ FlagWait( flag )
+ thread ScriptedRotatorRotate( baseAngles, rotator )
+ FlagWaitClear( flag )
+ Signal( rotator, "StopRotating" )
+
+ // Return when the flag is cleared
+ if ( returnToBaseAngle )
+ {
+ if ( !IsValid( rotator ) )
+ return
+
+ float rotateTime = 1.0
+ if ( rotator.HasKey( "rotate_to_time" ) )
+ rotateTime = float( rotator.kv.rotate_to_time )
+ float easeTime = 0.0
+ if ( rotator.HasKey( "rotate_to_ease" ) && rotator.kv.rotate_to_ease == "1" )
+ easeTime = rotateTime * 0.33
+
+ rotator.NonPhysicsRotateTo( baseAngles, rotateTime, easeTime, easeTime )
+ }
+ }
+ }
+ else
+ {
+ thread ScriptedRotatorRotate( baseAngles, rotator )
+ }
+}
+
+vector function GetRotationVector( entity rotator )
+{
+ string axis
+ if ( rotator.HasKey( "rotation_axis" ) )
+ axis = expect string( rotator.kv.rotation_axis )
+
+ vector angles = rotator.GetAngles()
+ switch ( axis )
+ {
+ case "pitch":
+ return AnglesToRight( angles )
+
+ case "yaw":
+ return AnglesToUp( angles )
+
+ case "roll":
+ default:
+ return AnglesToForward( angles )
+
+ }
+
+ unreachable
+}
+
+void function ScriptedRotatorRotate( vector baseAngles, entity rotator )
+{
+ Signal( rotator, "StopRotating" )
+ EndSignal( rotator, "OnDestroy" )
+ EndSignal( rotator, "StopRotating" )
+
+ OnThreadEnd(
+ function() : ( rotator )
+ {
+ if ( IsValid( rotator ) )
+ rotator.NonPhysicsRotate( Vector( 0, 0, 0), 0 )
+ }
+ )
+
+ if ( rotator.HasKey( "start_delay" ) )
+ {
+ float delay = float( rotator.kv.start_delay )
+ if ( delay > 0 )
+ wait delay
+ }
+
+ if ( rotator.kv.rotate_forever_speed != "0" )
+ {
+ // Rotate forever
+ float speed = float( rotator.kv.rotate_forever_speed )
+ Assert( speed != 0.0 )
+
+ vector rotateVec = GetRotationVector( rotator )
+ rotator.NonPhysicsRotate( rotateVec, speed )
+ WaitForever()
+ }
+ else
+ {
+ // Rotate specified amount
+ Assert( rotator.HasKey( "rotate_to_degrees" ) )
+ Assert( rotator.HasKey( "rotate_to_time" ) )
+
+ string soundEffect = ""
+ if ( rotator.HasKey( "script_sound" ) )
+ soundEffect = string( rotator.kv.script_sound )
+
+ float rotateTime = float( rotator.kv.rotate_to_time )
+ if ( rotateTime > 0.0 )
+ {
+ vector rotateAngles = AnglesCompose( baseAngles, Vector( 0.0, 0.0, float( rotator.kv.rotate_to_degrees ) ) )
+
+ float easeTime = 0.0
+ if ( rotator.HasKey( "rotate_to_ease" ) && rotator.kv.rotate_to_ease == "1" )
+ easeTime = rotateTime * 0.33
+
+ while ( true )
+ {
+ // Rotate to the goal angle
+ rotator.NonPhysicsRotateTo( rotateAngles, rotateTime, easeTime, easeTime )
+ if ( soundEffect != "" )
+ EmitSoundOnEntity( rotator, soundEffect )
+ wait rotateTime
+
+ // Rotate back to base angle if specified
+ if ( !rotator.HasKey( "rotate_to_return_delay" ) || rotator.kv.rotate_to_return_delay == "-1" )
+ return
+ Assert( float( rotator.kv.rotate_to_return_delay ) >= 0.0 )
+ wait float( rotator.kv.rotate_to_return_delay )
+ rotator.NonPhysicsRotateTo( baseAngles, rotateTime, easeTime, easeTime )
+ if ( soundEffect != "" )
+ EmitSoundOnEntity( rotator, soundEffect )
+ wait rotateTime
+
+ // Wait a delay and repeat the rotation if specified
+ if ( !rotator.HasKey( "rotate_to_loop_time" ) || rotator.kv.rotate_to_loop_time == "-1" )
+ return
+ Assert( float( rotator.kv.rotate_to_loop_time ) >= 0.0 )
+ wait float( rotator.kv.rotate_to_loop_time )
+ }
+ }
+ }
+}
+
+void function MoverInit( entity mover )
+{
+ if ( !mover.HasKey( "leveledplaced" ) || mover.kv.leveledplaced != "1" )
+ return
+
+ // Linked entities get parented
+ if ( mover.HasKey( "parent_linked_ents" ) && mover.kv.parent_linked_ents == "1" )
+ {
+ array<entity> linkedEnts = mover.GetLinkEntArray()
+ foreach( entity ent in linkedEnts )
+ {
+ if ( GetEditorClass( ent ) != "script_mover_path" )
+ ent.SetParent( mover, "", true )
+ }
+ }
+
+ if ( mover.HasKey( "player_collides" ) && mover.kv.player_collides == "1" )
+ mover.SetPusher( true )
+
+ if ( mover.HasKey( "change_navmesh" ) && mover.kv.change_navmesh == "1" )
+ mover.ChangeNPCPathsOnMove( true )
+
+ thread MoverThink( mover )
+}
+
+void function MoverThink( entity mover )
+{
+ EndSignal( mover, "OnDestroy" )
+
+ if ( mover.GetModelName() == $"models/dev/editor_ref.mdl" )
+ mover.Hide()
+
+ array<entity> pathNodes = GetNextMoverPathNodes( mover )
+
+ // Go down the path gathering the nodes and init any flags
+ foreach( entity node in pathNodes )
+ InitMoverNodeFlagsAndErrorCheck( node )
+
+ Assert( mover.HasKey( "path_speed") && float( mover.kv.path_speed ) >= 0.0, "script_mover doesnt have a valid path speed" )
+ float pathSpeed = float( mover.kv.path_speed )
+ bool easeIn
+ bool easeOut
+
+ string startFlag = ""
+ if ( mover.HasKey( "script_flag" ) && mover.kv.script_flag != "" )
+ {
+ startFlag = mover.GetValueForKey( "script_flag" )
+ FlagInit( startFlag )
+ }
+
+ if ( mover.HasKey( "dangerous_area_radius" ) )
+ mover.AllowNPCGroundEnt( false )
+
+ if ( pathNodes.len() == 0 )
+ return
+
+ if ( startFlag != "" )
+ FlagWait( startFlag )
+
+ if ( mover.HasKey( "start_delay" ) && float( mover.kv.start_delay ) > 0.0 )
+ wait float( mover.kv.start_delay )
+
+ entity pathNode = pathNodes.getrandom()
+ entity lastNode
+
+ easeOut = pathNode.HasKey( "ease_from_node" ) && pathNode.GetValueForKey( "ease_from_node" ) == "1"
+
+ bool isMoving = false
+
+ while( IsValid( pathNode ) )
+ {
+ bool teleport = false
+ if ( pathNode.HasKey( "teleport_to_node" ) )
+ teleport = pathNode.GetValueForKey( "teleport_to_node" ) == "1"
+
+ bool perfectRotation = false
+ if ( IsValid( lastNode ) && lastNode.HasKey( "perfect_circular_rotation" ) )
+ perfectRotation = lastNode.GetValueForKey( "perfect_circular_rotation" ) == "1"
+
+ float rotationTime = 0.0
+ if ( IsValid( lastNode ) && lastNode.HasKey( "circular_rotation_time" ) )
+ rotationTime = float( lastNode.GetValueForKey( "circular_rotation_time" ) )
+
+ float dist = Distance( pathNode.GetOrigin(), mover.GetOrigin() )
+
+ if ( !isMoving )
+ MoverPath_StartSound( mover, pathNode )
+
+ if ( dist > 0.0 && !teleport )
+ {
+ easeIn = pathNode.HasKey( "ease_to_node" ) && pathNode.GetValueForKey( "ease_to_node" ) == "1"
+ float moveTime = dist / pathSpeed
+ float easeLeaving = easeOut ? moveTime * 0.5 : 0.0
+ float easeArriving = easeIn ? moveTime * 0.5 : 0.0
+ float angleChange = IsValid( lastNode ) ? MoverPath_GetAngleChange( lastNode, pathNode ) : 0.0
+
+ if ( perfectRotation && angleChange != 0 )
+ {
+ string rotationSoundEvent = ""
+ if ( mover.HasKey( "sound_circular_rotation" ) )
+ rotationSoundEvent = mover.GetValueForKey( "sound_circular_rotation" )
+ if ( rotationSoundEvent != "" )
+ EmitSoundOnEntity( mover, rotationSoundEvent )
+
+ vector turnAnchorPos = MoverPath_GetAngleAnchor( lastNode, pathNode )
+
+ // Create a new mover because as far as I know I can't get all the children of the mover and clearparent and reparent.
+ entity curveMover = CreateScriptMover( turnAnchorPos, lastNode.GetAngles() )
+ curveMover.SetPusher( mover.GetPusher() )
+ mover.SetParent( curveMover, "", true )
+
+ // Find the circumference of the turn so we can calculate the rotation time based on the distance traveled around the bend
+ float c = 2 * PI * Length(turnAnchorPos - lastNode.GetOrigin())
+ float frac = fabs(angleChange) / 360.0
+ moveTime = (c * frac) / pathSpeed
+
+ isMoving = true
+ curveMover.NonPhysicsRotateTo( pathNode.GetAngles(), moveTime, 0.0, 0.0 )
+
+ wait moveTime - 0.01
+
+ mover.ClearParent()
+ curveMover.Destroy()
+
+ if ( rotationSoundEvent != "" )
+ StopSoundOnEntity( mover, rotationSoundEvent )
+ }
+ else
+ {
+ // Linear move/rotate
+ isMoving = true
+ if ( mover.HasKey( "dangerous_area_radius" ) )
+ thread CreateMoverDangrousAreas( mover, mover.GetOrigin(), pathNode.GetOrigin(), float( mover.GetValueForKey( "dangerous_area_radius" ) ), moveTime )
+ mover.NonPhysicsMoveTo( pathNode.GetOrigin(), moveTime, easeLeaving, easeArriving )
+ mover.NonPhysicsRotateTo( pathNode.GetAngles(), moveTime, easeLeaving, easeArriving )
+ wait moveTime - 0.01
+ }
+ }
+ else if ( dist == 0.0 && !teleport && rotationTime > 0.0 )
+ {
+ // Rotation in place
+ string rotationSoundEvent = ""
+ if ( mover.HasKey( "sound_rotation" ) )
+ rotationSoundEvent = mover.GetValueForKey( "sound_rotation" )
+ if ( rotationSoundEvent != "" )
+ EmitSoundOnEntity( mover, rotationSoundEvent )
+
+ isMoving = false
+ MoverPath_StopMoveSound( mover )
+ MoverPath_StopSound( mover, pathNode )
+ float easeIn = easeOut ? rotationTime * 0.5 : 0.0
+ float easeOut = easeIn ? rotationTime * 0.5 : 0.0
+ mover.NonPhysicsRotateTo( pathNode.GetAngles(), rotationTime, easeIn, easeOut )
+ wait rotationTime - 0.01
+
+ if ( rotationSoundEvent != "" )
+ StopSoundOnEntity( mover, rotationSoundEvent )
+ }
+ else
+ {
+ mover.SetOrigin( pathNode.GetOrigin() )
+ mover.SetAngles( pathNode.GetAngles() )
+ }
+
+ easeOut = pathNode.HasKey( "ease_from_node" ) && pathNode.GetValueForKey( "ease_from_node" ) == "1"
+
+ if ( pathNode.HasKey( "scr_flag_set" ) )
+ FlagSet( pathNode.GetValueForKey( "scr_flag_set" ) )
+
+ if ( pathNode.HasKey( "scr_flag_clear" ) )
+ FlagClear( pathNode.GetValueForKey( "scr_flag_clear" ) )
+
+ if ( pathNode.HasKey( "scr_flag_wait" ) )
+ {
+ string flag = pathNode.GetValueForKey( "scr_flag_wait" )
+ if ( !Flag( flag ) )
+ {
+ isMoving = false
+ MoverPath_StopMoveSound( mover )
+ MoverPath_StopSound( mover, pathNode )
+ FlagWait( flag )
+ }
+ }
+
+ if ( pathNode.HasKey( "scr_flag_wait_clear" ) )
+ {
+ string flag = pathNode.GetValueForKey( "scr_flag_wait_clear" )
+ if ( Flag( flag ) )
+ {
+ isMoving = false
+ MoverPath_StopMoveSound( mover )
+ MoverPath_StopSound( mover, pathNode )
+ FlagWaitClear( flag )
+ }
+ }
+
+ if ( pathNode.HasKey( "path_wait" ) )
+ {
+ float time = float( pathNode.GetValueForKey( "path_wait" ) )
+ if ( time > 0.0 )
+ {
+ isMoving = false
+ MoverPath_StopMoveSound( mover )
+ MoverPath_StopSound( mover, pathNode )
+ wait time
+ }
+ }
+
+ pathNodes = GetNextMoverPathNodes( pathNode )
+ if ( pathNodes.len() == 0 )
+ {
+ MoverPath_StopMoveSound( mover )
+ MoverPath_StopSound( mover, pathNode )
+ break
+ }
+
+ // Update speed based on the node
+ if ( pathNode.HasKey( "path_speed" ) )
+ pathSpeed = float( pathNode.GetValueForKey( "path_speed" ) )
+
+ lastNode = pathNode
+ pathNode = pathNodes.getrandom()
+ }
+}
+
+void function CreateMoverDangrousAreas( entity mover, vector start, vector end, float radius, float duration )
+{
+ float d = Distance( start, end )
+ float spacing = radius * 1.5
+ int numDangerousSpots = int( ceil( d / spacing ) )
+ vector direction = Normalize( end - start )
+ vector pos
+
+ for ( int i = 0 ; i < numDangerousSpots ; i++ )
+ {
+ pos = start + ( direction * spacing * i )
+ thread CreateMoverDangrousAreaUntilMoverPasses( mover, start, end, pos, radius, duration )
+ }
+}
+
+void function CreateMoverDangrousAreaUntilMoverPasses( entity mover, vector start, vector end, vector pos, float radius, float maxDuration )
+{
+ // Create entity to link it to (lifetime)
+ entity lifetimeEnt = CreateScriptRef( pos )
+
+ // Create the dangerous area
+ AI_CreateDangerousArea_Static( lifetimeEnt, null, radius, TEAM_INVALID, true, true, pos )
+
+ // Wait for mover to go past the dangerous area, or timeout
+ float endTime = Time() + maxDuration
+ while( Time() <= endTime )
+ {
+ if ( DotProduct( end - start, pos - mover.GetOrigin() ) < 0 )
+ break
+ WaitFrame()
+ }
+
+ lifetimeEnt.Destroy()
+}
+
+void function MoverPath_StopMoveSound( entity mover )
+{
+ // Stops any move sounds playing on the mover
+
+ // Stop playing a sound on the mover if one is specified & it is set to do so
+ if ( mover.HasKey( "sound_move" ) && mover.kv.sound_move != "" )
+ {
+ // "sound_move" sound continues to play after moving unless this is checked"
+ if ( mover.HasKey( "stop_sound_move_on_stop" ) && mover.GetValueForKey( "stop_sound_move_on_stop" ) == "1" )
+ StopSoundOnEntity( mover, string( mover.kv.sound_move ) )
+ }
+}
+
+void function MoverPath_StopSound( entity mover, entity node )
+{
+ // Play sound on the node if one is specified
+ if ( node.HasKey( "sound_stop_move" ) && node.kv.sound_stop_move != "" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, node.GetOrigin(), string( node.kv.sound_stop_move ) )
+
+ // Play sound on the mover if one is specified
+ if ( mover.HasKey( "sound_stop_move" ) && mover.kv.sound_stop_move != "" )
+ EmitSoundOnEntity( mover, string( mover.kv.sound_stop_move ) )
+}
+
+void function MoverPath_StartSound( entity mover, entity node )
+{
+ // Play sound on the node if one is specified
+ if ( node.HasKey( "sound_start_move" ) && node.kv.sound_start_move != "" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, node.GetOrigin(), string( node.kv.sound_start_move ) )
+
+ // Play sound on mover if one is specified
+ if ( mover.HasKey( "sound_move" ) && mover.kv.sound_move != "" )
+ EmitSoundOnEntity( mover, string( mover.kv.sound_move ) )
+}
+
+float function MoverPath_GetAngleChange( entity node1, entity node2 )
+{
+ vector vec1 = node1.GetForwardVector()
+ vector vec2 = node2.GetForwardVector()
+ float angle = acos( DotProduct( vec1, vec2 ) ) * 180 / PI
+ return angle
+}
+
+vector function MoverPath_GetAngleAnchor( entity node1, entity node2 )
+{
+ vector node1Origin = node1.GetOrigin()
+ vector node2Origin = node2.GetOrigin()
+ vector node1Angles = node1.GetAngles()
+ vector node2Angles = node2.GetAngles()
+ vector node1SideVec
+ vector node2SideVec
+
+ if ( node1Origin.z != node2Origin.z )
+ {
+ // vertical turn
+ node1SideVec = AnglesToUp( node1Angles )
+ node2SideVec = AnglesToUp( node2Angles )
+ }
+ else
+ {
+ // horizontal turn
+ node1SideVec = AnglesToRight( node1Angles )
+ node2SideVec = AnglesToRight( node2Angles )
+ }
+
+ float angleChange = MoverPath_GetAngleChange( node1, node2 )
+ Assert( angleChange != 0 )
+ if ( angleChange > 0 )
+ {
+ node1SideVec *= -1
+ node2SideVec *= -1
+ }
+ float d = Distance( node1Origin, node2Origin )
+ vector intersect = GetClosestPointToLineSegments( node1Origin, node1Origin + node1SideVec * d, node2Origin, node2Origin + node2SideVec * d )
+
+ //DebugDrawLine( intersect, node1Origin, 255, 255, 0, true, 5.0 )
+ //DebugDrawLine( intersect, node2Origin, 0, 255, 255, true, 5.0 )
+ //DebugDrawLine( node1Origin, node1Origin + node1SideVec * 250, 100, 100, 100, true, 5.0 )
+ //DebugDrawLine( node2Origin, node2Origin + node2SideVec * 250, 100, 100, 100, true, 5.0 )
+ //DebugDrawLine( intersect, intersect - <0,0,128>, 100, 0, 0, true, 5.0 )
+ //DebugDrawText( intersect, angleChange.tostring(), true, 5.0 )
+
+ return intersect
+}
+
+array<entity> function GetNextMoverPathNodes( entity node, bool errorChecking = false )
+{
+ array<entity> nodes
+ array<entity> linkedEnts = node.GetLinkEntArray()
+ foreach( entity ent in linkedEnts )
+ {
+ if ( GetEditorClass( ent ) == "script_mover_path" )
+ {
+ if ( !errorChecking && ent.HasKey( "switchtrack_flag" ) && !Flag( ent.GetValueForKey( "switchtrack_flag" ) ) )
+ continue
+ nodes.append( ent )
+ }
+ }
+ return nodes
+}
+
+void function InitMoverNodeFlagsAndErrorCheck( entity node )
+{
+ if ( node.e.moverPathPrecached )
+ return
+
+ if ( node.HasKey( "path_speed" ) )
+ Assert( float( node.kv.path_speed ) > 0.0, "Node path_speed at " + node.GetOrigin() + " must be greater than 0." )
+
+ if ( node.HasKey( "path_wait" ) )
+ Assert( float( node.kv.path_wait ) >= 0.0, "Node path_wait at " + node.GetOrigin() + " must be greater than 0." )
+
+ if ( node.HasKey( "teleport_to_node" ) && node.kv.teleport_to_node == "1" )
+ {
+ if ( node.HasKey( "ease_to_node" ) )
+ Assert( node.kv.ease_to_node == "0", "Node at " + node.GetOrigin() + " cant have both teleport_to_node and ease_to_node checked." )
+ }
+
+ if ( node.HasKey( "scr_flag_set" ) )
+ FlagInit( node.GetValueForKey( "scr_flag_set" ) )
+ if ( node.HasKey( "scr_flag_clear" ) )
+ FlagInit( node.GetValueForKey( "scr_flag_clear" ) )
+ if ( node.HasKey( "scr_flag_wait" ) )
+ FlagInit( node.GetValueForKey( "scr_flag_wait" ) )
+ if ( node.HasKey( "switchtrack_flag" ) )
+ FlagInit( node.GetValueForKey( "switchtrack_flag" ), true )
+
+ node.e.moverPathPrecached = true
+
+ array<entity> pathNodes = GetNextMoverPathNodes( node, true )
+ foreach( entity node in pathNodes )
+ InitMoverNodeFlagsAndErrorCheck( node )
+}
+
+entity function GetLinkedTrigger( entity ent )
+{
+ array<entity> linkedEnts = ent.GetLinkEntArray()
+ foreach ( entity ent in linkedEnts )
+ {
+ if ( ent.GetClassName() == "trigger_multiple" )
+ return ent
+ }
+ return null
+}
+
+struct SeeSawThinkStruct // struct that is internal to seeSaw think logic
+{
+ float speed
+ bool touching
+ bool wasTouched
+ vector startAngles
+ float oldSpeed
+ bool playerIgnore
+ float maxSpeed = 10
+ float acceleration = 0.425
+}
+
+void function SeeSawThink( entity seeSaw )
+{
+ seeSaw.Hide()
+
+ seeSaw.EndSignal( "OnDestroy" )
+// seeSaw.NonPhysicsSetRotateModeLocal( true )
+ seeSaw.SetPusher( true )
+
+ array<entity> parents = seeSaw.GetLinkParentArray()
+ array<entity> brushes
+ foreach ( ent in parents )
+ {
+ if ( ent.GetClassName() == "func_brush" )
+ brushes.append( ent )
+ }
+
+ float minz = 0
+ float maxz = 0
+ foreach ( brush in brushes )
+ {
+ vector mins = brush.GetBoundingMins()
+ vector maxs = brush.GetBoundingMaxs()
+
+ if ( mins.z < minz )
+ minz = mins.z
+
+ if ( maxs.z > maxz )
+ maxz = maxs.z
+ }
+
+ float height = fabs( minz ) + maxz
+
+ entity trigger = seeSaw.GetLinkEnt()
+ trigger.EndSignal( "OnDestroy" )
+
+ SeeSawThinkStruct e
+ e.startAngles = seeSaw.GetAngles()
+
+ if ( seeSaw.HasKey( "script_start_moving" ) && int( seeSaw.kv.script_start_moving ) > 0 )
+ {
+ e.wasTouched = true
+ e.speed = 8
+ }
+
+ thread SeeSawSpeedThink( seeSaw, e )
+
+ if ( seeSaw.HasKey( "script_player_ignore" ) && int( seeSaw.kv.script_player_ignore ) > 0 )
+ {
+ e.playerIgnore = true
+ }
+ else
+ {
+ thread SeeSawTriggerThink( seeSaw, trigger, height, e )
+ }
+
+
+}
+
+void function SeeSawTriggerThink( entity seeSaw, entity trigger, float height, SeeSawThinkStruct e )
+{
+ for ( ;; )
+ {
+ e.touching = false
+ table results = trigger.WaitSignal( "OnTrigger" )
+ entity player = expect entity( results.activator )
+ if ( !IsAlive( player ) )
+ continue
+
+ while ( trigger.IsTouching( player ) )
+ {
+ PlayerNearSeeSaw( player, seeSaw, height, e )
+ WaitFrame()
+ }
+ }
+}
+
+float ornull function SeeSawPitchLimitOverride()
+{
+// return 60.0
+ return null
+}
+
+void function SeeSawSpeedThink( entity seeSaw, SeeSawThinkStruct e )
+{
+ seeSaw.EndSignal( "OnDestroy" )
+ float pitchLimit = 50 // 70.75
+
+ if ( seeSaw.HasKey( "script_pitch_limit" ) )
+ pitchLimit = float( seeSaw.kv.script_pitch_limit )
+
+ vector angles = seeSaw.GetAngles()
+ vector forward = AnglesToRight( angles ) * -1
+ vector rotateDir = forward // < -1,0,0 >
+
+ for ( ;; )
+ {
+ WaitFrame()
+ SeeSawSpeedThink_internal( seeSaw, e, rotateDir, pitchLimit )
+ }
+}
+
+void function SeeSawSpeedThink_internal( entity seeSaw, SeeSawThinkStruct e, vector rotateDir, float pitchLimit )
+{
+ float ornull pitchLimitOverride = SeeSawPitchLimitOverride()
+ if ( pitchLimitOverride != null )
+ {
+ pitchLimit = expect float( pitchLimitOverride )
+ }
+ vector localAngles = seeSaw.GetLocalAngles()
+
+ if ( ( !e.touching && e.wasTouched ) || e.playerIgnore )
+ {
+ vector startForward = AnglesToForward( e.startAngles )
+ vector startUp = AnglesToUp( e.startAngles )
+ vector seeSawForward = AnglesToForward( seeSaw.GetAngles() )
+
+// if ( ge(106)==seeSaw)
+// {
+// printt( "dot is " + DotProduct( startForward, seeSawForward ) + " speed " + e.speed )
+// }
+ //printt( "Dot " + DotProduct( startForward, seeSawForward ) )
+ //printt( "Dot " +
+ //printt( DotProduct( startUp, seeSawForward ) )
+
+
+ // return to normal
+ //printt( "start angles " + e.startAngles + " current angles " + seeSaw.GetAngles() )
+
+ if ( fabs( DotProduct( startForward, seeSawForward ) ) < 0.75 )
+ {
+ if ( DotProduct( startUp, seeSawForward ) > 0 )
+ {
+ if ( e.speed < e.maxSpeed )
+ e.speed += e.acceleration
+ }
+ else
+ {
+ if ( e.speed > -e.maxSpeed )
+ e.speed -= e.acceleration
+ }
+ }
+
+ //DebugDrawText( seeSaw.GetOrigin(), "" + e.speed, true, 1 )
+ //DebugDrawLine( seeSaw.GetOrigin(), GetPlayerArray()[0].GetOrigin(), 255, 0, 0, true, 0.2 )
+ }
+
+// if ( ge(117) == seeSaw )
+// return
+ if ( e.speed < 0 )
+ {
+ if ( localAngles.x < -pitchLimit )
+ {
+ seeSaw.NonPhysicsRotate( rotateDir, 0 )
+ e.speed = 0
+ return
+ }
+ }
+ else
+ {
+ if ( localAngles.x > pitchLimit )
+ {
+ seeSaw.NonPhysicsRotate( rotateDir, 0 )
+ e.speed = 0
+ return
+ }
+ }
+
+ if ( e.oldSpeed != e.speed || pitchLimitOverride != null )
+ {
+ seeSaw.NonPhysicsRotate( rotateDir, e.speed )
+ e.oldSpeed = e.speed
+ }
+}
+
+void function PlayerNearSeeSaw( entity player, entity seeSaw, float height, SeeSawThinkStruct e )
+{
+ vector playerOrigin = player.GetOrigin()
+ vector seeSawOrigin = seeSaw.GetOrigin()
+ //DebugDrawLine( playerOrigin, seeSawOrigin, 255, 0, 0, true, 0.2 )
+ vector originDif = playerOrigin - seeSawOrigin
+ vector difNormal = Normalize( originDif )
+ vector seeSawAngles = seeSaw.GetAngles()
+ vector seeSawUp = AnglesToUp( seeSawAngles )
+ bool onTop = DotProduct( difNormal, seeSawUp ) > 0
+
+ // may need to do something special for hands holding on
+ if ( !onTop )
+ {
+ e.touching = false
+ return
+ }
+
+ float amountAbove = DotProduct( originDif, seeSawUp )
+ amountAbove -= height
+ amountAbove += 2.5 // player is in the ground?
+
+// if ( ge(117) == seeSaw )
+// {
+// printt( "amountAbove " + amountAbove )
+// }
+
+ if ( amountAbove < 0 || amountAbove > 15 )
+ {
+ e.touching = false
+ return
+ }
+
+
+
+ vector seeSawForward = AnglesToForward( seeSawAngles )
+ float amountForward = DotProduct( originDif, seeSawForward )
+ e.speed += Graph( amountForward, 0, 1000, 0, 2 )
+ float maxSpeed = 10
+ e.speed = min( maxSpeed, e.speed )
+ e.speed = max( -maxSpeed, e.speed )
+ e.touching = true
+ e.wasTouched = true
+}
+
+void function ClaspInit(entity clasp)
+{
+ //printt( "" )
+ //printt( "INIT SHOOTABLE CLASP" )
+ //printt( "" )
+
+ if ( clasp.HasKey( "scr_flag_set" ) )
+ FlagInit( clasp.GetValueForKey( "scr_flag_set" ) )
+
+ AddEntityCallback_OnDamaged( clasp, OnClaspDamaged )
+
+}
+
+void function OnClaspDamaged( entity clasp, var damageInfo)
+{
+ //printt( "CLASP HAS TAKEN DAMAGE" )
+
+ //if the attacker is not valid
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) || !attacker.IsPlayer() )
+ return
+
+ if ( clasp.HasKey( "scr_flag_set") )
+ {
+ //printt( clasp )
+ //printt( clasp.GetEncodedEHandle() )
+ //printt( clasp.GetTargetName() )
+ //printt( clasp.GetValueForKey( "scr_flag_set" ) )
+ FlagSet( clasp.GetValueForKey( "scr_flag_set" ) )
+ }
+
+ //printt( "" )
+ //printt( "DESTROYING CLASP" )
+ //printt( "" )
+
+ //Destroy the clasp
+ clasp.Destroy()
+}
+
+function SetSwitchUseFunc( button, func, ent = null )
+{
+ local Table = InitControlPanelUseFuncTable()
+ Table.useFunc <- func
+ Table.useEnt <- ent
+
+ if ( !( button in file.switchCallbacks ) )
+ file.switchCallbacks[ button ] <- []
+ file.switchCallbacks[ button ].append( Table )
+}
+
+
+void function FanPusherThink( entity fanPusher )
+{
+ float fanPushDist = 3000
+
+ if ( fanPusher.HasKey( "height" ) )
+ fanPushDist = float( fanPusher.kv.height )
+
+ if ( fanPusher.HasKey( "script_gravityscale" ) && fanPusher.kv.script_gravityscale != "" )
+ {
+ fanPushDist *= string( fanPusher.kv.script_gravityscale ).tofloat()
+ }
+ else
+ {
+ float gravityScale = expect float( GetPlayerSettingsFieldForClassName( DEFAULT_PILOT_SETTINGS, "gravityscale" ) )
+ fanPushDist *= gravityScale // adjusted for new gravity scale
+ }
+
+ float radius = float( fanPusher.kv.script_radius )
+ vector forward = AnglesToForward( fanPusher.GetAngles() )
+ vector cylinderBottom = fanPusher.GetOrigin()
+ vector cylinderTop = cylinderBottom + ( forward * fanPushDist )
+
+ if ( FAN_DEBUG )
+ DebugDrawCylinder( fanPusher.GetOrigin(), fanPusher.GetAngles(), radius, fanPushDist, 100, 0, 0, true, 120.0 )
+
+ string flag = ""
+ if ( fanPusher.HasKey( "script_flag" ) )
+ {
+ flag = string( fanPusher.kv.script_flag )
+ FlagInit( flag )
+ }
+
+ bool lifterFan = DotProduct( forward, <0,0,1> ) >= 0.98
+ float pushAccel = FAN_DEFAULT_PUSH_ACCEL
+ if ( fanPusher.HasKey( "strength" ) )
+ pushAccel = float( fanPusher.kv.strength )
+
+ array<entity> fanPushables
+ table<entity,float> startTimes
+ table<entity,float> startHeights
+
+ // Play fan sound on entity instead of at position because some occluder bug with miles. Easiest fix for late in game. Next project fan pusher shoudln't be info_target that you can't play sounds on
+ entity fanSoundEntity = CreateScriptMover( fanPusher.GetOrigin() )
+ fanSoundEntity.DisableHibernation()
+
+ // Delay to fix code bug where audio wont play at map load
+ wait 0.2
+
+ FanOnSoundEffects( fanPusher, radius, fanSoundEntity )
+
+ while( true )
+ {
+ if ( flag != "" && !Flag( flag ) )
+ {
+ foreach( entity ent, float time in startTimes )
+ ent.e.inWindTunnel = false
+
+ startTimes.clear()
+ startHeights.clear()
+
+ FanOffSoundEffects( fanPusher, radius, fanSoundEntity )
+ FlagWait( flag )
+ FanOnSoundEffects( fanPusher, radius, fanSoundEntity )
+ }
+
+ fanPushables.clear()
+ fanPushables.extend( GetPlayerArray() )
+ fanPushables.extend( GetNPCArray() )
+ fanPushables.extend( GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, cylinderBottom, fanPushDist ) )
+ fanPushables.extend( GetEntArrayByClass_Expensive( "prop_physics" ) )
+
+ foreach( entity ent in fanPushables )
+ {
+ array<vector> testPosArray = [ ent.GetWorldSpaceCenter(), ent.EyePosition() + <0,0,16>, ent.GetOrigin() - <0,0,16> ]
+ bool isInFanCylinder = false
+ vector testPos = ent.GetWorldSpaceCenter()
+ if ( ent.e.windPushEnabled )
+ {
+ foreach( vector pos in testPosArray )
+ {
+ if ( !PointInCylinder( cylinderBottom, cylinderTop, radius, pos ) )
+ continue
+ isInFanCylinder = true
+ break
+ }
+ }
+
+ if ( isInFanCylinder )
+ {
+ if ( ent.IsPlayer() && ent.IsNoclipping() )
+ continue
+
+ if ( !( ent in startTimes ) )
+ {
+ ent.e.inWindTunnel = true
+ ent.e.windTunnelDirection = forward
+ if ( ent.IsPlayer() )
+ thread PlayerInWindTunnel( ent )
+ else
+ ent.SetOrigin( ent.GetOrigin() + <0,0,48> )
+ startTimes[ ent ] <- Time()
+ }
+ float startTime = startTimes[ ent ]
+ ent.e.windTunnelStartTime = startTime
+
+ float startHeight
+ if ( lifterFan )
+ {
+ if ( !( ent in startHeights ) )
+ startHeights[ ent ] <- ent.GetOrigin().z
+ startHeight = startHeights[ ent ]
+ }
+
+ // Figure out what force should be based on proximity to fan
+ vector pointAlongTunnel = GetClosestPointOnLineSegment( cylinderBottom, cylinderTop, testPos )
+ float distanceFromFanAlongTunnel = Distance( pointAlongTunnel, cylinderBottom )
+
+ float fanStrength = GetFanStrengthWithGeoBlockage( ent, testPos, cylinderBottom, cylinderTop, forward )
+ if ( ent.IsPlayer() )
+ ent.SetPlayerNetFloatOverTime( "FanRumbleStrength", fanStrength, 0.0 )
+
+ if ( lifterFan )
+ fanStrength = GraphCapped( distanceFromFanAlongTunnel, 0.0, fanPushDist, 1.0, 0.0 )
+
+ if ( ent.IsProjectile() )
+ fanStrength *= 2.0
+
+ if ( ent.GetModelName() == $"models/containers/barrel.mdl" )
+ fanStrength = 0.0
+
+ // Ramp up the push over time when first entering
+ float ramp = GraphCapped( Time(), startTime, startTime + FAN_PUSH_RAMP_TIME, 0.0, 1.0 )
+
+ float dt = 0.01666667 // old behavior
+ vector velocity = ent.GetVelocity()
+
+ // Apply push to the velocity
+ velocity += forward * ( dt * pushAccel * fanStrength )
+
+ // Decay other directional movement on the vector
+ if ( FAN_PUSH_DECAY_SIDE_VELOCITY )
+ {
+ vector velInOtherDirs = velocity - forward * DotProduct( velocity, forward )
+ float decayFrac = pow( ramp * fanStrength, dt )
+ vector loseVelInOtherDirs = velInOtherDirs * (1 - decayFrac)
+ velocity -= loseVelInOtherDirs
+ }
+
+ // Add some anti-gravity
+ velocity.z += dt * FAN_PUSH_ANTI_GRAVITY * fanStrength
+ ent.e.windTunnelStrength = fanStrength
+
+ // Apply new force to ent
+ ent.SetVelocity( velocity )
+
+ // Hack for drones. You can't set velocity on them yet so I'm doing this for now to test the gameplay
+ if ( ent.GetClassName() == "npc_drone" )
+ {
+ float zChange = dt * pushAccel * fanStrength * 1
+ ent.SetOrigin( ent.GetOrigin() + < 0, 0, zChange > )
+ }
+ }
+ else
+ {
+ if ( ent in startTimes )
+ {
+ ent.e.inWindTunnel = false
+ delete startTimes[ ent ]
+ }
+
+ if ( lifterFan && ent in startHeights )
+ {
+ delete startHeights[ ent ]
+ }
+
+ if ( ent.IsPlayer() )
+ ent.SetPlayerNetFloatOverTime( "FanRumbleStrength", 0.0, 1.0 )
+ }
+ }
+ WaitFrame()
+ }
+}
+
+void function PlayerInWindTunnel( entity player )
+{
+ //int poseIndex = player.LookupPoseParameterIndex( "windfrac" )
+
+ EndSignal( player, "OnDestroy" )
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( FAN_DEBUG )
+ printt( "OUT!" )
+ if ( IsValid( player ) )
+ {
+ player.SetOneHandedWeaponUsageOff()
+ FadeOutSoundOnEntity( player, "Beacon_WindBuffet_Player", 1.0 )
+
+ player.kv.airSpeed = player.GetPlayerSettingsField( "airSpeed" )
+ player.kv.airAcceleration = player.GetPlayerSettingsField( "airAcceleration" )
+ }
+ }
+ )
+
+ if ( FAN_DEBUG )
+ printt( "IN!" )
+
+ bool playingWindBuffet = false
+
+ player.kv.airSpeed = 150
+ player.kv.airAcceleration = 650
+
+ while( player.e.inWindTunnel )
+ {
+ //player.GetViewModelEntity().SetPoseParameter( poseIndex, player.e.windTunnelStrength )
+
+ if ( player.e.windTunnelStrength > 0 )
+ {
+ player.SetOneHandedWeaponUsageOn()
+ if ( !playingWindBuffet )
+ {
+ EmitSoundOnEntityOnlyToPlayerWithFadeIn( player, player, "Beacon_WindBuffet_Player", 1.0 )
+ playingWindBuffet = true
+ }
+ }
+ else
+ {
+ player.SetOneHandedWeaponUsageOff()
+ if ( playingWindBuffet )
+ {
+ FadeOutSoundOnEntity( player, "Beacon_WindBuffet_Player", 1.0 )
+ playingWindBuffet = false
+ }
+ }
+ WaitFrame()
+ }
+}
+
+float function GetFanStrengthWithGeoBlockage( entity ent, vector testPos, vector cylinderBottom, vector cylinderTop, vector fanDirection )
+{
+ vector pointAlongFan = GetClosestPointOnLineSegment( cylinderBottom, cylinderTop, testPos )
+ vector vecFromFanCenter = testPos - pointAlongFan
+ vector traceEnd = cylinderBottom + vecFromFanCenter
+
+ // Trace from the entity towards the fan along the fan axis to see if we are getting blocked
+ TraceResults result = TraceLine( testPos, traceEnd, ent, TRACE_MASK_NPCSOLID, TRACE_COLLISION_GROUP_NONE )
+ if ( FAN_DEBUG )
+ {
+ DebugDrawLine( testPos, result.endPos, 255, 255, 0, true, 0.1 )
+ DebugDrawLine( result.endPos, traceEnd, 255, 255, 255, true, 0.1 )
+ //DebugDrawLine( <0,0,0>, result.endPos, 255, 0, 0, true, 0.1 )
+ }
+
+ float distFromCover = Distance( testPos, result.endPos )
+ float strength = GraphCapped( distFromCover, 256, 1024, 0.0, 1.0 )
+ if ( result.fraction == 1.0 )
+ strength = 1.0
+
+ //if ( FAN_DEBUG )
+ //{
+ // printt( "strength:", strength )
+ // printt( "fraction:", result.fraction )
+ // printt( "dist from fan:", Distance( testPos, cylinderBottom ) )
+ // printt( "distFromCover:", distFromCover )
+ //}
+
+ Assert( strength >= 0.0 && strength <= 1.0 )
+ return strength
+}
+
+void function FanOnSoundEffects( entity fanPusher, float radius, entity fanSoundEntity )
+{
+ // Turn on sound
+ if ( radius >= 350 )
+ {
+ EmitSoundOnEntity( fanSoundEntity, "Beacon_VerticalFanControl_On" )
+ if ( fanPusher.HasKey( "fan_loop_sound" ) )
+ EmitSoundOnEntity( fanSoundEntity, string( fanPusher.kv.fan_loop_sound ) )
+
+ }
+ else
+ {
+ EmitSoundOnEntity( fanSoundEntity, "Beacon_MediumBlueFan_On" )
+ string loopAlias = fanPusher.HasKey( "fan_loop_sound" ) ? string( fanPusher.kv.fan_loop_sound ) : "Beacon_MediumBlueFan_Loop_01"
+ EmitSoundOnEntity( fanSoundEntity, loopAlias )
+ //DebugDrawText( fanPusher.GetOrigin(), loopAlias, true, 90.0 )
+ }
+}
+
+void function FanOffSoundEffects( entity fanPusher, float radius, entity fanSoundEntity )
+{
+ // Turn off sound
+ if ( radius >= 350 )
+ {
+ string alias = "Beacon_VerticalFanControl_Off"
+ if ( fanPusher.HasKey( "shutoff_sound" ) )
+ alias = string( fanPusher.kv.shutoff_sound )
+ EmitSoundOnEntity( fanSoundEntity, alias )
+
+ if ( fanPusher.HasKey( "fan_loop_sound" ) )
+ StopSoundOnEntity( fanSoundEntity, string( fanPusher.kv.fan_loop_sound ) )
+ }
+ else
+ {
+ EmitSoundOnEntity( fanSoundEntity, "Beacon_MediumBlueFan_Off" )
+ string loopAlias = fanPusher.HasKey( "fan_loop_sound" ) ? string( fanPusher.kv.fan_loop_sound ) : "Beacon_MediumBlueFan_Loop_01"
+ StopSoundOnEntity( fanSoundEntity, loopAlias )
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_script_movers_light.gnut b/Northstar.CustomServers/scripts/vscripts/_script_movers_light.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_script_movers_light.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_script_triggers.gnut b/Northstar.CustomServers/scripts/vscripts/_script_triggers.gnut
new file mode 100644
index 000000000..c5e026b3c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_script_triggers.gnut
@@ -0,0 +1,318 @@
+
+untyped
+
+globalize_all_functions
+
+// flags defaults to player only
+entity function CreateTriggerRadiusMultiple( vector origin, float radius, array<entity> ents = [], int flags = TRIG_FLAG_PLAYERONLY, float top = 16384.0, float bottom = -16384.0 )
+{
+ return _CreateScriptCylinderTriggerInternal( origin, radius, flags, ents, top, bottom )
+}
+
+// flags defaults to player only
+entity function CreateTriggerRadiusOnce( vector origin, float radius, array<entity> ents = [], int flags = TRIG_FLAG_PLAYERONLY )
+{
+ return _CreateScriptCylinderTriggerInternal( origin, radius, flags | TRIG_FLAG_ONCE, ents, null, null )
+}
+
+entity function CreateScriptCylinderTrigger( vector origin, float radius, float ornull top = null, float ornull bottom = null )
+{
+ return _CreateScriptCylinderTriggerInternal( origin, radius, TRIG_FLAG_PLAYERONLY | TRIG_FLAG_START_DISABLED, [], top, bottom )
+}
+
+entity function _CreateScriptCylinderTriggerInternal( vector origin, float radius, int flags, array<entity> ents, float ornull top, float ornull bottom )
+{
+ entity trigger = CreateScriptRef( origin, < 0.0, 0.0, 0.0 > )
+
+
+ if ( flags & TRIG_FLAG_START_DISABLED )
+ trigger.e.scriptTriggerData.enabled = false
+ else
+ trigger.e.scriptTriggerData.enabled = true
+ trigger.e.scriptTriggerData.radius = radius
+
+ trigger.e.scriptTriggerData.top = 16384.0
+
+ if ( top != null )
+ trigger.e.scriptTriggerData.top = origin.z + expect float( top )
+
+ trigger.e.scriptTriggerData.bottom = -16384.0
+ if ( bottom != null )
+ trigger.e.scriptTriggerData.bottom = origin.z + expect float( bottom )
+
+ trigger.e.scriptTriggerData.flags = flags
+ trigger.e.scriptTriggerData.managedEntArrayHandle = -1
+
+ if ( ents.len() )
+ {
+ trigger.e.scriptTriggerData.flags = (flags | TRIG_FLAG_EXCLUSIVE)
+ trigger.e.scriptTriggerData.managedEntArrayHandle = CreateScriptManagedEntArray()
+
+ foreach ( ent in ents )
+ AddToScriptManagedEntArray( trigger.e.scriptTriggerData.managedEntArrayHandle, ent )
+ }
+
+ if ( flags & TRIG_FLAG_DEVDRAW )
+ DebugDrawTrigger( origin, radius, RandomInt( 255 ), RandomInt( 255 ), RandomInt( 255 ) )
+
+ thread CylinderTriggerThink( trigger )
+
+ return trigger
+}
+
+void function ScriptTriggerSetEnabled( entity trigger, bool state )
+{
+ trigger.e.scriptTriggerData.enabled = state
+}
+
+void function CylinderTriggerThink( entity triggerEnt )
+{
+ //Ensures that any callbacks the user sets are in place when the user spawns.
+ WaitFrame()
+
+ bool wasEnabled = triggerEnt.e.scriptTriggerData.enabled
+ int flags = triggerEnt.e.scriptTriggerData.flags
+
+ while ( IsValid( triggerEnt ) )
+ {
+ if ( !triggerEnt.e.scriptTriggerData.enabled )
+ {
+ if ( wasEnabled )
+ {
+ array<entity> entitiesToRemove // build an array since looping through a table and removing elements is undefined
+ foreach( ent in triggerEnt.e.scriptTriggerData.entities )
+ {
+ entitiesToRemove.append( ent )
+ }
+
+ foreach ( ent in entitiesToRemove )
+ {
+ ScriptTriggerRemoveEntity( triggerEnt, ent )
+ }
+
+ Assert( !triggerEnt.e.scriptTriggerData.entities.len() )
+ }
+ }
+ else
+ {
+ array<entity> entities
+ if ( flags & TRIG_FLAG_EXCLUSIVE )
+ {
+ entities = GetScriptManagedEntArray( triggerEnt.e.scriptTriggerData.managedEntArrayHandle )
+ // all of the entites from this array are gone, this trigger is of no use
+ if ( !entities.len() )
+ {
+ triggerEnt.Kill_Deprecated_UseDestroyInstead()
+ return
+ }
+ }
+ else if ( flags & TRIG_FLAG_PLAYERONLY )
+ {
+ entities = GetPlayerArray()
+ }
+ else if ( flags & TRIG_FLAG_NPCONLY )
+ {
+ entities = GetNPCArray()
+ }
+ else
+ {
+ entities = GetPlayerArray()
+ entities.extend( GetNPCArray() )
+ entities.extend( GetPlayerDecoyArray() )
+ }
+
+ foreach ( ent in entities )
+ {
+ if ( !IsAlive( ent ) )
+ {
+ if ( ent in triggerEnt.e.scriptTriggerData.entities )
+ ScriptTriggerRemoveEntity( triggerEnt, ent )
+ continue
+ }
+
+ if ( ent.IsPlayer() && ent.IsPhaseShifted() && (flags & TRIG_FLAG_NO_PHASE_SHIFT) )
+ {
+ if ( ent in triggerEnt.e.scriptTriggerData.entities )
+ ScriptTriggerRemoveEntity( triggerEnt, ent )
+ continue
+ }
+
+ vector entityOrg = ent.GetOrigin()
+
+ if ( Distance2D( entityOrg, triggerEnt.GetOrigin() ) < triggerEnt.e.scriptTriggerData.radius )
+ {
+ if ( entityOrg.z > triggerEnt.e.scriptTriggerData.top )
+ continue
+
+ if ( entityOrg.z + 72.0 < triggerEnt.e.scriptTriggerData.bottom ) //72 is magic number for height of players. Should account for height of NPCs
+ continue
+
+ if ( (flags & TRIG_FLAG_NOCONTEXTBUSY) && !ent.IsPlayerDecoy() && ent.ContextAction_IsBusy() ) //This should probably be ContextAction_IsActive()
+ continue
+
+ if ( !(ent in triggerEnt.e.scriptTriggerData.entities) )
+ {
+ ScriptTriggerAddEntity( triggerEnt, ent )
+ if ( flags & TRIG_FLAG_ONCE )
+ {
+ WaitEndFrame()
+ triggerEnt.Kill_Deprecated_UseDestroyInstead()
+ return
+ }
+ }
+ }
+ else if ( ent in triggerEnt.e.scriptTriggerData.entities )
+ {
+ ScriptTriggerRemoveEntity( triggerEnt, ent )
+ }
+ }
+ }
+
+ wasEnabled = triggerEnt.e.scriptTriggerData.enabled
+ WaitFrame()
+ }
+}
+
+void function ScriptTriggerRemoveEntity( entity triggerEnt, entity ent )
+{
+ Assert( ent in triggerEnt.e.scriptTriggerData.entities )
+
+ foreach ( callbackFunc in triggerEnt.e.scriptTriggerData.leaveCallbacks )
+ {
+ callbackFunc( triggerEnt, ent )
+ }
+
+ delete triggerEnt.e.scriptTriggerData.entities[ent]
+}
+
+void function ScriptTriggerAddEntity( entity triggerEnt, entity ent )
+{
+ Assert( !(ent in triggerEnt.e.scriptTriggerData.entities) )
+
+ triggerEnt.e.scriptTriggerData.entities[ent] <- ent
+
+ foreach ( callbackFunc in triggerEnt.e.scriptTriggerData.enterCallbacks )
+ {
+ callbackFunc( triggerEnt, ent )
+ }
+
+ triggerEnt.Signal( TRIGGER_INTERNAL_SIGNAL )
+
+ thread ScriptTriggerPlayerDisconnectThink( triggerEnt, ent )
+}
+
+void function ScriptTriggerPlayerDisconnectThink( entity triggerEnt, entity ent )
+{
+ triggerEnt.EndSignal( "OnDestroy" )
+ ent.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( triggerEnt, ent )
+ {
+ if ( !IsValid( ent ) )
+ return
+
+ if ( ent in triggerEnt.e.scriptTriggerData.entities )
+ ScriptTriggerRemoveEntity( triggerEnt, ent )
+ }
+ )
+
+ ent.WaitSignal( "OnDestroy" )
+}
+
+array<entity> function GetAllEntitiesInTrigger( entity trigger )
+{
+ array<entity> ents
+ foreach ( ent in trigger.e.scriptTriggerData.entities )
+ {
+ ents.append( ent )
+ }
+
+ return ents
+}
+
+void function AddCallback_ScriptTriggerEnter( entity trigger, void functionref( entity, entity ) callbackFunc )
+{
+ trigger.e.scriptTriggerData.enterCallbacks.append( callbackFunc )
+}
+
+void function AddCallback_ScriptTriggerLeave( entity trigger, void functionref( entity, entity ) callbackFunc )
+{
+ trigger.e.scriptTriggerData.leaveCallbacks.append( callbackFunc )
+}
+
+/*
+void function thing()
+{
+ RegisterSignal( "TriggerGetOutThink" )
+
+ array<entity> targets = GetEntArrayByScriptName( "fling_target" )
+
+ foreach ( target in targets )
+ {
+ entity trigger = CreateEntity( "trigger_cylinder" )
+ trigger.SetRadius( 128 )
+ trigger.SetAboveHeight( 64 ) //Still not quite a sphere, will see if close enough
+ trigger.SetBelowHeight( 32 )
+ trigger.SetOrigin( target.GetOrigin() )
+ trigger.ConnectOutput( "OnStartTouch", TriggerGetOutStartTouch )
+ DispatchSpawn( trigger )
+ }
+}
+*/
+void function TriggerGetOutStartTouch( entity trigger, entity ent, entity caller, var value )
+{
+ if ( !ent.IsPlayer() )
+ return
+
+ if ( ent.IsTitan() )
+ return
+
+ if ( !ent.IsAlive() )
+ return
+
+ thread TriggerGetOutThink( trigger, ent )
+}
+
+void function TriggerGetOutThink( entity trigger, entity player )
+{
+ const float FALL_OFF_SPEED_MIN = 100
+ const float FALL_OFF_SPEED_MAX = 300
+ const float FALL_OFF_ACCEL = 300
+ const float FALL_OFF_INTERVAL = 0.1
+
+ while ( IsAlive( player ) && trigger.IsTouching( player ) )
+ {
+ if ( player.IsOnGround() )
+ {
+ vector vel = player.GetVelocity()
+
+ float len = vel.Length2D()
+ while ( len < 1.0 )
+ {
+ vel.x = RandomFloatRange( -100, 100 )
+ vel.y = RandomFloatRange( -100, 100 )
+ len = vel.Length2D()
+ }
+
+ if ( len < FALL_OFF_SPEED_MIN )
+ {
+ float scale = FALL_OFF_SPEED_MIN / len
+ vel.x *= scale
+ vel.y *= scale
+ }
+ else if ( len < FALL_OFF_SPEED_MAX )
+ {
+ float newlen = len + FALL_OFF_INTERVAL * FALL_OFF_ACCEL
+ newlen = min( newlen, FALL_OFF_SPEED_MAX )
+ float scale = newlen / len
+ vel.x *= scale
+ vel.y *= scale
+ }
+
+ player.SetVelocity( vel )
+ }
+
+ WaitFrame()
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_side_notifications.gnut b/Northstar.CustomServers/scripts/vscripts/_side_notifications.gnut
new file mode 100644
index 000000000..2b3d39931
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_side_notifications.gnut
@@ -0,0 +1,6 @@
+global function PROTO_PlayLoadoutNotification
+
+void function PROTO_PlayLoadoutNotification(string weapon, entity player)
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_store.gnut b/Northstar.CustomServers/scripts/vscripts/_store.gnut
new file mode 100644
index 000000000..5ebf090ab
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_store.gnut
@@ -0,0 +1,38 @@
+//todo some of the stuff here should probably get moved to other scripts
+
+global function PIN_BuyItemWithRealMoney
+global function PIN_BuyItem
+global function PIN_GiveItem
+global function PIN_GiveCredits
+global function PIN_ConsumeItem
+global function PIN_AddToPlayerCountStat
+
+void function PIN_BuyItemWithRealMoney(entity player, bool _0, string name, int cost)
+{
+
+}
+
+void function PIN_BuyItem(entity player, bool _0, string name, int cost)
+{
+
+}
+
+void function PIN_GiveItem(entity player, bool _0, string name, int count)
+{
+
+}
+
+void function PIN_GiveCredits(entity player, int count)
+{
+
+}
+
+void function PIN_ConsumeItem(entity player, string name)
+{
+
+}
+
+void function PIN_AddToPlayerCountStat(entity player, string name)
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_trigger_functions.gnut b/Northstar.CustomServers/scripts/vscripts/_trigger_functions.gnut
new file mode 100644
index 000000000..0f82d9a6b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_trigger_functions.gnut
@@ -0,0 +1,585 @@
+untyped
+
+global function TriggerFunctions_Init
+
+global function InitFlagMaskTriggers
+global function TriggerInit
+global function AddToFlagTriggers
+global function AddTriggerEditorClassFunc
+global function UpdateTriggerStatusFromFlagChange
+
+const DEBUG_DEADLY_FOG = false
+const asset BIRD_ALERT_FX = $"P_bird_alert_white"
+struct BirdAlertInfo
+{
+ entity scriptRef //Also FX location
+ array<entity> triggers
+ float lastUseTime
+}
+
+struct
+{
+ //bool checkpointReached
+ //vector checkpointOrigin
+ //vector checkpointAngles
+ table<string, array<void functionref( entity )> > triggerEditorClassFunctions
+
+ table<string, int> flagTriggerEntArrayIndex
+ array<BirdAlertInfo> birdAlerts
+} file
+
+function TriggerFunctions_Init()
+{
+ AddCallback_EntitiesDidLoad( InitFlagMaskTriggers )
+
+ level._flagTriggers <- {} // triggers that can be enabled/disabled via flag
+
+ AddTriggerEditorClassFunc( "trigger_flag_set", TriggerSetFlagOnTrigger )
+ AddTriggerEditorClassFunc( "trigger_flag_clear", TriggerClearFlagOnTrigger )
+ AddTriggerEditorClassFunc( "trigger_flag_touching", TriggerTouchingFlagOnTrigger )
+
+ AddSpawnCallback( "trigger_multiple", TriggerInit )
+ AddSpawnCallback( "trigger_once", TriggerInit )
+ AddSpawnCallback( "trigger_hurt", TriggerInit )
+
+ AddTriggerEditorClassFunc( "trigger_death_fall", TriggerDeathFall )
+ AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_deadly_fog", DeadlyFogTriggerInit )
+
+ AddSpawnCallbackEditorClass( "script_ref", "script_bird_alert", BirdAlertInit )
+
+ PrecacheParticleSystem( BIRD_ALERT_FX )
+
+ RegisterSignal( "OutOfDeadlyFog" )
+
+ PrecacheParticleSystem( $"Swtch_Elec_hzd_rope_end" )
+}
+
+void function AddTriggerEditorClassFunc( string editorClass, void functionref( entity ) triggerFunc )
+{
+ if ( !( editorClass in file.triggerEditorClassFunctions ) )
+ file.triggerEditorClassFunctions[ editorClass ] <- []
+
+ file.triggerEditorClassFunctions[ editorClass ].append( triggerFunc )
+}
+
+void function AddKeyPairFunctionality( entity trigger )
+{
+ table< string, void functionref( entity, string )> funcs
+ funcs[ "scr_flagSet" ] <- TriggerFlagSet
+ funcs[ "scr_flagClear" ] <- TriggerFlagClear
+
+ foreach ( key, func in funcs )
+ {
+ if ( trigger.HasKey( key ) )
+ {
+ thread func( trigger, expect string( trigger.kv[ key ] ) )
+ }
+ }
+}
+
+function AddToFlagTriggers( entity self )
+{
+ level._flagTriggers[ self ] <- self
+}
+
+function GetFlagTriggers()
+{
+ foreach ( entity guy in clone level._flagTriggers )
+ {
+ if ( IsValid_ThisFrame( guy ) )
+ continue
+
+ delete level._flagTriggers[ guy ]
+ }
+
+ return level._flagTriggers
+}
+
+
+function AddKeyPairFunctionToClass( funcs, classname )
+{
+ array<entity> triggers = GetEntArrayByClass_Expensive( classname )
+
+ foreach ( trigger in triggers )
+ {
+ foreach ( key, func in funcs )
+ {
+ if ( trigger.HasKey( key ) )
+ {
+ thread func( trigger, trigger.kv[ key ] )
+ }
+ }
+ }
+}
+
+void function TriggerChangesFlagOnTrigger( entity trigger, string flag, void functionref( string ) func )
+{
+ trigger.EndSignal( "OnDestroy" )
+
+ array<string> flags = GetFlagsFromString( flag )
+
+ for ( ;; )
+ {
+ trigger.WaitSignal( "OnTrigger" )
+
+ foreach ( flag in flags )
+ {
+ func( flag )
+ }
+ }
+}
+
+void function TriggerFlagSet( entity trigger, string flagString )
+{
+ thread TriggerChangesFlagOnTrigger( trigger, flagString, FlagSet )
+}
+
+void function TriggerFlagClear( entity trigger, string flagString )
+{
+ thread TriggerChangesFlagOnTrigger( trigger, flagString, FlagClear )
+}
+
+void function TriggerInit( entity trigger )
+{
+ if ( trigger.HasKey( "editorclass" ) )
+ RunTriggerEditorClassFunctions( trigger )
+
+ InitFlagsFromTrigger( trigger )
+ AddKeyPairFunctionality( trigger )
+ AddToFlagTriggers( trigger )
+}
+
+function RunTriggerEditorClassFunctions( entity trigger )
+{
+ string editorClass = expect string( trigger.kv.editorclass )
+ if ( !( editorClass in file.triggerEditorClassFunctions ) )
+ return
+
+ foreach ( func in file.triggerEditorClassFunctions[ editorClass ] )
+ {
+ thread func( trigger )
+ }
+}
+
+void function TriggerSetFlagOnTrigger( entity trigger )
+{
+ trigger.EndSignal( "OnDestroy" )
+
+ string flag
+ if ( trigger.HasKey( "script_flag" ) )
+ flag = expect string( trigger.kv.script_flag )
+ else if ( trigger.HasKey( "scr_flagSet" ) )
+ flag = expect string( trigger.kv.scr_flagSet )
+
+ bool triggerOnce = trigger.HasKey( "trigger_once" ) && trigger.kv.trigger_once == "1"
+
+ Assert( flag != "", "Trigger " + GetEditorClass( trigger ) + " at " + trigger.GetOrigin() + "has empty flag value" )
+
+ while ( true )
+ {
+ trigger.WaitSignal( "OnTrigger" )
+ FlagSet( flag )
+
+ if ( triggerOnce )
+ return
+
+ FlagWaitClear( flag )
+ }
+}
+
+void function TriggerClearFlagOnTrigger( entity trigger )
+{
+ string flag
+ if ( trigger.HasKey( "script_flag" ) )
+ flag = expect string( trigger.kv.script_flag )
+ else if ( trigger.HasKey( "scr_flagClear" ) )
+ flag = expect string( trigger.kv.scr_flagClear )
+
+ Assert( flag != "" )
+
+ thread TriggerFlagClear( trigger, flag )
+}
+
+void function TriggerTouchingFlagOnTrigger( entity trigger )
+{
+ trigger.EndSignal( "OnDestroy" )
+ string flag = expect string( trigger.kv.script_flag )
+ Assert( flag != "" )
+
+ while ( true )
+ {
+ if ( !trigger.IsTouched() )
+ trigger.WaitSignal( "OnStartTouch" )
+
+ FlagSet( flag )
+
+ if ( trigger.IsTouched() )
+ trigger.WaitSignal( "OnEndTouchAll" )
+
+ FlagClear( flag )
+ }
+}
+
+array<string> function GetFlagRelatedKeys()
+{
+ array<string> check
+ check.append( "scr_flagTrueAll" )
+ check.append( "scr_flagTrueAny" )
+ check.append( "scr_flagFalseAll" )
+ check.append( "scr_flagFalseAny" )
+ check.append( "scr_flag" )
+ check.append( "script_flag" )
+ check.append( "scr_flagSet" )
+ check.append( "scr_flagClear" )
+
+ return check
+}
+
+void function InitFlagMaskTriggers()
+{
+ local triggers = GetFlagTriggers()
+ array<string> check = GetFlagRelatedKeys()
+ array<string> flags
+ local allTriggersWithFlags = {}
+
+ foreach ( trigger in triggers )
+ {
+ if ( trigger.HasKey( "scr_flagTrueAll" ) )
+ {
+ Assert( !trigger.HasKey( "scr_flagTrueAny" ), "Trigger at " + trigger.GetOrigin() + " has flag all and flag any" )
+ }
+ else
+ if ( trigger.HasKey( "scr_flagTrueAny" ) )
+ {
+ Assert( !trigger.HasKey( "scr_flagTrueAll" ), "Trigger at " + trigger.GetOrigin() + " has flag all and flag any" )
+ }
+
+ if ( trigger.HasKey( "scr_flagFalseAll" ) )
+ {
+ Assert( !trigger.HasKey( "scr_flagFalseAny" ), "Trigger at " + trigger.GetOrigin() + " has flag all and flag any" )
+ }
+ else
+ if ( trigger.HasKey( "scr_flagFalseAny" ) )
+ {
+ Assert( !trigger.HasKey( "scr_flagFalseAll" ), "Trigger at " + trigger.GetOrigin() + " has flag all and flag any" )
+ }
+
+ foreach ( field in check )
+ {
+ if ( trigger.HasKey( field ) )
+ {
+ allTriggersWithFlags[ trigger ] <- true
+ flags = GetFlagsFromField( trigger, field )
+
+ foreach ( flag in flags )
+ {
+ if ( !( flag in file.flagTriggerEntArrayIndex ) )
+ file.flagTriggerEntArrayIndex[ flag ] <- CreateScriptManagedEntArray()
+
+ AddToScriptManagedEntArray( file.flagTriggerEntArrayIndex[ flag ], trigger )
+
+ // init the flag so these flags an be used in hammer more easily
+ FlagInit( flag )
+ }
+ }
+ }
+ }
+
+ foreach ( trigger, _ in allTriggersWithFlags )
+ {
+ expect entity( trigger )
+ SetTriggerEnableFromFlag( trigger )
+ }
+}
+
+void function SetTriggerEnableFromFlag( entity trigger )
+{
+ if ( GetTriggerEnabled( trigger ) )
+ trigger.Fire( "Enable" )
+ else
+ trigger.Fire( "Disable" )
+}
+
+void function UpdateTriggerStatusFromFlagChange( string flag )
+{
+ // enable or disable triggers based on flag settings
+ if ( !( flag in file.flagTriggerEntArrayIndex ) )
+ return
+
+ array<entity> triggers = GetScriptManagedEntArray( file.flagTriggerEntArrayIndex[ flag ] )
+ foreach ( trigger in triggers )
+ {
+ SetTriggerEnableFromFlag( trigger )
+ }
+}
+
+function InitFlagsFromTrigger( entity trigger )
+{
+ array<string> check = GetFlagRelatedKeys()
+ array<string> flags
+
+ foreach ( field in check )
+ {
+ if ( !trigger.HasKey( field ) )
+ continue
+ flags = GetFlagsFromField( trigger, field )
+
+ foreach ( flag in flags )
+ {
+ // init the flag so these flags an be used in hammer more easily
+ FlagInit( flag )
+ }
+ }
+}
+
+
+void function DeadlyFogTriggerInit( entity trigger )
+{
+ trigger.ConnectOutput( "OnStartTouch", DeadlyFogStartTouch )
+ trigger.ConnectOutput( "OnEndTouch", DeadlyFogEndTouch )
+
+ if ( trigger.HasKey( "electricEffect" ) && trigger.kv.electricEffect == "1" )
+ thread DeadlyFogVisuals( trigger )
+}
+
+void function DeadlyFogStartTouch( entity trigger, entity ent, entity caller, var value )
+{
+ thread DeadlyFogDamagedEntity( trigger, ent )
+}
+
+void function DeadlyFogDamagedEntity( entity trigger, entity ent )
+{
+ if ( !IsAlive( ent ) || !IsValid( trigger ) )
+ return
+
+ EndSignal( ent, "OutOfDeadlyFog" )
+ EndSignal( trigger, "OnDestroy" )
+ EndSignal( ent, "OnDeath" )
+
+ bool damagePilots = trigger.kv.damagePilots == "1"
+ bool damageTitans = trigger.kv.damageTitans == "1"
+ if ( !damagePilots && !damageTitans )
+ return
+
+ float tickTime = 0.5
+ float timeTillDeath = 4.0
+
+ entity worldSpawn = GetEnt( "worldspawn" )
+ while( true )
+ {
+ if ( !IsValid( ent ) )
+ {
+ wait 0.5
+ continue
+ }
+
+ if ( IsPilot( ent ) && !damagePilots )
+ {
+ wait 0.5
+ continue
+ }
+
+ if ( ent.IsTitan() && !damageTitans )
+ {
+ wait 0.5
+ continue
+ }
+
+ local isTitan = ent.IsTitan()
+ local damageOrigin = ent.GetOrigin() + ( isTitan ? < 0.0, 0.0, 0.0 > : < 0.0 , 0.0, -200.0 > )
+ damageOrigin += < RandomFloatRange( -300.0, 300.0 ), RandomFloatRange( -300.0, 300.0 ), RandomFloatRange( -100.0, 100.0 ) >
+ local scriptTypeMask = damageTypes.dissolve | DF_STOPS_TITAN_REGEN
+
+ local damageAmount = ( ent.GetMaxHealth() / ( timeTillDeath / tickTime ) )
+
+ ent.TakeDamage( damageAmount, worldSpawn, worldSpawn, { origin = damageOrigin, scriptType = scriptTypeMask, damageSourceId = eDamageSourceId.deadly_fog } )
+
+ if ( ent.IsPlayer() )
+ StatusEffect_AddTimed( ent, eStatusEffect.emp, 1.0, 1.0, 0.5 )
+
+ wait 0.5
+ }
+}
+
+void function DeadlyFogEndTouch( entity trigger, entity ent, entity caller, var value )
+{
+ if ( IsValid( ent ) )
+ Signal( ent, "OutOfDeadlyFog" )
+}
+
+void function DeadlyFogVisuals( entity trigger )
+{
+ wait 0.5
+
+ // Get the trigger bounds
+ vector triggerMins = trigger.GetBoundingMins()
+ vector triggerMaxs = trigger.GetBoundingMaxs()
+ vector triggerOrigin = trigger.GetOrigin()
+ if ( DEBUG_DEADLY_FOG )
+ {
+ DebugDrawBox( triggerOrigin, triggerMins, triggerMaxs, 255, 255, 0, 1, 60.0 )
+ DebugDrawSphere( triggerOrigin, 25.0, 255, 200, 0, true, 60.0 )
+ }
+
+ // Divide the trigger into smaller squares
+ vector triggerDimension = triggerMaxs - triggerMins
+
+ int segmentSizeX = int( max( triggerDimension.x / 2000, 1500 ) )
+ int segmentSizeY = int( max( triggerDimension.y / 2000, 1500 ) )
+ int segmentSizeZ = int( min( 300, triggerDimension.z ) )
+
+ vector segmentSize = Vector( segmentSizeX, segmentSizeY, segmentSizeZ )
+ vector segmentCount = Vector( triggerDimension.x / segmentSize.x, triggerDimension.y / segmentSize.y, triggerDimension.z / segmentSize.z )
+
+ segmentCount.x = floor( segmentCount.x )
+ segmentCount.y = floor( segmentCount.y )
+ segmentCount.z = floor( segmentCount.z )
+ segmentCount.x = segmentCount.x < 1.0 ? 1.0 : segmentCount.x
+ segmentCount.y = segmentCount.y < 1.0 ? 1.0 : segmentCount.y
+ segmentCount.z = segmentCount.z < 1.0 ? 1.0 : segmentCount.z
+
+ vector startPos = triggerOrigin + triggerMins + segmentSize * 0.5
+ startPos.x += (triggerDimension.x - (segmentCount.x * segmentSize.x)) * 0.5
+ startPos.y += (triggerDimension.y - (segmentCount.y * segmentSize.y)) * 0.5
+ startPos.z += (triggerDimension.z - (segmentCount.z * segmentSize.z)) * 0.5
+
+ vector segmentPos = startPos
+ for ( int z = 0 ; z < segmentCount.z ; z++ )
+ {
+ // Only do effects on the top layer of the trigger
+ if ( z < ( segmentCount.z - 1 ) )
+ continue
+
+ for ( int y = 0 ; y < floor(segmentCount.y) ; y++ )
+ {
+ for ( int x = 0 ; x < floor(segmentCount.x) ; x++ )
+ {
+ vector segmentPos = startPos + Vector( segmentSize.x * x, segmentSize.y * y, segmentSize.z * z )
+ thread DeadlyFogEffect( segmentPos, segmentSize )
+ }
+ }
+ }
+}
+
+void function DeadlyFogEffect( vector origin, vector segmentSize )
+{
+ entity effect = CreateEntity( "info_particle_system" )
+ effect.SetValueForEffectNameKey( $"Swtch_Elec_hzd_rope_end" )
+ effect.kv.start_active = 0
+ effect.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ effect.SetOrigin( origin )
+ DispatchSpawn( effect )
+
+ vector mins = origin + (segmentSize * -0.5)
+ vector maxs = origin + (segmentSize * 0.5)
+ if ( DEBUG_DEADLY_FOG )
+ {
+ DebugDrawBox( origin, mins - origin, maxs - origin, 255, 255, 0, 1, 60.0 )
+ DebugDrawSphere( origin, 25.0, 255, 200, 0, true, 60.0 )
+ }
+
+ while( true )
+ {
+ wait RandomFloatRange( 2.1, 2.6 )
+
+ vector org = Vector( RandomFloatRange( mins.x, maxs.x ), RandomFloatRange( mins.y, maxs.y ), RandomFloatRange( mins.z, maxs.z ) )
+
+ if ( DEBUG_DEADLY_FOG )
+ DebugDrawLine( origin, org, 255, 0, 0, true, 2.0 )
+
+ effect.SetOrigin( org )
+ effect.Fire( "Start" )
+ effect.Fire( "StopPlayEndCap", "", 2.0 )
+ }
+}
+
+void function TriggerDeathFall( entity trigger )
+{
+ EndSignal( trigger, "OnDestroy" )
+
+ while ( true )
+ {
+ table results = trigger.WaitSignal( "OnTrigger" )
+ entity player = expect entity( results.activator )
+ if ( !IsValid( player ) || !player.IsPlayer() || !IsAlive( player ) )
+ continue
+
+ if ( player.IsGodMode() )
+ {
+ printt( "GOD MODE PLAYER CANT DIE" )
+ continue
+ }
+
+ if ( player.p.doingQuickDeath )
+ continue
+
+ if ( IsSingleplayer() )
+ FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost
+
+ player.EndSignal( "OnDeath" )
+
+ // Go to black and fade it out after a pause
+ float fadeTime = 0.5
+ float holdTime = 999
+ ScreenFade( player, 0, 1, 0, 255, fadeTime, holdTime, FFADE_OUT | FFADE_PURGE )
+
+ float deathTime = Time() + fadeTime
+ while ( Time() <= deathTime )
+ {
+ if ( player.IsOnGround() || player.IsWallRunning() || player.IsWallHanging() || player.p.doingQuickDeath )
+ break
+ WaitFrame()
+ }
+
+ if ( player.p.doingQuickDeath )
+ continue
+
+ if ( IsAlive( player ) )
+ {
+ KillPlayer( player, eDamageSourceId.fall )
+ return
+ }
+ }
+}
+
+
+void function BirdAlertInit( entity ref )
+{
+ BirdAlertInfo info
+ info.scriptRef = ref
+ array<entity> linkedEntities = ref.GetLinkEntArray()
+ foreach ( trigger in linkedEntities )
+ {
+ info.triggers.append( trigger )
+ trigger.ConnectOutput( "OnStartTouch", BirdAlertStartTouch )
+ }
+ file.birdAlerts.append( info )
+}
+
+void function BirdAlertStartTouch( entity trigger, entity ent, entity caller, var value )
+{
+ array<BirdAlertInfo> birdAlerts = GetBirdAlertInfoFromTrigger( trigger )
+ foreach( alert in birdAlerts )
+ {
+ float debounceTime = 6.0
+ if ( alert.lastUseTime + debounceTime > Time() )
+ return
+
+ StartParticleEffectInWorld( GetParticleSystemIndex( BIRD_ALERT_FX ), alert.scriptRef.GetOrigin(), alert.scriptRef.GetAngles() )
+ alert.lastUseTime = Time()
+ }
+}
+
+array<BirdAlertInfo> function GetBirdAlertInfoFromTrigger( entity trigger )
+{
+ array<BirdAlertInfo> birdAlerts
+ foreach ( infoStruct in file.birdAlerts )
+ {
+ if ( infoStruct.triggers.contains( trigger ) )
+ birdAlerts.append( infoStruct )
+ }
+
+ return birdAlerts
+ unreachable
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/_utility.gnut b/Northstar.CustomServers/scripts/vscripts/_utility.gnut
new file mode 100644
index 000000000..50851dae0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_utility.gnut
@@ -0,0 +1,4394 @@
+
+globalize_all_functions
+
+//=========================================================
+// _utility
+//
+//=========================================================
+
+int functionref( bool = false ) ornull te = null
+int EntTracker = 0
+
+global const C_PLAYFX_SINGLE = 0
+global const C_PLAYFX_MULTIPLE = 1
+global const C_PLAYFX_LOOP = 2
+
+global const MUTEALLFADEIN = 2
+
+global struct ZipLine
+{
+ entity start
+ entity mid
+ entity end
+}
+
+global int HUMAN_RAGDOLL_IMPACT_TABLE_IDX = -1
+
+global struct ArrayDotResultStruct
+{
+ entity ent
+ float dot
+}
+
+global struct ShieldDamageModifier
+{
+ float permanentDamageFrac = TITAN_SHIELD_PERMAMENT_DAMAGE_FRAC
+ bool normalizeShieldDamage = false
+ float damageScale = 1.0
+}
+
+
+struct
+{
+ bool isSkyboxView = false
+} file
+
+void function Utility_Init()
+{
+ te = TotalEnts
+ EntTracker = 0
+
+ #document( "SetDeathFuncName", "Sets the name of a function that runs when the NPC dies." )
+ #document( "CenterPrint", "Print to the screen" )
+ #document( "GetAllSoldiers", "Get all living soldiers." )
+ #document( "ClearDeathFuncName", "Clears the script death function." )
+
+ HUMAN_RAGDOLL_IMPACT_TABLE_IDX = PrecacheImpactEffectTable( "ragdoll_human" )
+
+ RegisterSignal( "PetTitanUpdated" )
+ RegisterSignal( "WaitDeadTimeOut" )
+ RegisterSignal( "InventoryChanged" )
+
+ AddClientCommandCallback( "OnDevnetBugScreenshot", ClientCommand_OnDevnetBugScreenshot )
+ #if DEV
+ FlagInit( "AimAssistSwitchTest_Enabled" )
+ AddClientCommandCallback( "DoomTitan", ClientCommand_DoomTitan )
+ #endif
+}
+
+void function GiveAllTitans()
+{
+
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ #if MP
+ GiveTitanToPlayer( player )
+ #endif
+
+ if ( player.IsTitan() )
+ {
+ entity soul = player.GetTitanSoul()
+ if ( soul )
+ {
+ SoulTitanCore_SetNextAvailableTime( soul, 1 )
+ }
+ }
+ }
+
+ array<entity> titans = GetNPCArrayByClass( "npc_titan" )
+ foreach ( titan in titans )
+ {
+ entity soul = titan.GetTitanSoul()
+ if ( soul )
+ SoulTitanCore_SetNextAvailableTime( soul, 1 )
+ }
+}
+
+#if DEV
+
+void function KillAllBadguys()
+{
+ array<entity> npcs = GetNPCArrayOfEnemies( GetPlayerArray()[0].GetTeam() )
+ foreach ( n in npcs )
+ {
+ if ( n.GetClassName() == "npc_bullseye" )
+ continue
+
+ if ( !IsAlive( n ) )
+ continue
+ n.Die()
+ }
+}
+
+bool function ClientCommand_DoomTitan( entity player, array<string> args )
+{
+ entity titan
+ if ( player.IsTitan() )
+ titan = player
+ else
+ titan = player.GetPetTitan()
+
+ if ( !IsAlive( titan ) )
+ return true
+
+ if ( GetDoomedState( titan ) )
+ return true
+
+ entity soul = titan.GetTitanSoul()
+ soul.SetShieldHealth( 0 )
+
+ titan.TakeDamage( titan.GetHealth(), null, null, { damageSourceId=damagedef_suicide, scriptType = DF_SKIP_DAMAGE_PROT } )
+
+ return true
+}
+#endif
+
+void function PrintPlaylists()
+{
+ printt( "=== PLAYLIST NAMES: ===" )
+
+ int count = GetPlaylistCount()
+ for ( int i = 0; i < count; i++ )
+ {
+ printt( "--", GetPlaylistName( i ) )
+ }
+}
+
+entity function CreateEntity( string name )
+{
+// if ( name == "npc_titan" )
+// {
+// DumpStack(3)
+// }
+// if ( name == "info_particle_system" )
+// {
+// printl( " " )
+// DumpStack(3)
+// }
+ return Entities_CreateByClassname( name )
+}
+
+// used from the console to quick-test fields
+void function setnpcfields( string key, var val )
+{
+ array<entity> npcs = GetNPCArrayByClass( "npc_soldier" )
+ foreach ( npc in npcs )
+ {
+ npc.kv[ key ] = val
+ }
+}
+
+void function WaitUntilNumDead( array<entity> guys, int numDead )
+{
+ Assert( numDead <= guys.len(), "asked for " + numDead + " guys to die, but only passed in an array of " + guys.len() + " guys." )
+ float timeout = -1
+ __WaitUntilDeadInternal( guys, numDead, timeout, __WaitUntilDeadTracker )
+}
+
+void function WaitUntilNumDeadWithTimeout( array<entity> guys, int numDead, float timeout )
+{
+ Assert( numDead <= guys.len(), "asked for " + numDead + " guys to die, but only passed in an array of " + guys.len() + " guys." )
+ Assert( timeout > 0 )
+ __WaitUntilDeadInternal( guys, numDead, timeout, __WaitUntilDeadTracker )
+}
+
+void function WaitUntilAllDead( array<entity> guys )
+{
+ int count = guys.len()
+ float timeout = -1
+ __WaitUntilDeadInternal( guys, count, timeout, __WaitUntilDeadTracker )
+}
+
+void function WaitUntilAllDeadWithTimeout( array<entity> guys, float timeout )
+{
+ int count = guys.len()
+ Assert( timeout > 0 )
+ __WaitUntilDeadInternal( guys, count, timeout, __WaitUntilDeadTracker )
+}
+
+void function WaitUntilNumDeadOrLeeched( array<entity> guys, int numDead )
+{
+ Assert( numDead <= guys.len(), "asked for " + numDead + " guys to die, but only passed in an array of " + guys.len() + " guys." )
+ float timeout = -1
+ __WaitUntilDeadInternal( guys, numDead, timeout, __WaitUntilDeadOrLeechedTracker )
+}
+
+void function WaitUntilNumDeadOrLeechedWithTimeout( array<entity> guys, int numDead, float timeout )
+{
+ Assert( numDead <= guys.len(), "asked for " + numDead + " guys to die, but only passed in an array of " + guys.len() + " guys." )
+ Assert( timeout > 0 )
+ __WaitUntilDeadInternal( guys, numDead, timeout, __WaitUntilDeadOrLeechedTracker )
+}
+
+void function WaitUntilAllDeadOrLeeched( array<entity> guys )
+{
+ int count = guys.len()
+ float timeout = -1
+ __WaitUntilDeadInternal( guys, count, timeout, __WaitUntilDeadOrLeechedTracker )
+}
+
+void function WaitUntilAllDeadOrLeechedWithTimeout( array<entity> guys, float timeout )
+{
+ int count = guys.len()
+ Assert( timeout > 0 )
+ __WaitUntilDeadInternal( guys, count, timeout, __WaitUntilDeadOrLeechedTracker )
+}
+
+void function __WaitUntilDeadInternal( array<entity> guys, int numDead, float timeout, void functionref( entity, table<string, int> ) deathTrackerFunc )
+{
+ table<string, int> master = { count = numDead }
+
+ if ( timeout > 0.0 )
+ thread __WaitUntilDeadTrackerDelayedSignal( master, "WaitDeadTimeOut", timeout )
+
+ //when the thread ends, let child threads know
+ OnThreadEnd(
+ function() : ( master )
+ {
+ Signal( master, "OnDestroy" )
+ }
+ )
+
+ foreach ( guy in guys )
+ thread deathTrackerFunc( guy, master )
+
+ while ( master.count > 0 )
+ {
+ table result = WaitSignal( master, "OnDeath", "WaitDeadTimeOut" )
+ if ( result.signal == "WaitDeadTimeOut" ) //can't do endsignal, because it will kill the calling function too
+ return
+ }
+}
+
+void function __WaitUntilDeadTrackerDelayedSignal( table<string, int> master, string signal, float delay )
+{
+ EndSignal( master, signal )
+
+ wait delay
+ if ( IsValid( master ) )
+ Signal( master, signal )
+}
+
+void function __WaitUntilDeadTracker( entity guy, table<string, int> master )
+{
+ EndSignal( master, "OnDestroy" )
+ if ( IsAlive( guy ) )
+ WaitSignal( guy, "OnDeath", "OnDestroy" )
+
+ master.count--
+ Signal( master, "OnDeath" )
+}
+
+void function __WaitUntilDeadOrLeechedTracker( entity guy, table<string, int> master )
+{
+ EndSignal( master, "OnDestroy" )
+ if ( IsAlive( guy ) )
+ WaitSignal( guy, "OnDeath", "OnDestroy", "OnLeeched" )
+
+ master.count--
+ Signal( master, "OnDeath" )
+}
+
+void function SetRebreatherMaskVisible( entity ent, bool visible )
+{
+ asset modelname = ent.GetModelName()
+
+ int maskIdx = ent.FindBodyGroup( "mask" )
+ if ( maskIdx == -1 )
+ return
+
+ int visibleIdx = 1
+ if ( !visible )
+ visibleIdx = 0
+
+ ent.SetBodygroup( maskIdx, visibleIdx )
+}
+
+int function EntHasSpawnflag( entity ent, int spawnflagHexVal )
+{
+ return ( expect int( ent.kv.spawnflags ) & spawnflagHexVal )
+}
+
+entity function CreateInfoTarget( vector origin = <0,0,0>, vector angles = <0,0,0> )
+{
+ entity info_target = CreateEntity( "info_target" )
+ info_target.SetOrigin( origin )
+ info_target.SetAngles( angles )
+ DispatchSpawn( info_target )
+ return info_target
+}
+
+entity function CreateExpensiveScriptMover( vector origin = <0.0, 0.0, 0.0>, vector angles = <0.0, 0.0, 0.0>, int solidType = 0 )
+{
+ entity script_mover = CreateEntity( "script_mover" )
+ script_mover.kv.solid = solidType
+ script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" )
+ script_mover.kv.SpawnAsPhysicsMover = 0
+ script_mover.SetOrigin( origin )
+ script_mover.SetAngles( angles )
+
+ DispatchSpawn( script_mover )
+ return script_mover
+}
+
+entity function CreateScriptMover( vector origin = <0.0, 0.0, 0.0>, vector angles = <0.0, 0.0, 0.0>, int solidType = 0 )
+{
+ entity script_mover = CreateEntity( "script_mover_lightweight" )
+ script_mover.kv.solid = solidType
+ script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" )
+ script_mover.kv.SpawnAsPhysicsMover = 0
+ script_mover.SetOrigin( origin )
+ script_mover.SetAngles( angles )
+ DispatchSpawn( script_mover )
+ return script_mover
+}
+
+entity function CreateScriptMoverModel( asset model, vector origin = <0.0, 0.0, 0.0>, vector angles = <0.0, 0.0, 0.0>, int solidType = 0, float fadeDist = -1 )
+{
+ entity script_mover = CreateEntity( "script_mover_lightweight" )
+ script_mover.kv.solid = solidType
+ script_mover.kv.fadedist = fadeDist
+ script_mover.SetValueForModelKey( model )
+ script_mover.kv.SpawnAsPhysicsMover = 0
+ script_mover.SetOrigin( origin )
+ script_mover.SetAngles( angles )
+ DispatchSpawn( script_mover )
+ return script_mover
+}
+
+entity function CreateExpensiveScriptMoverModel( asset model, vector origin = <0.0, 0.0, 0.0>, vector angles = <0.0, 0.0, 0.0>, int solidType = 0, float fadeDist = -1 )
+{
+ entity script_mover = CreateEntity( "script_mover" )
+ script_mover.kv.solid = solidType
+ script_mover.kv.fadedist = fadeDist
+ script_mover.SetValueForModelKey( model )
+ script_mover.kv.SpawnAsPhysicsMover = 0
+ script_mover.SetOrigin( origin )
+ script_mover.SetAngles( angles )
+ DispatchSpawn( script_mover )
+ return script_mover
+}
+
+entity function CreateOwnedScriptMover( entity owner )
+{
+ entity script_mover = CreateEntity( "script_mover" )
+ script_mover.kv.solid = 0
+ script_mover.SetValueForModelKey( $"models/dev/empty_model.mdl" )
+ script_mover.kv.SpawnAsPhysicsMover = 0
+ script_mover.SetOrigin( owner.GetOrigin() )
+ script_mover.SetAngles( owner.GetAngles() )
+ DispatchSpawn( script_mover )
+ script_mover.Hide()
+
+ script_mover.SetOwner( owner )
+ return script_mover
+}
+
+// useful for finding out what ents are in a level. Should only be used for debugging.
+int function TotalEnts( bool hidden = false )
+{
+ array<entity> entities
+
+ EntTracker++
+
+ entity ent = Entities_FindInSphere( null, < 0, 0, 0 >, 90000 )
+ string name
+ for ( ;; )
+ {
+ if ( ent == null )
+ break
+
+ entities.append( ent )
+
+ name = ent.GetTargetName()
+
+ string strPrefix = "Old ent"
+ if ( ent.e.totalEntsStoredID == 0 )
+ {
+ string strPrefix = "* New ent"
+ ent.e.totalEntsStoredID = EntTracker
+ }
+
+ string strPostfix = ""
+ if ( name != "" )
+ strPostfix = " \"" + name + "\""
+
+ if ( !hidden )
+ printl( strPrefix + " (" + ent.e.totalEntsStoredID + "): " + ent + strPostfix )
+
+ ent = Entities_FindInSphere( ent, < 0, 0, 0 >, 90000 )
+ }
+
+ if ( !hidden )
+ printl( "Total entities " + entities.len() )
+
+ return entities.len()
+}
+
+bool function IsThreadTop()
+{
+ return getstackinfos( 3 ) == null
+}
+
+string function ThisFunc()
+{
+ return expect string( expect table( getstackinfos( 2 ) )[ "func" ] )
+}
+
+entity function ge( int index ) // shorthand version for typing from console
+{
+ return GetEntByIndex( index )
+}
+
+entity function GetEnt( string name )
+{
+ entity ent = Entities_FindByName( null, name )
+ if ( ent == null )
+ {
+ ent = Entities_FindByClassname( null, name )
+ Assert( Entities_FindByClassname( ent, name ) == null, "Tried to GetEnt but there were multiple entities with that name" )
+ }
+ else
+ {
+ Assert( Entities_FindByName( ent, name ) == null, "Tried to GetEnt but there were multiple entities with name " + name )
+ }
+
+ return ent
+}
+
+array<entity> function ArrayWithinCenter( array<entity> ents, vector start, int range )
+{
+ array<entity> Array
+ foreach ( ent in ents )
+ {
+ if ( Distance( start, ent.GetWorldSpaceCenter() ) > range )
+ continue
+
+ Array.append( ent )
+ }
+
+ return Array
+}
+
+vector function GetCenter( array<entity> ents )
+{
+ vector total
+
+ foreach ( ent in ents )
+ {
+ total += ent.GetOrigin()
+ }
+
+ total.x /= float( ents.len() )
+ total.y /= float( ents.len() )
+ total.z /= float( ents.len() )
+
+ return total
+}
+
+void function TableRemoveInvalid( table<entity, entity> Table )
+{
+ array<entity> deleteKey = []
+
+ foreach ( entity key, entity value in Table )
+ {
+ if ( !IsValid_ThisFrame( key ) )
+ deleteKey.append( key )
+
+ if ( !IsValid_ThisFrame( value ) )
+ deleteKey.append( key )
+ }
+
+ foreach ( key in deleteKey )
+ {
+ // in this search, two things could end up on the same key
+ if ( key in Table )
+ delete Table[ key ]
+ }
+}
+
+void function TableRemoveInvalidByValue( table<entity, entity> Table )
+{
+ array<entity> deleteKey = []
+
+ foreach ( key, entity value in Table )
+ {
+ if ( !IsValid_ThisFrame( value ) )
+ deleteKey.append( key )
+ }
+
+ foreach ( key in deleteKey )
+ {
+ delete Table[ key ]
+ }
+}
+
+void function TableRemoveDeadByKey( table<entity, entity> Table )
+{
+ array<entity> deleteKey = []
+
+ foreach ( key, value in Table )
+ {
+ if ( !IsAlive( key ) )
+ deleteKey.append( key )
+ }
+
+ foreach ( key in deleteKey )
+ {
+ delete Table[ key ]
+ }
+}
+
+
+void function ArrayDump( array<var> Array )
+{
+ for ( int i = 0; i < Array.len(); i++ )
+ {
+ printl( "index " + i + " is: " + Array[i] )
+ }
+}
+
+
+int function DotCompareLargest( ArrayDotResultStruct a, ArrayDotResultStruct b )
+{
+ if ( a.dot < b.dot )
+ return 1
+ else if ( a.dot > b.dot )
+ return -1
+
+ return 0
+}
+
+int function DotCompareSmallest( ArrayDotResultStruct a, ArrayDotResultStruct b )
+{
+ if ( a.dot > b.dot )
+ return 1
+ else if ( a.dot < b.dot )
+ return -1
+
+ return 0
+}
+
+array<ArrayDotResultStruct> function ArrayDotResults( array<entity> Array, entity ent )
+{
+ array<ArrayDotResultStruct> allResults
+
+ foreach ( arrayEnt in Array )
+ {
+ ArrayDotResultStruct results
+
+ results.dot = VectorDot_EntToEnt( ent, arrayEnt )
+ results.ent = arrayEnt
+ allResults.append( results )
+ }
+
+ return allResults
+}
+
+
+// Return an array of entities ordered from closest to furthest from the facing of the entity
+array<entity> function ArrayClosestToView( array<entity> Array, entity ent )
+{
+ Assert( type( Array ) == "array" )
+ array<ArrayDotResultStruct> allResults = ArrayDotResults( Array, ent )
+
+ allResults.sort( DotCompareLargest )
+
+ array<entity> returnEntities = []
+
+ foreach ( index, result in allResults )
+ {
+ //printl( "Results are " + result.dot )
+ returnEntities.insert( index, result.ent )
+ }
+
+ // the actual distances aren't returned
+ return returnEntities
+}
+
+
+entity function SpawnRefEnt( entity ent )
+{
+ printl( "Ent model " + ent.GetValueForModelKey() )
+ int attach = ent.LookupAttachment( "ref" )
+ vector origin = ent.GetAttachmentOrigin( attach )
+ vector angles = ent.GetAttachmentAngles( attach )
+
+ entity ref = CreateEntity( "prop_dynamic" )
+ //ref.kv.SpawnAsPhysicsMover = 0
+ ref.SetValueForModelKey( $"models/dev/empty_model.mdl" )
+ DispatchSpawn( ref )
+
+ ref.SetOrigin( origin )
+ ref.SetAngles( angles )
+ ref.Hide()
+ return ref
+}
+
+entity function CreateScriptRef( vector ornull origin = null, vector ornull angles = null )
+{
+ entity ent = CreateEntity( "script_ref" )
+
+ if ( origin )
+ ent.SetOrigin( expect vector( origin ) )
+
+ if ( angles )
+ ent.SetAngles( expect vector( angles ) )
+
+ DispatchSpawn( ent )
+ return ent
+}
+
+entity function CreateScriptRefMinimap( vector origin, vector angles )
+{
+ entity ent = CreateEntity( "script_ref_minimap" )
+
+ ent.SetOrigin( origin )
+ ent.SetAngles( angles )
+
+ DispatchSpawn( ent )
+
+ return ent
+}
+
+bool function exists( table tbl, string val )
+{
+ if ( !(val in tbl) )
+ return false
+
+ return tbl[ val ] != null
+}
+
+var function TableRandomIndex( table Table )
+{
+ array Array = []
+
+ foreach ( index, _ in Table )
+ {
+ Array.append( index )
+ }
+
+ return Array.getrandom()
+}
+
+// should improve this
+float function YawDifference( float yaw1, float yaw2 )
+{
+ Assert( yaw1 >= 0 )
+ Assert( yaw1 <= 360 )
+ Assert( yaw2 >= 0 )
+ Assert( yaw2 <= 360 )
+
+ float diff = fabs( yaw1 - yaw2 )
+
+ if ( diff > 180 )
+ return 360 - diff
+ else
+ return diff
+
+ unreachable
+}
+
+
+
+/*function TrackIsTouching( ent )
+{
+ return // now uses IsTouching
+
+ //ent.s.touching <- {}
+ //ent.ConnectOutput( "OnStartTouch", TrackIsTouching_OnStartTouch )
+ //ent.ConnectOutput( "OnEndTouch", TrackIsTouching_OnEndTouch )
+}
+
+void function TrackIsTouching_OnStartTouch( entity self, entity activator, entity caller, var value )
+{
+ if ( activator )
+ {
+ self.s.touching[ activator ] <- true
+ }
+}
+
+void function TrackIsTouching_OnEndTouch( entity self, entity activator, entity caller, var value )
+{
+ if ( activator )
+ {
+ if ( activator in self.s.touching )
+ {
+ delete self.s.touching[ activator ]
+ }
+ }
+}*/
+
+void function NPC_NoTarget( entity self )
+{
+ self.SetNoTarget( true )
+ self.SetNoTargetSmartAmmo( true )
+}
+
+vector function GetSimpleTraceEnd( vector start, vector end, float frac )
+{
+ vector vec = end - start
+ vec *= frac
+ return start + vec
+}
+
+bool function LoadedMain()
+{
+ if ( "LoadedMain" in level )
+ return true
+
+ level.LoadedMain <- true
+
+ return false
+}
+
+void function Warning( string msg )
+{
+ printl( "*** WARNING ***" )
+ printl( msg )
+ DumpStack()
+ printl( "*** WARNING ***" )
+}
+
+void function TimeOut( float time )
+{
+ table Table = {}
+ EndSignal( Table, "OnDeath" )
+ delaythread( time ) Signal( Table, "OnDeath" )
+}
+
+string function GetActiveWeaponClass( entity player )
+{
+ entity weapon = player.GetActiveWeapon()
+ Assert( weapon != null )
+
+ string weaponclass = weapon.GetWeaponClassName()
+ return weaponclass
+}
+
+bool function HasWeapon( entity ent, string weaponClassName, array<string> mods = [] )
+{
+ Assert( ent.IsPlayer() || ent.IsNPC() )
+
+ array<entity> weaponArray = ent.GetMainWeapons()
+ foreach ( weapon in weaponArray )
+ {
+ if ( weapon.GetWeaponClassName() == weaponClassName )
+ {
+ if ( WeaponHasSameMods( weapon, mods ) )
+ return true
+ }
+ }
+
+ return false
+}
+
+bool function HasOrdnance( entity ent, string weaponClassName, array<string> mods = [] )
+{
+ return HasOffhandForSlot( ent, OFFHAND_ORDNANCE, weaponClassName, mods )
+}
+
+bool function HasCoreAbility( entity ent, string weaponClassName, array<string> mods = [] )
+{
+ return HasOffhandForSlot( ent, OFFHAND_EQUIPMENT, weaponClassName, mods )
+}
+
+bool function HasSpecial( entity ent, string weaponClassName, array<string> mods = [] )
+{
+ return HasOffhandForSlot( ent, OFFHAND_SPECIAL, weaponClassName, mods )
+}
+
+bool function HasAntiRodeo( entity ent, string weaponClassName, array<string> mods = [] )
+{
+ return HasOffhandForSlot( ent, OFFHAND_ANTIRODEO, weaponClassName, mods )
+}
+
+bool function HasMelee( entity ent, string weaponClassName, array<string> mods = [] )
+{
+ return HasOffhandForSlot( ent, OFFHAND_MELEE, weaponClassName, mods )
+}
+
+bool function HasOffhandForSlot( entity ent, int slot, string weaponClassName, array<string> mods = [] )
+{
+ Assert( ent.IsPlayer() || ent.IsNPC() )
+
+ entity weapon = ent.GetOffhandWeapon( slot )
+ if ( !IsValid( weapon ) )
+ return false
+
+ if ( weapon.GetWeaponClassName() != weaponClassName )
+ return false
+
+ return WeaponHasSameMods( weapon, mods )
+}
+
+bool function WeaponHasSameMods( entity weapon, array<string> mods = [] )
+{
+ array hasMods = clone mods
+ foreach ( mod in weapon.GetMods() )
+ {
+ hasMods.removebyvalue( mod )
+ }
+
+ // has all the same mods.
+ return hasMods.len() == 0
+}
+
+bool function HasOffhandWeapon( entity ent, string weaponClassName )
+{
+ Assert( ent.IsPlayer() || ent.IsNPC() )
+
+ array<entity> weaponArray = ent.GetOffhandWeapons()
+ foreach ( weapon in weaponArray )
+ {
+ if ( weapon.GetWeaponClassName() == weaponClassName )
+ return true
+ }
+
+ return false
+}
+
+float function GetFraction( float value, float min, float max )
+{
+ return ( value - min ) / ( max - min )
+}
+
+float function GetFractionClamped( float value, float min, float max )
+{
+ float frac = GetFraction( value, min, max )
+ return clamp( frac, 0.0, 1.0 )
+}
+
+float function GetValueFromFraction( float value, float value_min, float value_max, float return_min, float return_max )
+{
+ float frac = GetFractionClamped( value, value_min, value_max )
+ float retVal = return_min + ( ( return_max - return_min ) * frac )
+ return clamp( retVal, return_min, return_max )
+}
+
+bool function VectorCompare( vector vec1, vector vec2 )
+{
+ if ( vec1.x != vec2.x )
+ return false
+
+ if ( vec1.y != vec2.y )
+ return false
+
+ return vec1.z == vec2.z
+}
+
+// returns vectordot from viewEnt to targetEnt
+float function VectorDot_EntToEnt( entity viewEnt, entity targetEnt )
+{
+ vector maxs = targetEnt.GetBoundingMaxs()
+ vector mins = targetEnt.GetBoundingMins()
+ maxs += mins
+ maxs.x *= 0.5
+ maxs.y *= 0.5
+ maxs.z *= 0.5
+ vector targetOrg = targetEnt.GetOrigin() + maxs
+
+ maxs = viewEnt.GetBoundingMaxs()
+ mins = viewEnt.GetBoundingMins()
+ maxs += mins
+ maxs.x *= 0.5
+ maxs.y *= 0.5
+ maxs.z *= 0.5
+ vector viewOrg = viewEnt.GetOrigin() + maxs
+
+ //DebugDrawLine( targetOrg, viewOrg, 255, 255, 255, true, 0.5 )
+ vector vecToEnt = ( targetOrg - viewOrg )
+ vecToEnt = Normalize( vecToEnt )
+
+ float dotVal = DotProduct( vecToEnt, viewEnt.GetForwardVector() )
+ return dotVal
+}
+
+void function PrecacheEffect( asset effectName )
+{
+ entity warningParticle = CreateEntity( "info_particle_system" )
+ warningParticle.SetValueForEffectNameKey( effectName )
+ warningParticle.kv.start_active = 0
+ DispatchSpawn( warningParticle )
+ warningParticle.Destroy()
+}
+
+void function PrecacheEntity( string entName, asset model = $"" )
+{
+ entity tempEnt = CreateEntity( entName )
+
+ if ( model != $"" )
+ tempEnt.SetValueForModelKey( model )
+
+ tempEnt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+
+ DispatchSpawn( tempEnt )
+ tempEnt.Destroy()
+}
+
+void function PrecacheProjectileEntity( string entName, string weaponClassName, asset model = $"" )
+{
+ entity tempEnt = Entities_CreateProjectileByClassname( entName, weaponClassName )
+
+ if ( model != $"" )
+ tempEnt.SetValueForModelKey( model )
+
+ tempEnt.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID
+
+ DispatchSpawn( tempEnt )
+ tempEnt.Destroy()
+}
+
+void function PrecacheSprite( asset spriteName )
+{
+ entity sprite = CreateEntity( "env_sprite_oriented" )
+ sprite.SetValueForModelKey( spriteName )
+ sprite.kv.spawnflags = 1
+ DispatchSpawn( sprite )
+ sprite.Destroy()
+}
+
+entity function CreatePointMessage( string msg, vector origin, int displayRadius = 512 )
+{
+ entity point_message = CreateEntity( "point_message" )
+ point_message.SetOrigin( origin )
+ point_message.kv.message = msg
+ point_message.kv.radius = displayRadius
+
+ DispatchSpawn( point_message )
+
+ return point_message
+}
+
+entity function CreateGameText( string msg, float xPos, float yPos, int channel, string color = "255 255 255", float fadein = 2, float fadeout = 0.5, float holdtime = 2 )
+{
+ entity game_text = CreateEntity( "game_text" )
+
+ game_text.SetScriptName( "gt" + UniqueString() )
+ game_text.kv.message = msg
+ game_text.kv["x"] = xPos
+ game_text.kv["y"] = yPos
+ game_text.kv.channel = channel
+ game_text.kv.color = color
+ game_text.kv.color2 = "240 110 0" // doesn't appear to do anything atm, not supporting in params
+ game_text.kv.fadein = fadein
+ game_text.kv.fadeout = fadeout
+ game_text.kv.holdtime = holdtime
+ game_text.kv.fxtime = "0.25"
+
+ DispatchSpawn( game_text )
+
+ return game_text
+}
+
+// pass the origin where the player's feet would be
+// tests a player sized box for any collision and returns true only if it's clear
+bool function PlayerCanTeleportHere( entity player, vector testOrg, entity ignoreEnt = null ) //TODO: This is a copy of SP's PlayerPosInSolid(). Not changing it to avoid patching SP. Merge into one function next game
+{
+ int solidMask = TRACE_MASK_PLAYERSOLID
+ vector mins
+ vector maxs
+ int collisionGroup = TRACE_COLLISION_GROUP_PLAYER
+ array<entity> ignoreEnts = [ player ]
+
+ if ( IsValid( ignoreEnt ) )
+ ignoreEnts.append( ignoreEnt )
+ TraceResults result
+
+ mins = player.GetPlayerMins()
+ maxs = player.GetPlayerMaxs()
+ result = TraceHull( testOrg, testOrg + < 0, 0, 1 >, mins, maxs, ignoreEnts, solidMask, collisionGroup )
+
+ if ( result.startSolid )
+ return false
+
+ return true
+}
+
+
+enum eAttach
+{
+ No
+ ViewAndTeleport
+ View
+ Teleport
+ ThirdPersonView
+}
+
+
+void function LoadDiamond()
+{
+ printl( " " )
+ printl( " " )
+ printl( " " )
+
+ // Draw a diamond of a random size and phase (of the moon) so it is easy to separate sections of logs.
+ int random_spread = RandomIntRange( 4, 7 )
+ float random_fullness = RandomFloat( 2.0 )
+ bool functionref( int, int ) compare_func
+ string msg
+
+ if ( RandomFloat( 1.0 ) > 0.5 )
+ {
+ compare_func = bool function( int a, int b )
+ {
+ return a <= b
+ }
+ }
+ else
+ {
+ compare_func = bool function( int a, int b )
+ {
+ return a >= b
+ }
+ }
+
+ for ( int i = 0; i <= random_spread - 2; i++ )
+ {
+ msg = ""
+
+ for ( int p = 0; p <= random_spread - i; p++ )
+ {
+ msg = msg + " "
+ }
+
+ for ( int p = 0; p <= i * 2; p++ )
+ {
+ if ( p == i * 2 || p == 0 )
+ {
+ msg = msg + "*"
+ }
+ else
+ {
+ int an_int = int( i * random_fullness )
+
+ if ( compare_func( p, an_int ) )
+ msg = msg + "*"
+ else
+ msg = msg + " "
+ }
+ }
+
+ printl( msg )
+ }
+
+ for ( int i = random_spread - 1; i >= 0; i-- )
+ {
+ msg = ""
+
+ for ( int p = 0; p <= random_spread - i; p++ )
+ {
+ msg = msg + " "
+ }
+
+
+ for ( int p = 0; p <= i * 2; p++ )
+ {
+ if ( p == i * 2 || p == 0 )
+ {
+ msg = msg + "*"
+ }
+ else
+ {
+ if ( compare_func( p, int( i * random_fullness ) ) )
+ {
+ msg = msg + "*"
+ }
+ else
+ {
+ msg = msg + " "
+ }
+ }
+ }
+
+ printl( msg )
+ }
+
+ printl( " " )
+ printl( " " )
+ printl( " " )
+}
+
+// this will clear all dropped weapons in the map
+void function ClearDroppedWeapons( float delayTime = 0.0 )
+{
+ if ( delayTime > 0 )
+ wait delayTime
+
+ bool onlyNotOwnedWeapons = true // don't get the ones in guys' hands
+ array<entity> weapons = GetWeaponArray( onlyNotOwnedWeapons )
+
+ foreach ( weapon in weapons )
+ {
+ // don't clean up weapon pickups that were placed in leveled
+ int spawnflags = expect string( weapon.kv.spawnflags ).tointeger()
+ if ( spawnflags & SF_WEAPON_START_CONSTRAINED )
+ continue
+
+ weapon.Destroy()
+ }
+}
+
+void function ClearActiveProjectilesForTeam( int team, vector searchOrigin = <0,0,0>, float searchDist = -1 )
+{
+ array<entity> projectiles = GetProjectileArrayEx( "any", team, TEAM_ANY, searchOrigin, searchDist )
+
+ printt( "cleaning up", projectiles.len(), "weapon projectiles for team", team )
+
+ foreach ( proj in projectiles )
+ {
+ if( !IsValid( proj ) )
+ continue
+
+ proj.Destroy()
+ }
+}
+
+void function RestockPlayerAmmo_Silent( entity player = null )
+{
+ RestockPlayerAmmo( player, true )
+}
+
+void function RestockPlayerAmmo( entity player = null, bool isSilent = false )
+{
+ array<entity> players
+ if ( IsAlive( player ) )
+ players.append( player )
+ else
+ players = GetPlayerArray_Alive()
+
+ foreach( player in players )
+ {
+ player.RefillAllAmmo()
+
+ if ( !isSilent )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Coop_AmmoBox_AmmoRefill" )
+ }
+}
+
+entity function CreateLightSprite( vector origin, vector angles, string lightcolor = "255 0 0", float scale = 0.5 )
+{
+ // attach a light so we can see it
+ entity env_sprite = CreateEntity( "env_sprite" )
+ env_sprite.SetScriptName( UniqueString( "molotov_sprite" ) )
+ env_sprite.kv.rendermode = 5
+ env_sprite.kv.origin = origin
+ env_sprite.kv.angles = angles
+ env_sprite.kv.rendercolor = lightcolor
+ env_sprite.kv.renderamt = 255
+ env_sprite.kv.framerate = "10.0"
+ env_sprite.SetValueForModelKey( $"sprites/glow_05.vmt" )
+ env_sprite.kv.scale = string( scale )
+ env_sprite.kv.spawnflags = 1
+ env_sprite.kv.GlowProxySize = 16.0
+ env_sprite.kv.HDRColorScale = 1.0
+ DispatchSpawn( env_sprite )
+ EntFireByHandle( env_sprite, "ShowSprite", "", 0, null, null )
+
+ return env_sprite
+}
+
+// defaultWinner: if it's a tie, return this value
+int function GetCurrentWinner( int defaultWinner = TEAM_MILITIA )
+{
+ int imcScore
+ int militiaScore
+
+ if ( IsRoundBased() )
+ {
+ imcScore = GameRules_GetTeamScore2( TEAM_IMC )
+ militiaScore = GameRules_GetTeamScore2( TEAM_MILITIA )
+
+ if ( IsRoundBasedUsingTeamScore() && ( imcScore == militiaScore ) )
+ {
+ imcScore = GameRules_GetTeamScore( TEAM_IMC )
+ militiaScore = GameRules_GetTeamScore( TEAM_MILITIA )
+ }
+ }
+ else
+ {
+ imcScore = GameRules_GetTeamScore( TEAM_IMC )
+ militiaScore = GameRules_GetTeamScore( TEAM_MILITIA )
+ }
+
+ int currentWinner = defaultWinner
+
+ if ( militiaScore > imcScore )
+ currentWinner = TEAM_MILITIA
+ else if ( imcScore > militiaScore )
+ currentWinner = TEAM_IMC
+
+ return currentWinner
+}
+
+void function SetNpcFollowsPlayerOverride( entity player, void functionref( entity, entity ) override )
+{
+ player.p.followPlayerOverride = override
+}
+
+void function ClearNpcFollowsPlayerOverride( entity player )
+{
+ player.p.followPlayerOverride = null
+}
+
+void function AddCallback_GameStateEnter( int gameState, void functionref() callbackFunc )
+{
+ Assert( gameState < svGlobal.gameStateEnterCallbacks.len() )
+
+ Assert( !svGlobal.gameStateEnterCallbacks[ gameState ].contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_GameStateEnter" )
+
+ svGlobal.gameStateEnterCallbacks[ gameState ].append( callbackFunc )
+}
+
+void function GM_SetObserverFunc( void functionref( entity ) callbackFunc )
+{
+ svGlobal.observerFunc = callbackFunc
+}
+
+void function GM_AddPlayingThinkFunc( void functionref() callbackFunc )
+{
+ Assert( !svGlobal.playingThinkFuncTable.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with GM_AddPlayingThinkFunc" )
+
+ svGlobal.playingThinkFuncTable.append( callbackFunc )
+}
+
+void function GM_AddThirtySecondsLeftFunc( void functionref() callbackFunc )
+{
+ Assert( !svGlobal.thirtySecondsLeftFuncTable.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with GM_AddThirtySecondsLeftFunc" )
+
+ svGlobal.thirtySecondsLeftFuncTable.append( callbackFunc )
+}
+
+void function GM_SetMatchProgressAnnounceFunc( void functionref( int ) callbackFunc )
+{
+ svGlobal.matchProgressAnnounceFunc = callbackFunc
+}
+
+// Get an absolute offset poistion to an entity even if it's been rotated in world space
+vector function GetEntPosPlusOffset( entity ent, float offsetX, float offsetY, float offsetZ )
+{
+ vector entAngles = ent.GetAngles()
+ vector entOrg = ent.GetOrigin()
+
+ vector right = AnglesToRight( entAngles )
+ right = Normalize( right )
+ vector pos = entOrg + ( right * offsetY )
+
+ vector forward = AnglesToForward( entAngles )
+ forward = Normalize( forward )
+ pos = pos + ( forward * offsetX )
+
+ vector up = AnglesToUp( entAngles )
+ up = Normalize( up )
+ pos = pos + ( up * offsetZ )
+
+ return pos
+}
+
+void function EmitSoundToTeamPlayers( string alias, int team )
+{
+ array<entity> players = GetPlayerArrayOfTeam( team )
+
+ foreach ( player in players )
+ EmitSoundOnEntityOnlyToPlayer( player, player, alias )
+}
+
+void function EmitDifferentSoundsAtPositionForPlayerAndWorld( string soundForPlayer, string soundForWorld, vector position, entity player, int teamNum )
+{
+ if ( IsValid( player ) && player.IsPlayer() )
+ {
+ EmitSoundAtPositionExceptToPlayer( teamNum, position, player, soundForWorld )
+ EmitSoundAtPositionOnlyToPlayer( teamNum, position, player, soundForPlayer)
+ }
+ else
+ {
+ EmitSoundAtPosition( teamNum, position, soundForWorld )
+ }
+}
+
+void function EmitDifferentSoundsOnEntityForPlayerAndWorld( string soundForPlayer, string soundForWorld, entity soundEnt, entity player )
+{
+ if ( IsValid( player ) && player.IsPlayer() )
+ {
+ EmitSoundOnEntityExceptToPlayerNotPredicted( soundEnt, player, soundForWorld )
+ EmitSoundOnEntityOnlyToPlayer(soundEnt, player, soundForPlayer)
+ }
+ else
+ {
+ EmitSoundOnEntity( soundEnt, soundForWorld )
+ }
+}
+
+// Drop an entity to the ground by tracing straight down from its z-axis
+void function DropToGround( entity ent )
+{
+ vector targetOrigin = OriginToGround( ent.GetOrigin() + <0,0,1> )
+ ent.SetOrigin( targetOrigin )
+}
+
+void function DropTitanToGround( entity titan, array<entity> ignoreEnts )
+{
+ vector endOrigin = titan.GetOrigin() - < 0, 0, 20000 >
+ vector mins = GetBoundsMin( HULL_TITAN )
+ vector maxs = GetBoundsMax( HULL_TITAN )
+ TraceResults traceResult = TraceHull( titan.GetOrigin(), endOrigin, mins, maxs, ignoreEnts, TRACE_MASK_TITANSOLID, TRACE_COLLISION_GROUP_NONE )
+
+ titan.SetOrigin( traceResult.endPos )
+}
+
+vector function OriginToGround( vector origin )
+{
+ vector endOrigin = origin - < 0, 0, 20000 >
+ TraceResults traceResult = TraceLine( origin, endOrigin, [], TRACE_MASK_NPCWORLDSTATIC, TRACE_COLLISION_GROUP_NONE )
+
+ return traceResult.endPos
+}
+
+float function GetVerticalClearance( vector origin )
+{
+ vector endOrigin = origin + < 0, 0, 20000 >
+ TraceResults traceResult = TraceLine( origin, endOrigin, [], TRACE_MASK_NPCWORLDSTATIC, TRACE_COLLISION_GROUP_NONE )
+ vector endPos = traceResult.endPos
+ float zDelta = ( endPos.z - origin.z )
+
+ return zDelta
+}
+
+// ---------------------------------------------------------------------
+// Determine if an entity is a valid player spawnpoint
+// ---------------------------------------------------------------------
+bool function PlayerSpawnpointIsValid( entity ent )
+{
+ if ( ent.GetClassName() != "prop_dynamic" )
+ return false
+ if ( ent.GetValueForModelKey() != $"models/humans/pete/mri_male.mdl" )
+ return false
+
+ return true
+}
+
+// ---------------------------------------------------------------------
+// Make an NPC or the player invincible (true/false)
+// (_npc.nut intercepts incoming damage and negates it if the ent is tagged as invincible)
+// ---------------------------------------------------------------------
+void function MakeInvincible( entity ent )
+{
+ Assert( IsValid( ent ), "Tried to make invalid " + ent + " invincible" )
+ Assert( ent.IsNPC() || ent.IsPlayer(), "MakeInvincible() can only be called on NPCs and the player" )
+ Assert( IsAlive( ent ), "Tried to make dead ent " + ent + " invincible" )
+
+ ent.SetInvulnerable()
+}
+
+void function ClearInvincible( entity ent )
+{
+ Assert( IsValid( ent ), "Tried to clear invalid " + ent + " invincible" )
+
+ ent.ClearInvulnerable()
+}
+
+bool function IsInvincible( entity ent )
+{
+ return ent.IsInvulnerable()
+}
+
+//-------------------------------------
+// Teleport an entity (teleporter) to an entity's org and angles (ent)
+//--------------------------------------
+void function TeleportToEnt( entity teleporter, entity ent )
+{
+ Assert( teleporter != null, "Unable to teleport null entity" )
+ Assert( ent != null, "Unable to teleport to a null entity" )
+ teleporter.SetOrigin( ent.GetOrigin() )
+ teleporter.SetAngles( ent.GetAngles() )
+}
+
+//-----------------------------------------------------------
+// CreateShake() - create and fire an env_shake at a specified origin
+// - returns the shake in case you want to parent it
+//------------------------------------------------------------
+
+entity function CreateShake_internal( vector org, float amplitude, float frequency, float duration, float radius, int spawnFlags )
+{
+ entity env_shake = CreateEntity( "env_shake" )
+ env_shake.kv.amplitude = amplitude
+ env_shake.kv.radius = radius
+ env_shake.kv.duration = duration
+ env_shake.kv.frequency = frequency
+ env_shake.kv.spawnflags = spawnFlags
+
+ DispatchSpawn( env_shake )
+
+ env_shake.SetOrigin( org )
+
+ EntFireByHandle( env_shake, "StartShake", "", 0, null, null )
+ EntFireByHandle( env_shake, "Kill", "", ( duration + 1 ), null, null )
+
+ return env_shake
+}
+
+entity function CreateShake( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
+{
+ return CreateShake_internal( org, amplitude, frequency, duration, radius, 0 );
+}
+
+entity function CreateShakeRumbleOnly( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
+{
+ return CreateShake_internal( org, amplitude, frequency, duration, radius, SF_SHAKE_RUMBLE_ONLY );
+}
+
+entity function CreateShakeNoRumble( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
+{
+ return CreateShake_internal( org, amplitude, frequency, duration, radius, SF_SHAKE_NO_RUMBLE );
+}
+
+entity function CreateAirShake( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
+{
+ return CreateShake_internal( org, amplitude, frequency, duration, radius, SF_SHAKE_INAIR );
+}
+
+entity function CreateAirShakeRumbleOnly( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
+{
+ return CreateShake_internal( org, amplitude, frequency, duration, radius, (SF_SHAKE_INAIR | SF_SHAKE_RUMBLE_ONLY) );
+}
+
+entity function CreateAirShakeNoRumble( vector org, float amplitude = 16, float frequency = 150, float duration = 1.5, float radius = 2048 )
+{
+ return CreateShake_internal( org, amplitude, frequency, duration, radius, (SF_SHAKE_INAIR | SF_SHAKE_NO_RUMBLE) );
+}
+
+//-------------------------------------
+// CreatePhysExplosion - physExplosion...small, medium or large
+//--------------------------------------
+entity function CreatePhysExplosion( vector org, float radius, int magnitude = 1, int flags = 1, bool dealsDamage = true )
+{
+ entity env_physexplosion = CreateEntity( "env_physexplosion" )
+ env_physexplosion.kv.spawnflags = flags // default 1 = No Damage - Only Force
+ env_physexplosion.kv.magnitude = magnitude
+ env_physexplosion.kv.radius = string( radius )
+ env_physexplosion.SetOrigin( org )
+ env_physexplosion.kv.scriptDamageType = damageTypes.explosive
+ DispatchSpawn( env_physexplosion )
+
+ EntFireByHandle( env_physexplosion, "Explode", "", 0, null, null )
+ EntFireByHandle( env_physexplosion, "Kill", "", 2, null, null )
+}
+
+//-----------------------------------------------------------
+// CreatePropDynamic( model ) - create a generic prop_dynamic with default properties
+//------------------------------------------------------------
+entity function CreatePropDynamic( asset model, vector ornull origin = null, vector ornull angles = null, var solidType = 0, float fadeDist = -1 )
+{
+ entity prop_dynamic = CreateEntity( "prop_dynamic" )
+ prop_dynamic.SetValueForModelKey( model )
+ prop_dynamic.kv.fadedist = fadeDist
+ prop_dynamic.kv.renderamt = 255
+ prop_dynamic.kv.rendercolor = "255 255 255"
+ prop_dynamic.kv.solid = solidType // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+ if ( origin )
+ {
+ // hack: Setting origin twice. SetOrigin needs to happen before DispatchSpawn, otherwise the prop may not touch triggers
+ prop_dynamic.SetOrigin( expect vector( origin ) )
+ if ( angles )
+ prop_dynamic.SetAngles( expect vector( angles ) )
+ }
+ DispatchSpawn( prop_dynamic )
+ if ( origin )
+ {
+ // hack: Setting origin twice. SetOrigin needs to happen after DispatchSpawn, otherwise origin is snapped to nearest whole unit
+ prop_dynamic.SetOrigin( expect vector( origin ) )
+ if ( angles )
+ prop_dynamic.SetAngles( expect vector( angles ) )
+ }
+
+ return prop_dynamic
+}
+
+
+//-----------------------------------------------------------
+// CreatePropDynamicLightweight( model ) - create a generic prop_dynamic_lightweight with default properties
+//------------------------------------------------------------
+entity function CreatePropDynamicLightweight( asset model, vector ornull origin = null, vector ornull angles = null, var solidType = 0, float fadeDist = -1 )
+{
+ entity prop_dynamic = CreateEntity( "prop_dynamic_lightweight" )
+ prop_dynamic.SetValueForModelKey( model )
+ prop_dynamic.kv.fadedist = fadeDist
+ prop_dynamic.kv.renderamt = 255
+ prop_dynamic.kv.rendercolor = "255 255 255"
+ prop_dynamic.kv.solid = solidType // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+ if ( origin )
+ {
+ // hack: Setting origin twice. SetOrigin needs to happen before DispatchSpawn, otherwise the prop may not touch triggers
+ prop_dynamic.SetOrigin( expect vector( origin ) )
+ if ( angles )
+ prop_dynamic.SetAngles( expect vector( angles ) )
+ }
+ DispatchSpawn( prop_dynamic )
+ if ( origin )
+ {
+ // hack: Setting origin twice. SetOrigin needs to happen after DispatchSpawn, otherwise origin is snapped to nearest whole unit
+ prop_dynamic.SetOrigin( expect vector( origin ) )
+ if ( angles )
+ prop_dynamic.SetAngles( expect vector( angles ) )
+ }
+
+ return prop_dynamic
+}
+
+
+//-----------------------------------------------------------
+// CreatePropScript( model ) - create a generic prop_script with default properties
+//------------------------------------------------------------
+entity function CreatePropScript( asset model, vector ornull origin = null, vector ornull angles = null, int solidType = 0, float fadeDist = -1 )
+{
+ entity prop_script = CreateEntity( "prop_script" )
+ prop_script.SetValueForModelKey( model )
+ prop_script.kv.fadedist = fadeDist
+ prop_script.kv.renderamt = 255
+ prop_script.kv.rendercolor = "255 255 255"
+ prop_script.kv.solid = solidType // 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+ if ( origin )
+ {
+ // hack: Setting origin twice. SetOrigin needs to happen before DispatchSpawn, otherwise the prop may not touch triggers
+ prop_script.SetOrigin( expect vector( origin ) )
+ if ( angles )
+ prop_script.SetAngles( expect vector( angles ) )
+ }
+ DispatchSpawn( prop_script )
+ if ( origin )
+ {
+ // hack: Setting origin twice. SetOrigin needs to happen after DispatchSpawn, otherwise origin is snapped to nearest whole unit
+ prop_script.SetOrigin( expect vector( origin ) )
+ if ( angles )
+ prop_script.SetAngles( expect vector( angles ) )
+ }
+
+ return prop_script
+}
+
+
+
+//-----------------------------------------------------------
+// CreatePropPhysics( model ) - create a generic prop_physics with default properties
+//------------------------------------------------------------
+entity function CreatePropPhysics( asset model, vector origin, vector angles )
+{
+ entity prop_physics = CreateEntity( "prop_physics" )
+ prop_physics.SetValueForModelKey( model )
+ prop_physics.kv.spawnflags = 0
+ prop_physics.kv.fadedist = -1
+ prop_physics.kv.physdamagescale = 0.1
+ prop_physics.kv.inertiaScale = 1.0
+ prop_physics.kv.renderamt = 255
+ prop_physics.kv.rendercolor = "255 255 255"
+ SetTeam( prop_physics, TEAM_BOTH ) // need to have a team other then 0 or it won't take impact damage
+
+ prop_physics.SetOrigin( origin )
+ prop_physics.SetAngles( angles )
+ DispatchSpawn( prop_physics )
+
+ return prop_physics
+}
+
+//-----------------------------------------------------------
+// SpawnBullseye() - creates a npc_bullseye and attaches it to an entity
+//------------------------------------------------------------
+entity function SpawnBullseye( int team, entity ent = null )
+{
+ entity bullseye = CreateEntity( "npc_bullseye" )
+ bullseye.SetScriptName( UniqueString( "bullseye" ) )
+ bullseye.kv.rendercolor = "255 255 255"
+ bullseye.kv.renderamt = 0
+ bullseye.kv.health = 9999
+ bullseye.kv.max_health = -1
+ bullseye.kv.spawnflags = 516
+ bullseye.kv.FieldOfView = 0.5
+ bullseye.kv.FieldOfViewAlert = 0.2
+ bullseye.kv.AccuracyMultiplier = 1.0
+ bullseye.kv.physdamagescale = 1.0
+ bullseye.kv.WeaponProficiency = eWeaponProficiency.VERYGOOD
+ bullseye.kv.minangle = "360"
+ DispatchSpawn( bullseye )
+
+ SetTeam( bullseye, team )
+
+ if ( ent )
+ {
+ vector bounds = ent.GetBoundingMaxs()
+ bullseye.SetOrigin( ent.GetOrigin() + < 0, 0, bounds.z * 0.5 > )
+ bullseye.SetParent( ent )
+ }
+
+ return bullseye
+}
+
+void function CenterPrint( ... )
+{
+ string msg = ""
+ for ( int i = 0; i < vargc; i++ )
+ msg = ( msg + " " + string( vargv[ i ] ) )
+
+ int words = expect int( vargc )
+ if ( words < 1 )
+ words = 1
+
+ float delay = GraphCapped( float( words ), 2.0, 8.0, 2.1, 3.5 )
+
+ entity ent = CreateGameText( msg, -1, 0.5, 10, "255 255 255", 0.25, 0.25, delay )
+ EntFireByHandle( ent, "Display", "", 0, null, null )
+
+ thread DestroyCenterPrint( ent, delay )
+}
+
+void function DestroyCenterPrint( entity ent, float delay )
+{
+ wait( delay )
+ ent.Destroy()
+}
+
+bool function IsValidPlayer( entity player )
+{
+ if ( !IsValid( player ) )
+ return false
+
+ if ( !player.IsPlayer() )
+ return false
+
+ if ( IsDisconnected( player ) )
+ return false
+
+ return true
+}
+
+bool function IsDisconnected( entity player )
+{
+ return player.p.isDisconnected
+}
+
+/****************************************************************************************************\
+/*
+|* PLAY FX
+\*
+\****************************************************************************************************/
+
+entity function PlayFX( asset effectName, vector org, vector ornull optionalAng = null, vector ornull overrideAngle = null )
+{
+ return __CreateFxInternal( effectName, null, "", org, optionalAng, C_PLAYFX_SINGLE, null, -1, null, overrideAngle )
+}
+
+entity function PlayFXWithControlPoint( asset effectName, vector org, entity cpoint1, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null, vector ornull overrideAngle = null, int _type = C_PLAYFX_SINGLE )
+{
+ return __CreateFxInternal( effectName, null, "", org, null, _type, cpoint1, visibilityFlagOverride, visibilityFlagEntOverride, overrideAngle)
+}
+
+entity function PlayFXOnEntityWithControlPoint( asset effectName, entity ent, entity cpoint1, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null, vector ornull overrideAngle = null, int _type = C_PLAYFX_SINGLE )
+{
+ return __CreateFxInternal( effectName, ent, "", null, null, _type, cpoint1, visibilityFlagOverride, visibilityFlagEntOverride, overrideAngle)
+}
+
+entity function PlayFXOnEntity( asset effectName, entity ent, string optionalTag = "", vector ornull optionalTranslation = null, vector ornull optionalRotation = null, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null, vector ornull overrideAngle = null )
+{
+ return __CreateFxInternal( effectName, ent, optionalTag, optionalTranslation, optionalRotation, C_PLAYFX_SINGLE, null, visibilityFlagOverride, visibilityFlagEntOverride, overrideAngle )
+}
+
+entity function PlayFXForPlayer( asset effectName, entity player, vector ornull org, vector ornull optionalAng = null )
+{
+ return __CreateFxInternal( effectName, null, "", org, optionalAng, C_PLAYFX_SINGLE, null, ENTITY_VISIBLE_TO_OWNER, player )
+}
+
+entity function PlayFXOnEntityForEveryoneExceptPlayer( asset effectName, entity ent, entity player, string optionalTag = "", vector ornull optionalTranslation = null, vector ornull optionalRotation = null )
+{
+ return __CreateFxInternal( effectName, ent, optionalTag, optionalTranslation, optionalRotation, C_PLAYFX_SINGLE, null, (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY), player )
+}
+
+entity function PlayFXForEveryoneExceptPlayer( asset effectName, entity player, vector ornull org, vector ornull optionalAng = null )
+{
+ return __CreateFxInternal( effectName, null, "", org, optionalAng, C_PLAYFX_SINGLE, null, (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY), player )
+}
+
+void function PlayFXOnTitanPlayerForTime( asset effectName, entity titan, string attachment, float duration )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "DisembarkingTitan" )
+ titan.EndSignal( "TitanEjectionStarted" )
+
+ entity fx = PlayFXOnEntityForEveryoneExceptPlayer( effectName, titan, titan, attachment )
+
+ OnThreadEnd(
+ function() : ( fx )
+ {
+ if ( IsValid(fx) )
+ {
+ fx.Destroy()
+ }
+ }
+ )
+
+ wait duration
+}
+
+entity function ClientStylePlayFXOnEntity( asset effectName, entity ent, string tag, float duration = 2.0 )
+{
+ string name = ent.GetScriptName()
+ ent.SetScriptName( UniqueString() ) // hack because you can only specify control points by name
+ // hack this is also not quite right because we can't specify the attachment type on the server... should be trivial to add in code:
+ // change DEFINE_FIELD( m_parentAttachmentType, FIELD_INTEGER ), to DEFINE_KEYFIELD( m_parentAttachmentType, FIELD_INTEGER, "attachmentType" ),
+ entity result = __CreateFxInternal( effectName, ent, tag, null, null, C_PLAYFX_SINGLE, ent, (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY), ent )
+ EntFireByHandle( result, "Kill", "", duration, null, null )
+ ent.SetScriptName( name )
+
+ return result
+}
+
+entity function PlayLoopFX( asset effectName, vector ornull org, vector ornull optionalAng = null )
+{
+ return __CreateFxInternal( effectName, null, "", org, optionalAng, C_PLAYFX_LOOP )
+}
+
+entity function PlayLoopFXOnEntity( asset effectName, entity ent, string optionalTag = "", vector ornull optionalTranslation = null, vector ornull optionalRotation = null, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null )
+{
+ return __CreateFxInternal( effectName, ent, optionalTag, optionalTranslation, optionalRotation, C_PLAYFX_LOOP, null, visibilityFlagOverride, visibilityFlagEntOverride )
+}
+
+entity function __CreateFxInternal( asset effectName, entity ent, string optionalTag = "", vector ornull optionalTranslation = <0,0,0>, vector ornull optionalRotation = <0,0,0>,
+ int _type = C_PLAYFX_SINGLE, entity cpointEnt1 = null, int visibilityFlagOverride = -1, entity visibilityFlagEntOverride = null, vector ornull overrideAngle = null )
+{
+ entity fx = CreateEntity( "info_particle_system" )
+ fx.SetValueForEffectNameKey( effectName )
+ if( visibilityFlagOverride != -1 )
+ fx.kv.VisibilityFlags = visibilityFlagOverride
+ else
+ fx.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ fx.kv.start_active = 1
+ fx.e.fxType = _type
+
+ if ( _type == C_PLAYFX_SINGLE )
+ fx.DoNotCreateFXOnRestore();
+
+ vector coreOrg
+ vector coreAng
+
+ //are we attaching to an ent?
+ if ( ent )
+ {
+ //are we attaching to a tag on an ent
+ if ( optionalTag != "" )
+ {
+ int attachID = ent.LookupAttachment( optionalTag )
+ coreOrg = ent.GetAttachmentOrigin( attachID )
+ coreAng = ent.GetAttachmentAngles( attachID )
+ }
+ else
+ {
+ coreOrg = ent.GetOrigin()
+ coreAng = ent.GetAngles()
+ }
+
+ fx.Code_SetTeam( ent.GetTeam() );
+ }
+ else //if not we're just playing in space
+ {
+ optionalTag = ""
+ coreOrg = < 0, 0, 0 >
+ coreAng = < 0, 0, 0 >
+ }
+
+ if ( optionalTranslation )
+ {
+ expect vector( optionalTranslation )
+ if ( ent )
+ coreOrg = PositionOffsetFromEnt( ent, optionalTranslation.x, optionalTranslation.y, optionalTranslation.z )
+ else
+ coreOrg = coreOrg + optionalTranslation
+ }
+
+ coreOrg = ClampToWorldspace( coreOrg )
+ fx.SetOrigin( coreOrg )
+
+ if ( overrideAngle )
+ {
+ expect vector( overrideAngle )
+ fx.SetAngles( overrideAngle )
+ }
+ else if ( optionalRotation )
+ {
+ expect vector( optionalRotation )
+ fx.SetAngles( coreAng + optionalRotation )
+ }
+ else
+ {
+ fx.SetAngles( coreAng )
+ }
+
+ if ( ent )
+ {
+ if ( !ent.IsMarkedForDeletion() ) // TODO: This is a hack for shipping. The real solution is to spawn the FX before deleting the parent entity.
+ {
+ fx.SetParent( ent, optionalTag, true )
+ }
+ }
+
+ if ( visibilityFlagEntOverride != null )
+ fx.SetOwner( visibilityFlagEntOverride )
+
+ if ( cpointEnt1 )
+ fx.kv.cpoint1 = cpointEnt1.GetTargetName()
+
+ DispatchSpawn( fx )
+ thread __DeleteFxInternal( fx )
+
+ //SetTargetName( fx, "FX_" + effectName )
+ return fx
+}
+
+void function __DeleteFxInternal( entity fx )
+{
+ //if it loops or is multiple then don't delete internally
+ if ( fx.e.fxType == C_PLAYFX_MULTIPLE )
+ return
+
+ if ( fx.e.fxType == C_PLAYFX_LOOP )
+ return
+
+ wait 30 //no way to know when an effect is over
+ if ( !IsValid( fx ) )
+ return
+
+ fx.ClearParent()
+ fx.Destroy()
+}
+
+void function StartFX( entity fx )
+{
+ Assert( fx.e.fxType == C_PLAYFX_LOOP, "Tried to use StartFX() on effect that is not LOOPING" )
+ EntFireByHandle( fx, "Start", "", 0, null, null )
+}
+
+void function StopFX( entity fx )
+{
+ Assert( fx.e.fxType == C_PLAYFX_LOOP, "Tried to use StopFX() on effect that is not LOOPING" )
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+}
+
+void function ReplayFX( entity fx )
+{
+ Assert( fx.e.fxType == C_PLAYFX_MULTIPLE, "Tried to use ReplayFX() on effect that is not MULTIPLE" )
+ //thread it because there is a WaitFrame() inside the function
+ thread __ReplayFXInternal( fx )
+}
+
+void function __ReplayFXInternal( entity fx )
+{
+ //for non-looping fx, we must stop first before we can fire again
+ EntFireByHandle( fx, "Stop", "", 0, null, null )
+ //we can't start in the same frame, WaitFrame() skips 1 false
+ //it should be noted that "WaitFrame()" doesn't work with a timescale above 1
+ WaitFrame()
+ //may have died since the last frame
+ if ( IsValid( fx ) )
+ EntFireByHandle( fx, "Start", "", 0, null, null )
+}
+
+void function Entity_StopFXArray( entity ent )
+{
+ foreach( fx in ent.e.fxArray )
+ {
+ if ( IsValid( fx ) )
+ {
+ StopFX( fx )
+ fx.Destroy()
+ }
+ }
+}
+
+/****************************************************************************************************\
+|* end play fx
+\****************************************************************************************************/
+
+
+table function GetDamageTableFromInfo( var damageInfo )
+{
+ table Table = {}
+ Table.damageSourceId <- DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ Table.origin <- DamageInfo_GetDamagePosition( damageInfo )
+ Table.force <- DamageInfo_GetDamageForce( damageInfo )
+ Table.scriptType <- DamageInfo_GetCustomDamageType( damageInfo )
+
+ return Table
+}
+
+bool function EntityInSolid( entity ent, entity ignoreEnt = null, int buffer = 0 ) //TODO: This function returns true for a player standing inside a friendly grunt. It also returns true if you are right up against a ceiling.Needs fixing for next game
+{
+ Assert( IsValid( ent ) )
+ int solidMask
+ vector mins
+ vector maxs
+ int collisionGroup
+ array<entity> ignoreEnts = []
+
+ ignoreEnts.append( ent )
+
+ if ( IsValid( ignoreEnt ) )
+ {
+ ignoreEnts.append( ignoreEnt )
+ }
+
+ if ( ent.IsTitan() )
+ solidMask = TRACE_MASK_TITANSOLID
+ else if ( ent.IsPlayer() )
+ solidMask = TRACE_MASK_PLAYERSOLID
+ else
+ solidMask = TRACE_MASK_NPCSOLID
+
+ if ( ent.IsPlayer() )
+ {
+ mins = ent.GetPlayerMins()
+ maxs = ent.GetPlayerMaxs()
+ collisionGroup = TRACE_COLLISION_GROUP_PLAYER
+ }
+ else
+ {
+ Assert( ent.IsNPC() )
+ mins = ent.GetBoundingMins()
+ maxs = ent.GetBoundingMaxs()
+ collisionGroup = TRACE_COLLISION_GROUP_NONE
+ }
+
+ if ( buffer > 0 )
+ {
+ mins.x -= float( buffer )
+ mins.y -= float( buffer )
+ maxs.x += float( buffer )
+ maxs.y += float( buffer )
+ }
+
+ // if we got into solid, teleport back to safe place
+ vector currentOrigin = ent.GetOrigin()
+ TraceResults result = TraceHull( currentOrigin, currentOrigin + < 0, 0, 1 >, mins, maxs, ignoreEnts, solidMask, collisionGroup )
+ //PrintTable( result )
+ //DrawArrow( result.endPos, Vector(0,0,0), 5, 150 )
+ if ( result.startSolid )
+ return true
+
+ return result.fraction < 1.0 // TODO: Probably not needed according to Jiesang. Fix after ship.
+}
+
+bool function EntityInSpecifiedEnt( entity ent, entity specifiedEnt, int buffer = 0 )
+{
+ Assert( IsValid( ent ) )
+ Assert( IsValid( specifiedEnt ) )
+
+ int solidMask
+ vector mins
+ vector maxs
+ int collisionGroup
+
+ if ( ent.IsTitan() )
+ solidMask = TRACE_MASK_TITANSOLID
+ else if ( ent.IsPlayer() )
+ solidMask = TRACE_MASK_PLAYERSOLID
+ else
+ solidMask = TRACE_MASK_NPCSOLID
+
+ if ( ent.IsPlayer() )
+ {
+ mins = ent.GetPlayerMins()
+ maxs = ent.GetPlayerMaxs()
+ collisionGroup = TRACE_COLLISION_GROUP_PLAYER
+ }
+ else
+ {
+ Assert( ent.IsNPC() )
+ mins = ent.GetBoundingMins()
+ maxs = ent.GetBoundingMaxs()
+ collisionGroup = TRACE_COLLISION_GROUP_NONE
+ }
+
+ if ( buffer > 0 )
+ {
+ mins.x -= float( buffer )
+ mins.y -= float( buffer )
+ maxs.x += float( buffer )
+ maxs.y += float( buffer )
+ }
+
+ // if we got into solid, teleport back to safe place
+ vector currentOrigin = ent.GetOrigin()
+ TraceResults result = TraceHull( currentOrigin, currentOrigin + < 0, 0, 1 >, mins, maxs, null, solidMask, collisionGroup )
+ //PrintTable( result )
+ //DrawArrow( result.endPos, Vector(0,0,0), 5, 150 )
+ if ( result.startSolid == false )
+ return false
+
+ return result.hitEnt == specifiedEnt
+}
+
+void function KillFromInfo( entity ent, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity weapon = DamageInfo_GetWeapon( damageInfo )
+ //float amount = DamageInfo_GetDamage( damageInfo )
+
+ // JFS: if the player became invalid, the attacker becomes the projectile which is bad
+ if ( attacker.IsProjectile() )
+ attacker = svGlobal.worldspawn
+
+ if ( !weapon )
+ weapon = attacker
+
+ table Table = GetDamageTableFromInfo( damageInfo )
+ Table.forceKill <- true
+
+ ent.TakeDamage( 9999, attacker, weapon, Table )
+}
+
+
+entity function GetPlayerTitanInMap( entity player )
+{
+ // temporarily flipped
+ entity petTitan = player.GetPetTitan()
+ if ( IsValid( petTitan ) && IsAlive( petTitan ) )
+ return petTitan
+
+ // first try to return the player's actual titan
+ if ( player.IsTitan() )
+ return player
+
+ return null
+}
+
+
+entity function GetPlayerTitanFromSouls( entity player )
+{
+ // returns the first owned titan found
+ array<entity> souls = GetTitanSoulArray()
+ foreach ( soul in souls )
+ {
+ if ( !IsValid( soul ) )
+ continue
+
+ if ( soul.GetBossPlayer() != player )
+ continue
+
+ if ( !IsAlive( soul.GetTitan() ) )
+ continue
+
+ return soul.GetTitan()
+ }
+
+ return null
+}
+
+void function DisableTitanShield( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+
+ soul.SetShieldHealth( 0 )
+ soul.SetShieldHealthMax( 0 )
+}
+
+void function DisableShield( entity ent )
+{
+ entity soul = ent.GetTitanSoul()
+
+ if ( soul )
+ {
+ DisableTitanShield( ent )
+ return
+ }
+
+ ent.SetShieldHealth( 0 )
+ ent.SetShieldHealthMax( 0 )
+}
+
+void function SetVelocityTowardsEntity( entity entToMove, entity targetEnt, float speed )
+{
+ Assert( speed > 0 )
+ Assert( IsValid( entToMove ) )
+ Assert( IsValid( targetEnt ) )
+
+ vector direction = ( targetEnt.GetWorldSpaceCenter() - entToMove.GetOrigin() )
+ direction = Normalize( direction ) * speed
+ entToMove.SetVelocity( direction )
+}
+
+void function SetVelocityTowardsEntityTag( entity entToMove, entity targetEnt, string targetTag, float speed )
+{
+ Assert( speed > 0 )
+ Assert( IsValid( entToMove ) )
+ Assert( IsValid( targetEnt ) )
+
+ int attachID = targetEnt.LookupAttachment( targetTag )
+ vector attachOrigin = targetEnt.GetAttachmentOrigin( attachID )
+
+ vector direction = ( attachOrigin - entToMove.GetOrigin() )
+ direction = Normalize( direction ) * speed
+ entToMove.SetVelocity( direction )
+}
+
+void function EntityDemigod_TryAdjustDamageInfo( entity ent, var damageInfo )
+{
+ float dmg = DamageInfo_GetDamage( damageInfo )
+ if ( dmg <= 0 )
+ return
+
+ if ( ent.IsTitan() && !GetDoomedState( ent ) ) //Allow demigod titans to go into doomed
+ return
+
+ int bottomLimit = 5
+ if ( ent.GetHealth() <= bottomLimit )
+ ent.SetHealth( bottomLimit + 1 ) //Set it up so that you at least take 1 damage, for hit indicators etc to trigger
+
+ int health = ent.GetHealth()
+
+ if ( health - dmg <= bottomLimit )
+ {
+ int newdmg = health - bottomLimit
+ DamageInfo_SetDamage( damageInfo, newdmg )
+ //printt( "setting damage to ", newdmg )
+ }
+}
+
+void function DebugDamageInfo( entity ent, var damageInfo )
+{
+ printt( "damage to " + ent + ": " + DamageInfo_GetDamage( damageInfo ) )
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ printt( "explosive: " + ( damageType & DF_EXPLOSION ) )
+ printt( "bullet: " + ( damageType & DF_BULLET ) )
+ printt( "gib: " + ( damageType & DF_GIB ) )
+
+ vector dampos = DamageInfo_GetDamagePosition( damageInfo )
+ vector org = DamageInfo_GetInflictor( damageInfo ).GetOrigin()
+ DebugDrawLine( dampos, org, 255, 0, 0, true, 10.0 )
+ DebugDrawLine( org, ent.GetOrigin(), 255, 255, 0, true, 10.0 )
+}
+
+vector function RandomVecInDome( vector dir )
+{
+ vector angles = VectorToAngles( dir )
+ vector forward = AnglesToForward( angles )
+ vector right = AnglesToRight( angles )
+ vector up = AnglesToUp( angles )
+
+ float offsetRight = RandomFloatRange( -1, 1 )
+ float offsetUp = RandomFloatRange( -1, 1 )
+ float offsetForward = RandomFloat( 1.0 )
+
+ vector endPos = < 0, 0, 0 >
+ endPos += forward * offsetForward
+ endPos += up * offsetUp
+ endPos += right * offsetRight
+ endPos.Norm()
+
+ return endPos
+}
+
+float function GetAnimEventTime( asset modelname, string anim, string event )
+{
+ entity dummy = CreatePropDynamic( modelname )
+ dummy.Hide()
+
+ float duration = dummy.GetSequenceDuration( anim )
+ float frac = dummy.GetScriptedAnimEventCycleFrac( anim, event )
+
+ dummy.Destroy()
+
+ //this might cause some issues in R2 - but we'll fix them as we go - it's important this doesn't silently fail
+ Assert( frac > 0.0, "event: " + event + " doesn't exist in animation: " + anim )
+ Assert( frac < 1.0, "event: " + event + " doesn't exist in animation: " + anim )
+
+ return duration * frac
+}
+
+void function SetDeathFuncName( entity npc, string functionNameString )
+{
+ Assert( npc.kv.deathScriptFuncName == "", "deathScriptFuncName was already set" )
+ npc.kv.deathScriptFuncName = functionNameString
+}
+
+void function ClearDeathFuncName( entity npc )
+{
+ npc.kv.deathScriptFuncName = ""
+}
+
+/*function PushSunLightAngles( x, y, z )
+{
+ entity clight = GetEnt( "env_cascade_light" )
+ Assert( clight )
+
+ clight.PushAngles( x, y, z )
+}
+
+function PopSunLightAngles()
+{
+ entity clight = GetEnt( "env_cascade_light" )
+ Assert( clight )
+
+ clight.PopAngles()
+}*/
+
+entity function GetPilotAntiPersonnelWeapon( entity player )
+{
+ array<entity> weaponsArray = player.GetMainWeapons()
+ foreach( weapon in weaponsArray )
+ {
+ int weaponType = weapon.GetWeaponType()
+ if ( weaponType == WT_SIDEARM || weaponType == WT_ANTITITAN )
+ continue;
+
+ return weapon
+ }
+
+ return null
+}
+
+entity function GetPilotSideArmWeapon( entity player )
+{
+ array<entity> weaponsArray = player.GetMainWeapons()
+ foreach( weapon in weaponsArray )
+ {
+ if ( weapon.GetWeaponType() == WT_SIDEARM )
+ return weapon
+ }
+
+ return null
+}
+
+entity function GetPilotAntiTitanWeapon( entity player )
+{
+ array<entity> weaponsArray = player.GetMainWeapons()
+ foreach( weapon in weaponsArray )
+ {
+ if ( weapon.GetWeaponType() == WT_ANTITITAN )
+ return weapon
+ }
+
+ return null
+}
+
+bool function PilotHasSniperWeapon( entity player )
+{
+ array<entity> weaponsArray = player.GetMainWeapons()
+ foreach ( weapon in weaponsArray )
+ {
+ if ( IsValid( weapon ) && weapon.GetWeaponInfoFileKeyField( "is_sniper" ) == 1 )
+ return true
+ }
+
+ return false
+}
+
+bool function PilotActiveWeaponIsSniper( entity player )
+{
+ entity weapon = player.GetActiveWeapon()
+
+ if ( IsValid( weapon ) && weapon.GetWeaponInfoFileKeyField( "is_sniper" ) == 1 )
+ return true
+
+ return false
+}
+
+void function ScreenFadeToColor( entity player, float r, float g, float b, float a, float fadeTime = 1.7, float holdTime = 0.0 )
+{
+ Assert( IsValid( player ) )
+
+ ScreenFade( player, r, g, b, a, fadeTime, holdTime, FFADE_OUT | FFADE_PURGE )
+}
+
+
+void function ScreenFadeFromColor( entity player, float r, float g, float b, float a, float fadeTime = 2.0, float holdTime = 2.0 )
+{
+ Assert( IsValid( player ) )
+
+ ScreenFade( player, r, g, b, a, fadeTime, holdTime, FFADE_IN | FFADE_PURGE )
+}
+
+void function ScreenFadeToBlack( entity player, float fadeTime, float holdTime )
+{
+ Assert( IsValid( player ) )
+
+ ScreenFade( player, 0, 0, 1, 255, fadeTime, holdTime, FFADE_OUT | FFADE_PURGE )
+}
+
+void function ScreenFadeFromBlack( entity player, float fadeTime = 2.0, float holdTime = 2.0 )
+{
+ Assert( IsValid( player ) )
+
+ ScreenFade( player, 0, 1, 0, 255, fadeTime, holdTime, FFADE_IN | FFADE_PURGE )
+}
+
+void function ScreenFadeToBlackForever( entity player, float fadeTime = 1.7 )
+{
+ Assert( IsValid( player ) )
+
+ ScreenFade( player, 0, 0, 1, 255, fadeTime, 0, FFADE_OUT | FFADE_STAYOUT )
+}
+
+/*******************************************************
+/ Server Effects
+/
+/ CreateServerEffect_Friendly( effectName, team )
+/ CreateServerEffect_Enemy( effectName, team )
+/ CreateServerEffect_Owner( effectName, owner )
+/ SetServerEffectControlPoint( effectEnt, controlPoint, vecValue )
+/ StartServerEffect( effectEnt )
+/ StartServerEffectInWorld( effectEnt, origin, angles )
+/ StartServerEffectOnEntity( effectEnt, ent, tag = null )
+/
+*******************************************************/
+
+// NOTE: this does not play the effect, use StartEffectOnEntity
+entity function CreateServerEffect_Friendly( asset effectName, int team )
+{
+ entity friendlyEffect = _CreateServerEffect( effectName, 2 ) // ENTITY_VISIBLE_TO_FRIENDLY
+ friendlyEffect.kv.TeamNum = team
+ friendlyEffect.kv.teamnumber = team
+
+ return friendlyEffect
+}
+
+// NOTE: this does not play the effect, use StartEffectOnEntity
+entity function CreateServerEffect_Enemy( asset effectName, int team )
+{
+ entity enemyEffect = _CreateServerEffect( effectName, 4 ) // ENTITY_VISIBLE_TO_ENEMY
+ enemyEffect.kv.TeamNum = team
+ enemyEffect.kv.teamnumber = team
+
+ return enemyEffect
+}
+
+// NOTE: this does not play the effect, use StartEffectOnEntity
+entity function CreateServerEffect_Owner( asset effectName, entity owner )
+{
+ Assert( IsValid( owner ) )
+
+ entity ownerEffect = _CreateServerEffect( effectName, 1 ) // ENTITY_VISIBLE_TO_OWNER
+ ownerEffect.kv.TeamNum = owner.GetTeam()
+ ownerEffect.SetOwner( owner )
+
+ return ownerEffect
+}
+
+entity function _CreateServerEffect( asset effectName, int visFlags )
+{
+ entity serverEffect = CreateEntity( "info_particle_system" )
+ serverEffect.SetOrigin( <0, 0, 0> )
+ serverEffect.SetAngles( <0, 0, 0> )
+ serverEffect.SetValueForEffectNameKey( effectName )
+ serverEffect.kv.start_active = 1
+ serverEffect.kv.VisibilityFlags = visFlags
+
+ thread _ServerEffectCleanup( serverEffect )
+ return serverEffect
+}
+
+entity function SetServerEffectControlPoint( entity effectEnt, int controlPoint, vector vecValue ) // for now, only support static
+{
+ entity helper = CreateEntity( "info_placement_helper" )
+ helper.SetOrigin( vecValue )
+ effectEnt.SetControlPointEnt( controlPoint, helper )
+ effectEnt.e.fxControlPoints.append( helper )
+
+ return helper
+}
+
+void function StartServerEffect( entity effectEnt )
+{
+ DispatchSpawn( effectEnt )
+}
+
+void function StartServerEffectInWorld( entity effectEnt, vector origin, vector angles )
+{
+ effectEnt.SetOrigin( origin )
+ effectEnt.SetAngles( angles )
+ DispatchSpawn( effectEnt )
+}
+
+void function StartServerEffectOnEntity( entity effectEnt, entity ent, string tag = "" )
+{
+ Assert( IsValid( effectEnt ) )
+ Assert( IsValid( ent ) )
+
+ if ( tag != "" )
+ {
+ int attachID = ent.LookupAttachment( tag )
+ vector origin = ent.GetAttachmentOrigin( attachID )
+ vector angles = ent.GetAttachmentAngles( attachID )
+
+ origin = ClampToWorldspace( origin )
+ effectEnt.SetOrigin( origin )
+ effectEnt.SetAngles( angles )
+ effectEnt.SetParent( ent, tag, true )
+ }
+ else
+ {
+ effectEnt.SetParent( ent )
+ }
+
+ DispatchSpawn( effectEnt )
+}
+
+void function _ServerEffectCleanup( entity effectEnt )
+{
+ effectEnt.WaitSignal( "OnDestroy" )
+
+ foreach ( entity controlPoint in effectEnt.e.fxControlPoints )
+ {
+ controlPoint.Destroy()
+ }
+}
+
+float function GetYaw( vector org1, vector org2 )
+{
+ vector vec = org2 - org1
+ vector angles = VectorToAngles( vec )
+ return angles.y
+}
+
+void function HideName( entity ent )
+{
+ ent.SetNameVisibleToFriendly( false )
+ ent.SetNameVisibleToEnemy( false )
+ ent.SetNameVisibleToNeutral( false )
+ ent.SetNameVisibleToOwner( false )
+}
+
+void function ShowName( entity ent )
+{
+ ent.SetNameVisibleToFriendly( true )
+ ent.SetNameVisibleToEnemy( true )
+ ent.SetNameVisibleToNeutral( true )
+ ent.SetNameVisibleToOwner( true )
+}
+
+void function ShowNameToAllExceptOwner( entity ent )
+{
+ ent.SetNameVisibleToFriendly( true )
+ ent.SetNameVisibleToEnemy( true )
+ ent.SetNameVisibleToNeutral( true )
+ ent.SetNameVisibleToOwner( false )
+}
+
+void function EmitSoundOnEntityToTeamExceptPlayer( entity ent, string sound, int team, entity excludePlayer )
+{
+ array<entity> players = GetPlayerArrayOfTeam( team )
+
+ foreach ( player in players )
+ {
+ if ( player == excludePlayer )
+ continue
+
+ EmitSoundOnEntityOnlyToPlayer( ent, player, sound )
+ }
+}
+
+#if DEV
+// DEV function to toggle player view between the skybox and the real world.
+void function ToggleSkyboxView( float scale = 0.001 )
+{
+ entity player = GetEntByIndex( 1 )
+
+ entity skyboxCamLevel = GetEnt( "skybox_cam_level" )
+
+ Assert( IsValid( skyboxCamLevel ), "Could not find a sky_camera entity named \"skybox_cam_level\" in this map." )
+
+ vector skyOrigin = skyboxCamLevel.GetOrigin()
+
+ if ( !file.isSkyboxView )
+ {
+ if ( !player.IsNoclipping() )
+ {
+ ClientCommand( player, "noclip" )
+ wait( 0.25 )
+ }
+
+ ClientCommand( player, "sv_noclipspeed 0.1" )
+ file.isSkyboxView = true
+ vector offset = player.GetOrigin()
+ offset *= scale
+
+ player.SetOrigin( skyOrigin + offset - < 0.0, 0.0, 60.0 - (60.0 * scale) > )
+ }
+ else
+ {
+ ClientCommand( player, "sv_noclipspeed 5" )
+ file.isSkyboxView = false
+ vector offset = player.GetOrigin() - skyOrigin + < 0.0, 0.0, 60.0 - (60.0 * scale) >
+ offset *= 1.0 / scale
+
+ offset = ClampToWorldspace( offset )
+
+ player.SetOrigin( offset )
+ }
+}
+
+void function DamageRange( float value, float headShotMultiplier, int playerHealth = 200 )
+{
+ printt( "Damage Range: ", value, headShotMultiplier )
+
+ float bodyShot = value
+ float headShot = value * headShotMultiplier
+
+ int maxHeadshots = 0
+
+ int simHealth = playerHealth
+ while ( simHealth > 0 )
+ {
+ simHealth = (simHealth.tofloat() - headShot).tointeger()
+ maxHeadshots++
+ }
+
+ printt( "HeadShots: BodyShots: Total:" )
+
+ simHealth = playerHealth
+ int numHeadshots = 0
+ while ( numHeadshots < maxHeadshots )
+ {
+ simHealth = playerHealth
+ for ( int hsIdx = 0; hsIdx < numHeadshots; hsIdx++ )
+ {
+ simHealth = (simHealth.tofloat() - headShot).tointeger()
+ }
+
+ int numBodyShots = 0
+ while ( simHealth > 0 )
+ {
+ simHealth = (simHealth.tofloat() - bodyShot).tointeger()
+ numBodyShots++
+ }
+ printt( format( "%i %i %i", numHeadshots, numBodyShots, numHeadshots + numBodyShots ) )
+ numHeadshots++
+ }
+
+ printt( format( "%i %i %i", numHeadshots, 0, numHeadshots ) )
+}
+
+#endif // DEV
+
+void function MuteAll( entity player, int fadeOutTime = 2 )
+{
+ //DumpStack(2)
+ Assert( player.IsPlayer() )
+
+ Assert( fadeOutTime >= 1 && fadeOutTime <= 4 , "Only have 4 kinds of fadeout to play, time must be in the range [1,4]" )
+
+ string fadeoutSoundString
+
+ switch( fadeOutTime )
+ {
+ case 1:
+ fadeoutSoundString = "1_second_fadeout"
+ break
+
+ case 2:
+ fadeoutSoundString = "2_second_fadeout"
+ break
+
+ case 3:
+ fadeoutSoundString = "3_second_fadeout"
+ break
+
+ case 4:
+ fadeoutSoundString = "4_second_fadeout"
+ break
+
+ default:
+ unreachable
+
+ }
+
+ printt( "Apply " + fadeoutSoundString + " to player: " + player )
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, fadeoutSoundString )
+}
+
+//Mutes all except halftime sounds and dialogue
+void function MuteHalfTime( entity player )
+{
+ Assert( player.IsPlayer() )
+ printt( "Apply HalfTime_fadeout to player: " + player )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HalfTime_fadeout" )
+}
+
+void function UnMuteAll( entity player )
+{
+ //DumpStack(2)
+ Assert( player.IsPlayer() )
+
+ //Just stop all the possible fadeout sounds.
+ printt( "Stopping all fadeout for player: " + player )
+ StopSoundOnEntity( player, "1_second_fadeout" )
+ StopSoundOnEntity( player, "2_second_fadeout" )
+ StopSoundOnEntity( player, "3_second_fadeout" )
+ StopSoundOnEntity( player, "4_second_fadeout" )
+ StopSoundOnEntity( player, "HalfTime_fadeout" )
+}
+
+void function AllPlayersMuteAll( int time = MUTEALLFADEIN )
+{
+ array<entity> players = GetPlayerArray()
+
+ foreach ( player in players )
+ MuteAll( player, time )
+}
+
+void function AllPlayersUnMuteAll()
+{
+ array<entity> players = GetPlayerArray()
+
+ foreach ( player in players )
+ UnMuteAll( player )
+}
+
+void function TakeAmmoFromPlayer( entity player )
+{
+ array<entity> mainWeapons = player.GetMainWeapons()
+ array<entity> offhandWeapons = player.GetOffhandWeapons()
+
+ foreach ( weapon in mainWeapons )
+ {
+ weapon.SetWeaponPrimaryAmmoCount( 0 )
+
+ if ( weapon.GetWeaponPrimaryClipCountMax() > 0 )
+ weapon.SetWeaponPrimaryClipCount( 0 )
+ }
+
+ foreach ( weapon in offhandWeapons )
+ {
+ weapon.SetWeaponPrimaryAmmoCount( 0 )
+
+ if ( weapon.GetWeaponPrimaryClipCountMax() > 0 )
+ weapon.SetWeaponPrimaryClipCount( 0 )
+ }
+}
+
+
+bool function NearFlagSpawnPoint( vector dropPoint )
+{
+ if ( "flagSpawnPoint" in level && IsValid( level.flagSpawnPoint ) )
+ {
+ vector fspOrigin = expect entity( level.flagSpawnPoint ).GetOrigin()
+ if ( Distance( fspOrigin, dropPoint ) < SAFE_TITANFALL_DISTANCE_CTF )
+ return true
+ }
+
+ if ( "flagReturnPoint" in level && IsValid( level.flagReturnPoint ) )
+ {
+ vector fspOrigin = expect entity( level.flagReturnPoint ).GetOrigin()
+ if ( Distance( fspOrigin, dropPoint ) < SAFE_TITANFALL_DISTANCE_CTF )
+ return true
+ }
+
+ if ( "flagSpawnPoints" in level )
+ {
+ foreach ( flagSpawnPoint in svGlobal.flagSpawnPoints )
+ {
+ vector fspOrigin = flagSpawnPoint.GetOrigin()
+ if ( Distance( fspOrigin, dropPoint ) < SAFE_TITANFALL_DISTANCE_CTF )
+ return true
+ }
+ }
+
+ return false
+}
+
+bool function HasCinematicFlag( entity player, int flag )
+{
+ Assert( player.IsPlayer() )
+ Assert( IsValid( player ) )
+ return ( player.GetCinematicEventFlags() & flag ) != 0
+}
+
+void function AddCinematicFlag( entity player, int flag )
+{
+ Assert( player.IsPlayer() )
+ Assert( IsValid( player ) )
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() | flag )
+ player.Signal( "CE_FLAGS_CHANGED" )
+}
+
+void function RemoveCinematicFlag( entity player, int flag )
+{
+ Assert( player.IsPlayer() )
+ Assert( IsValid( player ) )
+ player.SetCinematicEventFlags( player.GetCinematicEventFlags() & ( ~flag ) )
+ player.Signal( "CE_FLAGS_CHANGED" )
+}
+
+void function SkyScaleDefault( entity ent, float time = 1.0 )
+{
+ if ( IsValid( ent ) )
+ ent.LerpSkyScale( SKYSCALE_DEFAULT, time )
+}
+
+void function MoveSpawn( string targetName, vector origin, vector angles )
+{
+ entity ent = GetEnt( targetName )
+ ent.SetOrigin( origin )
+ ent.SetAngles( angles )
+}
+
+/*
+function CheckDailyChallengeAchievement( entity player )
+{
+ if ( player.GetPersistentVar( "cu8achievement.ach_allDailyChallengesForDay" ) == true )
+ return
+
+ int maxRefs = PersistenceGetArrayCount( "activeDailyChallenges" )
+ int todaysDailiesComplete = 0
+ int today = Daily_GetDayForCurrentTime()
+ for ( int i = 0; i < maxRefs; i++ )
+ {
+ int day = player.GetPersistentVarAsInt( "activeDailyChallenges[" + i + "].day" )
+ if ( day != today )
+ continue
+
+ local ref = player.GetPersistentVar( "activeDailyChallenges[" + i + "].ref" )
+ if ( !IsChallengeComplete( ref, player ) )
+ continue
+
+ todaysDailiesComplete++
+ }
+
+ if ( todaysDailiesComplete >= 3 )
+ player.SetPersistentVar( "cu8achievement.ach_allDailyChallengesForDay", true )
+}
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Get an array of all linked entities targeted to one after the other down the chain
+array<entity> function GetEntityTargetChain_Deprecated( entity ent )
+{
+ array<entity> entityChain = []
+ entity currentEnt = ent
+ entity nextEnt
+
+ while ( true )
+ {
+ nextEnt = GetEnt( currentEnt.GetTarget_Deprecated() )
+ if ( IsValid( nextEnt ) )
+ entityChain.append( nextEnt )
+ else
+ return entityChain
+ currentEnt = nextEnt
+ }
+
+ unreachable
+}
+
+void function PlayImpactFXTable( vector origin, entity owner, string impactFX, int flags = 0 )
+{
+ Explosion(
+ origin, //center,
+ owner, //attacker,
+ owner, //inflictor,flags
+ 0, //damage,
+ 0, //damageHeavyArmor,
+ 1, //innerRadius,
+ 1, //outerRadius,
+ flags, //flags,
+ origin, //projectileLaunchOrigin,
+ 0, //explosionForce,
+ damageTypes.explosive, //scriptDamageFlags,
+ -1, //scriptDamageSourceIdentifier,
+ impactFX ) //impactEffectTableName
+}
+
+void function SetSignalDelayed( entity ent, string signal, float delay )
+{
+ thread __SetSignalDelayedThread( ent, signal, delay )
+}
+
+void function __SetSignalDelayedThread( entity ent, string signal, float delay )
+{
+ EndSignal( ent, signal ) // so that if we call this again with the same signal on the same ent we won't get multiple signal events.
+
+ wait delay
+ if ( IsValid( ent ) )
+ Signal( ent, signal )
+}
+
+#if DEV
+table function GetPlayerPos( entity player = null )
+{
+ if ( !player )
+ player = gp()[0]
+
+ vector org = player.GetOrigin()
+ vector vec = player.GetViewVector()
+ vector ang = VectorToAngles( vec )
+
+ return { origin = org, angles = ang }
+}
+
+string function GetScriptPos( entity player = null )
+{
+ table playerPos = GetPlayerPos( player )
+ vector origin = expect vector( playerPos.origin )
+ vector angles = expect vector( playerPos.angles )
+
+ string returnStr = CreateOriginAnglesString( origin, <0, angles.y, 0> )
+ return returnStr
+}
+
+string function CreateOriginAnglesString( vector origin, vector angles )
+{
+ string returnStr = "< " + origin.x + ", " + origin.y + ", " + origin.z + " >, < " + angles.x + ", " + angles.y + ", " + angles.z + " >"
+ return returnStr
+}
+
+void function DistCheck_SetTestPoint()
+{
+ svGlobal.distCheckTestPoint = expect vector( GetPlayerPos().origin )
+ printt( "DistCheck test point set to:", svGlobal.distCheckTestPoint )
+}
+
+void function DistCheck()
+{
+ vector here = expect vector( GetPlayerPos().origin )
+ float dist = Distance( here, svGlobal.distCheckTestPoint )
+ printt( "Distance:", dist, "units from", svGlobal.distCheckTestPoint )
+}
+#endif // DEV
+
+void function ClearChildren( entity parentEnt ) //Probably should have code give us a GetChildren() function that returns a list instead of having to iterate through NextMovePeer
+{
+ entity childEnt = parentEnt.FirstMoveChild()
+ entity nextChildEnt
+
+ while ( childEnt != null )
+ {
+ nextChildEnt = childEnt.NextMovePeer()
+ childEnt.ClearParent()
+
+ childEnt = nextChildEnt
+ }
+}
+
+void function ForceTimeLimitDone()
+{
+ level.devForcedWin = true
+ level.devForcedTimeLimit = true
+ svGlobal.levelEnt.Signal( "devForcedWin" )
+ ServerCommand( "mp_enabletimelimit 1" )
+ ServerCommand( "mp_enablematchending 1" )
+}
+
+
+void function UpdateBadRepPresent()
+{
+ array<entity> players = GetPlayerArray()
+ bool found = false
+/* always set to false for now
+ foreach ( player in players )
+ {
+ if ( player.HasBadReputation() )
+ {
+ found = true
+ break
+ }
+ }
+*/
+ level.nv.badRepPresent = found
+ level.ui.badRepPresent = found
+}
+
+void function Dev_PrintMessage( entity player, string text, string subText = "", float duration = 7.0, string soundAlias = "" )
+{
+ #if DEV
+ // Build the message on the client
+ string sendMessage
+ for ( int textType = 0 ; textType < 2 ; textType++ )
+ {
+ sendMessage = textType == 0 ? text : subText
+
+ for ( int i = 0; i < sendMessage.len(); i++ )
+ {
+ Remote_CallFunction_NonReplay( player, "Dev_BuildClientMessage", textType, sendMessage[i] )
+ }
+ }
+ Remote_CallFunction_NonReplay( player, "Dev_PrintClientMessage", duration )
+ if ( soundAlias != "" )
+ EmitSoundOnEntity( player, soundAlias )
+ #endif
+}
+
+bool function IsAttackDefendBased() //If needed to, we can make this a .nv and then move this function into utility_shared
+{
+ return expect bool( level.attackDefendBased )
+}
+
+bool function IsRoundBasedUsingTeamScore() //If needed to, we can make this a .nv and then move this function into utility_shared
+{
+ return IsRoundBased() && expect bool( level.roundBasedUsingTeamScore )
+}
+
+bool function ShouldResetRoundBasedTeamScore()
+{
+ return IsRoundBased() && svGlobal.roundBasedTeamScore_RoundReset
+}
+
+void function CreateZipline( vector startPos, vector endPos )
+{
+ string startpointName = UniqueString( "rope_startpoint" )
+ string endpointName = UniqueString( "rope_endpoint" )
+
+ entity rope_start = CreateEntity( "move_rope" )
+ SetTargetName( rope_start, startpointName )
+ rope_start.kv.NextKey = endpointName
+ rope_start.kv.MoveSpeed = 64
+ rope_start.kv.Slack = 25
+ rope_start.kv.Subdiv = "2"
+ rope_start.kv.Width = "2"
+ rope_start.kv.Type = "0"
+ rope_start.kv.TextureScale = "1"
+ rope_start.kv.RopeMaterial = "cable/zipline.vmt"
+ rope_start.kv.PositionInterpolator = 2
+ rope_start.kv.Zipline = "1"
+ rope_start.kv.ZiplineAutoDetachDistance = "150"
+ rope_start.kv.ZiplineSagEnable = "0"
+ rope_start.kv.ZiplineSagHeight = "50"
+ rope_start.SetOrigin( startPos )
+
+ entity rope_end = CreateEntity( "keyframe_rope" )
+ SetTargetName( rope_end, endpointName )
+ rope_end.kv.MoveSpeed = 64
+ rope_end.kv.Slack = 25
+ rope_end.kv.Subdiv = "2"
+ rope_end.kv.Width = "2"
+ rope_end.kv.Type = "0"
+ rope_end.kv.TextureScale = "1"
+ rope_end.kv.RopeMaterial = "cable/zipline.vmt"
+ rope_end.kv.PositionInterpolator = 2
+ rope_end.kv.Zipline = "1"
+ rope_end.kv.ZiplineAutoDetachDistance = "150"
+ rope_end.kv.ZiplineSagEnable = "0"
+ rope_end.kv.ZiplineSagHeight = "50"
+ rope_end.SetOrigin( endPos )
+
+ DispatchSpawn( rope_start )
+ DispatchSpawn( rope_end )
+}
+
+string function GetNPCTitanSettingFile( entity titan )
+{
+ Assert( titan.IsTitan(), titan + " is not a titan" )
+ return titan.ai.titanSettings.titanSetFile
+}
+
+void function DecodeBitField( int bitField )
+{
+ for ( int bitIndex = 0; bitIndex < 32; bitIndex++ )
+ {
+ if ( bitField & (1 << bitIndex) )
+ {
+ printt( "Comparison: ", bitField, "& ( 1 <<", bitIndex, ") = ", bitField & (1 << bitIndex) )
+ printt( "BIT SET: ", bitIndex, bitField, 1 << bitIndex )
+ }
+ }
+}
+
+void function DropWeapon( entity npc )
+{
+ entity weapon = npc.GetActiveWeapon()
+ if ( !weapon )
+ return
+
+ string name = weapon.GetWeaponClassName()
+
+ // giving the weapon you have drops a new one in its place
+ npc.GiveWeapon( name )
+ npc.TakeActiveWeapon()
+}
+
+void function TestGiveGunTo( int index, string weaponName )
+{
+ entity ent = GetEntByIndex( index )
+ if ( !ent )
+ {
+ printt( "No entity for index:", index )
+ return;
+ }
+
+ TakePrimaryWeapon( ent )
+ ent.GiveWeapon( weaponName )
+ ent.SetActiveWeaponByName( weaponName )
+}
+
+string function GetEditorClass( entity self )
+{
+ if ( self.HasKey( "editorclass" ) )
+ return expect string( self.kv.editorclass )
+
+ return ""
+}
+
+bool function TitanHasRegenningShield( entity soul )
+{
+ if ( !TitanShieldRegenEnabled() )
+ return false
+
+ if ( !TitanShieldDecayEnabled() )
+ return true
+
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_BOOST ) )
+ return true
+
+ return false
+}
+
+void function DelayShieldDecayTime( entity soul, float delay )
+{
+ soul.e.nextShieldDecayTime = Time() + delay
+}
+
+void function HighlightWeapon( entity weapon )
+{
+#if HAS_WEAPON_PICKUP_HIGHLIGHT
+ if ( weapon.IsLoadoutPickup() )
+ {
+ Highlight_SetOwnedHighlight( weapon, "sp_loadout_pickup" )
+ Highlight_SetNeutralHighlight( weapon, "sp_loadout_pickup" )
+ }
+ else
+ {
+ Highlight_SetOwnedHighlight( weapon, "weapon_drop_active" )
+ Highlight_SetNeutralHighlight( weapon, "weapon_drop_normal" )
+ }
+#endif // #if HAS_WEAPON_PICKUP_HIGHLIGHT
+}
+
+void function WaitTillLookingAt( entity player, entity ent, bool doTrace, float degrees, float minDist = 0, float timeOut = 0, entity trigger = null, string failsafeFlag = "" )
+{
+ EndSignal( ent, "OnDestroy" )
+ EndSignal( player, "OnDeath" )
+
+ //trigger = the trigger ther player must be touching while doing the check
+ //failsafeFlag = bypass everything if this flag gets set
+
+ if ( failsafeFlag != "" )
+ EndSignal( level, failsafeFlag )
+
+ float minDistSqr = minDist * minDist
+ Assert( minDistSqr >= 0 )
+ float timeoutTime = Time() + timeOut
+
+ while( true )
+ {
+
+ if ( timeOut > 0 && Time() > timeoutTime )
+ break
+
+ if ( failsafeFlag != "" && Flag( failsafeFlag ) )
+ break
+
+ // Within range?
+ if ( minDistSqr > 0 && DistanceSqr( player.GetOrigin(), ent.GetOrigin() ) > minDistSqr )
+ {
+ WaitFrame()
+ continue
+ }
+
+ // Touching trigger?
+ if ( ( trigger != null ) && ( !trigger.IsTouching( player ) ) )
+ {
+ WaitFrame()
+ continue
+ }
+
+ if ( PlayerCanSee( player, ent, doTrace, degrees ) )
+ break
+
+ WaitFrame()
+ }
+}
+
+void function SetTargetName( entity ent, string name )
+{
+ ent.SetValueForKey( "targetname", name )
+}
+
+ZipLine function CreateZipLine( vector start, vector end, int autoDetachDistance = 150, float ziplineMoveSpeedScale = 1.0 )
+{
+ string midpointName = UniqueString( "rope_midpoint" )
+ string endpointName = UniqueString( "rope_endpoint" )
+
+ entity rope_start = CreateEntity( "move_rope" )
+ rope_start.kv.NextKey = midpointName
+ rope_start.kv.MoveSpeed = 0
+ rope_start.kv.ZiplineMoveSpeedScale = ziplineMoveSpeedScale
+ rope_start.kv.Slack = 0
+ rope_start.kv.Subdiv = 0
+ rope_start.kv.Width = "2"
+ rope_start.kv.TextureScale = "1"
+ rope_start.kv.RopeMaterial = "cable/zipline.vmt"
+ rope_start.kv.PositionInterpolator = 2
+ rope_start.kv.Zipline = "1"
+ rope_start.kv.ZiplineAutoDetachDistance = string( autoDetachDistance )
+ rope_start.kv.ZiplineSagEnable = "0"
+ rope_start.kv.ZiplineSagHeight = "0"
+ rope_start.SetOrigin( start )
+
+ entity rope_mid = CreateEntity( "keyframe_rope" )
+ SetTargetName( rope_mid, midpointName )
+ rope_start.kv.NextKey = endpointName
+ rope_mid.SetOrigin( ( start + end ) * 0.5 )
+ //rope_mid.SetOrigin( start )
+
+ entity rope_end = CreateEntity( "keyframe_rope" )
+ SetTargetName( rope_end, endpointName )
+ rope_end.SetOrigin( end )
+
+ // Dispatch spawn entities
+ DispatchSpawn( rope_start )
+ DispatchSpawn( rope_mid )
+ DispatchSpawn( rope_end )
+
+ ZipLine zipLine
+ zipLine.start = rope_start
+ zipLine.mid = rope_mid
+ zipLine.end = rope_end
+
+ return zipLine
+}
+
+entity function GetPlayerFromEntity( entity ent )
+{
+ entity player = null
+
+ if ( ent.IsPlayer() )
+ {
+ player = ent
+ }
+ else if ( ent.IsNPC() )
+ {
+ player = ent.GetBossPlayer()
+ }
+ else
+ {
+ player = ent.GetOwner()
+ if ( !player || !player.IsPlayer() )
+ return null
+ }
+
+ if ( IsValid_ThisFrame( player ) )
+ return player
+
+ return null
+}
+
+void function SetHumanRagdollImpactTable( entity ent )
+{
+ ent.SetRagdollImpactFX( HUMAN_RAGDOLL_IMPACT_TABLE_IDX )
+}
+
+bool function ScriptManagedEntArrayContains( int handle, entity ent )
+{
+ array< entity > ents = GetScriptManagedEntArray( handle )
+ foreach ( ent in ents )
+ {
+ if ( ent == ent )
+ return true
+ }
+
+ return false
+}
+
+void function HideCrit( entity ent )
+{
+ int bodyGroupIndex = ent.FindBodyGroup( "hitpoints" )
+
+ if ( bodyGroupIndex == -1 )
+ {
+ return
+ }
+
+ ent.SetBodygroup( bodyGroupIndex, 1 )
+}
+
+void function ShowCrit( entity ent )
+{
+ int bodyGroupIndex = ent.FindBodyGroup( "hitpoints" )
+
+ if ( bodyGroupIndex == -1 )
+ {
+ return
+ }
+
+ ent.SetBodygroup( bodyGroupIndex, 0 )
+}
+
+
+#if DEV
+void function TeleportEnemyBotToView()
+{
+ entity player = gp()[0]
+
+ TraceResults traceResults = PlayerViewTrace( player )
+
+ if ( traceResults.fraction >= 1.0 )
+ return
+
+ array<entity> players = GetPlayerArrayOfEnemies_Alive( player.GetTeam() )
+ foreach ( enemy in players )
+ {
+ if ( !enemy.IsBot() )
+ continue
+
+ enemy.SetOrigin( traceResults.endPos )
+ return
+ }
+}
+
+void function TeleportEntityToView( entity ent )
+{
+ entity player = gp()[0]
+
+ TraceResults traceResults = PlayerViewTrace( player )
+
+ if ( traceResults.fraction >= 1.0 )
+ return
+
+ ent.SetOrigin( traceResults.endPos )
+ //traceResults.surfaceNormal
+}
+
+void function TeleportFriendlyBotToView()
+{
+ entity player = gp()[0]
+
+ TraceResults traceResults = PlayerViewTrace( player )
+
+ if ( traceResults.fraction >= 1.0 )
+ return
+
+ array<entity> players = GetPlayerArrayOfTeam_Alive( player.GetTeam() )
+ foreach ( enemy in players )
+ {
+ if ( !enemy.IsBot() )
+ continue
+
+ enemy.SetOrigin( traceResults.endPos )
+ return
+ }
+}
+
+void function TeleportBotToAbove()
+{
+ entity player = gp()[0]
+
+ array<entity> players = GetPlayerArray_AlivePilots()
+ foreach ( enemy in players )
+ {
+ if ( !enemy.IsBot() )
+ continue
+
+ enemy.SetOrigin( player.GetOrigin() + < 0, 0, 512 > )
+ return
+ }
+}
+
+TraceResults function PlayerViewTrace( entity player, float distance = 10000 )
+{
+ vector eyePosition = player.EyePosition()
+ vector viewVector = player.GetViewVector()
+
+ TraceResults traceResults = TraceLine( eyePosition, eyePosition + viewVector * distance, player, TRACE_MASK_SHOT_BRUSHONLY, TRACE_COLLISION_GROUP_NONE )
+
+ return traceResults
+}
+#endif
+
+void function ClearPlayerAnimViewEntity( entity player, float time = 0.3 )
+{
+ entity viewEnt = player.GetFirstPersonProxy()
+ viewEnt.HideFirstPersonProxy()
+ viewEnt.Anim_Stop()
+
+ player.AnimViewEntity_SetLerpOutTime( time )
+ player.AnimViewEntity_Clear()
+ player.p.currViewConeFunction = null
+}
+
+
+void function BrushMoves( entity brush )
+{
+ float moveTime = float( brush.kv.move_time )
+ int movedir = int( brush.kv.movedirection )
+
+ BrushMovesInDirection( brush, movedir, moveTime )
+}
+
+
+void function BrushMovesInDirection( entity ent, int dir, float moveTime = 0, float blendIn = 0, float blendOut = 0, float lip = 8 )
+{
+ entity mover = CreateOwnedScriptMover( ent )
+ OnThreadEnd(
+ function() : ( ent, mover )
+ {
+ if ( IsValid( ent ) )
+ ent.ClearParent()
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ }
+ )
+
+ dir %= 360
+ if ( dir > 180 )
+ dir -= 360
+ else if ( dir < -180 )
+ dir += 360
+
+ string moveAxis = GetMoveAxisFromDir( dir )
+
+ ent.SetParent( mover )
+ vector origin = ent.GetOrigin()
+ float moveAmount
+ if ( ent.HasKey( "move_amount" ) )
+ {
+ moveAmount = float( ent.kv.move_amount ) - lip
+ switch ( moveAxis )
+ {
+ case "x":
+ case "y":
+ if ( dir < 0 )
+ moveAmount *= -1
+ break
+
+ case "z":
+ if ( dir == -1 )
+ moveAmount *= -1
+ break
+ }
+ }
+ else
+ {
+ switch ( moveAxis )
+ {
+ case "x":
+ moveAmount = GetEntWidth( ent ) - lip
+ if ( dir < 0 )
+ moveAmount *= -1
+ break
+
+ case "y":
+ moveAmount = GetEntDepth( ent ) - lip
+ if ( dir < 0 )
+ moveAmount *= -1
+ break
+
+ case "z":
+ moveAmount = GetEntHeight( ent ) - lip
+ if ( dir == -1 )
+ moveAmount *= -1
+ break
+ }
+ }
+
+ switch ( moveAxis )
+ {
+ case "x":
+ origin.x += moveAmount
+ break
+ case "y":
+ origin.y += moveAmount
+ break
+ case "z":
+ origin.z += moveAmount
+ break
+ }
+
+ if ( moveTime > 0 )
+ {
+ mover.NonPhysicsMoveTo( origin, moveTime, blendIn, blendOut )
+ wait moveTime
+ }
+ else
+ {
+ mover.SetOrigin( origin )
+ }
+}
+
+string function GetMoveAxisFromDir( int dir )
+{
+ if ( dir == 1 || dir == -1 )
+ return "z"
+
+ if ( dir % 180 == 0 )
+ return "x"
+
+ return "y"
+}
+
+
+float function GetEntHeight( entity ent )
+{
+ return ent.GetBoundingMaxs().z - ent.GetBoundingMins().z
+}
+
+float function GetEntWidth( entity ent )
+{
+ return ent.GetBoundingMaxs().x - ent.GetBoundingMins().x
+}
+
+float function GetEntDepth( entity ent )
+{
+ return ent.GetBoundingMaxs().y - ent.GetBoundingMins().y
+}
+
+void function PushEntWithVelocity( entity ent, vector velocity )
+{
+ if ( !ent.IsPlayer() && !ent.IsNPC() )
+ return
+
+ if ( !IsAlive( ent ) )
+ return
+
+ float scale = 1.0
+ float pushbackScale = 1.0
+ if ( ent.IsTitan() )
+ {
+ entity soul = ent.GetTitanSoul()
+ if ( soul != null ) // defensive fix
+ {
+ string settings = GetSoulPlayerSettings( soul )
+ var scale = Dev_GetPlayerSettingByKeyField_Global( settings, "pushbackScale" )
+ if ( scale != null )
+ {
+ pushbackScale = expect float( scale )
+ }
+ }
+ }
+
+ scale = 1.0 - StatusEffect_Get( ent, eStatusEffect.pushback_dampen )
+ scale = scale * pushbackScale
+
+ velocity *= scale
+
+ ent.SetVelocity( velocity )
+}
+
+
+
+bool function IsPlayerMalePilot( entity player )
+{
+ Assert( player.IsPlayer() )
+
+ if ( !IsPilot( player ) )
+ return false
+
+ return !IsPlayerFemale( player )
+}
+
+bool function IsPlayerFemalePilot( entity player )
+{
+ Assert( player.IsPlayer() )
+
+ if ( !IsPilot( player ) )
+ return false
+
+ return IsPlayerFemale( player )
+}
+
+bool function IsFacingEnemy( entity guy, entity enemy, int viewAngle = 75 )
+{
+ vector dir = enemy.GetOrigin() - guy.GetOrigin()
+ dir = Normalize( dir )
+ float dot = DotProduct( guy.GetPlayerOrNPCViewVector(), dir )
+ float yaw = DotToAngle( dot )
+
+ return ( yaw < viewAngle )
+}
+
+void function SetSquad( entity guy, string squadName )
+{
+ Assert( IsValid( guy ) )
+
+ if ( guy.kv.squadname == squadName )
+ return
+
+ // we only want squads containing NPCs of the same class
+ #if HAS_AI_SQUAD_LIMITS
+ Assert( SquadValidForClass( squadName, guy.GetClassName() ), "Can't put AI " + guy + " in squad " + squadName + ", because it contains one or more AI with a different class." )
+ Assert( SquadCanAcceptNewMembers( guy, squadName ), "Can't add AI " + guy + " to squad " + squadName + ", because that squad already has " + SQUAD_SIZE + " slots filled or reserved." )
+ #endif
+
+ guy.SetSquad( squadName )
+}
+
+void function PushPlayersApart( entity target, entity attacker, float speed )
+{
+ vector dif = Normalize( target.GetOrigin() - attacker.GetOrigin() )
+ dif *= speed
+ PushPlayerAway( target, dif )
+ PushPlayerAway( attacker, -dif )
+}
+
+void function PushPlayerAway( entity target, vector velocity )
+{
+ #if MP
+ if ( !target.IsPlayer() && !target.IsNPC() )
+ return
+ #endif
+
+ vector result = velocity // + target.GetVelocity()
+ result.z = max( 200, fabs( velocity.z ) )
+ target.SetVelocity( result )
+ //DebugDrawLine( target.GetOrigin(), target.GetOrigin() + result * 5, 255, 0, 0, true, 5.0 )
+}
+
+
+int function SortBySpawnTime( entity ent1, entity ent2 )
+{
+ if ( ent1.e.spawnTime > ent2.e.spawnTime )
+ return 1
+
+ if ( ent2.e.spawnTime > ent1.e.spawnTime )
+ return -1
+
+ return 0
+}
+
+void function HolsterAndDisableWeapons( entity player )
+{
+ player.HolsterWeapon()
+ DisableOffhandWeapons( player )
+}
+
+void function HolsterViewModelAndDisableWeapons( entity player ) //Note that this skips the first person holster animation, and it appears to 3p observers you still have a gun out
+{
+ player.DisableWeaponViewModel()
+ DisableOffhandWeapons( player )
+}
+
+
+void function DeployAndEnableWeapons( entity player )
+{
+ player.DeployWeapon()
+ EnableOffhandWeapons( player )
+}
+
+void function DeployViewModelAndEnableWeapons( entity player )
+{
+ if ( IsAlive( player ) )
+ player.EnableWeaponViewModel()
+ EnableOffhandWeapons( player )
+}
+
+//Investigate: This might be getting called without enableoffhandweapons being called. If so, Server_TurnOffhandWeaponsDisabledOn() should be used instead of this stack system.
+void function DisableOffhandWeapons( entity player )
+{
+ player.Server_TurnOffhandWeaponsDisabledOn()
+ player.p.disableOffhandWeaponsStackCount++
+}
+
+void function EnableOffhandWeapons( entity player )
+{
+ player.p.disableOffhandWeaponsStackCount--
+ if ( player.p.disableOffhandWeaponsStackCount <= 0 )
+ player.Server_TurnOffhandWeaponsDisabledOff()
+
+ Assert( player.p.disableOffhandWeaponsStackCount >= 0, "Warning! Called EnableOffhandWeapons() but the weapons aren't disabled!" )
+}
+
+void function PushEntWithDamageInfoAndDistanceScale( entity ent, var damageInfo, float nearRange, float farRange, float nearScale, float farScale, float forceMultiplier_dotBase = 0.5 )
+{
+ float scale = GraphCapped( DamageInfo_GetDistFromAttackOrigin( damageInfo ), nearRange, farRange, nearScale, farScale )
+
+ if ( scale > 0 )
+ PushEntWithDamageInfo( ent, damageInfo, forceMultiplier_dotBase, scale )
+}
+
+void function PushEntWithDamageInfo( entity ent, var damageInfo, float forceMultiplier_dotBase = 0.5, float forceMultiplier_dotScale = 0.5 )
+{
+ int source = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ switch ( source )
+ {
+ case eDamageSourceId.mp_titanweapon_vortex_shield:
+ case eDamageSourceId.mp_titanweapon_vortex_shield_ion:
+ return
+ }
+
+ entity projectile = DamageInfo_GetInflictor( damageInfo )
+ if ( !IsValid( projectile ) )
+ return
+
+ vector attackDirection = Normalize( projectile.GetVelocity() )
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ PushEntWithDamageFromDirection( ent, damage, attackDirection, forceMultiplier_dotBase, forceMultiplier_dotScale )
+}
+
+void function PushEntWithDamageFromDirection( entity ent, float damage, vector attackDirection, float forceMultiplier_dotBase = 0.5, float forceMultiplier_dotScale = 0.5 )
+{
+
+ float speed
+ if ( damage < 900 )
+ speed = GraphCapped( damage, 0, 900, 0, 650 )
+ else
+ speed = GraphCapped( damage, 900, 1400, 650, 1400 )
+
+ vector direction = attackDirection + <0,0,0>
+ direction.z *= 0.25
+ vector force = direction * speed
+
+ force += < 0, 0, fabs( direction.z ) * 0.25 >
+
+ vector velocity = ent.GetVelocity()
+ vector baseVel = Normalize( velocity + <0,0,0> )
+
+ float dot = DotProduct( baseVel, attackDirection ) * -1
+ float dotMultiplier
+ if ( dot > 0 )
+ {
+ dot *= forceMultiplier_dotScale
+ }
+ else
+ {
+ dot = 0
+ }
+
+ force *= ( forceMultiplier_dotBase + dot )
+ //printt( "force " + Length( force ) )
+ velocity += force
+ PushEntWithVelocity( ent, velocity )
+}
+
+
+void function SetPlayerAnimViewEntity( entity player, entity model )
+{
+ // clear any attempts to hide the view anim entity
+ player.Signal( "NewViewAnimEntity" )
+ player.AnimViewEntity_SetEntity( model )
+}
+
+
+void function RandomizeHead( entity model ) //Randomize head across all available heads
+{
+ int headIndex = model.FindBodyGroup( "head" )
+ if ( headIndex == -1 )
+ {
+ //printt( "HeadIndex == -1, returning" )
+ return
+ }
+ int numOfHeads = model.GetBodyGroupModelCount( headIndex ) - 1 // last one is no head
+ //printt( "Num of Heads: " + numOfHeads )
+
+ if ( HasTeamSkin( model ) )
+ {
+ RandomizeHeadByTeam( model, headIndex, numOfHeads )
+ return
+ }
+ else
+ {
+ int randomHeadIndex = RandomInt( numOfHeads )
+ //printt( "Set head to: : " + randomHeadIndex )
+ model.SetBodygroup( headIndex, randomHeadIndex )
+ }
+}
+
+bool function HasTeamSkin( entity model )
+{
+ return "teamSkin" in model.CreateTableFromModelKeyValues()
+}
+
+void function RandomizeHeadByTeam( entity model, int headIndex, int numOfHeads ) //Randomize head across heads available to a particular team. Assumes for a model all imc heads are first, then all militia heads are later.
+{
+ float midPoint = float( numOfHeads / 2 )
+
+ int randomHeadIndex = 0
+ if ( model.GetTeam() == TEAM_IMC )
+ {
+ randomHeadIndex = RandomInt( midPoint )
+ }
+ else if ( model.GetTeam() == TEAM_MILITIA )
+ {
+ randomHeadIndex = RandomIntRange( midPoint, numOfHeads )
+ }
+ //printt( "Model ", model.GetModelName(), " is using ", numOfHeads, " randomHeadIndex")
+
+ //printt( "Set head to: : " + randomHeadIndex )
+ model.SetBodygroup( headIndex, randomHeadIndex )
+}
+
+void function TakeWeaponsForArray( entity ent, array<entity> weapons )
+{
+ foreach ( weapon in weapons )
+ {
+ ent.TakeWeaponNow( weapon.GetWeaponClassName() )
+ }
+}
+
+void function ScaleHealth( entity ent, float scale )
+{
+ Assert( IsAlive( ent ) )
+
+ int maxHealth = ent.GetMaxHealth()
+ float healthRatio = float( ent.GetHealth() ) / maxHealth
+ maxHealth = int( maxHealth * scale )
+ ent.SetHealth( maxHealth * healthRatio )
+ ent.SetMaxHealth( maxHealth )
+}
+
+void function TeleportPlayerToEnt( entity player, entity org )
+{
+ if ( !IsValid( player ) )
+ return
+ Assert( player.IsPlayer() )
+ player.SetOrigin( org.GetOrigin() )
+ player.SetAngles( org.GetAngles() )
+}
+
+float function ShieldModifyDamage( entity ent, var damageInfo )
+{
+ entity victim
+ if ( ent.IsTitan() )
+ victim = ent.GetTitanSoul()
+ else
+ victim = ent
+
+ int shieldHealth = victim.GetShieldHealth()
+
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ int damageSourceIdentifier = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ ShieldDamageModifier damageModifier = GetShieldDamageModifier( damageInfo )
+ damage *= damageModifier.damageScale
+
+ float healthFrac = GetHealthFrac( victim )
+
+ float permanentDamage = (damage * damageModifier.permanentDamageFrac * healthFrac)
+
+ float shieldDamage
+
+ if ( damageSourceIdentifier == eDamageSourceId.titanEmpField )
+ {
+ shieldDamage = min( 1000.0, float( shieldHealth ) )
+ }
+ else
+ {
+ if ( damageModifier.normalizeShieldDamage )
+ shieldDamage = damage * 0.5
+ else
+ shieldDamage = damage - permanentDamage
+
+ // if ( IsSoul( victim ) && SoulHasPassive( victim, ePassives.PAS_SHIELD_BOOST ) )
+ // shieldDamage *= SHIELD_BOOST_DAMAGE_DAMPEN
+
+ if ( IsSoul( victim ) && SoulHasPassive( victim, ePassives.PAS_BERSERKER ) )
+ shieldDamage *= BERSERKER_INCOMING_DAMAGE_DAMPEN
+ }
+
+ float newShieldHealth = shieldHealth - shieldDamage
+
+ victim.SetShieldHealth( max( 0, newShieldHealth ) )
+
+ if ( shieldHealth > 0 && newShieldHealth <= 0 )
+ {
+ if ( ent.IsPlayer() )
+ {
+ EmitSoundOnEntityExceptToPlayer( ent, ent, "titan_energyshield_down_3P" )
+ EmitSoundOnEntityOnlyToPlayer( ent, ent, "titan_energyshield_down_1P" )
+ }
+ else if ( ent.GetScriptName() == "fw_team_tower" )
+ {
+ EmitSoundOnEntity( ent, "TitanWar_Harvester_ShieldDown" )
+
+ #if FACTION_DIALOGUE_ENABLED
+ PlayFactionDialogueToTeam( "fortwar_baseShieldDownFriendly", ent.GetTeam() )
+ PlayFactionDialogueToTeam( "fortwar_baseShieldDownEnemy", GetOtherTeam( ent.GetTeam() ) )
+ #endif
+ }
+ else
+ {
+ EmitSoundOnEntity( ent, "titan_energyshield_down_3P" )
+ }
+ }
+
+ DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )
+
+ if ( newShieldHealth < 0 )
+ {
+ DamageInfo_SetDamage( damageInfo, fabs( newShieldHealth ) + permanentDamage )
+ }
+ else
+ {
+ if ( permanentDamage == 0 )
+ {
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ vector damageOrigin = GetDamageOrigin( damageInfo, ent )
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ int attackerEHandle = attacker.GetEncodedEHandle()
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ if ( attacker.IsPlayer() )
+ attacker.NotifyDidDamage( ent, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamagePosition( damageInfo ), damageType, shieldDamage, DamageInfo_GetDamageFlags( damageInfo ), DamageInfo_GetHitGroup( damageInfo ), DamageInfo_GetWeapon( damageInfo ), DamageInfo_GetDistFromAttackOrigin( damageInfo ) )
+ if ( ent.IsPlayer() )
+ Remote_CallFunction_Replay( ent, "ServerCallback_TitanTookDamage", shieldDamage, damageOrigin.x, damageOrigin.y, damageOrigin.z, damageType, damageSourceId, attackerEHandle, null, false, 0 )
+ }
+ DamageInfo_SetDamage( damageInfo, permanentDamage )
+ }
+
+ float actualShieldDamage = min( shieldHealth, shieldDamage )
+
+ if ( actualShieldDamage > 0 )
+ {
+ foreach ( func in ent.e.entPostShieldDamageCallbacks )
+ {
+ func( ent, damageInfo, actualShieldDamage )
+ }
+ }
+
+ return actualShieldDamage
+}
+
+ShieldDamageModifier function GetShieldDamageModifier( var damageInfo )
+{
+ ShieldDamageModifier damageModifier
+
+ // Disabling Shield Damage Modifiers and rebalancing the weapons. The below mechanics seem cool in an R1 style system though so leaving them commented out.
+ // NOTE: Changing Damage Scale has a buggy interaction with permanent damage that must be fixed if we re-enable this.
+ /*
+ int damageSourceIdentifier = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ switch ( damageSourceIdentifier )
+ {
+ case eDamageSourceId.mp_weapon_thermite_grenade:
+ damageModifier.permanentDamageFrac = 0.9
+ break
+ }
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_ELECTRICAL )
+ {
+ // amped version
+ if ( damageSourceIdentifier == eDamageSourceId.mp_titanweapon_xo16 )
+ damageModifier.damageScale *= 1.5
+
+ // amped version
+ if ( damageSourceIdentifier == eDamageSourceId.mp_titanweapon_triple_threat )
+ damageModifier.damageScale *= 1.5
+
+ if ( damageSourceIdentifier == eDamageSourceId.mp_titanweapon_arc_cannon )
+ damageModifier.damageScale *= 1.5
+ }
+ */
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_ELECTRICAL )
+ {
+ int damageSourceIdentifier = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ // Vanguard Arc Rounds
+ if ( damageSourceIdentifier == eDamageSourceId.mp_titanweapon_xo16_vanguard )
+ damageModifier.damageScale *= 1.5
+ }
+
+
+ return damageModifier
+}
+
+
+
+void function AddCallback_NPCLeeched( void functionref( entity, entity ) callbackFunc )
+{
+ Assert( !( svGlobal.onLeechedCustomCallbackFunc.contains( callbackFunc ) ) )
+ svGlobal.onLeechedCustomCallbackFunc.append( callbackFunc )
+}
+
+void function MessageToPlayer( entity player, int eventID, entity ent = null, var eventVal = null )
+{
+ var eHandle = null
+ if ( ent )
+ eHandle = ent.GetEncodedEHandle()
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_EventNotification", eventID, eHandle, eventVal )
+ //SendHudMessage( player, message, 0.33, 0.28, 255, 255, 255, 255, 0.15, 3.0, 0.5 )
+}
+
+
+void function MessageToTeam( int team, int eventID, entity excludePlayer = null, entity ent = null, var eventVal = null )
+{
+ array<entity> players = GetPlayerArray()
+
+ foreach ( player in players )
+ {
+ if ( player.GetTeam() != team )
+ continue
+
+ if ( player == excludePlayer )
+ continue
+
+ MessageToPlayer( player, eventID, ent, eventVal )
+ }
+}
+
+void function MessageToAll( int eventID, entity excludePlayer = null, entity ent = null, var eventVal = null )
+{
+ array<entity> players = GetPlayerArray()
+
+ foreach ( player in players )
+ {
+ if ( player == excludePlayer )
+ continue
+
+ MessageToPlayer( player, eventID, ent, eventVal )
+ }
+}
+
+
+
+string function ReloadScriptsInternal()
+{
+ reloadingScripts = true
+ reloadedScripts = true
+ ReloadingScriptsBegin()
+
+ if ( IsMenuLevel() )
+ {
+ reloadingScripts = false
+ ReloadingScriptsEnd()
+ return ""
+ }
+
+ TitanEmbark_Init()
+
+ ReloadScriptCallbacks()
+
+ reloadingScripts = false
+ ReloadingScriptsEnd()
+
+ return ( "reloaded server scripts" )
+}
+
+string function ReloadScripts()
+{
+ ServerCommand( "fs_report_sync_opens 0" ) // makes reload scripts slow
+ delaythread ( 0 ) ReloadScriptsInternal()
+
+ return ( "reloaded server scripts" )
+}
+
+int function GameTime_TimeLimitSeconds()
+{
+ if ( IsRoundBased() )
+ {
+ return ( GetRoundTimeLimit_ForGameMode() * 60.0 ).tointeger()
+ }
+ else
+ {
+ if ( IsSuddenDeathGameMode() && GetGameState() == eGameState.SuddenDeath )
+ return ( GetTimeLimit_ForGameMode() * 60.0 ).tointeger() + ( GetSuddenDeathTimeLimit_ForGameMode() * 60.0 ).tointeger()
+ else
+ return ( GetTimeLimit_ForGameMode() * 60.0 ).tointeger()
+ }
+ unreachable
+}
+
+int function GetSuddenDeathTimeLimit_ForGameMode()
+{
+ string mode = GameRules_GetGameMode()
+ string playlistString = "suddendeath_timelimit"
+
+ return GetCurrentPlaylistVarInt( playlistString, 4 )
+}
+
+int function GameTime_TimeLimitMinutes()
+{
+ if ( IsRoundBased() )
+ return floor( GetRoundTimeLimit_ForGameMode() ).tointeger()
+ else
+ return floor( GetTimeLimit_ForGameMode() ).tointeger()
+ unreachable
+}
+
+int function GameTime_TimeLeftMinutes()
+{
+ if ( GetGameState() == eGameState.WaitingForPlayers )
+ return 0
+ if ( GetGameState() == eGameState.Prematch )
+ return int( ( expect float( GetServerVar( "gameStartTime" ) ) - Time()) / 60.0 )
+
+ return floor( GameTime_TimeLimitMinutes() - GameTime_PlayingTime() / 60 ).tointeger()
+}
+
+int function GameTime_TimeLeftSeconds()
+{
+ if ( GetGameState() == eGameState.Prematch )
+ return int( expect float( GetServerVar( "gameStartTime" ) ) - Time() )
+
+ return floor( GameTime_TimeLimitSeconds() - GameTime_PlayingTime() ).tointeger()
+}
+
+int function GameTime_Seconds()
+{
+ return floor( Time() ).tointeger()
+}
+
+int function GameTime_Minutes()
+{
+ return int( floor( GameTime_Seconds() / 60 ) )
+}
+
+float function GameTime_PlayingTime()
+{
+ return GameTime_PlayingTimeSince( Time() )
+}
+
+float function GameTime_PlayingTimeSince( float sinceTime )
+{
+ int gameState = GetGameState()
+
+ // temp fix because i have no fucking clue why this crashes
+
+ if ( gameState < eGameState.Playing )
+ return 0
+
+ if ( IsRoundBased() )
+ {
+ if ( gameState > eGameState.SuddenDeath )
+ return (expect float( GetServerVar( "roundEndTime" ) ) - expect float( GetServerVar( "roundStartTime" ) ) )
+ else
+ return sinceTime - expect float( GetServerVar( "roundStartTime" ) )
+
+ }
+ else
+ {
+ if ( gameState > eGameState.SuddenDeath )
+ return (expect float( GetServerVar( "gameEndTime" ) ) - expect float( GetServerVar( "gameStartTime" ) ) )
+ else
+ return sinceTime - expect float( GetServerVar( "gameStartTime" ) )
+ }
+
+ unreachable
+}
+
+float function GameTime_TimeSpentInCurrentState()
+{
+ return Time() - expect float( GetServerVar( "gameStateChangeTime" ) )
+}
+
+int function GameScore_GetFirstToScoreLimit()
+{
+ return expect int( level.firstToScoreLimit )
+}
+
+bool function GameScore_AllowPointsOverLimit()
+{
+ return svGlobal.allowPointsOverLimit
+}
+
+int function GameScore_GetWinningTeam()
+{
+ if ( GameScore_GetFirstToScoreLimit() )
+ return GameScore_GetFirstToScoreLimit()
+
+ if ( IsRoundBased() )
+ {
+ if ( GameRules_GetTeamScore2( TEAM_IMC ) > GameRules_GetTeamScore2( TEAM_MILITIA ) )
+ return TEAM_IMC
+ else if ( GameRules_GetTeamScore2( TEAM_MILITIA ) > GameRules_GetTeamScore2( TEAM_IMC ) )
+ return TEAM_MILITIA
+ }
+ else
+ {
+ if ( GameRules_GetTeamScore( TEAM_IMC ) > GameRules_GetTeamScore( TEAM_MILITIA ) )
+ return TEAM_IMC
+ else if ( GameRules_GetTeamScore( TEAM_MILITIA ) > GameRules_GetTeamScore( TEAM_IMC ) )
+ return TEAM_MILITIA
+ }
+
+ return TEAM_UNASSIGNED
+}
+
+int function GameScore_GetWinningTeam_ThisRound()
+{
+ if ( GameScore_GetFirstToScoreLimit() )
+ return GameScore_GetFirstToScoreLimit()
+
+ Assert ( IsRoundBased() )
+
+ if ( GameRules_GetTeamScore( TEAM_IMC ) > GameRules_GetTeamScore( TEAM_MILITIA ) )
+ return TEAM_IMC
+ else if ( GameRules_GetTeamScore( TEAM_MILITIA ) > GameRules_GetTeamScore( TEAM_IMC ) )
+ return TEAM_MILITIA
+
+ return TEAM_UNASSIGNED
+}
+
+#if DEV
+void function KillIMC()
+{
+ array<entity> enemies = GetNPCArrayOfTeam( TEAM_IMC )
+ foreach ( enemy in enemies )
+ {
+ enemy.Die()
+ }
+}
+
+void function killtitans()
+{
+ printt( "Script command: Kill all titans" )
+ array<entity> titans = GetTitanArray()
+ foreach ( titan in titans )
+ titan.Die()
+}
+
+void function killminions()
+{
+ printt( "Script command: Kill all minions" )
+ array<entity> minions = GetAllMinions()
+ foreach ( minion in minions )
+ {
+ minion.Die()
+ }
+}
+#endif
+
+
+array<entity> function GetTeamMinions( int team )
+{
+ array<entity> ai = GetNPCArrayByClass( "npc_soldier" )
+ ai.extend( GetNPCArrayByClass( "npc_spectre" ) )
+
+ for ( int i = 0; i < ai.len(); i++ )
+ {
+ if ( ai[i].GetTeam() != team )
+ {
+ ai.remove(i)
+ i--
+ }
+ }
+
+ return ai
+}
+
+array<entity> function GetAllMinions()
+{
+ array<entity> ai = GetNPCArrayByClass( "npc_soldier" )
+ ai.extend( GetNPCArrayByClass( "npc_spectre" ) )
+ ai.extend( GetNPCArrayByClass( "npc_drone" ) )
+
+ return ai
+}
+
+
+bool function GameScore_IsLowScoreDifference()
+{
+ int winningTeam = GameScore_GetWinningTeam()
+
+ if ( !winningTeam )
+ return true
+
+ int losingTeam = GetOtherTeam( winningTeam )
+
+ int winningTeamScore
+ int losingTeamScore
+
+ if ( IsRoundBased() )
+ {
+ winningTeamScore = GameRules_GetTeamScore2( winningTeam )
+ losingTeamScore = GameRules_GetTeamScore2( losingTeam )
+ }
+ else
+ {
+ winningTeamScore = GameRules_GetTeamScore( winningTeam )
+ losingTeamScore = GameRules_GetTeamScore( losingTeam )
+ }
+
+ return ( winningTeamScore - losingTeamScore < 2 )
+}
+
+bool function IsFastPilot( entity player )
+{
+ Assert( IsPilot( player ), "Pilot only check" )
+
+ if ( player.IsWallHanging() )
+ return false
+
+ if ( player.IsWallRunning() )
+ return true
+
+ if ( !player.IsOnGround() )
+ return true
+
+ if ( LengthSqr( player.GetSmoothedVelocity() ) > 180*180 || LengthSqr( player.GetVelocity() ) > 180*180 )
+ return true
+
+ return false
+}
+
+void function KillPlayer( entity player, int damageSource )
+{
+ #if DEV
+ printt( "Played Killed from script: " )
+ DumpStack()
+ #endif
+
+ Assert( IsAlive( player ) )
+ Assert( player.IsPlayer() )
+ player.Die( svGlobal.worldspawn, svGlobal.worldspawn, { damageSourceId = damageSource, scriptType=DF_SKIP_DAMAGE_PROT | DF_SKIPS_DOOMED_STATE } )
+}
+
+
+//////////////////////////////////////////////////////////
+void function TurretChangeTeam( entity turret, int team )
+{
+ if ( team != TEAM_UNASSIGNED )
+ {
+ // If a turret is on some player's team it should never be invulnerable
+ MakeTurretVulnerable( turret )
+ }
+
+ SetTeam( turret, team )
+
+ // refresh the turret client side particle effects
+ UpdateTurretClientSideParticleEffects( turret )
+}
+
+void function MakeTurretInvulnerable( entity turret )
+{
+ Assert( IsValid( turret ) )
+ turret.SetInvulnerable()
+ turret.SetNoTarget(true)
+ turret.SetNoTargetSmartAmmo(true)
+}
+
+void function MakeTurretVulnerable( entity turret )
+{
+ Assert( IsValid( turret ) )
+ turret.ClearInvulnerable()
+ turret.SetNoTarget(false)
+ turret.SetNoTargetSmartAmmo(false)
+}
+
+
+void function UpdateTurretClientSideParticleEffects( entity turret )
+{
+ if ( !IsValid( turret ) )
+ return
+
+ int turretEHandle = turret.GetEncodedEHandle()
+ array<entity> players = GetPlayerArray()
+ foreach( player in players )
+ {
+ Remote_CallFunction_Replay( player, "ServerCallback_TurretRefresh", turretEHandle )
+ }
+}
+
+
+
+bool function TakePrimaryWeapon( entity player )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ foreach ( index, weaponEnt in weapons )
+ {
+ int weaponType = weaponEnt.GetWeaponType()
+ if ( weaponType == WT_SIDEARM || weaponType == WT_ANTITITAN )
+ continue;
+
+ string weapon = weaponEnt.GetWeaponClassName()
+ player.TakeWeaponNow( weapon )
+ return true
+ }
+ return false
+}
+
+bool function TakeSecondaryWeapon( entity player )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ foreach ( index, weaponEnt in weapons )
+ {
+ if ( weaponEnt.GetWeaponType() != WT_ANTITITAN )
+ continue
+
+ string weapon = weaponEnt.GetWeaponClassName()
+ player.TakeWeaponNow( weapon )
+ return true
+ }
+ return false
+}
+
+bool function TakeSidearmWeapon( entity player )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ foreach ( index, weaponEnt in weapons )
+ {
+ if ( weaponEnt.GetWeaponType() != WT_SIDEARM )
+ continue
+
+ string weapon = weaponEnt.GetWeaponClassName()
+ player.TakeWeaponNow( weapon )
+ return true
+ }
+ return false
+}
+
+void function TakeAllWeapons( entity ent )
+{
+ if ( ent.IsPlayer() )
+ {
+ ent.RemoveAllItems()
+ array<entity> weapons = ent.GetMainWeapons()
+ foreach ( weapon in weapons )
+ {
+ Assert( 0, ent + " still has weapon " + weapon.GetWeaponClassName() + " after doing takeallweapons" )
+ }
+ }
+ else
+ {
+ array<entity> weapons = ent.GetMainWeapons()
+ TakeWeaponsForArray( ent, weapons )
+
+ weapons = ent.GetOffhandWeapons()
+ foreach ( index, weapon in clone weapons )
+ {
+ ent.TakeOffhandWeapon( index )
+ }
+ TakeWeaponsForArray( ent, weapons )
+ }
+}
+
+
+void function SetSpawnflags( entity ent, int spawnFlags )
+{
+ ent.kv.spawnflags = spawnFlags
+}
+
+
+void function DestroyAfterDelay( entity ent, float delay )
+{
+ Assert( IsNewThread(), "Must be threaded off" )
+
+ ent.EndSignal( "OnDestroy" )
+
+ wait( delay )
+
+ ent.Destroy()
+}
+
+void function UnlockAchievement( entity player, int achievementID )
+{
+ Assert( IsValid( player ), "Can't unlock achievement on invalid player entity" )
+ Assert( player.IsPlayer(), "Can't unlock achivement on non-player entity" )
+ Assert( achievementID > 0 && achievementID < achievements.MAX_ACHIVEMENTS, "Tried to unlock achievement with invalid enum value" )
+
+ Remote_CallFunction_UI( player, "ScriptCallback_UnlockAchievement", achievementID )
+}
+
+void function UpdateHeroStatsForPlayer( entity player )
+{
+ if ( !IsValid( player ) )
+ return
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateHeroStats" )
+}
+
+void function TestDeathFall()
+{
+ entity trigger = GetEntByScriptName( "DeathFallTrigger" )
+ table results = WaitSignal( trigger, "OnTrigger" )
+ printt( "DEATH FALL TRIGGERED" )
+ PrintTable( results )
+}
+
+bool function PlayerHasTitan( entity player )
+{
+ entity titan
+ if ( player.IsTitan() )
+ titan = player
+ else
+ titan = player.GetPetTitan()
+
+ if ( IsAlive( titan ) )
+ return true
+
+ return false
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_utility_shared.nut b/Northstar.CustomServers/scripts/vscripts/_utility_shared.nut
new file mode 100644
index 000000000..e3cb0dbfb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_utility_shared.nut
@@ -0,0 +1,4069 @@
+untyped
+
+globalize_all_functions
+
+const DEV_DRAWALLTRIGGERS = 0
+
+global const CHARGE_TOOL = "sp_weapon_arc_tool"
+
+global const TRIG_FLAG_NONE = 0
+global const TRIG_FLAG_PLAYERONLY = 0x0001
+global const TRIG_FLAG_NPCONLY = 0x0002
+global const TRIG_FLAG_NOCONTEXTBUSY = 0x0004
+global const TRIG_FLAG_ONCE = 0x0008
+global const TRIG_FLAG_EXCLUSIVE = 0x0010 // can only be triggered by entities passed in at creation
+global const TRIG_FLAG_DEVDRAW = 0x0020
+global const TRIG_FLAG_START_DISABLED = 0x0040
+global const TRIG_FLAG_NO_PHASE_SHIFT = 0x0080
+global const float MAP_EXTENTS = 128*128
+/*
+const TRIG_FLAG_ = 0x0080
+const TRIG_FLAG_ = 0x0100*/
+
+global const TRIGGER_INTERNAL_SIGNAL = "OnTrigger"
+
+global const CALCULATE_SEQUENCE_BLEND_TIME = -1.0
+
+global struct ArrayDistanceEntry
+{
+ float distanceSqr
+ entity ent
+ vector origin
+}
+
+global struct GravityLandData
+{
+ array<vector> points
+ TraceResults& traceResults
+ float elapsedTime
+}
+
+global struct FirstPersonSequenceStruct
+{
+ string firstPersonAnim = ""
+ string thirdPersonAnim = ""
+ string firstPersonAnimIdle = ""
+ string thirdPersonAnimIdle = ""
+ string relativeAnim = ""
+ string attachment = ""
+ bool teleport = false
+ bool noParent = false
+ float blendTime = CALCULATE_SEQUENCE_BLEND_TIME
+ float firstPersonBlendOutTime = -1.0
+ bool noViewLerp = false
+ bool hideProxy = false
+ void functionref( entity ) viewConeFunction = null
+ vector ornull origin = null
+ vector ornull angles = null
+ bool enablePlanting = false
+ float setInitialTime = 0.0 //set the starting point of the animation in seconds
+ bool useAnimatedRefAttachment = false //Position entity using ref every frame instead of using root motion
+ bool renderWithViewModels = false
+ bool gravity = false // force gravity command on sequence
+ bool playerPushable = false
+ array< string > thirdPersonCameraAttachments = []
+ bool thirdPersonCameraVisibilityChecks = false
+ entity thirdPersonCameraEntity = null
+ bool snapPlayerFeetToEyes = true
+}
+
+global struct FrontRightDotProductsStruct
+{
+ float forwardDot = 0.0
+ float rightDot = 0.0
+}
+
+global struct RaySphereIntersectStruct
+{
+ bool result
+ float enterFrac
+ float leaveFrac
+}
+
+void function Utility_Shared_Init()
+{
+ RegisterSignal( TRIGGER_INTERNAL_SIGNAL )
+ RegisterSignal( "devForcedWin" )
+
+ #document( "IsAlive", "Returns true if the given ent is not null, and is alive." )
+ #document( "ArrayWithin", "Remove ents from array that are out of range" )
+}
+
+#if DEV
+// short cut for the console
+// script gp()[0].Die( gp()[1] )
+array<entity> function gp()
+{
+ return GetPlayerArray()
+}
+#endif
+
+void function InitWeaponScripts()
+{
+ SmartAmmo_Init()
+
+ // WEAPON SCRIPTS
+ ArcCannon_Init()
+ Grenade_FileInit()
+ Vortex_Init()
+
+// #if SERVER
+// PrecacheProjectileEntity( "grenade_frag" )
+// PrecacheProjectileEntity( "crossbow_bolt" )
+// #endif
+
+ MpWeaponDroneBeam_Init()
+ MpWeaponDroneRocket_Init()
+ MpWeaponDronePlasma_Init()
+ MpWeaponTurretPlasma_Init()
+ MpWeaponTurretLaser_Init()
+ MpWeaponSuperSpectre_Init()
+ MpWeaponGunshipLauncher_Init()
+ MpWeaponFragDrone_Init()
+ MpAbilityShifter_Init()
+ MpTitanabilityBubbleShield_Init()
+ MpTitanabilityAmpedWall_Init()
+ MpTitanabilityFusionCore_Init()
+ MpTitanweapon40mm_Init()
+ MpTitanWeaponpredatorcannon_Init()
+ MpTitanweaponRocketeetRocketStream_Init()
+ MpTitanweaponMeteor_Init()
+ MpTitanWeapon_SniperInit()
+ MpTitanweaponVortexShield_Init()
+ MpTitanweaponXo16_Init()
+ MpWeaponDefender_Init()
+ MpWeaponDmr_Init()
+ MpWeaponProximityMine_Init()
+ MpWeaponRocketLauncher_Init()
+ MpWeaponNPCRocketLauncher_Init()
+ MpWeaponSatchel_Init()
+ MpWeaponSmartPistol_Init()
+ MpWeaponSniper_Init()
+ MpWeaponLSTAR_Init()
+ MpTitanWeaponParticleAccelerator_Init()
+ MpWeaponMegaTurret_Init()
+ MpWeaponZipline_Init()
+ SpWeaponHoldBeam_Init()
+ MpTitanweaponArcBall_Init()
+ MpWeaponDeployableCover_Init()
+ MpTitanAbilityBasicBlock_Init()
+ MpTitanAbilityLaserTrip_Init()
+ MpTitanWeaponArcWave_Init()
+ MpTitanWeaponFlameWave_Init()
+ MpWeaponAlternatorSMG_Init()
+ MpWeaponGreandeElectricSmoke_Init()
+ MpWeaponGrenadeGravity_Init()
+ MpWeaponDeployableCloakfield_Init()
+ MpWeaponTether_Init()
+ MpWeaponTripWire_Init()
+ MpTitanAbilitySmartCore_Init()
+ MpTitanAbilitySlowTrap_Init()
+ MpTitanAbilityPowerShot_Init()
+ MpTitanAbilityAmmoSwap_Init()
+ MpTitanAbilityRocketeerAmmoSwap_Init()
+ MpTitanAbilityHeatShield_Init()
+ SonarGrenade_Init()
+ MpTitanAbilityGunShield_Init()
+ MpTitanWeaponLaserLite_Init()
+ MpTitanWeaponSword_Init()
+ MpTitanAbilityHover_Init()
+ MpTitanWeaponTrackerRockets_Init()
+ MpTitanWeaponStunLaser_Init()
+ MpTitanWeaponShoulderRockets_Init()
+ MpTitanAbilitySmoke_Init()
+ #if MP
+ MpWeaponArcTrap_Init()
+ #endif
+
+ #if SERVER
+ BallLightning_Init()
+ #endif
+}
+
+float function GetCurrentPlaylistVarFloat( string val, float useVal )
+{
+ var result = GetCurrentPlaylistVarOrUseValue( val, useVal + "" )
+ if ( result == null || result == "" )
+ return 0.0
+
+ return float( result )
+}
+
+void function SetSkinForTeam( entity ent, int team )
+{
+ if ( team == TEAM_IMC )
+ ent.SetSkin( 0 )
+ else if ( team == TEAM_MILITIA )
+ ent.SetSkin( 1 )
+}
+
+void function TableDump( table Table, int depth = 0 )
+{
+ if ( depth > 4 )
+ return
+
+ foreach ( k, v in Table )
+ {
+ printl( "Key: " + k + " Value: " + v )
+ if ( type( v ) == "table" && depth )
+ TableDump( expect table( v ), depth + 1 )
+ }
+}
+
+/*entity function GetVortexWeapon( entity player )
+{
+ for ( int weaponIndex = 0; weaponIndex < 2; weaponIndex++ )
+ {
+ entity weapon = player.GetOffhandWeapon( weaponIndex )
+ if ( !IsValid( weapon ) )
+ continue
+ if ( weapon.GetWeaponClassName() != "mp_titanweapon_vortex_shield" )
+ continue
+ return weapon
+ }
+
+ Assert( false, "Vortex weapon not found!" )
+ unreachable
+}*/
+
+entity function GetClosest( array<entity> entArray, vector origin, float maxdist = -1.0 )
+{
+ Assert( entArray.len() > 0 )
+
+ entity bestEnt = entArray[ 0 ]
+ float bestDistSqr = DistanceSqr( bestEnt.GetOrigin(), origin )
+
+ for ( int i = 1; i < entArray.len(); i++ )
+ {
+ entity newEnt = entArray[ i ]
+ float newDistSqr = LengthSqr( newEnt.GetOrigin() - origin )
+
+ if ( newDistSqr < bestDistSqr )
+ {
+ bestEnt = newEnt
+ bestDistSqr = newDistSqr
+ }
+ }
+
+ if ( maxdist >= 0.0 )
+ {
+ if ( bestDistSqr > maxdist * maxdist )
+ return null
+ }
+
+ return bestEnt
+}
+
+entity function GetClosest2D( array<entity> entArray, vector origin, float maxdist = -1.0 )
+{
+ Assert( entArray.len() > 0, "Empty array!" )
+
+ entity bestEnt = entArray[ 0 ]
+ float bestDistSqr = DistanceSqr( bestEnt.GetOrigin(), origin )
+
+ for ( int i = 1; i < entArray.len(); i++ )
+ {
+ entity newEnt = entArray[ i ]
+ float newDistSqr = Length2DSqr( newEnt.GetOrigin() - origin )
+
+ if ( newDistSqr < bestDistSqr )
+ {
+ bestEnt = newEnt
+ bestDistSqr = newDistSqr
+ }
+ }
+
+ if ( maxdist >= 0.0 )
+ {
+ if ( bestDistSqr > maxdist * maxdist )
+ return null
+ }
+
+ return bestEnt
+}
+
+bool function GameModeHasCapturePoints()
+{
+ #if CLIENT
+ return clGlobal.hardpointStringIDs.len() > 0
+ #elseif SERVER
+ return svGlobal.hardpointStringIDs.len() > 0
+ #endif
+}
+
+entity function GetFarthest( array<entity> entArray, vector origin )
+{
+ Assert( entArray.len() > 0, "Empty array!" )
+
+ entity bestEnt = entArray[0]
+ float bestDistSqr = DistanceSqr( bestEnt.GetOrigin(), origin )
+
+ for ( int i = 1; i < entArray.len(); i++ )
+ {
+ entity newEnt = entArray[ i ]
+ float newDistSqr = DistanceSqr( newEnt.GetOrigin(), origin )
+
+ if ( newDistSqr > bestDistSqr )
+ {
+ bestEnt = newEnt
+ bestDistSqr = newDistSqr
+ }
+ }
+
+ return bestEnt
+}
+
+int function GetClosestIndex( array<entity> Array, vector origin )
+{
+ Assert( Array.len() > 0 )
+
+ int index = 0
+ float distSqr = LengthSqr( Array[ index ].GetOrigin() - origin )
+
+ entity newEnt
+ float newDistSqr
+ for ( int i = 1; i < Array.len(); i++ )
+ {
+ newEnt = Array[ i ]
+ newDistSqr = LengthSqr( newEnt.GetOrigin() - origin )
+
+ if ( newDistSqr < distSqr )
+ {
+ index = i
+ distSqr = newDistSqr
+ }
+ }
+
+ return index
+}
+
+// nothing in the game uses the format "Table.r/g/b/a"... wtf is the point of this function
+table function StringToColors( string colorString, string delimiter = " " )
+{
+ PerfStart( PerfIndexShared.StringToColors + SharedPerfIndexStart )
+ array<string> tokens = split( colorString, delimiter )
+
+ Assert( tokens.len() >= 3 )
+
+ table Table = {}
+ Table.r <- int( tokens[0] )
+ Table.g <- int( tokens[1] )
+ Table.b <- int( tokens[2] )
+
+ if ( tokens.len() == 4 )
+ Table.a <- int( tokens[3] )
+ else
+ Table.a <- 255
+
+ PerfEnd( PerfIndexShared.StringToColors + SharedPerfIndexStart )
+ return Table
+}
+
+// TODO: Set return type to array<int> when SetColor() accepts this type
+function ColorStringToArray( string colorString )
+{
+ array<string> tokens = split( colorString, " " )
+
+ Assert( tokens.len() >= 3 && tokens.len() <= 4 )
+
+ array colorArray
+ foreach ( token in tokens )
+ colorArray.append( int( token ) )
+
+ return colorArray
+}
+
+// Evaluate a generic order ( coefficientArray.len() - 1 ) polynomial
+// e.g. to evaluate (Ax + B), call EvaluatePolynomial(x, A, B)
+// Note that EvaluatePolynomial(x) returns 0 and
+// EvaluatePolynomial(x, A) returns A, which are technically correct
+// but perhaps not what you expect
+float function EvaluatePolynomial( float x, array<float> coefficientArray )
+{
+ float sum = 0.0
+
+ for ( int i = 0; i < coefficientArray.len() - 1; ++i )
+ sum += coefficientArray[ i ] * pow( x, coefficientArray.len() -1 - i )
+
+ if ( coefficientArray.len() >= 1 )
+ sum += coefficientArray[ coefficientArray.len() - 1 ]
+
+ return sum
+}
+
+void function WaitForever()
+{
+ #if SERVER
+ svGlobal.levelEnt.WaitSignal( "forever" )
+ #elseif CLIENT
+ clGlobal.levelEnt.WaitSignal( "forever" )
+ #endif
+}
+
+#if SERVER
+
+bool function ShouldDoReplay( entity player, entity attacker, float replayTime, int methodOfDeath )
+{
+ if ( ShouldDoReplayIsForcedByCode() )
+ {
+ print( "ShouldDoReplay(): Doing a replay because code forced it." );
+ return true
+ }
+
+ if ( GetCurrentPlaylistVarInt( "replay_disabled", 0 ) == 1 )
+ {
+ print( "ShouldDoReplay(): Not doing a replay because 'replay_disabled' is enabled in the current playlist.\n" );
+ return false
+ }
+
+ switch( methodOfDeath )
+ {
+ case eDamageSourceId.human_execution:
+ case eDamageSourceId.titan_execution:
+ {
+ print( "ShouldDoReplay(): Not doing a replay because the player died from an execution.\n" );
+ return false
+ }
+ }
+
+ if ( level.nv.replayDisabled )
+ {
+ print( "ShouldDoReplay(): Not doing a replay because replays are disabled for the level.\n" );
+ return false
+ }
+
+ if ( Time() - player.p.connectTime <= replayTime ) //Bad things happen if we try to do a kill replay that lasts longer than the player entity existing on the server
+ {
+ print( "ShouldDoReplay(): Not doing a replay because the player is not old enough.\n" );
+ return false
+ }
+
+ if ( player == attacker )
+ {
+ print( "ShouldDoReplay(): Not doing a replay because the attacker is the player.\n" );
+ return false
+ }
+
+ if ( player.IsBot() == true )
+ {
+ print( "ShouldDoReplay(): Not doing a replay because the player is a bot.\n" );
+ return false
+ }
+
+ return AttackerShouldTriggerReplay( attacker )
+}
+
+// Don't let things like killbrushes show replays
+bool function AttackerShouldTriggerReplay( entity attacker )
+{
+ if ( !IsValid( attacker ) )
+ {
+ print( "AttackerShouldTriggerReplay(): Not doing a replay because the attacker is not valid.\n" )
+ return false
+ }
+
+ if ( attacker.IsPlayer() )
+ {
+ print( "AttackerShouldTriggerReplay(): Doing a replay because the attacker is a player.\n" )
+ return true
+ }
+
+ if ( attacker.IsNPC() )
+ {
+ print( "AttackerShouldTriggerReplay(): Doing a replay because the attacker is an NPC.\n" )
+ return true
+ }
+
+ print( "AttackerShouldTriggerReplay(): Not doing a replay by default.\n" )
+ return false
+}
+#endif // #if SERVER
+
+vector function RandomVec( float range )
+{
+ // could rewrite so it doesnt make a box of random.
+ vector vec = Vector( 0, 0, 0 )
+ vec.x = RandomFloatRange( -range, range )
+ vec.y = RandomFloatRange( -range, range )
+ vec.z = RandomFloatRange( -range, range )
+
+ return vec
+}
+
+function ArrayValuesToTableKeys( arr )
+{
+ Assert( type( arr ) == "array", "Not an array" )
+
+ local resultTable = {}
+ for ( int i = 0; i < arr.len(); ++ i)
+ {
+ resultTable[ arr[ i ] ] <- 1
+ }
+
+ return resultTable
+}
+
+function TableKeysToArray( tab )
+{
+ Assert( type( tab ) == "table", "Not a table" )
+
+ local resultArray = []
+ resultArray.resize( tab.len() )
+ int currentArrayIndex = 0
+ foreach ( key, val in tab )
+ {
+ resultArray[ currentArrayIndex ] = key
+ ++currentArrayIndex
+ }
+
+ return resultArray
+}
+
+function TableRandom( Table )
+{
+ Assert( type( Table ) == "table", "Not a table" )
+
+ local Array = []
+
+ foreach ( entry, contents in Table )
+ {
+ Array.append( contents )
+ }
+
+ return Array.getrandom()
+}
+
+int function RandomWeightedIndex( array Array )
+{
+ int count = Array.len()
+ Assert( count != 0, "Array is empty" )
+
+ int sum = int( ( count * ( count + 1 ) ) / 2.0 ) // ( n * ( n + 1 ) ) / 2
+ int randInt = RandomInt( sum )
+ for ( int i = 0 ; i < count ; i++ )
+ {
+ int rangeForThisIndex = count - i
+ if ( randInt < rangeForThisIndex )
+ return i
+
+ randInt -= rangeForThisIndex
+ }
+
+ Assert( 0 )
+ unreachable
+}
+
+bool function IsValid_ThisFrame( entity ent )
+{
+ if ( ent == null )
+ return false
+
+ return expect bool( ent.IsValidInternal() )
+}
+
+bool function IsAlive( entity ent )
+{
+ if ( ent == null )
+ return false
+ if ( !ent.IsValidInternal() )
+ return false
+
+ return ent.IsEntAlive()
+}
+
+#if DEV && SERVER
+void function vduon()
+{
+ PlayConversationToAll( "TitanReplacement" )
+}
+
+void function playconvtest( string conv )
+{
+ entity player = GetPlayerArray()[0]
+ array<entity> guys = GetAllSoldiers()
+ if ( !guys.len() )
+ {
+ printt( "No AI!!" )
+ return
+ }
+ entity guy = GetClosest( guys, player.GetOrigin() )
+ if ( conv in player.s.lastAIConversationTime )
+ delete player.s.lastAIConversationTime[ conv ]
+
+ printt( "Play ai conversation " + conv )
+ PlaySquadConversationToAll( conv, guy )
+}
+#endif //DEV
+
+void function FighterExplodes( entity ship )
+{
+ vector origin = ship.GetOrigin()
+ vector angles = ship.GetAngles()
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "AngelCity_Scr_RedeyeWeaponExplos" )
+ #if SERVER
+ PlayFX( FX_HORNET_DEATH, origin )
+ #else
+ int fxid = GetParticleSystemIndex( FX_HORNET_DEATH )
+ StartParticleEffectInWorld( fxid, origin, angles )
+ #endif
+}
+
+vector function PositionOffsetFromEnt( entity ent, float offsetX, float offsetY, float offsetZ )
+{
+ vector angles = ent.GetAngles()
+ vector origin = ent.GetOrigin()
+ origin += AnglesToForward( angles ) * offsetX
+ origin += AnglesToRight( angles ) * offsetY
+ origin += AnglesToUp( angles ) * offsetZ
+ return origin
+}
+
+vector function PositionOffsetFromOriginAngles( vector origin, vector angles, float offsetX, float offsetY, float offsetZ )
+{
+ origin += AnglesToForward( angles ) * offsetX
+ origin += AnglesToRight( angles ) * offsetY
+ origin += AnglesToUp( angles ) * offsetZ
+ return origin
+}
+
+
+bool function IsMenuLevel()
+{
+ return IsLobby()
+}
+
+function Dump( package, depth = 0 )
+{
+ if ( depth > 6 )
+ return
+
+ foreach ( k, v in package )
+ {
+ for ( int i = 0; i < depth; i++ )
+ print( " ")
+
+ if ( IsTable( package ) )
+ printl( "Key: " + k + " Value: " + v )
+ if ( IsArray( package ) )
+ printl( "Index: " + k + " Value: " + v )
+
+ if ( IsTable( v ) || IsArray( v ) )
+ Dump( v, depth + 1 )
+ }
+}
+
+bool function UseShortNPCTitles()
+{
+ return GetCurrentPlaylistVarInt( "npc_short_titles", 0 ) ? true : false
+}
+
+string function GetShortNPCTitle( int team )
+{
+ return GetTeamName( team )
+}
+
+bool function IsIMCOrMilitiaTeam( int team )
+{
+ return team == TEAM_MILITIA || team == TEAM_IMC
+}
+
+int function GetOtherTeam( int team )
+{
+ if ( team == TEAM_IMC )
+ return TEAM_MILITIA
+
+ if ( team == TEAM_MILITIA )
+ return TEAM_IMC
+
+ Assert( false, "Trying to GetOtherTeam() for team: " + team + " that is neither Militia nor IMC" )
+ unreachable
+}
+
+float function VectorDot_PlayerToOrigin( entity player, vector targetOrigin )
+{
+ vector playerEyePosition = player.EyePosition()
+ vector vecToEnt = ( targetOrigin - playerEyePosition )
+ vecToEnt.Norm()
+
+ // GetViewVector() only works on the player
+ float dotVal = vecToEnt.Dot( player.GetViewVector() )
+ return dotVal
+}
+
+float function VectorDot_DirectionToOrigin( entity player, vector direction, vector targetOrigin )
+{
+ vector playerEyePosition = player.EyePosition()
+ vector vecToEnt = ( targetOrigin - playerEyePosition )
+ vecToEnt.Norm()
+
+ // GetViewVector() only works on the player
+ float dotVal = DotProduct( vecToEnt, direction )
+ return dotVal
+}
+
+void function WaitUntilWithinDistance( entity player, entity titan, float dist )
+{
+ float distSqr = dist * dist
+ for ( ;; )
+ {
+ if ( !IsAlive( titan ) )
+ return
+
+ if ( IsAlive( player ) )
+ {
+ if ( DistanceSqr( player.GetOrigin(), titan.GetOrigin() ) <= distSqr )
+ return
+ }
+ wait 0.1
+ }
+}
+
+void function WaitUntilBeyondDistance( entity player, entity titan, float dist )
+{
+ float distSqr = dist * dist
+ for ( ;; )
+ {
+ if ( !IsAlive( titan ) )
+ return
+
+ if ( IsAlive( player ) )
+ {
+ if ( DistanceSqr( player.GetOrigin(), titan.GetOrigin() ) > distSqr )
+ return
+ }
+ wait 0.1
+ }
+}
+
+bool function IsModelViewer()
+{
+ return GetMapName() == "mp_model_viewer"
+}
+
+
+//----------------------------------//
+// Tweening functions //
+// Pass in a fraction 0.0 - 1.0 //
+// Get a fraction back 0.0 - 1.0 //
+//----------------------------------//
+
+// simple linear tweening - no easing, no acceleration
+float function Tween_Linear( float frac )
+{
+ Assert( frac >= 0.0 && frac <= 1.0 )
+ return frac
+}
+
+// quadratic easing out - decelerating to zero velocity
+float function Tween_QuadEaseOut( float frac )
+{
+ Assert( frac >= 0.0 && frac <= 1.0 )
+ return -1.0 * frac*(frac-2)
+}
+
+// exponential easing out - decelerating to zero velocity
+float function Tween_ExpoEaseOut( float frac )
+{
+ Assert( frac >= 0.0 && frac <= 1.0 )
+ return -pow( 2.0, -10.0 * frac ) + 1.0
+}
+
+float function Tween_ExpoEaseIn( float frac )
+{
+ Assert( frac >= 0.0 && frac <= 1.0 )
+ return pow( 2, 10 * ( frac - 1 ) );
+}
+
+bool function LegalOrigin( vector origin )
+{
+ if ( fabs( origin.x ) > MAX_WORLD_COORD )
+ return false
+
+ if ( fabs( origin.y ) > MAX_WORLD_COORD )
+ return false
+
+ if ( fabs( origin.z ) > MAX_WORLD_COORD )
+ return false
+
+ return true
+}
+
+vector function AnglesOnSurface( surfaceNormal, playerVelocity )
+{
+ playerVelocity.Norm()
+ vector right = CrossProduct( playerVelocity, surfaceNormal )
+ vector forward = CrossProduct( surfaceNormal, right )
+ vector angles = VectorToAngles( forward )
+ angles.z = atan2( right.z, surfaceNormal.z ) * RAD_TO_DEG
+
+ return angles
+}
+
+vector function ClampToWorldspace( vector origin )
+{
+ // temp solution for start positions that are outside the world bounds
+ origin.x = clamp( origin.x, -MAX_WORLD_COORD, MAX_WORLD_COORD )
+ origin.y = clamp( origin.y, -MAX_WORLD_COORD, MAX_WORLD_COORD )
+ origin.z = clamp( origin.z, -MAX_WORLD_COORD, MAX_WORLD_COORD )
+
+ return origin
+}
+
+function UseReturnTrue( user, usee )
+{
+ return true
+}
+
+function ControlPanel_CanUseFunction( playerUser, controlPanel )
+{
+ expect entity( playerUser )
+ expect entity( controlPanel )
+
+ // Does a simple cone FOV check from the screen to the player's eyes
+ int maxAngleToAxisAllowedDegrees = 60
+
+ vector playerEyePos = playerUser.EyePosition()
+ int attachmentIndex = controlPanel.LookupAttachment( "PANEL_SCREEN_MIDDLE" )
+
+ Assert( attachmentIndex != 0 )
+ vector controlPanelScreenPosition = controlPanel.GetAttachmentOrigin( attachmentIndex )
+ vector controlPanelScreenAngles = controlPanel.GetAttachmentAngles( attachmentIndex )
+ vector controlPanelScreenForward = AnglesToForward( controlPanelScreenAngles )
+
+ vector screenToPlayerEyes = Normalize( playerEyePos - controlPanelScreenPosition )
+
+ return DotProduct( screenToPlayerEyes, controlPanelScreenForward ) > deg_cos( maxAngleToAxisAllowedDegrees )
+}
+
+function SentryTurret_CanUseFunction( playerUser, sentryTurret )
+{
+ expect entity( playerUser )
+ expect entity( sentryTurret )
+
+ // Does a simple cone FOV check from the screen to the player's eyes
+ int maxAngleToAxisAllowedDegrees = 90
+
+ vector playerEyePos = playerUser.EyePosition()
+ int attachmentIndex = sentryTurret.LookupAttachment( "turret_player_use" )
+
+ Assert( attachmentIndex != 0 )
+ vector sentryTurretUsePosition = sentryTurret.GetAttachmentOrigin( attachmentIndex )
+ vector sentryTurretUseAngles = sentryTurret.GetAttachmentAngles( attachmentIndex )
+ vector sentryTurretUseForward = AnglesToForward( sentryTurretUseAngles )
+
+ vector useToPlayerEyes = Normalize( playerEyePos - sentryTurretUsePosition )
+
+ return DotProduct( useToPlayerEyes, sentryTurretUseForward ) > deg_cos( maxAngleToAxisAllowedDegrees )
+}
+
+void function ArrayRemoveInvalid( array<entity> ents )
+{
+ for ( int i = ents.len() - 1; i >= 0; i-- )
+ {
+ if ( !IsValid( ents[ i ] ) )
+ ents.remove( i )
+ }
+}
+
+bool function HasDamageStates( entity ent )
+{
+ if ( !IsValid( ent ) )
+ return false
+ return ( "damageStateInfo" in ent.s )
+}
+
+bool function HasHitData( entity ent )
+{
+ return ( "hasHitData" in ent.s && expect bool( ent.s.hasHitData ) )
+}
+
+FrontRightDotProductsStruct function GetFrontRightDots( entity baseEnt, entity relativeEnt, string optionalTag = "" )
+{
+ if ( optionalTag != "" )
+ {
+ int attachIndex = baseEnt.LookupAttachment( optionalTag )
+ vector origin = baseEnt.GetAttachmentOrigin( attachIndex )
+ vector angles = baseEnt.GetAttachmentAngles( attachIndex )
+ angles.x = 0
+ angles.z = 0
+ vector forward = AnglesToForward( angles )
+ vector right = AnglesToRight( angles )
+
+ vector targetOrg = relativeEnt.GetOrigin()
+ vector vecToEnt = ( targetOrg - origin )
+// printt( "vecToEnt ", vecToEnt )
+ vecToEnt.z = 0
+
+ vecToEnt.Norm()
+
+
+ FrontRightDotProductsStruct result
+ result.forwardDot = DotProduct( vecToEnt, forward )
+ result.rightDot = DotProduct( vecToEnt, right )
+
+ // red: forward for incoming ent
+ //DebugDrawLine( origin, origin + vecToEnt * 150, 255, 0, 0, true, 5 )
+
+ // green: tag forward
+ //DebugDrawLine( origin, origin + forward * 150, 0, 255, 0, true, 5 )
+
+ // blue: tag right
+ //DebugDrawLine( origin, origin + right * 150, 0, 0, 255, true, 5 )
+ return result
+ }
+
+ vector targetOrg = relativeEnt.GetOrigin()
+ vector origin = baseEnt.GetOrigin()
+ vector vecToEnt = ( targetOrg - origin )
+ vecToEnt.Norm()
+
+ FrontRightDotProductsStruct result
+ result.forwardDot = vecToEnt.Dot( baseEnt.GetForwardVector() )
+ result.rightDot = vecToEnt.Dot( baseEnt.GetRightVector() )
+ return result
+}
+
+
+
+array<vector> function GetAllPointsOnBezier( array<vector> points, int numSegments, float debugDrawTime = 0.0 )
+{
+ Assert( points.len() >= 2 )
+ Assert( numSegments > 0 )
+ array<vector> curvePoints = []
+
+ // Debug draw the points used for the curve
+ if ( debugDrawTime )
+ {
+ for ( int i = 0; i < points.len() - 1; i++ )
+ DebugDrawLine( points[i], points[i + 1], 150, 150, 150, true, debugDrawTime )
+ }
+
+ for ( int i = 0; i < numSegments; i++ )
+ {
+ float t = ( i.tofloat() / ( numSegments.tofloat() - 1.0 ) ).tofloat()
+ curvePoints.append( GetSinglePointOnBezier( points, t ) )
+ }
+
+ return curvePoints
+}
+
+vector function GetSinglePointOnBezier( array<vector> points, float t )
+{
+ // evaluate a point on a bezier-curve. t goes from 0 to 1.0
+
+ array<vector> lastPoints = clone points
+ for(;;)
+ {
+ array<vector> newPoints = []
+ for ( int i = 0; i < lastPoints.len() - 1; i++ )
+ newPoints.append( lastPoints[i] + ( lastPoints[i+1] - lastPoints[i] ) * t )
+
+ if ( newPoints.len() == 1 )
+ return newPoints[0]
+
+ lastPoints = newPoints
+ }
+
+ unreachable
+}
+
+bool function GetDoomedState( entity ent )
+{
+ entity soul = ent.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return false
+
+ return soul.IsDoomed()
+}
+
+bool function TitanCoreInUse( entity player )
+{
+ Assert( player.IsTitan() )
+
+ if ( !IsAlive( player ) )
+ return false
+
+ return Time() < SoulTitanCore_GetExpireTime( player.GetTitanSoul() )
+}
+
+
+// Return float or null
+function GetTitanCoreTimeRemaining( entity player )
+{
+ if ( !player.IsTitan() )
+ return null
+
+ entity soul = player.GetTitanSoul()
+
+ if ( !soul )
+ return null
+
+ return SoulTitanCore_GetExpireTime( soul ) - Time()
+}
+
+bool function CoreAvailableDuringDoomState()
+{
+ return true
+}
+
+bool function HasAntiTitanWeapon( entity guy )
+{
+ foreach ( weapon in guy.GetMainWeapons() )
+ {
+ if ( weapon.GetWeaponType() == WT_ANTITITAN )
+ return true
+ }
+ return false
+}
+
+float function GetTitanCoreActiveTime( entity player )
+{
+ entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( weapon ) )
+ {
+ printt( "WARNING: tried to get core active time, but core weapon was invalid." )
+ printt( "titan is alive? " + IsAlive( player ) )
+ return 5.0 // default
+ }
+
+ return GetTitanCoreDurationFromWeapon( weapon )
+}
+
+float function GetTitanCoreChargeTime( entity player )
+{
+ entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( weapon ) )
+ {
+ printt( "WARNING: tried to get core charge time, but core weapon was invalid." )
+ printt( "titan is alive? " + IsAlive( player ) )
+ return 1.0 // default
+ }
+
+ return GetTitanCoreChargeTimeFromWeapon( weapon )
+}
+
+float function GetTitanCoreChargeTimeFromWeapon( entity weapon )
+{
+ return expect float( weapon.GetWeaponInfoFileKeyField( "chargeup_time" ) )
+}
+
+float function GetTitanCoreBuildTimeFromWeapon( entity weapon )
+{
+ return expect float( weapon.GetWeaponInfoFileKeyField( "core_build_time" ).tofloat() )
+}
+
+float function GetTitanCoreDurationFromWeapon( entity weapon )
+{
+ float coreDuration = weapon.GetCoreDuration()
+
+ entity player = weapon.GetWeaponOwner()
+ if ( IsValid( player ) && player.IsPlayer() )
+ {
+ if ( PlayerHasPassive( player, ePassives.PAS_MARATHON_CORE ) )
+ coreDuration *= TITAN_CORE_MARATHON_CORE_MULTIPLIER
+ }
+
+ return coreDuration
+}
+
+float function GetCoreBuildTime( entity titan )
+{
+ if ( titan.IsPlayer() )
+ titan = GetTitanFromPlayer( titan )
+
+ Assert( titan != null )
+
+ entity coreWeapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( coreWeapon ) )
+ {
+ //printt( "WARNING: tried to set build timer, but core weapon was invalid." )
+ //printt( "titan is alive? " + IsAlive( titan ) )
+ return 200.0 // default
+ }
+
+
+ return GetTitanCoreBuildTimeFromWeapon( coreWeapon )
+}
+
+string function GetCoreShortName( entity titan )
+{
+ entity coreWeapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( coreWeapon ) )
+ {
+ printt( "WARNING: tried to get core name, but core weapon was invalid." )
+ printt( "titan is alive? " + IsAlive( titan ) )
+ return "#HUD_READY"
+ }
+
+ string name = expect string( coreWeapon.GetWeaponInfoFileKeyField( "shortprintname" ) )
+ return name
+}
+
+string ornull function GetCoreOSConversationName( entity titan, string event )
+{
+ entity coreWeapon = titan.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( coreWeapon ) )
+ {
+ printt( "WARNING: tried to get core sound for " + event + ", but core weapon was invalid." )
+ printt( "titan is alive? " + IsAlive( titan ) )
+ return null
+ }
+
+ var alias = coreWeapon.GetWeaponInfoFileKeyField( "dialog_" + event )
+
+ if ( alias == null )
+ return null
+
+ expect string( alias )
+
+ return alias
+}
+
+entity function GetTitanFromPlayer( entity player )
+{
+ Assert( player.IsPlayer() )
+ if ( player.IsTitan() )
+ return player
+
+ return player.GetPetTitan()
+}
+
+int function GetNuclearPayload( entity player )
+{
+ if ( !GetDoomedState( player ) )
+ return 0
+
+ int payload = 0
+ if ( PlayerHasPassive( player, ePassives.PAS_NUCLEAR_CORE ) )
+ payload += 2
+
+ if ( PlayerHasPassive( player, ePassives.PAS_BUILD_UP_NUCLEAR_CORE ) )
+ payload += 1
+
+ return payload
+}
+
+entity function GetCloak( entity ent )
+{
+ return GetOffhand( ent, "mp_ability_cloak" )
+}
+
+entity function GetOffhand( entity ent, string classname )
+{
+ entity offhand = ent.GetOffhandWeapon( OFFHAND_LEFT )
+ if ( IsValid( offhand ) && offhand.GetWeaponClassName() == classname )
+ return offhand
+
+ offhand = ent.GetOffhandWeapon( OFFHAND_RIGHT )
+ if ( IsValid( offhand ) && offhand.GetWeaponClassName() == classname )
+ return offhand
+
+ return null
+}
+
+bool function IsCloaked( entity ent )
+{
+ return ent.IsCloaked( true ) //pass true to ignore flicker time -
+}
+
+float function TimeSpentInCurrentState()
+{
+ return Time() - expect float( level.nv.gameStateChangeTime )
+}
+
+float function DotToAngle( float dot )
+{
+ return acos( dot ) * RAD_TO_DEG
+}
+
+float function AngleToDot( float angle )
+{
+ return cos( angle * DEG_TO_RAD )
+}
+
+int function GetGameState()
+{
+ return expect int( GetServerVar( "gameState" ) )
+}
+
+bool function GamePlaying()
+{
+ return GetGameState() == eGameState.Playing
+}
+
+bool function GamePlayingOrSuddenDeath()
+{
+ int gameState = GetGameState()
+ return gameState == eGameState.Playing || gameState == eGameState.SuddenDeath
+}
+
+bool function IsOdd( int num )
+{
+ return ( num % 2 ) == 1
+}
+
+bool function IsEven( int num )
+{
+ return !IsOdd( num )
+}
+
+vector function VectorReflectionAcrossNormal( vector vec, vector normal )
+{
+ return ( vec - normal * ( 2 * DotProduct( vec, normal ) ) )
+}
+
+// Return an array of entities ordered from farthest to closest to the specified origin
+array<entity> function ArrayFarthest( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResults( entArray, origin )
+
+ allResults.sort( DistanceCompareFarthest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ returnEntities.append( result.ent )
+
+ // the actual distances aren't returned
+ return returnEntities
+}
+
+// Return an array of vectors ordered from closest to furthest from the specified origin
+array<vector> function ArrayFarthestVector( array<vector> vecArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResultsVector( vecArray, origin )
+
+ allResults.sort( DistanceCompareFarthest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ returnVecs.append( result.origin )
+
+ return returnVecs
+}
+
+// Return an array of entities ordered from closest to furthest from the specified origin
+array<entity> function ArrayClosest( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResults( entArray, origin )
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ returnEntities.append( result.ent )
+
+ return returnEntities
+}
+
+// Return an array of vectors ordered from closest to furthest from the specified origin
+array<vector> function ArrayClosestVector( array<vector> vecArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResultsVector( vecArray, origin )
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ returnVecs.append( result.origin )
+
+ return returnVecs
+}
+
+array<entity> function ArrayClosestWithinDistance( array<entity> entArray, vector origin, float maxDistance )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResults( entArray, origin )
+ float maxDistSq = maxDistance * maxDistance
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ {
+ if ( result.distanceSqr > maxDistSq )
+ break
+
+ returnEntities.append( result.ent )
+ }
+
+ return returnEntities
+}
+
+array<vector> function ArrayClosestVectorWithinDistance( array<vector> vecArray, vector origin, float maxDistance )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistanceResultsVector( vecArray, origin )
+ float maxDistSq = maxDistance * maxDistance
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ {
+ if ( result.distanceSqr > maxDistSq )
+ break
+
+ returnVecs.append( result.origin )
+ }
+
+ return returnVecs
+}
+
+// Return an array of entities ordered from closest to furthest from the specified origin, ignoring z
+array<entity> function ArrayClosest2D( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistance2DResults( entArray, origin )
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ returnEntities.append( result.ent )
+
+ return returnEntities
+}
+
+// Return an array of entities ordered from closest to furthest from the specified origin, ignoring z
+array<vector> function ArrayClosest2DVector( array<vector> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistance2DResultsVector( entArray, origin )
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ returnVecs.append( result.origin )
+
+ return returnVecs
+}
+
+array<entity> function ArrayClosest2DWithinDistance( array<entity> entArray, vector origin, float maxDistance )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistance2DResults( entArray, origin )
+ float maxDistSq = maxDistance * maxDistance
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<entity> returnEntities
+
+ foreach ( result in allResults )
+ {
+ if ( result.distanceSqr > maxDistSq )
+ break
+
+ returnEntities.append( result.ent )
+ }
+
+ return returnEntities
+}
+
+// Return an array of entities ordered from closest to furthest from the specified origin, ignoring z
+array<vector> function ArrayClosest2DVectorWithinDistance( array<vector> entArray, vector origin, float maxDistance )
+{
+ array<ArrayDistanceEntry> allResults = ArrayDistance2DResultsVector( entArray, origin )
+ float maxDistSq = maxDistance * maxDistance
+
+ allResults.sort( DistanceCompareClosest )
+
+ array<vector> returnVecs
+
+ foreach ( result in allResults )
+ {
+ if ( result.distanceSqr > maxDistSq )
+ break
+
+ returnVecs.append( result.origin )
+ }
+
+ return returnVecs
+}
+
+bool function ArrayEntityWithinDistance( array<entity> entArray, vector origin, float distance )
+{
+ float distSq = distance * distance
+ foreach( entity ent in entArray )
+ {
+ if ( DistanceSqr( ent.GetOrigin(), origin ) <= distSq )
+ return true
+ }
+ return false
+}
+
+function TableRemove( Table, entry )
+{
+ Assert( typeof Table == "table" )
+
+ foreach ( index, tableEntry in Table )
+ {
+ if ( tableEntry == entry )
+ {
+ Table[ index ] = null
+ }
+ }
+}
+
+function TableInvert( Table )
+{
+ table invertedTable = {}
+ foreach ( key, value in Table )
+ invertedTable[ value ] <- key
+
+ return invertedTable
+}
+
+int function DistanceCompareClosest( ArrayDistanceEntry a, ArrayDistanceEntry b )
+{
+ if ( a.distanceSqr > b.distanceSqr )
+ return 1
+ else if ( a.distanceSqr < b.distanceSqr )
+ return -1
+
+ return 0;
+}
+
+int function DistanceCompareFarthest( ArrayDistanceEntry a, ArrayDistanceEntry b )
+{
+ if ( a.distanceSqr < b.distanceSqr )
+ return 1
+ else if ( a.distanceSqr > b.distanceSqr )
+ return -1
+
+ return 0;
+}
+
+array<ArrayDistanceEntry> function ArrayDistanceResults( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults
+
+ foreach ( ent in entArray )
+ {
+ ArrayDistanceEntry entry
+
+ vector entOrigin = ent.GetOrigin()
+ if ( IsSpawner( ent ) )
+ {
+ var spawnKVs = ent.GetSpawnEntityKeyValues()
+ entOrigin = StringToVector( string( spawnKVs.origin ) )
+ }
+ entry.distanceSqr = DistanceSqr( entOrigin, origin )
+ entry.ent = ent
+ entry.origin = entOrigin
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+array<ArrayDistanceEntry> function ArrayDistanceResultsVector( array<vector> vecArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults
+
+ foreach ( vec in vecArray )
+ {
+ ArrayDistanceEntry entry
+
+ entry.distanceSqr = DistanceSqr( vec, origin )
+ entry.ent = null
+ entry.origin = vec
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+array<ArrayDistanceEntry> function ArrayDistance2DResults( array<entity> entArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults
+
+ foreach ( ent in entArray )
+ {
+ ArrayDistanceEntry entry
+
+ vector entOrigin = ent.GetOrigin()
+
+ entry.distanceSqr = Distance2DSqr( entOrigin, origin )
+ entry.ent = ent
+ entry.origin = entOrigin
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+array<ArrayDistanceEntry> function ArrayDistance2DResultsVector( array<vector> vecArray, vector origin )
+{
+ array<ArrayDistanceEntry> allResults
+
+ foreach ( vec in vecArray )
+ {
+ ArrayDistanceEntry entry
+
+ entry.distanceSqr = Distance2DSqr( vec, origin )
+ entry.ent = null
+ entry.origin = vec
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+GravityLandData function GetGravityLandData( vector startPos, vector parentVelocity, vector objectVelocity, float timeLimit, bool bDrawPath = false, float bDrawPathDuration = 0.0, array pathColor = [ 255, 255, 0 ] )
+{
+ GravityLandData returnData
+
+ Assert( timeLimit > 0 )
+
+ float MAX_TIME_ELAPSE = 6.0
+ float timeElapsePerTrace = 0.1
+
+ float sv_gravity = 750.0
+ float ent_gravity = 1.0
+ float gravityScale = 1.0
+
+ vector traceStart = startPos
+ vector traceEnd = traceStart
+ float traceFrac
+ int traceCount = 0
+
+ objectVelocity += parentVelocity
+
+ while( returnData.elapsedTime <= timeLimit )
+ {
+ objectVelocity.z -= ( ent_gravity * sv_gravity * timeElapsePerTrace * gravityScale )
+
+ traceEnd += objectVelocity * timeElapsePerTrace
+ returnData.points.append( traceEnd )
+ if ( bDrawPath )
+ DebugDrawLine( traceStart, traceEnd, pathColor[0], pathColor[1], pathColor[2], false, bDrawPathDuration )
+
+ traceFrac = TraceLineSimple( traceStart, traceEnd, null )
+ traceCount++
+ if ( traceFrac < 1.0 )
+ {
+ returnData.traceResults = TraceLine( traceStart, traceEnd, null, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
+ return returnData
+ }
+ traceStart = traceEnd
+ returnData.elapsedTime += timeElapsePerTrace
+ }
+
+ return returnData
+}
+
+float function GetPulseFrac( rate = 1, startTime = 0 )
+{
+ return (1 - cos( ( Time() - startTime ) * (rate * (2*PI)) )) / 2
+}
+
+bool function IsPetTitan( titan )
+{
+ Assert( titan.IsTitan() )
+
+ return titan.GetTitanSoul().GetBossPlayer() != null
+}
+
+vector function StringToVector( string vecString, string delimiter = " " )
+{
+ array<string> tokens = split( vecString, delimiter )
+
+ Assert( tokens.len() >= 3 )
+
+ return Vector( float( tokens[0] ), float( tokens[1] ), float( tokens[2] ) )
+}
+
+float function GetShieldHealthFrac( entity ent )
+{
+ if ( !IsAlive( ent ) )
+ return 0.0
+
+ if ( HasSoul( ent ) )
+ {
+ entity soul = ent.GetTitanSoul()
+ if ( IsValid( soul ) )
+ ent = soul
+ }
+
+ int shieldHealth = ent.GetShieldHealth()
+ int shieldMaxHealth = ent.GetShieldHealthMax()
+
+ if ( shieldMaxHealth == 0 )
+ return 0.0
+
+ return float( shieldHealth ) / float( shieldMaxHealth )
+}
+
+vector function HackGetDeltaToRef( vector origin, vector angles, entity ent, string anim )
+{
+ AnimRefPoint animStartPos = ent.Anim_GetStartForRefPoint( anim, origin, angles )
+
+ vector delta = origin - animStartPos.origin
+ return origin + delta
+}
+
+vector function HackGetDeltaToRefOnPlane( vector origin, vector angles, entity ent, string anim, vector up )
+{
+ AnimRefPoint animStartPos = ent.Anim_GetStartForRefPoint( anim, origin, angles )
+
+ vector delta = origin - animStartPos.origin
+ vector nDelta = Normalize( delta )
+ vector xProd = CrossProduct( nDelta, up )
+ vector G = CrossProduct( up, xProd )
+ vector planarDelta = G * DotProduct( delta, G )
+ vector P = origin + planarDelta
+
+// DebugDrawLine( origin + delta, origin, 255, 0, 0, true, 1.0 )
+// DebugDrawLine( P, origin, 0,255, 100, true, 1.0 )
+
+ return P
+}
+
+TraceResults function GetViewTrace( entity ent )
+{
+ vector traceStart = ent.EyePosition()
+ vector traceEnd = traceStart + (ent.GetPlayerOrNPCViewVector() * 56756) // longest possible trace given our map size limits
+ array<entity> ignoreEnts = [ ent ]
+
+ return TraceLine( traceStart, traceEnd, ignoreEnts, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
+}
+
+function GetModSourceID( modString )
+{
+ foreach ( name, id in getconsttable().eModSourceId )
+ {
+ if ( string( name ) == modString )
+ return id
+ }
+
+ return null
+}
+
+void function ArrayRemoveDead( array<entity> entArray )
+{
+ for ( int i = entArray.len() - 1; i >= 0; i-- )
+ {
+ if ( !IsAlive( entArray[ i ] ) )
+ entArray.remove( i )
+ }
+}
+
+array<entity> function GetSortedPlayers( IntFromEntityCompare compareFunc, int team )
+{
+ array<entity> players
+
+ if ( team )
+ players = GetPlayerArrayOfTeam( team )
+ else
+ players = GetPlayerArray()
+
+ players.sort( compareFunc )
+
+ return players
+}
+
+
+// Sorts by kills and resolves ties in this order: fewest deaths, most titan kills, most assists
+int function CompareKills( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal > bVal )
+ return 1
+ else if ( aVal < bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_ASSISTS )
+ bVal = b.GetPlayerGameStat( PGS_ASSISTS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+// Sorts by kills and resolves ties in this order: fewest deaths, most titan kills, most assists
+int function CompareAssaultScore( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareScore( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareAssault( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareDefense( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareLTS( entity a, entity b )
+{
+ int result = CompareTitanKills( a, b )
+ if ( result != 0 )
+ return result
+
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareCP( entity a, entity b )
+{
+ // Capture Point sorting. Sort priority = assault + defense > pilot kills > titan kills > death
+
+ {
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ aVal += a.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+ bVal += b.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Pilot Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Titan Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 4) Deaths
+ {
+ int aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ int bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+ }
+
+ return 0
+}
+
+
+int function CompareCTF( entity a, entity b )
+{
+ // Capture the flag sorting. Sort priority = flag captures > flag returns > pilot kills > titan kills > death
+
+ // 1) Flag Captures
+ int result = CompareAssault( a, b )
+ if ( result != 0 )
+ return result
+
+ // 2) Flag Returns
+ result = CompareDefense( a, b )
+ if ( result != 0 )
+ return result
+
+ // 3) Pilot Kills
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 3) Titan Kills
+ aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 4) Deaths
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+
+ return 0
+}
+
+int function CompareSpeedball( entity a, entity b )
+{
+ // Capture the flag sorting. Sort priority = pilot kills > flag captures > death
+
+ // 1) Pilot Kills
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 2) Flag Captures
+ int result = CompareAssault( a, b )
+ if ( result != 0 )
+ return result
+
+ // 3) Deaths
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+
+ return 0
+}
+
+int function CompareMFD( entity a, entity b )
+{
+ // 1) Marks Killed
+ int result = CompareAssault( a, b )
+ if ( result != 0 )
+ return result
+
+ // 2) Marks Outlasted
+ result = CompareDefense( a, b )
+ if ( result != 0 )
+ return result
+
+ // 3) Pilot Kills
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 4) Titan Kills
+ aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 5) Deaths
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+
+ return 0
+}
+
+int function CompareScavenger( entity a, entity b )
+{
+ // 1) Ore Captured
+ int result = CompareAssault( a, b )
+ if ( result != 0 )
+ return result
+
+ // 2) Pilot Kills
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 3) Titan Kills
+ aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ // 4) Deaths
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+
+ return 0
+}
+
+int function CompareFW( entity a, entity b )
+{
+ // Capture Point sorting. Sort priority = assault + defense > pilot kills > titan kills > death
+
+ {
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ aVal += a.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+ bVal += b.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Pilot Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Titan Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 4) Deaths
+ {
+ int aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ int bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+ }
+
+ return 0
+}
+
+int function CompareHunter( entity a, entity b )
+{
+ // Capture Point sorting. Sort priority = assault + defense > pilot kills > titan kills > death
+
+ {
+ int aVal = a.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+
+ aVal += a.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+ bVal += b.GetPlayerGameStat( PGS_DEFENSE_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Pilot Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 3) Titan Kills
+ {
+ int aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+ }
+
+ // 4) Deaths
+ {
+ int aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ int bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal < bVal )
+ return -1
+ else if ( aVal > bVal )
+ return 1
+ }
+
+ return 0
+}
+
+// Sorts by kills, deaths and then cash
+int function CompareATCOOP( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_DEATHS )
+ bVal = b.GetPlayerGameStat( PGS_DEATHS )
+
+ if ( aVal > bVal )
+ return 1
+ else if ( aVal < bVal )
+ return -1
+
+ aVal = a.GetPlayerGameStat( PGS_SCORE )
+ bVal = b.GetPlayerGameStat( PGS_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareFD( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_DETONATION_SCORE )
+ int bVal = b.GetPlayerGameStat( PGS_DETONATION_SCORE )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+int function CompareTitanKills( entity a, entity b )
+{
+ int aVal = a.GetPlayerGameStat( PGS_TITAN_KILLS )
+ int bVal = b.GetPlayerGameStat( PGS_TITAN_KILLS )
+
+ if ( aVal < bVal )
+ return 1
+ else if ( aVal > bVal )
+ return -1
+
+ return 0
+}
+
+bool function TitanEjectIsDisabled()
+{
+ return GetGlobalNetBool( "titanEjectEnabled" ) == false
+}
+
+bool function IsHitEffectiveVsTitan( entity victim, int damageType )
+{
+ Assert( victim.IsTitan() )
+
+ if ( victim.IsPlayer() )
+ {
+ if ( PlayerHasPassive( victim, ePassives.PAS_BERSERKER ) )
+ return false
+ }
+
+ if ( !( damageType & DF_CRITICAL ) && ( damageType & DF_BULLET || damageType & DF_MAX_RANGE ) )
+ return false
+
+ return true
+}
+
+bool function IsHitEffectiveVsNonTitan( entity victim, int damageType )
+{
+ if ( damageType & DF_BULLET || damageType & DF_MAX_RANGE )
+ return false;
+
+ return true
+}
+
+bool function IsPilot( entity ent )
+{
+ if ( !IsValid( ent ) )
+ return false
+
+ if ( !ent.IsPlayer() )
+ return false
+
+ if ( ent.IsTitan() )
+ return false
+
+ return true
+}
+
+bool function IsPilotDecoy( entity ent )
+{
+ if ( !IsValid( ent ) )
+ return false
+
+ if ( ent.GetClassName() != "player_decoy" )
+ return false
+
+ return true
+}
+
+string function HardpointIDToString( int id )
+{
+ array<string> hardpointIDString = [ "a", "b", "c" ]
+
+ Assert( id >= 0 && id < hardpointIDString.len() )
+
+ return hardpointIDString[ id ]
+}
+
+string function Dev_TeamIDToString( id )
+{
+ if ( id == TEAM_IMC )
+ return "IMC"
+ if ( id == TEAM_MILITIA )
+ return "MIL"
+
+ return "UNASSIGNED/UNKNOWN TEAM NAME"
+}
+
+array<entity> function ArrayWithin( array<entity> Array, vector origin, float maxDist )
+{
+ float maxDistSqr = maxDist * maxDist
+
+ array<entity> resultArray = []
+ foreach ( ent in Array )
+ {
+ float distSqr = DistanceSqr( origin, ent.GetOrigin() )
+ if ( distSqr <= maxDistSqr )
+ resultArray.append( ent )
+ }
+ return resultArray
+}
+
+function GetTitanChassis( entity titan )
+{
+ if ( !("titanChassis" in titan.s ) )
+ {
+ if ( HasSoul( titan ) )
+ {
+ entity soul = titan.GetTitanSoul()
+ titan.s.titanChassis <- GetSoulTitanSubClass( soul )
+ }
+ else
+ {
+ return "Invalid Chassis"
+ }
+ }
+
+ return titan.s.titanChassis
+}
+
+vector function ClampVectorToCube( vector vecStart, vector vec, vector cubeOrigin, float cubeSize )
+{
+ float halfCubeSize = cubeSize * 0.5
+ vector cubeMins = < -halfCubeSize, -halfCubeSize, -halfCubeSize >
+ vector cubeMaxs = < halfCubeSize, halfCubeSize, halfCubeSize >
+
+ return ClampVectorToBox( vecStart, vec, cubeOrigin, cubeMins, cubeMaxs )
+}
+
+vector function ClampVectorToBox( vector vecStart, vector vec, vector cubeOrigin, vector cubeMins, vector cubeMaxs )
+{
+ float smallestClampScale = 1.0
+ vector vecEnd = vecStart + vec
+
+ smallestClampScale = ClampVectorComponentToCubeMax( cubeOrigin.x, cubeMaxs.x, vecStart.x, vecEnd.x, vec.x, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMax( cubeOrigin.y, cubeMaxs.y, vecStart.y, vecEnd.y, vec.y, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMax( cubeOrigin.z, cubeMaxs.z, vecStart.z, vecEnd.z, vec.z, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMin( cubeOrigin.x, cubeMins.x, vecStart.x, vecEnd.x, vec.x, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMin( cubeOrigin.y, cubeMins.y, vecStart.y, vecEnd.y, vec.y, smallestClampScale )
+ smallestClampScale = ClampVectorComponentToCubeMin( cubeOrigin.z, cubeMins.z, vecStart.z, vecEnd.z, vec.z, smallestClampScale )
+
+ return vec * smallestClampScale
+}
+
+float function ClampVectorComponentToCubeMax( float cubeOrigin, float cubeSize, float vecStart, float vecEnd, float vec, float smallestClampScale )
+{
+ float max = cubeOrigin + cubeSize
+ float clearance = fabs( vecStart - max )
+ if ( vecEnd > max )
+ {
+ float scale = fabs( clearance / ( ( vecStart + vec ) - vecStart ) )
+ if ( scale > 0 && scale < smallestClampScale )
+ return scale
+ }
+
+ return smallestClampScale
+}
+
+float function ClampVectorComponentToCubeMin( float cubeOrigin, float cubeSize, float vecStart, float vecEnd, float vec, float smallestClampScale )
+{
+ float min = cubeOrigin - cubeSize
+ float clearance = fabs( min - vecStart )
+ if ( vecEnd < min )
+ {
+ float scale = fabs( clearance / ( ( vecStart + vec ) - vecStart ) )
+ if ( scale > 0 && scale < smallestClampScale )
+ return scale
+ }
+
+ return smallestClampScale
+}
+
+bool function PointInCapsule( vector vecBottom, vector vecTop, float radius, vector point )
+{
+ return GetDistanceFromLineSegment( vecBottom, vecTop, point ) <= radius
+}
+
+bool function PointInCylinder( vector vecBottom, vector vecTop, float radius, vector point )
+{
+ if ( GetDistanceFromLineSegment( vecBottom, vecTop, point ) > radius )
+ return false
+
+ vector bottomVec = Normalize( vecTop - vecBottom )
+ vector pointToBottom = Normalize( point - vecBottom )
+
+ vector topVec = Normalize( vecBottom - vecTop )
+ vector pointToTop = Normalize( point - vecTop )
+
+ if ( DotProduct( bottomVec, pointToBottom ) < 0 )
+ return false
+
+ if ( DotProduct( topVec, pointToTop ) < 0.0 )
+ return false
+
+ return true
+}
+
+float function AngleDiff( float ang, float targetAng )
+{
+ float delta = ( targetAng - ang ) % 360.0
+ if ( targetAng > ang )
+ {
+ if ( delta >= 180.0 )
+ delta -= 360.0;
+ }
+ else
+ {
+ if ( delta <= -180.0 )
+ delta += 360.0;
+ }
+ return delta
+}
+
+
+float function ClampAngle( float ang )
+{
+ while( ang > 360 )
+ ang -= 360
+ while( ang < 0 )
+ ang += 360
+ return ang
+}
+
+float function ClampAngle180( float ang )
+{
+ while( ang > 180 )
+ ang -= 180
+ while( ang < -180 )
+ ang += 180
+ return ang
+}
+
+vector function ShortestRotation( vector ang, vector targetAng )
+{
+ return Vector( AngleDiff( ang.x, targetAng.x ), AngleDiff( ang.y, targetAng.y ), AngleDiff( ang.z, targetAng.z ) )
+}
+
+int function GetWinningTeam()
+{
+ if ( level.nv.winningTeam != null )
+ return expect int( level.nv.winningTeam )
+
+ if ( IsFFAGame() )
+ return GetWinningTeam_FFA()
+
+ if ( IsRoundBased() )
+ {
+ if ( GameRules_GetTeamScore2( TEAM_IMC ) > GameRules_GetTeamScore2( TEAM_MILITIA ) )
+ return TEAM_IMC
+
+ if ( GameRules_GetTeamScore2( TEAM_MILITIA ) > GameRules_GetTeamScore2( TEAM_IMC ) )
+ return TEAM_MILITIA
+ }
+ else
+ {
+ if ( GameRules_GetTeamScore( TEAM_IMC ) > GameRules_GetTeamScore( TEAM_MILITIA ) )
+ return TEAM_IMC
+
+ if ( GameRules_GetTeamScore( TEAM_MILITIA ) > GameRules_GetTeamScore( TEAM_IMC ) )
+ return TEAM_MILITIA
+ }
+
+ return TEAM_UNASSIGNED
+}
+
+int function GetWinningTeam_FFA()
+{
+ if ( level.nv.winningTeam != null )
+ return expect int( level.nv.winningTeam )
+
+ int maxScore = 0
+ int playerTeam
+ int currentScore
+ int winningTeam = TEAM_UNASSIGNED
+
+ foreach( player in GetPlayerArray() )
+ {
+ playerTeam = player.GetTeam()
+ if ( IsRoundBased() )
+ currentScore = GameRules_GetTeamScore2( playerTeam )
+ else
+ currentScore = GameRules_GetTeamScore( playerTeam )
+
+ if ( currentScore == maxScore) //Treat multiple teams as having the same score as no team winning
+ winningTeam = TEAM_UNASSIGNED
+
+ if ( currentScore > maxScore )
+ {
+ maxScore = currentScore
+ winningTeam = playerTeam
+ }
+ }
+
+ return winningTeam
+
+}
+
+void function EmitSkyboxSoundAtPosition( vector positionInSkybox, string sound, float skyboxScale = 0.001, bool clamp = false )
+{
+ if ( IsServer() )
+ clamp = true // sounds cannot play outside 16k limit on server
+ vector position = SkyboxToWorldPosition( positionInSkybox, skyboxScale, clamp )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, position, sound )
+}
+
+vector function SkyboxToWorldPosition( vector positionInSkybox, float skyboxScale = 0.001, bool clamp = true )
+{
+ Assert( skyboxScale > 0 )
+ Assert( "skyboxCamOrigin" in level )
+
+ vector position = Vector( 0.0, 0.0, 0.0 )
+ vector skyOrigin = expect vector( level.skyboxCamOrigin )
+
+ #if CLIENT
+ position = ( positionInSkybox - skyOrigin ) * ( 1.0 / skyboxScale )
+
+ if ( clamp )
+ {
+ entity localViewPlayer = GetLocalViewPlayer()
+ Assert( localViewPlayer )
+ vector localViewPlayerOrg = localViewPlayer.GetOrigin()
+
+ position = localViewPlayerOrg + ClampVectorToCube( localViewPlayerOrg, position - localViewPlayerOrg, Vector( 0.0, 0.0, 0.0 ), 32000.0 )
+ }
+ #else
+ position = ( positionInSkybox - skyOrigin ) * ( 1.0 / skyboxScale )
+
+ if ( clamp )
+ position = ClampVectorToCube( Vector( 0.0, 0.0, 0.0 ), position, Vector( 0.0, 0.0, 0.0 ), 32000.0 )
+ #endif // CLIENT
+
+ return position
+}
+
+void function FadeOutSoundOnEntityAfterDelay( entity ent, string soundAlias, float delay, float fadeTime )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ ent.EndSignal( "OnDestroy" )
+ wait delay
+ FadeOutSoundOnEntity( ent, soundAlias, fadeTime )
+}
+
+function GetRandomKeyFromWeightedTable( Table )
+{
+ local weightTotal = 0.0
+ foreach ( key, value in Table )
+ {
+ weightTotal += value
+ }
+
+ local randomValue = RandomFloat( weightTotal )
+
+ foreach ( key, value in Table )
+ {
+ if ( randomValue <= weightTotal && randomValue >= weightTotal - value)
+ return key
+ weightTotal -= value
+ }
+}
+
+bool function IsMatchOver()
+{
+ if ( IsRoundBased() && level.nv.gameEndTime )
+ return true
+ else if ( !IsRoundBased() && level.nv.gameEndTime && Time() > level.nv.gameEndTime )
+ return true
+
+ return false
+}
+
+bool function IsScoringNonStandard()
+{
+ return expect bool( level.nv.nonStandardScoring )
+}
+
+bool function IsRoundBased()
+{
+ return expect bool( level.nv.roundBased )
+}
+
+int function GetRoundsPlayed()
+{
+ return expect int( level.nv.roundsPlayed )
+}
+
+bool function IsEliminationBased()
+{
+ return Riff_EliminationMode() != eEliminationMode.Default
+}
+
+bool function IsPilotEliminationBased()
+{
+ return ( Riff_EliminationMode() == eEliminationMode.Pilots || Riff_EliminationMode() == eEliminationMode.PilotsTitans )
+}
+
+bool function IsTitanEliminationBased()
+{
+ return ( Riff_EliminationMode() == eEliminationMode.Titans || Riff_EliminationMode() == eEliminationMode.PilotsTitans )
+}
+
+bool function IsSingleTeamMode()
+{
+ return ( 1 == GetCurrentPlaylistVarInt( "max_teams", 2 ) )
+}
+
+void function __WarpInEffectShared( vector origin, vector angles, string sfx, float preWaitOverride = -1.0 )
+{
+ float preWait = 2.0
+ float sfxWait = 0.1
+ float totalTime = WARPINFXTIME
+
+ if ( sfx == "" )
+ sfx = "dropship_warpin"
+
+ if ( preWaitOverride >= 0.0 )
+ wait preWaitOverride
+ else
+ wait preWait //this needs to go and the const for warpin fx time needs to change - but not this game - the intro system is too dependent on it
+
+ #if CLIENT
+ int fxIndex = GetParticleSystemIndex( FX_GUNSHIP_CRASH_EXPLOSION_ENTRANCE )
+ StartParticleEffectInWorld( fxIndex, origin, angles )
+ #else
+ entity fx = PlayFX( FX_GUNSHIP_CRASH_EXPLOSION_ENTRANCE, origin, angles )
+ fx.FXEnableRenderAlways()
+ fx.DisableHibernation()
+ #endif // CLIENT
+
+ wait sfxWait
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, sfx )
+
+ wait totalTime - preWait - sfxWait
+}
+
+void function __WarpOutEffectShared( entity dropship )
+{
+ int attach = dropship.LookupAttachment( "origin" )
+ vector origin = dropship.GetAttachmentOrigin( attach )
+ vector angles = dropship.GetAttachmentAngles( attach )
+
+ #if CLIENT
+ int fxIndex = GetParticleSystemIndex( FX_GUNSHIP_CRASH_EXPLOSION_EXIT )
+ StartParticleEffectInWorld( fxIndex, origin, angles )
+ #else
+ entity fx = PlayFX( FX_GUNSHIP_CRASH_EXPLOSION_EXIT, origin, angles )
+ fx.FXEnableRenderAlways()
+ fx.DisableHibernation()
+ #endif // CLIENT
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "dropship_warpout" )
+}
+
+bool function IsSwitchSidesBased()
+{
+ return (level.nv.switchedSides != null)
+}
+
+int function HasSwitchedSides() //This returns an int instead of a bool! Should rewrite
+{
+ return expect int( level.nv.switchedSides )
+}
+
+bool function IsFirstRoundAfterSwitchingSides()
+{
+ if ( !IsSwitchSidesBased() )
+ return false
+
+ if ( IsRoundBased() )
+ return level.nv.switchedSides > 0 && GetRoundsPlayed() == level.nv.switchedSides
+ else
+ return level.nv.switchedSides > 0
+
+ unreachable
+}
+
+void function CamBlendFov( entity cam, float oldFov, float newFov, float transTime, float transAccel, float transDecel )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + transTime
+
+ while ( endTime > currentTime )
+ {
+ float interp = Interpolate( startTime, endTime - startTime, transAccel, transDecel )
+ cam.SetFOV( GraphCapped( interp, 0.0, 1.0, oldFov, newFov ) )
+ wait( 0.0 )
+ currentTime = Time()
+ }
+}
+
+void function CamFollowEnt( entity cam, entity ent, float duration, vector offset = <0.0, 0.0, 0.0>, string attachment = "", bool isInSkybox = false )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ vector camOrg = Vector( 0.0, 0.0, 0.0 )
+
+ vector targetPos = Vector( 0.0, 0.0, 0.0 )
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + duration
+ vector diff = Vector( 0.0, 0.0, 0.0 )
+ int attachID = ent.LookupAttachment( attachment )
+
+ while ( endTime > currentTime )
+ {
+ camOrg = cam.GetOrigin()
+
+ if ( attachID <= 0 )
+ targetPos = ent.GetOrigin()
+ else
+ targetPos = ent.GetAttachmentOrigin( attachID )
+
+ if ( isInSkybox )
+ targetPos = SkyboxToWorldPosition( targetPos )
+ diff = ( targetPos + offset ) - camOrg
+
+ cam.SetAngles( VectorToAngles( diff ) )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+void function CamFacePos( entity cam, vector pos, float duration )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + duration
+ vector diff = Vector( 0.0, 0.0, 0.0 )
+
+ while ( endTime > currentTime )
+ {
+ diff = pos - cam.GetOrigin()
+
+ cam.SetAngles( VectorToAngles( diff ) )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+void function CamBlendFromFollowToAng( entity cam, entity ent, vector endAng, float transTime, float transAccel, float transDecel )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ vector camOrg = cam.GetOrigin()
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + transTime
+
+ while ( endTime > currentTime )
+ {
+ vector diff = ent.GetOrigin() - camOrg
+ vector anglesToEnt = VectorToAngles( diff )
+
+ float frac = Interpolate( startTime, endTime - startTime, transAccel, transDecel )
+
+ vector newAngs = anglesToEnt + ShortestRotation( anglesToEnt, endAng ) * frac
+
+ cam.SetAngles( newAngs )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+void function CamBlendFromPosToPos( entity cam, vector startPos, vector endPos, float transTime, float transAccel, float transDecel )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + transTime
+ vector diff = endPos - startPos
+
+ while ( endTime > currentTime )
+ {
+ float frac = Interpolate( startTime, endTime - startTime, transAccel, transDecel )
+
+ vector newAngs = startPos + diff * frac
+
+ cam.SetOrigin( newAngs )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+void function CamBlendFromAngToAng( entity cam, vector startAng, vector endAng, float transTime, float transAccel, float transDecel )
+{
+ if ( !IsValid( cam ) )
+ return
+
+ cam.EndSignal( "OnDestroy" )
+
+ float currentTime = Time()
+ float startTime = currentTime
+ float endTime = startTime + transTime
+
+ while ( endTime > currentTime )
+ {
+ float frac = Interpolate( startTime, endTime - startTime, transAccel, transDecel )
+
+ vector newAngs = startAng + ShortestRotation( startAng, endAng ) * frac
+
+ cam.SetAngles( newAngs )
+
+ wait( 0.0 )
+
+ currentTime = Time()
+ }
+}
+
+int function AddBitMask( int bitsExisting, int bitsToAdd )
+{
+ return bitsExisting | bitsToAdd
+}
+
+int function RemoveBitMask( int bitsExisting, int bitsToRemove )
+{
+ return bitsExisting & ( ~bitsToRemove )
+}
+
+bool function HasBitMask( int bitsExisting, int bitsToCheck )
+{
+ int bitsCommon = bitsExisting & bitsToCheck
+ return bitsCommon == bitsToCheck
+}
+
+float function GetDeathCamLength( entity player )
+{
+ if ( !GamePlayingOrSuddenDeath() )
+ return DEATHCAM_TIME_SHORT
+ else
+ return DEATHCAM_TIME
+
+ unreachable
+}
+
+float function GetRespawnButtonCamTime( player )
+{
+ if ( !GamePlayingOrSuddenDeath() )
+ return DEATHCAM_TIME_SHORT + RESPAWN_BUTTON_BUFFER
+ else
+ return DEATHCAM_TIME + RESPAWN_BUTTON_BUFFER
+
+ unreachable
+}
+
+float function GetKillReplayAfterTime( player )
+{
+ if ( IsSingleplayer() )
+ return 4.0
+
+ if ( !GamePlayingOrSuddenDeath() )
+ return KILL_REPLAY_AFTER_KILL_TIME_SHORT
+
+ return KILL_REPLAY_AFTER_KILL_TIME
+}
+
+function IntroPreviewOn()
+{
+ local bugnum = GetBugReproNum()
+ switch( bugnum )
+ {
+ case 1337:
+ case 13371:
+ case 13372:
+ case 13373:
+ case 1338:
+ case 13381:
+ case 13382:
+ case 13383:
+ return bugnum
+
+ default:
+ return null
+ }
+}
+
+bool function EntHasModelSet( entity ent )
+{
+ asset modelName = ent.GetModelName()
+
+ if ( modelName == $"" || modelName == $"?" )
+ return false
+
+ return true
+}
+
+string function GenerateTitanOSAlias( entity player, string aliasSuffix )
+{
+ //HACK: Temp fix for blocker bug. Fixing correctly next.
+ if ( IsSingleplayer() )
+ {
+ return "diag_gs_titanBt_" + aliasSuffix
+ }
+ else
+ {
+ entity titan
+ if ( player.IsTitan() )
+ titan = player
+ else
+ titan = player.GetPetTitan()
+
+ Assert( IsValid( titan ) )
+ string titanCharacterName = GetTitanCharacterName( titan )
+ string primeTitanString = ""
+
+ if ( IsTitanPrimeTitan( titan ) )
+ primeTitanString = "_prime"
+
+ string modifiedAlias = "diag_gs_titan" + titanCharacterName + primeTitanString + "_" + aliasSuffix
+ return modifiedAlias
+ }
+ unreachable
+}
+
+void function AddCallback_OnUseEntity( entity ent, callbackFunc )
+{
+ AssertParameters( callbackFunc, 2, "ent, player" )
+
+ if ( !( "onUseEntityCallbacks" in ent.s ) )
+ ent.s.onUseEntityCallbacks <- []
+
+ Assert( !ent.s.onUseEntityCallbacks.contains( callbackFunc ), "Already added " + FunctionToString( callbackFunc ) + " with AddCalback_OnUseEntity" )
+ ent.s.onUseEntityCallbacks.append( callbackFunc )
+}
+
+void function SetWaveSpawnType( int spawnType )
+{
+ shGlobal.waveSpawnType = spawnType
+}
+
+int function GetWaveSpawnType()
+{
+ return shGlobal.waveSpawnType
+}
+
+void function SetWaveSpawnInterval( float interval )
+{
+ shGlobal.waveSpawnInterval = interval
+}
+
+float function GetWaveSpawnInterval()
+{
+ return shGlobal.waveSpawnInterval
+}
+
+bool function IsArcTitan( entity npc )
+{
+ return npc.GetAISettingsName() == "npc_titan_arc"
+}
+
+bool function IsNukeTitan( entity npc )
+{
+ return npc.GetAISettingsName() == "npc_titan_nuke"
+}
+
+bool function IsMortarTitan( entity npc )
+{
+ return npc.GetAISettingsName() == "npc_titan_mortar"
+}
+
+bool function IsFragDrone( entity npc )
+{
+ #if SERVER
+ return npc.GetClassName() == "npc_frag_drone"
+ #endif
+
+ #if CLIENT
+ return npc.GetSignifierName() == "npc_frag_drone"
+ #endif
+}
+
+bool function IsSniperSpectre( entity npc )
+{
+ return false
+}
+
+bool function IsVortexSphere( entity ent )
+{
+ return ( ent.GetClassName() == "vortex_sphere" )
+}
+
+bool function PointIsWithinBounds( vector point, vector mins, vector maxs )
+{
+ Assert( mins.x < maxs.x )
+ Assert( mins.y < maxs.y )
+ Assert( mins.z < maxs.z )
+
+ return ( ( point.z >= mins.z && point.z <= maxs.z ) &&
+ ( point.x >= mins.x && point.x <= maxs.x ) &&
+ ( point.y >= mins.y && point.y <= maxs.y ) )
+}
+
+int function GetSpStartIndex()
+{
+ //HACK -> this should use some other code driven thing, not GetBugReproNum
+ int index = GetBugReproNum()
+
+ if ( index < 0 )
+ return 0
+
+ return index
+}
+
+// return all living soldiers
+array<entity> function GetAllSoldiers()
+{
+ return GetNPCArrayByClass( "npc_soldier" )
+}
+
+int function GameTeams_GetNumLivingPlayers( int teamIndex = TEAM_ANY )
+{
+ int noOfLivingPlayers = 0
+
+ array<entity> players
+ if ( teamIndex == TEAM_ANY )
+ players = GetPlayerArray()
+ else
+ players = GetPlayerArrayOfTeam( teamIndex )
+
+ foreach ( player in players )
+ {
+ if ( !IsAlive( player ) )
+ continue
+
+ ++noOfLivingPlayers
+ }
+
+ return noOfLivingPlayers
+}
+
+bool function GameTeams_TeamHasDeadPlayers( int team )
+{
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( team )
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( !IsAlive( teamPlayer ) )
+ return true
+ }
+ return false
+}
+
+typedef EntitiesDidLoadCallbackType void functionref()
+array<EntitiesDidLoadCallbackType> _EntitiesDidLoadTypedCallbacks
+
+void function RunCallbacks_EntitiesDidLoad()
+{
+ // reloading the level so don't do callbacks
+ if ( "forcedReloading" in level )
+ return
+
+ foreach ( callback in _EntitiesDidLoadTypedCallbacks )
+ {
+ thread callback()
+ }
+}
+
+void function AddCallback_EntitiesDidLoad( EntitiesDidLoadCallbackType callback )
+{
+ _EntitiesDidLoadTypedCallbacks.append( callback )
+}
+
+bool function IsTitanNPC( entity ent )
+{
+ return ent.IsTitan() && ent.IsNPC()
+}
+
+entity function InflictorOwner( entity inflictor )
+{
+ if ( IsValid( inflictor ) )
+ {
+ entity inflictorOwner = inflictor.GetOwner()
+ if ( IsValid( inflictorOwner ) )
+ inflictor = inflictorOwner
+ }
+
+ return inflictor
+}
+
+bool function IsPlayerControlledSpectre( entity ent )
+{
+ return ent.GetClassName() == "npc_spectre" && ent.GetBossPlayer() != null
+}
+
+bool function IsPlayerControlledTurret( entity ent )
+{
+ return IsTurret( ent ) && ent.GetBossPlayer() != null
+}
+
+bool function TitanShieldDecayEnabled()
+{
+ return ( GetCurrentPlaylistVarInt( "titan_shield_decay", 0 ) == 1 )
+}
+
+bool function TitanShieldRegenEnabled()
+{
+ return ( GetCurrentPlaylistVarInt( "titan_shield_regen", 0 ) == 1 )
+}
+
+bool function DoomStateDisabled()
+{
+ return ( GetCurrentPlaylistVarString( "titan_doomstate_variation", "default" ) == "disabled" || GetCurrentPlaylistVarString( "titan_doomstate_variation", "default" ) == "lastsegment" )
+}
+
+bool function NoWeaponDoomState()
+{
+ return ( GetCurrentPlaylistVarString( "titan_doomstate_variation", "default" ) == "noweapon" )
+}
+
+entity function GetPetTitanOwner( entity titan )
+{
+ array<entity> players = GetPlayerArray()
+ entity foundPlayer
+ foreach ( player in players )
+ {
+ if ( player.GetPetTitan() == titan )
+ {
+ Assert( foundPlayer == null, player + " and " + foundPlayer + " both own " + titan )
+ foundPlayer = player
+ }
+ }
+
+ return foundPlayer
+}
+
+entity function GetSoulFromPlayer( entity player )
+{
+ Assert( player.IsPlayer(), "argument should be a player" )
+
+ if ( player.IsTitan() )
+ return player.GetTitanSoul()
+ else if ( IsValid( player.GetPetTitan() ) )
+ return player.GetPetTitan().GetTitanSoul()
+
+ return null
+}
+
+string function GetPlayerBodyType( player )
+{
+ return expect string( player.GetPlayerSettingsField( "weaponClass" ) )
+}
+
+
+void function SetTeam( entity ent, int team )
+{
+ #if CLIENT
+ ent.Code_SetTeam( team )
+ #else
+ if ( ent.IsPlayer() )
+ {
+ ent.Code_SetTeam( team )
+ }
+ else if ( ent.IsNPC() )
+ {
+ int currentTeam = ent.GetTeam()
+ bool alreadyAssignedValidTeam = ( currentTeam == TEAM_IMC || currentTeam == TEAM_MILITIA )
+
+ ent.Code_SetTeam( team )
+
+ if ( ent.GetModelName() == $"" )
+ return
+
+ FixupTitle( ent )
+
+ if ( IsGrunt( ent ) || IsSpectre( ent ) )
+ {
+ if ( IsMultiplayer() )
+ {
+ int eHandle = ent.GetEncodedEHandle()
+
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ Remote_CallFunction_Replay( player, "ServerCallback_UpdateOverheadIconForNPC", eHandle )
+ }
+ }
+ }
+ else if ( IsShieldDrone( ent ) )
+ {
+ if ( team == 0 )
+ {
+ // anybody can use neutral shield drone
+ ent.SetUsable()
+ }
+ else
+ {
+ // only friendlies use a team shield drone
+ ent.SetUsableByGroup( "friendlies pilot" )
+ }
+ }
+
+ table modelTable = ent.CreateTableFromModelKeyValues()
+
+ if ( !( "teamSkin" in modelTable ) )
+ return
+
+ if ( alreadyAssignedValidTeam && ( !( "swapTeamOnLeech" in modelTable.teamSkin ) ) )
+ return
+
+ SetSkinForTeam( ent, team )
+ }
+ else
+ {
+ ent.Code_SetTeam( team )
+ }
+ #endif
+}
+
+void function PrintTraceResults( TraceResults results )
+{
+ printt( "TraceResults: " )
+ printt( "=========================" )
+ printt( "hitEnt: " + results.hitEnt )
+ printt( "endPos: " + results.endPos )
+ printt( "surfaceNormal: " + results.surfaceNormal )
+ printt( "surfaceName: " + results.surfaceName )
+ printt( "fraction: " + results.fraction )
+ printt( "fractionLeftSolid: " + results.fractionLeftSolid )
+ printt( "hitGroup: " + results.hitGroup )
+ printt( "startSolid: " + results.startSolid )
+ printt( "allSolid: " + results.allSolid )
+ printt( "hitSky: " + results.hitSky )
+ printt( "contents: " + results.contents )
+ printt( "=========================" )
+}
+
+bool function PROTO_AlternateDoomedState()
+{
+ return ( GetCurrentPlaylistVarInt( "infinite_doomed_state", 1 ) == 1 )
+}
+
+bool function PROTO_VariableRegenDelay()
+{
+ return ( GetCurrentPlaylistVarInt( "variable_regen_delay", 1 ) == 1 )
+}
+
+bool function PROTO_AutoTitansDisabled()
+{
+ return ( GetCurrentPlaylistVarInt( "always_enable_autotitans", 1 ) == 0 )
+}
+
+bool function TitanDamageRewardsTitanCoreTime()
+{
+ if ( GetCurrentPlaylistVarInt( "titan_core_from_titan_damage", 0 ) != 0 )
+ return true
+ return false
+}
+
+vector function ClampToMap( vector pos )
+{
+ return IterateAxis( pos, LimitAxisToMapExtents )
+}
+
+vector function IterateAxis( vector pos, float functionref( float ) func )
+{
+ pos.x = func( pos.x )
+ pos.y = func( pos.y )
+ pos.z = func( pos.z )
+ return pos
+}
+
+float function LimitAxisToMapExtents( float axisVal )
+{
+ if ( axisVal >= MAP_EXTENTS )
+ axisVal = MAP_EXTENTS - 1
+ else if ( axisVal <= -MAP_EXTENTS )
+ axisVal = -( MAP_EXTENTS - 1 )
+ return axisVal
+}
+
+bool function PilotSpawnOntoTitanIsEnabledInPlaylist( entity player )
+{
+ if ( GetCurrentPlaylistVarInt( "titan_spawn_deploy_enabled", 0 ) != 0 )
+ return true
+ return false
+}
+
+bool function PlayerCanSpawnIntoTitan( entity player )
+{
+ if ( !PilotSpawnOntoTitanIsEnabledInPlaylist( player ) )
+ return false
+
+ entity titan = player.GetPetTitan()
+
+ if ( !IsAlive( titan ) )
+ return false
+
+ if ( GetDoomedState( titan ) )
+ return false
+
+ if ( titan.ContextAction_IsActive() )
+ return false
+
+ return false // turned off until todd figures out how to enable
+}
+
+array< vector > function EntitiesToOrigins( array< entity > ents )
+{
+ array<vector> origins
+
+ foreach ( ent in ents )
+ {
+ origins.append( ent.GetOrigin() )
+ }
+
+ return origins
+}
+
+vector function GetMedianOriginOfEntities( array<entity> ents )
+{
+ array<vector> origins = EntitiesToOrigins( ents )
+ return GetMedianOrigin( origins )
+}
+
+vector function GetMedianOrigin( array<vector> origins )
+{
+ if ( origins.len() == 1 )
+ return origins[0]
+
+ vector median
+
+ int middleIndex1
+ int middleIndex2
+
+ if ( IsEven( origins.len() ) )
+ {
+ middleIndex1 = origins.len() / 2
+ middleIndex2 = middleIndex1
+ }
+ else
+ {
+ middleIndex1 = int( floor( origins.len() / 2.0 ) )
+ middleIndex2 = middleIndex1 + 1
+ }
+
+ origins.sort( CompareVecX )
+ median.x = ( origins[ middleIndex1 ].x + origins[ middleIndex2 ].x ) / 2.0
+
+ origins.sort( CompareVecY )
+ median.y = ( origins[ middleIndex1 ].y + origins[ middleIndex2 ].y ) / 2.0
+
+ origins.sort( CompareVecZ )
+ median.z = ( origins[ middleIndex1 ].z + origins[ middleIndex2 ].z ) / 2.0
+
+ return median
+}
+
+int function CompareVecX( vector a, vector b )
+{
+ if ( a.x > b.x )
+ return 1
+
+ return -1
+}
+
+int function CompareVecY( vector a, vector b )
+{
+ if ( a.y > b.y )
+ return 1
+
+ return -1
+}
+
+int function CompareVecZ( vector a, vector b )
+{
+ if ( a.z > b.z )
+ return 1
+
+ return -1
+}
+
+float function GetFractionAlongPath( array<entity> nodes, vector p )
+{
+ float totalDistance = GetPathDistance( nodes )
+
+ // See which segment we are currently on (closest to)
+ int closestSegment = -1
+ float closestDist = 9999
+ for( int i = 0 ; i < nodes.len() - 1; i++ )
+ {
+ float dist = GetDistanceSqrFromLineSegment( nodes[i].GetOrigin(), nodes[i + 1].GetOrigin(), p )
+ if ( closestSegment < 0 || dist < closestDist )
+ {
+ closestSegment = i
+ closestDist = dist
+ }
+ }
+ Assert( closestSegment >= 0 )
+ Assert( closestSegment < nodes.len() - 1 )
+
+ // Get the distance along the path already traveled
+ float distTraveled = 0.0
+ for( int i = 0 ; i < closestSegment; i++ )
+ {
+ //DebugDrawLine( nodes[i].GetOrigin(), nodes[i + 1].GetOrigin(), 255, 255, 0, true, 0.1 )
+ distTraveled += Distance( nodes[i].GetOrigin(), nodes[i+1].GetOrigin() )
+ }
+
+ // Add the distance traveled on current segment
+ vector closestPointOnSegment = GetClosestPointOnLineSegment( nodes[closestSegment].GetOrigin(), nodes[closestSegment + 1].GetOrigin(), p )
+ //DebugDrawLine( nodes[closestSegment].GetOrigin(), closestPointOnSegment, 255, 255, 0, true, 0.1 )
+ distTraveled += Distance( nodes[closestSegment].GetOrigin(), closestPointOnSegment )
+
+ return clamp( distTraveled / totalDistance, 0.0, 1.0 )
+}
+
+float function GetPathDistance( array<entity> nodes )
+{
+ float totalDist = 0.0
+ for( int i = 0 ; i < nodes.len() - 1; i++ )
+ {
+ //DebugDrawSphere( nodes[i].GetOrigin(), 16.0, 255, 0, 0, true, 0.1 )
+ totalDist += Distance( nodes[i].GetOrigin(), nodes[i+1].GetOrigin() )
+ }
+ //DebugDrawSphere( nodes[nodes.len() -1].GetOrigin(), 16.0, 255, 0, 0, true, 0.1 )
+
+ return totalDist
+}
+
+void function WaittillAnimDone( entity animatingEnt )
+{
+ waitthread WaittillAnimDone_Thread( animatingEnt )
+}
+
+void function WaittillAnimDone_Thread( entity animatingEnt )
+{
+ if ( animatingEnt.IsPlayer() )
+ animatingEnt.EndSignal( "OnDestroy" )
+
+ animatingEnt.EndSignal( "OnAnimationInterrupted" )
+ animatingEnt.WaitSignal( "OnAnimationDone" )
+}
+
+array<entity> function GetEntityLinkChain( entity startNode )
+{
+ Assert( IsValid( startNode ) )
+ array<entity> nodes
+ nodes.append( startNode )
+ while(true)
+ {
+ entity nextNode = nodes[nodes.len() - 1].GetLinkEnt()
+ if ( !IsValid( nextNode ) )
+ break
+ nodes.append( nextNode )
+ }
+ return nodes
+}
+
+float function HealthRatio( entity ent )
+{
+ int health = ent.GetHealth()
+ int maxHealth = ent.GetMaxHealth()
+ return float( health ) / maxHealth
+}
+
+vector function GetPointOnPathForFraction( array<entity> nodes, float frac )
+{
+ Assert( frac >= 0 )
+
+ float totalPathDist = GetPathDistance( nodes )
+ float distRemaining = totalPathDist * frac
+ vector point = nodes[0].GetOrigin()
+
+ for( int i = 0 ; i < nodes.len() - 1; i++ )
+ {
+ float segmentDist = Distance( nodes[i].GetOrigin(), nodes[i+1].GetOrigin() )
+ if ( segmentDist <= distRemaining )
+ {
+ // Add the whole segment
+ distRemaining -= segmentDist
+ point = nodes[i+1].GetOrigin()
+ }
+ else
+ {
+ // Fraction ends somewhere in this segment
+ vector dirVec = Normalize( nodes[i+1].GetOrigin() - nodes[i].GetOrigin() )
+ point = nodes[i].GetOrigin() + ( dirVec * distRemaining )
+ distRemaining = 0
+ }
+ if ( distRemaining <= 0 )
+ break
+ }
+
+ if ( frac > 1.0 && distRemaining > 0 )
+ {
+ vector dirVec = Normalize( nodes[nodes.len() - 1].GetOrigin() - nodes[nodes.len() - 2].GetOrigin() )
+ point = nodes[nodes.len() - 1].GetOrigin() + ( dirVec * distRemaining )
+ }
+
+ return point
+}
+
+bool function PlayerBlockedByTeamEMP( entity player )
+{
+ return ( player.nv.empEndTime > Time() )
+}
+
+#if SERVER
+void function Embark_Allow( entity player )
+{
+ player.SetTitanEmbarkEnabled( true )
+}
+
+void function Embark_Disallow( entity player )
+{
+ player.SetTitanEmbarkEnabled( false )
+}
+
+void function Disembark_Allow( entity player )
+{
+ player.SetTitanDisembarkEnabled( true )
+}
+
+void function Disembark_Disallow( entity player )
+{
+ player.SetTitanDisembarkEnabled( false )
+}
+#endif
+
+bool function CanEmbark( entity player )
+{
+ return player.GetTitanEmbarkEnabled()
+}
+
+bool function CanDisembark( entity player )
+{
+ return player.GetTitanDisembarkEnabled()
+}
+
+string function GetDroneType( entity npc )
+{
+ return expect string( npc.Dev_GetAISettingByKeyField( "drone_type" ) )
+}
+
+vector function FlattenVector( vector vec )
+{
+ return Vector( vec.x, vec.y, 0 )
+}
+
+vector function FlattenAngles( vector angles )
+{
+ return Vector( 0, angles.y, 0 )
+}
+
+bool function IsHumanSized( entity ent )
+{
+ if ( ent.IsPlayer() )
+ return ent.IsHuman()
+
+ if ( ent.IsNPC() )
+ {
+
+ if ( ent.GetAIClass() == AIC_SMALL_TURRET )
+ return true
+
+ string bodyType = ent.GetBodyType()
+ return bodyType == "human" || bodyType == "marvin"
+ }
+
+ return false
+}
+
+bool function IsDropship( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_dropship"
+#elseif CLIENT
+ if ( !ent.IsNPC() )
+ return false
+ //Probably should not use GetClassName, but npc_dropship isn't a class so can't use instanceof?
+ return ( ent.GetClassName() == "npc_dropship" || ent.GetSignifierName() == "npc_dropship" )
+#endif
+}
+
+bool function IsSpecialist( entity ent )
+{
+ return IsGrunt( ent ) && ent.IsMechanical()
+}
+
+bool function IsGrunt( entity ent )
+{
+#if SERVER
+ return ent.IsNPC() && ent.GetClassName() == "npc_soldier"
+#elseif CLIENT
+ return ent.IsNPC() && ent.GetSignifierName() == "npc_soldier"
+#endif
+}
+
+bool function IsMarvin( entity ent )
+{
+ return ent.IsNPC() && ent.GetAIClass() == AIC_MARVIN
+}
+
+bool function IsSpectre( entity ent )
+{
+ return ent.IsNPC() && ent.GetAIClass() == AIC_SPECTRE
+}
+
+bool function IsWorldSpawn( entity ent )
+{
+ #if SERVER
+ return ent.GetClassName() == "worldspawn"
+ #elseif CLIENT
+ return ent.GetSignifierName() == "worldspawn"
+ #endif
+}
+
+bool function IsEnvironment( entity ent )
+{
+ #if SERVER
+ return ent.GetClassName() == "trigger_hurt"
+ #elseif CLIENT
+ return ent.GetSignifierName() == "trigger_hurt"
+ #endif
+}
+
+bool function IsSuperSpectre( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_super_spectre"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_super_spectre"
+#endif
+}
+
+bool function IsAndroidNPC( entity ent )
+{
+ return ( IsSpectre( ent ) || IsStalker( ent ) || IsMarvin( ent ) )
+}
+
+bool function IsStalker( entity ent )
+{
+ return ent.IsNPC() && ( ent.GetAIClass() == AIC_STALKER || ent.GetAIClass() == AIC_STALKER_CRAWLING )
+}
+
+bool function IsProwler( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_prowler"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_prowler"
+#endif
+}
+
+bool function IsAirDrone( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_drone"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_drone"
+#endif
+}
+
+bool function IsPilotElite( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_pilot_elite"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_pilot_elite"
+#endif
+}
+
+bool function IsAttackDrone( entity ent )
+{
+ return ( ent.IsNPC() && !ent.IsNonCombatAI() && IsAirDrone( ent ) )
+}
+
+bool function IsGunship( entity ent )
+{
+#if SERVER
+ return ent.GetClassName() == "npc_gunship"
+#elseif CLIENT
+ return ent.GetSignifierName() == "npc_gunship"
+#endif
+}
+
+bool function IsMinion( entity ent )
+{
+ if ( IsGrunt( ent ) )
+ return true
+
+ if ( IsSpectre( ent ) )
+ return true
+
+ return false
+}
+
+bool function IsShieldDrone( entity ent )
+{
+#if SERVER
+ if ( ent.GetClassName() != "npc_drone" )
+ return false
+#elseif CLIENT
+ if ( ent.GetSignifierName() != "npc_drone" )
+ return false
+#endif
+
+ return GetDroneType( ent ) == "drone_type_shield"
+}
+
+#if SERVER
+bool function IsTick( entity ent )
+{
+ return (ent.IsNPC() && (ent.GetAIClass() == AIC_FRAG_DRONE))
+}
+
+bool function IsNPCTitan( entity ent )
+{
+ return ent.IsNPC() && ent.IsTitan()
+}
+#endif
+
+bool function NPC_GruntChatterSPEnabled( entity npc )
+{
+ if ( !IsSingleplayer() )
+ return false
+
+ if ( !npc.IsNPC() )
+ return false
+
+ if ( npc.GetClassName() != "npc_soldier" )
+ return false
+
+ return true
+}
+
+RaySphereIntersectStruct function IntersectRayWithSphere( vector rayStart, vector rayEnd, vector sphereOrigin, float sphereRadius )
+{
+ RaySphereIntersectStruct intersection
+
+ vector vecSphereToRay = rayStart - sphereOrigin
+
+ vector vecRayDelta = rayEnd - rayStart
+ float a = DotProduct( vecRayDelta, vecRayDelta )
+
+ if ( a == 0.0 )
+ {
+ intersection.result = LengthSqr( vecSphereToRay ) <= sphereRadius * sphereRadius
+ intersection.enterFrac = 0.0
+ intersection.leaveFrac = 0.0
+ return intersection
+ }
+
+ float b = 2 * DotProduct( vecSphereToRay, vecRayDelta )
+ float c = DotProduct( vecSphereToRay, vecSphereToRay ) - sphereRadius * sphereRadius
+ float discrim = b * b - 4 * a * c
+ if ( discrim < 0.0 )
+ {
+ intersection.result = false
+ return intersection
+ }
+
+ discrim = sqrt( discrim )
+ float oo2a = 0.5 / a
+ intersection.enterFrac = ( - b - discrim ) * oo2a
+ intersection.leaveFrac = ( - b + discrim ) * oo2a
+
+ if ( ( intersection.enterFrac > 1.0 ) || ( intersection.leaveFrac < 0.0 ) )
+ {
+ intersection.result = false
+ return intersection
+ }
+
+ if ( intersection.enterFrac < 0.0 )
+ intersection.enterFrac = 0.0
+ if ( intersection.leaveFrac > 1.0 )
+ intersection.leaveFrac = 1.0
+
+ intersection.result = true
+ return intersection
+}
+
+table function GetTableFromString( string inString )
+{
+ if ( inString.len() > 0 )
+ return expect table( getconsttable()[ inString ] )
+
+ return {}
+}
+
+int function GetWeaponDamageNear( entity weapon, entity victim )
+{
+ entity weaponOwner = weapon.GetWeaponOwner()
+ if ( weaponOwner.IsNPC() )
+ {
+ if ( victim.GetArmorType() == ARMOR_TYPE_HEAVY )
+ return weapon.GetWeaponSettingInt( eWeaponVar.npc_damage_near_value_titanarmor )
+ else
+ return weapon.GetWeaponSettingInt( eWeaponVar.npc_damage_near_value )
+ }
+ else
+ {
+ if ( victim.GetArmorType() == ARMOR_TYPE_HEAVY )
+ return weapon.GetWeaponSettingInt( eWeaponVar.damage_near_value_titanarmor )
+ else
+ return weapon.GetWeaponSettingInt( eWeaponVar.damage_near_value )
+ }
+
+ unreachable
+}
+
+void function PrintFirstPersonSequenceStruct( FirstPersonSequenceStruct fpsStruct )
+{
+ printt( "Printing FirstPersonSequenceStruct:" )
+
+ printt( "firstPersonAnim: " + fpsStruct.firstPersonAnim )
+ printt( "thirdPersonAnim: " + fpsStruct.thirdPersonAnim )
+ printt( "firstPersonAnimIdle: " + fpsStruct.firstPersonAnimIdle )
+ printt( "thirdPersonAnimIdle: " + fpsStruct.thirdPersonAnimIdle )
+ printt( "relativeAnim: " + fpsStruct.relativeAnim )
+ printt( "attachment: " + fpsStruct.attachment )
+ printt( "teleport: " + fpsStruct.teleport )
+ printt( "noParent: " + fpsStruct.noParent )
+ printt( "blendTime: " + fpsStruct.blendTime )
+ printt( "noViewLerp: " + fpsStruct.noViewLerp )
+ printt( "hideProxy: " + fpsStruct.hideProxy )
+ printt( "viewConeFunction: " + string( fpsStruct.viewConeFunction ) )
+ printt( "origin: " + string( fpsStruct.origin ) )
+ printt( "angles: " + string ( fpsStruct.angles ) )
+ printt( "enablePlanting: " + fpsStruct.enablePlanting )
+ printt( "setInitialTime: " + fpsStruct.setInitialTime )
+ printt( "useAnimatedRefAttachment: " + fpsStruct.useAnimatedRefAttachment )
+ printt( "renderWithViewModels: " + fpsStruct.renderWithViewModels )
+ printt( "gravity: " + fpsStruct.gravity )
+
+}
+
+void function WaitSignalOrTimeout( entity ent, float timeout, string signal1, string signal2 = "", string signal3 = "" )
+{
+ Assert( IsValid( ent ) )
+
+ ent.EndSignal( signal1 )
+
+ if ( signal2 != "" )
+ ent.EndSignal( signal2 )
+
+ if ( signal3 != "" )
+ ent.EndSignal( signal3 )
+
+ wait( timeout )
+}
+
+array<vector> function GetShortestLineSegmentConnectingLineSegments( vector line1Point1, vector line1Point2, vector line2Point1, vector line2Point2 )
+{
+ // From Paul Bourke's algorithm "The shortest line between two lines in 3D" at http://paulbourke.net/geometry/pointlineplane/
+
+ vector p1 = line1Point1
+ vector p2 = line1Point2
+ vector p3 = line2Point1
+ vector p4 = line2Point2
+ vector p13 = p1 - p3
+ vector p21 = p2 - p1
+ vector p43 = p4 - p3
+
+ if ( Length( p43 ) < 1.0 )
+ {
+ array<vector> resultVectors
+ resultVectors.append( p4 )
+ resultVectors.append( p3 )
+ return resultVectors
+ }
+
+ if ( Length( p21 ) < 1.0 )
+ {
+ array<vector> resultVectors
+ resultVectors.append( p2 )
+ resultVectors.append( p1 )
+ return resultVectors
+ }
+
+ float d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z
+ float d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z
+ float d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z
+ float d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z
+ float d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z
+
+
+ float denom = d2121 * d4343 - d4321 * d4321
+ Assert( fabs( denom ) > 0.01 )
+ float numer = d1343 * d4321 - d1321 * d4343
+
+ float mua = numer / denom
+ float mub = (d1343 + d4321 * (mua)) / d4343
+
+ vector resultVec1
+ vector resultVec2
+ resultVec1.x = p1.x + mua * p21.x
+ resultVec1.y = p1.y + mua * p21.y
+ resultVec1.z = p1.z + mua * p21.z
+ resultVec2.x = p3.x + mub * p43.x
+ resultVec2.y = p3.y + mub * p43.y
+ resultVec2.z = p3.z + mub * p43.z
+
+ array<vector> resultVectors
+ resultVectors.append( resultVec1 )
+ resultVectors.append( resultVec2 )
+ return resultVectors
+}
+
+vector function GetClosestPointToLineSegments( vector line1Point1, vector line1Point2, vector line2Point1, vector line2Point2 )
+{
+ array<vector> results = GetShortestLineSegmentConnectingLineSegments( line1Point1, line1Point2, line2Point1, line2Point2 )
+ Assert( results.len() == 2 )
+ return ( results[0] + results[1] ) / 2.0
+}
+
+
+bool function PlayerCanSee( entity player, entity ent, bool doTrace, float degrees )
+{
+ float minDot = deg_cos( degrees )
+
+ // On screen?
+ float dot = DotProduct( Normalize( ent.GetWorldSpaceCenter() - player.EyePosition() ), player.GetViewVector() )
+ if ( dot < minDot )
+ return false
+
+ // Can trace to it?
+ if ( doTrace )
+ {
+ TraceResults trace = TraceLine( player.EyePosition(), ent.GetWorldSpaceCenter(), null, TRACE_MASK_BLOCKLOS, TRACE_COLLISION_GROUP_NONE )
+ if ( trace.hitEnt == ent || trace.fraction >= 0.99 )
+ return true
+ else
+ return false
+ }
+ else
+ return true
+
+ Assert( 0, "shouldn't ever get here")
+ unreachable
+}
+
+bool function PlayerCanSeePos( entity player, vector pos, bool doTrace, float degrees )
+{
+ float minDot = deg_cos( degrees )
+ float dot = DotProduct( Normalize( pos - player.EyePosition() ), player.GetViewVector() )
+ if ( dot < minDot )
+ return false
+
+ if ( doTrace )
+ {
+ TraceResults trace = TraceLine( player.EyePosition(), pos, null, TRACE_MASK_BLOCKLOS, TRACE_COLLISION_GROUP_NONE )
+ if ( trace.fraction < 0.99 )
+ return false
+ }
+
+ return true
+}
+
+bool function VectorsFacingSameDirection( vector v1, vector v2, float degreesThreshold )
+{
+ float minDot = deg_cos( degreesThreshold )
+ float dot = DotProduct( Normalize( v1 ), Normalize( v2 ) )
+ return ( dot >= minDot )
+}
+
+vector function GetRelativeDelta( vector origin, entity ref, string attachment = "" )
+{
+ vector pos
+ vector right
+ vector forward
+ vector up
+
+ if ( attachment != "" )
+ {
+ int attachID = ref.LookupAttachment( attachment )
+ pos = ref.GetAttachmentOrigin( attachID )
+ vector angles = ref.GetAttachmentAngles( attachID )
+ right = AnglesToRight( angles )
+ forward = AnglesToForward( angles )
+ up = AnglesToUp( angles )
+ }
+ else
+ {
+ pos = ref.GetOrigin()
+ right = ref.GetRightVector()
+ forward = ref.GetForwardVector()
+ up = ref.GetUpVector()
+ }
+
+ vector x = GetClosestPointOnLineSegment( pos + right * -16384, pos + right * 16384, origin )
+ vector y = GetClosestPointOnLineSegment( pos + forward * -16384, pos + forward * 16384, origin )
+ vector z = GetClosestPointOnLineSegment( pos + up * -16384, pos + up * 16384, origin )
+
+ float distx = Distance(pos, x)
+ float disty = Distance(pos, y)
+ float distz = Distance(pos, z)
+
+ if ( DotProduct( x - pos, right ) < 0 )
+ distx *= -1
+ if ( DotProduct( y - pos, forward ) < 0 )
+ disty *= -1
+ if ( DotProduct( z - pos, up ) < 0 )
+ distz *= -1
+
+ return Vector( distx, disty, distz )
+}
+
+#if SERVER
+float function GetRoundTimeLimit_ForGameMode()
+{
+ #if DEV
+ if ( level.devForcedTimeLimit )
+ {
+ //Make it needed to be called multiple times for RoundBasedGameModes
+ level.devForcedTimeLimit = 0
+ return 0.1
+ }
+ #endif
+
+ #if MP
+ if ( GameState_GetTimeLimitOverride() >= 0 )
+ return GameState_GetTimeLimitOverride()
+ #endif
+
+ if ( !GameMode_IsDefined( GAMETYPE ) )
+ return GetCurrentPlaylistVarFloat( "roundtimelimit", 10 )
+ else
+ return GameMode_GetRoundTimeLimit( GAMETYPE )
+
+ unreachable
+}
+#endif
+
+bool function HasIronRules()
+{
+ bool result = (GetCurrentPlaylistVarInt( "iron_rules", 0 ) != 0)
+ return result
+}
+
+vector function GetWorldOriginFromRelativeDelta( vector delta, entity ref )
+{
+ vector right = ref.GetRightVector() * delta.x
+ vector forward = ref.GetForwardVector() * delta.y
+ vector up = ref.GetUpVector() * delta.z
+
+ return ref.GetOrigin() + right + forward + up
+}
+
+bool function IsHardcoreGameMode()
+{
+ return GetCurrentPlaylistVarInt( "gm_hardcore_settings", 0 ) == 1
+}
+
+bool function PlayerHasWeapon( entity player, string weaponName )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ weapons.extend( player.GetOffhandWeapons() )
+
+ foreach ( weapon in weapons )
+ {
+ if ( weapon.GetWeaponClassName() == weaponName )
+ return true
+ }
+
+ return false
+}
+
+bool function PlayerCanUseWeapon( entity player, string weaponClass )
+{
+ return ( ( player.IsTitan() && weaponClass == "titan" ) || ( !player.IsTitan() && weaponClass == "human" ) )
+}
+
+string function GetTitanCharacterName( entity titan )
+{
+ Assert( titan.IsTitan() )
+
+ string setFile
+
+ if ( titan.IsPlayer() )
+ {
+ setFile = titan.GetPlayerSettings()
+ }
+ else
+ {
+ string aiSettingsFile = titan.GetAISettingsName()
+ setFile = expect string( Dev_GetAISettingByKeyField_Global( aiSettingsFile, "npc_titan_player_settings" ) )
+ }
+
+ return GetTitanCharacterNameFromSetFile( setFile )
+}
+
+bool function IsTitanPrimeTitan( entity titan )
+{
+ Assert( titan.IsTitan() )
+ string setFile
+
+ if ( titan.IsPlayer() )
+ {
+ setFile = titan.GetPlayerSettings()
+ }
+ else
+ {
+ string aiSettingsFile = titan.GetAISettingsName()
+ setFile = expect string( Dev_GetAISettingByKeyField_Global( aiSettingsFile, "npc_titan_player_settings" ) )
+ }
+
+ return Dev_GetPlayerSettingByKeyField_Global( setFile, "isPrime" ) == 1
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/_viewcone.gnut b/Northstar.CustomServers/scripts/vscripts/_viewcone.gnut
new file mode 100644
index 000000000..025c9dfd8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_viewcone.gnut
@@ -0,0 +1,520 @@
+
+global function Viewcone_Init
+
+ //DEFAULT: if you don't set one - defaults to this one
+global function ViewConeRampFree
+
+//misc
+global function ViewConeZero
+global function ViewConeZeroInstant
+global function ViewConeNarrow
+global function ViewConeTight
+global function ViewConeSmall
+global function ViewConeWide
+global function ViewConeFreeLookingForward
+global function ViewConeLockedForward
+global function ViewConeTDay
+global function ViewConeTDayZero
+global function ViewConeFastball
+
+//run out ramp
+global function ViewConeRampFrontLeft
+global function ViewConeRampFrontRight
+global function ViewConeRampBackLeft
+global function ViewConeRampBackRight
+
+//droppod
+global function ViewConeDropPodFrontR
+global function ViewConeDropPodFrontL
+global function ViewConeDropPodBackR
+global function ViewConeDropPodBackL
+global function ViewConeDropPod
+
+//right side jump
+global function ViewConeSideRightStandFront
+global function ViewConeSideRightStandBack
+global function ViewConeSideRightSitFront
+global function ViewConeSideRightSitBack
+
+//right side jump - focus on hero
+global function ViewConeSideRightWithHeroStandFront
+global function ViewConeSideRightWithHeroStandBack
+global function ViewConeSideRightWithHeroSitFront
+global function ViewConeSideRightWithHeroSitBack
+
+//right side jump - locked 180 view forward
+global function ViewConeSideRightLockedForwardStandFront
+global function ViewConeSideRightLockedForwardSitFront
+global function ViewConeSideRightLockedForwardStandBack
+global function ViewConeSideRightLockedForwardSitBack
+
+global function ViewConeSpSpawn
+global function InitView
+
+//evac
+global function ViewConeFree
+
+global function IsViewConeCurrent
+
+void function Viewcone_Init()
+{
+ AddGlobalAnimEvent( "ViewConeZero", ViewConeZero )
+ AddGlobalAnimEvent( "ViewConeTight", ViewConeTight )
+ AddGlobalAnimEvent( "ViewConeDropPod", ViewConeDropPod )
+ AddGlobalAnimEvent( "ViewConeDropPodFrontR", ViewConeDropPodFrontR )
+ AddGlobalAnimEvent( "ViewConeDropPodFrontL", ViewConeDropPodFrontL )
+ AddGlobalAnimEvent( "ViewConeDropPodBackR", ViewConeDropPodBackR )
+ AddGlobalAnimEvent( "ViewConeDropPodBackL", ViewConeDropPodBackL )
+ AddGlobalAnimEvent( "ViewConeNarrow", ViewConeNarrow )
+ AddGlobalAnimEvent( "ViewConeSmall", ViewConeSmall )
+ AddGlobalAnimEvent( "ViewConeRampFrontLeft", ViewConeRampFrontLeft )
+ AddGlobalAnimEvent( "ViewConeRampFrontRight", ViewConeRampFrontRight )
+ AddGlobalAnimEvent( "ViewConeRampBackLeft", ViewConeRampBackLeft )
+ AddGlobalAnimEvent( "ViewConeRampBackRight", ViewConeRampBackRight )
+ AddGlobalAnimEvent( "ViewConeRampFree", ViewConeRampFree )
+ AddGlobalAnimEvent( "ViewConeFree", ViewConeFree )
+ AddGlobalAnimEvent( "ViewConeFreeLookingForward", ViewConeFreeLookingForward )
+ AddGlobalAnimEvent( "ViewConeTDay", ViewConeTDay )
+ AddGlobalAnimEvent( "ViewConeTDayZero", ViewConeTDayZero )
+}
+
+void function ViewConeZero( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( 0 )
+ player.PlayerCone_SetMaxYaw( 0 )
+ player.PlayerCone_SetMinPitch( 0 )
+ player.PlayerCone_SetMaxPitch( 0 )
+}
+
+void function ViewConeZeroInstant( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.0 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( 0 )
+ player.PlayerCone_SetMaxYaw( 0 )
+ player.PlayerCone_SetMinPitch( 0 )
+ player.PlayerCone_SetMaxPitch( 0 )
+}
+
+void function ViewConeTight( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -15 )
+ player.PlayerCone_SetMaxYaw( 15 )
+ player.PlayerCone_SetMinPitch( -15 )
+ player.PlayerCone_SetMaxPitch( 15 )
+}
+
+void function ViewConeSmall( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -38 )
+ player.PlayerCone_SetMaxYaw( 38 )
+ player.PlayerCone_SetMinPitch( -25 )
+ player.PlayerCone_SetMaxPitch( 25 )
+}
+
+void function ViewConeWide( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -50 )
+ player.PlayerCone_SetMaxYaw( 50 )
+ player.PlayerCone_SetMinPitch( -35 )
+ player.PlayerCone_SetMaxPitch( 35 )
+}
+
+void function ViewConeDropPod( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -70 )
+ player.PlayerCone_SetMaxYaw( 70 )
+ player.PlayerCone_SetMinPitch( -30 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+const FRONTDIF = -75
+const BACKDIF = -30
+
+void function ViewConeDropPodFrontR( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ //range is 140
+ player.PlayerCone_SetMinYaw( -70 - FRONTDIF )
+ player.PlayerCone_SetMaxYaw( 70 - FRONTDIF )
+ player.PlayerCone_SetMinPitch( -30 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+void function ViewConeDropPodFrontL( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ //range is 140
+ player.PlayerCone_SetMinYaw( -70 + FRONTDIF )
+ player.PlayerCone_SetMaxYaw( 70 + FRONTDIF )
+ player.PlayerCone_SetMinPitch( -30 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+void function ViewConeDropPodBackR( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ //range is 140
+ player.PlayerCone_SetMinYaw( -70 - BACKDIF )
+ player.PlayerCone_SetMaxYaw( 70 - BACKDIF )
+ player.PlayerCone_SetMinPitch( -30 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+void function ViewConeDropPodBackL( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ //range is 140
+ player.PlayerCone_SetMinYaw( -70 + BACKDIF )
+ player.PlayerCone_SetMaxYaw( 70 + BACKDIF )
+ player.PlayerCone_SetMinPitch( -30 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+void function ViewConeNarrow( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -60 )
+ player.PlayerCone_SetMaxYaw( 60 )
+ player.PlayerCone_SetMinPitch( -60 )
+ player.PlayerCone_SetMaxPitch( 60 )
+}
+
+void function ViewConeTDay( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 1.0 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -30 )
+ player.PlayerCone_SetMaxYaw( 30 )
+ player.PlayerCone_SetMinPitch( 0 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+void function ViewConeTDayZero( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 1.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( 0 )
+ player.PlayerCone_SetMaxYaw( 0 )
+ player.PlayerCone_SetMinPitch( 0 )
+ player.PlayerCone_SetMaxPitch( 0 )
+}
+
+void function ViewConeFastball( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -25 )
+ player.PlayerCone_SetMaxYaw( 25 )
+ player.PlayerCone_SetMinPitch( -15 )
+ player.PlayerCone_SetMaxPitch( 15 )
+}
+
+void function ViewConeFreeLookingForward( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 0, 180, ViewConeFreeLookingForward )
+}
+
+void function ViewConeSpSpawn( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+
+ player.PlayerCone_SetLerpTime( 0.25 )
+ player.PlayerCone_FromAnim()
+
+ player.PlayerCone_SetMinYaw( 125 )
+ player.PlayerCone_SetMaxYaw( 125 )
+ player.PlayerCone_SetMinPitch( 7 )
+ player.PlayerCone_SetMaxPitch( 7 )
+
+ thread InitView( player, 7, 125, ViewConeSpSpawn )
+}
+
+void function ViewConeSideRightLockedForwardStandFront( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeLockedForward( player )
+
+ thread InitView( player, 15, 20, ViewConeSideRightLockedForwardStandFront )
+}
+
+void function ViewConeSideRightLockedForwardSitFront( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeLockedForward( player )
+
+ thread InitView( player, 0, 50, ViewConeSideRightLockedForwardSitFront )
+}
+
+void function ViewConeSideRightLockedForwardStandBack( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeLockedForward( player )
+
+ thread InitView( player, 15, 45, ViewConeSideRightLockedForwardStandBack )
+}
+
+void function ViewConeSideRightLockedForwardSitBack( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeLockedForward( player )
+
+ thread InitView( player, 0, 50, ViewConeSideRightLockedForwardSitBack )
+}
+
+void function ViewConeSideRightWithHeroStandFront( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 15, 20, ViewConeSideRightWithHeroStandFront )
+}
+
+void function ViewConeSideRightWithHeroSitFront( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 0, 50, ViewConeSideRightWithHeroSitFront )
+}
+
+void function ViewConeSideRightWithHeroStandBack( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 15, 45, ViewConeSideRightWithHeroStandBack )
+}
+
+void function ViewConeSideRightWithHeroSitBack( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 0, 50, ViewConeSideRightWithHeroSitBack )
+}
+
+void function ViewConeSideRightStandFront( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 30, -10, ViewConeSideRightStandFront )
+}
+
+void function ViewConeSideRightSitFront( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 20, -20, ViewConeSideRightSitFront )
+}
+
+void function ViewConeSideRightStandBack( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 30, 20, ViewConeSideRightStandBack )
+}
+
+void function ViewConeSideRightSitBack( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeFree( player )
+
+ thread InitView( player, 20, 35, ViewConeSideRightSitBack )
+}
+
+void function ViewConeRampFrontLeft( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeRampFree( player )
+
+ thread InitView( player, 5, 70, ViewConeRampFrontLeft )
+}
+
+void function ViewConeRampFrontRight( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeRampFree( player )
+
+ thread InitView( player, 5, -70, ViewConeRampFrontRight )
+}
+
+void function ViewConeRampBackLeft( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeRampFree( player )
+
+ thread InitView( player, 5, 100, ViewConeRampBackLeft )
+}
+
+void function ViewConeRampBackRight( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ ViewConeRampFree( player )
+
+ thread InitView( player, 5, -100, ViewConeRampBackRight )
+}
+
+void function ViewConeRampFree( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+
+ player.PlayerCone_SetMinYaw( -179 )
+ player.PlayerCone_SetMaxYaw( 181 )
+ player.PlayerCone_SetMinPitch( -30 )
+ player.PlayerCone_SetMaxPitch( 30 )
+}
+
+void function ViewConeLockedForward( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+
+ player.PlayerCone_SetMinYaw( -89 )
+ player.PlayerCone_SetMaxYaw( 81 )
+ player.PlayerCone_SetMinPitch( -30 )
+ player.PlayerCone_SetMaxPitch( 60 )
+}
+
+void function ViewConeFree( entity player )
+{
+ if ( !player.IsPlayer() )
+ return
+ player.PlayerCone_SetLerpTime( 0.5 )
+
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -179 )
+ player.PlayerCone_SetMaxYaw( 181 )
+ player.PlayerCone_SetMinPitch( -60 )
+ player.PlayerCone_SetMaxPitch( 60 )
+}
+
+void function InitView( entity player, int pitch, int yaw, void functionref( entity )callFunction )
+{
+ if ( !player.IsPlayer() )
+ return
+
+ //have we already init the viewcone from this function before
+ if ( IsViewConeCurrent( player, callFunction ) )
+ return
+
+ player.EndSignal( "OnDestroy" )
+
+ entity dropship = player.GetParent()
+
+ while( !dropship )
+ {
+ wait 0.05
+ dropship = player.GetParent()
+ }
+
+ for ( int i = 0; i < 5; i++ )
+ {
+ player.SetLocalAngles( Vector( pitch, yaw, 0 ) )
+ wait 0.1
+ }
+}
+
+bool function IsViewConeCurrent( entity actor, void functionref(entity ) func )
+{
+ entity player = actor
+
+ if ( !IsValid( player ) )
+ return false
+
+ Assert( player.IsPlayer() )
+
+ if ( player.p.currViewConeFunction == func )
+ return true
+
+ player.p.currViewConeFunction = func
+ return false
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_vscript.gnut b/Northstar.CustomServers/scripts/vscripts/_vscript.gnut
new file mode 100644
index 000000000..52b69c5da
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_vscript.gnut
@@ -0,0 +1,84 @@
+untyped
+
+//========== Copyright © 2008, Valve Corporation, All rights reserved. ========
+
+global function UniqueString
+global function EntFire
+global function __DumpScope
+
+
+int __uniqueStringId = 0
+string function UniqueString( string str = "" )
+{
+ return str + "_us" + __uniqueStringId++;
+}
+
+function EntFire( target, action, value = null, delay = 0.0, activator = null )
+{
+ if ( !value )
+ {
+ value = "";
+ }
+
+ local caller = null;
+ if ( "self" in this )
+ {
+ caller = this.self;
+ if ( !activator )
+ {
+ activator = this.self;
+ }
+ }
+
+ DoEntFire( string( target ), string( action ), string( value ), delay, activator, caller );
+}
+
+//---------------------------------------------------------
+// Text dump this scope's contents to the console.
+//---------------------------------------------------------
+void function __DumpScope( int depth, var Table )
+{
+ local indent=function( count )
+ {
+ local i;
+ for( i = 0 ; i < count ; i++ )
+ {
+ print(" ");
+ }
+ }
+
+ foreach(key, value in Table)
+ {
+ indent(depth);
+ print( key );
+ switch (type(value))
+ {
+ case "table":
+ print("(TABLE)\n");
+ indent(depth);
+ print("{\n");
+ __DumpScope( depth + 1, value);
+ indent(depth);
+ print("}");
+ break;
+ case "array":
+ print("(ARRAY)\n");
+ indent(depth);
+ print("[\n")
+ __DumpScope( depth + 1, value);
+ indent(depth);
+ print("]");
+ break;
+ case "string":
+ print(" = \"");
+ print(value);
+ print("\"");
+ break;
+ default:
+ print(" = ");
+ print(value);
+ break;
+ }
+ print("\n");
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/_xp.gnut b/Northstar.CustomServers/scripts/vscripts/_xp.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/_xp.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_chatter.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_chatter.gnut
new file mode 100644
index 000000000..0429895b1
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_chatter.gnut
@@ -0,0 +1,129 @@
+global function DialogueChatter_Init
+
+global function TitanVO_AlertTitansIfTargetWasKilled
+global function TitanVO_TellPlayersThatAreAlsoFightingThisTarget
+global function TitanVO_AlertTitansTargetingThisTitanOfRodeo
+global function TitanVO_DelayedTitanDown
+
+const TITAN_VO_DIST_SQR = 2000 * 2000
+
+const CHATTER_TIME_LAPSE = 30.0
+//const CHATTER_TIME_LAPSE = 5.0 //For testing
+//const CHATTER_TIME_LAPSE = 8.0 //For testing
+//const CHATTER_TIME_LAPSE = 15.0 //For testing
+
+void function DialogueChatter_Init()
+{
+}
+
+void function TitanVO_TellPlayersThatAreAlsoFightingThisTarget( entity attacker, entity soul )
+{
+ int voEnum
+ if ( attacker.IsTitan() )
+ voEnum = eTitanVO.FRIENDLY_TITAN_HELPING
+ else
+ voEnum = eTitanVO.PILOT_HELPING
+
+ bool atackerIsTitan = attacker.IsTitan()
+ int attackerTeam = attacker.GetTeam()
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ if ( !player.IsTitan() )
+ continue
+
+ if ( player.GetTeam() != attackerTeam )
+ continue
+ // attacker gets a score callout
+ if ( player == attacker )
+ continue
+
+ if ( soul != player.p.currentTargetPlayerOrSoul_Ent )
+ continue
+
+ float timeDif = Time() - player.p.currentTargetPlayerOrSoul_LastHitTime
+ if ( timeDif > CURRENT_TARGET_FORGET_TIME )
+ continue
+
+ // alert other player that cared about this target
+ Remote_CallFunction_Replay( player, "SCB_TitanDialogue", voEnum )
+ }
+}
+
+void function TitanVO_AlertTitansTargetingThisTitanOfRodeo( entity rodeoer, entity soul )
+{
+ int team = rodeoer.GetTeam()
+
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ if ( !player.IsTitan() )
+ continue
+
+ if ( player.GetTeam() != team )
+ continue
+
+ if ( soul != player.p.currentTargetPlayerOrSoul_Ent )
+ continue
+
+ // if we havent hurt the target recently then forget about it
+ if ( Time() - player.p.currentTargetPlayerOrSoul_LastHitTime > CURRENT_TARGET_FORGET_TIME )
+ continue
+
+ Remote_CallFunction_Replay( player, "SCB_TitanDialogue", eTitanVO.FRIENDLY_RODEOING_ENEMY )
+ }
+}
+
+void function TitanVO_DelayedTitanDown( entity ent )
+{
+ vector titanOrigin = ent.GetOrigin()
+ int team = ent.GetTeam()
+
+ wait 0.9
+
+ array<entity> playerArray = GetPlayerArray()
+ float dist = TITAN_VO_DIST_SQR
+
+ foreach ( player in playerArray )
+ {
+ // only titans get BB vo
+ if ( !player.IsTitan() )
+ continue
+
+ if ( DistanceSqr( titanOrigin, player.GetOrigin() ) > dist )
+ continue
+
+ if ( player.GetTeam() != team )
+ Remote_CallFunction_Replay( player, "SCB_TitanDialogue", eTitanVO.ENEMY_TITAN_DEAD )
+ else
+ Remote_CallFunction_Replay( player, "SCB_TitanDialogue", eTitanVO.FRIENDLY_TITAN_DEAD )
+ }
+}
+
+
+void function TitanVO_AlertTitansIfTargetWasKilled( entity victim, entity attacker )
+{
+ array<entity> enemyPlayers = GetPlayerArrayOfEnemies( victim.GetTeam() )
+
+ if ( victim.IsTitan() )
+ victim = victim.GetTitanSoul()
+
+ foreach ( player in enemyPlayers )
+ {
+ if ( !player.IsTitan() )
+ continue
+
+ // attacker gets a score callout
+ if ( player == attacker )
+ continue
+
+ if ( victim != player.p.currentTargetPlayerOrSoul_Ent )
+ continue
+
+ if ( Time() - player.p.currentTargetPlayerOrSoul_LastHitTime > CURRENT_TARGET_FORGET_TIME )
+ continue
+
+ // alert other player that cared about this target
+ Remote_CallFunction_Replay( player, "SCB_TitanDialogue", eTitanVO.ENEMY_TARGET_ELIMINATED )
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_cloak_drone.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_cloak_drone.gnut
new file mode 100644
index 000000000..e3addf812
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_cloak_drone.gnut
@@ -0,0 +1,678 @@
+untyped
+
+global function CloakDrone_Init
+
+global function SpawnCloakDrone
+global function GetNPCCloakedDrones
+global function RemoveLeftoverCloakedDrones
+const FX_DRONE_CLOAK_BEAM = $"P_drone_cloak_beam"
+
+const float CLOAK_DRONE_REACHED_HARVESTER_DIST = 1300.0
+
+struct
+{
+ int cloakedDronesManagedEntArrayID
+ table<entity,string> cloakedDroneClaimedSquadList
+} file
+
+struct CloakDronePath
+{
+ vector start
+ vector goal
+ bool goalValid = false
+ float lastHeight
+}
+
+function CloakDrone_Init()
+{
+ PrecacheParticleSystem( FX_DRONE_CLOAK_BEAM )
+
+ file.cloakedDronesManagedEntArrayID = CreateScriptManagedEntArray()
+
+ RegisterSignal( "DroneCleanup" )
+ RegisterSignal( "DroneCrashing" )
+}
+
+entity function SpawnCloakDrone( int team, vector origin, vector angles, vector towerOrigin )
+{
+ int droneCount = GetNPCCloakedDrones().len()
+
+ // add some minor randomness to the spawn location as well as an offset based on number of drones in the world.
+ origin += < RandomIntRange( -64, 64 ), RandomIntRange( -64, 64 ), 300 + (droneCount * 128) >
+
+ entity cloakedDrone = CreateGenericDrone( team, origin, angles )
+ SetSpawnOption_AISettings( cloakedDrone, "npc_drone_cloaked" )
+
+ //these enable global damage callbacks for the cloakedDrone
+ cloakedDrone.s.isHidden <- false
+ cloakedDrone.s.fx <- null
+ cloakedDrone.s.towerOrigin <- towerOrigin
+
+ DispatchSpawn( cloakedDrone )
+ SetTeam( cloakedDrone, team )
+ SetTargetName( cloakedDrone, "Cloak Drone" )
+ cloakedDrone.SetTitle( "#NPC_CLOAK_DRONE" )
+ cloakedDrone.SetMaxHealth( 250 )
+ cloakedDrone.SetHealth( 250 )
+ cloakedDrone.SetTakeDamageType( DAMAGE_YES )
+ cloakedDrone.SetDamageNotifications( true )
+ cloakedDrone.SetDeathNotifications( true )
+ cloakedDrone.Solid()
+ cloakedDrone.Show()
+ cloakedDrone.EnableNPCFlag( NPC_IGNORE_ALL )
+
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_HOVER_LOOP_SFX )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_LOOPING_SFX )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_WARP_IN_SFX )
+
+ cloakedDrone.s.fx = CreateDroneCloakBeam( cloakedDrone )
+
+ SetVisibleEntitiesInConeQueriableEnabled( cloakedDrone, true )
+
+ thread CloakedDronePathThink( cloakedDrone )
+ thread CloakedDroneCloakThink( cloakedDrone )
+
+ #if R1_VGUI_MINIMAP
+ cloakedDrone.Minimap_SetDefaultMaterial( $"vgui/hud/cloak_drone_minimap_orange" )
+ #endif
+ cloakedDrone.Minimap_SetAlignUpright( true )
+ cloakedDrone.Minimap_AlwaysShow( TEAM_IMC, null )
+ cloakedDrone.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ cloakedDrone.Minimap_SetObjectScale( MINIMAP_CLOAKED_DRONE_SCALE )
+ cloakedDrone.Minimap_SetZOrder( MINIMAP_Z_NPC )
+
+ ShowName( cloakedDrone )
+
+ AddToGlobalCloakedDroneList( cloakedDrone )
+ return cloakedDrone
+}
+
+function AddToGlobalCloakedDroneList( cloakedDrone )
+{
+ AddToScriptManagedEntArray( file.cloakedDronesManagedEntArrayID, cloakedDrone )
+}
+
+array<entity> function GetNPCCloakedDrones()
+{
+ return GetScriptManagedEntArray( file.cloakedDronesManagedEntArrayID )
+}
+
+function RemoveLeftoverCloakedDrones()
+{
+ array<entity> droneArray = GetNPCCloakedDrones()
+ foreach ( cloakedDrone in droneArray )
+ {
+ thread CloakedDroneWarpOutAndDestroy( cloakedDrone )
+ }
+}
+
+void function CloakedDroneWarpOutAndDestroy( entity cloakedDrone )
+{
+ cloakedDrone.EndSignal( "OnDestroy" )
+ cloakedDrone.EndSignal( "OnDeath" )
+ cloakedDrone.SetInvulnerable()
+
+ CloakedDroneWarpOut( cloakedDrone, cloakedDrone.GetOrigin() )
+ cloakedDrone.Destroy()
+}
+
+/************************************************************************************************\
+
+ ###### ## ####### ### ## ## #### ## ## ######
+## ## ## ## ## ## ## ## ## ## ### ## ## ##
+## ## ## ## ## ## ## ## ## #### ## ##
+## ## ## ## ## ## ##### ## ## ## ## ## ####
+## ## ## ## ######### ## ## ## ## #### ## ##
+## ## ## ## ## ## ## ## ## ## ## ### ## ##
+ ###### ######## ####### ## ## ## ## #### ## ## ######
+
+\************************************************************************************************/
+//HACK - this should probably move into code
+function CloakedDroneCloakThink( cloakedDrone )
+{
+ expect entity( cloakedDrone )
+
+ cloakedDrone.EndSignal( "OnDestroy" )
+ cloakedDrone.EndSignal( "OnDeath" )
+ cloakedDrone.EndSignal( "DroneCrashing" )
+ cloakedDrone.EndSignal( "DroneCleanup" )
+
+ wait 2 // wait a few seconds since it would start cloaking before picking an npc to follow
+ // some npcs might not be picked since they where already cloaked by accident.
+
+ CloakerThink( cloakedDrone, 400.0, [ "any" ], < 0, 0, -350 >, CloakDroneShouldCloakGuy, 1.5 )
+}
+
+function CloakDroneShouldCloakGuy( cloakedDrone, guy )
+{
+ expect entity( guy )
+ if ( !( guy.IsTitan() || IsSpectre( guy ) || IsGrunt( guy ) || IsSuperSpectre( guy ) ) )
+ return false
+
+ if ( guy.GetTargetName() == "empTitan" )
+ return false
+
+ if ( IsSniperSpectre( guy ) )
+ return false
+
+ if ( IsValid( GetRodeoPilot( guy ) ) )
+ return false
+
+ if ( cloakedDrone.s.isHidden )
+ return false
+
+ if ( StatusEffect_Get( guy, eStatusEffect.sonar_detected ) )
+ return false
+
+ // if ( !cloakedDrone.CanSee( guy ) )
+ // return false
+
+ return true
+}
+
+/************************************************************************************************\
+
+######## ### ######## ## ## #### ## ## ######
+## ## ## ## ## ## ## ## ### ## ## ##
+## ## ## ## ## ## ## ## #### ## ##
+######## ## ## ## ######### ## ## ## ## ## ####
+## ######### ## ## ## ## ## #### ## ##
+## ## ## ## ## ## ## ## ### ## ##
+## ## ## ## ## ## #### ## ## ######
+
+\************************************************************************************************/
+//HACK -> this should probably move into code
+const VALIDPATHFRAC = 0.99
+
+void function CloakedDronePathThink( entity cloakedDrone )
+{
+ cloakedDrone.EndSignal( "OnDestroy" )
+ cloakedDrone.EndSignal( "OnDeath" )
+ cloakedDrone.EndSignal( "DroneCrashing" )
+ cloakedDrone.EndSignal( "DroneCleanup" )
+
+ entity goalNPC = null
+ entity previousNPC = null
+ vector spawnOrigin = cloakedDrone.GetOrigin()
+ vector lastOrigin = cloakedDrone.GetOrigin()
+ float stuckDistSqr = 64.0*64.0
+ float targetLostTime = Time()
+ array<entity> claimedGuys = []
+
+ while( 1 )
+ {
+ while( goalNPC == null )
+ {
+ wait 1.0
+ array<entity> testArray = GetNPCArrayEx( "any", cloakedDrone.GetTeam(), TEAM_ANY, < 0, 0, 0 >, -1 )
+
+ // remove guys already being followed by an cloakedDrone
+ // or in other ways not suitable
+ array<entity> NPCs = []
+ foreach ( guy in testArray )
+ {
+ if ( !IsAlive( guy ) )
+ continue
+
+ //Only cloak titans, spectres, grunts,
+ if ( !( guy.IsTitan() || IsSpectre( guy ) || IsGrunt( guy ) || IsSuperSpectre( guy ) ) )
+ continue
+
+ //Don't cloak arc titans
+ if ( guy.GetTargetName() == "empTitan" )
+ continue
+
+ if ( IsSniperSpectre( guy ) )
+ continue
+
+ if ( IsFragDrone( guy ) )
+ continue
+
+ if ( guy == previousNPC )
+ continue
+
+ if ( guy.ContextAction_IsBusy() )
+ continue
+
+ if ( guy.GetParent() != null )
+ continue
+
+ if ( IsCloaked( guy ) )
+ continue
+
+ if ( IsSquadCenterClose( guy ) == false )
+ continue
+
+ if ( "cloakedDrone" in guy.s && IsAlive( expect entity( guy.s.cloakedDrone ) ) )
+ continue
+
+ if ( CloakedDroneIsSquadClaimed( expect string( guy.kv.squadname ) ) )
+ continue
+
+ if ( IsValid( GetRodeoPilot( guy ) ) )
+ continue
+
+ if ( StatusEffect_Get( guy, eStatusEffect.sonar_detected ) )
+ continue
+
+ NPCs.append( guy )
+ }
+
+ if ( NPCs.len() == 0 )
+ {
+ previousNPC = null
+
+ if ( Time() - targetLostTime > 10 )
+ {
+ // couldn't find anything to cloak for 10 seconds so we'll warp out until we find something
+ if ( cloakedDrone.s.isHidden == false )
+ CloakedDroneWarpOut( cloakedDrone, spawnOrigin )
+ }
+ continue
+ }
+
+ goalNPC = FindBestCloakTarget( NPCs, cloakedDrone.GetOrigin(), cloakedDrone )
+ Assert( goalNPC )
+ }
+
+ CloakedDroneClaimSquad( cloakedDrone, expect string( goalNPC.kv.squadname ) )
+
+ waitthread CloakedDronePathFollowNPC( cloakedDrone, goalNPC )
+
+ CloakedDroneReleaseSquad( cloakedDrone )
+
+ previousNPC = goalNPC
+ goalNPC = null
+ targetLostTime = Time()
+
+ float distSqr = DistanceSqr( lastOrigin, cloakedDrone.GetOrigin() )
+ if ( distSqr < stuckDistSqr )
+ CloakedDroneWarpOut( cloakedDrone, spawnOrigin )
+
+ lastOrigin = cloakedDrone.GetOrigin()
+ }
+}
+
+void function CloakedDroneClaimSquad( entity cloakedDrone, string squadname )
+{
+ if ( GetNPCSquadSize( squadname ) )
+ file.cloakedDroneClaimedSquadList[ cloakedDrone ] <- squadname
+}
+
+void function CloakedDroneReleaseSquad( entity cloakedDrone )
+{
+ if ( cloakedDrone in file.cloakedDroneClaimedSquadList )
+ delete file.cloakedDroneClaimedSquadList[ cloakedDrone ]
+}
+
+bool function CloakedDroneIsSquadClaimed( string squadname )
+{
+ table<entity,string> cloneTable = clone file.cloakedDroneClaimedSquadList
+ foreach ( entity cloakedDrone, squad in cloneTable )
+ {
+ if ( !IsAlive( cloakedDrone ) )
+ delete file.cloakedDroneClaimedSquadList[ cloakedDrone ]
+ else if ( squad == squadname )
+ return true
+ }
+ return false
+}
+
+void function CloakedDronePathFollowNPC( entity cloakedDrone, entity goalNPC )
+{
+ cloakedDrone.EndSignal( "OnDestroy" )
+ cloakedDrone.EndSignal( "OnDeath" )
+ cloakedDrone.EndSignal( "DroneCrashing" )
+ goalNPC.EndSignal( "OnDeath" )
+ goalNPC.EndSignal( "OnDestroy" )
+
+ if ( !( "cloakedDrone" in goalNPC.s ) )
+ goalNPC.s.cloakedDrone <- null
+ goalNPC.s.cloakedDrone = cloakedDrone
+
+ OnThreadEnd(
+ function() : ( goalNPC )
+ {
+ if ( IsAlive( goalNPC ) )
+ goalNPC.s.cloakedDrone = null
+ }
+ )
+
+ int droneTeam = cloakedDrone.GetTeam()
+
+ //vector maxs = < 64, 64, 53.5 >//bigger than model to compensate for large effect
+ //vector mins = < -64, -64, -64 >
+
+ vector maxs = < 32, 32, 32 >//bigger than model to compensate for large effect
+ vector mins = < -32, -32, -32 >
+
+ int mask = cloakedDrone.GetPhysicsSolidMask()
+
+ float defaultHeight = 300
+ array<float> traceHeightsLow = [ -75.0, -150.0, -250.0 ]
+ array<float> traceHeightsHigh = [ 150.0, 300.0, 800.0, 1500.0 ]
+
+ float waitTime = 0.25
+
+ CloakDronePath path
+ path.goalValid = false
+ path.lastHeight = defaultHeight
+
+ //If drone is following titan wait for titan to leave bubble shield.
+ if ( goalNPC.IsTitan() )
+ WaitTillHotDropComplete( goalNPC )
+
+ while( goalNPC.GetTeam() == droneTeam )
+ {
+ if ( IsValid( GetRodeoPilot( goalNPC ) ) )
+ return
+
+ //If our target npc gets revealed by a sonar pulse, ditch that chump.
+ if ( StatusEffect_Get( goalNPC, eStatusEffect.sonar_detected ) )
+ return
+
+ float minDist = CLOAK_DRONE_REACHED_HARVESTER_DIST * CLOAK_DRONE_REACHED_HARVESTER_DIST
+ float distToGenerator = DistanceSqr( goalNPC.GetOrigin(), cloakedDrone.s.towerOrigin )
+ //if we've gotten our npc to the generator, go find someone farther out to escort.
+ if ( distToGenerator <= minDist )
+ return
+
+ //DebugDrawCircleOnEnt( goalNPC, 20, 255, 0, 0, 0.1 )
+
+ float startTime = Time()
+ path.goalValid = false
+
+ CloakedDroneFindPathDefault( path, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+
+ //find a new path if necessary
+ if ( !path.goalValid )
+ {
+ //lets check some heights and see if any are valid
+ CloakedDroneFindPathHorizontal( path, traceHeightsLow, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+
+ if ( !path.goalValid )
+ {
+ //OK so no way to directly go to those heights - lets see if we can move vertically down,
+ CloakedDroneFindPathVertical( path, traceHeightsLow, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+
+ if ( !path.goalValid )
+ {
+ //still no good...lets check up
+ CloakedDroneFindPathHorizontal( path, traceHeightsHigh, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+
+ if ( !path.goalValid )
+ {
+ //no direct shots up - lets try moving vertically up first
+ CloakedDroneFindPathVertical( path, traceHeightsHigh, defaultHeight, mins, maxs, cloakedDrone, goalNPC, mask )
+ }
+ }
+ }
+ }
+
+ // if we can't find a valid path find a new goal
+ if ( !path.goalValid )
+ {
+ waitthread CloakedDroneWarpOut( cloakedDrone, GetCloakTargetOrigin( goalNPC ) + < 0, 0, defaultHeight > )
+ CloakedDroneWarpIn( cloakedDrone, GetCloakTargetOrigin( goalNPC ) + < 0, 0, defaultHeight > )
+ continue
+ }
+
+ if ( cloakedDrone.s.isHidden == true )
+ CloakedDroneWarpIn( cloakedDrone, cloakedDrone.GetOrigin() )
+
+ thread AssaultOrigin( cloakedDrone, path.goal )
+
+ float endTime = Time()
+ float elapsedTime = endTime - startTime
+ if ( elapsedTime < waitTime )
+ wait waitTime - elapsedTime
+ }
+}
+
+bool function CloakedDroneFindPathDefault( CloakDronePath path, float defaultHeight, vector mins, vector maxs, entity cloakedDrone, entity goalNPC, int mask )
+{
+ vector offset = < 0, 0, defaultHeight >
+ path.start = ( cloakedDrone.GetOrigin() ) + < 0, 0, 32 > //Offset so path start is just above drone instead at bottom of drone.
+ path.goal = GetCloakTargetOrigin( goalNPC ) + offset
+
+ //find out if we can get there using the default height
+ TraceResults result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ] , mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( path.start, path.goal, 50, 0, 0, true, 1.0 )
+ if ( result.fraction >= VALIDPATHFRAC )
+ {
+ path.lastHeight = defaultHeight
+ path.goalValid = true
+ }
+
+ return path.goalValid
+}
+
+bool function CloakedDroneFindPathHorizontal( CloakDronePath path, array<float> traceHeights, float defaultHeight, vector mins, vector maxs, entity cloakedDrone, entity goalNPC, int mask )
+{
+ wait 0.1
+
+ vector offset
+ float testHeight
+
+ //slight optimization... recheck if the last time was also not the default height
+ if ( path.lastHeight != defaultHeight )
+ {
+ offset = < 0, 0, defaultHeight + path.lastHeight >
+ path.start = ( cloakedDrone.GetOrigin() )
+ path.goal = GetCloakTargetOrigin( goalNPC ) + offset
+
+ TraceResults result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ], mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( path.start, path.goal, 0, 255, 0, true, 1.0 )
+ if ( result.fraction >= VALIDPATHFRAC )
+ {
+ path.goalValid = true
+ return path.goalValid
+ }
+ }
+
+ for ( int i = 0; i < traceHeights.len(); i++ )
+ {
+ testHeight = traceHeights[ i ]
+ if ( path.lastHeight == testHeight )
+ continue
+
+// wait 0.1
+
+ offset = < 0, 0, defaultHeight + testHeight >
+ path.start = ( cloakedDrone.GetOrigin() ) + ( testHeight > 0 ? < 0, 0, 0 > : < 0, 0, 32 > ) //Check from the top or bottom of the drone depending on if the drone is going up or down
+ path.goal = GetCloakTargetOrigin( goalNPC ) + offset
+
+ TraceResults result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ], mask, TRACE_COLLISION_GROUP_NONE )
+ if ( result.fraction < VALIDPATHFRAC )
+ {
+ //DebugDrawLine( path.start, path.goal, 200, 0, 0, true, 3.0 )
+ continue
+ }
+
+ //DebugDrawLine( path.start, path.goal, 0, 255, 0, true, 3.0 )
+
+ path.lastHeight = testHeight
+ path.goalValid = true
+ break
+ }
+
+ return path.goalValid
+}
+
+bool function CloakedDroneFindPathVertical( CloakDronePath path, array<float> traceHeights, float defaultHeight, vector mins, vector maxs, entity cloakedDrone, entity goalNPC, int mask )
+{
+ vector offset
+ vector origin
+ float testHeight
+
+ for ( int i = 0; i < traceHeights.len(); i++ )
+ {
+ wait 0.1
+
+ testHeight = traceHeights[ i ]
+ origin = cloakedDrone.GetOrigin()
+ offset = < 0, 0, defaultHeight + testHeight >
+ path.start = < origin.x, origin.y, defaultHeight + testHeight >
+ path.goal = GetCloakTargetOrigin( goalNPC ) + offset
+
+ TraceResults result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ], mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( path.start, path.goal, 50, 50, 100, true, 1.0 )
+ if ( result.fraction < VALIDPATHFRAC )
+ continue
+
+ //ok so it's valid - lets see if we can move to it from where we are
+// wait 0.1
+
+ path.goal = < path.start.x, path.start.y, path.start.z >
+ path.start = cloakedDrone.GetOrigin()
+
+ result = TraceHull( path.start, path.goal, mins, maxs, [ cloakedDrone, goalNPC ], mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( path.start, path.goal, 255, 255, 0, true, 1.0 )
+ if ( result.fraction < VALIDPATHFRAC )
+ continue
+
+ path.lastHeight = testHeight
+ path.goalValid = true
+ break
+ }
+
+ return path.goalValid
+}
+
+void function CloakedDroneWarpOut( entity cloakedDrone, vector origin )
+{
+ if ( cloakedDrone.s.isHidden == false )
+ {
+ // only do this if we are not already hidden
+ FadeOutSoundOnEntity( cloakedDrone, CLOAKED_DRONE_LOOPING_SFX, 0.5 )
+ FadeOutSoundOnEntity( cloakedDrone, CLOAKED_DRONE_HOVER_LOOP_SFX, 0.5 )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_WARP_OUT_SFX )
+
+ cloakedDrone.s.fx.Fire( "StopPlayEndCap" )
+ cloakedDrone.SetTitle( "" )
+ cloakedDrone.s.isHidden = true
+ cloakedDrone.NotSolid()
+ cloakedDrone.Minimap_Hide( TEAM_IMC, null )
+ cloakedDrone.Minimap_Hide( TEAM_MILITIA, null )
+ cloakedDrone.SetNoTarget( true )
+ // let the beam fx end
+
+ if ( "smokeEffect" in cloakedDrone.s )
+ {
+ cloakedDrone.s.smokeEffect.Kill_Deprecated_UseDestroyInstead()
+ delete cloakedDrone.s.smokeEffect
+ }
+ UntrackAllToneMarks( cloakedDrone )
+
+ wait 0.3 // wait a bit before hidding the done so that the fx looks better
+ cloakedDrone.Hide()
+ }
+
+ wait 2.0
+
+ cloakedDrone.DisableBehavior( "Follow" )
+ thread AssaultOrigin( cloakedDrone, origin )
+ cloakedDrone.SetOrigin( origin )
+}
+
+void function CloakedDroneWarpIn( entity cloakedDrone, vector origin )
+{
+ cloakedDrone.DisableBehavior( "Follow" )
+ cloakedDrone.SetOrigin( origin )
+ PutEntityInSafeSpot( cloakedDrone, cloakedDrone, null, cloakedDrone.GetOrigin() + <0, 0, 32>, cloakedDrone.GetOrigin() )
+ thread AssaultOrigin( cloakedDrone, origin )
+
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_HOVER_LOOP_SFX )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_LOOPING_SFX )
+ EmitSoundOnEntity( cloakedDrone, CLOAKED_DRONE_WARP_IN_SFX )
+
+ cloakedDrone.Show()
+ cloakedDrone.s.fx.Fire( "start" )
+ cloakedDrone.SetTitle( "#NPC_CLOAK_DRONE" )
+ cloakedDrone.s.isHidden = false
+ cloakedDrone.Solid()
+ cloakedDrone.Minimap_AlwaysShow( TEAM_IMC, null )
+ cloakedDrone.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ cloakedDrone.SetNoTarget( false )
+}
+
+
+entity function CreateDroneCloakBeam( entity cloakedDrone )
+{
+ entity fx = PlayLoopFXOnEntity( FX_DRONE_CLOAK_BEAM, cloakedDrone, "", null, < 90, 0, 0 > )//, visibilityFlagOverride = null, visibilityFlagEntOverride = null )
+ return fx
+}
+
+entity function FindBestCloakTarget( array<entity> npcArray, vector origin, entity drone )
+{
+ entity selectedNPC = null
+ float maxDist = 10000 * 10000
+ float minDist = 1300 * 1300
+ float highestScore = -1
+
+ foreach ( npc in npcArray )
+ {
+ float score = 0
+ float distToGenerator = DistanceSqr( npc.GetOrigin(), drone.s.towerOrigin )
+ if ( distToGenerator > minDist )
+ {
+ // only give dist bonus if we aren't to close to the generator.
+ local dist = DistanceSqr( npc.GetOrigin(), origin )
+ score = GraphCapped( dist, maxDist, minDist, 0, 1 )
+ }
+
+ if ( npc.IsTitan() )
+ {
+ score += 0.75
+ if ( IsArcTitan( npc ) )
+ score -= 0.1
+ if ( IsMortarTitan( npc ) )
+ score -= 0.2
+// if ( IsNukeTitan( npc ) )
+// score += 0.1
+ }
+ if ( score > highestScore )
+ {
+ highestScore = score
+ selectedNPC = npc
+ }
+ }
+
+ return selectedNPC
+}
+
+vector function GetCloakTargetOrigin( entity npc )
+{
+ // returns the center of squad if the npc is in one
+ // else returns a good spot to cloak a titan
+
+ vector origin
+
+ if ( GetNPCSquadSize( npc.kv.squadname ) == 0 )
+ {
+ origin = npc.GetOrigin() + npc.GetNPCVelocity()
+ }
+ else
+ origin = npc.GetSquadCentroid()
+
+ Assert( origin.x < ( 16384 * 100 ) );
+
+ // defensive hack
+ if ( origin.x > ( 16384 * 100 ) )
+ origin = npc.GetOrigin()
+
+ return origin
+}
+
+function IsSquadCenterClose( npc, dist = 256 )
+{
+ // return true if there is no squad
+ if ( GetNPCSquadSize( npc.kv.squadname ) == 0 )
+ return true
+
+ // return true if the squad isn't too spread out.
+ if ( DistanceSqr( npc.GetSquadCentroid(), npc.GetOrigin() ) <= ( dist * dist ) )
+ return true
+
+ return false
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_drone.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_drone.gnut
new file mode 100644
index 000000000..c0d56de73
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_drone.gnut
@@ -0,0 +1,1388 @@
+untyped
+
+global function AiDrone_Init
+
+global function CreateDroneSquadString
+global function SetDroneSquadStringForOwner
+global function GetDroneSquadStringFromOwner
+global function DroneGruntThink
+global function RunDroneTypeThink
+global function DroneHasNoOwner
+global function CreateSingleDroneRope
+global function DroneDialogue
+global function IsDroneRebooting
+global function DroneOnLeeched
+global function SetRepairDroneTarget
+
+global const DRONE_SHIELD_COOLDOWN = 8
+global const DRONE_SHIELD_WALL_HEALTH = 200
+global const DRONE_SHIELD_WALL_RADIUS_TITAN = 200
+global const DRONE_SHIELD_WALL_RADIUS_HUMAN = 90
+global const DRONE_SHIELD_WALL_HEIGHT_TITAN = 450
+global const DRONE_SHIELD_WALL_HEIGHT_HUMAN = 190
+global const DRONE_SHIELD_WALL_FOV_TITAN = 115
+global const DRONE_SHIELD_WALL_FOV_HUMAN = 105
+global const DRONE_MINIMUM_DEPLOY_CLEARANCE_FROM_GROUND = 120
+global const MIN_DRONE_SHIELD_FROM_OWNER_DIST = 256 //if shield drone gets more than this distance away from host, will drop shield
+global const MIN_DRONE_SHIELD_FROM_OWNER_DIST_TITAN = 400 //if shield drone gets more than this distance away from host, will drop shield
+global const DRONE_LEASH_DISTANCE_SQR = 589824 // Further than this distance, drones will disengage from combat and go back to their owner.
+
+global const SOUND_DRONE_EXPLODE_DEFAULT = "Drone_DeathExplo"
+global const SOUND_DRONE_EXPLODE_CLOAK = "Drone_DeathExplo"
+
+global const FX_DRONE_SHIELD_WALL_TITAN = $"P_drone_shield_wall_XO"
+const FX_DRONE_SHIELD_WALL_HUMAN = $"P_drone_shield_wall"
+global const FX_DRONE_EXPLOSION = $"P_drone_exp_md"
+global const FX_DRONE_R_EXPLOSION = $"P_drone_exp_rocket"
+global const FX_DRONE_P_EXPLOSION = $"P_drone_exp_plasma"
+global const FX_DRONE_W_EXPLOSION = $"P_drone_exp_worker"
+global const FX_DRONE_SHIELD_ROPE_GLOW = $"acl_light_white"
+
+function AiDrone_Init()
+{
+ PrecacheParticleSystem( FX_DRONE_EXPLOSION )
+ PrecacheParticleSystem( FX_DRONE_R_EXPLOSION )
+ PrecacheParticleSystem( FX_DRONE_P_EXPLOSION )
+ PrecacheParticleSystem( FX_DRONE_W_EXPLOSION )
+ PrecacheParticleSystem( FX_DRONE_SHIELD_WALL_TITAN )
+ PrecacheParticleSystem( FX_DRONE_SHIELD_WALL_HUMAN )
+ PrecacheParticleSystem( FX_DRONE_SHIELD_ROPE_GLOW )
+
+ PrecacheModel( $"models/robots/drone_air_attack/drone_air_attack_rockets.mdl" )
+ PrecacheModel( $"models/robots/drone_air_attack/drone_air_attack_plasma.mdl" )
+
+ PrecacheMaterial( $"cable/cable_selfillum.vmt" )
+ PrecacheModel( $"cable/cable_selfillum.vmt" )
+ AddDeathCallback( "npc_drone", DroneDeath )
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Fallback behavior if we can't find a valid owner for an orphan Drone
+function DroneHasNoOwner( entity drone )
+{
+ switch ( GetDroneType( drone ) )
+ {
+ case "drone_type_shield":
+ //Transform into a Rocket drone and find some buddies
+ thread DroneTransformsToRocketClass( drone )
+ break
+
+ case "drone_type_engineer_combat":
+ case "drone_type_engineer_shield":
+ EngineerDroneHasNoOwner( drone )
+ break
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DroneTransformsToRocketClass( entity drone )
+{
+ if ( !IsAlive( drone ) )
+ return
+
+ drone.EndSignal( "OnDeath" )
+ drone.EndSignal( "OnDestroy" )
+
+ wait 1.5
+
+ // dont do it if we're parented for some reason
+ if ( IsValid( drone.GetParent() ) )
+ return
+
+ DroneDialogue( drone, "transform_shield_to_assault" )
+ wait 3
+
+ // dont do it if we're parented for some reason
+ if ( IsValid( drone.GetParent() ) )
+ return
+
+ int team = drone.GetTeam()
+ int health = drone.GetHealth()
+ vector origin = drone.GetOrigin()
+ vector angles = drone.GetAngles()
+ angles.x = 0
+ angles.z = 0
+
+ entity newDrone = CreateRocketDrone( team, origin, angles )
+ DispatchSpawn( newDrone )
+ newDrone.SetHealth( health )
+
+ entity enemy = drone.GetEnemy()
+ if ( IsAlive( enemy ) )
+ newDrone.SetEnemyLKP( enemy, enemy.GetOrigin() )
+
+ drone.TransferChildrenTo( newDrone )
+
+ drone.Destroy()
+
+}
+
+function EngineerDroneHasNoOwner( drone )
+{
+ //TODO: Should probably protect nearest ally, and return to Engineer when he gets close.
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Change drone type on spawn or during gameplay (may transform from one to the other eventually)
+function RunDroneTypeThink( drone )
+{
+ expect entity( drone )
+ #if DEV
+ Assert( !( "RunDroneTypeThink" in drone.s ), "Already ran drone think!" )
+ drone.s.RunDroneTypeThink <- true
+ #endif
+ ////initialize it's type only after the anim is complete
+ //local delay = drone.GetSequenceDuration( spawnAnimDrone )
+ drone.EndSignal( "OnDeath" )
+
+ switch ( GetDroneType( drone ) )
+ {
+ case "drone_type_beam":
+ case "drone_type_rocket":
+ case "drone_type_plasma":
+ local owner = drone.GetFollowTarget()
+ if ( IsValid( owner ) )
+ owner.Signal( "OnEndFollow" )
+ DroneRocketThink( drone ) //may delay if it's waiting for a spawn anim to finish
+ break
+
+ case "drone_type_shield":
+ DroneShieldThink( drone ) //may delay if it's waiting for a spawn anim to finish
+ break
+
+ case "drone_type_engineer_combat":
+ EngineerCombatDroneThink( drone ) //may delay if it's waiting for a spawn anim to finish
+ break
+
+ case "drone_type_engineer_shield":
+ EngineerShieldDroneThink( drone ) //may delay if it's waiting for a spawn anim to finish
+ break
+
+ case "drone_type_repair":
+ RepairDroneThink( drone )
+ break
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function DroneRocketThink( entity drone )
+{
+ drone.EndSignal( "OnDeath" )
+
+ entity owner
+ entity currentTarget
+ local accuracyMultiplierBase = drone.kv.AccuracyMultiplier
+ local accuracyMultiplierAgainstDrones = 100
+
+ //--------------------------------------------
+ // transform if this used to be a shield drone
+ //--------------------------------------------
+ RemoveDroneRopes( drone )
+ drone.SetAttackMode( true )
+
+ while ( true )
+ {
+ wait 0.25
+
+ //----------------------------------
+ // Get owner and current enemy
+ //----------------------------------
+ currentTarget = drone.GetEnemy()
+ owner = drone.GetFollowTarget()
+
+ //----------------------------------
+ // Free roam if owner is dead or HasEnemy
+ //----------------------------------
+ if ( ( !IsAlive( owner ) ) || ( currentTarget != null ) )
+ {
+ drone.DisableBehavior( "Follow" )
+ }
+
+ //---------------------------------------------------------------------
+ // If owner is alive and no enemies in sight, go back and follow owner
+ //----------------------------------------------------------------------
+ if ( IsAlive( owner ) )
+ {
+ local distSqr = DistanceSqr( owner.GetOrigin(), drone.GetOrigin() )
+
+ if ( currentTarget == null || distSqr > DRONE_LEASH_DISTANCE_SQR )
+ {
+ drone.ClearEnemy()
+ drone.EnableBehavior( "Follow" )
+ }
+ }
+
+ //----------------------------------------------
+ // Jack up accuracy if targeting another drone
+ //----------------------------------------------
+ if ( ( currentTarget != null ) && ( IsAirDrone( currentTarget ) ) )
+ {
+ drone.kv.AccuracyMultiplier = accuracyMultiplierAgainstDrones
+ }
+ else
+ {
+ drone.kv.AccuracyMultiplier = accuracyMultiplierBase
+ }
+ }
+
+}
+
+function ShieldDroneShieldsUser( entity drone )
+{
+ for ( ;; )
+ {
+ var player = drone.WaitSignal( "OnPlayerUse" ).player
+
+ Assert( false, "REMOVED; see mp_pilot_ability_shield to ressurect" )
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function DroneShieldThink( drone )
+{
+ expect entity( drone )
+ if ( !IsValid( drone ) )
+ return
+ drone.EndSignal( "OnDestroy" )
+ drone.EndSignal( "OnDeath" )
+ //drone.EndSignal( "OnNewOwner" )
+
+ entity owner
+ local newOwner
+ string ownerSquadName = ""
+ local distSq
+ local distSqHuman = MIN_DRONE_SHIELD_FROM_OWNER_DIST * MIN_DRONE_SHIELD_FROM_OWNER_DIST
+ local distSqTitan = MIN_DRONE_SHIELD_FROM_OWNER_DIST_TITAN * MIN_DRONE_SHIELD_FROM_OWNER_DIST_TITAN
+ bool titanStateCurrent = false
+ bool titanStatePrevious = false
+ bool titanStateChanged = false
+ local e = {}
+ e.droneShieldTable <- null
+
+ drone.SetUsePrompts( "#SHIELD_DRONE_HOLD_USE", "#SHIELD_DRONE_PRESS_USE" )
+
+ //------------------------------------------
+ // Cleanup shield if Drone dies
+ //------------------------------------------
+ OnThreadEnd(
+ function() : ( e, drone )
+ {
+ DroneShieldDestroy( e.droneShieldTable )
+ if ( IsAlive( drone ) )
+ thread ShieldDroneLandsAfterLeaderDeath( drone )
+ }
+ )
+
+ thread ShieldDroneShieldsUser( drone )
+
+ //------------------------------------------
+ // Drone tentacles/ropes
+ //------------------------------------------
+ local droneRopeTable = CreateDroneRopes( drone )
+ if ( !( "droneRopeTable" in drone.s ) )
+ drone.s.droneRopeTable <- null
+ drone.s.droneRopeTable = droneRopeTable
+
+ //------------------------------------------
+ // Drone shield think loop
+ //------------------------------------------
+
+ while ( true )
+ {
+ wait 0.25
+
+ if ( GetDroneType( drone ) != "drone_type_shield" )
+ {
+ DroneShieldDestroy( e.droneShieldTable )
+ break
+ }
+
+ //------------------------------------------
+ // If rebooting from EMP blast, get rid of shield
+ //------------------------------------------
+ if ( IsDroneRebooting( drone ) )
+ {
+ DroneShieldDestroy( e.droneShieldTable )
+ continue
+ }
+ //------------------------------------------
+ // If owner dead, kill shield until new owner found
+ //------------------------------------------
+ owner = drone.GetFollowTarget()
+ if ( !IsAlive( owner ) )
+ {
+ DroneShieldDestroy( e.droneShieldTable )
+ break
+ }
+
+ //------------------------------------------
+ // Still no valid owner? End this thread
+ //------------------------------------------
+ if ( !IsValid( owner ) )
+ break
+
+ //ownerSquadName = owner.Get( "squadname" )
+
+ //------------------------------------------
+ // Owner is valid. Is it differnt owner?
+ //------------------------------------------
+ if ( newOwner != owner )
+ {
+ //Kill current shield since it will get redeployed on new owner
+ DroneShieldDestroy( e.droneShieldTable )
+ }
+
+ //------------------------------------------
+ // Owner is valid. Has owner changed Titan state?
+ //------------------------------------------
+ newOwner = owner
+ titanStatePrevious = titanStateCurrent //previous state is whatever current was set to last loop around
+
+ if ( owner.IsTitan() )
+ {
+ distSq = distSqTitan //adjust min dist for shield based on titan state
+ titanStateCurrent = true //toggle so we can see if owner just changed state
+ }
+ else
+ {
+ distSq = distSqHuman
+ titanStateCurrent = false
+ }
+
+ if ( titanStateCurrent != titanStatePrevious )
+ titanStateChanged = true
+ else
+ titanStateChanged = false
+
+ //--------------------------------------------------------------------------------------
+ // We have a valid owner and a valid shield, continue unless we have changed Titan state
+ //--------------------------------------------------------------------------------------
+ if ( ( DroneShieldExists( e.droneShieldTable ) ) && ( !titanStateChanged ) )
+ continue
+
+ //------------------------------------------
+ // Too far away from owner, destoy shield
+ //------------------------------------------
+ if ( DistanceSqr( drone.GetOrigin(), owner.GetOrigin() ) > distSq )
+ {
+ //printl( "Drone is too far away from host to create a shield")
+ DroneShieldDestroy( e.droneShieldTable )
+ continue
+ }
+
+ //------------------------------------------
+ // Owner embarked/disembarked in a Titan, destroy shield
+ //------------------------------------------
+ if ( titanStateChanged )
+ {
+ //printl( "Drone host embarked/disembarked a Titan, destroying shield")
+ DroneShieldDestroy( e.droneShieldTable )
+ continue
+ }
+ //----------------------------------------------------------
+ // Valid owner, valid dist, etc...make a shield for the current owner
+ //-----------------------------------------------------------
+ e.droneShieldTable = MakeDroneShield( drone, owner )
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function EngineerCombatDroneThink( entity drone )
+{
+ if ( !IsValid( drone ) )
+ return
+
+ drone.EndSignal( "OnDeath" )
+
+ entity owner
+ local currentTarget
+ local accuracyMultiplierPlayers = 50
+ local accuracyMultiplierAgainstNPC = 90
+
+ //--------------------------------------------
+ // transform if this used to be a shield drone
+ //--------------------------------------------
+ RemoveDroneRopes( drone )
+ drone.SetAttackMode( true )
+
+ while ( true )
+ {
+ wait 0.25
+
+ //----------------------------------
+ // Get owner and current enemy
+ //----------------------------------
+ currentTarget = drone.GetEnemy()
+ owner = drone.GetFollowTarget()
+
+ //----------------------------------
+ // Free roam if owner is dead or HasEnemy
+ //----------------------------------
+ if ( ( !IsAlive( owner ) ) || ( currentTarget != null ) )
+ {
+ drone.DisableBehavior( "Follow" )
+ }
+
+ //---------------------------------------------------------------------
+ // If owner is alive and no enemies in sight, go back and follow owner
+ //----------------------------------------------------------------------
+ if ( IsAlive( owner ) )
+ {
+ float distSqr = DistanceSqr( owner.GetOrigin(), drone.GetOrigin() )
+
+ if ( currentTarget == null || distSqr > DRONE_LEASH_DISTANCE_SQR )
+ {
+ drone.ClearEnemy()
+ drone.EnableBehavior( "Follow" )
+ }
+ }
+
+ //----------------------------------------------
+ // Jack up accuracy if targeting another drone
+ //----------------------------------------------
+ if ( ( currentTarget != null ) && ( currentTarget.IsNPC() ) )
+ {
+ drone.kv.AccuracyMultiplier = accuracyMultiplierAgainstNPC
+ }
+ else
+ {
+ drone.kv.AccuracyMultiplier = accuracyMultiplierPlayers
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function EngineerShieldDroneThink( drone )
+{
+ if ( !IsValid( drone ) )
+ return
+ drone.EndSignal( "OnDestroy" )
+ drone.EndSignal( "OnDeath" )
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function IsDroneRebooting( drone )
+{
+ if ( !( "rebooting" in drone.s ) )
+ return false
+
+ return drone.s.rebooting
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// HACK: may just use generic function "CreateShield()" from particle_wall.nut, but just in prototype mode now
+function MakeDroneShield( drone, owner )
+{
+ expect entity( owner )
+
+ if ( !( "shieldTable" in drone.s ) )
+ drone.s.shieldTable <- null
+ else
+ DroneShieldDestroy( drone.s.shieldTable )
+
+ //------------------------------
+ // Shield vars
+ //------------------------------
+ vector origin = owner.GetOrigin()
+ vector angles = owner.GetAngles() + Vector( 0, 0, 180 )
+ local attachmentTag
+ local DroneShieldTable = {}
+ DroneShieldTable.vortexSphere <- null
+ DroneShieldTable.shieldWallFX = null
+ DroneShieldTable.shieldRopes <- null
+
+ asset shieldFx
+ float wallFOV
+ float shieldWallRadius
+ float shieldWallHeight
+ if ( owner.IsTitan() )
+ {
+ shieldWallRadius = DRONE_SHIELD_WALL_RADIUS_TITAN
+ shieldFx = FX_DRONE_SHIELD_WALL_TITAN
+ wallFOV = DRONE_SHIELD_WALL_FOV_TITAN
+ shieldWallHeight = DRONE_SHIELD_WALL_HEIGHT_TITAN
+ }
+ else
+ {
+ shieldWallRadius = DRONE_SHIELD_WALL_RADIUS_HUMAN
+ shieldFx = FX_DRONE_SHIELD_WALL_HUMAN
+ wallFOV = DRONE_SHIELD_WALL_FOV_HUMAN
+ shieldWallHeight = DRONE_SHIELD_WALL_HEIGHT_HUMAN
+ }
+
+ local Spawn
+ //------------------------------
+ // Vortex to block the actual bullets
+ //------------------------------
+ entity vortexSphere = CreateEntity( "vortex_sphere" )
+
+ vortexSphere.kv.spawnflags = SF_ABSORB_BULLETS | SF_BLOCK_OWNER_WEAPON | SF_BLOCK_NPC_WEAPON_LOF | SF_ABSORB_CYLINDER
+ vortexSphere.kv.enabled = 0
+ vortexSphere.kv.radius = shieldWallRadius
+ vortexSphere.kv.height = shieldWallHeight
+ vortexSphere.kv.bullet_fov = wallFOV
+ vortexSphere.kv.physics_pull_strength = 25
+ vortexSphere.kv.physics_side_dampening = 6
+ vortexSphere.kv.physics_fov = 360
+ vortexSphere.kv.physics_max_mass = 2
+ vortexSphere.kv.physics_max_size = 6
+
+ vortexSphere.SetAngles( angles ) // viewvec?
+ vortexSphere.SetOrigin( origin + Vector( 0, 0, shieldWallRadius - 64 ) )
+ vortexSphere.SetMaxHealth( DRONE_SHIELD_WALL_HEALTH )
+ vortexSphere.SetHealth( DRONE_SHIELD_WALL_HEALTH )
+
+ if ( IsSingleplayer() )
+ {
+ thread PROTO_VortexSlowsPlayers( vortexSphere, owner )
+ }
+
+ DispatchSpawn( vortexSphere )
+
+ vortexSphere.Fire( "Enable" )
+
+ vortexSphere.SetInvulnerable() // make particle wall invulnerable to weapon damage. It will still drain over time
+
+ // Shield wall fx control point
+ entity cpoint = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpoint, UniqueString( "shield_wall_controlpoint" ) )
+ DispatchSpawn( cpoint )
+
+ //------------------------------------------
+ // Shield wall fx for visuals/health drain
+ //------------------------------------------
+
+ entity shieldWallFX = PlayFXWithControlPoint( shieldFx, origin + Vector( 0, 0, -64 ), cpoint, -1, null, angles )
+ vortexSphere.e.shieldWallFX = shieldWallFX
+ SetVortexSphereShieldWallCPoint( vortexSphere, cpoint )
+
+ entity mover = CreateScriptMover()
+ mover.SetOrigin( owner.GetOrigin() )
+ mover.SetAngles( owner.GetAngles() )
+
+ //-----------------------
+ // Attach shield to owner
+ //------------------------
+ vortexSphere.SetParent( mover )
+ shieldWallFX.SetParent( mover )
+
+ thread ShieldMoverFollowsOwner( owner, mover, vortexSphere, shieldWallFX )
+
+ //-----------------------
+ // Rope attach to shield
+ //------------------------
+ local ropeAttachOrigin1 = PositionOffsetFromEnt( owner, shieldWallRadius -16, wallFOV -16, 128 )
+ local ropeAttachOrigin2 = PositionOffsetFromEnt( owner, shieldWallRadius -16, ( ( wallFOV - 16) * -1 ), 128 )
+ if ( owner.IsTitan() )
+ {
+ ropeAttachOrigin1 = PositionOffsetFromEnt( owner, shieldWallRadius - 78, wallFOV + 22, 256 )
+ ropeAttachOrigin2 = PositionOffsetFromEnt( owner, shieldWallRadius - 78, -( wallFOV + 22), 256 )
+ }
+
+ local shieldRopes = []
+ local shieldRope1 = CreateSingleDroneRope( drone, "ROPE_0", false )
+ local shieldRope2 = CreateSingleDroneRope( drone, "ROPE_0", false )
+ shieldRopes.append( shieldRope1 )
+ shieldRopes.append( shieldRope2 )
+ entity ropeEnt1 = CreateEntity( "info_target" )
+ entity ropeEnt2 = CreateEntity( "info_target" )
+ ropeEnt1.SetOrigin( ropeAttachOrigin1 )
+ ropeEnt2.SetOrigin( ropeAttachOrigin2 )
+ ropeEnt1.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ ropeEnt2.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( ropeEnt1 )
+ DispatchSpawn( ropeEnt2 )
+
+ ropeEnt1.SetParent( vortexSphere )
+ ropeEnt2.SetParent( vortexSphere )
+ shieldRope1.s.ropeEnd.SetOrigin( ropeEnt1.GetOrigin() )
+ shieldRope2.s.ropeEnd.SetOrigin( ropeEnt2.GetOrigin() )
+ shieldRope1.s.ropeEnd.SetParent( ropeEnt1 )
+ shieldRope2.s.ropeEnd.SetParent( ropeEnt2 )
+
+ PlayFXOnEntity( FX_DRONE_SHIELD_ROPE_GLOW, ropeEnt1 )
+ PlayFXOnEntity( FX_DRONE_SHIELD_ROPE_GLOW, ropeEnt2 )
+
+ //-----------------------
+ // DroneShieldTable
+ //------------------------
+ DroneShieldTable.vortexSphere = vortexSphere
+ DroneShieldTable.shieldWallFX = shieldWallFX
+ DroneShieldTable.shieldRopes = shieldRopes
+
+ //-----------------------
+ // Health and cleanup
+ //------------------------
+ drone.s.shieldTable = DroneShieldTable
+ UpdateShieldWallColorForFrac( shieldWallFX, 1.0 )
+
+ return DroneShieldTable
+}
+
+void function ShieldMoverFollowsOwner( entity owner, entity mover, entity vortexSphere, entity shieldWallFX )
+{
+ vortexSphere.EndSignal( "OnDestroy" )
+ shieldWallFX.EndSignal( "OnDestroy" )
+ owner.EndSignal( "OnDeath" )
+ mover.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( mover )
+ {
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ }
+ )
+
+ for ( ;; )
+ {
+ UpdateMoverPosition( mover, owner )
+ }
+}
+
+void function UpdateMoverPosition( entity mover, entity owner )
+{
+ vector origin = owner.GetOrigin()
+ mover.NonPhysicsMoveTo( origin, 0.1, 0.0, 0.0 )
+ mover.NonPhysicsRotateTo( owner.GetAngles(), 0.75, 0.0, 0.0 )
+ WaitFrame()
+}
+
+void function PROTO_VortexSlowsPlayers( entity vortexSphere, entity owner )
+{
+ vortexSphere.EndSignal( "OnDestroy" )
+ owner.EndSignal( "OnDeath" )
+
+ float radius = float(vortexSphere.kv.radius )
+ float height = float(vortexSphere.kv.height )
+ float bullet_fov = float( vortexSphere.kv.bullet_fov )
+ float dot = cos( bullet_fov * 0.5 )
+
+ for ( ;; )
+ {
+ vector origin = vortexSphere.GetOrigin()
+ vector angles = vortexSphere.GetAngles()
+ vector forward = AnglesToForward( angles )
+ int team = owner.GetTeam()
+
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( player.GetTeam() == team )
+ continue
+ VortexStunCheck( player, origin, height, radius, bullet_fov, dot, forward )
+ }
+ WaitFrame()
+ }
+}
+
+void function VortexStunCheck( entity player, vector origin, float height, float radius, float bullet_fov, float dot, vector forward )
+{
+ if ( Time() - player.p.lastDroneShieldStunPushTime < 1.75 )
+ return
+
+ vector playerOrg = player.GetOrigin()
+ float dist2d = Distance2D( playerOrg, origin )
+
+ if ( dist2d > radius + 5 )
+ return
+ if ( dist2d < radius - 15 )
+ return
+
+ float heightOffset = fabs( playerOrg.z - origin.z )
+
+ if ( heightOffset < 0 || heightOffset > height )
+ return
+
+ vector dif = Normalize( playerOrg - origin )
+
+ if ( DotProduct2D( dif, forward ) < dot )
+ return
+
+ const float VORTEX_STUN_DURATION = 1.0
+ GiveEMPStunStatusEffects( player, VORTEX_STUN_DURATION + 0.5 )
+ float strength = 0.4
+ StatusEffect_AddTimed( player, eStatusEffect.emp, strength, VORTEX_STUN_DURATION, 0.5 )
+ thread TempLossOfAirControl( player, VORTEX_STUN_DURATION )
+ vector velocity = forward * 300
+ velocity.z = 400
+ player.p.lastDroneShieldStunPushTime = Time()
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "explo_proximityemp_impact_3p" )
+ player.SetVelocity( velocity )
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function CreateDroneRopes( drone )
+{
+ local droneRopeTable = {}
+ droneRopeTable.rope01 <- CreateSingleDroneRope( drone, "ROPE_0" )
+ droneRopeTable.rope02 <- CreateSingleDroneRope( drone, "ROPE_0" )
+ droneRopeTable.rope03 <- CreateSingleDroneRope( drone, "ROPE_1" )
+ droneRopeTable.rope04 <- CreateSingleDroneRope( drone, "ROPE_2" )
+ droneRopeTable.rope05 <- CreateSingleDroneRope( drone, "ROPE_3" )
+ droneRopeTable.rope06 <- CreateSingleDroneRope( drone, "ROPE_4" )
+
+ return droneRopeTable
+}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function RemoveDroneRopes( entity drone )
+{
+ if ( !( "droneRopeTable" in drone.s ) )
+ return
+
+ local droneRopeTable = drone.s.droneRopeTable
+ if ( IsValid( droneRopeTable.rope01.s.ropeEnd ) )
+ droneRopeTable.rope01.s.ropeEnd.Destroy()
+ if ( IsValid( droneRopeTable.rope02.s.ropeEnd ) )
+ droneRopeTable.rope02.s.ropeEnd.Destroy()
+ if ( IsValid( droneRopeTable.rope03.s.ropeEnd ) )
+ droneRopeTable.rope03.s.ropeEnd.Destroy()
+ if ( IsValid( droneRopeTable.rope04.s.ropeEnd ) )
+ droneRopeTable.rope04.s.ropeEnd.Destroy()
+ if ( IsValid( droneRopeTable.rope05.s.ropeEnd ) )
+ droneRopeTable.rope05.s.ropeEnd.Destroy()
+ if ( IsValid( droneRopeTable.rope06.s.ropeEnd ) )
+ droneRopeTable.rope06.s.ropeEnd.Destroy()
+ if ( IsValid( droneRopeTable.rope01 ) )
+ droneRopeTable.rope01.Destroy()
+ if ( IsValid( droneRopeTable.rope02 ) )
+ droneRopeTable.rope02.Destroy()
+ if ( IsValid( droneRopeTable.rope03 ) )
+ droneRopeTable.rope03.Destroy()
+ if ( IsValid( droneRopeTable.rope04 ) )
+ droneRopeTable.rope04.Destroy()
+ if ( IsValid( droneRopeTable.rope05 ) )
+ droneRopeTable.rope05.Destroy()
+ if ( IsValid( droneRopeTable.rope06 ) )
+ droneRopeTable.rope06.Destroy()
+
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function CreateSingleDroneRope( drone, attachTag, dangling = true )
+{
+ local subdivisions = 15 // 25
+ local slack = 200 // 25
+ string startpointName = UniqueString( "rope_startpoint" )
+ string endpointName = UniqueString( "rope_endpoint" )
+
+ local attach_id = drone.LookupAttachment( attachTag )
+ Assert( attach_id > 0, "Invalid attachment: " + attachTag )
+ local attachPos = drone.GetAttachmentOrigin( attach_id )
+
+ entity rope_start = CreateEntity( "move_rope" )
+ SetTargetName( rope_start, startpointName )
+ rope_start.kv.NextKey = endpointName
+ rope_start.kv.MoveSpeed = 32
+ rope_start.kv.Slack = slack
+ rope_start.kv.Subdiv = subdivisions
+ rope_start.kv.Width = "1"
+ rope_start.kv.TextureScale = "1"
+ rope_start.kv.RopeMaterial = "cable/cable_selfillum.vmt"
+ rope_start.kv.PositionInterpolator = 2
+ rope_start.kv.dangling = dangling
+ rope_start.SetOrigin( attachPos )
+ rope_start.SetParent( drone, attachTag )
+
+ entity rope_end = CreateEntity( "keyframe_rope" )
+ SetTargetName( rope_end, endpointName )
+ rope_end.kv.MoveSpeed = 32
+ rope_end.kv.Slack = slack
+ rope_end.kv.Subdiv = subdivisions
+ rope_end.kv.Width = "1"
+ rope_end.kv.TextureScale = "1"
+ rope_end.kv.RopeMaterial = "cable/cable_selfillum.vmt"
+ rope_end.SetOrigin( attachPos )
+
+ DispatchSpawn( rope_start )
+ DispatchSpawn( rope_end )
+
+ rope_start.s.ropeEnd <- rope_end
+
+ return rope_start
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function DroneShieldDestroy( DroneShieldTable )
+{
+ if ( !IsValid( DroneShieldTable ) )
+ return
+
+ local vortexSphere = DroneShieldTable.vortexSphere
+ local shieldWallFX = DroneShieldTable.shieldWallFX
+ local ropes = DroneShieldTable.shieldRopes
+
+ StopShieldWallFX( expect entity( vortexSphere ) )
+ if ( IsValid( vortexSphere ) )
+ vortexSphere.Destroy()
+
+ if ( !IsValid( ropes ) )
+ return
+
+ foreach ( rope in ropes )
+ {
+ if ( IsValid( rope.s.ropeEnd ) )
+ rope.s.ropeEnd.Destroy()
+ if ( IsValid( rope ) )
+ rope.Destroy()
+ }
+
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function DroneShieldExists( DroneShieldTable )
+{
+ if ( !IsValid( DroneShieldTable) )
+ return false
+
+ Assert( "vortexSphere" in DroneShieldTable, "DroneShieldTable doesn't contain any valid entries for vortexSphere." )
+ Assert( "shieldWallFX" in DroneShieldTable, "DroneShieldTable doesn't contain any valid entries for shieldWallFX." )
+
+ if ( ( IsValid( DroneShieldTable.vortexSphere ) ) && ( IsValid( DroneShieldTable.shieldWallFX ) ) )
+ return true
+
+ return false
+}
+
+void function DroneThrow( entity npc, entity drone, string spawnAnimDrone )
+{
+ drone.EndSignal( "OnDeath" )
+
+ drone.EnableNPCFlag( NPC_DISABLE_SENSING )
+
+// EmitSoundOnEntity( drone, "Drone_Power_On" )
+
+ #if GRUNTCHATTER_ENABLED
+ if ( NPC_GruntChatterSPEnabled( npc ) )
+ GruntChatter_TryFriendlyEquipmentDeployed( npc, "npc_drone" )
+ #endif
+
+ vector origin = npc.GetOrigin()
+ vector angles = npc.GetAngles()
+
+ //animate the drone properly from the npc's hand
+ PlayAnimTeleport( drone, spawnAnimDrone, origin, angles )
+
+ if ( IsAlive( npc ) )
+ {
+ entity enemy = npc.GetEnemy()
+ if ( IsAlive( enemy ) )
+ drone.SetEnemyLKP( enemy, npc.GetEnemyLKP() )
+ }
+
+ drone.DisableNPCFlag( NPC_DISABLE_SENSING )
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#if !SP
+void function DroneCleanupOnOwnerDeath_Thread( entity owner, entity drone )
+{
+ drone.EndSignal( "OnDestroy" )
+ drone.EndSignal( "OnDeath" )
+
+ for ( ; ; )
+ {
+ if ( !IsAlive( owner ) )
+ break
+
+ WaitFrame()
+ }
+
+ wait RandomFloatRange( 2.0, 10.0 )
+ drone.Die()
+}
+#endif // #if !SP
+
+entity function SpawnDroneFromNPC( entity npc, string aiSettings )
+{
+ //he's busy right now
+ if ( !IsAlive( npc ) || !npc.IsInterruptable() )
+ return null
+
+ vector origin = npc.GetOrigin()
+ vector angles = npc.GetAngles()
+ int team = npc.GetTeam()
+ entity owner = npc
+ vector deployOrigin = PositionOffsetFromEnt( npc, 64, 0, 0 )
+ float verticalClearance = GetVerticalClearance( deployOrigin )
+ string spawnAnimDrone
+ string spawnAnimSoldier
+
+ //-------------------------------------------------------------------
+ // Make sure enough clearance to spawn drone, and get correct anim
+ //-------------------------------------------------------------------
+ if ( verticalClearance >= 256 )
+ {
+ spawnAnimDrone = "dr_activate_drone_spin"
+ spawnAnimSoldier = "pt_activate_drone_spin"
+ }
+ else if ( ( verticalClearance < 256 ) && ( verticalClearance > DRONE_MINIMUM_DEPLOY_CLEARANCE_FROM_GROUND ) )
+ {
+ spawnAnimDrone = "dr_activate_drone_indoor"
+ spawnAnimSoldier = "pt_activate_drone_indoor"
+ }
+ else
+ {
+ printt( "NPC at ", npc.GetOrigin(), " couldn't spawn drone because there is less than ", DRONE_MINIMUM_DEPLOY_CLEARANCE_FROM_GROUND, " units of clearance from his origin." )
+ return null
+ }
+
+ //------------------------------------------
+ // NPC throws drone into air
+ //------------------------------------------
+ entity drone = CreateNPC( "npc_drone", team, origin, angles )
+ SetSpawnOption_AISettings( drone, aiSettings )
+ DispatchSpawn( drone )
+
+ if ( !IsAlive( drone ) )
+ return null
+
+ drone.NotSolid()
+ thread PlayAnim( npc, spawnAnimSoldier, origin, angles )
+ thread DroneSolidDelayed( drone )
+ thread DroneThrow( npc, drone, spawnAnimDrone )
+
+#if !SP
+ thread DroneCleanupOnOwnerDeath_Thread( npc, drone )
+#endif // #if !SP
+
+ npc.EnableNPCFlag( NPC_PAIN_IN_SCRIPTED_ANIM )
+
+ return drone
+}
+
+void function DroneSolidDelayed( entity drone )
+{
+ drone.EndSignal( "OnDestroy" )
+ wait 3.0 // wait for custom scale to finish in the animation
+ drone.Solid()
+}
+
+void function ShieldDroneLandsAfterLeaderDeath( entity drone )
+{
+ Assert( IsNewThread(), "Must be threaded off" )
+ drone.EndSignal( "OnDeath" )
+
+ drone.DisableBehavior( "Follow" )
+ //SetTeam( drone, TEAM_UNASSIGNED )
+ vector start = drone.GetOrigin()
+ vector end = start + Vector(0,0,-5000)
+ vector mins = drone.GetBoundingMins()
+ vector maxs = drone.GetBoundingMaxs()
+
+ TraceResults traceResult = TraceHull( start, end, mins, maxs, null, TRACE_MASK_NPCWORLDSTATIC, TRACE_COLLISION_GROUP_NONE )
+ if ( traceResult.fraction >= 1.0 )
+ {
+ // cant touch ground
+ drone.Die()
+ return
+ }
+
+ RemoveDroneRopes( drone )
+
+ //drone.SetUsable()
+ drone.AssaultPoint( traceResult.endPos )
+ //drone.SetInvulnerable()
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function CreateDroneSquadString( owner )
+{
+ Assert( IsValid( owner ), "Trying to MakeDroneSquad name for an invalid entity." )
+
+ local squadName
+
+ if ( owner.IsPlayer() )
+ squadName = "player" + owner.entindex() + "droneSquad"
+ else if ( owner.IsNPC() )
+ squadName = "npc" + owner.entindex() + "droneSquad"
+ else
+ Assert( 0, "Trying to CreateDroneSquadString for a non-NPC non-player entity at " + owner.GetOrigin() )
+
+ return squadName
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function SetDroneSquadStringForOwner( owner, squadName )
+{
+ Assert( IsValid( owner ), "Trying to SetDroneSquadStringForOwner name on an invalid entity." )
+
+ if ( !( "squadNameDrones" in owner.s ) )
+ owner.s.squadNameDrones <- null
+
+ owner.s.squadNameDrones = squadName
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function GetDroneSquadStringFromOwner( owner )
+{
+ Assert( IsValid( owner ), "Trying to GetDroneSquadStringFromOwner name on an invalid entity." )
+ if ( !( "squadNameDrones" in owner.s ) )
+ return null
+ else
+ return owner.s.squadNameDrones
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// DroneGrunt deploys drone after cooldown when drone is destroyed
+function DroneGruntThink( entity npc, string aiSettings )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ npc.EndSignal( "OnDestroy" )
+ npc.EndSignal( "OnDeath" )
+
+ entity drone
+ float spawnCooldown
+ entity closestEnemy
+ npc.EnableNPCFlag( NPC_USE_SHOOTING_COVER | NPC_CROUCH_COMBAT )
+
+ while ( true )
+ {
+ //if ( npc.GetNPCState() == "idle" )
+ //{
+ // npc.WaitSignal( "OnStateChange" )
+ // continue
+ //}
+
+ wait ( RandomFloatRange( 0, 1.0 ) )
+
+ //dont do stuff when animating on a parent
+ if ( npc.GetParent() )
+ continue
+
+ // Don't deploy if would hit ceiling, droppod, etc
+ if ( !DroneHasEnoughRoomToDeployFromNPC( npc ) )
+ continue
+
+ entity enemy = npc.GetEnemy()
+ if ( !IsAlive( enemy ) )
+ continue
+
+ //vector pos = npc.LastKnownPosition( enemy )
+ //if ( !WithinEngagementRange( npc, pos ) )
+ // continue
+
+ drone = SpawnDroneFromNPC( npc, aiSettings )
+ if ( drone == null )
+ continue
+
+ waitthread DroneWaitTillDeadOrHacked( drone )
+
+ wait 15
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function DroneHasEnoughRoomToDeployFromNPC( npc )
+{
+ expect entity( npc )
+
+ if ( !IsValid( npc ) )
+ return false
+ //-----------------------------------------------
+ // Grunt throws drone a bit in front of him
+ //-----------------------------------------------
+ vector deployOrigin = PositionOffsetFromEnt( npc, 64, 0, 0 )
+
+ if ( GetVerticalClearance( deployOrigin ) < DRONE_MINIMUM_DEPLOY_CLEARANCE_FROM_GROUND )
+ return false
+ else
+ return true
+
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function DroneWaitTillDeadOrHacked( drone )
+{
+ drone.EndSignal( "OnDestroy" )
+ drone.EndSignal( "OnDeath" )
+ drone.EndSignal( "OnNewOwner" )
+
+ WaitForever()
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function DroneDeath( entity drone, var damageInfo )
+{
+ local deathFX
+
+ switch ( GetDroneType( drone ) )
+ {
+ case "drone_type_rocket":
+ deathFX = FX_DRONE_R_EXPLOSION
+ break
+ case "drone_type_plasma":
+ deathFX = FX_DRONE_P_EXPLOSION
+ break
+ case "drone_type_marvin":
+ deathFX = FX_DRONE_W_EXPLOSION
+ break
+ case "drone_type_shield":
+ case "drone_type_engineer_shield":
+ case "drone_type_engineer_combat":
+ default:
+ deathFX = FX_DRONE_EXPLOSION
+ break
+ }
+
+ // Explosion effect
+ entity explosion = CreateEntity( "info_particle_system" )
+ explosion.SetOrigin( drone.GetWorldSpaceCenter() )
+ explosion.SetAngles( drone.GetAngles() )
+ explosion.SetValueForEffectNameKey( deathFX )
+ explosion.kv.start_active = 1
+ DispatchSpawn( explosion )
+
+ local deathSound
+
+ // this sound get should be moved to ai settings file
+ switch ( GetDroneType( drone ) )
+ {
+ case "drone_type_rocket":
+ case "drone_type_plasma":
+ case "drone_type_marvin":
+ case "drone_type_shield":
+ case "drone_type_engineer_shield":
+ case "drone_type_engineer_combat":
+ deathSound = SOUND_DRONE_EXPLODE_DEFAULT
+ break
+ default:
+ deathSound = SOUND_DRONE_EXPLODE_DEFAULT
+ break
+ }
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, drone.GetOrigin(), deathSound )
+ explosion.Kill_Deprecated_UseDestroyInstead( 3 )
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+function DroneDialogue( drone, event, player = null )
+{
+ expect entity( drone )
+ expect entity( player )
+
+ if ( !IsAlive( drone ) )
+ return
+
+ if ( player != null )
+ {
+ if ( !IsAlive( player ) )
+ return
+ }
+
+ local alias
+ bool playToPlayerOnly = true
+
+ switch ( event )
+ {
+ case "smoke_deploy":
+ //Foreign entity attached, deploying countermeasures.
+ alias = "diag_gs_drone_detectEnemyRodeo"
+ break
+ case "hack_success":
+ //New host accepted.
+ alias = "diag_gs_drone_hostAcceptNew"
+
+ //Foreign host accepted.
+ if ( CoinFlip() )
+ alias = "diag_gs_drone_hostAcceptForeign"
+ break
+ case "transform_shield_to_assault":
+ //Drone host eliminated, engaging assault mode
+ alias = "diag_gs_drone_elimHost"
+ playToPlayerOnly = false
+ break
+ default:
+ Assert( 0, "Invalid DroneDialogue event: " + event )
+ }
+
+ if ( playToPlayerOnly )
+ EmitSoundOnEntityOnlyToPlayer( drone, player, alias )
+ else
+ EmitSoundOnEntity( drone, alias )
+
+
+/*
+Hostiles detected, marking targets
+diag_gs_drone_detectHostileTargets
+
+Drone targets marked
+diag_gs_drone_targetsMarked
+
+Escort drone destroyed
+diag_gs_drone_escortDestroyed
+
+Multiple escort drones combined. Shield radius increased
+diag_gs_drone_combinedShieldRadius
+
+Multiple escort drones combined. Projectile accuracy increased
+diag_gs_drone_combinedWpnAccuracy
+
+Recharging drone shield
+diag_gs_drone_rechargingShield
+
+Target lost
+diag_gs_drone_targetLost
+
+Target acquired
+diag_gs_drone_targetAcquired
+*/
+
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+function DroneOnLeeched( drone, player )
+{
+ //global behavior when this npc gets leeched
+ delaythread ( 1 ) DroneDialogue( drone, "hack_success", player )
+}
+
+function DroneSelfDestruct( drone, delay )
+{
+ drone.EndSignal( "OnDeath" )
+ wait delay
+ drone.Die()
+}
+
+function RepairDroneThink( entity drone )
+{
+ drone.EndSignal( "OnDeath" )
+ local attachID
+ EmitSoundOnEntity( drone, "colony_spectre_initialize_beep" )
+ thread DroneSelfDestruct( drone, 60 )
+
+ for ( ;; )
+ {
+ if ( drone.e.repairSoul == null )
+ {
+ wait 1
+ continue
+ }
+
+ string attachName = "HIJACK"
+ entity repairTitan = drone.e.repairSoul.GetTitan()
+
+ /*
+ if ( IsSoul( repairTarget ) )
+ {
+ repairTarget = repairTarget.GetTitan()
+ attachName = "HIJACK"
+ }
+ else
+ {
+ Assert( !repairTarget.IsTitan() )
+ attachName = "ORIGIN"
+ }
+
+ if ( !IsAlive( repairTitan ) )
+ {
+ wait 2
+ continue
+ }
+ */
+
+ drone.SetOwner( repairTitan )
+
+ if ( DroneCanRepairTarget( drone, repairTitan, attachName ) )
+ {
+ // close enough to repair?
+ //P_wpn_defender_beam
+ waitthread DroneRepairsTarget( drone, repairTitan, attachName )
+ }
+ WaitFrame()
+ }
+}
+
+bool function DroneCanRepairTarget( drone, ent, attachName )
+{
+ expect entity( ent )
+
+ if ( !IsAlive( ent ) )
+ return false
+
+ if ( ent.GetHealth() >= ent.GetMaxHealth() )
+ return false
+
+ local attachID = ent.LookupAttachment( attachName )
+ local origin = ent.GetAttachmentOrigin( attachID )
+ local droneOrigin = drone.GetOrigin()
+ if ( Distance( droneOrigin, origin ) > 600 )
+ return false
+
+ float trace = TraceLineSimple( droneOrigin, origin, ent )
+ return trace == 1.0
+}
+
+function DroneRepairsTarget( drone, ent, attachName )
+{
+ expect entity( drone )
+ expect entity( ent )
+
+ drone.EndSignal( "OnDestroy" )
+ EmitSoundOnEntity( drone, "EMP_Titan_Electrical_Field" )
+
+ OnThreadEnd(
+ function() : ( drone )
+ {
+ if ( IsValid( drone ) )
+ StopSoundOnEntity( drone, "EMP_Titan_Electrical_Field" )
+ }
+ )
+
+ int followBehavior = GetDefaultNPCFollowBehavior( drone )
+ drone.SetOwner( ent )
+ drone.InitFollowBehavior( ent, followBehavior )
+ drone.EnableBehavior( "Follow" )
+
+ for ( ;; )
+ {
+ if ( !DroneCanRepairTarget( drone, ent, attachName ) )
+ return
+
+ DroneRepairFX( drone, ent, attachName )
+
+ local maxHealth = ent.GetMaxHealth()
+ local healAmount = maxHealth * 0.015 // 0.005
+ float healTime = RandomFloatRange( 0.8, 1.2 )
+
+ for ( float i = 0.0; i < healTime; i++ )
+ {
+ if ( !IsAlive( ent ) )
+ return
+
+ local newHealth = ent.GetHealth() + healAmount
+ newHealth = min( newHealth, maxHealth )
+ ent.SetHealth( newHealth )
+ WaitFrame()
+ }
+ }
+}
+
+function DroneRepairFX( drone, ent, attachName )
+{
+ // Control point sets the end position of the effect
+ entity cpEnd = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpEnd, UniqueString( "arc_cannon_beam_cpEnd" ) )
+ cpEnd.SetParent( ent, attachName, false, 0.0 )
+ DispatchSpawn( cpEnd )
+
+ entity zapBeam = CreateEntity( "info_particle_system" )
+ zapBeam.kv.cpoint1 = cpEnd.GetTargetName()
+
+ zapBeam.SetValueForEffectNameKey( ARC_CANNON_BEAM_EFFECT )
+ zapBeam.kv.start_active = 0
+ zapBeam.SetOwner( drone )
+ zapBeam.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY)
+ zapBeam.SetParent( drone, "ORIGIN", false, 0.0 )
+ DispatchSpawn( zapBeam )
+
+ zapBeam.Fire( "Start" )
+ zapBeam.Fire( "StopPlayEndCap", "", 2.0 )
+ zapBeam.Kill_Deprecated_UseDestroyInstead( 2.0 )
+ cpEnd.Kill_Deprecated_UseDestroyInstead( 2.0 )
+}
+
+
+function SetRepairDroneTarget( entity drone, entity repairTitan )
+{
+ Assert( IsAlive( repairTitan ), "Repair target " + repairTitan + " is dead" )
+ Assert( repairTitan.IsTitan() )
+ drone.e.repairSoul = repairTitan.GetTitanSoul()
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_emp_titans.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_emp_titans.gnut
new file mode 100644
index 000000000..638166c83
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_emp_titans.gnut
@@ -0,0 +1,181 @@
+untyped
+
+global function EmpTitans_Init
+
+global function EMPTitanThinkConstant
+
+const DAMAGE_AGAINST_TITANS = 150
+const DAMAGE_AGAINST_PILOTS = 40
+
+const EMP_DAMAGE_TICK_RATE = 0.3
+const FX_EMP_FIELD = $"P_xo_emp_field"
+const FX_EMP_FIELD_1P = $"P_body_emp_1P"
+
+function EmpTitans_Init()
+{
+ AddDamageCallbackSourceID( eDamageSourceId.titanEmpField, EmpField_DamagedEntity )
+ PrecacheParticleSystem( FX_EMP_FIELD )
+ PrecacheParticleSystem( FX_EMP_FIELD_1P )
+
+ RegisterSignal( "StopEMPField" )
+}
+
+void function EMPTitanThinkConstant( entity titan )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "Doomed" )
+ titan.EndSignal( "StopEMPField" )
+
+ //We don't want pilots accidently rodeoing an electrified titan.
+ DisableTitanRodeo( titan )
+
+ //Used to identify this titan as an arc titan
+ SetTargetName( titan, "empTitan" )
+
+ //Wait for titan to stand up and exit bubble shield before deploying arc ability.
+ WaitTillHotDropComplete( titan )
+
+ if ( HasSoul( titan ) )
+ {
+ entity soul = titan.GetTitanSoul()
+ soul.EndSignal( "StopEMPField" )
+ }
+
+ local attachment = GetEMPAttachmentForTitan( titan )
+
+ local attachID = titan.LookupAttachment( attachment )
+
+ EmitSoundOnEntity( titan, "EMP_Titan_Electrical_Field" )
+
+ array<entity> particles = []
+
+ //emp field fx
+ vector origin = titan.GetAttachmentOrigin( attachID )
+ if ( titan.IsPlayer() )
+ {
+ entity particleSystem = CreateEntity( "info_particle_system" )
+ particleSystem.kv.start_active = 1
+ particleSystem.kv.VisibilityFlags = ENTITY_VISIBLE_TO_OWNER
+ particleSystem.SetValueForEffectNameKey( FX_EMP_FIELD_1P )
+
+ particleSystem.SetOrigin( origin )
+ particleSystem.SetOwner( titan )
+ DispatchSpawn( particleSystem )
+ particleSystem.SetParent( titan, GetEMPAttachmentForTitan( titan ) )
+ particles.append( particleSystem )
+ }
+
+ entity particleSystem = CreateEntity( "info_particle_system" )
+ particleSystem.kv.start_active = 1
+ if ( titan.IsPlayer() )
+ particleSystem.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // everyone but owner
+ else
+ particleSystem.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ particleSystem.SetValueForEffectNameKey( FX_EMP_FIELD )
+ particleSystem.SetOwner( titan )
+ particleSystem.SetOrigin( origin )
+ DispatchSpawn( particleSystem )
+ particleSystem.SetParent( titan, GetEMPAttachmentForTitan( titan ) )
+ particles.append( particleSystem )
+
+ titan.SetDangerousAreaRadius( ARC_TITAN_EMP_FIELD_RADIUS )
+
+ OnThreadEnd(
+ function () : ( titan, particles )
+ {
+ if ( IsValid( titan ) )
+ {
+ StopSoundOnEntity( titan, "EMP_Titan_Electrical_Field" )
+ EnableTitanRodeo( titan ) //Make the arc titan rodeoable now that it is no longer electrified.
+ }
+
+ foreach ( particleSystem in particles )
+ {
+ if ( IsValid_ThisFrame( particleSystem ) )
+ {
+ particleSystem.ClearParent()
+ particleSystem.Fire( "StopPlayEndCap" )
+ particleSystem.Kill_Deprecated_UseDestroyInstead( 1.0 )
+ }
+ }
+ }
+ )
+
+ wait RandomFloat( EMP_DAMAGE_TICK_RATE )
+
+ while ( true )
+ {
+ origin = titan.GetAttachmentOrigin( attachID )
+
+ RadiusDamage(
+ origin, // center
+ titan, // attacker
+ titan, // inflictor
+ DAMAGE_AGAINST_PILOTS, // damage
+ DAMAGE_AGAINST_TITANS, // damageHeavyArmor
+ ARC_TITAN_EMP_FIELD_INNER_RADIUS, // innerRadius
+ ARC_TITAN_EMP_FIELD_RADIUS, // outerRadius
+ SF_ENVEXPLOSION_NO_DAMAGEOWNER, // flags
+ 0, // distanceFromAttacker
+ DAMAGE_AGAINST_PILOTS, // explosionForce
+ DF_ELECTRICAL | DF_STOPS_TITAN_REGEN, // scriptDamageFlags
+ eDamageSourceId.titanEmpField ) // scriptDamageSourceIdentifier
+
+ wait EMP_DAMAGE_TICK_RATE
+ }
+}
+
+void function EmpField_DamagedEntity( entity target, var damageInfo )
+{
+ if ( !IsAlive( target ) )
+ return
+
+ entity titan = DamageInfo_GetAttacker( damageInfo )
+
+ if ( !IsValid( titan ) )
+ return
+
+ local className = target.GetClassName()
+ if ( className == "rpg_missile" || className == "npc_turret_sentry" || className == "grenade" )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ if ( DamageInfo_GetDamage( damageInfo ) <= 0 )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ if ( target.IsPlayer() )
+ {
+ if ( !titan.IsPlayer() && IsArcTitan( titan ) )
+ {
+ if ( !titan.s.electrocutedPlayers.contains( target ) )
+ titan.s.electrocutedPlayers.append( target )
+ }
+
+ const ARC_TITAN_SCREEN_EFFECTS = 0.085
+ const ARC_TITAN_EMP_DURATION = 0.35
+ const ARC_TITAN_EMP_FADEOUT_DURATION = 0.35
+
+ local attachID = titan.LookupAttachment( "hijack" )
+ local origin = titan.GetAttachmentOrigin( attachID )
+ local distSqr = DistanceSqr( origin, target.GetOrigin() )
+
+ local minDist = ARC_TITAN_EMP_FIELD_INNER_RADIUS_SQR
+ local maxDist = ARC_TITAN_EMP_FIELD_RADIUS_SQR
+ local empFxHigh = ARC_TITAN_SCREEN_EFFECTS
+ local empFxLow = ( ARC_TITAN_SCREEN_EFFECTS * 0.6 )
+ float screenEffectAmplitude = GraphCapped( distSqr, minDist, maxDist, empFxHigh, empFxLow )
+
+ StatusEffect_AddTimed( target, eStatusEffect.emp, screenEffectAmplitude, ARC_TITAN_EMP_DURATION, ARC_TITAN_EMP_FADEOUT_DURATION )
+ }
+}
+
+string function GetEMPAttachmentForTitan( entity titan )
+{
+ return "hijack"
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_gunship.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_gunship.gnut
new file mode 100644
index 000000000..2f1fdc96f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_gunship.gnut
@@ -0,0 +1,97 @@
+untyped
+
+global function AiGunship_Init
+
+global function GunshipThink
+
+global const SOUND_GUNSHIP_HOVER = "Gunship_Hover"
+global const SOUND_GUNSHIP_EXPLODE_DEFAULT = "Gunship_Explode"
+global const FX_GUNSHIP_EXPLOSION = $"P_veh_exp_crow"
+
+function AiGunship_Init()
+{
+ PrecacheParticleSystem( FX_GUNSHIP_EXPLOSION )
+ AddDeathCallback( "npc_gunship", GunshipDeath )
+}
+
+
+function GunshipThink( gunship )
+{
+ gunship.EndSignal( "OnDeath" )
+
+ entity owner
+ entity currentTarget
+ local accuracyMultiplierBase = gunship.kv.AccuracyMultiplier
+ local accuracyMultiplierAgainstDrones = 100
+
+ while( true )
+ {
+ wait 0.25
+
+ //----------------------------------
+ // Get owner and current enemy
+ //----------------------------------
+ currentTarget = expect entity( gunship.GetEnemy() )
+ owner = expect entity( gunship.GetFollowTarget() )
+
+ //----------------------------------
+ // Free roam if owner is dead or HasEnemy
+ //----------------------------------
+ if ( ( !IsAlive( owner ) ) || ( currentTarget != null ) )
+ {
+ gunship.DisableBehavior( "Follow" )
+ }
+
+ //---------------------------------------------------------------------
+ // If owner is alive and no enemies in sight, go back and follow owner
+ //----------------------------------------------------------------------
+ if ( ( IsAlive( owner ) ) && ( currentTarget == null ) )
+ {
+ gunship.EnableBehavior( "Follow" )
+ }
+
+
+ //----------------------------------------------
+ // Jack up accuracy if targeting a small target (like a drone)
+ //----------------------------------------------
+ if ( ( currentTarget != null ) && ( IsAirDrone( currentTarget ) ) )
+ {
+ gunship.kv.AccuracyMultiplier = accuracyMultiplierAgainstDrones
+ }
+ else
+ {
+ gunship.kv.AccuracyMultiplier = accuracyMultiplierBase
+ }
+ }
+
+}
+
+
+void function GunshipDeath( entity gunship, var damageInfo )
+{
+ /*
+ Script errors
+
+ // Explosion effect
+ entity explosion = CreateEntity( "info_particle_system" )
+ explosion.SetOrigin( gunship.GetWorldSpaceCenter() )
+ explosion.SetAngles( gunship.GetAngles() )
+ explosion.SetValueForEffectNameKey( FX_GUNSHIP_EXPLOSION )
+ explosion.kv.start_active = 1
+ DispatchSpawn( explosion )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, gunship.GetOrigin(), SOUND_GUNSHIP_EXPLODE_DEFAULT )
+ explosion.destroy( 3 )
+
+ gunship.Destroy()
+
+ P_veh_exp_hornet, TAG_ORIGIN, attach
+
+ */
+
+ //TEMP
+ PlayFX( FX_GUNSHIP_EXPLOSION, gunship.GetOrigin() )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, gunship.GetOrigin(), "Goblin_Dropship_Explode" )
+ gunship.Destroy()
+}
+
+
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_lethality.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_lethality.gnut
new file mode 100644
index 000000000..771fe6d93
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_lethality.gnut
@@ -0,0 +1,97 @@
+untyped
+
+global enum eAILethality
+{
+ VeryLow,
+ Low,
+ Medium,
+ High,
+ VeryHigh
+}
+
+global function SetAILethality
+
+global function UpdateNPCForAILethality
+
+function SetAILethality( aiLethality )
+{
+ Assert( IsMultiplayer() )
+ level.nv.aiLethality = aiLethality
+
+ switch ( aiLethality )
+ {
+ case eAILethality.Medium:
+ break
+
+ case eAILethality.High:
+ NPCSetAimConeFocusParams( 6, 2.5 )
+ NPCSetAimPatternFocusParams( 4, 0.3, 0.8 )
+ break
+ case eAILethality.VeryHigh:
+ NPCSetAimConeFocusParams( 5, 2.0 )
+ NPCSetAimPatternFocusParams( 4, 0.3, 0.8 )
+ break
+ }
+
+ // reset ai lethality
+
+ array<entity> npcs = GetNPCArray()
+ foreach ( npc in npcs )
+ {
+ UpdateNPCForAILethality( npc )
+ }
+}
+
+
+function SetTitanAccuracyAndProficiency( entity npcTitan )
+{
+ Assert( IsMultiplayer() )
+ int lethality = Riff_AILethality()
+ float accuracyMultiplier = 1.0
+ int weaponProficiency = eWeaponProficiency.GOOD
+
+ entity player = GetPetTitanOwner( npcTitan )
+ entity soul = npcTitan.GetTitanSoul()
+
+ // auto titans have lower proficiency
+ if ( player && soul == null)
+ {
+ soul = player.GetTitanSoul() // in mid transfer
+ }
+
+ if ( IsValid( soul ) )
+ {
+ if ( SoulHasPassive( soul, ePassives.PAS_ENHANCED_TITAN_AI ) )
+ {
+ weaponProficiency = eWeaponProficiency.GOOD
+ }
+ else if ( player )
+ {
+ weaponProficiency = eWeaponProficiency.AVERAGE
+ entity ordnanceWeapon = npcTitan.GetOffhandWeapon( OFFHAND_ORDNANCE )
+ if ( IsValid( ordnanceWeapon ) )
+ ordnanceWeapon.AllowUse( false )
+
+ entity centerWeapon = npcTitan.GetOffhandWeapon( OFFHAND_TITAN_CENTER )
+ if ( IsValid( centerWeapon ) )
+ centerWeapon.AllowUse( false )
+ }
+ }
+
+ npcTitan.kv.AccuracyMultiplier = accuracyMultiplier
+ npcTitan.kv.WeaponProficiency = weaponProficiency
+}
+
+function UpdateNPCForAILethality( entity npc )
+{
+ Assert( IsMultiplayer() )
+ if ( npc.IsTitan() )
+ {
+ SetTitanAccuracyAndProficiency( npc )
+ return
+ }
+
+ if ( IsMinion( npc ) )
+ SetProficiency( npc )
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvin_faces.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvin_faces.gnut
new file mode 100644
index 000000000..e6d3bcf0a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvin_faces.gnut
@@ -0,0 +1,226 @@
+untyped
+
+global function MarvinFaces_Init
+
+global function MarvinFace
+global function MarvinThinksAwhile
+global function MarvinFaceExists
+global function SetMarvinBodyType
+global function MarvinSetFace
+
+function MarvinFaces_Init()
+{
+
+ RegisterSignal( "StopThinking" )
+
+ AddSpawnCallback( "npc_marvin", MarvinSpawnCallback )
+
+ SetupMarvinFaces()
+}
+
+function SetupMarvinFaces()
+{
+ // setup marvin face mappings
+ level.marvinFaces <- {}
+ level.marvinFaces[ MARVIN_TYPE_WORKER ] <-
+ {
+ none = 0
+ happy = 1
+ sad = 2
+ angry = 3
+ think1 = 4
+ think2 = 5
+ question = 6
+ }
+
+ // Use the yellow worker marvin skins since shooters are from SP and shooter value = 0 which is LevelEd's default.
+ // Real shooter skins are 7-13. If we want real skins, we can add them here and adjust the marvin spawn points per level.
+ level.marvinFaces[ MARVIN_TYPE_SHOOTER ] <-
+ {
+ none = 0
+ happy = 1
+ sad = 2
+ angry = 3
+ think1 = 4
+ think2 = 5
+ question = 6
+ }
+
+ level.marvinFaces[ MARVIN_TYPE_FIREFIGHTER ] <-
+ {
+ none = 14
+ happy = 15
+ sad = 16
+ angry = 17
+ think1 = 18
+ think2 = 19
+ question = 20
+ }
+
+ // No idea what this type of marvin is...legacy stuff. Just make them yellow.
+ level.marvinFaces[ MARVIN_TYPE_MARVINONE ] <-
+ {
+ none = 0
+ happy = 1
+ sad = 2
+ angry = 3
+ think1 = 4
+ think2 = 5
+ question = 6
+ }
+
+// Nothing uses this except debug statements that are commented out
+/*
+ level.marvinFaceNames <- {}
+ // invert map for tests
+ foreach ( key, val in level.marvinFace )
+ {
+ level.marvinFaceNames[ val ] <- key
+ }
+*/
+}
+
+void function MarvinFace( entity marvin )
+{
+ thread MarvinFaceThink( marvin )
+}
+
+void function MarvinFaceThink( entity marvin )
+{
+ //printl( "Setting up marvin face for " + marvin )
+ for ( ;; )
+ {
+ waitthread MarvinUndamagedFacePicker( marvin )
+
+// printl( "damaged " + marvin )
+ if ( !IsAlive( marvin ) )
+ break
+
+ waitthread MarvinWounded( marvin )
+
+ if ( !IsAlive( marvin ) )
+ break
+ }
+
+ if ( IsValid_ThisFrame( marvin ) )
+ MarvinSetFace( marvin, "none" )
+}
+
+function MarvinWounded( marvin )
+{
+ marvin.EndSignal( "OnDeath" )
+ MarvinSetFace( marvin, "sad" )
+ wait 2.3
+ waitthread MarvinThinksAwhile( marvin, RandomFloatRange( 2, 4 ) )
+}
+
+void function EntSignals( entity ent, string signal )
+{
+ if ( IsValid_ThisFrame( ent ) )
+ ent.Signal( signal )
+}
+
+function MarvinThinksAwhile( marvin, time )
+{
+ expect entity( marvin )
+
+ marvin.EndSignal( "StopThinking" )
+ delaythread( time ) EntSignals( marvin, "StopThinking" )
+
+ // think for a bit
+ for ( ;; )
+ {
+ MarvinSetFace( marvin, "think1" )
+ wait 0.4
+ MarvinSetFace( marvin, "think2" )
+ wait 0.4
+ }
+}
+
+function MarvinUndamagedFacePicker( marvin )
+{
+ marvin.EndSignal( "OnDeath" )
+ marvin.EndSignal( "OnDamaged" )
+ local i
+
+ for ( ;; )
+ {
+ if ( !marvin.GetEnemy() )
+ {
+ MarvinSetFace( marvin, "happy" )
+ marvin.WaitSignal( "OnFoundEnemy" )
+ }
+
+ waitthread MarvinThinksAwhile( marvin, RandomFloatRange( 2, 4 ) )
+
+ if ( marvin.GetEnemy() )
+ {
+ MarvinSetFace( marvin, "angry" )
+ marvin.WaitSignal( "OnLostEnemy" )
+ }
+ }
+}
+
+function MarvinSetFace( self, face )
+{
+// printl( self + " got face " + face )
+ Assert( MarvinFaceExists( self, face ), "No face " + face + " in level.marvinFace" )
+
+ //prin( "Changing " + self + " face from " + level.marvinFaceNames[ skin ] + " to " + face )
+ self.SetSkin( GetMarvinFace( self, face ) )
+ self.Signal( "StopThinking" )
+}
+
+function MarvinFaceExists( npc_marvin, face )
+{
+ local marvinType = GetMarvinBodyType( npc_marvin )
+
+ if ( marvinType in level.marvinFaces )
+ return true
+
+// return ( face in level.marvinFaces[ marvinType ] )
+}
+
+function GetMarvinFace( npc_marvin, face )
+{
+ local marvinType = GetMarvinBodyType( npc_marvin )
+
+ Assert( MarvinFaceExists( npc_marvin, face ), "No face " + face + " in level.marvinFace" )
+
+ local faceID = level.marvinFaces[ marvinType ][ face ]
+
+ return faceID
+}
+
+function SetMarvinBodyType( npc_marvin )
+{
+ if( "bodytype" in npc_marvin.s )
+ {
+ Assert( npc_marvin.s.bodytype >= MARVIN_TYPE_SHOOTER && npc_marvin.s.bodytype <= MARVIN_TYPE_FIREFIGHTER, "Specified invalid body type index " + npc_marvin.s.bodytype + ", Use values from 0-2 instead." )
+
+ switch( npc_marvin.s.bodytype )
+ {
+ case MARVIN_TYPE_FIREFIGHTER:
+ local index = npc_marvin.FindBodyGroup( "firefighter" )
+ local state = 1
+ npc_marvin.SetBodygroup( index, state )
+ break
+ }
+ }
+}
+
+function GetMarvinBodyType( npc_marvin )
+{
+ local bodyType = MARVIN_TYPE_WORKER
+
+ if( "bodytype" in npc_marvin.s )
+ bodyType = npc_marvin.s.bodytype
+
+ return bodyType
+}
+
+void function MarvinSpawnCallback( entity npc_marvin )
+{
+ SetMarvinBodyType( npc_marvin )
+ npc_marvin.SetDeathNotifications( true ) //Primarily so we can do HandleDeathPackage for Marvins. Can just add a deathcallback if this is too expensive
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvin_jobs.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvin_jobs.gnut
new file mode 100644
index 000000000..588b4d75e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvin_jobs.gnut
@@ -0,0 +1,600 @@
+
+/*
+ ToDo:
+ -if marvin has no jobs to go to make him to back to spawn position instead of standing at last node
+*/
+
+global function MarvinJobs_Init
+global function MarvinJobThink
+global function GetMarvinType
+
+const DEBUG_MARVIN_JOBS = false
+const MAX_JOB_SEARCH_DIST_SQR = 1000 * 1000
+const JOB_NODE_COOLDOWN_TIME = 15.0
+
+struct MarvinJob
+{
+ string validMarvinType
+ entity node
+ entity user
+ string jobType
+ bool tempJob
+ float nextUsableTime = 0
+ entity barrel
+}
+
+struct
+{
+ array<MarvinJob> marvinJobs
+ table<string,void functionref( entity,MarvinJob)> jobFunctions
+} file
+
+
+
+
+// ██╗███╗ ██╗██╗████████╗
+// ██║████╗ ██║██║╚â•â•â–ˆâ–ˆâ•”â•â•â•
+// ██║██╔██╗ ██║██║ ██║
+// ██║██║╚██╗██║██║ ██║
+// ██║██║ ╚████║██║ ██║
+// â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•šâ•â• â•šâ•â•
+
+void function MarvinJobs_Init()
+{
+ file.jobFunctions[ "welding" ] <- SimpleJobAnims
+ file.jobFunctions[ "welding_under" ] <- SimpleJobAnims
+ file.jobFunctions[ "window" ] <- SimpleJobAnims
+ file.jobFunctions[ "fightFire" ] <- SimpleJobAnims
+ file.jobFunctions[ "barrel_pickup" ] <- MarvinPicksUpBarrel
+ file.jobFunctions[ "barrel_putdown" ] <- MarvinPutsDownBarrel
+ file.jobFunctions[ "repair_over_edge" ] <- SimpleJobAnims
+ file.jobFunctions[ "repair_above" ] <- SimpleJobAnims
+ file.jobFunctions[ "repair_under" ] <- SimpleJobAnims
+ file.jobFunctions[ "datacards" ] <- SimpleJobAnims
+
+ file.jobFunctions[ "drone_welding" ] <- SimpleJobAnims
+ file.jobFunctions[ "drone_inspect" ] <- SimpleJobAnims
+
+ RegisterSignal( "pickup_barrel" )
+ RegisterSignal( "putdown_barrel" )
+ RegisterSignal( "JobStarted" )
+ RegisterSignal( "StopDoingJobs" )
+
+ AddSpawnCallback( "script_marvin_job", InitMarvinJob )
+
+ AddCallback_EntitiesDidLoad( MarvinJobsEntitiesDidLoad )
+}
+
+void function InitMarvinJob( entity node )
+{
+ Assert( node.HasKey( "job" ) )
+ Assert( node.kv.job != "" )
+ Assert( string( node.kv.job ) in file.jobFunctions, "Marvin job node at " + node.GetOrigin() + " has unhandled job type " + string( node.kv.job ) )
+ string editorClass = GetEditorClass( node )
+
+ // Drop node to ground for certain types or if checked on the entity
+ if ( editorClass == "" )
+ {
+ if ( !node.HasKey( "hover" ) || node.kv.hover != "1" )
+ DropToGround( node )
+ }
+
+ if ( DEBUG_MARVIN_JOBS )
+ DebugDrawAngles( node.GetOrigin(), node.GetAngles() )
+
+ // Create marvin job struct
+ MarvinJob marvinJob
+ marvinJob.node = node
+ marvinJob.jobType = string( node.kv.job )
+ marvinJob.tempJob = node.HasKey( "tempJob" ) && node.kv.tempJob == "1"
+
+ if ( marvinJob.jobType == "barrel_pickup" )
+ marvinJob.barrel = CreateBarrel( node )
+
+ // Set what marvin_type of NPC can use this job
+ switch ( editorClass )
+ {
+ case "script_marvin_drone_job":
+ marvinJob.validMarvinType = "marvin_type_drone"
+ break
+ default:
+ marvinJob.validMarvinType = "marvin_type_walker"
+ break
+ }
+
+ file.marvinJobs.append( marvinJob )
+}
+
+void function MarvinJobsEntitiesDidLoad()
+{
+ if ( DEBUG_MARVIN_JOBS )
+ DebugMarvinJobs()
+}
+
+
+
+
+
+// ████████╗██╗ ██╗██╗███╗ ██╗██╗ ██╗
+// â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘ ██║██║████╗ ██║██║ ██╔â•
+// ██║ ███████║██║██╔██╗ ██║█████╔â•
+// ██║ ██╔â•â•â–ˆâ–ˆâ•‘██║██║╚██╗██║██╔â•â–ˆâ–ˆâ•—
+// ██║ ██║ ██║██║██║ ╚████║██║ ██╗
+// â•šâ•â• â•šâ•â• â•šâ•â•â•šâ•â•â•šâ•â• â•šâ•â•â•â•â•šâ•â• â•šâ•â•
+
+void function MarvinJobThink( entity marvin )
+{
+ EndSignal( marvin, "OnDeath" )
+ EndSignal( marvin, "OnDestroy" )
+ EndSignal( marvin, "StopDoingJobs" )
+
+ // Wait a frame because npcs that are spawned at map load may run this function before job nodes are finished being initialized
+ WaitFrame()
+
+ // Get all jobs this marvin can do
+ array<MarvinJob> jobs = GetJobsForMarvin( marvin )
+ if ( jobs.len() == 0 )
+ return
+
+ OnThreadEnd(
+ function() : ( marvin )
+ {
+ Assert( !IsAlive( marvin ), "MarvinJobThink ended but the marvin is still alive" )
+ }
+ )
+
+ while ( true )
+ {
+ foreach ( MarvinJob job in jobs )
+ {
+ waitthread MarvinDoJob( marvin, job )
+ WaitFrame()
+ }
+
+ jobs.randomize()
+ WaitFrame()
+ }
+}
+
+void function MarvinDoJob( entity marvin, MarvinJob job )
+{
+ Assert( IsAlive( marvin ), "Marvin " + marvin + " is not alive" )
+ EndSignal( marvin, "OnFailedToPath" )
+ EndSignal( marvin, "OnDeath" )
+
+ // Don't do a job that's already in use or not ready to be used again
+ if ( IsValid( job.user ) || Time() < job.nextUsableTime )
+ return
+
+ // Don't use a barrel put down job if you can'r carrying a barrel
+ if ( job.jobType == "barrel_putdown" && !IsValid( marvin.ai.carryBarrel ) )
+ return
+
+ // If you're carrying a barrel, only do a barrel put down job
+ if ( IsValid( marvin.ai.carryBarrel ) && job.jobType != "barrel_putdown" )
+ return
+
+ OnThreadEnd(
+ function() : ( job )
+ {
+ job.user = null
+ job.nextUsableTime = Time() + JOB_NODE_COOLDOWN_TIME
+ }
+ )
+
+ // Default walk anim
+ MarvinDefaultMoveAnim( marvin )
+
+ // Node gets occupied
+ job.user = marvin
+
+ if ( DEBUG_MARVIN_JOBS )
+ DebugDrawLine( marvin.GetWorldSpaceCenter(), job.node.GetOrigin(), 255, 0, 0, true, 3.0 )
+
+ // Run the job function
+ thread DontDisableJobOnPathFailOrDeath( marvin, job )
+ waitthread file.jobFunctions[ job.jobType ]( marvin, job )
+ if ( IsValid( marvin ) )
+ marvin.Anim_Stop()
+}
+
+void function DontDisableJobOnPathFailOrDeath( entity marvin, MarvinJob job )
+{
+ EndSignal( marvin, "JobStarted" )
+ WaitSignal( marvin, "OnFailedToPath", "OnDeath" )
+ job.nextUsableTime = Time()
+}
+
+
+
+
+
+// ██╗ ██████╗ ██████╗ ███████╗██╗ ██╗███╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗███████╗
+// ██║██╔â•â•â•â–ˆâ–ˆâ•—██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â•â•â•â–ˆâ–ˆâ•‘ ██║████╗ ██║██╔â•â•â•â•â•â•šâ•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘██╔â•â•â•â–ˆâ–ˆâ•—████╗ ██║██╔â•â•â•â•â•
+// ██║██║ ██║██████╔╠█████╗ ██║ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗
+// ██ ██║██║ ██║██╔â•â•â–ˆâ–ˆâ•— ██╔â•â•â• ██║ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚â•â•â•â•â–ˆâ–ˆâ•‘
+// ╚█████╔â•â•šâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”â•â–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ–ˆâ•”╠██║ ╚██████╔â•â–ˆâ–ˆâ•‘ ╚████║╚██████╗ ██║ ██║╚██████╔â•â–ˆâ–ˆâ•‘ ╚████║███████║
+// â•šâ•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â• â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•â•â•šâ•â•â•â•â•â•â•
+
+void function SimpleJobAnims( entity marvin, MarvinJob job )
+{
+ // Get the anims to use for the job
+ array<string> anims
+ switch ( job.jobType )
+ {
+ // Marvin jobs
+ case "welding":
+ anims.append( "mv_idle_weld" )
+ break
+ case "welding_under":
+ anims.append( "mv_weld_under" )
+ anims.append( "mv_weld_under" )
+ anims.append( "mv_weld_under_stumble" )
+ break
+ case "window":
+ anims.append( "mv_idle_wash_window_noloop" )
+ anims.append( "mv_idle_buff_window_noloop" )
+ break
+ case "fightFire":
+ anims.append( "mv_fireman_idle" )
+ anims.append( "mv_fireman_shift" )
+ break
+ case "repair_over_edge":
+ anims.append( "mv_repair_overedge" )
+ anims.append( "mv_repair_overedge" )
+ anims.append( "mv_repair_overedge_stumble" )
+ break
+ case "repair_above":
+ anims.append( "mv_repair_ship_above" )
+ break
+ case "repair_under":
+ anims.append( "mv_repair_under" )
+ anims.append( "mv_repair_under_stumble" )
+ break
+ case "datacards":
+ anims.append( "mv_job_replace_datacards" )
+ break
+
+ // Marvin drone jobs
+ case "drone_welding":
+ anims.append( "dw_jobs_welding_wallpanel" )
+ break
+ case "drone_inspect":
+ anims.append( "inspect1" )
+ anims.append( "inspect2" )
+ break
+ }
+ Assert( anims.len() > 0 )
+
+ if ( IsMarvinWalker( marvin ) )
+ waitthread MarvinRunToAnimStart( marvin, anims[0], job.node )
+ else
+ waitthread MarvinFlyToAnimStart( marvin, anims[0], job.node )
+
+ Signal( marvin, "JobStarted" )
+
+ while ( true )
+ {
+ anims.randomize()
+ foreach ( string anim in anims )
+ {
+ float animLength = marvin.GetSequenceDuration( anim ) // wait anim length because some anims may be looping so we can't wait for them to end
+
+ if ( IsMarvinDrone( marvin ) )
+ thread PlayAnimTeleport( marvin, anim, job.node )
+ else
+ thread PlayAnim( marvin, anim, job.node, null, 0.6 )
+
+ wait animLength
+ }
+ if ( job.tempJob )
+ break
+ }
+}
+
+void function MarvinPicksUpBarrel( entity marvin, MarvinJob job )
+{
+ // Don't try to pick up a barrel if there isn't one nearby
+ if ( !IsValid( job.barrel ) )
+ return
+ if ( Distance( job.node.GetOrigin(), job.barrel.GetOrigin() ) > 25 )
+ return
+
+ EndSignal( job.barrel, "OnDestroy" )
+
+ entity info_target = CreateEntity( "info_target" )
+ DispatchSpawn( info_target )
+
+ OnThreadEnd(
+ function () : ( info_target )
+ {
+ info_target.Destroy()
+ }
+ )
+
+ vector barrelFlatAngles = job.barrel.GetAngles()
+ barrelFlatAngles.x = 0
+ barrelFlatAngles.z = 0
+
+ info_target.SetOrigin( job.barrel.GetOrigin() )
+ info_target.SetAngles( barrelFlatAngles )
+
+ DropToGround( info_target )
+
+ if ( info_target.GetOrigin().z < -MAX_WORLD_COORD )
+ return // Fell through map
+
+ if ( DEBUG_MARVIN_JOBS )
+ thread DrawAnglesForMovingEnt( info_target, 30.0 )
+
+
+ // Go to the barrel
+ MarvinRunToAnimStart( marvin, "mv_carry_barrel_pickup", info_target )
+
+ // Try to pick it up
+ thread PlayAnim( marvin, "mv_carry_barrel_pickup", info_target, null, 0.6 )
+
+ // Wait until animation should pick up the barrel
+ marvin.WaitSignal( "pickup_barrel" )
+
+ // Get attachment info
+ string attachment = "PROPGUN"
+ int attachIndex = marvin.LookupAttachment( attachment )
+ vector attachOrigin = marvin.GetAttachmentOrigin( attachIndex )
+
+ // Make sure the barrel is close when it's time to parent the barrel
+ if ( Distance( attachOrigin, job.barrel.GetOrigin() ) > 25 )
+ {
+ marvin.Anim_Stop()
+ return
+ }
+
+ // Marvin picks up the barrel and carries it
+ thread MarvinCarryBarrel( marvin, job.barrel )
+
+ marvin.WaitSignal( "OnAnimationDone" )
+}
+
+void function MarvinCarryBarrel( entity marvin, entity barrel )
+{
+ marvin.EndSignal( "OnDeath" )
+ marvin.EndSignal( "OnDamaged" )
+ marvin.EndSignal( "putdown_barrel" )
+
+ OnThreadEnd(
+ function () : ( marvin, barrel )
+ {
+ if ( IsValid( barrel ) )
+ {
+ barrel.kv.solid = SOLID_VPHYSICS
+ barrel.ClearParent()
+ barrel.SetOwner( null )
+ EntFireByHandle( barrel, "wake", "", 0, null, null )
+ EntFireByHandle( barrel, "enablemotion", "", 0, null, null )
+ }
+
+ if ( IsAlive( marvin ) )
+ {
+ MarvinDefaultMoveAnim( marvin )
+ marvin.ClearIdleAnim()
+ marvin.ai.carryBarrel = null
+ }
+ }
+ )
+
+ string attachment = "PROPGUN"
+ marvin.SetMoveAnim( "mv_carry_barrel_walk" )
+ marvin.SetIdleAnim( "mv_carry_barrel_idle" )
+ barrel.SetParent( marvin, attachment, false, 0.5 )
+ barrel.SetOwner( marvin )
+
+ barrel.kv.solid = 0 // not solid
+
+ marvin.ai.carryBarrel = barrel
+
+ WaitSignal( marvin, "OnDestroy" )
+}
+
+void function MarvinPutsDownBarrel( entity marvin, MarvinJob job )
+{
+ Assert( IsValid( marvin.ai.carryBarrel ) )
+
+ // Don't place a barrel here if there is already one
+ if ( IsValid( job.barrel ) )
+ {
+ if ( Distance( job.node.GetOrigin(), job.barrel.GetOrigin() ) <= 25 )
+ return
+ }
+
+ EndSignal( marvin.ai.carryBarrel, "OnDestroy" )
+
+ marvin.SetMoveAnim( "mv_carry_barrel_walk" )
+ marvin.SetIdleAnim( "mv_carry_barrel_idle" )
+
+ // Walk to the put down spot
+ MarvinRunToAnimStart( marvin, "mv_carry_barrel_putdown", job.node )
+
+ // Put down the barrel
+ thread PlayAnim( marvin, "mv_carry_barrel_putdown", job.node, null, 0.6 )
+
+ // Wait for release
+ marvin.WaitSignal( "putdown_barrel" )
+
+ marvin.WaitSignal( "OnAnimationDone" )
+}
+
+
+
+
+// ██╗ ██╗████████╗██╗██╗ ██╗████████╗██╗ ██╗
+// ██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•â–ˆâ–ˆâ•‘██║ ██║╚â•â•â–ˆâ–ˆâ•”â•â•â•â•šâ–ˆâ–ˆâ•— ██╔â•
+// ██║ ██║ ██║ ██║██║ ██║ ██║ ╚████╔â•
+// ██║ ██║ ██║ ██║██║ ██║ ██║ ╚██╔â•
+// ╚██████╔╠██║ ██║███████╗██║ ██║ ██║
+// â•šâ•â•â•â•â•â• â•šâ•â• â•šâ•â•â•šâ•â•â•â•â•â•â•â•šâ•â• â•šâ•â• â•šâ•â•
+
+bool function IsMarvinWalker( entity marvin )
+{
+ return GetMarvinType( marvin ) == "marvin_type_walker"
+}
+
+bool function IsMarvinDrone( entity marvin )
+{
+ return GetMarvinType( marvin ) == "marvin_type_drone"
+}
+
+string function GetMarvinType( entity npc )
+{
+ var marvinType = npc.Dev_GetAISettingByKeyField( "marvin_type" )
+ if ( marvinType == null )
+ return "not_marvin"
+
+ return expect string( marvinType )
+}
+
+bool function IsJobNode( entity node )
+{
+ if ( node.GetClassName() == "script_marvin_job" )
+ return true
+ if ( GetEditorClass( node ) == "script_marvin_drone_job" )
+ return true
+ return false
+}
+
+void function MarvinDefaultMoveAnim( entity marvin )
+{
+ if ( IsMarvinWalker( marvin ) )
+ {
+ marvin.SetNPCMoveSpeedScale( 1.0 )
+ marvin.SetMoveAnim( "walk_all" )
+ }
+}
+
+array<MarvinJob> function GetJobsForMarvin( entity marvin )
+{
+ string marvinType = GetMarvinType( marvin )
+
+ // Get jobs this marvin links to, if any, and randomize
+ array<MarvinJob> linkedJobs
+ array<entity> linkedEnts = marvin.GetLinkEntArray()
+ foreach ( entity ent in linkedEnts )
+ {
+ if ( IsJobNode( ent ) )
+ {
+ MarvinJob linkedJob = GetMarvinJobForNode( ent )
+ Assert( IsValid( linkedJob.node ) )
+
+ // Error if we are linking to the wrong type of job node
+ Assert( marvinType == linkedJob.validMarvinType, "npc_marvin at " + marvin.GetOrigin() + " links to a marvin job of the wrong marvin_type" )
+
+ linkedJobs.append( linkedJob )
+ }
+ }
+ linkedJobs.randomize()
+
+ // If marvin was linked to jobs we only consider those
+ if ( marvin.HasKey( "LinkedJobsOnly" ) && marvin.kv.LinkedJobsOnly == "1" )
+ {
+ Assert( linkedJobs.len() > 0, "marvin at " + marvin.GetOrigin() + " has LinkedJobsOnly marked but does not link to any job nodes" )
+ return linkedJobs
+ }
+
+ // Add all jobs within valid distance and randomize
+ array<MarvinJob> jobs
+ foreach ( MarvinJob marvinJob in file.marvinJobs )
+ {
+ if ( marvinType != marvinJob.validMarvinType )
+ continue
+
+ // Don't re-add a job that was linked to
+ if ( linkedJobs.contains( marvinJob ) )
+ continue
+
+ // Teleport nodes are for special case jobs with no nav mesh do son't consider them automatically
+ if ( marvinJob.node.HasKey( "teleport" ) && marvinJob.node.kv.teleport == "1" )
+ continue
+
+ // Only search for jobs within a max distance
+ if ( DistanceSqr( marvinJob.node.GetOrigin(), marvin.GetOrigin() ) <= MAX_JOB_SEARCH_DIST_SQR )
+ jobs.append( marvinJob )
+ }
+
+ // Randomize the order so the marvin does them out of order
+ jobs.randomize()
+
+ // Add the linked jobs to the list, and put them at the beginning of the priority
+ foreach ( MarvinJob linkedJob in linkedJobs )
+ jobs.insert( 0, linkedJob )
+
+ // Debug draw jobs this marvin can take
+ if ( DEBUG_MARVIN_JOBS )
+ {
+ foreach ( MarvinJob job in jobs )
+ {
+ if ( linkedJobs.contains( job ) )
+ DebugDrawLine( marvin.GetOrigin(), job.node.GetOrigin(), 255, 255, 0, true, 10.0 )
+ else
+ DebugDrawLine( marvin.GetOrigin(), job.node.GetOrigin(), 200, 200, 200, true, 10.0 )
+ }
+ }
+
+ return jobs
+}
+
+void function DebugMarvinJobs()
+{
+ while ( true )
+ {
+ foreach ( MarvinJob marvinJob in file.marvinJobs )
+ {
+ string appendText = "AVAILABLE"
+ float timeTillNextUse = marvinJob.nextUsableTime - Time()
+ if ( IsValid( marvinJob.user ) )
+ appendText = "RESERVED"
+ else if ( timeTillNextUse > 0 )
+ appendText = format( "%.1f", timeTillNextUse )
+ DebugDrawText( marvinJob.node.GetOrigin(), marvinJob.jobType + " (" + appendText + ")", true, 0.1 )
+ }
+ wait 0.05
+ }
+}
+
+MarvinJob function GetMarvinJobForNode( entity node )
+{
+ MarvinJob marvinJob
+ foreach ( MarvinJob marvinJob in file.marvinJobs )
+ {
+ if ( marvinJob.node == node )
+ return marvinJob
+ }
+ return marvinJob
+}
+
+entity function CreateBarrel( entity node )
+{
+ return CreatePropPhysics( node.GetModelName(), node.GetOrigin(), node.GetAngles() )
+}
+
+void function MarvinRunToAnimStart( entity marvin, string anim, entity jobNode )
+{
+ if ( jobNode.HasKey( "teleport" ) && jobNode.kv.teleport == "1" )
+ wait 0.1
+ else
+ RunToAnimStartPos( marvin, anim, jobNode )
+}
+
+void function MarvinFlyToAnimStart( entity marvin, string anim, entity jobNode )
+{
+ if ( jobNode.HasKey( "teleport" ) && jobNode.kv.teleport == "1" )
+ {
+ wait 0.1
+ return
+ }
+
+ AnimRefPoint animStartInfo = marvin.Anim_GetStartForRefPoint( anim, jobNode.GetOrigin(), jobNode.GetAngles() )
+
+ marvin.AssaultPoint( animStartInfo.origin )
+ marvin.AssaultSetAngles( animStartInfo.angles, true )
+ marvin.AssaultSetArrivalTolerance( 16 )
+ marvin.WaitSignal( "OnFinishedAssault" )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvins.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvins.gnut
new file mode 100644
index 000000000..fc8b7d1ee
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_marvins.gnut
@@ -0,0 +1,141 @@
+untyped
+
+global function AiMarvins_Init
+
+
+function AiMarvins_Init()
+{
+ FlagInit( "Disable_Marvins" )
+ FlagSet( "Disable_Marvins" )
+
+ level.livingMarvins <- {}
+ AddSpawnCallback( "npc_marvin", LivingMarvinSpawned )
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+}
+
+void function EntitiesDidLoad()
+{
+ if ( IsAutoPopulateEnabled() == false )
+ return
+
+ FlagEnd( "disable_npcs" )
+
+ array<entity> marvin_spawners = GetEntArrayByClass_Expensive( "info_spawnpoint_marvin" )
+
+ if ( !marvin_spawners.len() )
+ return
+
+ for ( ;; )
+ {
+ wait 3
+
+ if ( !Flag( "Disable_Marvins" ) )
+ {
+ if ( TotalLivingMarvins() < 5 )
+ {
+ SpawnRandomMarvin( marvin_spawners )
+ }
+ }
+ }
+}
+
+void function LivingMarvinSpawned( entity self )
+{
+ level.livingMarvins[ self ] <- self
+}
+
+function TotalLivingMarvins()
+{
+ local count = 0
+ foreach ( entity marvin in clone level.livingMarvins )
+ {
+ if ( IsAlive( marvin ) )
+ {
+ count++
+ continue
+ }
+
+ // cleanup dead marvins
+ delete level.livingMarvins[ marvin ]
+ }
+ return count
+}
+
+entity function SpawnRandomMarvin( array<entity> marvin_spawners )
+{
+ marvin_spawners.randomize()
+ entity spawnpoint = marvin_spawners[0] // if no valid spawn is found use this one
+ for ( int i = 0; i < marvin_spawners.len(); i++ )
+ {
+ if ( IsMarvinSpawnpointValid( marvin_spawners[ i ] ) )
+ {
+ spawnpoint = marvin_spawners[ i ]
+ break
+ }
+ }
+
+ entity marvin = SpawnAmbientMarvin( spawnpoint )
+ return marvin
+}
+
+bool function IsMarvinSpawnpointValid( entity spawnpoint )
+{
+ // ensure spawnpoint is not occupied (i.e. would spawn inside another player or object )
+ if ( spawnpoint.IsOccupied() )
+ return false
+
+ bool visible = spawnpoint.IsVisibleToEnemies( TEAM_IMC ) || spawnpoint.IsVisibleToEnemies( TEAM_MILITIA )
+ if ( visible )
+ return false
+
+ return true
+}
+
+entity function SpawnAmbientMarvin( entity spawnpoint )
+{
+ entity npc_marvin = CreateEntity( "npc_marvin" )
+ SetTargetName( npc_marvin, UniqueString( "mp_random_marvin") )
+ npc_marvin.SetOrigin( spawnpoint.GetOrigin() )
+ npc_marvin.SetAngles( spawnpoint.GetAngles() )
+ //npc_marvin.kv.rendercolor = "255 255 255"
+ npc_marvin.kv.health = -1
+ npc_marvin.kv.max_health = -1
+ npc_marvin.kv.spawnflags = 516 // Fall to ground, Fade Corpse
+ //npc_marvin.kv.FieldOfView = 0.5
+ //npc_marvin.kv.FieldOfViewAlert = 0.2
+ npc_marvin.kv.AccuracyMultiplier = 1.0
+ npc_marvin.kv.physdamagescale = 1.0
+ npc_marvin.kv.WeaponProficiency = eWeaponProficiency.GOOD
+
+ Marvin_SetModels( npc_marvin, spawnpoint )
+
+ DispatchSpawn( npc_marvin )
+
+ SetTeam( npc_marvin, TEAM_UNASSIGNED )
+
+ return npc_marvin
+}
+
+function Marvin_SetModels( entity npc_marvin, entity spawnpoint )
+{
+ //default
+ npc_marvin.s.bodytype <- MARVIN_TYPE_WORKER
+
+ // set body and head based on KVP
+ if ( spawnpoint.HasKey( "bodytype" ) )
+ {
+ local bodytype = spawnpoint.GetValueForKey( "bodytype" ).tointeger()
+
+ Assert( bodytype >= MARVIN_TYPE_SHOOTER && bodytype <= MARVIN_TYPE_FIREFIGHTER, "Specified invalid body type index " + bodytype + " for info_spawnpoint_marvin " + spawnpoint + ", Use values from 0-2 instead." )
+
+ npc_marvin.s.bodytype = bodytype
+ }
+
+
+ if ( spawnpoint.HasKey( "headtype" ) )
+ {
+ local headtype = spawnpoint.GetValueForKey( "headtype" )
+ npc_marvin.kv.body = headtype
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_mortar_spectres.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_mortar_spectres.gnut
new file mode 100644
index 000000000..4aa3ac302
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_mortar_spectres.gnut
@@ -0,0 +1,7 @@
+global function MortarSpectreGetSquadFiringPositions
+
+array<vector> function MortarSpectreGetSquadFiringPositions(vector origin, vector testTarget)
+{
+ array< vector > ret
+ return ret
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_mortar_titans.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_mortar_titans.gnut
new file mode 100644
index 000000000..08598808a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_mortar_titans.gnut
@@ -0,0 +1,395 @@
+untyped
+
+global function MortarTitanThink
+global function MortarTitans_Init
+
+global function MortarTitanDeathCleanup
+global function MortarMissileFiredCallback
+global function MoveToMortarPosition
+
+global function MortarTitanKneelToAttack
+
+global function MortarTitanAttack
+
+global function MortarTitanStopAttack
+
+//global function MortarAIWaitToEngage
+
+const float MORTAR_TITAN_ABORT_ATTACK_HEALTH_FRAC = 0.90 // will stop mortar attack if he's health gets below 90% of his current health.
+const float MORTAR_TITAN_POSITION_SEARCH_RANGE = 1024 //3072 // How far away from his spawn point a mortar titan will look for positions to mortar from.
+const float MORTAR_TITAN_ENGAGE_DELAY = 3.0 // How long before a mortar titan start to attack the generator if he's taken damage getting to his mortar position.
+const float MORTAR_TITAN_REENGAGE_DELAY = 7.0 // How long before a mortar titan goes back to attacking the generator after breaking of an attack.
+
+// --------------------------------------------------------------------
+// MORTAR TITAN LOGIC
+// --------------------------------------------------------------------
+
+function MortarTitans_Init()
+{
+ RegisterSignal( "InterruptMortarAttack" )
+ RegisterSignal( "BeginMortarAttack" )
+}
+
+void function MortarTitanDeathCleanup( entity titan )
+{
+ titan.EndSignal( "OnSyncedMeleeVictim" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( titan )
+ {
+ entity animEnt = titan.ai.carryBarrel
+
+ if ( IsValid( animEnt ) )
+ animEnt.Destroy()
+
+ if ( IsAlive( titan ) )
+ {
+ titan.Signal( "InterruptMortarAttack" )
+ titan.Anim_Stop()
+ }
+ }
+ )
+
+ WaitForever()
+}
+
+void function MortarMissileFiredCallback( entity missile, entity weaponOwner )
+{
+ thread MortarMissileThink( missile, weaponOwner )
+}
+
+void function MortarMissileThink( entity missile, entity weaponOwner )
+{
+ Assert( IsValid( missile ) )
+
+ missile.EndSignal( "OnDestroy" )
+ missile.EndSignal( "OnDeath" )
+
+ if ( !IsValid( weaponOwner.ai.mortarTarget ) )
+ return
+
+ entity targetEnt = weaponOwner.ai.mortarTarget
+
+ missile.DamageAliveOnly( true )
+ missile.kv.lifetime = 6.0
+ missile.s.mortar <- true
+ vector startPos = missile.GetOrigin()
+
+ // made a hacky way to get the mortar arc to go higher and still have it hit it's target.
+
+ float dist = Distance( startPos, targetEnt.GetOrigin() )
+
+ // radius tightens over time
+ float radius = GraphCapped( Time() - weaponOwner.ai.spawnTime, 60.0, 180.0, 220, 100 )
+ missile.SetMissileTarget( targetEnt, < RandomFloatRange( -radius, radius ), RandomFloatRange( -radius, radius ), 0 > )
+
+ string sound = "weapon_spectremortar_projectile"
+ if ( weaponOwner.IsTitan() )
+ sound = "Weapon_FlightCore_Incoming_Projectile"
+
+ EmitSoundAtPosition( weaponOwner.GetTeam(), targetEnt.GetOrigin(), sound )
+
+ float homingSpeedMin = 10.0
+ float homingSpeedMax = Graph( dist, 2500, 7000, 400, 200 )
+ float estTravelTime = GraphCapped( dist, 0, 7000, 0, 5 )
+
+ float startTime = Time()
+ while( true )
+ {
+ float frac = min( 1, pow( ( Time() - startTime ) / estTravelTime, 2.0 ) )
+
+ if ( frac > 1.0 )
+ break
+
+ float homingSpeed = GraphCapped( frac, 0, 1, homingSpeedMin, homingSpeedMax )
+
+ missile.SetHomingSpeeds( homingSpeed, 0 )
+
+ wait 0.25
+ }
+
+ missile.ClearMissileTargetPosition()
+}
+
+void function MoveToMortarPosition( entity titan, vector origin, entity target )
+{
+ titan.EndSignal( "OnSyncedMeleeVictim" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ titan.SetLookDistOverride( 320 )
+ titan.SetHearingSensitivity( 0 )
+ titan.EnableNPCMoveFlag( NPCMF_PREFER_SPRINT )
+
+ local animEnt = titan.ai.carryBarrel
+
+ local dir = target.GetOrigin() - origin
+ local dist = dir.Norm()
+ local angles = VectorToAngles( dir )
+ angles.x = 0
+ angles.z = 0
+
+ float frac = TraceLineSimple( origin + < 0, 0, 32 >, origin + < 0, 0, -32 >, titan )
+ if ( frac > 0 && frac < 1 )
+ origin = origin + < 0, 0, 32 > - < 0, 0, 64 * frac >
+
+ animEnt.SetOrigin( origin )
+ animEnt.SetAngles( angles )
+
+ float goalRadius = titan.GetMinGoalRadius()
+
+ OnThreadEnd(
+ function() : ( titan )
+ {
+ if ( !IsValid( titan ) )
+ return
+
+ local classname = titan.GetClassName()
+ titan.DisableLookDistOverride()
+ titan.SetHearingSensitivity( 1 )
+ titan.DisableNPCMoveFlag( NPCMF_PREFER_SPRINT )
+ }
+ )
+
+ local tries = 0
+ while( true )
+ {
+ local dist = Distance( titan.GetOrigin(), origin )
+ if ( dist <= goalRadius * 2 )
+ break
+
+ printt( "Mortar titan moving toward his goal", dist, tries++ )
+ titan.AssaultPoint( origin )
+ titan.AssaultSetGoalRadius( goalRadius )
+
+ local result = WaitSignal( titan, "OnFinishedAssault", "OnEnterGoalRadius" )
+ }
+}
+
+void function MortarTitanKneelToAttack( entity titan )
+{
+ titan.EndSignal( "OnSyncedMeleeVictim" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ entity animEnt = titan.ai.carryBarrel
+ waitthread PlayAnim( titan, "at_mortar_stand2knee", animEnt )
+}
+
+function MortarTitanAttack( entity titan, entity target )
+{
+ titan.EndSignal( "OnSyncedMeleeVictim" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "InterruptMortarAttack" )
+
+ OnThreadEnd(
+ function() : ( titan )
+ {
+ if ( !IsValid( titan ) )
+ return
+
+ if ( "selectedPosition" in titan.s )
+ {
+ titan.s.selectedPosition.inUse = false
+ delete titan.s.selectedPosition
+ }
+
+ if ( IsAlive( titan ) )
+ thread MortarTitanAttackEnd( titan )
+ }
+ )
+
+ titan.ai.mortarTarget = target
+ entity animEnt = titan.ai.carryBarrel
+
+ entity weapon = titan.GetActiveWeapon()
+
+ while ( weapon.IsWeaponOffhand() )
+ {
+ WaitFrame()
+ weapon = titan.GetActiveWeapon()
+ }
+
+ weapon.SetMods( [ "coop_mortar_titan" ] )
+
+ while( true )
+ {
+ waitthread PlayAnim( titan, "at_mortar_knee", animEnt )
+ }
+}
+
+function MortarTitanAttackEnd( entity titan )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ entity animEnt = titan.ai.carryBarrel
+
+ // remove the mortar mod, we do this so that we don't get mortar sound and fx when firing normal
+ entity weapon = titan.GetActiveWeapon()
+
+ while ( weapon.IsWeaponOffhand() )
+ {
+ WaitFrame()
+ weapon = titan.GetActiveWeapon()
+ }
+
+ weapon.SetMods( [] )
+
+ WaitEndFrame() // if I didn't add this PlayAnim, below, would return immediately for some unknown reason.
+
+ if ( IsValid( animEnt ) && IsAlive( titan ) )
+ waitthread PlayAnim( titan, "at_mortar_knee2stand", animEnt )
+}
+
+function MortarTitanStopAttack( titan )
+{
+ titan.Signal( "InterruptMortarAttack" )
+}
+
+function MortarTitanStopAttack_Internal( titan )
+{
+ titan.Signal( "InterruptMortarAttack" )
+ titan.Anim_Stop()
+}
+
+void function MortarAIWaitToEngage( entity titan, float timeFrame, int minDamage = 75 )
+{
+ entity soul = titan.GetTitanSoul()
+ float endtime = Time() + timeFrame
+ int lastHealth = titan.GetHealth() + soul.GetShieldHealth()
+ float tickTime = 1.0
+
+ while ( Time() < endtime )
+ {
+ wait tickTime
+
+ int currentHealth = titan.GetHealth() + soul.GetShieldHealth()
+ if ( lastHealth > ( currentHealth + minDamage ) ) // add minDamage so that we ignore low amounts of damage.
+ {
+ lastHealth = currentHealth
+ endtime = Time() + timeFrame
+ }
+ }
+}
+
+
+/*******************************************************************\
+ MORTAR TITANS
+\*******************************************************************/
+//Function assumes that given Titan is spawned as npc_titan_atlas_tracker_mortar. Changing the Titan's AISettings post-spawn
+//disrupts the Titan's titanfall animations and can result in the Titan landing outside the level.
+void function MortarTitanThink( entity titan, entity generator )
+{
+ titan.EndSignal( "OnSyncedMeleeVictim" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ entity soul = titan.GetTitanSoul()
+ soul.EndSignal( "OnDestroy" )
+
+ titan.ai.carryBarrel = CreateScriptRef()
+ titan.TakeWeaponNow( titan.GetActiveWeapon().GetWeaponClassName() )
+ titan.GiveWeapon( "mp_titanweapon_rocketeer_rocketstream" )
+ titan.SetActiveWeaponByName( "mp_titanweapon_rocketeer_rocketstream" )
+ titan.SetScriptName( "mortar_titan" )
+
+ entity weapon = titan.GetActiveWeapon()
+ weapon.w.missileFiredCallback = MortarMissileFiredCallback
+ thread MortarTitanDeathCleanup( titan )
+
+ WaitTillHotDropComplete( titan )
+
+ float minEngagementDuration = 5
+ StationaryAIPosition ornull mortarPosition = GetRandomStationaryPosition( titan.GetOrigin(), MORTAR_TITAN_POSITION_SEARCH_RANGE, eStationaryAIPositionTypes.MORTAR_TITAN )
+ while ( mortarPosition == null )
+ {
+ // incase all stationary titan positions are in use wait for one to become available
+ wait 5
+ mortarPosition = GetRandomStationaryPosition( titan.GetOrigin(), MORTAR_TITAN_POSITION_SEARCH_RANGE, eStationaryAIPositionTypes.MORTAR_TITAN )
+ }
+
+ expect StationaryAIPosition( mortarPosition )
+
+ ClaimStationaryAIPosition( mortarPosition )
+
+ OnThreadEnd(
+ function() : ( mortarPosition )
+ {
+ // release mortar position when dead
+ ReleaseStationaryAIPosition( mortarPosition )
+ }
+ )
+
+ float minDamage = 75 // so that the titan doesn't care about small amounts of damage.
+
+ while( true )
+ {
+ vector origin = mortarPosition.origin
+
+ float startHealth = float( titan.GetHealth() + soul.GetShieldHealth() )
+ waitthread MoveToMortarPosition( titan, origin, generator )
+
+ if ( startHealth > ( ( titan.GetHealth() + soul.GetShieldHealth() ) + minDamage ) || !titan.IsInterruptable() )
+ {
+ // we took damage getting to the mortar location lets wait until we stop taking damage
+ waitthread MortarAIWaitToEngage( titan, MORTAR_TITAN_ENGAGE_DELAY )
+ continue
+ }
+
+ waitthread MortarTitanKneelToAttack( titan )
+ thread MortarTitanAttack( titan, generator )
+
+ wait minEngagementDuration // aways mortar the target for a while before potentially breaking out
+
+ // wait for interruption
+ waitthread WaitForInteruption( titan )
+
+ MortarTitanStopAttack_Internal( titan )
+
+ // lets wait until we stop taking damage before going back to attacking the generator
+ waitthread MortarAIWaitToEngage( titan, MORTAR_TITAN_REENGAGE_DELAY )
+ }
+}
+
+void function WaitForInteruption( entity titan )
+{
+ Assert( IsNewThread(), "Must be threaded off" )
+
+ titan.EndSignal( "OnSyncedMeleeVictim" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "InterruptMortarAttack" )
+
+ entity soul = titan.GetTitanSoul()
+ soul.EndSignal( "OnDestroy" )
+
+ float playerProximityDistSqr = pow( 256, 2 )
+ float healthBreakOff = ( titan.GetHealth() + soul.GetShieldHealth() ) * MORTAR_TITAN_ABORT_ATTACK_HEALTH_FRAC
+
+ while( true )
+ {
+ if ( IsEnemyWithinDist( titan, playerProximityDistSqr ) )
+ break
+ if ( ( titan.GetHealth() + soul.GetShieldHealth() ) < healthBreakOff )
+ break
+ wait 1
+ }
+}
+
+bool function IsEnemyWithinDist( entity titan, float dist )
+{
+ vector origin = titan.GetOrigin()
+ array<entity> players = GetPlayerArrayOfEnemies_Alive( titan.GetTeam() )
+
+ foreach( player in players )
+ {
+ if ( DistanceSqr( player.GetOrigin(), origin ) < dist )
+ return true
+ }
+
+ return false
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_nuke_titans.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_nuke_titans.gnut
new file mode 100644
index 000000000..0d4b43c92
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_nuke_titans.gnut
@@ -0,0 +1,129 @@
+untyped
+
+global function NukeTitanThink
+
+global function AutoTitan_SelfDestruct
+
+const NUKE_TITAN_PLAYER_DETECT_RANGE = 500
+const NUKE_TITAN_RANGE_CHECK_SLEEP_SECS = 1.0
+
+void function AutoTitan_SelfDestruct( entity titan )
+{
+ if ( titan.ContextAction_IsBusy() )
+ titan.ContextAction_ClearBusy()
+
+ thread TitanEjectPlayer( titan )
+}
+
+void function NukeTitanThink( entity titan, entity generator )
+{
+ //Function assumes that given Titan is spawned as npc_titan_ogre_meteor_nuke. Changing the Titan's AISettings post-spawn
+ //disrupts the Titan's titanfall animations and can result in the Titan landing outside the level.
+ NPC_SetNuclearPayload( titan )
+ AddEntityCallback_OnPostDamaged( titan, AutoTitan_NuclearPayload_PostDamageCallback )
+
+ WaitTillHotDropComplete( titan )
+
+ thread NukeTitanSeekOutGenerator( titan, generator )
+}
+
+
+void function NukeTitanSeekOutGenerator( entity titan, entity generator )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "Doomed" )
+
+ WaitSignal( titan, "FD_ReachedHarvester", "OnFailedToPath" )
+
+ float goalRadius = 100
+ float checkRadiusSqr = 400 * 400
+
+ //array<vector> pos = NavMesh_RandomPositions( generator.GetOrigin(), HULL_TITAN, 5, 250, 350 )
+ array<vector> pos = NavMesh_GetNeighborPositions( generator.GetOrigin(), HULL_TITAN, 5 )
+ pos = ArrayClosestVector( pos, titan.GetOrigin() )
+
+ array<vector> validPos
+ foreach ( point in pos )
+ {
+ if ( DistanceSqr( generator.GetOrigin(), point ) <= checkRadiusSqr && NavMesh_IsPosReachableForAI( titan, point ) )
+ {
+ validPos.append( point )
+ //DebugDrawSphere( point, 32, 255, 0, 0, true, 60 )
+ }
+ }
+
+ int posLen = validPos.len()
+ while( posLen >= 1 )
+ {
+ titan.SetEnemy( generator )
+ thread AssaultOrigin( titan, validPos[0], goalRadius )
+ titan.AssaultSetFightRadius( goalRadius )
+
+ wait 0.5
+
+ if ( DistanceSqr( titan.GetOrigin(), generator.GetOrigin() ) > checkRadiusSqr )
+ continue
+
+ break
+ }
+
+ thread AutoTitan_SelfDestruct( titan )
+}
+
+// intercept damage to nuke titans in damage callback so we can nuke them before death 100% of the time
+void function AutoTitan_NuclearPayload_PostDamageCallback( entity titan, var damageInfo )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ entity titanOwner = titan.GetBossPlayer()
+ if ( IsValid( titanOwner ) )
+ {
+ Assert( titanOwner.IsPlayer() )
+ Assert( GetPlayerTitanInMap( titanOwner ) == titan )
+ return
+ }
+
+ int nuclearPayload = NPC_GetNuclearPayload( titan )
+ if ( nuclearPayload == 0 )
+ return
+
+ if ( !GetDoomedState( titan ) )
+ return
+
+ if ( titan.GetTitanSoul().IsEjecting() )
+ return
+
+ // Nuke eject as soon as the titan enters doom state.
+ if ( !( "doomedStateNukeTriggerHealth" in titan.s ) )
+ {
+ titan.s.doomedStateNukeTriggerHealth <- titan.GetMaxHealth()
+ }
+
+ if ( titan.GetHealth() > titan.s.doomedStateNukeTriggerHealth )
+ {
+ //printt( "titan health:", titan.GetHealth(), "health to nuke:", titan.s.doomedStateNukeTriggerHealth )
+ return
+ }
+
+ printt( "NUKE TITAN DOOMED TRIGGER HEALTH REACHED, NUKING! Health:", titan.s.doomedStateNukeTriggerHealth )
+
+ thread AutoTitan_SelfDestruct( titan )
+}
+
+function AutoTitan_CanDoRangeCheck( autoTitan )
+{
+ if ( !( "nextPlayerTitanRangeCheckTime" in autoTitan.s ) )
+ autoTitan.s.nextPlayerTitanRangeCheckTime <- -1
+
+ if ( Time() < autoTitan.s.nextPlayerTitanRangeCheckTime )
+ {
+ return false
+ }
+ else
+ {
+ autoTitan.s.nextPlayerTitanRangeCheckTime = Time() + NUKE_TITAN_RANGE_CHECK_SLEEP_SECS
+ return true
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_personal_shield.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_personal_shield.gnut
new file mode 100644
index 000000000..f1fbdb80f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_personal_shield.gnut
@@ -0,0 +1,371 @@
+global function AiPersonalShield
+global function ActivatePersonalShield
+const FX_DRONE_SHIELD_WALL_HUMAN = $"P_drone_shield_wall_sm"
+const SHIELD_BREAK_FX = $"P_xo_armor_break_CP"
+const SHIELD_HEALTH = 620
+global const AI_PERSONAL_SHIELD_PAIN_SHIELD_STYLE = true
+const float PERSONAL_SHIELD_HEALTH_FRAC_DAMAGED = 0.5 // below what frac of total health will the personal shield owner want to chatter about shield damage?
+
+struct
+{
+ table<entity, entity> npcVortexSpheres
+} file
+
+
+void function AiPersonalShield()
+{
+ PrecacheParticleSystem( FX_DRONE_SHIELD_WALL_HUMAN )
+ PrecacheParticleSystem( SHIELD_BREAK_FX )
+ AddSyncedMeleeServerCallback( GetSyncedMeleeChooser( "human", "human" ), DisableShieldOnExecution )
+}
+
+void function DisableShieldOnExecution( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity target )
+{
+ if ( !( target in file.npcVortexSpheres ) )
+ return
+
+ entity vortex = file.npcVortexSpheres[ target ]
+ vortex.Destroy()
+}
+
+void function ActivatePersonalShield( entity owner )
+{
+ owner.EndSignal( "OnDeath" )
+ for ( ;; )
+ {
+ waitthread ActivatePersonalShield_Recreate( owner )
+
+ // got stunned? make new shield after awhile
+ wait 15
+ }
+}
+
+void function ShieldProtectsOwnerFromMelee( entity ent, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsAlive( attacker ) )
+ return
+ if ( !attacker.IsPlayer() )
+ return
+ if ( !IsPilot( attacker ) )
+ return
+ entity weapon = DamageInfo_GetWeapon( damageInfo )
+ if ( !IsValid( weapon ) )
+ weapon = attacker.GetActiveWeapon()
+ if ( !IsValid( weapon ) )
+ return
+ var weaponType = weapon.GetWeaponInfoFileKeyField( "weaponType" )
+ if ( weaponType != "melee" )
+ return
+
+ Assert( ent in file.npcVortexSpheres )
+ entity vortexSphere = file.npcVortexSpheres[ ent ]
+
+ float radius = float( vortexSphere.kv.radius )
+ float height = float( vortexSphere.kv.height )
+ float bullet_fov = float( vortexSphere.kv.bullet_fov )
+ float dot = cos( bullet_fov * 0.5 )
+
+ vector origin = vortexSphere.GetOrigin()
+ vector angles = vortexSphere.GetAngles()
+ vector forward = AnglesToForward( angles )
+ int team = vortexSphere.GetTeam()
+
+ if ( ProtectedFromShield( attacker, origin, height, radius, bullet_fov, dot, forward ) )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ StunPushBack( attacker, forward )
+ }
+}
+
+entity function ActivatePersonalShield_Recreate( entity owner )
+{
+ if ( !AI_PERSONAL_SHIELD_PAIN_SHIELD_STYLE )
+ AddEntityCallback_OnDamaged( owner, ShieldProtectsOwnerFromMelee )
+ //------------------------------
+ // Shield vars
+ //------------------------------
+ vector origin = owner.GetOrigin()
+ vector angles = owner.GetAngles() + Vector( 0, 0, 180 )
+
+ float shieldWallRadius = 45 // 90
+ asset shieldFx = FX_DRONE_SHIELD_WALL_HUMAN
+ float wallFOV = DRONE_SHIELD_WALL_FOV_HUMAN
+ float shieldWallHeight = 102
+
+ //------------------------------
+ // Vortex to block the actual bullets
+ //------------------------------
+ entity vortexSphere = CreateEntity( "vortex_sphere" )
+
+ Assert( !( owner in file.npcVortexSpheres ), owner + " already has a shield" )
+ file.npcVortexSpheres[ owner ] <- vortexSphere
+ vortexSphere.kv.spawnflags = SF_ABSORB_BULLETS | SF_BLOCK_OWNER_WEAPON | SF_BLOCK_NPC_WEAPON_LOF | SF_ABSORB_CYLINDER
+ vortexSphere.kv.enabled = 0
+ vortexSphere.kv.radius = shieldWallRadius
+ vortexSphere.kv.height = shieldWallHeight
+ vortexSphere.kv.bullet_fov = wallFOV
+ vortexSphere.kv.physics_pull_strength = 25
+ vortexSphere.kv.physics_side_dampening = 6
+ vortexSphere.kv.physics_fov = 360
+ vortexSphere.kv.physics_max_mass = 2
+ vortexSphere.kv.physics_max_size = 6
+
+ StatusEffect_AddEndless( vortexSphere, eStatusEffect.destroyed_by_emp, 1.0 )
+
+ vortexSphere.SetAngles( angles ) // viewvec?
+ vortexSphere.SetOrigin( origin + Vector( 0, 0, shieldWallRadius - 64 ) )
+ vortexSphere.SetMaxHealth( SHIELD_HEALTH )
+ vortexSphere.SetHealth( SHIELD_HEALTH )
+ SetTeam( vortexSphere, owner.GetTeam() )
+
+ thread PROTO_VortexSlowsPlayers_PersonalShield( owner, vortexSphere )
+
+ DispatchSpawn( vortexSphere )
+
+ EntFireByHandle( vortexSphere, "Enable", "", 0, null, null )
+
+ vortexSphere.SetTakeDamageType( DAMAGE_YES )
+ vortexSphere.ClearInvulnerable() // make particle wall invulnerable to weapon damage. It will still drain over time
+
+ //------------------------------------------
+ // Shield wall fx for visuals/health drain
+ //------------------------------------------
+ entity cpoint = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpoint, UniqueString( "shield_wall_controlpoint" ) )
+ DispatchSpawn( cpoint )
+
+ entity mover = CreateScriptMover()
+ mover.SetOrigin( owner.GetOrigin() )
+ vector moverAngles = owner.GetAngles()
+ mover.SetAngles( AnglesCompose( moverAngles, <0,0,180> ) )
+
+ int fxid = GetParticleSystemIndex( FX_DRONE_SHIELD_WALL_HUMAN )
+ entity shieldWallFX = StartParticleEffectOnEntity_ReturnEntity( mover, fxid, FX_PATTACH_ABSORIGIN_FOLLOW, 0 )
+ shieldWallFX.DisableHibernation()
+ EffectSetControlPointEntity( shieldWallFX, 0, mover )
+
+ //thread DrawArrowOnTag( mover )
+ vortexSphere.e.shieldWallFX = shieldWallFX
+ vector color = GetShieldTriLerpColor( 0.0 )
+
+ cpoint.SetOrigin( color )
+ EffectSetControlPointEntity( shieldWallFX, 1, cpoint )
+ SetVortexSphereShieldWallCPoint( vortexSphere, cpoint )
+
+ #if GRUNTCHATTER_ENABLED
+ // have to do this, vortex shield isn't an entity that works with AddEntityCallback_OnDamaged
+ thread PersonalShieldOwner_ReactsToDamage( owner, vortexSphere )
+ #endif
+
+ //-----------------------
+ // Attach shield to owner
+ //------------------------
+ vortexSphere.SetParent( mover )
+
+ vortexSphere.EndSignal( "OnDestroy" )
+ Assert( IsAlive( owner ) )
+ owner.EndSignal( "OnDeath" )
+ owner.EndSignal( "ArcStunned" )
+ mover.EndSignal( "OnDestroy" )
+ #if MP
+ shieldWallFX.EndSignal( "OnDestroy" )
+ #endif
+
+ OnThreadEnd(
+ function() : ( owner, mover, vortexSphere )
+ {
+ delete file.npcVortexSpheres[ owner ]
+ if ( IsValid( owner ) )
+ {
+ owner.kv.defenseActive = false
+ if ( !AI_PERSONAL_SHIELD_PAIN_SHIELD_STYLE )
+ RemoveEntityCallback_OnDamaged( owner, ShieldProtectsOwnerFromMelee )
+ }
+
+ StopShieldWallFX( vortexSphere )
+
+ if ( IsValid( vortexSphere ) )
+ vortexSphere.Destroy()
+
+ if ( IsValid( mover ) )
+ {
+ //PlayFX( SHIELD_BREAK_FX, mover.GetOrigin(), mover.GetAngles() )
+ mover.Destroy()
+ }
+ }
+ )
+
+ owner.kv.defenseActive = true
+
+ for ( ;; )
+ {
+ Assert( IsAlive( owner ) )
+ UpdateShieldPosition( mover, owner )
+
+ #if MP
+ if ( IsCloaked( owner ) )
+ EntFireByHandle( shieldWallFX, "Stop", "", 0, null, null )
+ else
+ EntFireByHandle( shieldWallFX, "Start", "", 0, null, null )
+ #endif
+ }
+}
+
+#if GRUNTCHATTER_ENABLED
+void function PersonalShieldOwner_ReactsToDamage( entity owner, entity vortexSphere )
+{
+ EndSignal( owner, "OnDeath" )
+ EndSignal( vortexSphere, "OnDestroy" )
+
+ float alertHealth = vortexSphere.GetMaxHealth() * PERSONAL_SHIELD_HEALTH_FRAC_DAMAGED
+
+ while ( vortexSphere.GetHealth() >= alertHealth )
+ wait 0.25
+
+ GruntChatter_TryPersonalShieldDamaged( owner ) //Commenting out to unblock tree. See bug 186062
+}
+#endif
+
+float function GetYawForEnemyOrLKP( entity owner )
+{
+ entity enemy = owner.GetEnemy()
+ if ( !IsValid( enemy ) )
+ return owner.GetAngles().y
+
+ vector ornull lkp = owner.LastKnownPosition( enemy )
+ if ( lkp == null )
+ return owner.GetAngles().y
+
+ expect vector( lkp )
+ vector dif = lkp - owner.GetOrigin()
+ return VectorToAngles( dif ).y
+}
+
+void function UpdateShieldPosition( entity mover, entity owner )
+{
+ mover.NonPhysicsMoveTo( owner.GetOrigin(), 0.1, 0.0, 0.0 )
+ vector angles = owner.EyeAngles()
+ float yaw = angles.y
+ yaw %= 360
+ mover.NonPhysicsRotateTo( <0,yaw,180>, 1.35, 0, 0 )
+
+// float yaw = GetYawForEnemyOrLKP( owner )
+// float boost = sin( Time() * 1.5 ) * 65
+// yaw += boost
+// yaw %= 360
+// mover.NonPhysicsRotateTo( <0,yaw,0>, 0.95, 0, 0 )
+
+ WaitFrame()
+}
+
+void function PROTO_VortexSlowsPlayers_PersonalShield( entity owner, entity vortexSphere )
+{
+ owner.EndSignal( "OnDeath" )
+ vortexSphere.EndSignal( "OnDestroy" )
+
+ float radius = float(vortexSphere.kv.radius )
+ float height = float(vortexSphere.kv.height )
+ float bullet_fov = float( vortexSphere.kv.bullet_fov )
+ float dot = cos( bullet_fov * 0.5 )
+
+ for ( ;; )
+ {
+ vector origin = vortexSphere.GetOrigin()
+ vector angles = vortexSphere.GetAngles()
+ vector forward = AnglesToForward( angles )
+ int team = vortexSphere.GetTeam()
+
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( !IsAlive( player ) )
+ continue
+ if ( player.GetTeam() == team )
+ continue
+ if ( VortexStunCheck_PersonalShield( player, origin, height, radius, bullet_fov, dot, forward ) )
+ {
+ player.p.lastDroneShieldStunPushTime = Time()
+
+ if ( AI_PERSONAL_SHIELD_PAIN_SHIELD_STYLE )
+ {
+ Explosion_DamageDefSimple( damagedef_shield_captain_arc_shield, player.GetOrigin(),owner, owner, player.GetOrigin() )
+ }
+ }
+ }
+ WaitFrame()
+ }
+}
+
+bool function ProtectedFromShield( entity player, vector origin, float height, float radius, float bullet_fov, float dotLimit, vector forward )
+{
+ vector playerOrg = player.GetOrigin()
+ vector dif = Normalize( playerOrg - origin )
+
+ float dot = DotProduct2D( dif, forward )
+ return dot >= dotLimit
+}
+
+bool function VortexStunCheck_PersonalShield( entity player, vector origin, float height, float radius, float bullet_fov, float dot, vector forward )
+{
+ if ( !IsPilot( player ) )
+ return false
+
+ if ( player.IsGodMode() )
+ return false
+
+ if ( AI_PERSONAL_SHIELD_PAIN_SHIELD_STYLE )
+ {
+ if ( Time() - player.p.lastDroneShieldStunPushTime < 1.00 )
+ return false
+ }
+ else
+ {
+ if ( Time() - player.p.lastDroneShieldStunPushTime < 1.75 )
+ return false
+ }
+
+ vector playerOrg = player.GetOrigin()
+ float dist2d = Distance2D( playerOrg, origin )
+
+ if ( dist2d > radius + 5 )
+ return false
+ if ( dist2d < radius - 15 )
+ return false
+
+ float heightOffset = fabs( playerOrg.z - origin.z )
+
+ if ( heightOffset < 0 || heightOffset > height )
+ return false
+
+ if ( !ProtectedFromShield( player, origin, height, radius, bullet_fov, dot, forward ) )
+ return false
+
+ if ( AI_PERSONAL_SHIELD_PAIN_SHIELD_STYLE )
+ {
+ const float VORTEX_STUN_DURATION = 1.0
+ GiveEMPStunStatusEffects( player, VORTEX_STUN_DURATION + 0.5 )
+ float strength = 0.4
+ StatusEffect_AddTimed( player, eStatusEffect.emp, strength, VORTEX_STUN_DURATION, 0.5 )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "flesh_electrical_damage_1p" )
+ }
+ else
+ {
+ StunPushBack( player, forward )
+ }
+
+ return true
+}
+
+void function StunPushBack( entity player, vector forward )
+{
+ const float VORTEX_STUN_DURATION = 1.0
+ GiveEMPStunStatusEffects( player, VORTEX_STUN_DURATION + 0.5 )
+ float strength = 0.4
+ StatusEffect_AddTimed( player, eStatusEffect.emp, strength, VORTEX_STUN_DURATION, 0.5 )
+ thread TempLossOfAirControl( player, VORTEX_STUN_DURATION )
+ vector velocity = forward * 300
+ velocity.z = 400
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "flesh_electrical_damage_1p" )
+ player.SetVelocity( velocity )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_pilots.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_pilots.gnut
new file mode 100644
index 000000000..3c2e36ce0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_pilots.gnut
@@ -0,0 +1,808 @@
+untyped
+
+global const NPC_TITAN_PILOT_PROTOTYPE = 0
+global function AiPilots_Init
+
+global function CaptainThink
+
+
+#if NPC_TITAN_PILOT_PROTOTYPE
+global function NpcPilotCallTitanThink
+global function NpcPilotStopCallTitanThink
+global function NpcPilotCallsInAndEmbarksTitan
+global function NpcPilotRunsToAndEmbarksFallingTitan
+global function NpcPilotCallsInTitan
+global function NpcPilotRunsToEmbarkTitan
+global function NpcPilotEmbarksTitan
+global function NpcPilotDisembarksTitan
+global function NpcPilotBecomesTitan
+global function NpcTitanBecomesPilot
+global function TitanHasNpcPilot
+global function NpcPilotGetPetTitan
+global function NpcPilotSetPetTitan
+#endif
+
+global function NpcSetNextTitanRespawnAvailable
+global function NpcResetNextTitanRespawnAvailable
+
+global function AddCallback_OnNpcTitanBecomesPilot
+global function AddCallback_OnNpcPilotBecomesTitan
+
+global struct NPCPilotStruct
+{
+ bool isValid = false
+
+ int team
+ int spawnflags
+ float accuracy
+ float proficieny
+ float health
+ float physDamageScale
+ string weapon
+ string squadName
+
+ asset modelAsset
+ string title
+
+ bool isInvulnerable
+}
+
+const NPC_NEXT_TITANTIME_RESET = -1
+const NPC_NEXT_TITANTIME_MIN = 45
+const NPC_NEXT_TITANTIME_MAX = 60
+const NPC_NEXT_TITANTIME_INTERUPT = 15
+
+function AiPilots_Init()
+{
+ RegisterSignal( "grenade_throw" )
+ RegisterSignal( "NpcPilotBecomesTitan" )
+ RegisterSignal( "NpcTitanBecomesPilot" )
+ RegisterSignal( "StopCallTitanThink" )
+ RegisterSignal( "NpcTitanRespawnAvailableUpdated" )
+
+ level.onNpcPilotBecomesTitanCallbacks <- []
+ level.onNpcTitanBecomesPilotCallbacks <- []
+
+}
+
+function ScriptCallback_OnNpcPilotBecomesTitan( pilot, titan )
+{
+ local result = { pilot = pilot, titan = titan }
+ Signal( pilot, "NpcPilotBecomesTitan", result )
+ Signal( titan, "NpcPilotBecomesTitan", result )
+
+ foreach ( callbackFunc in level.onNpcPilotBecomesTitanCallbacks )
+ {
+ callbackFunc( pilot, titan )
+ }
+}
+
+function ScriptCallback_OnNpcTitanBecomesPilot( pilot, titan )
+{
+ local result = { pilot = pilot, titan = titan }
+ Signal( pilot, "NpcTitanBecomesPilot", result )
+ Signal( titan, "NpcTitanBecomesPilot", result )
+
+ foreach ( callbackFunc in level.onNpcTitanBecomesPilotCallbacks )
+ {
+ callbackFunc( pilot, titan )
+ }
+}
+
+function AddCallback_OnNpcPilotBecomesTitan( callbackFunc )
+{
+ Assert( "onNpcPilotBecomesTitanCallbacks" in level )
+ AssertParameters( callbackFunc, 2, "pilotNPC, titanNPC" )
+
+ level.onNpcPilotBecomesTitanCallbacks.append( callbackFunc )
+}
+
+function AddCallback_OnNpcTitanBecomesPilot( callbackFunc )
+{
+ Assert( "onNpcTitanBecomesPilotCallbacks" in level )
+ AssertParameters( callbackFunc, 2, "pilotNPC, titanNPC" )
+
+ level.onNpcTitanBecomesPilotCallbacks.append( callbackFunc )
+}
+
+function NpcSetNextTitanRespawnAvailable( npc, time )
+{
+ Assert( "nextTitanRespawnAvailable" in npc.s )
+ npc.s.nextTitanRespawnAvailable = time
+ npc.Signal( "NpcTitanRespawnAvailableUpdated" )
+}
+
+function NpcResetNextTitanRespawnAvailable( npc )
+{
+ Assert( "nextTitanRespawnAvailable" in npc.s )
+ npc.s.nextTitanRespawnAvailable = NPC_NEXT_TITANTIME_RESET
+ npc.Signal( "NpcTitanRespawnAvailableUpdated" )
+}
+
+function NpcPilotStopCallTitanThink( pilot )
+{
+ pilot.Signal( "StopCallTitanThink" )
+}
+
+/************************************************************************************************\
+
+######## #### ## ####### ######## ######## ## ## #### ## ## ## ##
+## ## ## ## ## ## ## ## ## ## ## ### ## ## ##
+## ## ## ## ## ## ## ## ## ## ## #### ## ## ##
+######## ## ## ## ## ## ## ######### ## ## ## ## #####
+## ## ## ## ## ## ## ## ## ## ## #### ## ##
+## ## ## ## ## ## ## ## ## ## ## ### ## ##
+## #### ######## ####### ## ## ## ## #### ## ## ## ##
+
+\************************************************************************************************/
+function CaptainThink( entity npc )
+{
+ npc.EndSignal( "OnDestroy" )
+ npc.EndSignal( "OnDeath" )
+
+ Assert( !( "nextTitanRespawnAvailable" in npc.s ) )
+ Assert( !( "petTitan" in npc.s ) )
+
+ npc.s.petTitan <- null
+ npc.s.nextTitanRespawnAvailable <- null
+
+ //wait for in combat...
+ WaitForNpcInCombat( npc )
+
+ //... before we call in a titan
+ if ( npc.s.nextTitanRespawnAvailable == null )
+ npc.s.nextTitanRespawnAvailable = Time() + RandomFloatRange( 2, 10 )
+
+ WaitEndFrame() //wait a frame for things like petTitan and nextTitanRespawnAvailable to have a chance to be set from custom scripts
+ #if NPC_TITAN_PILOT_PROTOTYPE
+ thread NpcPilotCallTitanThink( npc )
+ #endif
+}
+
+#if NPC_TITAN_PILOT_PROTOTYPE
+
+function NpcPilotCallTitanThink( entity pilot )
+{
+ Assert( pilot.IsNPC() )
+ Assert( IsAlive( pilot ) )
+ Assert ( !pilot.IsTitan() )
+
+ pilot.EndSignal( "OnDestroy" )
+ pilot.EndSignal( "OnDeath" )
+ pilot.Signal( "StopCallTitanThink" )
+ pilot.EndSignal( "StopCallTitanThink" )
+
+
+ string title = pilot.GetTitle() + "'s Titan"
+ local count = 1 //1 titan call in at a time
+
+ while ( true ) //this loop usually only happens once, unless the titan called in is destroyed before the living pilot can get to it
+ {
+ entity titan = NpcPilotGetPetTitan( pilot )
+ if ( !IsAlive( titan ) )
+ {
+ //wait for ready titan
+ waitthread __WaitforTitanCallinReady( pilot )
+
+ //ready to call in - look for a good spot
+ SpawnPointFP spawnPoint
+ while ( true )
+ {
+ wait ( RandomFloatRange( 1, 2 ) )
+
+ //dont do stuff when animating on a parent
+ if ( pilot.GetParent() )
+ continue
+
+ //Don't deploy if too close to an enemy
+ if ( HasEnemyWithinDist( pilot, 300.0 ) )
+ continue
+
+ // DO the opposite - only deploy if has an enemy within this distance
+ // if ( !HasEnemyWithinDist( pilot, 2000.0 ) )
+ // continue
+
+ //don't do stuff if you dont have a spawnPoint
+ spawnPoint = FindSpawnPointForNpcCallin( pilot, TITAN_MEDIUM_AJAX_MODEL, HOTDROP_TURBO_ANIM )
+ if ( !spawnPoint.valid )
+ continue
+
+ break
+ }
+
+ //call in a titan, run to it, and embark
+ //in SP by default, the friendlys do NOT do the beacon tell
+ titan = NpcPilotCallsInAndEmbarksTitan( pilot, spawnPoint.origin, spawnPoint.angles )
+ titan.SetTitle( title )
+ }
+ else
+ {
+ Assert( IsAlive( titan ) )
+
+ if ( HasEnemyRodeo( titan ) )
+ {
+ while ( HasEnemyRodeo( titan ) )
+ {
+ WaitSignal( titan.GetTitanSoul(), "RodeoRiderChanged", "OnDestroy" )
+ }
+
+ wait 4 //don't pop back in immediately
+ }
+
+ if ( !IsAlive( titan ) )
+ continue //the titan didn't make it, lets loop back up and try again
+
+ if ( titan.GetTitanSoul().IsDoomed() )
+ {
+ titan.WaitSignal( "OnDestroy" )
+ continue //the titan didn't make it, lets loop back up and try again
+ }
+
+ //start running to titan as it kneels
+ thread NpcPilotRunsToEmbarkTitan( pilot, titan )
+ thread __TitanKneelsForPilot( pilot, titan )
+ wait 2.0 //wait for titan to be in position
+
+ if ( !IsAlive( titan ) )
+ continue //the titan didn't make it, lets loop back up and try again
+
+ //run to the titan
+ waitthread NpcPilotRunsToEmbarkTitan( pilot, titan )
+
+ if ( !IsAlive( titan ) )
+ continue //the titan didn't make it, lets loop back up and try again
+
+ //embark titan
+ thread NpcPilotEmbarksTitan( pilot, titan )
+ }
+
+ local result = WaitSignal( titan, "NpcPilotBecomesTitan", "OnDeath", "OnDestroy" )
+ if ( result.signal != "NpcPilotBecomesTitan" )
+ continue //the titan didn't make it, lets loop back up and try again
+ }
+}
+
+/************************************************************************************************\
+
+ ###### ### ## ## #### ## ## ######## #### ######## ### ## ##
+## ## ## ## ## ## ## ### ## ## ## ## ## ## ### ##
+## ## ## ## ## ## #### ## ## ## ## ## ## #### ##
+## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
+## ######### ## ## ## ## #### ## ## ## ######### ## ####
+## ## ## ## ## ## ## ## ### ## ## ## ## ## ## ###
+ ###### ## ## ######## ######## #### ## ## ## #### ## ## ## ## ##
+
+\************************************************************************************************/
+
+
+entity function NpcPilotCallsInAndEmbarksTitan( entity pilot, vector origin, vector angles )
+{
+ entity titan = NpcPilotCallsInTitan( pilot, origin, angles )
+ thread NpcPilotRunsToAndEmbarksFallingTitan( pilot, titan )
+
+ return titan
+}
+
+function NpcPilotRunsToAndEmbarksFallingTitan( entity pilot, entity titan )
+{
+ titan.EndSignal( "OnDeath" )
+
+ //wait for it to land
+ waitthread WaitTillHotDropComplete( titan )
+ ShowName( titan )
+
+ if ( !IsAlive( titan ) )
+ return
+ titan.EndSignal( "OnDeath" )
+
+ //titan is alive on land so clean it up on thread end
+ OnThreadEnd(
+ function () : ( titan )
+ {
+ if ( !IsAlive( titan ) )
+ return
+
+ SetStanceStand( titan.GetTitanSoul() )
+
+ //the pilot never made it to embark - lets stand our titan up so he can fight
+ if ( !TitanHasNpcPilot( titan ) )
+ {
+ thread PlayAnimGravity( titan, "at_hotdrop_quickstand" )
+ HideName( titan )
+ }
+ }
+ )
+
+ //if the pilot has died, early out
+ if ( !IsAlive( pilot ) )
+ return
+
+ pilot.EndSignal( "OnDeath" )
+
+ //run to the titan
+ waitthread NpcPilotRunsToEmbarkTitan( pilot, titan )
+
+ //embark titan
+ waitthread NpcPilotEmbarksTitan( pilot, titan )
+}
+
+entity function NpcPilotCallsInTitan( entity pilot, vector origin, vector angles )
+{
+ Assert( !pilot.IsTitan() )
+ Assert( IsAlive( pilot ) )
+ Assert( !NpcPilotGetPetTitan( pilot ) )
+
+ //reset the next titan callin timer
+ NpcResetNextTitanRespawnAvailable( pilot )
+
+ //spawn a titan
+ array<string> settingsArray = GetAllowedTitanAISettings()
+
+ string titanSettings = settingsArray.getrandom()
+ entity titan = CreateNPC( "npc_titan", pilot.GetTeam(), origin, angles )
+ SetSpawnOption_AISettings( titan, titanSettings )
+ DispatchSpawn( titan )
+
+ NpcPilotSetPetTitan( pilot, titan )
+
+ //call it in
+ thread NPCTitanHotdrops( titan, false, "at_hotdrop_drop_2knee_turbo_upgraded" )
+ thread __TitanKneelOrStandAfterDropin( titan, pilot )
+
+ //get the titan ready to be embarked
+ SetStanceKneel( titan.GetTitanSoul() )
+ titan.SetTitle( pilot.GetTitle() + "'s Titan" )
+ UpdateEnemyMemoryFromTeammates( titan )
+
+ return titan
+}
+
+void function __TitanKneelOrStandAfterDropin( entity titan, entity pilot )
+{
+ Assert( IsAlive( titan ) )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ titan.WaitSignal( "TitanHotDropComplete" )
+
+ if ( IsAlive( pilot ) )
+ thread PlayAnimGravity( titan, "at_MP_embark_idle" )
+ //else the titan will automatically stand up
+}
+
+//HACK -> this behavior should be completely in code
+void function NpcPilotRunsToEmbarkTitan( entity pilot, entity titan )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+ pilot.EndSignal( "OnDeath" )
+ pilot.EndSignal( "OnDestroy" )
+
+ pilot.SetNoTarget( true )
+ pilot.Anim_Stop()
+ pilot.DisableNPCMoveFlag( NPCMF_INDOOR_ACTIVITY_OVERRIDE )
+ pilot.EnableNPCMoveFlag( NPCMF_IGNORE_CLUSTER_DANGER_TIME | NPCMF_PREFER_SPRINT )
+ pilot.DisableArrivalOnce( true )
+ bool canMoveAndShoot = pilot.GetCapabilityFlag( bits_CAP_MOVE_SHOOT )
+ pilot.SetCapabilityFlag( bits_CAP_MOVE_SHOOT, false )
+
+ OnThreadEnd(
+ function () : ( pilot, canMoveAndShoot )
+ {
+ if ( !IsAlive( pilot ) )
+ return
+
+ pilot.SetNoTarget( false )
+ pilot.EnableNPCMoveFlag( NPCMF_INDOOR_ACTIVITY_OVERRIDE )
+ pilot.DisableNPCMoveFlag( NPCMF_IGNORE_CLUSTER_DANGER_TIME | NPCMF_PREFER_SPRINT )
+ pilot.SetCapabilityFlag( bits_CAP_MOVE_SHOOT, canMoveAndShoot )
+ }
+ )
+
+ local titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() )
+ local embarkSet = FindBestEmbarkForNpcAnim( pilot, titan )
+ string pilotAnim = GetAnimFromAlias( titanSubClass, embarkSet.animSet.thirdPersonKneelingAlias )
+
+ pilot.ClearAllEnemyMemory()
+ waitthread RunToAnimStartForced_Deprecated( pilot, pilotAnim, titan, "hijack" )
+}
+
+/************************************************************************************************\
+
+ ###### ## ## #### ######## ###### ## ##
+## ## ## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ##
+ ###### ## ## ## ## ## ## #########
+ ## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ## ## ##
+ ###### ### ### #### ## ###### ## ##
+
+\************************************************************************************************/
+function NpcPilotEmbarksTitan( entity pilot, entity titan )
+{
+ Assert( IsAlive( pilot ) )
+ Assert( IsAlive( titan ) )
+ Assert( !pilot.IsTitan() )
+ Assert( titan.IsTitan() )
+
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function () : ( titan, pilot )
+ {
+ if ( IsAlive( titan ) )
+ {
+ if ( titan.ContextAction_IsBusy() )
+ titan.ContextAction_ClearBusy()
+ titan.ClearInvulnerable()
+
+ Assert( !IsAlive( pilot ) )
+ }
+ }
+ )
+
+ local isInvulnerable = pilot.IsInvulnerable()
+ pilot.SetInvulnerable()
+ titan.SetInvulnerable()
+
+ local titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() )
+ local embarkSet = FindBestEmbark( pilot, titan )
+
+ while ( embarkSet == null )
+ {
+ wait 1.0
+ embarkSet = FindBestEmbark( pilot, titan )
+ }
+
+ local pilotAnim = GetAnimFromAlias( titanSubClass, embarkSet.animSet.thirdPersonKneelingAlias )
+ local titanAnim = embarkSet.animSet.titanKneelingAnim
+
+ if ( !titan.ContextAction_IsBusy() ) //might be set from kneeling
+ titan.ContextAction_SetBusy()
+ pilot.ContextAction_SetBusy()
+
+ if ( IsCloaked( pilot ) )
+ pilot.SetCloakDuration( 0, 0, 1.5 )
+
+ //pilot.SetParent( titan, "hijack", false, 0.5 ) //the time is just in case their not exactly at the right starting position
+ EmitSoundOnEntity( titan, embarkSet.audioSet.thirdPersonKneelingAudioAlias )
+ thread PlayAnim( pilot, pilotAnim, titan, "hijack" )
+ waitthread PlayAnim( titan, titanAnim )
+
+ if ( !isInvulnerable )
+ pilot.ClearInvulnerable()
+
+ NpcPilotBecomesTitan( pilot, titan )
+}
+
+entity function NpcPilotDisembarksTitan( entity titan )
+{
+ Assert( titan.IsTitan() )
+ Assert( TitanHasNpcPilot( titan ) )
+
+ entity pilot = NpcTitanBecomesPilot( titan )
+ Assert( !pilot.IsTitan() )
+
+ NpcPilotSetPetTitan( pilot, titan )
+
+ thread __NpcPilotDisembarksTitan( pilot, titan )
+
+ return pilot
+}
+
+function __NpcPilotDisembarksTitan( pilot, titan )
+{
+ expect entity( pilot )
+ expect entity( titan )
+
+ titan.ContextAction_SetBusy()
+ pilot.ContextAction_SetBusy()
+
+ if ( pilot.GetTitle() != "" )
+ {
+ titan.SetTitle( pilot.GetTitle() + "'s Titan" )
+ }
+
+ local isInvulnerable = pilot.IsInvulnerable()
+ pilot.SetInvulnerable()
+ titan.SetInvulnerable()
+
+ local pilot3pAnim, pilot3pAudio, titanDisembarkAnim
+ local titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() )
+ local standing = titan.GetTitanSoul().GetStance() >= STANCE_STANDING // STANCE_STANDING = 2, STANCE_STAND = 3
+
+ if ( standing )
+ {
+ titanDisembarkAnim = "at_dismount_stand"
+ pilot3pAnim = "pt_dismount_" + titanSubClass + "_stand"
+ pilot3pAudio = titanSubClass + "_Disembark_Standing_3P"
+ }
+ else
+ {
+ titanDisembarkAnim = "at_dismount_crouch"
+ pilot3pAnim = "pt_dismount_" + titanSubClass + "_crouch"
+ pilot3pAudio = titanSubClass + "_Disembark_Kneeling_3P"
+ }
+
+// pilot.SetParent( titan, "hijack" )
+ EmitSoundOnEntity( titan, pilot3pAudio )
+ thread PlayAnim( titan, titanDisembarkAnim )
+ waitthread PlayAnim( pilot, pilot3pAnim, titan, "hijack" )
+
+ //pilot.ClearParent()
+ titan.ContextAction_ClearBusy()
+ pilot.ContextAction_ClearBusy()
+ if ( !isInvulnerable )
+ pilot.ClearInvulnerable()
+ titan.ClearInvulnerable()
+
+ if ( !standing )
+ SetStanceKneel( titan.GetTitanSoul() )
+}
+
+void function NpcPilotBecomesTitan( entity pilot, entity titan )
+{
+ Assert( IsAlive( pilot ) )
+ Assert( IsAlive( titan ) )
+ Assert( IsGrunt( pilot ) || IsPilotElite( pilot ) )
+ Assert( titan.IsTitan() )
+
+ entity titanSoul = titan.GetTitanSoul()
+
+ titanSoul.soul.seatedNpcPilot.isValid = true
+
+ titanSoul.soul.seatedNpcPilot.team = pilot.GetTeam()
+ titanSoul.soul.seatedNpcPilot.spawnflags = expect int( pilot.kv.spawnflags )
+ titanSoul.soul.seatedNpcPilot.accuracy = expect float( pilot.kv.AccuracyMultiplier )
+ titanSoul.soul.seatedNpcPilot.proficieny = expect float( pilot.kv.WeaponProficiency )
+ titanSoul.soul.seatedNpcPilot.health = expect float( pilot.kv.max_health )
+ titanSoul.soul.seatedNpcPilot.physDamageScale = expect float( pilot.kv.physdamagescale )
+ titanSoul.soul.seatedNpcPilot.weapon = pilot.GetMainWeapons()[0].GetWeaponClassName()
+ titanSoul.soul.seatedNpcPilot.squadName = expect string( pilot.kv.squadname )
+
+ titanSoul.soul.seatedNpcPilot.modelAsset = pilot.GetModelName()
+ titanSoul.soul.seatedNpcPilot.title = pilot.GetTitle()
+
+ titanSoul.soul.seatedNpcPilot.isInvulnerable = pilot.IsInvulnerable()
+
+ titan.SetTitle( titanSoul.soul.seatedNpcPilot.title )
+
+ thread __TitanPilotRodeoCounter( titan )
+
+ ScriptCallback_OnNpcPilotBecomesTitan( pilot, titan )
+
+ pilot.Destroy()
+}
+
+entity function NpcTitanBecomesPilot( entity titan )
+{
+ Assert( IsValid( titan ) )
+ Assert( titan.IsTitan() )
+
+ entity titanSoul = titan.GetTitanSoul()
+ titanSoul.soul.seatedNpcPilot.isValid = false
+
+ string weapon = titanSoul.soul.seatedNpcPilot.weapon
+ string squadName = titanSoul.soul.seatedNpcPilot.squadName
+ asset model = titanSoul.soul.seatedNpcPilot.modelAsset
+ string title = titanSoul.soul.seatedNpcPilot.title
+ int team = titanSoul.soul.seatedNpcPilot.team
+ vector origin = titan.GetOrigin()
+ vector angles = titan.GetAngles()
+ entity pilot = CreateElitePilot( team, origin, angles )
+
+ SetSpawnOption_Weapon( pilot, weapon )
+ SetSpawnOption_SquadName( pilot, squadName )
+ pilot.SetValueForModelKey( model )
+ DispatchSpawn( pilot )
+ pilot.SetModel( model ) // this is a hack, trying to avoid having a model spawn option because its easy to abuse
+
+ NpcPilotSetPetTitan( pilot, titan )
+ NpcResetNextTitanRespawnAvailable( pilot )
+
+ pilot.kv.spawnflags = titanSoul.soul.seatedNpcPilot.spawnflags
+ pilot.kv.AccuracyMultiplier = titanSoul.soul.seatedNpcPilot.accuracy
+ pilot.kv.WeaponProficiency = titanSoul.soul.seatedNpcPilot.proficieny
+ pilot.kv.health = titanSoul.soul.seatedNpcPilot.health
+ pilot.kv.max_health = titanSoul.soul.seatedNpcPilot.health
+ pilot.kv.physDamageScale = titanSoul.soul.seatedNpcPilot.physDamageScale
+
+ if ( titanSoul.soul.seatedNpcPilot.isInvulnerable )
+ pilot.SetInvulnerable()
+
+ titan.SetOwner( pilot )
+ NPCFollowsNPC( titan, pilot )
+
+ UpdateEnemyMemoryFromTeammates( pilot )
+ thread __TitanStanceThink( pilot, titan )
+
+ ScriptCallback_OnNpcTitanBecomesPilot( pilot, titan )
+
+ return pilot
+}
+
+bool function TitanHasNpcPilot( entity titan )
+{
+ Assert( titan.IsTitan() )
+
+ entity titanSoul = titan.GetTitanSoul()
+ if ( !IsValid( titanSoul ) )
+ return false
+
+ if ( !titanSoul.soul.seatedNpcPilot.isValid )
+ return false
+
+ return true
+}
+
+entity function NpcPilotGetPetTitan( entity pilot )
+{
+ Assert( !pilot.IsTitan() )
+ Assert( "petTitan" in pilot.s )
+
+ if ( !IsAlive( expect entity( pilot.s.petTitan ) ) )
+ return null
+
+ Assert( pilot.s.petTitan.IsTitan() )
+ return expect entity( pilot.s.petTitan )
+}
+
+void function NpcPilotSetPetTitan( entity pilot, entity titan )
+{
+ Assert( !pilot.IsTitan() )
+ Assert( titan.IsTitan() )
+ Assert( "petTitan" in pilot.s )
+
+ pilot.s.petTitan = titan
+ pilot.Signal( "PetTitanUpdated" )
+}
+#endif // NPC_TITAN_PILOT_PROTOTYPE
+
+function __TitanStanceThink( entity pilot, entity titan )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ if ( titan.GetTitanSoul().IsDoomed() )
+ return
+
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "NpcPilotBecomesTitan" )
+
+ WaittillAnimDone( titan ) //wait for disembark anim
+
+ // kneel in certain circumstances
+ while ( IsAlive( pilot ) )
+ {
+ if ( !ChangedStance( titan ) )
+ waitthread TitanWaitsToChangeStance_or_PilotDeath( pilot, titan )
+ }
+
+ if ( titan.GetTitanSoul().GetStance() < STANCE_STANDING )
+ {
+ while ( !TitanCanStand( titan ) )
+ wait 2
+
+ TitanStandUp( titan )
+ }
+}
+
+function TitanWaitsToChangeStance_or_PilotDeath( pilot, titan )
+{
+ pilot.EndSignal( "OnDeath" )
+ pilot.EndSignal( "OnDestroy" )
+
+ TitanWaitsToChangeStance( titan )
+}
+
+/************************************************************************************************\
+
+######## ####### ####### ## ######
+ ## ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ######
+ ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ## ##
+ ## ####### ####### ######## ######
+
+\************************************************************************************************/
+
+function __WaitforTitanCallinReady( entity pilot )
+{
+ pilot.EndSignal( "OnDeath" )
+ pilot.EndSignal( "OnDestroy" )
+
+ //HACK TODO: handle eTitanAvailability.Default vs custom and none, AND ALSO make a way to kill this thread
+
+ while ( true )
+ {
+ if ( pilot.s.nextTitanRespawnAvailable == NPC_NEXT_TITANTIME_RESET )
+ pilot.s.nextTitanRespawnAvailable = Time() + RandomFloatRange( NPC_NEXT_TITANTIME_MIN, NPC_NEXT_TITANTIME_MAX ) //this is just a random number - maybe in the future it will be based on the npc's kills...maybe also on the players if it's a slot
+
+ if ( pilot.s.nextTitanRespawnAvailable <= Time() )
+ break
+
+ float delay = max( pilot.s.nextTitanRespawnAvailable - Time(), 0.1 ) //make sure min delay of 0.1 to account for floating point error
+
+ thread SetSignalDelayed( pilot, "NpcTitanRespawnAvailableUpdated", delay )
+ pilot.WaitSignal( "NpcTitanRespawnAvailableUpdated" )
+
+ //keep looping backup just in case this value changes outside this function, we get an update
+ continue
+ }
+
+ Assert( Time() >= pilot.s.nextTitanRespawnAvailable )
+ Assert( pilot.s.nextTitanRespawnAvailable != NPC_NEXT_TITANTIME_RESET )
+}
+
+function __TitanKneelsForPilot( pilot, titan )
+{
+ expect entity( pilot )
+ expect entity( titan )
+
+ pilot.EndSignal( "OnDeath" )
+ pilot.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function () : ( pilot, titan )
+ {
+ if ( !IsAlive( titan ) )
+ return
+
+ SetStanceStand( titan.GetTitanSoul() )
+
+ //the pilot never made it to embark - lets stand our titan up so he can fight
+ if ( !IsAlive( pilot ) )
+ {
+ thread PlayAnimGravity( titan, "at_hotdrop_quickstand" )
+ HideName( titan )
+ titan.ContextAction_ClearBusy()
+ }
+ }
+ )
+
+ if ( !titan.ContextAction_IsBusy() ) //might be set from kneeling
+ titan.ContextAction_SetBusy()
+ SetStanceKneel( titan.GetTitanSoul() )
+
+ waitthread PlayAnimGravity( titan, "at_MP_stand2knee_straight" )
+ waitthread PlayAnim( titan, "at_MP_embark_idle" )
+}
+
+function HasEnemyRodeo( titan )
+{
+ expect entity( titan )
+
+ if ( !IsAlive( titan ) )
+ return false
+
+ if ( IsValid( GetEnemyRodeoPilot( titan ) ) )
+ return true
+
+ return false
+}
+
+function __TitanPilotRodeoCounter( entity titan )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ while ( true )
+ {
+ while ( !HasEnemyRodeo( titan ) )
+ titan.GetTitanSoul().WaitSignal( "RodeoRiderChanged" )
+
+ wait RandomFloatRange( 3, 6 ) //give some time for debounce in case the rider jumps right off
+ if ( !HasEnemyRodeo( titan ) )
+ continue
+
+ #if NPC_TITAN_PILOT_PROTOTYPE
+ thread NpcPilotDisembarksTitan( titan )
+ return
+ #endif
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_sniper_titans.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_sniper_titans.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_sniper_titans.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_soldiers.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_soldiers.gnut
new file mode 100644
index 000000000..9717c76d9
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_soldiers.gnut
@@ -0,0 +1,787 @@
+untyped
+
+global const RPG_USE_ALWAYS = 2
+
+global const STANDARDGOALRADIUS = 100
+
+global function AiSoldiers_Init
+
+global function MakeSquadName
+global function GetPlayerSpectreSquadName
+global function disable_npcs
+global function disable_new_npcs
+global function Disable_IMC
+global function Disable_MILITIA
+
+global function CommonMinionInit
+global function DisableMinionUsesHeavyWeapons
+global function SetupMinionForRPGs
+global function IsNPCSpawningEnabled
+global function EnableAutoPopulate
+global function DisableAutoPopulate
+global function OnEnemyChanged_MinionSwitchToHeavyArmorWeapon
+global function OnEnemyChanged_MinionUpdateAimSettingsForEnemy
+global function OnEnemyChanged_TryHeavyArmorWeapon
+global function ResetNPCs
+global function IsValidRocketTarget
+global function GetMilitiaTitle
+
+global function AssaultOrigin
+global function SquadAssaultOrigin
+
+global function ClientCommand_SpawnViewGrunt
+
+global function OnSoldierSeeEnemy
+global function TryFriendlyPassingNearby
+
+global function OnSpectreSeeEnemy
+
+global function onlyimc // debug
+global function onlymilitia // debug
+
+global function SetGlobalNPCHealth //debug
+
+
+//=========================================================
+// MP ai soldier
+//
+//=========================================================
+
+struct
+{
+ int militiaTitlesIndex
+ array<string> militiaTitles
+} file
+
+function AiSoldiers_Init()
+{
+ level.COOP_AT_WEAPON_RATES <- {}
+ level.COOP_AT_WEAPON_RATES[ "mp_weapon_rocket_launcher" ] <- 0.5
+ level.COOP_AT_WEAPON_RATES[ "mp_weapon_smr" ] <- 0.4
+ level.COOP_AT_WEAPON_RATES[ "mp_weapon_mgl" ] <- 0.1
+
+ PrecacheSprite( $"sprites/glow_05.vmt" )
+ FlagInit( "disable_npcs" )
+ FlagInit( "Disable_IMC" )
+ FlagInit( "Disable_MILITIA" )
+
+ level.onlySpawn <- null
+
+ level.spectreSpawnStyle <- eSpectreSpawnStyle.MORE_FOR_ENEMY_TITANS
+
+ FlagInit( "AllSpectre" )
+ FlagInit( "AllSpectreIMC" )
+ FlagInit( "AllSpectreMilitia" )
+ FlagInit( "NoSpectreIMC" )
+ FlagInit( "NoSpectreMilitia" )
+
+ RegisterSignal( "OnSendAIToAssaultPoint" )
+
+ InitMilitiaTitles()
+
+ AddCallback_OnClientConnecting( AiSoldiers_InitPlayer )
+
+ if ( GetDeveloperLevel() > 0 )
+ AddClientCommandCallback( "SpawnViewGrunt", ClientCommand_SpawnViewGrunt )
+
+}
+
+bool function ClientCommand_SpawnViewGrunt( entity player, array<string> args )
+{
+ int team = args[0].tointeger()
+ if ( GetDeveloperLevel() < 1 )
+ return true
+
+ vector origin = player.EyePosition()
+ vector angles = player.EyeAngles()
+ vector forward = AnglesToForward( angles )
+ TraceResults result = TraceLine( origin, origin + forward * 2000, player )
+ angles.x = 0
+ angles.z = 0
+
+ entity guy = CreateSoldier( team, result.endPos, angles )
+ DispatchSpawn( guy )
+ return true
+}
+
+// debug commands
+function onlyimc()
+{
+ level.onlySpawn = TEAM_IMC
+ printt( "Only spawning IMC AI" )
+}
+
+// debug commands
+function onlymilitia()
+{
+ level.onlySpawn = TEAM_MILITIA
+ printt( "Only spawning Militia AI" )
+}
+
+//////////////////////////////////////////////////////////
+void function AiSoldiers_InitPlayer( entity player )
+{
+ player.s.next_ai_callout_time <- -1
+
+ string squadName = GetPlayerSpectreSquadName( player )
+ player.p.spectreSquad = squadName
+}
+
+//////////////////////////////////////////////////////////
+string function MakeSquadName( int team, string msg )
+{
+ string teamStr
+
+ if ( team == TEAM_IMC )
+ teamStr = "imc"
+ else if ( team == TEAM_MILITIA )
+ teamStr = "militia"
+ else
+ teamStr = "default"
+
+ return "squad_" + teamStr + msg
+}
+
+//////////////////////////////////////////////////////////
+
+
+//////////////////////////////////////////////////////////
+// common init for grunts and spectres
+void function CommonMinionInit( entity npc )
+{
+ RandomizeHead( npc )
+
+ if ( IsMultiplayer() )
+ {
+ npc.kv.alwaysAlert = 1
+ npc.EnableNPCFlag( NPC_STAY_CLOSE_TO_SQUAD | NPC_NEW_ENEMY_FROM_SOUND )
+ npc.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE )
+ }
+
+ npc.s.cpState <- eNPCStateCP.NONE
+
+ if ( npc.kv.alwaysalert.tointeger() == 1 )
+ npc.SetDefaultSchedule( "SCHED_ALERT_SCAN" )
+}
+
+function SetupMinionForRPGs( entity soldier )
+{
+ soldier.SetEnemyChangeCallback( OnEnemyChanged_MinionSwitchToHeavyArmorWeapon )
+}
+
+
+void function OnSoldierSeeEnemy( entity guy )
+{
+ guy.EndSignal( "OnDeath" )
+
+ if ( NPC_GruntChatterSPEnabled( guy ) )
+ return
+
+ while ( true )
+ {
+ var results = WaitSignal( guy, "OnSeeEnemy" )
+
+ if ( !IsValid( guy ) )
+ return
+
+ TrySpottedCallout( guy, expect entity( results.activator ) )
+ }
+}
+
+void function TryFriendlyPassingNearby( entity grunt )
+{
+ grunt.EndSignal( "OnDeath" )
+
+ if ( NPC_GruntChatterSPEnabled( grunt ) )
+ return
+
+ while ( true )
+ {
+ wait 5
+
+ if ( !IsValid( grunt ) )
+ return
+
+ #if GRUNT_CHATTER_MP_ENABLED
+ // only do this a minute into the match
+ if ( Time() > 60.0 && TryFriendlyCallout( grunt, "pilot", "bc_reactFriendlyPilot" , 500 ) )
+ continue
+ if ( TryFriendlyCallout( grunt, "titan", "bc_reactTitanfallFriendlyArrives" , 500 ) )
+ continue
+ if ( TryFriendlyCallout( grunt, "npc_super_spectre", "bc_reactReaperFriendlyArrives" , 500 ) )
+ continue
+ if ( TryFriendlyCallout( grunt, "npc_frag_drone", "bc_reactTickSpawnFriendly" , 500 ) )
+ continue
+ if ( IsAlive( grunt.GetEnemy() ) )
+ {
+ entity enemy = grunt.GetEnemy()
+ if ( enemy.IsTitan() )
+ PlayGruntChatterMPLine( grunt, "bc_generalCombatTitan" )
+ else
+ PlayGruntChatterMPLine( grunt, "bc_generalCombat" )
+ }
+ else
+ {
+ PlayGruntChatterMPLine( grunt, "bc_generalNonCombat" )
+ }
+ #endif
+ }
+}
+
+#if GRUNT_CHATTER_MP_ENABLED
+bool function TryFriendlyCallout( entity grunt, string npcClassname, string callout, float dist )
+{
+ array<entity> nearbyFriendlies
+ float distSq = dist*dist
+ if ( npcClassname == "pilot" )
+ {
+ array<entity> players = GetPlayerArrayOfTeam_AlivePilots( grunt.GetTeam() )
+ foreach( p in players )
+ {
+ if ( DistanceSqr( p.GetOrigin(), grunt.GetOrigin() ) > distSq )
+ continue
+ nearbyFriendlies.append( p )
+ }
+ }
+ else if ( npcClassname == "titan" )
+ {
+ nearbyFriendlies = GetNPCArrayEx( "npc_titan", grunt.GetTeam(), TEAM_ANY, grunt.GetOrigin(), dist )
+ array<entity> players = GetPlayerArrayOfTeam_Alive( grunt.GetTeam() )
+ foreach( p in players )
+ {
+ if ( !p.IsTitan() )
+ continue
+ if ( DistanceSqr( p.GetOrigin(), grunt.GetOrigin() ) > distSq )
+ continue
+ nearbyFriendlies.append( p )
+ }
+ }
+ else
+ {
+ nearbyFriendlies = GetNPCArrayEx( npcClassname, grunt.GetTeam(), TEAM_ANY, grunt.GetOrigin(), dist )
+ }
+
+ foreach ( friendly in nearbyFriendlies )
+ {
+ if ( !IsAlive( friendly ) )
+ continue
+
+ if ( GetDoomedState( friendly ) )
+ continue
+
+ PlayGruntChatterMPLine( grunt, callout )
+ return true
+ }
+
+ return false
+}
+#endif
+
+void function OnSpectreSeeEnemy( entity guy )
+{
+ guy.EndSignal( "OnDeath" )
+
+ while ( true )
+ {
+ var results = WaitSignal( guy, "OnGainEnemyLOS" )
+
+ TrySpottedCallout( guy, expect entity( results.activator ) )
+ }
+}
+
+
+//////////////////////////////////////////////////////////
+bool function IsValidRocketTarget( entity enemy )
+{
+ return enemy.GetArmorType() == ARMOR_TYPE_HEAVY
+}
+
+//////////////////////////////////////////////////////////
+function DisableMinionUsesHeavyWeapons( entity soldier )
+{
+ soldier.SetEnemyChangeCallback( OnEnemyChanged_MinionUpdateAimSettingsForEnemy )
+}
+
+void function OnEnemyChanged_MinionSwitchToHeavyArmorWeapon( entity soldier )
+{
+ OnEnemyChanged_TryHeavyArmorWeapon( soldier )
+ OnEnemyChanged_MinionUpdateAimSettingsForEnemy( soldier )
+}
+
+//////////////////////////////////////////////////////////
+void function OnEnemyChanged_MinionUpdateAimSettingsForEnemy( entity soldier )
+{
+ SetProficiency( soldier )
+}
+
+
+bool function AssignNPCAppropriateWeaponFromWeapons( entity npc, array<entity> weapons, bool isRocketTarget )
+{
+ // first try to find an appropriate weapon
+ foreach ( weapon in weapons )
+ {
+ bool isAntiTitan = weapon.GetWeaponType() == WT_ANTITITAN
+ if ( isAntiTitan == isRocketTarget )
+ {
+ // found a weapon to use
+ npc.SetActiveWeaponByName( weapon.GetWeaponClassName() )
+ return true
+ }
+ }
+ return false
+}
+
+//////////////////////////////////////////////////////////
+void function OnEnemyChanged_TryHeavyArmorWeapon( entity npc )
+{
+ entity enemy = npc.GetEnemy()
+ if ( !IsAlive( enemy ) )
+ return
+
+ array<entity> weapons = npc.GetMainWeapons()
+
+ // do we have a weapon to switch to?
+ if ( !weapons.len() )
+ return
+
+ entity activeWeapon = npc.GetActiveWeapon()
+ bool isRocketTarget = IsValidRocketTarget( enemy )
+
+ if ( activeWeapon == null )
+ {
+ if ( AssignNPCAppropriateWeaponFromWeapons( npc, weapons, isRocketTarget ) )
+ return
+
+ // if that fails, use the first weapon, so we do consistent behavior
+ npc.SetActiveWeaponByName( weapons[0].GetWeaponClassName() )
+ return
+ }
+
+ bool isActiveWeapon_AntiTitan = activeWeapon.GetWeaponType() == WT_ANTITITAN
+
+ // already using an appropriate weapon?
+ if ( isActiveWeapon_AntiTitan == isRocketTarget )
+ return
+
+ AssignNPCAppropriateWeaponFromWeapons( npc, weapons, isRocketTarget )
+}
+
+const float NPC_CLOSE_DISTANCE_SQR_THRESHOLD = 1000.0 * 1000.0
+
+//////////////////////////////////////////////////////////
+void function TrySpottedCallout( entity guy, entity enemy )
+{
+ if ( !IsAlive( guy ) )
+ return
+
+ if ( !IsAlive( enemy ) )
+ return
+
+ float distanceSqr = DistanceSqr( guy.GetOrigin(), enemy.GetOrigin() )
+ bool isClose = distanceSqr <= NPC_CLOSE_DISTANCE_SQR_THRESHOLD
+
+ if ( enemy.IsTitan() )
+ {
+ if ( IsSpectre( guy ) ) //Spectre callouts
+ {
+ #if SPECTRE_CHATTER_MP_ENABLED
+ PlaySpectreChatterMPLine( guy, "diag_imc_spectre_gs_spotclosetitancall_01" )
+ #else
+ if ( isClose )
+ PlaySpectreChatterToAll( "spectre_gs_spotclosetitancall_01", guy )
+ else
+ PlaySpectreChatterToAll( "spectre_gs_spotfartitan_1_1", guy )
+ #endif
+
+ }
+ else //Grunt callouts
+ {
+ #if GRUNT_CHATTER_MP_ENABLED
+ PlayGruntChatterMPLine( guy, "bc_enemytitanspotcall" )
+ #endif
+ }
+ }
+ else if ( enemy.IsPlayer() )
+ {
+ if ( IsSpectre( guy ) ) //Spectre callouts
+ {
+ #if SPECTRE_CHATTER_MP_ENABLED
+ PlaySpectreChatterMPLine( guy, "diag_imc_spectre_gs_engagepilotenemy_01_1" )
+ #else
+ if ( isClose )
+ PlaySpectreChatterToAll( "spectre_gs_engagepilotenemy_01_1", guy )
+ else
+ PlaySpectreChatterToAll( "spectre_gs_spotenemypilot_01_1", guy )
+ #endif
+ }
+ else //Grunt callouts
+ {
+ #if GRUNT_CHATTER_MP_ENABLED
+ if ( isClose )
+ PlayGruntChatterMPLine( guy, "bc_spotenemypilot" )
+ else
+ PlayGruntChatterMPLine( guy, "bc_engagepilotenemy" )
+ #endif
+ }
+ }
+ else if ( IsSuperSpectre( enemy ) )
+ {
+ if ( !IsSpectre( guy ) ) //Spectre callouts
+ {
+ #if GRUNT_CHATTER_MP_ENABLED
+ PlayGruntChatterMPLine( guy, "bc_reactEnemyReaper" )
+ #endif
+ }
+ }
+ else
+ {
+ if ( !IsSpectre( guy ) ) //Spectre callouts
+ {
+ #if GRUNT_CHATTER_MP_ENABLED
+ PlayGruntChatterMPLine( guy, "bc_reactEnemySpotted" )
+ #endif
+ }
+ }
+}
+
+
+//////////////////////////////////////////////////////////
+string function GetPlayerSpectreSquadName( entity player )
+{
+ return "player" + player.entindex() + "spectreSquad"
+}
+
+
+//////////////////////////////////////////////////////////
+
+string function GetMilitiaTitle()
+{
+ file.militiaTitlesIndex++
+ if ( file.militiaTitlesIndex >= file.militiaTitles.len() )
+ file.militiaTitlesIndex = 0
+
+ return file.militiaTitles[ file.militiaTitlesIndex ]
+}
+
+void function InitMilitiaTitles()
+{
+ file.militiaTitles = [
+ "#NPC_MILITIA_NAME_AND_RANK_0",
+ "#NPC_MILITIA_NAME_AND_RANK_1",
+ "#NPC_MILITIA_NAME_AND_RANK_2",
+ "#NPC_MILITIA_NAME_AND_RANK_3",
+ "#NPC_MILITIA_NAME_AND_RANK_4",
+ "#NPC_MILITIA_NAME_AND_RANK_5",
+ "#NPC_MILITIA_NAME_AND_RANK_6",
+ "#NPC_MILITIA_NAME_AND_RANK_7",
+ "#NPC_MILITIA_NAME_AND_RANK_8",
+ "#NPC_MILITIA_NAME_AND_RANK_9",
+ "#NPC_MILITIA_NAME_AND_RANK_10",
+ "#NPC_MILITIA_NAME_AND_RANK_11",
+ "#NPC_MILITIA_NAME_AND_RANK_12",
+ "#NPC_MILITIA_NAME_AND_RANK_13",
+ "#NPC_MILITIA_NAME_AND_RANK_14",
+ "#NPC_MILITIA_NAME_AND_RANK_15",
+ "#NPC_MILITIA_NAME_AND_RANK_16",
+ "#NPC_MILITIA_NAME_AND_RANK_17",
+ "#NPC_MILITIA_NAME_AND_RANK_18",
+ "#NPC_MILITIA_NAME_AND_RANK_19",
+ "#NPC_MILITIA_NAME_AND_RANK_20",
+ "#NPC_MILITIA_NAME_AND_RANK_21",
+ "#NPC_MILITIA_NAME_AND_RANK_22",
+ "#NPC_MILITIA_NAME_AND_RANK_23",
+ "#NPC_MILITIA_NAME_AND_RANK_24",
+ "#NPC_MILITIA_NAME_AND_RANK_25",
+ "#NPC_MILITIA_NAME_AND_RANK_26",
+ "#NPC_MILITIA_NAME_AND_RANK_27",
+ "#NPC_MILITIA_NAME_AND_RANK_28",
+ "#NPC_MILITIA_NAME_AND_RANK_29",
+ "#NPC_MILITIA_NAME_AND_RANK_30",
+ "#NPC_MILITIA_NAME_AND_RANK_31",
+ "#NPC_MILITIA_NAME_AND_RANK_32",
+ "#NPC_MILITIA_NAME_AND_RANK_33",
+ "#NPC_MILITIA_NAME_AND_RANK_34",
+ "#NPC_MILITIA_NAME_AND_RANK_35",
+ "#NPC_MILITIA_NAME_AND_RANK_36",
+ "#NPC_MILITIA_NAME_AND_RANK_37",
+ "#NPC_MILITIA_NAME_AND_RANK_38",
+ "#NPC_MILITIA_NAME_AND_RANK_39",
+ "#NPC_MILITIA_NAME_AND_RANK_40",
+ "#NPC_MILITIA_NAME_AND_RANK_41",
+ "#NPC_MILITIA_NAME_AND_RANK_42",
+ "#NPC_MILITIA_NAME_AND_RANK_43",
+ "#NPC_MILITIA_NAME_AND_RANK_44",
+ "#NPC_MILITIA_NAME_AND_RANK_45",
+ "#NPC_MILITIA_NAME_AND_RANK_46",
+ "#NPC_MILITIA_NAME_AND_RANK_47",
+ "#NPC_MILITIA_NAME_AND_RANK_48",
+ "#NPC_MILITIA_NAME_AND_RANK_49",
+ "#NPC_MILITIA_NAME_AND_RANK_50",
+ "#NPC_MILITIA_NAME_AND_RANK_51",
+ "#NPC_MILITIA_NAME_AND_RANK_52",
+ "#NPC_MILITIA_NAME_AND_RANK_53",
+ "#NPC_MILITIA_NAME_AND_RANK_54",
+ "#NPC_MILITIA_NAME_AND_RANK_55",
+ "#NPC_MILITIA_NAME_AND_RANK_56",
+ "#NPC_MILITIA_NAME_AND_RANK_57",
+ "#NPC_MILITIA_NAME_AND_RANK_58",
+ "#NPC_MILITIA_NAME_AND_RANK_59",
+ "#NPC_MILITIA_NAME_AND_RANK_60",
+ "#NPC_MILITIA_NAME_AND_RANK_61",
+ "#NPC_MILITIA_NAME_AND_RANK_62",
+ "#NPC_MILITIA_NAME_AND_RANK_63",
+ "#NPC_MILITIA_NAME_AND_RANK_64",
+ "#NPC_MILITIA_NAME_AND_RANK_65",
+ "#NPC_MILITIA_NAME_AND_RANK_66",
+ "#NPC_MILITIA_NAME_AND_RANK_67",
+ "#NPC_MILITIA_NAME_AND_RANK_68",
+ "#NPC_MILITIA_NAME_AND_RANK_69",
+ "#NPC_MILITIA_NAME_AND_RANK_70",
+ "#NPC_MILITIA_NAME_AND_RANK_71",
+ "#NPC_MILITIA_NAME_AND_RANK_72",
+ "#NPC_MILITIA_NAME_AND_RANK_73",
+ "#NPC_MILITIA_NAME_AND_RANK_74",
+ "#NPC_MILITIA_NAME_AND_RANK_75",
+ "#NPC_MILITIA_NAME_AND_RANK_76",
+ "#NPC_MILITIA_NAME_AND_RANK_77",
+ "#NPC_MILITIA_NAME_AND_RANK_78",
+ "#NPC_MILITIA_NAME_AND_RANK_79",
+ "#NPC_MILITIA_NAME_AND_RANK_80",
+ "#NPC_MILITIA_NAME_AND_RANK_81",
+ "#NPC_MILITIA_NAME_AND_RANK_82",
+ "#NPC_MILITIA_NAME_AND_RANK_83",
+ "#NPC_MILITIA_NAME_AND_RANK_84",
+ "#NPC_MILITIA_NAME_AND_RANK_85",
+ "#NPC_MILITIA_NAME_AND_RANK_86",
+ "#NPC_MILITIA_NAME_AND_RANK_87",
+ "#NPC_MILITIA_NAME_AND_RANK_88",
+ "#NPC_MILITIA_NAME_AND_RANK_89",
+ "#NPC_MILITIA_NAME_AND_RANK_90",
+ "#NPC_MILITIA_NAME_AND_RANK_91",
+ "#NPC_MILITIA_NAME_AND_RANK_92",
+ "#NPC_MILITIA_NAME_AND_RANK_93",
+ "#NPC_MILITIA_NAME_AND_RANK_94",
+ "#NPC_MILITIA_NAME_AND_RANK_95"
+ "#NPC_MILITIA_NAME_AND_RANK_96",
+ "#NPC_MILITIA_NAME_AND_RANK_97",
+ "#NPC_MILITIA_NAME_AND_RANK_98",
+ "#NPC_MILITIA_NAME_AND_RANK_99"
+ ]
+
+ file.militiaTitles.randomize()
+ file.militiaTitlesIndex = 0
+}
+
+//////////////////////////////////////////////////////////
+function disable_npcs()
+{
+ FlagSet( "disable_npcs" )
+ printl( "disabling_npcs" )
+ array<entity> guys = GetNPCArray()
+ foreach ( guy in guys )
+ {
+ if ( guy.GetClassName() == "npc_turret_mega" )
+ continue
+ if ( guy.GetClassName() == "npc_turret_sentry" )
+ continue
+ if ( guy.GetClassName() == "npc_titan" )
+ continue
+
+ guy.Destroy()
+ }
+}
+//////////////////////////////////////////////////////////
+// //hack - we want to toggle new AI on and off through the dev menu even though playlist defaults to use them all the time
+function disable_new_npcs()
+{
+ array<entity> guys = GetNPCArray()
+ foreach ( guy in guys )
+ {
+ if ( guy.GetClassName() == "npc_turret_mega" )
+ continue
+ if ( guy.GetClassName() == "npc_turret_sentry" )
+ continue
+ if ( guy.GetClassName() == "npc_titan" )
+ continue
+
+ guy.Destroy()
+ }
+}
+
+function ResetNPCs()
+{
+ array<entity> guys = GetNPCArray()
+ foreach ( guy in guys )
+ {
+ if ( guy.GetClassName() == "npc_turret_mega" )
+ continue
+ if ( guy.GetClassName() == "npc_turret_sentry" )
+ continue
+
+ if ( guy.GetClassName() == "npc_titan" && IsValid( guy.GetTitanSoul() ) )
+ {
+ guy.GetTitanSoul().Destroy()
+ }
+
+ guy.Destroy()
+ }
+}
+
+//////////////////////////////////////////////////////////
+function Disable_IMC()
+{
+ DisableAutoPopulate( TEAM_IMC )
+ printl( "Disable_IMC" )
+ array<entity> guys = GetNPCArray()
+ foreach ( guy in guys )
+ {
+ if ( guy.GetTeam() == TEAM_IMC )
+ guy.Kill_Deprecated_UseDestroyInstead()
+ }
+}
+
+
+//////////////////////////////////////////////////////////
+function Disable_MILITIA()
+{
+ DisableAutoPopulate( TEAM_MILITIA )
+ printl( "Disable_MILITIA" )
+ array<entity> guys = GetNPCArray()
+ foreach ( guy in guys )
+ {
+ if ( guy.GetTeam() == TEAM_MILITIA )
+ guy.Kill_Deprecated_UseDestroyInstead()
+ }
+}
+
+//////////////////////////////////////////////////////////
+function IsNPCSpawningEnabled()
+{
+ if ( Riff_AllowNPCs() != eAllowNPCs.Default )
+ {
+ if ( Riff_AllowNPCs() == eAllowNPCs.None )
+ return false
+
+ return true
+ }
+
+ return true
+}
+
+
+function DisableAutoPopulate( team )
+{
+ switch ( team )
+ {
+ case TEAM_IMC:
+ FlagSet( "Disable_IMC" )
+ break
+
+ case TEAM_MILITIA:
+ FlagSet( "Disable_MILITIA" )
+ break
+
+ default:
+ Assert( 0, "team number " + team + " not setup for autoPopulation.")
+ break
+ }
+}
+
+function EnableAutoPopulate( team )
+{
+ switch ( team )
+ {
+ case TEAM_IMC:
+ FlagClear( "Disable_IMC" )
+ break
+
+ case TEAM_MILITIA:
+ FlagClear( "Disable_MILITIA" )
+ break
+
+ default:
+ Assert( 0, "team number " + team + " not setup for autoPopulation.")
+ break
+ }
+}
+
+//////////////////////////////////////////////////////////
+
+
+function GuyTeleportsOnPathFail( guy, origin )
+{
+ expect entity( guy )
+
+ guy.EndSignal( "OnFailedToPath" )
+
+ local e = {}
+ e.waited <- false
+ OnThreadEnd(
+ function() : ( guy, origin, e )
+ {
+ if ( !IsAlive( guy ) )
+ return
+
+ // wait was cut off
+ if ( !e.waited )
+ guy.SetOrigin( origin )
+ }
+ )
+
+ wait 2
+ e.waited = true
+}
+
+void function SquadAssaultOrigin( array<entity> group, vector origin, float radius = STANDARDGOALRADIUS )
+{
+ foreach ( member in group )
+ {
+ thread AssaultOrigin( member, origin, radius )
+ }
+}
+
+void function AssaultOrigin( entity guy, vector origin, float radius = STANDARDGOALRADIUS )
+{
+ waitthread SendAIToAssaultPoint( guy, origin, <0,0,0>, radius )
+}
+
+void function SendAIToAssaultPoint( entity guy, vector origin, vector angles, float radius = STANDARDGOALRADIUS )
+{
+ Assert( IsAlive( guy ) )
+ guy.Signal( "OnSendAIToAssaultPoint" )
+ guy.Anim_Stop() // in case we were doing an anim already
+ guy.EndSignal( "OnDeath" )
+ guy.EndSignal( "OnSendAIToAssaultPoint" )
+
+ bool allowFlee = guy.GetNPCFlag( NPC_ALLOW_FLEE )
+ bool allowHandSignal = guy.GetNPCFlag( NPC_ALLOW_HAND_SIGNALS )
+
+ OnThreadEnd(
+ function() : ( guy, allowFlee, allowHandSignal )
+ {
+ if ( IsAlive( guy ) )
+ {
+ guy.SetNPCFlag( NPC_ALLOW_FLEE, allowFlee )
+ guy.SetNPCFlag( NPC_ALLOW_HAND_SIGNALS, allowHandSignal )
+ }
+ }
+ )
+
+ guy.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS )
+ guy.AssaultPoint( origin )
+ guy.AssaultSetGoalRadius( radius )
+ guy.WaitSignal( "OnFinishedAssault" )
+
+}
+
+function SetGlobalNPCHealth( healthValue ) //Debug, for trailer team
+{
+ array<entity> npcArray = GetNPCArray()
+
+ foreach ( npc in npcArray )
+ {
+ npc.SetMaxHealth( healthValue )
+ npc.SetHealth( healthValue )
+ }
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_soldiers_mp.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_soldiers_mp.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_soldiers_mp.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_spawn.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_spawn.gnut
new file mode 100644
index 000000000..7e4d2cddf
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_spawn.gnut
@@ -0,0 +1,696 @@
+untyped
+
+global function AiSpawn_Init
+
+global function __GetWeaponModel
+global function AssaultLinkedMoveTarget
+global function AssaultMoveTarget
+global function AutoSquadnameAssignment
+global function CreateArcTitan
+global function CreateAtlas
+global function CreateElitePilot
+global function CreateElitePilotAssassin
+global function CreateFragDrone
+global function CreateFragDroneCan
+global function CreateGenericDrone
+global function CreateGunship
+global function CreateHenchTitan
+global function CreateMarvin
+global function CreateNPC
+global function CreateNPCFromAISettings
+global function CreateNPCTitan
+global function CreateOgre
+global function CreateProwler
+global function CreateRocketDrone
+global function CreateRocketDroneGrunt
+global function CreateShieldDrone
+global function CreateShieldDroneGrunt
+global function CreateSoldier
+global function CreateSpectre
+global function CreateStalker
+global function CreateStryder
+global function CreateSuperSpectre
+global function CreateWorkerDrone
+global function CreateZombieStalker
+global function CreateZombieStalkerMossy
+global function StopAssaultMoveTarget
+
+global const HACK_CAP_BACK1 = $"models/sandtrap/sandtrap_wall_bracket.mdl"
+global const HACK_CAP_BACK2 = $"models/pipes/pipe_modular_grey_bracket_cap.mdl"
+global const HACK_CAP_BACK3 = $"models/lamps/office_lights_hanging_wire.mdl"
+global const HACK_DRONE_BACK1 = $"models/Weapons/ammoboxes/backpack_single.mdl"
+global const HACK_DRONE_BACK2 = $"models/barriers/fence_wire_holder_double.mdl"
+global const DEFAULT_TETHER_RADIUS = 1500
+global const DEFAULT_COVER_BEHAVIOR_CYLINDER_HEIGHT = 512
+
+struct
+{
+ array<string> moveTargetClasses
+} file
+
+void function AiSpawn_Init()
+{
+ PrecacheModel( HACK_CAP_BACK1 )
+ PrecacheModel( HACK_CAP_BACK2 )
+ PrecacheModel( HACK_CAP_BACK3 )
+ PrecacheModel( HACK_DRONE_BACK1 )
+ PrecacheModel( HACK_DRONE_BACK2 )
+ PrecacheModel( TEAM_IMC_GRUNT_MODEL )
+ PrecacheModel( TEAM_IMC_GRUNT_MODEL_LMG )
+ PrecacheModel( TEAM_IMC_GRUNT_MODEL_RIFLE )
+ PrecacheModel( TEAM_IMC_GRUNT_MODEL_ROCKET )
+ PrecacheModel( TEAM_IMC_GRUNT_MODEL_SHOTGUN )
+ PrecacheModel( TEAM_IMC_GRUNT_MODEL_SMG )
+
+ PrecacheModel( TEAM_MIL_GRUNT_MODEL )
+ PrecacheModel( TEAM_MIL_GRUNT_MODEL_LMG )
+ PrecacheModel( TEAM_MIL_GRUNT_MODEL_RIFLE )
+ PrecacheModel( TEAM_MIL_GRUNT_MODEL_ROCKET )
+ PrecacheModel( TEAM_MIL_GRUNT_MODEL_SHOTGUN )
+ PrecacheModel( TEAM_MIL_GRUNT_MODEL_SMG )
+
+ file.moveTargetClasses = [ "info_move_target", "info_move_animation" ]
+ foreach ( movetargetClass in file.moveTargetClasses )
+ {
+ AddSpawnCallbackEditorClass( "info_target", movetargetClass, InitInfoMoveTargetFlags )
+ }
+
+ RegisterSignal( "StopAssaultMoveTarget" )
+ RegisterSignal( "OnFinishedAssaultChain" )
+
+ AiSpawnContent_Init()
+ #if DEV
+ // just to insure that ai settings are being setup properly.
+ InitNpcSettingsFileNamesForDevMenu()
+ SetupSpawnAIButtons( TEAM_MILITIA )
+ AddCallback_EntitiesDidLoad( AiSpawn_EntitiesDidLoad )
+ #endif
+}
+
+void function AiSpawn_EntitiesDidLoad()
+{
+ #if DEV
+ // On load in dev, verify that subclass matches leveled_aisettings. Subclass is being eradicated.
+ foreach ( spawner in GetSpawnerArrayByClassName( "npc_titan" ) )
+ {
+ table spawnerKeyValues = spawner.GetSpawnEntityKeyValues()
+ if ( "model" in spawnerKeyValues )
+ {
+ switch ( spawnerKeyValues.model.tolower() )
+ {
+ case "models/titans/atlas/atlas_titan.mdl":
+ case "models/titans/ogre/ogre_titan.mdl":
+ case "models/titans/stryder/stryder_titan.mdl":
+ CodeWarning( "Titan has deprecated model at " + spawnerKeyValues.origin )
+ break
+ }
+ }
+ }
+
+ foreach ( model in GetEntArrayByClass_Expensive( "prop_dynamic" ) )
+ {
+ switch ( model.GetModelName() )
+ {
+ case $"models/titans/atlas/atlas_titan.mdl":
+ case $"models/titans/ogre/ogre_titan.mdl":
+ case $"models/titans/stryder/stryder_titan.mdl":
+ CodeWarning( "Prop has deprecated model at " + model.GetOrigin() )
+ break
+ }
+ }
+
+ if ( IsSingleplayer() )
+ {
+ foreach ( spawner in GetSpawnerArrayByClassName( "npc_titan" ) )
+ {
+ table kvs = spawner.GetSpawnEntityKeyValues()
+ vector origin = StringToVector( expect string( kvs.origin ) )
+ if ( !( "leveled_aisettings" in kvs ) )
+ {
+ CodeWarning( "Titan Spawner at " + origin + " has no leveled_aisettings" )
+ continue
+ }
+
+ string aiSettings = expect string( kvs.leveled_aisettings )
+ string playerSettings = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "npc_titan_player_settings" ) )
+ string playerModel = expect string( GetPlayerSettingsFieldForClassName( playerSettings, "bodymodel" ) )
+ string npcModel = expect string( kvs.model )
+ if ( npcModel != playerModel )
+ CodeWarning( "Titan spawner at " + origin + " has model " + npcModel + " that does not match player settings model " + playerModel )
+ }
+ }
+
+ #endif
+
+ table<entity, bool> foundSpawners
+ // precache weapons from the AI
+ foreach ( aiSettings in GetAllNPCSettings() )
+ {
+ // any of these spawned in the level?
+ string baseClass = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "BaseClass" ) )
+ array<entity> spawners = GetSpawnerArrayByClassName( baseClass )
+
+ foreach ( spawner in spawners )
+ {
+ if ( spawner in foundSpawners )
+ continue
+ foundSpawners[ spawner ] <- true
+ // this may be set on the entity in leveled
+ table kvs = spawner.GetSpawnEntityKeyValues()
+ if ( !( "subclass" in kvs ) )
+ continue
+
+ string origin = expect string( spawner.GetSpawnEntityKeyValues().origin )
+ string subclass = expect string( spawner.GetSpawnEntityKeyValues().subclass )
+ CodeWarning( "NPC spawner at " + origin + " has subclass " + subclass + ". Replace deprecated subclass key with leveled_aisettings." )
+ }
+ }
+
+}
+
+const ESCALATION_INCOMBAT_TIMEOUT = 180
+const ESCALATION_FRACTION_DEAD = 0.5
+
+
+/************************************************************************************************\
+
+######## #### ######## ### ## ##
+ ## ## ## ## ## ### ##
+ ## ## ## ## ## #### ##
+ ## ## ## ## ## ## ## ##
+ ## ## ## ######### ## ####
+ ## ## ## ## ## ## ###
+ ## #### ## ## ## ## ##
+
+\************************************************************************************************/
+
+//////////////////////////////////////////////////////////
+
+entity function CreateHenchTitan( string titanType, vector origin, vector angles )
+{
+ entity npc = CreateNPCTitan( titanType, TEAM_IMC, origin, angles, [] )
+ string settings = expect string( Dev_GetPlayerSettingByKeyField_Global( titanType, "sp_aiSettingsFile" ) )
+ SetSpawnOption_AISettings( npc, settings )
+ SetSpawnOption_Titanfall( npc )
+ SetSpawnOption_Alert( npc )
+ SetSpawnOption_NPCTitan( npc, TITAN_HENCH )
+ npc.ai.titanSpawnLoadout.setFile = titanType
+ OverwriteLoadoutWithDefaultsForSetFile( npc.ai.titanSpawnLoadout )
+ return npc
+}
+
+entity function CreateAtlas( int team, vector origin, vector angles, array<string> settingsMods = [] )
+{
+ entity npc = CreateNPCTitan( "titan_atlas", team, origin, angles, settingsMods )
+ SetSpawnOption_AISettings( npc, "npc_titan_atlas" )
+ return npc
+}
+
+entity function CreateStryder( int team, vector origin, vector angles, array<string> settingsMods = [] )
+{
+ entity npc = CreateNPCTitan( "titan_stryder", team, origin, angles, settingsMods )
+ SetSpawnOption_AISettings( npc, "npc_titan_stryder" )
+ return npc
+}
+
+entity function CreateOgre( int team, vector origin, vector angles, array<string> settingsMods = [] )
+{
+ entity npc = CreateNPCTitan( "titan_ogre", team, origin, angles, settingsMods )
+ SetSpawnOption_AISettings( npc, "npc_titan_ogre" )
+ return npc
+}
+
+entity function CreateArcTitan( int team, vector origin, vector angles, array<string> settingsMods = [] )
+{
+ entity npc = CreateNPCTitan( "titan_stryder", team, origin, angles, settingsMods )
+ SetSpawnOption_AISettings( npc, "npc_titan_arc" )
+ return npc
+}
+
+entity function CreateNPCTitan( string settings, int team, vector origin, vector angles, array<string> settingsMods = [] )
+{
+ entity npc = CreateEntity( "npc_titan" )
+ npc.kv.origin = origin
+ npc.kv.angles = Vector( 0, angles.y, 0 )
+ npc.kv.teamnumber = team
+ SetTitanSettings( npc.ai.titanSettings, settings, settingsMods )
+ return npc
+}
+
+entity function CreateSpectre( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_spectre", team, origin, angles )
+}
+
+entity function CreateStalker( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_stalker", team, origin, angles )
+}
+
+entity function CreateZombieStalker( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_stalker", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_stalker_zombie" )
+ return npc
+}
+
+entity function CreateZombieStalkerMossy( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_stalker", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_stalker_zombie_mossy" )
+ return npc
+}
+
+entity function CreateSuperSpectre( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_super_spectre", team, origin, angles )
+}
+
+entity function CreateGunship( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_gunship", team, origin, angles )
+}
+
+entity function CreateSoldier( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_soldier", team, origin, angles )
+}
+
+entity function CreateProwler( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_prowler", team, origin, angles )
+}
+
+entity function CreateRocketDroneGrunt( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_soldier", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_soldier_drone_summoner_rocket" )
+ return npc
+}
+
+entity function CreateShieldDroneGrunt( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_soldier", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_soldier_drone_summoner" )
+ return npc
+}
+
+entity function CreateElitePilot( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_pilot_elite", team, origin, angles )
+}
+
+entity function CreateElitePilotAssassin( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_pilot_elite", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_pilot_elite_assassin" )
+ return npc
+}
+
+entity function CreateFragDrone( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_frag_drone", team, origin, angles )
+}
+
+entity function CreateFragDroneCan( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_frag_drone", team, origin, angles )
+ npc.ai.fragDroneArmed = false
+ return npc
+}
+
+entity function CreateRocketDrone( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_drone", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_drone_rocket" )
+ return npc
+}
+
+entity function CreateShieldDrone( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_drone", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_drone_shield" )
+ return npc
+}
+
+entity function CreateGenericDrone( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_drone", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_drone" )
+ return npc
+}
+
+entity function CreateWorkerDrone( int team, vector origin, vector angles )
+{
+ entity npc = CreateNPC( "npc_drone", team, origin, angles )
+ SetSpawnOption_AISettings( npc, "npc_drone_worker" )
+ return npc
+}
+
+entity function CreateMarvin( int team, vector origin, vector angles )
+{
+ return CreateNPC( "npc_marvin", team, origin, angles )
+}
+
+entity function CreateNPC( baseClass, team, origin, angles )
+{
+ entity npc = CreateEntity( expect string( baseClass ) )
+ npc.kv.teamnumber = team
+ npc.kv.origin = origin
+ npc.kv.angles = angles
+
+ return npc
+}
+
+entity function CreateNPCFromAISettings( string aiSettings, int team, vector origin, vector angles )
+{
+ string baseClass = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "BaseClass" ) )
+ entity npc = CreateNPC( baseClass, team, origin, angles )
+ SetSpawnOption_AISettings( npc, aiSettings )
+ return npc
+}
+
+
+
+/************************************************************************************************\
+
+ ###### ####### ## ## ## ## ####### ## ##
+## ## ## ## ### ### ### ### ## ## ### ##
+## ## ## #### #### #### #### ## ## #### ##
+## ## ## ## ### ## ## ### ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ## ## ####
+## ## ## ## ## ## ## ## ## ## ## ###
+ ###### ####### ## ## ## ## ####### ## ##
+
+\************************************************************************************************/
+
+entity function GetTargetOrLink( entity npc )
+{
+ string target = npc.GetTarget_Deprecated()
+ if ( target != "" )
+ return GetEnt( target )
+
+ array<entity> links = npc.GetLinkEntArray()
+ if ( links.len() )
+ return links.getrandom()
+
+ return null
+}
+
+bool function IsMoveTarget( entity ent )
+{
+ if ( !ent.HasKey( "editorclass" ) )
+ return false
+
+ string editorClass = expect string( ent.kv.editorclass )
+ foreach ( moveTargetClass in file.moveTargetClasses )
+ {
+ if ( editorClass == moveTargetClass )
+ return true
+ }
+ return false
+}
+
+bool function IsPotentialThreatTarget( entity ent )
+{
+ if ( !ent.HasKey( "editorclass" ) )
+ return false
+
+ string editorClass = expect string( ent.kv.editorclass )
+ if ( editorClass == "info_potential_threat_target" )
+ return true
+
+ return false
+}
+
+function AssaultLinkedMoveTarget( entity npc )
+{
+ entity ent = GetTargetOrLink( npc )
+ if ( ent == null )
+ return
+ if ( !IsMoveTarget( ent ) )
+ return
+
+ AssaultMoveTarget( npc, ent )
+}
+
+function AssaultMoveTarget( entity npc, entity ent )
+{
+ npc.EndSignal( "OnDeath" )
+ npc.EndSignal( "OnDestroy" )
+ npc.EndSignal( "StopAssaultMoveTarget" )
+ ent.EndSignal( "OnDestroy" )
+
+ Assert( IsMoveTarget( ent ) )
+
+ OnThreadEnd(
+ function() : ( npc )
+ {
+ if ( IsAlive( npc ) )
+ {
+ Signal( npc, "OnFinishedAssaultChain" )
+ }
+ }
+ )
+
+ for ( ;; )
+ {
+ vector origin = ent.GetOrigin()
+ vector angles = ent.GetAngles()
+ float radius = 750
+ float height = 750
+
+ if ( ent.HasKey( "script_predelay" ) )
+ {
+ float time = float( ent.GetValueForKey( "script_predelay" ) )
+ if ( time > 0.0 )
+ wait time
+ }
+
+ if ( ent.HasKey( "script_goal_radius" ) )
+ radius = float( ent.kv.script_goal_radius )
+
+ if ( ent.HasKey( "script_goal_height" ) )
+ height = float( ent.kv.script_goal_height )
+
+ npc.AssaultPointClamped( origin )
+ npc.AssaultSetGoalRadius( radius )
+ npc.AssaultSetGoalHeight( height )
+
+ if ( ent.HasKey( "face_angles" ) && ent.kv.face_angles == "1" )
+ npc.AssaultSetAngles( angles, true )
+
+ if ( ent.HasKey( "script_fight_radius" ) )
+ {
+ float fightRadius = float( ent.kv.script_fight_radius )
+ npc.AssaultSetFightRadius( fightRadius )
+ }
+
+ if ( npc.IsLinkedToEnt( ent ) && ent.HasKey( "unlink" ) && ent.kv.unlink == "1" )
+ npc.UnlinkFromEnt( ent )
+
+ array<entity> entChildren = ent.GetLinkEntArray()
+
+ bool finalDestination = entChildren.len() == 0
+ npc.AssaultSetFinalDestination( finalDestination ) // this doesn't seem to make any difference as far as I can tell. Bug #117062
+
+ if ( ent.HasKey( "clear_potential_threat_pos" ) && int( ent.kv.clear_potential_threat_pos ) == 1 )
+ npc.ClearPotentialThreatPos()
+
+ foreach ( ent in entChildren )
+ {
+ if ( IsPotentialThreatTarget( ent ) )
+ {
+ npc.SetPotentialThreatPos( ent.GetOrigin() )
+ break
+ }
+ }
+
+ table results
+
+ bool skipRunto = ent.HasKey( "skip_runto" ) && int( ent.kv.skip_runto ) == 1
+ if ( !skipRunto )
+ {
+ // If pathing fails we retry waiting for the other signals for 3 seconds.
+ // This solves an issue with npc that failed to path because they where falling.
+
+ const float RETRY_TIME = 3.0
+ float waitStartTime = Time()
+
+ while( true )
+ {
+ // activator, caller, self, signal, value
+ results = WaitSignal( npc, "OnFinishedAssault", "OnEnterGoalRadius", "OnFailedToPath" )
+
+ if ( results.signal != "OnFailedToPath" || waitStartTime + RETRY_TIME < Time() )
+ break
+ }
+ }
+
+ if ( ent.HasKey( "scr_signal" ) )
+ Signal( npc, ent.GetValueForKey( "scr_signal" ), { nodeSignal = results.signal, node = ent } )
+
+ if ( ent.HasKey( "leveled_animation" ) )
+ {
+ string animation = expect string( ent.kv.leveled_animation )
+ Assert( npc.Anim_HasSequence( animation ), "Npc " + npc + " with model " + npc.GetModelName() + " does not have animation sequence " + animation )
+ if ( skipRunto )
+ waitthread PlayAnimTeleport( npc, animation, ent )
+ else
+ waitthread PlayAnimRun( npc, animation, ent, false )
+ }
+
+ if ( ent.HasKey( "scr_flag_set" ) )
+ FlagSet( ent.GetValueForKey( "scr_flag_set" ) )
+
+ if ( ent.HasKey( "scr_flag_clear" ) )
+ FlagClear( ent.GetValueForKey( "scr_flag_clear" ) )
+
+ if ( ent.HasKey( "scr_flag_wait" ) )
+ FlagWait( ent.GetValueForKey( "scr_flag_wait" ) )
+
+ if ( ent.HasKey( "scr_flag_wait_clear" ) )
+ FlagWaitClear( ent.GetValueForKey( "scr_flag_wait_clear" ) )
+
+ if ( ent.HasKey( "path_wait" ) )
+ {
+ float time = float( ent.GetValueForKey( "path_wait" ) )
+ if ( time > 0.0 )
+ wait time
+ }
+
+ if ( ent.HasKey( "disable_assault_on_goal" ) && int( ent.kv.disable_assault_on_goal ) == 1 )
+ npc.DisableBehavior( "Assault" )
+
+ if ( entChildren.len() == 0 )
+ return
+
+ entChildren.randomize()
+ ent = null
+ foreach ( child in entChildren )
+ {
+ if ( IsMoveTarget( child ) )
+ {
+ ent = child
+ break
+ }
+ }
+
+ if ( ent == null )
+ return
+ }
+}
+
+void function StopAssaultMoveTarget( entity npc )
+{
+ npc.Signal( "StopAssaultMoveTarget" )
+}
+
+void function InitInfoMoveTargetFlags( entity infoMoveTarget )
+{
+ #if DEV
+ if ( infoMoveTarget.HasKey( "script_goal_radius" ) )
+ {
+ int radius = int( infoMoveTarget.kv.script_goal_radius )
+ if ( radius < 64 )
+ CodeWarning( "move target at " + infoMoveTarget.GetOrigin() + " had goal radius " + radius + " which is less than minimum 64" )
+ }
+ #endif
+ if ( infoMoveTarget.HasKey( "scr_flag_set" ) )
+ FlagInit( infoMoveTarget.GetValueForKey( "scr_flag_set" ) )
+ if ( infoMoveTarget.HasKey( "scr_flag_clear" ) )
+ FlagInit( infoMoveTarget.GetValueForKey( "scr_flag_clear" ) )
+ if ( infoMoveTarget.HasKey( "scr_flag_wait" ) )
+ FlagInit( infoMoveTarget.GetValueForKey( "scr_flag_wait" ) )
+ if ( infoMoveTarget.HasKey( "scr_flag_wait_clear" ) )
+ FlagInit( infoMoveTarget.GetValueForKey( "scr_flag_wait_clear" ) )
+
+ if ( infoMoveTarget.HasKey( "scr_signal" ) )
+ RegisterSignal( infoMoveTarget.GetValueForKey( "scr_signal" ) )
+}
+
+/************************************************************************************************\
+
+######## ####### ####### ## ######
+ ## ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ######
+ ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ## ##
+ ## ####### ####### ######## ######
+
+\************************************************************************************************/
+asset function __GetWeaponModel( weapon )
+{
+ switch ( weapon )
+ {
+ case "mp_weapon_rspn101":
+ return $"models/weapons/rspn101/r101_ab_01.mdl"//$"models/weapons/rspn101/w_rspn101.mdl" --> this is the one I want to spawn, but I get a vague code error when I try
+ break
+
+ default:
+ Assert( 0, "weapon: " + weapon + " not handled to return a model" )
+ break
+ }
+ unreachable
+}
+
+void function AutoSquadnameAssignment( entity npc )
+{
+ int team = npc.GetTeam()
+ switch ( npc.GetClassName() )
+ {
+ case "npc_turret_sentry":
+ case "npc_turret_mega":
+ case "npc_dropship":
+ case "npc_dropship_hero":
+ return
+ }
+
+ switch ( npc.GetTeam() )
+ {
+ case TEAM_IMC:
+ case TEAM_MILITIA:
+ int index = svGlobal.npcsSpawnedThisFrame_scriptManagedArray[ team ]
+ if ( GetScriptManagedEntArrayLen( index ) == 0 )
+ {
+ thread AutosquadnameAssignment_Thread( index, npc, team )
+ }
+
+ AddToScriptManagedEntArray( index, npc )
+ break
+
+ default:
+ break
+ }
+}
+
+void function AutosquadnameAssignment_Thread( int scriptManagedArrayIndex, entity npc, int team )
+{
+ WaitEndFrame() // wait for everybody to spawn this frame
+
+ array<entity> entities = GetScriptManagedEntArray( scriptManagedArrayIndex )
+ if ( entities.len() <= 1 )
+ {
+ foreach ( npc in entities )
+ {
+ RemoveFromScriptManagedEntArray( scriptManagedArrayIndex, npc )
+ }
+ return
+ }
+
+ string squadName = UniqueString( "autosquad_team_" + team )
+
+ foreach ( npc in entities )
+ {
+ RemoveFromScriptManagedEntArray( scriptManagedArrayIndex, npc )
+ if ( !IsValid( npc ) )
+ continue
+ if ( npc.kv.squadname != "" )
+ continue
+ if ( !IsAlive( npc ) )
+ continue
+ SetSquad( npc, squadName )
+ }
+ Assert( GetScriptManagedEntArrayLen( scriptManagedArrayIndex ) == 0 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_spawn_content.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_spawn_content.gnut
new file mode 100644
index 000000000..c6e7f9f4e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_spawn_content.gnut
@@ -0,0 +1,879 @@
+untyped
+
+global const PROTOTYPE_DEFAULT_TITAN_RODEO_SLOTS = 3 // Todo: remove and set this in titan_base.set
+
+global function CommonNPCOnSpawned
+global function ShouldSpawn
+global function AiSpawnContent_Init
+global function FixupTitle
+
+struct
+{
+ array<string> pilotAntiTitanWeapons
+ int nextAntiTitanWeaponAutoAssign
+} file
+
+function AiSpawnContent_Init()
+{
+ RegisterSignal( "Stop_SimulateGrenadeThink" )
+
+ if ( IsMultiplayer() )
+ file.pilotAntiTitanWeapons = [ "mp_weapon_rocket_launcher" ]
+
+ #if DEV
+ if ( IsSingleplayer() )
+ {
+ array<string> aiSettings = GetAllowedTitanAISettings()
+ foreach ( npcSettings in aiSettings )
+ {
+ asset npcModel = Dev_GetAISettingAssetByKeyField_Global( npcSettings, "DefaultModelName" )
+ string playerSettings = expect string( Dev_GetAISettingByKeyField_Global( npcSettings, "npc_titan_player_settings" ) )
+ asset playerModel = GetPlayerSettingsAssetForClassName( playerSettings, "bodymodel" )
+
+ Assert( npcModel == playerModel, "NPC settings " + npcSettings + " has model " + npcModel + ", which does not match player model for same titan " + playerModel )
+ }
+ }
+ #endif
+}
+
+
+function CommonNPCOnSpawned( entity npc )
+{
+ npc.ai.spawnTime = Time()
+ npc.ai.spawnOrigin = npc.GetOrigin()
+
+ if ( npc.HasKey( "script_goal_radius" ) )
+ {
+ var radius = npc.kv.script_goal_radius
+ if ( radius != null && radius != "" )
+ {
+ npc.AssaultSetGoalRadius( int( radius ) )
+ npc.AssaultPoint( npc.GetOrigin() )
+ }
+ }
+
+ if ( npc.HasKey( "script_goal_height" ) )
+ {
+ var height = npc.kv.script_goal_height
+ if ( height != null && height != "" )
+ {
+ npc.AssaultSetGoalHeight( int( height ) )
+ }
+ }
+
+ if ( npc.HasKey( "script_flag_killed" ) )
+ {
+ thread SetupFlagKilledForNPC( npc )
+ }
+
+ string aisetting = GetDefaultAISetting( npc )
+
+ SetAISettingsWrapper( npc, aisetting )
+
+ Assert( !npc.executedSpawnOptions, npc + " tried to spawn twice?" )
+ npc.executedSpawnOptions = true
+
+ if ( npc.Dev_GetAISettingByKeyField( "SpawnLimping" ) )
+ npc.SetActivityModifier( ACT_MODIFIER_STAGGER, true )
+
+ InitHighlightSettings( npc )
+
+ if ( npc.Dev_GetAISettingByKeyField( "DrawTargetHealthBar" ) )
+ npc.SetValidHealthBarTarget( true )
+
+ // baseclass logic
+ if ( npc.IsTitan() )
+ {
+ if ( !SpawnWithoutSoul( npc ) )
+ {
+ CreateTitanSoul( npc )
+ }
+ }
+
+ if ( npc.GetTeam() <= 0 )
+ {
+ SetTeam( npc, expect int( npc.kv.teamnumber.tointeger() ) )
+ }
+
+ if ( IsMinion( npc ) )
+ {
+ SetupMinionForRPGs( npc )
+ CommonMinionInit( npc )
+ }
+ else if ( !npc.IsTitan() )
+ {
+ npc.SetEnemyChangeCallback( OnEnemyChanged_MinionUpdateAimSettingsForEnemy )
+ }
+
+ if ( npc.GetTitle() == "" )
+ {
+ var title = npc.GetSettingTitle()
+ if ( title != null && title != "" )
+ npc.SetTitle( title )
+ }
+
+ // start alert
+ if ( npc.mySpawnOptions_alert != null )
+ npc.kv.alwaysalert = npc.mySpawnOptions_alert
+ else if ( npc.HasKey( "start_alert") )
+ npc.kv.alwaysalert = npc.kv.start_alert
+
+ npc.kv.physdamagescale = 1.0
+
+ if ( npc.HasKey( "script_buddha" ) && npc.kv.script_buddha == "1" )
+ {
+ npc.ai.buddhaMode = true
+ }
+
+ if ( npc.IsTitan() )
+ {
+ // set boss titan type before setting proficiency for Titans
+ if ( npc.HasKey( "TitanType" ) )
+ {
+ npc.ai.bossTitanType = int( npc.kv.TitanType )
+
+ // this is to get rid of all weak titans
+ if ( npc.ai.bossTitanType == TITAN_WEAK )
+ {
+ CodeWarning( "Spawned weak Titan at " + npc.GetOrigin() + ". Change TitanType to Henchman Titan." )
+
+ // GetSettingsTitle() is causing a script error. Removed for now.
+ // CodeWarning( "Spawned weak Titan " + npc.GetSettingsTitle() + " at " + npc.GetOrigin() + ". Change TitanType to Henchman Titan." )
+ npc.ai.bossTitanType = TITAN_HENCH
+ }
+ }
+
+ if ( npc.HasKey( "disable_vdu" ) )
+ npc.ai.bossTitanVDUEnabled = int( npc.kv.disable_vdu ) == 0
+ }
+
+ // Set proficiency before giving weapons
+ SPMP_UpdateNPCProficiency( npc )
+
+ if ( npc.IsTitan() )
+ {
+ UpdateTitanMinimapStatusToOtherPlayers( npc )
+ CommonNPCTitanOnSpawned( npc )
+// Assert( npc.Dev_GetAISettingByKeyField( "footstep_type" ) != "", "NPC " + npc + " has no footstep type set" )
+ }
+ else
+ {
+ UpdateAIMinimapStatusToOtherPlayers( npc )
+
+ if ( npc.ai.mySpawnOptions_weapon != null )
+ {
+ array<entity> weapons = npc.GetMainWeapons()
+ TakeWeaponsForArray( npc, weapons )
+
+ NPCDefaultWeapon spawnoptionsweapon = expect NPCDefaultWeapon( npc.ai.mySpawnOptions_weapon )
+ npc.GiveWeapon( spawnoptionsweapon.wep, spawnoptionsweapon.mods )
+ }
+
+ entity weapon = npc.GetActiveWeapon()
+ if ( weapon != null && weapon.GetWeaponType() == WT_SIDEARM )
+ npc.DisableNPCFlag( NPC_CROUCH_COMBAT )
+ }
+
+ if ( npc.HasKey( "drop_battery" ) )
+ {
+ npc.ai.shouldDropBattery = (npc.kv.drop_battery == "1")
+ }
+
+ switch ( npc.GetClassName() )
+ {
+ case "npc_bullseye":
+ npc.NotSolid()
+ npc.SetInvulnerable()
+ break
+
+ case "npc_drone":
+ InitMinimapSettings( npc )
+
+ if ( GetMarvinType( npc ) == "marvin_type_drone" )
+ {
+ thread MarvinJobThink( npc )
+ return
+ }
+
+ npc.s.rebooting <- null
+ npc.ai.preventOwnerDamage = true
+ npc.s.lastSmokeDeployTime <- Time()
+
+ thread RunDroneTypeThink( npc )
+
+ switch ( GetDroneType( npc ) )
+ {
+ case "drone_type_engineer_combat":
+ npc.kv.rendercolor = "0 0 0"
+ break
+
+ case "drone_type_engineer_shield":
+ npc.kv.rendercolor = "255 255 255"
+ break
+ }
+ break
+
+ case "npc_dropship":
+ npc.SetSkin( 1 ) //Use skin where the lights are on for dropship.
+ npc.EnableRenderAlways()
+ npc.SetAimAssistAllowed( false )
+ //npc.kv.CollisionGroup = TRACE_COLLISION_GROUP_BLOCK_WEAPONS
+ AddAnimEvent( npc, "dropship_warpout", WarpoutEffect )
+
+ InitLeanDropship( npc )
+ break
+
+
+ case "npc_frag_drone":
+ MakeSuicideSpectre( npc )
+ break
+
+ case "npc_gunship":
+ InitMinimapSettings( npc )
+ EmitSoundOnEntity( npc, SOUND_GUNSHIP_HOVER )
+
+ npc.ai.preventOwnerDamage = true
+ npc.s.rebooting <- null
+ npc.s.plantedMinesManagedEntArrayID <- CreateScriptManagedEntArray()
+
+ npc.kv.crashOnDeath = false
+ //npc.kv.secondaryWeaponName = "mp_weapon_gunship_missile"
+
+ EnableLeeching( npc )
+ npc.SetUsableByGroup( "enemies pilot" )
+
+ thread GunshipThink( npc )
+ break
+
+ case "npc_marvin":
+ asset model = npc.GetModelName()
+ npc.EnableNPCFlag( NPC_DISABLE_SENSING ) // don't do traces to look for enemies or players
+ thread MarvinFace( npc )
+ thread MarvinJobThink( npc )
+ break
+
+ case "npc_pilot_elite":
+ npc.kv.physdamagescale = 1.0
+ npc.kv.WeaponProficiency = eWeaponProficiency.VERYGOOD
+ break
+
+ case "npc_prowler":
+ npc.kv.disengageEnemyDist = 1500
+ npc.DisableNPCFlag( NPC_ALLOW_FLEE ) //HACK until we get a way to make last guy not run away and hide
+ //SetSquad( npc, spawnOptions.squadName ) //not sure why this is here - jake had it in his original spawn func, so I'm keeping it
+ //SetNPCSquadMode( spawnOptions.squadName, SQUAD_MODE_MULTIPRONGED_ATTACK )
+ break
+
+ case "npc_soldier":
+
+ InitMinimapSettings( npc )
+
+ SetHumanRagdollImpactTable( npc )
+
+ npc.EnableNPCFlag( NPC_CROUCH_COMBAT )
+
+ thread OnSoldierSeeEnemy( npc )
+ thread TryFriendlyPassingNearby( npc )
+
+ int team = npc.GetTeam()
+
+ //grunt specific
+ npc.SetDoFaceAnimations( true ) //HACK: assumption that militia are the only grunt models with faces ( will need a better thing for R2 )
+
+ bool alreadyGaveASecondary = false;
+ entity weapon = npc.GetActiveWeapon()
+ string weaponSubClass
+ if ( weapon )
+ weaponSubClass = string( weapon.GetWeaponInfoFileKeyField( "weaponSubClass" ) )
+
+ #if SP
+ if ( weaponSubClass == "sniper" )
+ {
+ if ( AssignDefaultNPCSidearm( npc ) )
+ alreadyGaveASecondary = true
+ }
+ #endif
+
+ if ( !alreadyGaveASecondary && SP_GetPilotAntiTitanWeapon( npc ) == null )
+ TryAutoAssignAntiTitanWeapon( npc )
+
+ if ( npc.Dev_GetAISettingByKeyField( "PersonalShield" ) != null )
+ {
+ npc.DisableNPCFlag( NPC_ALLOW_FLEE | NPC_ALLOW_HAND_SIGNALS | NPC_USE_SHOOTING_COVER | NPC_CROUCH_COMBAT )
+ thread ActivatePersonalShield( npc )
+ }
+
+ if ( npc.ai.droneSpawnAISettings != "" )
+ {
+ thread DroneGruntThink( npc, npc.ai.droneSpawnAISettings )
+ }
+
+ AssignGruntModelForWeaponClass( npc, weapon, weaponSubClass )
+
+ break
+
+ case "npc_spectre":
+ InitMinimapSettings( npc )
+ thread OnSpectreSeeEnemy( npc )
+
+ if ( IsMultiplayer() )
+ {
+ npc.EnableNPCFlag( NPC_CROUCH_COMBAT )
+ //Only enable spectre hacking if the playlist var is enabled
+ if ( ( npc.GetTeam() == TEAM_IMC || npc.GetTeam() == TEAM_MILITIA ) && GetCurrentPlaylistVarInt( "enable_spectre_hacking", 0 ) == 1 )
+ {
+ EnableLeeching( npc )
+ npc.SetUsableByGroup( "enemies pilot" )
+ }
+ }
+ else
+ {
+ EnableLeeching( npc )
+ npc.SetUsableByGroup( "enemies pilot" )
+
+ if ( npc.HasKey( "carrying_battery" ) )
+ {
+ if ( npc.kv.carrying_battery == "1" )
+ {
+ thread NPCCarriesBattery( npc )
+ }
+ }
+ }
+
+ if ( SP_GetPilotAntiTitanWeapon( npc ) == null )
+ TryAutoAssignAntiTitanWeapon( npc )
+ break
+
+ case "npc_stalker":
+ InitMinimapSettings( npc )
+
+ if ( IsSingleplayer() && npc.kv.squadname != "" )
+ SetNPCSquadMode( npc.kv.squadname, SQUAD_MODE_MULTIPRONGED_ATTACK )
+
+ break
+
+ case "npc_super_spectre":
+
+ InitMinimapSettings( npc )
+
+ npc.GiveOffhandWeapon( "mp_weapon_spectre_spawner", 0 )
+
+ DisableLeeching( npc )
+
+ npc.SetCapabilityFlag( bits_CAP_NO_HIT_SQUADMATES, false )
+
+ npc.ai.preventOwnerDamage = true
+
+ npc.SetDeathNotifications( true )
+
+ AddAnimEvent( npc, "SuperSpectre_OnGroundSlamImpact", SuperSpectre_OnGroundSlamImpact )
+ AddAnimEvent( npc, "SuperSpectre_OnGroundLandImpact", SuperSpectre_OnGroundLandImpact )
+
+ thread SuperSpectreThink( npc )
+
+ SuperSpectreIntro( npc )
+ break
+
+
+ case "npc_titan":
+ InitMinimapSettings( npc )
+
+ // used so the titan can stand/kneel without cutting off functionality
+ npc.s.standQueued <- false
+ npc.ai.preventOwnerDamage = true
+ if ( IsMultiplayer() )
+ {
+ npc.e.hasDefaultEnemyHighlight = true
+ SetDefaultMPEnemyHighlight( npc )
+ }
+ break
+
+
+ case "npc_turret_mega":
+ InitMinimapSettings( npc )
+ npc.EnableNPCFlag( NPC_AIM_DIRECT_AT_ENEMY )
+ npc.SetAimAssistAllowed( false )
+ #if R1_VGUI_MINIMAP
+ npc.Minimap_SetDefaultMaterial( GetMinimapMaterial( "turret_neutral" ) )
+ npc.Minimap_SetFriendlyMaterial( GetMinimapMaterial( "turret_friendly" ) )
+ npc.Minimap_SetEnemyMaterial( GetMinimapMaterial( "turret_enemy" ) )
+ npc.Minimap_SetBossPlayerMaterial( GetMinimapMaterial( "turret_friendly" ) )
+ #endif
+ break
+
+ case "npc_turret_sentry":
+ InitMinimapSettings( npc )
+ npc.SetAimAssistAllowed( false )
+ break
+
+ }
+
+ thread AssaultLinkedMoveTarget( npc )
+
+ FixupTitle( npc )
+ #if DEV
+ // stop all the wandering in sp_enemies.
+ if ( GetMapName() == "sp_enemies" )
+ npc.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE )
+ #endif
+}
+
+void function FixupTitle( entity npc )
+{
+ if ( IsMultiplayer() )
+ return
+
+ if ( !npc.IsTitan() )
+ npc.SetTitle( "" )
+ /*
+ if ( npc.GetTitle() == "" )
+ return
+ switch ( npc.GetTeam() )
+ {
+ case TEAM_UNASSIGNED:
+ case TEAM_MILITIA:
+ break
+ default:
+ npc.SetTitle( "" )
+ break
+ }
+ */
+}
+
+var function GetTitanHotdropSetting( entity npc )
+{
+ if ( npc.mySpawnOptions_titanfallSpawn != null )
+ return "titanfall"
+ if ( npc.mySpawnOptions_warpfallSpawn != null )
+ return "warpfall"
+
+ if ( npc.HasKey( "script_hotdrop" ) )
+ {
+ switch ( npc.kv.script_hotdrop )
+ {
+ case "0":
+ return null
+ case "3":
+ case "1":
+ return "titanfall"
+ case "4":
+ case "2":
+ return "warpfall"
+ }
+ }
+
+ return null
+}
+
+function CommonNPCTitanOnSpawned( entity npc )
+{
+ if ( npc.ai.titanSpawnLoadout.primary != "" )
+ {
+ Assert( npc.GetMainWeapons().len() == 0 )
+
+ // if designer overwrites weapons, apply them
+ GiveTitanLoadout( npc, npc.ai.titanSpawnLoadout )
+ }
+ else if ( npc.GetMainWeapons().len() == 0 )
+ {
+ GiveTitanLoadout( npc, npc.ai.titanSpawnLoadout )
+ }
+
+ //Assert( npc.ai.titanSpawnLoadout.setFile == npc.ai.titanSettings.titanSetFile )
+ string playerSettings = expect string( npc.Dev_GetAISettingByKeyField( "npc_titan_player_settings" ) )
+ asset modelName = GetPlayerSettingsAssetForClassName( playerSettings, "bodymodel" )
+ if ( npc.GetModelName() != modelName )
+ npc.SetModel( modelName )
+// Assert( npc.GetModelName() == modelName )
+
+ int camoIndex = GetTitanCamoIndexFromLoadoutAndPrimeStatus( npc.ai.titanSpawnLoadout )
+ int skinIndex = GetTitanSkinIndexFromLoadoutAndPrimeStatus( npc.ai.titanSpawnLoadout )
+ int decalIndex = GetTitanDecalIndexFromLoadoutAndPrimeStatus ( npc.ai.titanSpawnLoadout )
+
+ if ( camoIndex > 0 )
+ {
+ npc.SetSkin( TITAN_SKIN_INDEX_CAMO )
+ npc.SetCamo( camoIndex )
+ }
+ else
+ {
+ int skin
+ if ( npc.HasKey( "modelskin" ) )
+ skin = expect int( npc.kv.modelskin.tointeger() )
+
+ if ( skinIndex > 0 )
+ {
+ Assert( skin == 0, "Both npc.kv.modelskin and skinIndex were > 0. Pick one." )
+ skin = skinIndex
+ }
+
+ if ( skin > 0 )
+ npc.SetSkin( skin )
+ }
+
+ npc.SetDecal( decalIndex )
+
+ #if HAS_BOSS_AI
+ if ( IsMercTitan( npc ) )
+ {
+ array<entity> weapons = GetPrimaryWeapons( npc )
+ Assert( weapons.len() == 1 )
+ string character = GetMercCharacterForWeapon( weapons[0].GetWeaponClassName() )
+ npc.ai.bossCharacterName = character
+ npc.ai.mercCharacterID = GetBossTitanID( character )
+
+ int id = GetBossTitanID( character )
+ string title = GetBossTitleFromID( id )
+
+ npc.SetTitle( title )
+ }
+ #endif
+
+ // force sp titans to use specific loadouts
+ if ( !IsMultiplayer() )
+ ResetTitanLoadoutFromPrimary( npc )
+
+ npc.EnableNPCFlag( NPC_NO_MOVING_PLATFORM_DEATH )
+ //npc.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE )
+
+ if ( IsMultiplayer() )
+ {
+ npc.kv.alwaysalert = 1
+ }
+ else
+ {
+ if ( npc.GetAIClass() == AIC_TITAN_BUDDY )
+ {
+ npc.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE )
+ array<entity> enemies = GetNPCArrayEx( "any", TEAM_ANY, npc.GetTeam(), npc.GetOrigin(), 4000 )
+ if ( enemies.len() > 0 )
+ npc.SetAlert()
+
+ if ( npc.GetTitanSoul() )
+ {
+ // create buddy titan dialogue ent
+ // it will be transfered during embark and disembark automatically
+ entity dialogueEnt = CreateScriptMover()
+ dialogueEnt.DisableHibernation()
+ dialogueEnt.SetParent( npc, "HEADFOCUS", false, 0 )
+ npc.GetTitanSoul().SetTitanSoulNetEnt( "dialogueEnt", dialogueEnt )
+ }
+ }
+ }
+
+ local maxHealth = GetPlayerSettingsFieldForClassName_Health( npc.ai.titanSettings.titanSetFile ) //FD META - TO UPDATE with NPC equivalent of .GetPlayerModHealth()
+ if ( npc.ai.titanSpawnLoadout.setFileMods.contains( "fd_health_upgrade" ) )
+ maxHealth += 2500
+ //TEMP - GetPlayerSettingsFieldForClassName_Health doesn't return modded values.
+ if ( IsHardcoreGameMode() )
+ maxHealth *= 0.5
+
+ // this will override whatever health is set in the aisettings txt file.
+ npc.SetMaxHealth( maxHealth )
+ npc.SetHealth( maxHealth )
+ npc.SetValidHealthBarTarget( true )
+
+ #if HAS_BOSS_AI
+ UpdateMercTitanHealthForDifficulty( npc )
+ #endif
+
+ switch ( GetTitanHotdropSetting( npc ) )
+ {
+ case "titanfall":
+ thread NPCTitanHotdrops( npc, true )
+ break
+
+ case "warpfall":
+ thread NPCTitanHotdrops( npc, true, "at_hotdrop_drop_2knee_turbo_upgraded" )
+ break
+ }
+
+ // TODO: Have code allow us to put this in titan_base.set
+ npc.SetNumRodeoSlots( PROTOTYPE_DEFAULT_TITAN_RODEO_SLOTS )
+
+ if ( IsValid( npc.mySpawnOptions_ownerPlayer ) )
+ {
+ entity soul = npc.GetTitanSoul()
+ entity player = expect entity( npc.mySpawnOptions_ownerPlayer )
+
+ if ( IsValid( soul ) )
+ {
+ soul.soul.lastOwner = player
+ SoulBecomesOwnedByPlayer( soul, player )
+ }
+
+ SetupAutoTitan( npc, player )
+ }
+
+ if ( npc.HasKey( "disable_offhand_ordnance" ) )
+ {
+ if ( bool( npc.kv.disable_offhand_ordnance ) )
+ {
+ npc.TakeOffhandWeapon( OFFHAND_ORDNANCE )
+ }
+ }
+
+ if ( npc.HasKey( "disable_offhand_defense" ) )
+ {
+ if ( bool( npc.kv.disable_offhand_defense ) )
+ {
+ npc.TakeOffhandWeapon( OFFHAND_SPECIAL )
+ }
+ }
+
+ if ( npc.HasKey( "disable_offhand_tactical" ) )
+ {
+ if ( bool( npc.kv.disable_offhand_tactical ) )
+ {
+ entity weapon = npc.GetOffhandWeapon( OFFHAND_ANTIRODEO )
+ if ( weapon && weapon.GetWeaponClassName() == "mp_titanability_hover" )
+ npc.SetAllowSpecialJump( false )
+
+ npc.TakeOffhandWeapon( OFFHAND_ANTIRODEO )
+ }
+ }
+
+ if ( npc.HasKey( "disable_offhand_core" ) )
+ {
+ if ( bool( npc.kv.disable_offhand_core ) )
+ {
+ npc.TakeOffhandWeapon( OFFHAND_EQUIPMENT )
+ }
+ }
+
+ if ( npc.HasKey( "follow_mode" ) )
+ {
+ if ( bool( npc.kv.follow_mode ) )
+ {
+ entity player = GetPlayerArray()[0] // gross
+ int followBehavior = GetDefaultNPCFollowBehavior( npc )
+ npc.InitFollowBehavior( player, followBehavior )
+ npc.EnableBehavior( "Follow" )
+ npc.DisableBehavior( "Assault" )
+ }
+ }
+
+ var hasTraverse = npc.Dev_GetAISettingByKeyField( "can_traverse" )
+ if ( hasTraverse == null || expect int( hasTraverse ) == 0 )
+ {
+ npc.SetCapabilityFlag( bits_CAP_MOVE_TRAVERSE, false )
+ }
+
+ entity soul = npc.GetTitanSoul()
+ if ( IsValid( soul ) )
+ {
+ soul.soul.titanLoadout = npc.ai.titanSpawnLoadout
+ }
+}
+
+function ShouldSpawn( team, forced )
+{
+ //we're not allowed to spawn AI at all - return false
+ if ( !IsNPCSpawningEnabled() && !forced )
+ {
+ printt( "WARNING: tried to spawn an NPC but NPC Spawning is Disabled." )
+ return false
+ }
+ return true
+}
+
+
+function HACK_DroneGruntModel( grunt )
+{
+ string tag = "CHESTFOCUS"
+ int attachID = expect int( grunt.LookupAttachment( tag ) )
+ vector origin = expect vector( grunt.GetAttachmentOrigin( attachID ) )
+ vector angles = expect vector( grunt.GetAttachmentAngles( attachID ) )
+ vector forward = AnglesToForward( angles )
+ vector right = AnglesToRight( angles )
+ vector up = AnglesToUp( angles )
+
+ vector angles1 = AnglesCompose( angles, Vector( 0, -90, 90 ) )
+ vector origin1 = origin + ( forward * -4 ) + ( up * -1.5 )
+ entity back1 = CreatePropDynamic( HACK_DRONE_BACK1, origin1, angles1 )
+ back1.SetParent( grunt, tag, true, 0 )
+
+ vector angles2 = AnglesCompose( angles, Vector( 0, -90, 0 ) )
+ vector origin2 = origin + ( forward * -9 ) + ( up * 11 ) + ( right * -1 )
+ entity back2 = CreatePropDynamic( HACK_DRONE_BACK2, origin2, angles2 )
+ back2.SetParent( grunt, tag, true, 0 )
+}
+
+void function TryAutoAssignAntiTitanWeapon( entity npc )
+{
+ // disabling this while anti titan weapons settle down
+ if ( !IsMultiplayer() )
+ return
+
+ if ( file.pilotAntiTitanWeapons.len() == 0 )
+ return
+
+ Assert( !HasAntiTitanWeapon( npc ) )
+
+ // each 4th npc gets a rocket
+ file.nextAntiTitanWeaponAutoAssign--
+ if ( file.nextAntiTitanWeaponAutoAssign > 0 )
+ return
+
+ file.nextAntiTitanWeaponAutoAssign = 3
+
+ string weapon = file.pilotAntiTitanWeapons.getrandom()
+ npc.GiveWeapon( weapon )
+
+ if ( IsGrunt( npc ) )
+ {
+ // show rockets on the back
+ switch ( npc.GetTeam() )
+ {
+ case TEAM_IMC:
+ npc.SetModel( TEAM_IMC_GRUNT_MODEL_ROCKET )
+ break
+
+#if SP
+ case TEAM_MILITIA:
+ npc.SetModel( TEAM_MIL_GRUNT_MODEL_ROCKET )
+ break
+#endif
+ }
+ }
+}
+
+function SpawnWithoutSoul( ent )
+{
+ if ( ent.HasKey( "noSoul" ) )
+ {
+ return ent.kv.noSoul
+ }
+
+ return "spawnWithoutSoul" in ent.s
+}
+
+function DisableAimAssisst( self )
+{
+ self.SetAimAssistAllowed( false )
+}
+
+void function SuperSpectreIntro( entity npc )
+{
+ bool warpfall
+ if ( npc.mySpawnOptions_warpfallSpawn != null )
+ warpfall = true
+ else if ( npc.HasKey( "script_hotdrop" ) && npc.kv.script_hotdrop.tolower() == "warpfall" )
+ warpfall = true
+
+ if ( warpfall )
+ thread SuperSpectre_WarpFall( npc )
+}
+
+void function AssignGruntModelForWeaponClass( entity npc, entity weapon, string weaponSubClass )
+{
+ // We only have IMC grunt models for weapon class
+ if ( !npc.Dev_GetAISettingByKeyField( "IsGenericGrunt" ) )
+ return
+
+ asset model
+
+ switch ( npc.GetTeam() )
+ {
+//#if SP
+ case TEAM_MILITIA:
+ switch ( weaponSubClass )
+ {
+ case "lmg":
+ case "sniper":
+ model = TEAM_MIL_GRUNT_MODEL_LMG
+ break
+
+ case "rocket":
+ case "shotgun":
+ case "projectile_shotgun":
+ model = TEAM_MIL_GRUNT_MODEL_SHOTGUN
+ break
+
+ case "handgun":
+ case "smg":
+ case "sidearm":
+ model = TEAM_MIL_GRUNT_MODEL_SMG
+ break
+
+ case "rifle":
+ default:
+ model = TEAM_MIL_GRUNT_MODEL_RIFLE
+ break
+ }
+ break
+//#endif
+
+ case TEAM_IMC:
+ default:
+ switch ( weaponSubClass )
+ {
+ case "lmg":
+ case "sniper":
+ model = TEAM_IMC_GRUNT_MODEL_LMG
+ break
+
+ case "rocket":
+ case "shotgun":
+ case "projectile_shotgun":
+ model = TEAM_IMC_GRUNT_MODEL_SHOTGUN
+ break
+
+ case "handgun":
+ case "smg":
+ case "sidearm":
+ model = TEAM_IMC_GRUNT_MODEL_SMG
+ break
+
+ case "rifle":
+ default:
+#if SP
+ model = TEAM_IMC_GRUNT_MODEL_RIFLE
+#else
+ // no shotgun/smg grunts in MP right now
+ switch ( RandomInt( 3 ) )
+ {
+ case 0:
+ model = TEAM_IMC_GRUNT_MODEL_RIFLE
+ break
+ case 1:
+ model = TEAM_IMC_GRUNT_MODEL_SHOTGUN
+ break
+ case 2:
+ model = TEAM_IMC_GRUNT_MODEL_SMG
+ break
+ }
+#endif
+ break
+ }
+ break
+
+ }
+
+ if ( model != $"" )
+ {
+ npc.SetModel( model )
+ return
+ }
+
+ if ( IsValid( weapon ) )
+ CodeWarning( "Grunt at " + npc.GetOrigin() + " couldnt get assigned a body model for weapon " + weapon.GetWeaponClassName() + " because that weapon is missing or has invalid weaponSubClass field" )
+ else
+ CodeWarning( "Grunt at " + npc.GetOrigin() + " has no weapon" )
+}
+
+
+entity function SP_GetPilotAntiTitanWeapon( entity ent )
+{
+ array<entity> weaponsArray = ent.GetMainWeapons()
+ foreach ( weapon in weaponsArray )
+ {
+ foreach ( weaponName in file.pilotAntiTitanWeapons )
+ {
+ if ( weapon.GetWeaponClassName() == weaponName )
+ return weapon
+ }
+ }
+
+ return null
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_spectre.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_spectre.gnut
new file mode 100644
index 000000000..214aff96e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_spectre.gnut
@@ -0,0 +1,131 @@
+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( "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<entity> 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
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut
new file mode 100644
index 000000000..f49560e02
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_stalker.gnut
@@ -0,0 +1,606 @@
+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
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_stationary_firing_positions.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_stationary_firing_positions.gnut
new file mode 100644
index 000000000..50b6cc759
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_stationary_firing_positions.gnut
@@ -0,0 +1,261 @@
+global function AddStationaryAIPosition //Add stationary positions to pending list.
+global function AddTestTargetPosForStationaryPositionValidation //Add test target location for validating stationary positions.
+global function ValidateAndFinalizePendingStationaryPositions //Runs error-checking/validation logic on stationary positions and finalizes them for use by AI.
+global function GetRandomStationaryPosition
+global function GetClosestAvailableStationaryPosition
+global function ClaimStationaryAIPosition
+global function ReleaseStationaryAIPosition
+
+global enum eStationaryAIPositionTypes
+{
+ MORTAR_TITAN,
+ MORTAR_SPECTRE,
+ SNIPER_TITAN,
+ LAUNCHER_REAPER
+}
+
+global struct StationaryAIPosition
+{
+ vector origin
+ bool inUse
+}
+
+global struct ArrayDistanceEntryForStationaryAIPosition
+{
+ float distanceSqr
+ StationaryAIPosition& ent
+ vector origin
+}
+
+struct
+{
+ array<vector> validationTestTargets
+ table<int, array<vector> > pendingPositions
+ table<int, array<StationaryAIPosition> > stationaryPositions
+} file
+
+void function AddTestTargetPosForStationaryPositionValidation( vector origin )
+{
+ file.validationTestTargets.append( origin )
+}
+
+void function AddStationaryAIPosition( vector origin, int type )
+{
+ AddPendingStationaryAIPosition_Internal( origin, type )
+}
+
+void function AddStationaryAIPosition_Internal( vector origin, int type )
+{
+ StationaryAIPosition pos
+ pos.origin = origin
+ pos.inUse = false
+
+ //Throw warnings for bad positions
+ foreach ( vector testTarget in file.validationTestTargets )
+ {
+ switch( type )
+ {
+ case eStationaryAIPositionTypes.MORTAR_TITAN:
+ if ( NavMesh_ClampPointForHullWithExtents( origin, HULL_TITAN, <100, 100, 20> ) == null )
+ {
+ CodeWarning( "Mortar Titan Firing Position at " + origin + " does not have enough space to accomidate Titan, skipping." )
+ return
+ }
+ break
+
+ #if MP
+ case eStationaryAIPositionTypes.MORTAR_SPECTRE:
+
+ array<vector> testLocations = MortarSpectreGetSquadFiringPositions( origin, testTarget )
+
+ foreach ( vector testLocation in testLocations )
+ {
+ if ( NavMesh_ClampPointForHullWithExtents( testLocation, HULL_HUMAN, <100, 100, 20> ) == null )
+ {
+ CodeWarning( "Mortar Spectre Firing Position at " + origin + " does not have enough space to accomidate squad, skipping." )
+ return
+ }
+ }
+
+ break
+ #endif //MP
+
+ case eStationaryAIPositionTypes.SNIPER_TITAN:
+ if ( NavMesh_ClampPointForHullWithExtents( origin, HULL_TITAN, <100, 100, 20> ) == null )
+ {
+ CodeWarning( "Sniper Titan Firing Position at " + origin + " does not have enough space to accomidate Titan, skipping." )
+ return
+ }
+ break
+
+ case eStationaryAIPositionTypes.LAUNCHER_REAPER:
+ if ( NavMesh_ClampPointForHullWithExtents( origin, HULL_MEDIUM, <100, 100, 20> ) == null )
+ {
+ CodeWarning( "Tick Launching Reaper Firing Position at " + origin + " does not have enough space to accomidate Reaper, skipping." )
+ return
+ }
+ break
+ }
+ }
+
+ if ( !( type in file.stationaryPositions ) )
+ {
+ file.stationaryPositions[ type ] <- []
+ }
+
+ file.stationaryPositions[ type ].append( pos )
+}
+
+//Function tests stationary AI positions for given type relative to given mortar target.
+void function AddPendingStationaryAIPosition_Internal( vector origin, int type )
+{
+ if ( !( type in file.pendingPositions ) )
+ file.pendingPositions[ type ] <- []
+
+ //Add position to table so we can validate and add it when all entities finish loading.
+ file.pendingPositions[ type ].append( origin )
+}
+
+void function ValidateAndFinalizePendingStationaryPositions()
+{
+
+ Assert( file.validationTestTargets.len(), "Test targets are required to validate stationary positions. Use AddTestTargetPosForStationaryPositionValidation to add them before running validation." )
+
+ foreach ( type, origins in file.pendingPositions )
+ {
+ //Make sure we have pending positions for given ai type.
+ Assert( file.pendingPositions[ type ].len(), "Stationary Positions for type " + type + " could not be found in this map. Add Some." )
+
+ foreach ( vector origin in origins )
+ {
+ AddStationaryAIPosition_Internal( origin, type )
+ }
+
+ //Make sure we have positions for given AI type after we validate and finalize positions.
+ Assert( file.stationaryPositions[ type ].len(), "No valid stationary positions for type " + type + " remain after validation. Adjust positions and retry." )
+ }
+}
+
+StationaryAIPosition function GetClosestAvailableStationaryPosition( vector origin, float maxDist, int type )
+{
+
+ array<StationaryAIPosition> resultArray = []
+ float maxDistSqr = maxDist * maxDist
+
+ array<StationaryAIPosition> positions = file.stationaryPositions[type]
+
+ array<ArrayDistanceEntryForStationaryAIPosition> allResults = ArrayDistanceResultsForStationaryAIPosition( positions, origin )
+ allResults.sort( DistanceCompareClosestForStationaryAIPosition )
+
+ //Remove all in use stationary positions up front.
+ array<ArrayDistanceEntryForStationaryAIPosition> freePositions
+ foreach ( result in allResults )
+ {
+ StationaryAIPosition position = result.ent
+ if ( position.inUse )
+ continue
+
+ freePositions.append( result )
+ }
+
+ //Tell us if all spots for a given AI type are taken.
+ Assert( freePositions.len() > 0, "Could not find free mortar positions for type " + type + ", all positions are currently in use. Add more AddStationaryTitanPosition to the map." )
+
+ foreach( result in freePositions )
+ {
+ StationaryAIPosition position = result.ent
+
+ // if too far, throw warning and continue search beyond maxDist
+ if ( result.distanceSqr > maxDistSqr )
+ {
+ CodeWarning( "Couldn't find a mortar position within " + maxDist + " units for type " + type + " around " + origin.tostring() + " that wasn't in use. Expanding Search. Add more AddStationaryTitanPositions to the map near this point." )
+ }
+
+ return position
+ }
+
+ unreachable
+}
+
+StationaryAIPosition function GetRandomStationaryPosition( vector origin, float maxDist, int type )
+{
+ array<StationaryAIPosition> resultArray = []
+ array<StationaryAIPosition> positions = file.stationaryPositions[type]
+
+ //Remove all in use stationary positions up front.
+ array<StationaryAIPosition> freePositions
+ foreach ( position in positions )
+ {
+ if ( position.inUse )
+ continue
+
+ freePositions.append( position )
+ }
+
+ //Tell us if all spots for a given AI type are taken.
+ Assert( freePositions.len() > 0, "Could not find free mortar positions for type " + type + ", all positions are currently in use. Add more AddStationaryTitanPosition to the map." )
+
+ int attemptCount = 1
+ while ( resultArray.len() == 0 )
+ {
+
+ //Expand our search radius each time we reattempt our search.
+ float maxDistSqr = ( maxDist * attemptCount ) * ( maxDist * attemptCount )
+
+ foreach( position in freePositions )
+ {
+ float dist = Distance2DSqr( origin, position.origin )
+ if ( dist <= maxDistSqr )
+ resultArray.append( position )
+ }
+
+ if ( resultArray.len() == 0 )
+ {
+ CodeWarning( "Couldn't find a mortar position within " + maxDist + " units for type " + type + " around " + origin.tostring() + " that wasn't in use. Expanding Search. Add more AddStationaryTitanPositions to the map near this point." )
+ attemptCount += 1
+ }
+ }
+
+ return resultArray.getrandom()
+}
+
+void function ClaimStationaryAIPosition( StationaryAIPosition stationaryTitanPositions )
+{
+ Assert( stationaryTitanPositions.inUse == false )
+ stationaryTitanPositions.inUse = true
+}
+
+void function ReleaseStationaryAIPosition( StationaryAIPosition stationaryTitanPositions )
+{
+ Assert( stationaryTitanPositions.inUse == true )
+ stationaryTitanPositions.inUse = false
+}
+
+array<ArrayDistanceEntryForStationaryAIPosition> function ArrayDistanceResultsForStationaryAIPosition( array<StationaryAIPosition> entArray, vector origin )
+{
+ array<ArrayDistanceEntryForStationaryAIPosition> allResults
+
+ foreach ( ent in entArray )
+ {
+ ArrayDistanceEntryForStationaryAIPosition entry
+
+ vector entOrigin = ent.origin
+ entry.distanceSqr = DistanceSqr( entOrigin, origin )
+ entry.ent = ent
+ entry.origin = entOrigin
+
+ allResults.append( entry )
+ }
+
+ return allResults
+}
+
+int function DistanceCompareClosestForStationaryAIPosition( ArrayDistanceEntryForStationaryAIPosition a, ArrayDistanceEntryForStationaryAIPosition b )
+{
+ if ( a.distanceSqr > b.distanceSqr )
+ return 1
+ else if ( a.distanceSqr < b.distanceSqr )
+ return -1
+
+ return 0;
+} \ No newline at end of file
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<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 )
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_turret.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_turret.gnut
new file mode 100644
index 000000000..eca5849bf
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_turret.gnut
@@ -0,0 +1,24 @@
+global function AiTurret_Init
+global function GetMegaTurretLinkedToPanel
+global function MegaTurretUsabilityFunc
+global function SetUsePromptForPanel
+
+void function AiTurret_Init()
+{
+
+}
+
+entity function GetMegaTurretLinkedToPanel(entity panel)
+{
+ return null
+}
+
+string function MegaTurretUsabilityFunc(var turret, var panel)
+{
+ return "pilot"
+}
+
+void function SetUsePromptForPanel(var panel, var turret)
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_ai_utility.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_ai_utility.gnut
new file mode 100644
index 000000000..67c686003
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_ai_utility.gnut
@@ -0,0 +1,558 @@
+untyped
+
+globalize_all_functions
+
+function AiUtility_Init()
+{
+ RegisterSignal( "OnNewOwner" )
+ RegisterSignal( "squadInCombat" )
+ RegisterSignal( "OnEndFollow" )
+ RegisterSignal( "OnStunned" )
+
+}
+////////////////////////////////////////////////////////////////////////////////
+// Cloaks npc forever (to be used by anim events)
+function NpcCloakOn( npc )
+{
+ //SetCloakDuration( fade in, duration, fade out )
+ npc.SetCloakDuration( 2.0, -1, 0 )
+ EmitSoundOnEntity( npc, CLOAKED_DRONE_CLOAK_START_SFX )
+ EmitSoundOnEntity( npc, CLOAKED_DRONE_CLOAK_LOOP_SFX )
+ npc.Minimap_Hide( TEAM_IMC, null )
+ npc.Minimap_Hide( TEAM_MILITIA, null )
+}
+////////////////////////////////////////////////////////////////////////////////
+// De-cloaks npc
+function NpcCloakOff( npc)
+{
+ npc.SetCloakDuration( 0, 0, 1.5 )
+ StopSoundOnEntity( npc, CLOAKED_DRONE_CLOAK_LOOP_SFX )
+ npc.Minimap_AlwaysShow( TEAM_IMC, null )
+ npc.Minimap_AlwaysShow( TEAM_MILITIA, null )
+}
+
+int function GetDefaultNPCFollowBehavior( npc )
+{
+ switch ( npc.GetAIClass() )
+ {
+ case AIC_FLYING_DRONE:
+ return AIF_SUPPORT_DRONE
+
+ case AIC_VEHICLE:
+ return AIF_GUNSHIP
+
+ case AIC_TITAN:
+ case AIC_TITAN_BUDDY:
+ return AIF_TITAN_FOLLOW_PILOT
+ }
+
+ return AIF_FIRETEAM
+}
+
+void function DieOnPlayerDisconnect( entity npc, entity player )
+{
+ Assert( IsNewThread(), "Must be threaded off" )
+ Assert( npc.IsNPC() )
+ Assert( player.IsPlayer() )
+ Assert( IsAlive( npc ) )
+ Assert( npc.GetBossPlayer() == player )
+ Assert( !IsDisconnected( player ) )
+ npc.EndSignal( "OnDeath" )
+
+ player.WaitSignal( "OnDestroy" )
+
+ // my boss quit the server!
+ if ( IsAlive( npc ) && npc.GetBossPlayer() == player )
+ npc.Die()
+}
+
+void function NPCFollowsPlayer( entity npc, entity leader )
+{
+ Assert( IsAlive( npc ) )
+ Assert( leader.IsPlayer() )
+
+ npc.SetBossPlayer( leader )
+
+ // team
+ SetTeam( npc, leader.GetTeam() )
+
+ if ( IsSpectre( npc ) )
+ {
+ string squadName = GetPlayerSpectreSquadName( leader )
+ SetSquad( npc, squadName )
+ }
+
+ thread DieOnPlayerDisconnect( npc, leader )
+ #if SP
+ Highlight_SetFriendlyHighlight( npc, "friendly_ai" )
+ #else
+ Highlight_SetOwnedHighlight( npc, "friendly_ai" )
+ #endif
+
+ NpcFollowsEntity( npc, leader )
+}
+
+void function NPCFollowsNPC( entity npc, entity leader )
+{
+ Assert( IsAlive( npc ) )
+ Assert( IsAlive( leader ) )
+ Assert( leader.IsNPC() )
+
+ // team
+ SetTeam( npc, leader.GetTeam() )
+
+ // squad
+ string squadNameOwner = expect string( leader.Get( "squadname" ) )
+ if ( squadNameOwner != "" && leader.GetClassName() == npc.GetClassName() )
+ SetSquad( npc, squadNameOwner )
+
+ NpcFollowsEntity( npc, leader )
+}
+
+void function NpcFollowsEntity( entity npc, entity leader )
+{
+ // stop scripted things
+ if ( IsMultiplayer() )
+ npc.Signal( "StopHardpointBehavior" )
+
+ if ( leader.IsPlayer() && leader.p.followPlayerOverride != null )
+ {
+ leader.p.followPlayerOverride( npc, leader )
+ return
+ }
+
+ // follow!
+ int followBehavior = GetDefaultNPCFollowBehavior( npc )
+ npc.InitFollowBehavior( leader, followBehavior )
+ npc.DisableBehavior( "Assault" )
+ npc.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_USE_SHOOTING_COVER )
+ npc.EnableBehavior( "Follow" )
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+bool function HasEnemyWithinDist( entity npc, float dist )
+{
+ float distSq = dist * dist
+
+ array<entity> enemies
+ entity closestEnemy = npc.GetClosestEnemy()
+ if ( closestEnemy )
+ enemies.append( closestEnemy )
+
+ entity currentEnemy = npc.GetEnemy()
+ if ( currentEnemy && currentEnemy != closestEnemy )
+ enemies.append( currentEnemy )
+
+ if ( !enemies.len() )
+ return false
+
+ vector origin = npc.GetOrigin()
+ foreach ( enemy in enemies )
+ {
+ if ( DistanceSqr( origin, enemy.GetOrigin() ) < distSq )
+ return true
+ }
+
+ return false
+}
+
+SpawnPointFP function FindSpawnPointForNpcCallin( entity npc, asset model, string anim )
+{
+ float yaw = npc.EyeAngles().y
+
+ vector npcView = AnglesToForward( npc.EyeAngles() )
+ FlightPath flightPath = GetAnalysisForModel( model, anim )
+
+ CallinData drop
+ InitCallinData( drop )
+ SetCallinStyle( drop, eDropStyle.NEAREST_YAW_FALLBACK )
+ SetCallinOwnerEyePos( drop, npc.EyePosition() )
+ drop.dist = 800
+ drop.origin = npc.GetOrigin() + npcView * 250
+ drop.yaw = yaw
+
+ vector angles = Vector( 0, yaw, 0 )
+ SpawnPointFP spawnPoint = GetSpawnPointForStyle( flightPath, drop )
+ if ( spawnPoint.valid )
+ return spawnPoint
+
+ //if it didn't find one where he was looking - try near him
+ drop.origin = npc.GetOrigin()
+ spawnPoint = GetSpawnPointForStyle( flightPath, drop )
+
+ return spawnPoint
+}
+
+function WaitForSquadInCombat( squad )
+{
+ local master = {}
+
+ //when the thread ends, let child threads now
+ OnThreadEnd(
+ function() : ( master )
+ {
+ Signal( master, "OnDestroy" )
+ }
+ )
+
+ // this internal function keeps track of each guy
+ local combatTracker =
+ function( guy, master )
+ {
+ expect entity( guy )
+ expect entity( master )
+
+ EndSignal( master, "OnDestroy" )
+ EndSignal( guy, "OnDeath", "OnDestroy" )
+ if ( !IsAlive( guy ) )
+ return
+
+ while ( guy.GetNPCState() != "combat" )
+ guy.WaitSignal( "OnStateChange" )
+
+ Signal( master, "squadInCombat" )
+ }
+
+ foreach ( guy in squad )
+ {
+ thread combatTracker( guy, master )
+ }
+
+ WaitSignal( master, "squadInCombat" )
+}
+
+function WaitForNpcInCombat( npc )
+{
+ while ( npc.GetNPCState() != "combat" )
+ npc.WaitSignal( "OnStateChange" )
+}
+
+int function GetNpcHullType( entity npc )
+{
+ string aiSettings = npc.GetAISettingsName()
+ return int ( Dev_GetAISettingByKeyField_Global( aiSettings, "HullType" ) )
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// "SPAWN AI" DEV MENU Fuctions
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+const float CROSSHAIR_VERT_OFFSET = 32
+
+vector function GetPlayerCrosshairOriginRaw( entity player )
+{
+ vector angles = player.EyeAngles()
+ vector forward = AnglesToForward( angles )
+ vector origin = player.EyePosition()
+
+ vector start = origin
+ vector end = origin + forward * 50000
+ TraceResults result = TraceLine( start, end )
+ vector crosshairOrigin = result.endPos
+
+ return crosshairOrigin
+}
+
+vector function GetPlayerCrosshairOrigin( entity player )
+{
+ return (GetPlayerCrosshairOriginRaw( player ) + Vector( 0, 0, CROSSHAIR_VERT_OFFSET ))
+}
+
+void function DEV_SpawnBTAtCrosshair( bool hotdrop = false )
+{
+ DisablePrecacheErrors()
+ wait 0.2
+ entity player = GetPlayerArray()[ 0 ]
+
+ entity pet_titan = player.GetPetTitan()
+ if ( IsValid(pet_titan) )
+ pet_titan.Destroy()
+
+ vector origin = GetPlayerCrosshairOrigin( player )
+ vector angles = Vector( 0, 0, 0 )
+
+ TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap()
+ entity npc = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, origin, angles )
+
+ SetSpawnOption_AISettings( npc, "npc_titan_buddy" )
+
+ DispatchSpawn( npc )
+
+ SetPlayerPetTitan( player, npc )
+
+ if ( hotdrop )
+ thread NPCTitanHotdrops( npc, false )
+}
+
+void function DEV_SpawnAllNPCsWithTeam( int team )
+{
+ printt( "script thread DEV_SpawnAllNPCsWithTeam( " + team + " )" )
+ Assert( IsNewThread(), "Must be threaded off due to precache issues" )
+ bool restoreHostThreadMode = GetConVarInt( "host_thread_mode" ) != 0
+ if ( restoreHostThreadMode )
+ {
+ DisablePrecacheErrors()
+ wait 0.5
+ }
+
+ entity player = GetPlayerArray()[ 0 ]
+ vector origin = GetPlayerCrosshairOrigin( player )
+ array<string> aiSettings = GetAllNPCSettings()
+
+ foreach ( settings in aiSettings )
+ {
+ vector angles = < 0, RandomFloat( 360 ), 0 >
+ entity npc = CreateNPCFromAISettings( settings, team, origin, angles )
+ DispatchSpawn( npc )
+ }
+
+ if ( restoreHostThreadMode )
+ {
+ wait 0.2
+ RestorePrecacheErrors()
+ }
+}
+
+void function DEV_SpawnNPCWithWeaponAtCrosshair( string baseClass, string aiSettings, int team, string weaponName = "" )
+{
+ printt( "script thread DEV_SpawnNPCWithWeaponAtCrosshair( \"" + baseClass + "\", \"" + aiSettings + "\", " + team + ", \"" + weaponName + "\")" )
+ Assert( IsNewThread(), "Must be threaded off due to precache issues" )
+ bool restoreHostThreadMode = GetConVarInt( "host_thread_mode" ) != 0
+ entity npc = DEV_SpawnNPCWithWeaponAtCrosshairStart( restoreHostThreadMode, baseClass, aiSettings, team, weaponName )
+ DispatchSpawn( npc )
+ DEV_SpawnNPCWithWeaponAtCrosshairEnd( restoreHostThreadMode )
+}
+
+void function DEV_SpawnMercTitanAtCrosshair( string mercName )
+{
+ printt( "script thread DEV_SpawnMercTitanAtCrosshair( \"" + mercName + "\")" )
+ Assert( IsNewThread(), "Must be threaded off due to precache issues" )
+ TitanLoadoutDef ornull loadout = GetTitanLoadoutForBossCharacter( mercName )
+ if ( loadout == null )
+ return
+ expect TitanLoadoutDef( loadout )
+ string baseClass = "npc_titan"
+ string aiSettings = GetNPCSettingsFileForTitanPlayerSetFile( loadout.setFile )
+
+ bool restoreHostThreadMode = GetConVarInt( "host_thread_mode" ) != 0
+ entity npc = DEV_SpawnNPCWithWeaponAtCrosshairStart( restoreHostThreadMode, baseClass, aiSettings, TEAM_IMC )
+ SetSpawnOption_NPCTitan( npc, TITAN_MERC )
+ SetSpawnOption_TitanLoadout( npc, loadout )
+ npc.ai.bossTitanPlayIntro = false
+
+ DispatchSpawn( npc )
+ DEV_SpawnNPCWithWeaponAtCrosshairEnd( restoreHostThreadMode )
+}
+
+void function DEV_SpawnWeaponAtCrosshair( string weaponName )
+{
+ printt( "script thread DEV_SpawnWeaponAtCrosshair( \"" + weaponName + "\")" )
+
+ Assert( IsNewThread(), "Must be threaded off due to precache issues" )
+
+ entity player = GetPlayerArray()[ 0 ]
+ if ( !IsValid( player ) )
+ return
+ vector origin = GetPlayerCrosshairOrigin( player )
+ vector angles = Vector( 0, 0, 0 )
+ entity weapon = CreateWeaponEntityByNameWithPhysics( weaponName, origin, angles )
+
+#if SP
+ bool isTitanWeapon = weaponName.find( "mp_titanweapon_" ) != null
+ if ( isTitanWeapon )
+ thread TitanLoadoutWaitsForPickup( weapon, SPTitanLoadoutPickup )
+#endif
+
+}
+
+string function GetAISettingsFromPlayerSetFile( string playerSetfile )
+{
+ TitanLoadoutDef ornull loadout = GetTitanLoadoutForColumn( "setFile", playerSetfile )
+ Assert( loadout != null, "Couldn't find loadout with set file " + playerSetfile )
+ expect TitanLoadoutDef( loadout )
+
+ return expect string( Dev_GetPlayerSettingByKeyField_Global( playerSetfile, GetAISettingsStringForMode() ) )
+}
+
+
+void function DEV_SpawnBossTitanAtCrosshair( string playerSetfile )
+{
+ string aiSettings = GetAISettingsFromPlayerSetFile( playerSetfile )
+ printt( "script thread DEV_SpawnBossTitanAtCrosshair( \"" + aiSettings + "\")" )
+ Assert( IsNewThread(), "Must be threaded off due to precache issues" )
+
+ string baseClass = "npc_titan"
+ bool restoreHostThreadMode = GetConVarInt( "host_thread_mode" ) != 0
+ entity npc = DEV_SpawnNPCWithWeaponAtCrosshairStart( restoreHostThreadMode, baseClass, aiSettings, TEAM_IMC )
+ SetSpawnOption_NPCTitan( npc, TITAN_BOSS )
+// SetSpawnOption_TitanLoadout( npc, loadout )
+
+ string builtInLoadout = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "npc_titan_player_settings" ) )
+// SetTitanSettings( npc.ai.titanSettings, builtInLoadout )
+ npc.ai.titanSpawnLoadout.setFile = builtInLoadout
+ OverwriteLoadoutWithDefaultsForSetFile( npc.ai.titanSpawnLoadout ) // get the entire loadout, including defensive and tactical
+
+ DispatchSpawn( npc )
+ DEV_SpawnNPCWithWeaponAtCrosshairEnd( restoreHostThreadMode )
+}
+
+entity function DEV_SpawnNPCWithWeaponAtCrosshairStart( bool restoreHostThreadMode, string baseClass, string aiSettings, int team, string weaponName = "" )
+{
+ if ( restoreHostThreadMode )
+ {
+ DisablePrecacheErrors()
+ wait 0.5
+ }
+
+ float time = Time()
+ for ( ;; )
+ {
+ if ( Time() > time )
+ break
+ WaitFrame()
+ }
+ entity player = GetPlayerArray()[ 0 ]
+ if ( !IsValid( player ) )
+ return
+
+ vector origin = GetPlayerCrosshairOrigin( player )
+ vector angles = Vector( 0, 0, 0 )
+
+ entity npc = CreateNPC( baseClass, team, origin, angles )
+ if ( IsTurret( npc ) )
+ npc.kv.origin -= Vector( 0, 0, CROSSHAIR_VERT_OFFSET )
+ SetSpawnOption_AISettings( npc, aiSettings )
+
+ if ( npc.GetClassName() == "npc_soldier" || npc.GetClassName() == "npc_spectre" )
+ npc.kv.squadname = "crosshairSpawnSquad_team_" + team + "_" + baseClass + "_" + aiSettings
+
+ if ( weaponName != "" )
+ SetSpawnOption_Weapon( npc, weaponName )
+
+ if ( npc.GetClassName() == "npc_titan" )
+ {
+ string builtInLoadout = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "npc_titan_player_settings" ) )
+ SetTitanSettings( npc.ai.titanSettings, builtInLoadout )
+ npc.ai.titanSpawnLoadout.setFile = builtInLoadout
+ OverwriteLoadoutWithDefaultsForSetFile( npc.ai.titanSpawnLoadout ) // get the entire loadout, including defensive and tactical
+ }
+
+ return npc
+}
+
+void function DEV_SpawnNPCWithWeaponAtCrosshairEnd( bool restoreHostThreadMode )
+{
+ if ( restoreHostThreadMode )
+ {
+ wait 0.2
+ RestorePrecacheErrors()
+ }
+}
+
+
+function SetAISettingsWrapper( entity npc, string settings )
+{
+ npc.SetAISettings( settings )
+ Assert( settings.find( npc.GetClassName() ) == 0, "NPC classname " + npc.GetClassName() + " not found in " + settings )
+
+ if ( IsSingleplayer() )
+ {
+ FixupTitle( npc )
+ }
+}
+
+bool function WithinEngagementRange( entity npc, vector origin )
+{
+ entity weapon = npc.GetActiveWeapon()
+ if ( weapon == null )
+ return false
+
+ float dist = Distance( npc.GetOrigin(), origin )
+ if ( dist < weapon.GetWeaponInfoFileKeyField( "npc_min_engage_range" ) )
+ return false
+
+ return dist <= weapon.GetWeaponInfoFileKeyField( "npc_max_engage_range" )
+}
+
+
+function DEV_AITitanDuel()
+{
+ thread DEV_AITitanDuelThread()
+}
+
+entity function DEV_AITitanDuelSpawn( entity player, int team, vector origin, vector angles, aiSetting )
+{
+ entity titan = CreateNPC( "npc_titan", team, origin, angles )
+ SetSpawnOption_AISettings( titan, aiSetting )
+ DispatchSpawn( titan )
+
+ vector ornull clampedPos = NavMesh_ClampPointForAI( origin, titan )
+ if ( clampedPos != null )
+ {
+ titan.SetOrigin( expect vector( clampedPos ) )
+ }
+ else
+ {
+ array<entity> spawnpoints = SpawnPoints_GetTitan()
+ if ( spawnpoints.len() )
+ {
+ entity spawnpoint = GetClosest( spawnpoints, origin )
+ titan.SetOrigin( spawnpoint.GetOrigin() )
+ }
+ }
+
+ return titan
+}
+
+function DEV_AITitanDuelThread()
+{
+ DisablePrecacheErrors()
+ wait 0.5
+
+ array<string> aiSettings = GetAllowedTitanAISettings()
+
+ aiSettings.randomize()
+
+ entity player = GetPlayerArray()[ 0 ]
+
+ entity imcTitan = null
+ entity militiaTitan = null
+
+ int currentSetting = 0
+
+
+ while ( 1 )
+ {
+ if ( !IsValid( imcTitan ) )
+ {
+ vector origin = GetPlayerCrosshairOrigin( player ) + < -300, -300, 0 >
+ vector angles = Vector( 0, 0, 0 )
+
+ imcTitan = DEV_AITitanDuelSpawn( player, TEAM_IMC, origin, angles, aiSettings[currentSetting] )
+ currentSetting = (currentSetting + 1) % aiSettings.len()
+
+ if ( IsValid( militiaTitan ) )
+ {
+ imcTitan.SetEnemyLKP( militiaTitan, militiaTitan.GetOrigin() )
+ militiaTitan.SetEnemyLKP( imcTitan, imcTitan.GetOrigin() )
+ }
+ }
+
+ if ( !IsValid( militiaTitan ) )
+ {
+ vector origin = GetPlayerCrosshairOrigin( player ) + < 300, 300, 0 >
+ vector angles = Vector( 0, 180, 0 )
+
+ militiaTitan = DEV_AITitanDuelSpawn( player, TEAM_MILITIA, origin, angles, aiSettings[currentSetting] )
+ currentSetting = (currentSetting + 1) % aiSettings.len()
+
+ if ( IsValid( imcTitan ) )
+ {
+ militiaTitan.SetEnemyLKP( imcTitan, imcTitan.GetOrigin() )
+ imcTitan.SetEnemyLKP( militiaTitan, militiaTitan.GetOrigin() )
+ }
+ }
+
+ wait 2
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_droppod.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_droppod.gnut
new file mode 100644
index 000000000..847efa8b8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_droppod.gnut
@@ -0,0 +1,6 @@
+global function DropPod_Init
+
+void function DropPod_Init()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_droppod_fireteam.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_droppod_fireteam.gnut
new file mode 100644
index 000000000..b93631ac8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_droppod_fireteam.gnut
@@ -0,0 +1,246 @@
+global function DropPodFireteam_Init
+
+global function InitFireteamDropPod
+global function ActivateFireteamDropPod
+global function DropPodActiveThink
+
+global function CreateDropPodDoor
+
+const DP_ARM_MODEL = $"models/vehicle/droppod_fireteam/droppod_fireteam_arm.mdl"
+const DP_DOOR_MODEL = $"models/vehicle/droppod_fireteam/droppod_fireteam_door.mdl"
+
+global enum eDropPodFlag
+{
+ DISSOLVE_AFTER_DISEMBARKS = (1<<0)
+}
+
+struct DroppodStruct
+{
+ entity door
+ bool openDoor = false
+ int numGuys = 0
+ int flags = 0
+}
+
+struct
+{
+ table<entity, DroppodStruct> droppodTable
+}
+file
+
+void function DropPodFireteam_Init()
+{
+ RegisterSignal( "OpenDoor" )
+
+ PrecacheModel( DP_DOOR_MODEL )
+ PrecacheModel( DP_ARM_MODEL )
+}
+
+void function InitFireteamDropPod( entity pod, int flags = 0 )
+{
+ pod.NotSolid()
+
+ DroppodStruct droppodData
+ droppodData.flags = flags
+ droppodData.door = CreateDropPodDoor( pod )
+ file.droppodTable[ pod ] <- droppodData
+
+ pod.Anim_Play( "idle" )
+}
+
+void function ActivateFireteamDropPod( entity pod, array<entity> guys )
+{
+ DroppodStruct droppodData = file.droppodTable[ pod ]
+ droppodData.openDoor = true
+ pod.Signal( "OpenDoor" )
+
+ if ( guys.len() >= 1 )
+ {
+ SetAnim( guys[0], "drop_pod_exit_anim", "pt_dp_exit_a" )
+ SetAnim( guys[0], "drop_pod_idle_anim", "pt_dp_idle_a" )
+ }
+
+ if ( guys.len() >= 2 )
+ {
+ SetAnim( guys[1], "drop_pod_exit_anim", "pt_dp_exit_b" )
+ SetAnim( guys[1], "drop_pod_idle_anim", "pt_dp_idle_b" )
+ }
+
+ if ( guys.len() >= 3 )
+ {
+ SetAnim( guys[2], "drop_pod_exit_anim", "pt_dp_exit_c" )
+ SetAnim( guys[2], "drop_pod_idle_anim", "pt_dp_idle_c" )
+ }
+
+ if ( guys.len() >= 4 )
+ {
+ SetAnim( guys[3], "drop_pod_exit_anim", "pt_dp_exit_d" )
+ SetAnim( guys[3], "drop_pod_idle_anim", "pt_dp_idle_d" )
+ }
+
+ foreach ( guy in guys )
+ {
+ if ( IsAlive( guy ) )
+ {
+ guy.MakeVisible()
+ entity weapon = guy.GetActiveWeapon()
+ if ( IsValid( weapon ) )
+ weapon.MakeVisible()
+
+ thread GuyHangsInPod( guy, pod )
+ }
+ }
+
+ thread DropPodActiveThink( pod )
+}
+
+void function DropPodActiveThink( entity pod )
+{
+ DroppodStruct droppodData = file.droppodTable[ pod ]
+
+ OnThreadEnd(
+ function() : ( pod )
+ {
+ DroppodStruct droppodData = file.droppodTable[pod]
+ if ( droppodData.flags & eDropPodFlag.DISSOLVE_AFTER_DISEMBARKS )
+ CleanupFireteamPod( pod )
+ else
+ delaythread( 10 ) CleanupFireteamPod( pod )
+ }
+ )
+
+ pod.EndSignal( "OnDestroy" )
+
+ if ( DropPodDoorInGround( pod ) )
+ droppodData.door.Destroy()
+ else
+ DropPodOpenDoor( pod, droppodData.door )
+
+ while ( droppodData.numGuys )
+ WaitFrame()
+}
+
+bool function DropPodDoorInGround( entity pod )
+{
+ string attachment = "hatch"
+ int attachIndex = pod.LookupAttachment( attachment )
+ vector end = pod.GetAttachmentOrigin( attachIndex )
+
+ string originAttachment = "origin"
+ int originAttachIndex = pod.LookupAttachment( originAttachment )
+ vector start = pod.GetAttachmentOrigin( originAttachIndex )
+
+ TraceResults result = TraceLine( start, end, pod, TRACE_MASK_SOLID, TRACE_COLLISION_GROUP_NONE )
+
+ return result.fraction < 1.0
+}
+
+void function CleanupFireteamPod( entity pod )
+{
+ DroppodStruct droppodData = file.droppodTable[ pod ]
+
+ if ( !IsValid( pod ) )
+ return
+
+ if ( IsValid( droppodData.door ) )
+ droppodData.door.Dissolve( ENTITY_DISSOLVE_CORE, Vector( 0, 0, 0 ), 500 )
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, pod.GetOrigin(), "droppod_dissolve" )
+
+ delete file.droppodTable[ pod ]
+
+ pod.NotSolid()
+ foreach( ent in pod.e.attachedEnts )
+ {
+ ent.NotSolid()
+ }
+ pod.Dissolve( ENTITY_DISSOLVE_CORE, Vector( 0, 0, 0 ), 500 )
+}
+
+entity function CreateDropPodDoor( entity pod )
+{
+ string attachment = "hatch"
+ int attachIndex = pod.LookupAttachment( attachment )
+ vector origin = pod.GetAttachmentOrigin( attachIndex )
+ vector angles = pod.GetAttachmentAngles( attachIndex )
+
+ entity prop_physics = CreateEntity( "prop_physics" )
+ SetTargetName( prop_physics, "door" + UniqueString() )
+ prop_physics.SetValueForModelKey( DP_DOOR_MODEL )
+ // Start Asleep
+ // Debris - Don't collide with the player or other debris
+ // Generate output on +USE
+ prop_physics.kv.spawnflags = 261 // non solid for now
+ prop_physics.kv.fadedist = -1
+ prop_physics.kv.physdamagescale = 0.1
+ prop_physics.kv.inertiaScale = 1.0
+ prop_physics.kv.renderamt = 0
+ prop_physics.kv.rendercolor = "255 255 255"
+
+ DispatchSpawn( prop_physics )
+
+ prop_physics.SetOrigin( origin )
+ prop_physics.SetAngles( angles )
+ prop_physics.SetParent( pod, "HATCH", false )
+ prop_physics.MarkAsNonMovingAttachment()
+
+ return prop_physics
+}
+
+void function DropPodOpenDoor( entity pod, entity door )
+{
+ door.ClearParent()
+ door.SetVelocity( door.GetForwardVector() * 500 )
+ EmitSoundOnEntity( pod, "droppod_door_open" )
+}
+
+void function GuyHangsInPod( entity guy, entity pod )
+{
+ DroppodStruct droppodData = file.droppodTable[ pod ]
+
+ guy.EndSignal( "OnDeath" )
+ guy.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( droppodData )
+ {
+ droppodData.numGuys--
+ }
+ )
+
+ droppodData.numGuys++
+
+ string idleAnim
+ string exitAnim
+
+ if ( !droppodData.openDoor )
+ {
+ guy.SetEfficientMode( true )
+
+ guy.SetParent( pod, "ATTACH", false )
+
+ idleAnim = expect string( GetAnim( guy, "drop_pod_idle_anim" ) )
+ if ( guy.LookupSequence( idleAnim ) != -1 )
+ guy.Anim_ScriptedPlay( idleAnim )
+
+ pod.WaitSignal( "OpenDoor" )
+
+ //wait POST_TURRET_DELAY
+
+ guy.SetEfficientMode( false )
+ }
+
+
+ guy.SetParent( pod, "ATTACH", false )
+
+ exitAnim = expect string ( GetAnim( guy, "drop_pod_exit_anim" ) )
+ bool exitAnimExists = guy.LookupSequence( exitAnim ) != -1
+ if ( exitAnimExists )
+ guy.Anim_ScriptedPlay( exitAnim )
+
+ guy.ClearParent()
+
+ if ( exitAnimExists )
+ WaittillAnimDone( guy )
+ guy.Signal( "npc_deployed" )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_squad_spawn.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_squad_spawn.gnut
new file mode 100644
index 000000000..9dbdd699d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_squad_spawn.gnut
@@ -0,0 +1,167 @@
+
+
+
+global function GetNPCBaseClassFromSpawnFunc
+
+global function CreateZipLineSquadDropTable
+
+string function GetNPCBaseClassFromSpawnFunc( entity functionref( int, vector, vector ) spawnFunc )
+{
+ // temp spawn a guy to get his baseclass.
+ entity npc = spawnFunc( TEAM_IMC, Vector(0,0,0), Vector(0,0,0) )
+ string baseClass = npc.GetClassName()
+ npc.Destroy()
+ return baseClass
+}
+
+
+
+void function DropOffAISide_NPCThink( entity npc, int index, entity dropship, string attach )
+{
+ npc.EndSignal( "OnDeath" )
+
+ //init
+ npc.SetParent( dropship, attach )
+ npc.SetEfficientMode( true )
+
+ //deploy
+ array<string> deployAnims = DropOffAISide_GetDeployAnims()
+ array<float> seekTimes = DropOffAISide_GetSeekTimes()
+
+ thread PlayAnimTeleport( npc, deployAnims[ index ], dropship, attach )
+ npc.Anim_SetInitialTime( seekTimes[ index ] )
+ WaittillAnimDone( npc )
+
+ npc.SetEfficientMode( false )
+
+ //disperse
+ array<string> disperseAnims = DropOffAISide_GetDisperseAnims()
+ vector origin = HackGetDeltaToRef( npc.GetOrigin(), npc.GetAngles(), npc, disperseAnims[ index ] ) + Vector( 0,0,2 )
+ waitthread PlayAnimGravity( npc, disperseAnims[ index ], origin, npc.GetAngles() )
+}
+
+void function DropOffAISide_WarpOutShip( entity dropship, vector origin, vector angles )
+{
+ wait 1.5
+ dropship.EndSignal( "OnDeath" )
+
+ string anim = "cd_dropship_rescue_side_end"
+ thread PlayAnim( dropship, anim, origin, angles )
+
+ //blend
+ wait dropship.GetSequenceDuration( anim ) - 0.2
+
+ dropship.Hide()
+ thread WarpoutEffect( dropship )
+}
+
+float function GetInstantSpawnRadius( entity npc )
+{
+ float radius = 64
+
+ if ( npc )
+ {
+ switch ( npc.GetClassName() )
+ {
+ case "npc_gunship":
+ case "npc_dropship":
+ radius = 512
+ break
+
+ case "npc_titan":
+ radius = 256
+ break
+
+ case "npc_super_spectre":
+ case "npc_prowler":
+ radius = 128
+ break
+
+ default:
+ radius = 64
+ break
+ }
+ }
+
+ return radius
+}
+
+
+
+
+/************************************************************************************************\
+
+## ## #### ###### ###### ######## ####### ####### ## ######
+### ### ## ## ## ## ## ## ## ## ## ## ## ## ##
+#### #### ## ## ## ## ## ## ## ## ## ##
+## ### ## ## ###### ## ## ## ## ## ## ## ######
+## ## ## ## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
+## ## #### ###### ###### ## ####### ####### ######## ######
+
+\************************************************************************************************/
+
+
+
+
+array<string> function DropOffAISide_GetIdleAnims()
+{
+ array<string> anims = [
+ "pt_ds_side_intro_gen_idle_A", //standing right
+ "pt_ds_side_intro_gen_idle_B", //standing left
+ "pt_ds_side_intro_gen_idle_C", //sitting right
+ "pt_ds_side_intro_gen_idle_D" ] //sitting left
+
+ return anims
+}
+
+array<string> function DropOffAISide_GetDeployAnims()
+{
+ array<string> anims = [
+ "pt_generic_side_jumpLand_A", //standing right
+ "pt_generic_side_jumpLand_B", //standing left
+ "pt_generic_side_jumpLand_C", //sitting right
+ "pt_generic_side_jumpLand_D" ] //sitting left
+
+ return anims
+}
+
+array<string> function DropOffAISide_GetDisperseAnims()
+{
+ array<string> anims = [
+ "React_signal_thatway", //standing right
+ "React_spot_radio2", //standing left
+ "stand_2_run_45R", //sitting right
+ "stand_2_run_45L" ] //sitting left
+
+ return anims
+}
+
+array<float> function DropOffAISide_GetSeekTimes()
+{
+ array<float> anims = [
+ 9.75, //standing right
+ 10.0, //standing left
+ 10.5, //sitting right
+ 11.25 ] //sitting left
+
+ return anims
+}
+
+
+CallinData function CreateZipLineSquadDropTable( int team, int count, vector origin, vector angles, string squadName = "" )
+{
+ if ( squadName == "" )
+ squadName = MakeSquadName( team, UniqueString( "ZiplineTable" ) )
+
+ CallinData drop
+ drop.origin = origin
+ drop.yaw = angles.y
+ drop.dist = 768
+ drop.team = team
+ drop.squadname = squadName
+ SetDropTableSpawnFuncs( drop, CreateSoldier, count )
+ SetCallinStyle( drop, eDropStyle.ZIPLINE_NPC )
+
+ return drop
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/ai/_titan_npc_behavior.gnut b/Northstar.CustomServers/scripts/vscripts/ai/_titan_npc_behavior.gnut
new file mode 100644
index 000000000..347cb6441
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/ai/_titan_npc_behavior.gnut
@@ -0,0 +1,404 @@
+untyped
+
+global function TitanNpcBehavior_Init
+
+global function TitanNPC_Think
+global function TitanNPC_WaitForBubbleShield_StartAutoTitanBehavior
+global function TitanStandUp
+global function TitanKneel
+global function GetBubbleShieldDuration
+global function ShowMainTitanWeapons
+
+global function ChangedStance
+
+global function TitanWaitsToChangeStance
+global function ShouldBecomeAutoTitan
+
+function TitanNpcBehavior_Init()
+{
+ FlagInit( "DisableTitanKneelingEmbark" )
+
+ RegisterSignal( "TitanStopsThinking" )
+ RegisterSignal( "RodeoRiderChanged" )
+
+ if ( IsMultiplayer() )
+ {
+ AddCallback_OnTitanBecomesPilot( OnClassChangeBecomePilot )
+ AddCallback_OnPilotBecomesTitan( OnClassChangeBecomeTitan )
+ }
+}
+
+void function OnClassChangeBecomePilot( entity player, entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !SoulHasPassive( soul, ePassives.PAS_ENHANCED_TITAN_AI ) )
+ {
+ entity ordnanceWeapon = titan.GetOffhandWeapon( OFFHAND_ORDNANCE )
+ if ( IsValid( ordnanceWeapon ) )
+ ordnanceWeapon.AllowUse( false )
+
+ entity centerWeapon = titan.GetOffhandWeapon( OFFHAND_TITAN_CENTER )
+ if ( IsValid( centerWeapon ) )
+ centerWeapon.AllowUse( false )
+ }
+}
+
+void function OnClassChangeBecomeTitan( entity player, entity titan )
+{
+ entity soul = player.GetTitanSoul()
+
+ entity ordnanceWeapon = player.GetOffhandWeapon( OFFHAND_ORDNANCE )
+ if ( IsValid( ordnanceWeapon ) )
+ ordnanceWeapon.AllowUse( true )
+
+ entity centerWeapon = player.GetOffhandWeapon( OFFHAND_TITAN_CENTER )
+ if ( IsValid( centerWeapon ) )
+ centerWeapon.AllowUse( true )
+}
+
+float function GetBubbleShieldDuration( entity player )
+{
+ if ( PlayerHasPassive( player, ePassives.PAS_LONGER_BUBBLE ) )
+ return EMBARK_TIMEOUT + 10.0
+ else
+ return EMBARK_TIMEOUT
+
+ unreachable
+}
+
+void function TitanNPC_WaitForBubbleShield_StartAutoTitanBehavior( entity titan )
+{
+ Assert( IsAlive( titan ) )
+
+ titan.Signal( "TitanStopsThinking" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "TitanStopsThinking" )
+ titan.EndSignal( "ContextAction_SetBusy" )
+
+ entity bossPlayer = titan.GetBossPlayer()
+ if ( !bossPlayer )
+ return
+
+ OnThreadEnd(
+ function () : ( titan )
+ {
+ if ( IsAlive( titan ) )
+ {
+ titan.SetNoTarget( false )
+ thread TitanNPC_Think( titan )
+ }
+ }
+ )
+
+ titan.EndSignal( "ChangedTitanMode" )
+
+ float timeout
+ if ( SoulHasPassive( titan.GetTitanSoul(), ePassives.PAS_BUBBLESHIELD ) )
+ {
+ entity player = titan.GetBossPlayer()
+ timeout = GetBubbleShieldDuration( player )
+ }
+ else
+ {
+ timeout = 0
+ }
+
+ wait timeout
+}
+
+function TitanNPC_Think( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+
+ // JFS - Shouldn't have to check for presence of soul.
+ // The real fix for next game would be to make sure no other script can run between transferring away a titan's soul and destroying the titan.
+ // This particular bug occurred if TitanNPC_WaitForBubbleShield_StartAutoTitanBehavior() was called before soul transferred from npc to player,
+ // in which case the soul transfer killed the thread via Signal( "TitanStopsThinking" ), which causes the OnThreadEnd() to run TitanNPC_Think().
+ if ( !IsValid( soul ) )
+ return;
+
+ if ( soul.capturable || !ShouldBecomeAutoTitan( titan ) )
+ {
+ // capturable titan just kneels
+ if ( soul.GetStance() > STANCE_KNEELING )
+ thread TitanKneel( titan )
+ return
+ }
+
+ Assert( IsAlive( titan ) )
+
+ if ( !TitanCanStand( titan ) )// sets the var
+ {
+ // try to put the titan on the navmesh
+ vector ornull clampedPos = NavMesh_ClampPointForAIWithExtents( titan.GetOrigin(), titan, < 100, 100, 100 > )
+ if ( clampedPos != null )
+ {
+ expect vector( clampedPos )
+ titan.SetOrigin( clampedPos )
+ TitanCanStand( titan )
+ }
+ }
+
+ if ( !titan.GetBossPlayer() )
+ {
+ titan.Signal( "TitanStopsThinking" )
+ return
+ }
+
+ if ( "disableAutoTitanConversation" in titan.s ) //At this point the Titan has stood up and is ready to talk
+ delete titan.s.disableAutoTitanConversation
+
+ titan.EndSignal( "TitanStopsThinking" )
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "player_embarks_titan" )
+
+ // kneel in certain circumstances
+ for ( ;; )
+ {
+ if ( !ChangedStance( titan ) )
+ waitthread TitanWaitsToChangeStance( titan )
+ }
+}
+
+bool function ChangedStance( entity titan )
+{
+ if ( GetEmbarkDisembarkPlayer( titan ) )
+ return false
+
+ local soul = titan.GetTitanSoul()
+
+ // in a scripted sequence?
+ if ( IsValid( titan.GetParent() ) )
+ return false
+
+ if ( soul.GetStance() > STANCE_KNEELING )
+ {
+ if ( TitanShouldKneel( titan ) )
+ {
+ //waitthread PlayAnimGravity( titan, "at_MP_stand2knee_straight" )
+ waitthread KneelToShowRider( titan )
+ thread PlayAnim( titan, "at_MP_embark_idle_blended" )
+ SetStanceKneel( soul )
+ return true
+ }
+ }
+ else
+ {
+ if ( !TitanShouldKneel( titan ) && TitanCanStand( titan ) )
+ {
+ waitthread TitanStandUp( titan )
+ return true
+ }
+
+ if ( soul.GetStance() == STANCE_KNEEL )
+ {
+ thread PlayAnim( titan, "at_MP_embark_idle_blended" )
+ }
+ }
+
+ return false
+}
+
+function TitanShouldKneel( entity titan )
+{
+ local soul = titan.GetTitanSoul()
+
+ if ( soul.capturable )
+ return true
+
+ //if( HasEnemyRodeoRiders( titan ) )
+ // return true
+ if ( !TitanCanStand( titan ) )
+ return false
+
+ if ( !ShouldBecomeAutoTitan( titan ) )
+ return true
+
+ return false
+}
+
+function TitanWaitsToChangeStance( titan )
+{
+ local soul = titan.GetTitanSoul()
+ soul.EndSignal( "RodeoRiderChanged" )
+
+ titan.EndSignal( "OnAnimationInterrupted" )
+ titan.EndSignal( "OnAnimationDone" )
+
+ WaitForever()
+}
+
+function TitanStandUp( titan )
+{
+ local soul = titan.GetTitanSoul()
+ // stand up
+ titan.s.standQueued = false
+ ShowMainTitanWeapons( titan )
+ titan.Anim_Stop()
+ waitthread PlayAnimGravity( titan, "at_hotdrop_quickstand" )
+ Assert( soul == titan.GetTitanSoul() )
+ SetStanceStand( soul )
+}
+
+
+void function TitanKneel( entity titan )
+{
+ titan.EndSignal( "TitanStopsThinking" )
+ titan.EndSignal( "OnDeath" )
+ Assert( IsAlive( titan ) )
+ local soul = titan.GetTitanSoul()
+
+ waitthread KneelToShowRider( titan )
+
+ thread PlayAnim( titan, "at_MP_embark_idle_blended" )
+ SetStanceKneel( soul )
+}
+
+
+/*
+function TitanWaittillShouldStand( entity titan )
+{
+ //Don't wait if player is dead - titan should just stand up immediately
+ local player = titan.GetBossPlayer()
+ if ( !IsAlive( player ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+
+ for ( ;; )
+ {
+ if ( TitanCanStand( titan ) )
+ break
+
+ wait 5
+ }
+ if ( titan.s.standQueued )
+ return
+
+ titan.WaitSignal( "titanStand" )
+}
+*/
+
+void function KneelToShowRider( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ entity player = soul.GetBossPlayer()
+ local animation
+ local yawDif
+
+ //if ( IsAlive( player ) )
+ //{
+ // local table = GetFrontRightDots( titan, player )
+ //
+ // local dotForward = Table.dotForward
+ // local dotRight = Table.dotRight
+ //
+ //// DebugDrawLine( titanOrg, titanOrg + titan.GetForwardVector() * 200, 255, 0, 0, true, 5 )
+ //// DebugDrawLine( titanOrg, titanOrg + vecToEnt * 200, 0, 255, 0, true, 5 )
+ //
+ // if ( dotForward > 0.88 )
+ // {
+ // animation = "at_MP_stand2knee_L90"
+ // yawDif = 0
+ // }
+ // else
+ // if ( dotForward < -0.88 )
+ // {
+ // animation = "at_MP_stand2knee_R90"
+ // yawDif = 180
+ // }
+ // else
+ // if ( dotRight > 0 )
+ // {
+ // animation = "at_MP_stand2knee_straight"
+ // yawDif = 90
+ // }
+ // else
+ // {
+ // animation = "at_MP_stand2knee_180"
+ // yawDif = -90
+ // }
+ //}
+ //else
+ {
+ animation = "at_MP_stand2knee_straight"
+ yawDif = 0
+ }
+
+ thread HideOgreMainWeaponFromEnemies( titan )
+
+ if ( !IsAlive( player ) )
+ {
+ waitthread PlayAnimGravity( titan, animation )
+ return
+ }
+
+ local titanOrg = titan.GetOrigin()
+ local playerOrg = player.GetOrigin()
+
+ /*
+ local vec = playerOrg - titanOrg
+ vec.z = 0
+
+ local angles = VectorToAngles( vec )
+
+ angles.y += yawDif
+ */
+
+ local angles = titan.GetAngles()
+
+ titan.Anim_ScriptedPlayWithRefPoint( animation, titanOrg, angles, 0.5 )
+ titan.Anim_EnablePlanting()
+
+ WaittillAnimDone( titan )
+}
+
+function HideOgreMainWeaponFromEnemies( titan )
+{
+ expect entity( titan )
+
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ wait 1.0
+
+ entity soul = titan.GetTitanSoul()
+
+ Assert( IsValid( soul ) )
+
+ local titanSubClass = GetSoulTitanSubClass( soul )
+ if ( titanSubClass == "ogre" )
+ {
+ if ( IsValid( GetEnemyRodeoPilot( titan ) ) )
+ HideMainWeaponsFromEnemies( titan )
+ }
+}
+
+function HideMainWeaponsFromEnemies( titan )
+{
+ local weapons = titan.GetMainWeapons()
+ foreach ( weapon in weapons )
+ weapon.kv.visibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
+}
+
+function ShowMainTitanWeapons( titan )
+{
+ local weapons = titan.GetMainWeapons()
+ foreach ( weapon in weapons )
+ {
+ weapon.kv.visibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ }
+}
+
+bool function ShouldBecomeAutoTitan( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+
+ if ( soul != null )
+ {
+ if ( SoulHasPassive( soul, ePassives.PAS_ENHANCED_TITAN_AI ) )
+ return true
+ }
+
+ return ( !PROTO_AutoTitansDisabled() )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/burnmeter/_burnmeter.gnut b/Northstar.CustomServers/scripts/vscripts/burnmeter/_burnmeter.gnut
new file mode 100644
index 000000000..8e1cb71ff
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/burnmeter/_burnmeter.gnut
@@ -0,0 +1,42 @@
+global function BurnMeter_Init
+global function InitBurnMeterPersistentData
+global function BurnMeter_GiveRewardDirect
+global function RunBurnCardUseFunc
+global function UseBurnCardWeapon
+global function UseBurnCardWeaponInCriticalSection
+global function GetBurnCardWeaponSkin
+
+void function BurnMeter_Init()
+{
+
+}
+
+void function InitBurnMeterPersistentData(entity player)
+{
+
+}
+
+void function BurnMeter_GiveRewardDirect( entity player, string itemRef )
+{
+
+}
+
+void function RunBurnCardUseFunc(entity player, string itemRef)
+{
+
+}
+
+void function UseBurnCardWeapon( entity weapon, entity ownerPlayer )
+{
+
+}
+
+void function UseBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer )
+{
+
+}
+
+int function GetBurnCardWeaponSkin(entity weapon)
+{
+ return 0
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/class/CHardPointEntity.nut b/Northstar.CustomServers/scripts/vscripts/class/CHardPointEntity.nut
new file mode 100644
index 000000000..4eb979558
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/class/CHardPointEntity.nut
@@ -0,0 +1,15 @@
+untyped
+
+global function CodeCallback_RegisterClass_C_HardPointEntity
+
+function CodeCallback_RegisterClass_C_HardPointEntity()
+{
+ /*C_HardPointEntity.ClassName <- "C_HardPointEntity"
+
+
+ function C_HardPointEntity::Enabled()
+ {
+ return this.GetHardpointID() >= 0
+ }
+ #document( "C_HardPointEntity::Enabled", "Returns true if this hardpoint is enabled" )*/
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/class/cai_basenpc.nut b/Northstar.CustomServers/scripts/vscripts/class/cai_basenpc.nut
new file mode 100644
index 000000000..631e01fc0
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/class/cai_basenpc.nut
@@ -0,0 +1,272 @@
+untyped
+
+global function IsCrawling
+global function CodeCallback_RegisterClass_CAI_BaseNPC
+global function SetSpawnOption_AISettings
+global function SetSpawnOption_Alert
+global function SetSpawnOption_NotAlert
+global function SetSpawnOption_Ordnance
+global function SetSpawnOption_OwnerPlayer
+global function SetSpawnOption_Sidearm
+global function SetSpawnOption_SquadName
+global function SetSpawnOption_Special
+global function SetSpawnOption_Melee
+global function SetSpawnOption_CoreAbility
+global function SetSpawnOption_Antirodeo
+global function SetSpawnOption_Titanfall
+global function SetSpawnOption_TitanSoulPassive1
+global function SetSpawnOption_TitanSoulPassive2
+global function SetSpawnOption_TitanSoulPassive3
+global function SetSpawnOption_TitanSoulPassive4
+global function SetSpawnOption_TitanSoulPassive5
+global function SetSpawnOption_TitanSoulPassive6
+global function SetSpawnOption_Warpfall
+global function SetSpawnOption_Weapon
+global function SetSpawnOption_NPCTitan
+global function SetSpawnOption_TitanLoadout
+
+function CodeCallback_RegisterClass_CAI_BaseNPC()
+{
+ #document( "SetSpawnOption_AISettings", "Specify AI Setting" )
+ #document( "SetSpawnOption_Alert", "Enable spawn alerted" )
+ #document( "SetSpawnOption_NotAlert", "Enable spawn alerted" )
+ #document( "SetSpawnOption_Ordnance", "Specify spawn ordnance" )
+ #document( "SetSpawnOption_OwnerPlayer", "This titan will be the auto titan of this player" )
+ #document( "SetSpawnOption_SquadName", "Specify spawn squadname" )
+ #document( "SetSpawnOption_Special", "Specify spawn tactical ability" )
+ #document( "SetSpawnOption_Titanfall", "npc titan will spawn via titanfall" )
+ #document( "SetSpawnOption_TitanSoulPassive1", "Set this passive on the titan soul" )
+ #document( "SetSpawnOption_TitanSoulPassive2", "Set this passive on the titan soul" )
+ #document( "SetSpawnOption_TitanSoulPassive3", "Set this passive on the titan soul" )
+ #document( "SetSpawnOption_TitanSoulPassive4", "Set this passive on the titan soul" )
+ #document( "SetSpawnOption_TitanSoulPassive5", "Set this passive on the titan soul" )
+ #document( "SetSpawnOption_TitanSoulPassive6", "Set this passive on the titan soul" )
+ #document( "SetSpawnOption_Warpfall", "Titan or super spectre will spawn via warpsfall" )
+ #document( "SetSpawnOption_Weapon", "Specify spawn weapon and mods" )
+ #document( "SetSpawnOption_NPCTitan", "Spawn titan of type" )
+
+
+ //printl( "Class Script: CAI_BaseNPC" )
+
+ CAI_BaseNPC.ClassName <- "CAI_BaseNPC"
+ CAI_BaseNPC.supportsXRay <- null
+
+ CAI_BaseNPC.mySpawnOptions_aiSettings <- null
+ CAI_BaseNPC.mySpawnOptions_alert <- null
+ CAI_BaseNPC.mySpawnOptions_sidearm <- null
+ CAI_BaseNPC.mySpawnOptions_titanfallSpawn <- null
+ CAI_BaseNPC.mySpawnOptions_warpfallSpawn <- null
+ CAI_BaseNPC.mySpawnOptions_routeTD <- null
+ CAI_BaseNPC.mySpawnOptions_ownerPlayer <- null
+ CAI_BaseNPC.executedSpawnOptions <- null
+
+ function CAI_BaseNPC::HasXRaySupport()
+ {
+ return ( this.supportsXRay != null )
+ }
+
+ function CAI_BaseNPC::ForceCombat()
+ {
+ this.FireNow( "UpdateEnemyMemory", "!player" )
+ }
+ #document( CAI_BaseNPC, "ForceCombat", "Force into combat state by updating NPC's memory of the player." )
+
+ function CAI_BaseNPC::InCombat()
+ {
+ entity enemy = expect entity( this ).GetEnemy()
+ if ( !IsValid( enemy ) )
+ return false
+
+ return this.CanSee( enemy )
+ }
+ #document( CAI_BaseNPC, "InCombat", "Returns true if NPC is in combat" )
+}
+
+
+
+function SetSpawnOption_AISettings( entity npc, setting )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.mySpawnOptions_aiSettings = setting
+}
+
+function SetSpawnOption_Alert( entity npc )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.mySpawnOptions_alert = true
+}
+
+function SetSpawnOption_NotAlert( entity npc )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.mySpawnOptions_alert = false
+}
+
+void function SetSpawnOption_Weapon( entity npc, string weapon, array<string> mods = [] )
+{
+ Assert( weapon != "", "Tried to assign no weapon as a spawn weapon" )
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+
+ if ( npc.IsTitan() )
+ {
+ npc.ai.titanSpawnLoadout.primary = weapon
+ npc.ai.titanSpawnLoadout.primaryMods = mods
+ }
+ else
+ {
+ NPCDefaultWeapon spawnoptionsweapon
+ spawnoptionsweapon.wep = weapon
+ spawnoptionsweapon.mods = mods
+
+ npc.ai.mySpawnOptions_weapon = spawnoptionsweapon
+ }
+}
+
+void function SetSpawnOption_Sidearm( entity npc, string weapon, array<string> mods = [])
+{
+ Assert( weapon != "", "Tried to assign no weapon as a spawn weapon" )
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+
+ if ( !npc.IsTitan() )
+ npc.mySpawnOptions_sidearm = { wep = weapon, mods = mods }
+}
+
+void function SetSpawnOption_Ordnance( entity npc, string ordnance, array<string> mods = [] )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.ordnance = ordnance
+ npc.ai.titanSpawnLoadout.ordnanceMods = mods
+}
+
+void function SetSpawnOption_Special( entity npc, string special, array<string> mods = [] )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.special = special
+ npc.ai.titanSpawnLoadout.specialMods = mods
+}
+
+void function SetSpawnOption_Antirodeo( entity npc, string antirodeo, array<string> mods = [] )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.antirodeo = antirodeo
+ npc.ai.titanSpawnLoadout.antirodeoMods = mods
+}
+
+void function SetSpawnOption_Melee( entity npc, string melee )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.melee = melee
+}
+
+void function SetSpawnOption_CoreAbility( entity npc, string core )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.coreAbility = core
+}
+
+function SetSpawnOption_SquadName( entity npc, squadName )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.kv.squadname = squadName
+}
+
+function SetSpawnOption_Titanfall( entity npc )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ Assert( npc.IsTitan(), "npc is for titans only" )
+ npc.mySpawnOptions_titanfallSpawn = true
+}
+
+function SetSpawnOption_Warpfall( entity npc )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ Assert( npc.IsTitan() || npc.GetClassName() == "npc_super_spectre", "npc is for titans and superspectres only" )
+ npc.mySpawnOptions_warpfallSpawn = true
+}
+
+function SetSpawnOption_OwnerPlayer( entity npc, entity player )
+{
+ Assert( IsValid( player ) )
+ Assert( player.IsPlayer() )
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.mySpawnOptions_ownerPlayer = player
+}
+
+function SetSpawnOption_TitanSoulPassive1( entity npc, string passive )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.passive1 = passive
+}
+
+function SetSpawnOption_TitanSoulPassive2( entity npc, string passive )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.passive2 = passive
+}
+
+function SetSpawnOption_TitanSoulPassive3( entity npc, string passive )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.passive3 = passive
+}
+
+function SetSpawnOption_TitanSoulPassive4( entity npc, string passive )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.passive4 = passive
+}
+
+function SetSpawnOption_TitanSoulPassive5( entity npc, string passive )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.passive5 = passive
+}
+
+function SetSpawnOption_TitanSoulPassive6( entity npc, string passive )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout.passive6 = passive
+}
+
+function SetSpawnOption_NPCTitan( entity npc, int type )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( npc.IsTitan(), npc + " is not a Titan!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.bossTitanType = type
+}
+
+
+function SetSpawnOption_TitanLoadout( entity npc, TitanLoadoutDef loadout )
+{
+ Assert( IsValid( npc ) && npc.IsNPC(), npc + " is not an npc!" )
+ Assert( npc.IsTitan(), npc + " is not a Titan!" )
+ Assert( !npc.executedSpawnOptions, npc + " tried to set spawn options after npc was dispatchspawned." )
+ npc.ai.titanSpawnLoadout = loadout
+}
+
+bool function IsCrawling( entity npc )
+{
+ return npc.ai.crawling
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/class/cbasecombatcharacter.nut b/Northstar.CustomServers/scripts/vscripts/class/cbasecombatcharacter.nut
new file mode 100644
index 000000000..11018ceab
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/class/cbasecombatcharacter.nut
@@ -0,0 +1,28 @@
+untyped
+
+global function CodeCallback_RegisterClass_CBaseCombatCharacter
+
+function CodeCallback_RegisterClass_CBaseCombatCharacter()
+{
+ CBaseCombatCharacter.ClassName <- "CBaseCombatCharacter"
+
+ RegisterSignal( "ContextAction_SetBusy" ) // signalled from ContextAction_SetBusy() in code
+
+
+ /*
+ CBaseCombatCharacter.__SetActiveWeaponByName <- CBaseCombatCharacter.SetActiveWeaponByName
+ function CBaseCombatCharacter::SetActiveWeaponByName( weapon )
+ {
+ printt( "set active weapon " + weapon + " for " + this )
+ return this.__SetActiveWeaponByName( weapon )
+ }
+
+ CBaseCombatCharacter.__TakeWeapon <- CBaseCombatCharacter.TakeWeapon
+ function CBaseCombatCharacter::TakeWeapon( weapon )
+ {
+ // printt( "Take weapon " + weapon + " from " + this )
+ return this.__TakeWeapon( weapon )
+ }
+
+ */
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/class/cbaseentity.nut b/Northstar.CustomServers/scripts/vscripts/class/cbaseentity.nut
new file mode 100644
index 000000000..08d2b2e17
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/class/cbaseentity.nut
@@ -0,0 +1,229 @@
+untyped
+
+global function CodeCallback_RegisterClass_CBaseEntity
+
+//=========================================================
+// CBaseEntity
+// Properties and methods added here can be accessed on all script entities
+//=========================================================
+
+#if DEV
+table __scriptVarDelegate = {}
+#endif
+
+function CodeCallback_RegisterClass_CBaseEntity()
+{
+ //printl( "Class Script: CBaseEntity" )
+
+ CBaseEntity.ClassName <- "CBaseEntity"
+
+ // Script variables; initializing these to something other than "null" will cause them to be treated as a
+ // static variable on the class instead of a unique variable on each instance.
+ CBaseEntity.s <- null
+
+ CBaseEntity.funcsByString <- null
+
+ CBaseEntity.useFunction <- null // should match on server/client
+
+ CBaseEntity._entityVars <- null
+
+ CBaseEntity.invulnerable <- 0
+
+ // this replacement could just be made in the scripts where this call happens,
+ // but there's too many for me to replace right now
+ CBaseEntity.__KeyValueFromString <- CBaseEntity.SetValueForKey
+ CBaseEntity.__KeyValueFromInt <- CBaseEntity.SetValueForKey
+
+ function CBaseEntity::constructor()
+ {
+ #if DEV
+ this.s = delegate __scriptVarDelegate : {}
+ #else
+ this.s = {}
+ #endif
+
+ this.useFunction = UseReturnTrue // default use function
+
+ this.funcsByString = {}
+ }
+
+ #if DEV
+ function __scriptVarDelegate::_typeof()
+ {
+ return "ScriptVariableTable"
+ }
+
+ disableoverwrite( __scriptVarDelegate )
+ #endif
+
+ /*
+ Do not delete, thanks.
+
+ CBaseEntity.__SetOrigin <- CBaseEntity.SetOrigin
+ function CBaseEntity::SetOrigin( origin )
+ {
+ if ( this.GetTargetName() == "xauto_1" )
+ {
+ printl( "\n\n" )
+ DumpStack()
+ }
+ this.__SetOrigin( origin )
+ }
+ */
+
+ function CBaseEntity::_typeof()
+ {
+ return format( "[%d] %s: %s", this.entindex(), this.GetClassName(), this.GetTargetName() )
+ }
+
+ function CBaseEntity::Get( val )
+ {
+ return this.GetValueForKey( val )
+ }
+
+ function CBaseEntity::Set( key, val )
+ {
+ return this.SetValueForKey( key, val )
+ }
+
+ // This exists in code too and is only here for untyped entity variables
+ function CBaseEntity::WaitSignal( signalID )
+ {
+ return WaitSignal( this, signalID )
+ }
+
+ // This exists in code too and is only here for untyped entity variables
+ function CBaseEntity::EndSignal( signalID )
+ {
+ EndSignal( this, signalID )
+ }
+
+ // This exists in code too and is only here for untyped entity variables
+ function CBaseEntity::Signal( signalID, results = null )
+ {
+ Signal( this, signalID, results )
+ }
+
+ function CBaseEntity::DisableDraw()
+ {
+ this.FireNow( "DisableDraw" )
+ }
+ #document( "CBaseEntity::DisableDraw", "consider this the mega hide" )
+
+ function CBaseEntity::EnableDraw()
+ {
+ this.FireNow( "EnableDraw" )
+ }
+ #document( "CBaseEntity::EnableDraw", "its back!" )
+
+
+ // --------------------------------------------------------
+ function CBaseEntity::Kill_Deprecated_UseDestroyInstead( time = 0 )
+ {
+ EntFireByHandle( this, "kill", "", time, null, null )
+ }
+ #document( "CBaseEntity::Kill_Deprecated_UseDestroyInstead", "Kill this entity: this function is deprecated because it has a one-frame delay; instead, call ent.Destroy()" )
+
+ // --------------------------------------------------------
+ function CBaseEntity::Fire( output, param = "", delay = 0, activator = null, caller = null )
+ {
+ Assert( type( output ) == "string", "output type " + type( output ) + " is not a string" )
+ EntFireByHandle( this, output, string( param ), delay, activator, caller )
+ }
+ #document( "CBaseEntity::Fire", "Fire an output on this entity, with optional parm and delay" )
+
+ function CBaseEntity::FireNow( output, param = "", activator = null, caller = null )
+ {
+ Assert( type( output ) == "string" )
+ EntFireByHandleNow( this, output, string( param ), activator, caller )
+ }
+ #document( "CBaseEntity::FireNow", "Fire an output on this entity, with optional parm and delay (synchronous)" )
+
+ // --------------------------------------------------------
+ function CBaseEntity::AddOutput( outputName, target, inputName, parameter = "", delay = 0, maxFires = 0 )
+ {
+ local targetName = target
+
+ if ( type( target ) != "string" )
+ {
+ Assert( type( target ) == "instance" )
+ targetName = target.GetTargetName()
+ Assert( targetName.len(), "AddOutput: targetted entity must have a name!" )
+ }
+ Assert( targetName.len(), "Attemped to AddOutput on an unnamed target" )
+
+ local addOutputString = outputName + " " + targetName + ":" + inputName + ":" + parameter + ":" + delay + ":" + maxFires
+ //printl(" Added output string: " + addOutputString )
+
+ EntFireByHandle( this, "AddOutput", addOutputString, 0, null, null )
+ }
+ #document( "CBaseEntity::AddOutput", "Connects an output on this entity to an input on another entity via code. The \"target\" can be a name or a named entity." )
+
+ /*
+ function MoveTo()
+ */
+
+ function CBaseEntity::MoveTo( dest, time, easeIn = 0, easeOut = 0 )
+ {
+ if ( this.GetClassName() == "script_mover" )
+ {
+ this.NonPhysicsMoveTo( dest, time, easeIn, easeOut )
+ }
+ else
+ {
+ this.SetOrigin( dest )
+ CodeWarning( "Used moveto on non script_mover: " + this.GetClassName() + ", " + this )
+ }
+ }
+ #document( "CBaseEntity::MoveTo", "Move to the specified origin over time with ease in and ease out." )
+
+ /*
+ function RotateTo()
+ */
+
+ function CBaseEntity::RotateTo( dest, time, easeIn = 0, easeOut = 0 )
+ {
+ if ( this.GetClassName() == "script_mover" )
+ {
+ this.NonPhysicsRotateTo( dest, time, easeIn, easeOut )
+ }
+ else
+ {
+ this.SetAngles( dest )
+ CodeWarning( "Used rotateto on non script_mover: " + this.GetClassName() + ", " + this )
+ }
+ }
+ #document( "CBaseEntity::RotateTo", "Rotate to the specified angles over time with ease in and ease out." )
+
+ function CBaseEntity::AddVar( varname, value )
+ {
+ Assert( !( varname in this.s ), "Tried to add variable to " + this + " that already existed: " + varname )
+
+ this.s[ varname ] <- value
+ }
+
+ function CBaseEntity::CreateStringForFunction( func )
+ {
+ // this is a general purpose function that returns a string which, when executed,
+ // runs the given function on this entity.
+ // the function must be called (or the entity deleted) at some point to avoid leaking the new slot we make in this Table.
+
+ Assert( type( func ) == "function" )
+
+ string newGuid = UniqueString()
+ this.funcsByString[newGuid] <- func
+
+ return "_RunFunctionByString( \"" + newGuid + "\" )"
+ }
+
+ function _RunFunctionByString( guid )
+ {
+ local activator = this.activator
+ Assert( activator.funcsByString[guid] )
+
+ local func = activator.funcsByString[guid].bindenv( activator.scope() )
+ delete activator.funcsByString[guid]
+
+ func()
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/class/cplayer.nut b/Northstar.CustomServers/scripts/vscripts/class/cplayer.nut
new file mode 100644
index 000000000..b9f8f7eb8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/class/cplayer.nut
@@ -0,0 +1,355 @@
+untyped
+
+
+global function CodeCallback_RegisterClass_CPlayer
+global function PlayerDropsScriptedItems
+global function IsDemigod
+global function EnableDemigod
+global function DisableDemigod
+
+int __nextInputHandle = 0
+
+
+global struct PlayerSlowDownEffect
+{
+ float slowEndTime
+ float speedCap // max speed multiplier if this slow effect is active
+}
+
+function CodeCallback_RegisterClass_CPlayer()
+{
+ //printl( "Class Script: CPlayer" )
+
+ CPlayer.ClassName <- "CPlayer"
+ CPlayer.hasSpawned <- null
+ CPlayer.hasConnected <- null
+ CPlayer.isSpawning <- null
+ CPlayer.isSpawningHotDroppingAsTitan <- false
+ CPlayer.disableWeaponSlots <- false
+ CPlayer.supportsXRay <- null
+
+ CPlayer.lastTitanTime <- 0
+
+ CPlayer.globalHint <- null
+ CPlayer.playerClassData <- null
+ CPlayer.escalation <- null
+ CPlayer.pilotAbility <- null
+ CPlayer.titansBuilt <- 0
+ CPlayer.spawnTime <- 0
+ CPlayer.serverFlags <- 0
+ CPlayer.watchingKillreplayEndTime <- 0.0
+ CPlayer.cloakedForever <- false
+ CPlayer.stimmedForever <- false
+
+ RegisterSignal( "OnRespawnPlayer" )
+ RegisterSignal( "NewViewAnimEntity" )
+ RegisterSignal( "PlayerDisconnected" )
+
+ function CPlayer::constructor()
+ {
+ CBaseEntity.constructor()
+ }
+
+ function CPlayer::RespawnPlayer( ent )
+ {
+ this.Signal( "OnRespawnPlayer", { ent = ent } )
+
+ // hack. Players should clear all these on spawn.
+ this.ViewOffsetEntity_Clear()
+ ClearPlayerAnimViewEntity( expect entity( this ) )
+ this.spawnTime = Time()
+
+ this.ClearReplayDelay()
+ this.ClearViewEntity()
+
+ // titan melee can set these vars, and they need to clear on respawn:
+ this.SetOwner( null )
+ this.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+
+
+ Assert( !this.GetParent(), this + " should not have a parent yet! - Parent: " + this.GetParent() )
+ Assert( this.s.respawnCount <= 1 || IsMultiplayer(), "Tried to respawn in single player, see callstack" )
+ this.Code_RespawnPlayer( ent )
+ }
+
+ /*
+ CPlayer.__SetTrackEntity <- CPlayer.SetTrackEntity
+ function CPlayer::SetTrackEntity( ent )
+ {
+ printl( "\nTime " + Time() + " Ent " + ent )
+
+ DumpStack()
+ this.__SetTrackEntity( ent )
+ }
+ */
+
+ function CPlayer::GetDropEntForPoint( origin )
+ {
+ return null
+ }
+
+
+ function CPlayer::GetPlayerClassData( myClass )
+ {
+ Assert( myClass in this.playerClassData, myClass + " not in playerClassData" )
+ return this.playerClassData[ myClass ]
+ }
+
+
+ function CPlayer::InitMPClasses()
+ {
+ this.playerClassData = {}
+
+ Titan_AddPlayer( this )
+ Wallrun_AddPlayer( this )
+ }
+
+
+ function CPlayer::InitSPClasses()
+ {
+ this.playerClassData = {}
+ SetTargetName( expect entity( this ), expect string( this.GetTargetName() + this.entindex() ) )
+
+ Titan_AddPlayer( this )
+ }
+
+
+ // function SpawnAsClass()
+ function CPlayer::SpawnAsClass( className = null )
+ {
+ if ( !className )
+ {
+ className = this.GetPlayerClass()
+ }
+
+ switch ( className )
+ {
+ case level.pilotClass:
+ Wallrun_OnPlayerSpawn( this )
+ break
+
+ default:
+ Assert( 0, "Tried to spawn as unsupported " + className )
+ }
+ }
+
+
+ function CPlayer::GiveScriptWeapon( weaponName, equipSlot = null )
+ {
+ this.scope().GiveScriptWeapon( weaponName, equipSlot )
+ }
+
+ function CPlayer::OnDeathAsClass( damageInfo )
+ {
+ switch ( this.GetPlayerClass() )
+ {
+ case "titan":
+ Titan_OnPlayerDeath( expect entity( this ), damageInfo )
+ break
+
+ case level.pilotClass:
+ Wallrun_OnPlayerDeath( expect entity( this ), damageInfo )
+ break
+ }
+ }
+
+ function CPlayer::Disconnected()
+ {
+ this.Signal( "_disconnectedInternal" )
+ svGlobal.levelEnt.Signal( "PlayerDisconnected" )
+
+ if ( HasSoul( expect entity( this ) ) )
+ {
+ thread SoulDies( expect entity( this ).GetTitanSoul(), null )
+ }
+
+ entity titan = GetPlayerTitanInMap( expect entity( this ) )
+ if ( IsAlive( titan ) && titan.IsNPC() )
+ {
+ local soul = titan.GetTitanSoul()
+ if ( IsValid( soul ) && soul.followOnly )
+ FreeAutoTitan( titan )
+ else
+ titan.Die( null, null, { damageSourceId = eDamageSourceId.damagedef_suicide } )
+ // titan.Die()
+ }
+
+ PROTO_CleanupTrackedProjectiles( expect entity( this ) )
+
+ if ( this.globalHint != null )
+ {
+ this.globalHint.Kill_Deprecated_UseDestroyInstead()
+ this.globalHint = null
+ }
+ }
+
+
+ function CPlayer::GetClassDataEnts()
+ {
+ local ents = []
+ local added
+
+ if ( this.playerClassData == null )
+ return ents;
+
+ foreach ( ent in this.playerClassData )
+ {
+ added = false
+
+ foreach ( newent in ents )
+ {
+ if ( newent == ent )
+ {
+ added = true
+ break
+ }
+ }
+
+ if ( !added )
+ ents.append( ent )
+ }
+
+ return ents
+ }
+
+
+ function CPlayer::CleanupMPClasses()
+ {
+ }
+
+ function CPlayer::HasXRaySupport()
+ {
+ return ( this.supportsXRay != null )
+ }
+
+ function CPlayer::GiveExtraWeaponMod( mod )
+ {
+ if ( this.HasExtraWeaponMod( mod ) )
+ return
+
+ local mods = this.GetExtraWeaponMods()
+ mods.append( mod )
+
+ this.SetExtraWeaponMods( mods )
+ }
+
+
+ function CPlayer::HasExtraWeaponMod( mod )
+ {
+ local mods = this.GetExtraWeaponMods()
+ foreach( _mod in mods )
+ {
+ if ( _mod == mod )
+ return true
+ }
+ return false
+ }
+
+
+ function CPlayer::TakeExtraWeaponMod( mod )
+ {
+ if ( !this.HasExtraWeaponMod( mod ) )
+ return
+
+ local mods = this.GetExtraWeaponMods()
+ mods.fastremovebyvalue( mod )
+
+ this.SetExtraWeaponMods( mods )
+ }
+
+ function CPlayer::ClearExtraWeaponMods()
+ {
+ this.SetExtraWeaponMods( [] )
+ }
+
+
+ function CPlayer::SetPlayerPilotSettings( settingsName )
+ {
+ this.SetPlayerRequestedSettings( settingsName )
+ }
+
+ function CPlayer::RecordLastMatchContribution( contribution )
+ {
+ // replace with code function
+ }
+
+ function CPlayer::RecordLastMatchPerformance( matchPerformance )
+ {
+ // replace with code function
+ }
+
+ function CPlayer::RecordSkill( skill )
+ {
+ // replace with code function
+ this.SetPersistentVar( "ranked.recordedSkill", skill )
+ }
+
+ function CPlayer::SetPlayerSettings( settings )
+ {
+ local oldPlayerClass = CPlayer.GetPlayerClass()
+
+ CPlayer.SetPlayerSettingsWithMods( settings, [] )
+
+ this.RunSettingsChangedFuncs( settings, oldPlayerClass )
+ }
+
+ function CPlayer::SetPlayerSettingsFromDataTable( pilotDataTable )
+ {
+ local oldPlayerClass = CPlayer.GetPlayerClass()
+
+ local settings = pilotDataTable.playerSetFile
+
+ local mods = pilotDataTable.playerSetFileMods
+
+ this.SetPlayerSettingsWithMods( settings, mods )
+
+ this.RunSettingsChangedFuncs( settings, oldPlayerClass )
+ }
+
+ function CPlayer::RunSettingsChangedFuncs( settings, oldPlayerClass )
+ {
+ if ( IsAlive( expect entity( this ) ) && !this.IsTitan() && GetCurrentPlaylistVarFloat( "pilot_health_multiplier", 0.0 ) != 0.0 )
+ {
+ float pilotHealthMultiplier = GetCurrentPlaylistVarFloat( "pilot_health_multiplier", 1.0 )
+ int pilotMaxHealth = int( this.GetMaxHealth() * pilotHealthMultiplier )
+ this.SetMaxHealth( pilotMaxHealth )
+ this.SetHealth( pilotMaxHealth )
+ }
+
+ if ( this.IsTitan() )
+ {
+ entity soul = expect entity ( this.GetTitanSoul() )
+ local index = PlayerSettingsNameToIndex( settings )
+ soul.SetPlayerSettingsNum( index )
+
+ foreach ( func in svGlobal.soulSettingsChangeFuncs )
+ {
+ func( soul )
+ }
+ }
+ }
+}
+
+void function PlayerDropsScriptedItems( entity player )
+{
+ foreach ( callbackFunc in svGlobal.onPlayerDropsScriptedItemsCallbacks )
+ callbackFunc( player )
+}
+
+bool function IsDemigod( entity player )
+{
+ return player.p.demigod
+}
+
+void function EnableDemigod( entity player )
+{
+ Assert( player.IsPlayer() )
+ player.p.demigod = true
+}
+
+void function DisableDemigod( entity player )
+{
+ Assert( player.IsPlayer() )
+ player.p.demigod = false
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/class/ctitansoul.nut b/Northstar.CustomServers/scripts/vscripts/class/ctitansoul.nut
new file mode 100644
index 000000000..6f5ddb3e2
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/class/ctitansoul.nut
@@ -0,0 +1,50 @@
+untyped
+
+global function CodeCallback_RegisterClass_CTitanSoul
+
+function CodeCallback_RegisterClass_CTitanSoul()
+{
+ CTitanSoul.ClassName <- "CTitanSoul"
+
+ // all soul-specific vars should be created here
+ CTitanSoul.lastAttackInfo <- null
+ CTitanSoul.hijackProgress <- null
+ CTitanSoul.lastHijackTime <- null
+ CTitanSoul.capturable <- null
+ CTitanSoul.followOnly <- null
+ CTitanSoul.passives <- null
+ CTitanSoul.createTime <- null
+ CTitanSoul.rodeoRiderTracker <- null
+ CTitanSoul.doomedTime <- null
+ CTitanSoul.nextRegenTime <- 0.0
+ CTitanSoul.nextHealthRegenTime <- 0.0
+ CTitanSoul.rodeoReservedSlots <- null
+
+ // the functions below should not change
+
+ function CTitanSoul::constructor()
+ {
+ CBaseEntity.constructor()
+
+ this.lastAttackInfo = { time = 0 }
+ this.passives = arrayofsize( GetNumPassives(), false )
+ this.createTime = Time()
+ this.doomedTime = null
+ this.rodeoRiderTracker = {} // all players that rode this titan, so they cant get multiple score events
+ this.capturable = false
+ this.followOnly = false
+ this.rodeoReservedSlots = arrayofsize( PROTOTYPE_DEFAULT_TITAN_RODEO_SLOTS, null ) //hardcoded 3 slots for now!
+ }
+
+
+ // function SoulDeath()
+ function CTitanSoul::SoulDestroy()
+ {
+ // transfer the soul away from the last owner
+ entity titan = expect entity( this.GetTitan() )
+ foreach ( func in svGlobal.soulTransferFuncs )
+ {
+ func( expect entity( this ), null, titan )
+ }
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/conversation/_battle_chatter.gnut b/Northstar.CustomServers/scripts/vscripts/conversation/_battle_chatter.gnut
new file mode 100644
index 000000000..961816c7c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/conversation/_battle_chatter.gnut
@@ -0,0 +1,25 @@
+global function BattleChatter_Init
+global function PlayBattleChatterLine
+global function TryPlayWeaponBattleChatterLine
+
+void function BattleChatter_Init()
+{
+ //ShBattleChatter_Init()
+}
+
+void function PlayBattleChatterLine( entity player, string conversationType )
+{
+ foreach( entity otherPlayer in GetPlayerArray() )
+ if ( ShouldPlayBattleChatter( conversationType, otherPlayer, player ) && player != otherPlayer )
+ Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_PlayBattleChatter", GetConversationIndex( conversationType ), player.GetEncodedEHandle() )
+}
+
+void function TryPlayWeaponBattleChatterLine( entity player, entity weapon )
+{
+ var chatterEvent = weapon.GetWeaponInfoFileKeyField( "battle_chatter_event" )
+ if ( chatterEvent == null )
+ return
+
+ expect string( chatterEvent )
+ PlayBattleChatterLine( player, chatterEvent )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/conversation/_conversation_schedule.gnut b/Northstar.CustomServers/scripts/vscripts/conversation/_conversation_schedule.gnut
new file mode 100644
index 000000000..089d4b711
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/conversation/_conversation_schedule.gnut
@@ -0,0 +1,629 @@
+untyped
+
+global function DialogueScheduleServer_Init
+
+global function GetConversationIndex
+global function PlaySquadConversationToPlayer
+global function PlaySquadConversationToTeam
+global function PlaySquadConversationToAll
+global function PlaySpectreChatterToAll
+global function PlaySpectreChatterToTeam
+global function PlaySpectreChatterToPlayer
+global function PlaySquadConversation
+global function PlayConversationToPlayer
+global function Delayed_PlayConversationToPlayer
+global function PlayConversationToTeam
+global function PlayConversationToAll
+global function PlayConversationToAllExcept
+global function PlayConversationToTeamExceptPlayer
+global function ForcePlayConversationToPlayer
+global function ForcePlayConversationToAll
+global function ForcePlayConversationToTeam
+global function SetGlobalForcedDialogueOnly
+global function SetPlayerForcedDialogueOnly
+global function CodeCallback_ScriptedDialogue
+global function GetNearbyEnemyGrunts
+global function GetNearbyFriendlyGrunts
+global function CodeCallback_OnNPCLookAtHint
+
+global function ScriptDialog_PilotCloaked
+
+struct
+{
+ array< void functionref( entity ) > codeDialogueFunc
+
+} file
+
+void function DialogueScheduleServer_Init()
+{
+ #document( "PlayConversationToPlayer", " Play conversation passed in to player specified" )
+
+ // dialogue that comes from ai schedule notifies
+
+ // must match order of enum eCodeDialogueID
+ file.codeDialogueFunc = [
+ CodeDialogue_ManDown,
+ CodeDialogue_GruntSalute,
+ CodeDialogue_EnemyContact, //As per Conger's advice: Don't depend on this one. Use WaitSignal( guy, "OnFoundEnemy", "OnSeeEnemy", "OnLostEnemy" )
+ CodeDialogue_RunFromEnemy,
+ CodeDialogue_Reload,
+ CodeDialogue_MoveToAssault,
+ CodeDialogue_MoveToSquadLeader,
+ CodeDialogue_FanOut,
+ CodeDialogue_TakeCoverFromEnemy,
+ CodeDialogue_ChaseEnemy,
+ CodeDialogue_GrenadeOut,
+ CodeDialogue_DangerousAreaDisplace,
+ CodeDialogue_ReactSurprised,
+ ]
+
+ Assert( file.codeDialogueFunc.len() == eCodeDialogueID.DIALOGUE_COUNT )
+}
+
+void function ScriptDialog_PilotCloaked( entity guy, entity enemy )
+{
+ Assert( IsPilot( enemy ), "These dialog lines assume enemy is a pilot" )
+
+ if ( NPC_GruntChatterSPEnabled( guy ) )
+ {
+ #if GRUNTCHATTER_ENABLED
+ GruntChatter_TryCloakedPilotSpotted( guy, enemy )
+ #endif
+ }
+ else
+ {
+ #if GRUNT_CHATTER_MP_ENABLED
+ PlayGruntChatterMPLine( guy, "bc_engageenemycloakedpilot" )
+ #endif
+ }
+}
+
+void function CodeDialogue_GruntSalute( entity guy )
+{
+ //EmitSoundOnEntity( guy, "grunt_salute" )
+ //PlaySquadConversationToAll( "grunt_salute" )
+}
+
+void function CodeDialogue_EnemyContact( entity guy ) //As per Conger's advice: Don't depend on this one. Use WaitSignal( guy, "OnFoundEnemy", "OnSeeEnemy", "OnLostEnemy" )
+{
+}
+
+
+void function CodeDialogue_RunFromEnemy( entity guy )
+{
+ //MP and SP use different systems.
+ #if GRUNT_CHATTER_MP_ENABLED
+ //MP, use PlayOneLinerConversationOnEntWithPriority() as base function
+ entity enemy = guy.GetEnemy()
+ if ( !IsAlive( enemy ) )
+ return
+
+ if ( enemy.IsTitan() )
+ PlayGruntChatterMPLine( guy, "bc_fleePlayerTitanCall" )
+ #else
+ //SP, use r1 style PlayConversation calls()
+ // only imc has these currently
+ if ( guy.GetTeam() != TEAM_IMC )
+ return
+
+ entity enemy = guy.GetEnemy()
+ if ( !IsAlive( enemy ) )
+ return
+
+ if ( enemy.IsTitan() )
+ {
+ local squadName = guy.Get( "squadname" )
+
+ bool isSquad = false
+
+ if ( squadName != "" )
+ {
+ array<entity> squad = GetNPCArrayBySquad( squadName )
+ isSquad = squad.len() > 1
+ }
+
+ if ( isSquad )
+ {
+ // has a safe hint? running to building
+ if ( guy.GetSafeHint() )
+ PlaySquadConversationToAll( "grunt_flees_titan_building", guy )
+ else
+ PlaySquadConversationToAll( "grunt_group_flees_titan", guy )
+ }
+ else
+ {
+ PlaySquadConversationToAll( "grunt_flees_titan", guy )
+ }
+ }
+ #endif
+}
+
+void function CodeDialogue_Reload( entity guy )
+{
+ //PlaySquadConversationToAll( "aichat_reload", guy )
+}
+
+void function CodeDialogue_FanOut( entity guy )
+{
+}
+
+void function CodeDialogue_MoveToSquadLeader( entity guy )
+{
+}
+
+void function CodeDialogue_MoveToAssault( entity guy )
+{
+}
+
+void function CodeDialogue_TakeCoverFromEnemy( entity guy )
+{
+ #if HAS_BOSS_AI
+ if ( guy.IsTitan() )
+ BossTitanRetreat( guy )
+ #endif
+}
+
+void function CodeDialogue_ChaseEnemy( entity guy )
+{
+ #if HAS_BOSS_AI
+ if ( guy.IsTitan() )
+ BossTitanAdvance( guy )
+ #endif
+}
+
+void function CodeDialogue_GrenadeOut( entity guy )
+{
+ if ( NPC_GruntChatterSPEnabled( guy ) )
+ {
+ #if GRUNTCHATTER_ENABLED
+ // Ticks are actually thrown like grenades, but the callouts work differently because only Specialists use them
+ // TODO- move this info to the weapon data file
+ if ( guy.kv.grenadeWeaponName == "mp_weapon_frag_drone" )
+ GruntChatter_TryFriendlyEquipmentDeployed( guy, "mp_weapon_frag_drone" )
+ else
+ GruntChatter_TryThrowingGrenade( guy )
+ #endif
+ }
+ else
+ {
+ if ( IsSpectre( guy ) )
+ {
+ #if SPECTRE_CHATTER_MP_ENABLED
+ PlaySpectreChatterMPLine( guy, "diag_imc_spectre_gs_grenadeout_01_1" )
+ #else
+ PlaySpectreChatterToAll( "spectre_gs_grenadeout_01_1", guy )
+
+ #endif
+ }
+ else if ( IsGrunt( guy ) )
+ {
+ #if GRUNT_CHATTER_MP_ENABLED
+ PlayGruntChatterMPLine( guy, "bc_grenadeOutCall" )
+ #endif
+ }
+ }
+}
+
+void function CodeDialogue_DangerousAreaDisplace( entity guy )
+{
+ #if GRUNT_CHATTER_MP_ENABLED
+ //MP ONly
+ string dangerousAreaWeaponName = guy.GetDangerousAreaWeapon()
+ //printt( "CodeDialogue_DangerousAreaDisplace, Dangerous weapon name: " + dangerousAreaWeaponName )
+ string conversationName = ""
+ switch ( dangerousAreaWeaponName ) //String comparison, not great...
+ {
+ case "mp_weapon_frag_grenade":
+ conversationName = "bc_grenadecall"
+ break
+
+ case "mp_weapon_thermite_grenade":
+ conversationName = "bc_reactGrenadeThermite"
+ break
+
+ case "mp_weapon_grenade_gravity": //By the time this triggers it looks like they're already being sucked in.
+ conversationName = "bc_reactGrenadeGravity"
+ break
+
+ case "mp_weapon_grenade_electric_smoke":
+ conversationName = "bc_reactGrenadeElecSmoke"
+ break
+
+ //Arc grenades have their dialogue triggered by PlayGruntChatterMP_DamagedByEMP() since arc grenades don't create dangerous areas
+ }
+
+ if( conversationName != "" )
+ PlayGruntChatterMPLine( guy, conversationName )
+
+ #endif
+ #if GRUNTCHATTER_ENABLED
+ //SP Only
+ if ( NPC_GruntChatterSPEnabled( guy ) )
+ GruntChatter_TryDisplacingFromDangerousArea( guy )
+ #endif
+}
+
+void function CodeDialogue_ReactSurprised( entity guy )
+{
+ #if GRUNTCHATTER_ENABLED
+ if ( NPC_GruntChatterSPEnabled( guy ) )
+ {
+ int aiSurprisedReactionType = guy.GetSurprisedReactionReason()
+
+ switch ( aiSurprisedReactionType )
+ {
+ case RSR_SIDE_FLANK:
+ case RSR_REAR_FLANK:
+ GruntChatter_TryGruntFlankedByPlayer( guy, aiSurprisedReactionType )
+ break
+ }
+ }
+ #endif
+}
+
+void function CodeDialogue_ManDown( entity guy )
+{
+}
+
+void function SetGlobalForcedDialogueOnly( bool value )
+{
+ level.nv.forcedDialogueOnly = value
+}
+
+void function SetPlayerForcedDialogueOnly( entity player, bool value )
+{
+ player.SetForcedDialogueOnly( value )
+}
+
+void function Delayed_PlayConversationToPlayer( string conversation, entity player, float delay )
+{
+ player.EndSignal( "OnDeath" )
+ wait delay
+ PlayConversationToPlayer( conversation, player )
+}
+
+void function PlayConversationToPlayer( string conversationType, entity player )
+{
+ if ( IsForcedDialogueOnly( player ) )
+ {
+ printt( "ForcedDialogueOnly, not playing conversationType: " + conversationType )
+ return
+ }
+
+ PlayConversation_internal( conversationType, player )
+}
+
+void function PlayConversationToTeam( string conversationType, int team )
+{
+ array<entity> playerArr = GetPlayerArrayOfTeam( team )
+ foreach( player in playerArr )
+ PlayConversationToPlayer( conversationType, player )
+}
+
+void function PlayConversationToTeamExceptPlayer( string conversationType, int team, entity excludePlayer )
+{
+ array<entity> playerArr = GetPlayerArrayOfTeam( team )
+ foreach( player in playerArr )
+ {
+ if ( player == excludePlayer )
+ continue
+
+ PlayConversation_internal( conversationType, player )
+ }
+}
+
+void function PlayConversationToAll( string conversationType )
+{
+ array<entity> playerArr = GetPlayerArray()
+ foreach( player in playerArr )
+ PlayConversationToPlayer( conversationType, player )
+}
+
+void function PlayConversation_internal( string conversationType, entity player )
+{
+ #if FACTION_DIALOGUE_ENABLED
+ return
+ #endif
+
+ int conversationID = GetConversationIndex( conversationType )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayConversation", conversationID )
+}
+
+void function ForcePlayConversationToAll( string conversationType )
+{
+ array<entity> playerArr = GetPlayerArray()
+ foreach( player in playerArr )
+ {
+ ForcePlayConversationToPlayer( conversationType, player )
+ }
+}
+
+void function ForcePlayConversationToTeam( string conversationType, team )
+{
+ array<entity> playerArr = GetPlayerArrayOfTeam( team )
+ foreach( player in playerArr )
+ {
+ ForcePlayConversationToPlayer( conversationType, player )
+ }
+}
+
+//Like PlayConversation, but no checking for flags
+void function ForcePlayConversationToPlayer( string conversationType, entity player )
+{
+ PlayConversation_internal( conversationType, player )
+}
+
+array<entity> function GetNearbyFriendlyGrunts( vector origin, int team, range = null )
+{
+ float searchRange = AI_CONVERSATION_RANGE
+ if ( range != null )
+ searchRange = expect float( range )
+
+ array<entity> guys
+ array<entity> ai = GetNPCArrayEx( "npc_soldier", team, TEAM_ANY, origin, searchRange )
+ foreach ( guy in ai )
+ {
+ if ( IsAlive( guy ) )
+ guys.append( guy )
+ }
+
+ return guys
+}
+
+array<entity> function GetNearbyEnemyGrunts( vector origin, int team, range = null )
+{
+ float searchRange = AI_CONVERSATION_RANGE
+ if ( range != null )
+ searchRange = expect float( range )
+
+ array<entity> guys
+ array<entity> ai = GetNPCArrayEx( "npc_soldier", TEAM_ANY, team, origin, searchRange )
+ foreach ( guy in ai )
+ {
+ if ( IsAlive( guy ) )
+ guys.append( guy )
+ }
+
+ return guys
+}
+
+bool function SquadExistsForConversation( entity ai, string conversationType )
+{
+ if ( !IsAlive( ai ) )
+ return false
+
+ // only soldiers play squad conversations
+ if ( !IsGrunt( ai ) )
+ return false
+
+ //Squadless AI don't play squad conversations
+ local squadName = ai.Get( "squadname" )
+ if ( squadName == "" )
+ return false
+
+ // only all-soldier squads can use squad conversations
+ array<entity> squad = GetNPCArrayBySquad( squadName )
+ if ( !squad.len() )
+ return false
+
+ bool foundNonSoldier = false
+ foreach ( guy in squad )
+ {
+ if ( !IsGrunt( guy ) )
+ {
+ foundNonSoldier = true
+ break
+ }
+ }
+
+ if ( !(DoesConversationExist( conversationType ) ))
+ {
+ printt( "*****CONVERSATION WARNING***** Conversation " + conversationType + " does not exist! Returning" )
+ return false
+ }
+
+ return true
+}
+
+function GetSquadEHandles( ai )
+{
+ expect entity( ai )
+
+ local aiHandles = [ null, null, null, null ]
+
+ string squadName = expect string( ai.Get( "squadname" ) )
+
+ if ( squadName == "" )
+ return aiHandles
+
+ array<entity> squad = GetNPCArrayBySquad( squadName )
+ squad.fastremovebyvalue( ai )
+ aiHandles[0] = ai.GetEncodedEHandle()
+
+ int nextIdx = 1
+
+ foreach ( guy in squad )
+ {
+ if ( !IsValid( guy ) )
+ continue
+
+ switch ( guy.GetClassName() )
+ {
+ case "npc_soldier":
+ aiHandles[ nextIdx ] = guy.GetEncodedEHandle()
+ ++nextIdx
+ break
+ }
+
+ if ( nextIdx >= aiHandles.len() )
+ break
+ }
+
+ return aiHandles
+}
+
+void function PlaySquadConversationToPlayer( string conversationType, entity player, entity ai, float rangeSqr = AI_CONVERSATION_RANGE_SQR )
+{
+ if ( SquadExistsForConversation( ai, conversationType ) )
+ {
+ local aiHandles = GetSquadEHandles( ai )
+ PlaySquadConversationToPlayer_Internal( conversationType, player, ai, rangeSqr, aiHandles )
+ }
+}
+
+// All PlaySquadConversation functions eventually funnel down to this.
+// Funciton is broken apart from PlaySquadConversationToPlayer since PlaySquadConversationToPlayer has
+// a few expensive checks that only need to be run once for every conversation we're trying to play,
+// as opposed to for every player we're trying to play a conversation to.
+void function PlaySquadConversationToPlayer_Internal( string conversationType, entity player, entity ai, float rangeSqr, aiHandles )
+{
+ #if GRUNT_CHATTER_MP_ENABLED
+ return
+ #endif
+
+ Assert( IsAlive( ai ), ai + " is dead." )
+ Assert( aiHandles.len() == 4 )
+ vector org = ai.GetOrigin()
+ float debounceTime = GetConversationDebounce( conversationType )
+ float allowedTime = Time() - debounceTime
+
+ // tell client to play conversation
+ int conversationID = GetConversationIndex( conversationType )
+ if ( !ShouldPlaySquadConversation( player, conversationType, allowedTime, org, rangeSqr ) )
+ return
+
+ UpdateConversationTracking( player, conversationType, Time() )
+ Remote_CallFunction_Replay( player, "ServerCallback_PlaySquadConversation", conversationID, aiHandles[0], aiHandles[1], aiHandles[2], aiHandles[3] )
+}
+
+void function PlaySquadConversation( string conversationType, entity ai )
+{
+ PlaySquadConversationToAll( conversationType, ai )
+}
+
+void function PlaySquadConversationToAll( string conversationType, entity ai, float rangeSqr = AI_CONVERSATION_RANGE_SQR )
+{
+ if ( !SquadExistsForConversation( ai, conversationType ) )
+ return
+
+ local aiHandles = GetSquadEHandles( ai )
+
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ PlaySquadConversationToPlayer_Internal( conversationType, player, ai, rangeSqr, aiHandles )
+ }
+}
+
+void function PlaySquadConversationToTeam( string conversationType, int team, entity ai, float rangeSqr = AI_CONVERSATION_RANGE_SQR )
+{
+ if ( !SquadExistsForConversation( ai, conversationType ) )
+ return
+
+ local aiHandles = GetSquadEHandles( ai )
+
+ array<entity> players = GetPlayerArrayOfTeam( team )
+ foreach ( player in players )
+ {
+ PlaySquadConversationToPlayer_Internal( conversationType, player, ai, rangeSqr, aiHandles )
+ }
+}
+
+void function PlaySpectreChatterToAll( string conversationType, entity spectre, float rangeSqr = AI_CONVERSATION_RANGE_SQR )
+{
+ PlaySpectreChatterToTeam( conversationType, TEAM_IMC, spectre, rangeSqr )
+ PlaySpectreChatterToTeam( conversationType, TEAM_MILITIA, spectre, rangeSqr )
+}
+
+void function PlaySpectreChatterToTeam( string conversationType, team, entity spectre, float rangeSqr = AI_CONVERSATION_RANGE_SQR )
+{
+ array<entity> players = GetPlayerArrayOfTeam( team )
+ foreach ( player in players )
+ {
+ PlaySpectreChatterToPlayer( conversationType, player, spectre, rangeSqr )
+ }
+}
+
+void function PlaySpectreChatterToPlayer( string conversationType, entity player, entity spectre, float rangeSqr = AI_CONVERSATION_RANGE_SQR )
+{
+ //PrintFunc()
+ vector spectreOrigin = spectre.GetOrigin()
+ float debounceTime = DEFAULT_CONVERSATION_DEBOUNCE_TIME // Spectre conversations aren't as real as the Grunt ones- they don't get registered bc they just EmitSound
+ float allowedTime = Time() - debounceTime
+
+ string teamSpecificSoundAlias = GetSpectreTeamSpecificSoundAlias( spectre, conversationType )
+
+ if ( teamSpecificSoundAlias == "" )
+ // neutral AI don't have dialog
+ return
+
+ Assert( DoesAliasExist( teamSpecificSoundAlias ) )
+
+ //printt( "Trying to play spectre chatter: " + teamSpecificSoundAlias + " to player: " + player)
+ if ( !ShouldPlaySquadConversation( player, teamSpecificSoundAlias, allowedTime, spectreOrigin, rangeSqr ) )
+ return
+
+ UpdateConversationTracking( player, teamSpecificSoundAlias, Time() )
+
+ EmitSoundOnEntityOnlyToPlayer( spectre, player, teamSpecificSoundAlias )
+}
+
+string function GetSpectreTeamSpecificSoundAlias( entity spectre, string partialConversationAlias )
+{
+ int spectreTeam = spectre.GetTeam()
+
+ if ( spectreTeam == TEAM_IMC )
+ return "diag_imc_" + partialConversationAlias
+ else if ( spectreTeam == TEAM_MILITIA )
+ return "diag_militia_" + partialConversationAlias
+
+ return ""
+}
+
+void function PlayConversationToAllExcept( string conversationType, array<entity> exceptions )
+{
+ array<entity> playerArr = GetPlayerArray()
+
+ table<entity, int> exceptionsTable
+ foreach( exceptionPlayer in exceptions )
+ {
+ exceptionsTable[ exceptionPlayer ] <- 1
+ }
+
+ foreach ( player in playerArr )
+ {
+ if ( player in exceptionsTable )
+ continue
+
+ PlayConversationToPlayer( conversationType, player )
+ }
+}
+
+void function CodeCallback_ScriptedDialogue( entity guy, int dialogueID )
+{
+ Assert( dialogueID < file.codeDialogueFunc.len() )
+
+ if ( dialogueID in file.codeDialogueFunc )
+ {
+ file.codeDialogueFunc[ dialogueID ]( guy )
+ }
+}
+
+function UpdateConversationTracking( player, conversationType, time )
+{
+ if ( !(conversationType in player.s.lastAIConversationTime) )
+ player.s.lastAIConversationTime[ conversationType ] <- time
+ else
+ player.s.lastAIConversationTime[ conversationType ] = time
+}
+
+int function GetConversationIndex( string conversation )
+{
+ Assert( conversation != "", "No conversation specified." )
+ Assert( typeof(conversation) == "string" )
+ return GetConversationToIndexTable()[ conversation ]
+}
+
+void function CodeCallback_OnNPCLookAtHint( entity npc, entity hint )
+{
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/conversation/_faction_dialogue.gnut b/Northstar.CustomServers/scripts/vscripts/conversation/_faction_dialogue.gnut
new file mode 100644
index 000000000..ccb5cd6eb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/conversation/_faction_dialogue.gnut
@@ -0,0 +1,46 @@
+global function FactionDialogue_Init
+global function InitFactionDialoguePersistence
+global function PlayFactionDialogueToPlayer
+global function PlayFactionDialogueToTeam
+global function PlayFactionDialogueToTeamExceptPlayer
+
+void function FactionDialogue_Init()
+{
+ AddCallback_OnClientConnected( AssignEnemyFactionToPlayer )
+}
+
+void function InitFactionDialoguePersistence( entity player )
+{
+ // doesn't seem to be used? required to compile tho
+}
+
+void function PlayFactionDialogueToPlayer( string conversationType, entity player )
+{
+ #if !FACTION_DIALOGUE_ENABLED
+ return
+ #endif
+
+ if ( !ShouldPlayFactionDialogue( conversationType, player ) )
+ return
+
+ int conversationIndex = GetConversationIndex( conversationType )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayFactionDialogue", conversationIndex )
+}
+
+void function PlayFactionDialogueToTeam( string conversationType, int team )
+{
+ foreach ( entity player in GetPlayerArrayOfTeam( team ) )
+ PlayFactionDialogueToPlayer( conversationType, player )
+}
+
+void function PlayFactionDialogueToTeamExceptPlayer( string conversationType, int team, entity except )
+{
+ foreach ( entity player in GetPlayerArrayOfTeam( team ) )
+ if ( player != except )
+ PlayFactionDialogueToPlayer( conversationType, player )
+}
+
+void function AssignEnemyFactionToPlayer( entity player )
+{
+ AssignEnemyFaction( player, expect string( player.GetPersistentVar( "factionChoice" ) ) )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/conversation/_grunt_chatter_mp.gnut b/Northstar.CustomServers/scripts/vscripts/conversation/_grunt_chatter_mp.gnut
new file mode 100644
index 000000000..b638e92bb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/conversation/_grunt_chatter_mp.gnut
@@ -0,0 +1,18 @@
+global function GruntChatter_MP_Init
+global function PlayGruntChatterMPLine
+
+void function GruntChatter_MP_Init()
+{
+ //ShGruntChatter_MP_Init()
+}
+
+void function PlayGruntChatterMPLine( entity grunt, string conversationType )
+{
+ #if !GRUNT_CHATTER_MP_ENABLED
+ return
+ #endif
+
+ foreach ( entity player in GetPlayerArray() )
+ if ( ShouldPlayGruntChatterMPLine( conversationType, player, grunt ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayGruntChatterMP", GetConversationIndex( conversationType ), grunt.GetEncodedEHandle() )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/conversation/_spectre_chatter_mp.gnut b/Northstar.CustomServers/scripts/vscripts/conversation/_spectre_chatter_mp.gnut
new file mode 100644
index 000000000..2f9e0f844
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/conversation/_spectre_chatter_mp.gnut
@@ -0,0 +1,18 @@
+global function SpectreChatter_MP_Init
+global function PlaySpectreChatterMPLine
+
+void function SpectreChatter_MP_Init()
+{
+ //ShSpectreChatter_MP_Init()
+}
+
+void function PlaySpectreChatterMPLine( entity spectre, string conversationType )
+{
+ #if !SPECTRE_CHATTER_MP_ENABLED
+ return
+ #endif
+
+ foreach ( entity player in GetPlayerArray() )
+ if ( ShouldPlaySpectreChatterMPLine( conversationType, player, spectre ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlaySpectreChatterMP", GetConversationIndex( conversationType ), spectre.GetEncodedEHandle() )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/earn_meter/sv_earn_meter.gnut b/Northstar.CustomServers/scripts/vscripts/earn_meter/sv_earn_meter.gnut
new file mode 100644
index 000000000..dda84976c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/earn_meter/sv_earn_meter.gnut
@@ -0,0 +1,508 @@
+global function Sv_EarnMeter_Init
+global function PlayerEarnMeter_SoftReset
+global function PlayerEarnMeter_SetOwnedFrac
+global function PlayerEarnMeter_Reset
+global function PlayerEarnMeter_Empty
+global function PlayerEarnMeter_AddEarnedFrac
+global function PlayerEarnMeter_AddOwnedFrac
+global function PlayerEarnMeter_AddEarnedAndOwned
+global function PlayerEarnMeter_SetMode
+global function PlayerEarnMeter_SetRewardFrac
+
+global function PlayerEarnMeter_GetPilotMultiplier
+global function PlayerEarnMeter_GetPilotOverdriveEnum
+
+global function PlayerEarnMeter_RefreshGoal
+
+global function PlayerEarnMeter_SetReward
+global function PlayerEarnMeter_SetGoal
+
+global function PlayerEarnMeter_SetGoalUsed
+global function PlayerEarnMeter_EnableGoal
+global function PlayerEarnMeter_DisableGoal
+
+global function PlayerEarnMeter_SetRewardUsed
+global function PlayerEarnMeter_DisableReward
+global function PlayerEarnMeter_EnableReward
+
+global function PlayerEarnMeter_CanEarn
+
+global function SetCallback_EarnMeterGoalEarned
+global function SetCallback_EarnMeterRewardEarned
+
+global function AddEarnMeterThresholdEarnedCallback
+
+global function JFS_PlayerEarnMeter_CoreRewardUpdate
+global function GiveOffhandElectricSmoke
+
+global function SharedEarnMeter_AddEarnedAndOwned
+global function PlayerEarnMeter_SetEnabled
+global function PlayerEarnMeter_Enabled
+
+global struct EarnMeterThresholdEarnedStruct
+{
+ float threshold
+ bool triggerFunctionOnFullEarnMeter = false
+ void functionref( entity player ) thresholdEarnedCallback
+}
+
+struct
+{
+ void functionref( entity player ) goalEarnedCallback
+ void functionref( entity player ) rewardEarnedCallback
+ array<EarnMeterThresholdEarnedStruct> thresholdEarnedCallbacks
+
+ float earn_meter_pilot_multiplier
+ int earn_meter_pilot_overdrive // ePilotOverdrive
+ bool earnMeterEnabled = true
+} file
+
+void function Sv_EarnMeter_Init()
+{
+ if ( !EARNMETER_ENABLED )
+ return
+
+ RegisterSignal( "EarnMeterDecayThink" )
+
+ SetCallback_EarnMeterGoalEarned( DummyGoalEarnedCallback )
+ SetCallback_EarnMeterRewardEarned( DummyRewardEarnedCallback )
+
+ file.earn_meter_pilot_multiplier = PlayerEarnMeter_GetPilotMultiplier()
+ file.earn_meter_pilot_overdrive = PlayerEarnMeter_GetPilotOverdriveEnum()
+}
+
+float function PlayerEarnMeter_GetPilotMultiplier()
+{
+ return GetCurrentPlaylistVarFloat( "earn_meter_pilot_multiplier", 1.0 )
+}
+
+int function PlayerEarnMeter_GetPilotOverdriveEnum()
+{
+ return GetCurrentPlaylistVarInt( "earn_meter_pilot_overdrive", ePilotOverdrive.Enabled )
+}
+
+void function AddEarnMeterThresholdEarnedCallback( float thresholdForCallback, void functionref( entity player ) callbackFunc, bool triggerFunctionOnFullEarnMeter = false )
+{
+ EarnMeterThresholdEarnedStruct thresholdStruct
+ thresholdStruct.threshold = thresholdForCallback
+ thresholdStruct.thresholdEarnedCallback = callbackFunc
+ thresholdStruct.triggerFunctionOnFullEarnMeter = triggerFunctionOnFullEarnMeter
+
+ Assert( !AlreadyContainsThresholdCallback( thresholdStruct ), "Already added " + string( callbackFunc ) + " with threshold " + thresholdForCallback )
+ file.thresholdEarnedCallbacks.append( thresholdStruct )
+}
+
+bool function AlreadyContainsThresholdCallback( EarnMeterThresholdEarnedStruct thresholdStruct )
+{
+ foreach( existingThresholdStruct in file.thresholdEarnedCallbacks )
+ {
+ if ( existingThresholdStruct.threshold != thresholdStruct.threshold )
+ continue
+
+ if ( existingThresholdStruct.thresholdEarnedCallback != thresholdStruct.thresholdEarnedCallback )
+ continue
+
+ if ( existingThresholdStruct.triggerFunctionOnFullEarnMeter != thresholdStruct.triggerFunctionOnFullEarnMeter )
+ continue
+
+ return true
+ }
+
+ return false
+}
+
+void function SetCallback_EarnMeterGoalEarned( void functionref( entity player ) callback )
+{
+ if ( file.goalEarnedCallback == null || file.goalEarnedCallback == DummyGoalEarnedCallback )
+ file.goalEarnedCallback = callback
+}
+
+void function SetCallback_EarnMeterRewardEarned( void functionref( entity player ) callback )
+{
+ if ( file.rewardEarnedCallback == null || file.rewardEarnedCallback == DummyRewardEarnedCallback )
+ file.rewardEarnedCallback = callback
+}
+
+
+void function PlayerEarnMeter_SetMode( entity player, int mode )
+{
+ player.SetPlayerNetInt( EARNMETER_MODE, mode )
+}
+
+
+void function PlayerEarnMeter_AddEarnedFrac( entity player, float earnedFrac )
+{
+ PlayerEarnMeter_AddEarnedAndOwned( player, earnedFrac, 0.0 )
+}
+
+
+void function PlayerEarnMeter_AddOwnedFrac( entity player, float addValue )
+{
+ PlayerEarnMeter_AddEarnedAndOwned( player, 0.0, addValue )
+}
+
+
+bool function PlayerEarnMeter_CanEarn( entity player )
+{
+ if ( PlayerEarnMeter_GetMode( player ) != eEarnMeterMode.DEFAULT || player.IsTitan() || IsValid( player.GetPetTitan() ) )
+ return false
+
+ return file.earnMeterEnabled
+}
+
+void function SharedEarnMeter_AddEarnedAndOwned( entity player, float addOverdriveValue, float addOwnedValue )
+{
+ int teamShareEarnMeter = Riff_TeamShareEarnMeter()
+ Assert( teamShareEarnMeter != eTeamShareEarnMeter.Disabled )
+
+ float sharedEarnMeterScale = GetCurrentPlaylistVarFloat( "riff_team_share_earn_meter_scale", 0.5 )
+
+ float overdriveValue = addOverdriveValue * sharedEarnMeterScale
+ float ownedValue = addOwnedValue * sharedEarnMeterScale
+
+ array<entity> teamPlayers = GetPlayerArrayOfTeam_Alive( player.GetTeam() )
+ foreach ( teamPlayer in teamPlayers )
+ {
+ if ( teamPlayer == player )
+ continue
+
+ if ( !PlayerEarnMeter_CanEarn( teamPlayer ) )
+ continue
+
+ if ( teamShareEarnMeter == eTeamShareEarnMeter.Enabled )
+ PlayerEarnMeter_AddEarnedAndOwned( teamPlayer, overdriveValue, ownedValue )
+ else if ( teamShareEarnMeter == eTeamShareEarnMeter.OwnedOnly )
+ PlayerEarnMeter_AddOwnedFrac( teamPlayer, ownedValue )
+ else if ( teamShareEarnMeter == eTeamShareEarnMeter.OverdriveOnly )
+ PlayerEarnMeter_AddEarnedFrac( teamPlayer, overdriveValue )
+ }
+}
+
+void function PlayerEarnMeter_AddEarnedAndOwned( entity player, float addOverdriveValue, float addOwnedValue )
+{
+ // TODO: Core Meter should be unified with earn meter so this can go away and we keep the hot streak concept for Titan Cores.
+ if ( player.IsTitan() )
+ {
+ AddCreditToTitanCoreBuilder( player, addOwnedValue )
+ return
+ }
+
+ if ( !PlayerEarnMeter_CanEarn( player ) )
+ return
+
+ if ( addOverdriveValue == 0 && addOwnedValue == 0 )
+ return
+
+ if ( file.earn_meter_pilot_overdrive == ePilotOverdrive.Only )
+ addOwnedValue = 0.0
+
+ if ( file.earn_meter_pilot_overdrive == ePilotOverdrive.Disabled )
+ addOverdriveValue = 0.0
+
+ float startingOverdriveValue = PlayerEarnMeter_GetEarnedFrac( player )
+ float startingOwnedValue = PlayerEarnMeter_GetOwnedFrac( player )
+ float startingOverdriveDiff = max( 0, startingOverdriveValue - startingOwnedValue )
+
+ float multipliedOwnedValue = addOwnedValue * file.earn_meter_pilot_multiplier
+ float newOwnedValue = min( startingOwnedValue + multipliedOwnedValue, 1.0 )
+ PlayerEarnMeter_SetOwnedFrac( player, min( newOwnedValue, 1.0 ) )
+
+ float multipliedOverdriveValue = addOverdriveValue * file.earn_meter_pilot_multiplier
+ float newOverdriveValue = max( min( newOwnedValue + startingOverdriveDiff + multipliedOverdriveValue, 1.0 ), 0.0 )
+ PlayerEarnMeter_SetEarnedFrac( player, newOverdriveValue )
+
+ if ( newOverdriveValue > startingOverdriveValue )
+ thread EarnMeterDecayThink( player )
+
+ foreach( thresholdStruct in file.thresholdEarnedCallbacks )
+ {
+ if ( newOverdriveValue < thresholdStruct.threshold ) //We're not past the threshold yet, don't run the function
+ continue
+
+ if ( startingOverdriveValue >= thresholdStruct.threshold ) //This isn't the first time we're past the threshold, don't run the function
+ continue
+
+ if ( newOwnedValue == 1.0 && thresholdStruct.triggerFunctionOnFullEarnMeter == false ) //We've earned enough earn meter to just fill out the bar, we should just run whatever functionality
+ continue
+
+ thresholdStruct.thresholdEarnedCallback( player )
+ }
+
+ if ( PlayerEarnMeter_IsRewardEnabled( player ) )
+ {
+ float rewardFrac = PlayerEarnMeter_GetRewardFrac( player )
+
+ // If we earned our reward
+ if ( (startingOverdriveValue < rewardFrac && newOverdriveValue >= rewardFrac) || (startingOwnedValue < rewardFrac && newOwnedValue >= rewardFrac) )
+ {
+ //if ( newOwnedValue < rewardFrac ) // if the owned portion isn't already maxed out, do so
+ // PlayerEarnMeter_SetOwnedFrac( player, rewardFrac )
+
+ PlayerEarnMeter_TryMakeRewardAvailable( player )
+ }
+ }
+
+ // If we earned our goal
+ if ( (startingOverdriveValue < 1.0 && newOverdriveValue >= 1.0) || (startingOwnedValue < 1.0 && newOwnedValue >= 1.0) )
+ {
+ if ( newOwnedValue < 1.0 ) // if the owned portion isn't already maxed out, do so
+ PlayerEarnMeter_SetOwnedFrac( player, 1.0 )
+
+ PlayerEarnMeter_TryMakeGoalAvailable( player )
+ }
+
+ //#if MP
+ // Remote_CallFunction_NonReplay( player, "ServerCallback_EarnMeterAwarded", addOverdriveValue, addOwnedValue )
+ //#endif
+}
+
+
+void function PlayerEarnMeter_RefreshGoal( entity player )
+{
+ if ( player.GetPlayerNetInt( "goalState" ) == eRewardState.AVAILABLE )
+ {
+ file.goalEarnedCallback( player )
+ }
+}
+
+
+void function PlayerEarnMeter_SetEarnedFrac( entity player, float value )
+{
+ player.p.earnMeterOverdriveFrac = value
+ player.SetPlayerNetFloat( EARNMETER_EARNEDFRAC, value )
+}
+
+
+void function PlayerEarnMeter_SetOwnedFrac( entity player, float value )
+{
+ player.p.earnMeterOwnedFrac = value
+ player.SetPlayerNetFloat( EARNMETER_OWNEDFRAC, value )
+}
+
+
+void function PlayerEarnMeter_SetRewardFrac( entity player, float value )
+{
+ player.p.earnMeterRewardFrac = value
+ player.SetPlayerNetFloat( EARNMETER_REWARDFRAC, value )
+}
+
+
+void function PlayerEarnMeter_SoftReset( entity player )
+{
+ float ownedFrac = PlayerEarnMeter_GetOwnedFrac( player )
+ PlayerEarnMeter_SetEarnedFrac( player, ownedFrac )
+}
+
+
+void function PlayerEarnMeter_Reset( entity player )
+{
+ player.Signal( "EarnMeterDecayThink" )
+
+ PlayerEarnMeter_SetEarnedFrac( player, 0.0 )
+ PlayerEarnMeter_SetOwnedFrac( player, 0.0 )
+ PlayerEarnMeter_SetRewardFrac( player, 0.0 )
+
+ player.SetPlayerNetInt( "goalState", eRewardState.DISABLED )
+ player.SetPlayerNetInt( "rewardState", eRewardState.DISABLED )
+}
+
+void function PlayerEarnMeter_Empty( entity player )
+{
+ player.Signal( "EarnMeterDecayThink" )
+
+ PlayerEarnMeter_SetEarnedFrac( player, 0.0 )
+ PlayerEarnMeter_SetOwnedFrac( player, 0.0 )
+ PlayerEarnMeter_SetRewardFrac( player, 0.0 )
+}
+
+
+void function EarnMeterDecayThink( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.Signal( "EarnMeterDecayThink" )
+ player.EndSignal( "EarnMeterDecayThink" )
+
+ if ( EarnMeter_DecayHold() < 0 )
+ return
+
+ wait EarnMeter_DecayHold()
+
+ float earnedValue = PlayerEarnMeter_GetEarnedFrac( player )
+ float ownedValue = PlayerEarnMeter_GetOwnedFrac( player )
+
+ // 10% over 20 seconds
+ float decayRate = 1.0 / 135.0
+
+ //float startTime = Time()
+ while ( earnedValue > ownedValue )
+ {
+ //float frameTime = Time() - startTime
+ //startTime = Time()
+
+ PlayerEarnMeter_AddEarnedFrac( player, -(decayRate * 0.25) )
+
+ wait 0.25
+
+ earnedValue = PlayerEarnMeter_GetEarnedFrac( player )
+ ownedValue = PlayerEarnMeter_GetOwnedFrac( player )
+ }
+}
+
+
+bool function PlayerEarnMeter_TryMakeGoalAvailable( entity player )
+{
+ if ( player.GetPlayerNetInt( "goalState" ) == eRewardState.USED )
+ return false
+
+ if ( player.GetPlayerNetInt( "goalState" ) == eRewardState.DISABLED )
+ return false
+
+ if ( player.GetPlayerNetInt( "goalState" ) == eRewardState.AVAILABLE )
+ return false
+
+ player.SetPlayerNetInt( "goalState", eRewardState.AVAILABLE )
+
+ file.goalEarnedCallback( player )
+
+ return true
+}
+
+
+void function PlayerEarnMeter_DisableReward( entity player )
+{
+ player.SetPlayerNetInt( "rewardState", eRewardState.DISABLED )
+}
+
+
+void function PlayerEarnMeter_EnableReward( entity player )
+{
+ player.SetPlayerNetInt( "rewardState", eRewardState.UNAVAILABLE )
+}
+
+
+void function PlayerEarnMeter_SetRewardUsed( entity player )
+{
+ player.SetPlayerNetInt( "rewardState", eRewardState.USED )
+}
+
+
+void function PlayerEarnMeter_DisableGoal( entity player )
+{
+ player.SetPlayerNetInt( "goalState", eRewardState.DISABLED )
+}
+
+
+void function PlayerEarnMeter_EnableGoal( entity player )
+{
+ player.SetPlayerNetInt( "goalState", eRewardState.UNAVAILABLE )
+}
+
+
+void function PlayerEarnMeter_SetGoalUsed( entity player )
+{
+ player.SetPlayerNetInt( "goalState", eRewardState.USED )
+}
+
+
+bool function PlayerEarnMeter_TryMakeRewardAvailable( entity player )
+{
+ if ( player.GetPlayerNetInt( "rewardState" ) == eRewardState.USED )
+ return false
+
+ if ( player.GetPlayerNetInt( "rewardState" ) == eRewardState.DISABLED )
+ return false
+
+ if ( player.GetPlayerNetInt( "rewardState" ) == eRewardState.AVAILABLE )
+ return false
+
+ player.SetPlayerNetInt( "rewardState", eRewardState.AVAILABLE )
+
+ file.rewardEarnedCallback( player )
+ return true
+}
+
+
+void function PlayerEarnMeter_SetReward( entity player, EarnObject earnObject )
+{
+ Assert( earnObject.id > -1 )
+ Assert( earnObject.earnType == "REWARD" )
+
+ player.SetPlayerNetInt( EARNMETER_REWARDID, earnObject.id )
+}
+
+void function PlayerEarnMeter_SetGoal( entity player, EarnObject earnObject )
+{
+ Assert( earnObject.id > -1 )
+ //Assert( earnObject.earnType == "GOAL" )
+
+ player.SetPlayerNetInt( EARNMETER_GOALID, earnObject.id )
+}
+
+
+void function DummyRewardEarnedCallback( entity player )
+{
+ Assert( false, "Must set a reward earned callback with SetCallback_EarnMeterRewardEarned() if rewards are in use" )
+}
+
+
+void function DummyGoalEarnedCallback( entity player )
+{
+ Assert( false, "Must set a goal earned callback with SetCallback_EarnMeterGoalEarned() if meter is in use" )
+}
+
+// Hook into the existing core system until it can be replaced.
+void function JFS_PlayerEarnMeter_CoreRewardUpdate( entity titan, float startingCoreValue, float newCoreValue )
+{
+ #if ANTI_RODEO_SMOKE_ENABLED
+ if ( startingCoreValue < CORE_SMOKE_FRAC && newCoreValue >= CORE_SMOKE_FRAC )
+ {
+ GiveOffhandElectricSmoke( titan )
+
+ if ( titan.IsPlayer() )
+ Remote_CallFunction_NonReplay( titan, "ServerCallback_RewardReadyMessage", (Time() - GetPlayerLastRespawnTime( titan )) )
+
+ if ( titan.IsPlayer() )
+ PlayerEarnMeter_SetRewardUsed( titan )
+ }
+ #endif
+}
+
+void function GiveOffhandElectricSmoke( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ bool hasAntiRodeoKit = IsValid( soul ) && SoulHasPassive( soul, ePassives.PAS_ANTI_RODEO )
+ if ( titan.GetOffhandWeapon( OFFHAND_INVENTORY ) != null )
+ {
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_INVENTORY )
+ if ( hasAntiRodeoKit )
+ weapon.SetWeaponPrimaryAmmoCount( weapon.GetWeaponPrimaryAmmoCount() + 2 )
+ else
+ weapon.SetWeaponPrimaryAmmoCount( weapon.GetWeaponPrimaryAmmoCount() + 1 )
+ }
+ else
+ {
+ titan.GiveOffhandWeapon( CORE_SMOKE_WEAPON, OFFHAND_INVENTORY )
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_INVENTORY )
+ if ( hasAntiRodeoKit )
+ {
+ weapon.SetWeaponPrimaryAmmoCount( weapon.GetWeaponPrimaryAmmoCount() + 1 )
+ }
+ if ( soul.GetTitanSoulNetInt( "upgradeCount" ) >= 2 && SoulHasPassive( soul, ePassives.PAS_VANGUARD_CORE5 ) )
+ {
+ entity weapon = titan.GetOffhandWeapon( OFFHAND_INVENTORY )
+ array<string> mods = weapon.GetMods()
+ mods.append( "maelstrom" )
+ weapon.SetMods( mods )
+ }
+ }
+}
+
+void function PlayerEarnMeter_SetEnabled( bool enabled )
+{
+ file.earnMeterEnabled = enabled
+}
+
+bool function PlayerEarnMeter_Enabled()
+{
+ return file.earnMeterEnabled
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut b/Northstar.CustomServers/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut
new file mode 100644
index 000000000..b5ad4fb12
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut
@@ -0,0 +1,128 @@
+global function Sv_EarnMeterMP_Init
+global function EarnMeterMP_SetTitanLoadout
+
+struct {
+ float playingStartTime
+} file
+
+void function Sv_EarnMeterMP_Init()
+{
+ if ( !EARNMETER_ENABLED )
+ return
+
+ AddCallback_OnClientConnected( SetupPlayerEarnMeter )
+ AddCallback_GameStateEnter( eGameState.Playing, OnPlaying ) // can't change boost after prematch
+ AddCallback_OnPlayerRespawned( OnPlayerRespawned )
+}
+
+void function EarnMeterMP_SetTitanLoadout( entity player )
+{
+ if ( EarnMeterMP_IsTitanEarnGametype() )
+ PlayerEarnMeter_SetGoal( player, EarnObject_GetByRef( GetTitanLoadoutForPlayer( player ).titanClass ) )
+}
+
+void function SetupPlayerEarnMeter( entity player )
+{
+ PlayerEarnMeter_Reset( player )
+
+ // todo: need to do burnmeter stuff here ( e.g. rewards/boosts )
+ if ( EarnMeterMP_IsTitanEarnGametype() )
+ PlayerEarnMeter_SetGoal( player, EarnObject_GetByRef( GetTitanLoadoutForPlayer( player ).titanClass ) )
+
+ PlayerEarnMeter_EnableGoal( player ) // prevents goalstate from being set incorrectly
+
+ // catchup bonus for late joiners
+ // todo: maths on this is fine but for some reason it won't set correctly, could be getting reset somewhere?
+ PlayerEarnMeter_AddOwnedFrac( player, ( ( Time() - file.playingStartTime ) / 4.0 ) * 0.01 )
+}
+
+void function OnPlaying()
+{
+ file.playingStartTime = Time()
+ foreach ( entity player in GetPlayerArray() )
+ SetupPlayerEarnMeter( player )
+
+ // do this in playing so that gamemodes/maps can disable and this'll take affect
+ if ( EarnMeterMP_IsTitanEarnGametype() ) // settitanavailable when earnmeter full
+ {
+ Riff_ForceTitanAvailability( eTitanAvailability.Custom ) // doesn't seem to affect anything aside from preventing some annoying client stuff
+ svGlobal.titanAvailabilityCheck = IsTitanAvailable
+ AddEarnMeterThresholdEarnedCallback( 1.0, void function( entity player ) { SetTitanAvailable( player ) }, true )
+ }
+ else // if no titans from earnmeter in this mode, just reset when we finish meter
+ AddEarnMeterThresholdEarnedCallback( 1.0, PlayerEarnMeter_Reset, true )
+}
+
+void function OnPlayerRespawned( entity player )
+{
+ thread EarnMeterMP_PlayerLifeThink( player )
+}
+
+void function EarnMeterMP_PlayerLifeThink( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ int lastEarnMeterMode = PlayerEarnMeter_GetMode( player )
+ float lastPassiveGainTime = Time()
+
+ while ( true )
+ {
+ int desiredEarnMeterMode
+
+ if ( player.IsTitan() )
+ {
+ entity soul = player.GetTitanSoul()
+ if ( SoulTitanCore_GetExpireTime( soul ) > Time() )
+ desiredEarnMeterMode = eEarnMeterMode.CORE_ACTIVE
+ else
+ desiredEarnMeterMode = eEarnMeterMode.CORE
+ }
+ else if ( IsValid( player.GetPetTitan() ) )
+ desiredEarnMeterMode = eEarnMeterMode.PET
+ else
+ desiredEarnMeterMode = eEarnMeterMode.DEFAULT
+
+ if ( desiredEarnMeterMode != lastEarnMeterMode )
+ {
+ PlayerEarnMeter_SetMode( player, desiredEarnMeterMode )
+
+ if ( desiredEarnMeterMode == eEarnMeterMode.DEFAULT )
+ {
+ if ( !IsTitanAvailable( player ) && PlayerEarnMeter_GetOwnedFrac( player ) == 1.0 ) // this should only be the case after player has dropped their titan
+ PlayerEarnMeter_Reset( player )
+
+ if ( PlayerEarnMeter_GetRewardFrac( player ) != 0 )
+ PlayerEarnMeter_EnableReward( player )
+ }
+ else
+ {
+ PlayerEarnMeter_DisableGoal( player )
+ PlayerEarnMeter_DisableReward( player )
+ }
+
+ lastEarnMeterMode = desiredEarnMeterMode
+ }
+
+ if ( lastEarnMeterMode == eEarnMeterMode.DEFAULT )
+ {
+ if ( PlayerEarnMeter_GetOwnedFrac( player ) < 1.0 )
+ PlayerEarnMeter_DisableGoal( player )
+ else if ( player.GetPlayerNetInt( "goalState" ) != eRewardState.UNAVAILABLE )
+ {
+ // if goal is enabled then the client will show "titan ready" alerts even if it isn't
+ // the problem is that if the goal isn't available when we fill the earnmeter, then it won't make it available
+ // so unfortunately we have to do this manually
+ player.SetPlayerNetInt( "goalState", eRewardState.AVAILABLE )
+ PlayerEarnMeter_RefreshGoal( player )
+ }
+
+ if ( Time() - lastPassiveGainTime > 4.0 ) // this might be 5.0
+ {
+ lastPassiveGainTime = Time()
+ PlayerEarnMeter_AddOwnedFrac( player, 0.01 )
+ }
+ }
+
+ WaitFrame()
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/scripts/vscripts/evac/_evac.gnut
new file mode 100644
index 000000000..ba473cae9
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/evac/_evac.gnut
@@ -0,0 +1,315 @@
+untyped
+
+global function Evac_Init
+global function Evac_AddLocation
+global function Evac_SetSpacePosition
+global function Evac_SetEnabled
+global function Evac_IsEnabled
+global function IsEvacDropship
+global function EvacMain
+
+const float EVAC_ARRIVAL_TIME = 40.0
+const float EVAC_WAIT_TIME = 18.0
+
+struct {
+ bool enabled = true
+
+ array<Point> evacPoints
+ Point spacePosition
+
+ entity evacDropship
+ array<entity> evacPlayers
+} file
+
+void function Evac_Init()
+{
+ EvacShared_Init()
+
+ AddCallback_GameStateEnter( eGameState.Epilogue, Evac_OnEpilogue )
+}
+
+void function Evac_SetEnabled( bool enabled )
+{
+ file.enabled = enabled
+}
+
+bool function Evac_IsEnabled()
+{
+ return false // shit is busted rn lol
+ //return file.enabled && GetClassicMPMode() && !IsRoundBased()
+}
+
+void function Evac_AddLocation( vector origin, vector angles )
+{
+ Point evacPoint
+ evacPoint.origin = origin
+ evacPoint.angles = angles
+
+ file.evacPoints.append( evacPoint )
+}
+
+void function Evac_SetSpacePosition( vector origin, vector angles )
+{
+ file.spacePosition.origin = origin
+ file.spacePosition.angles = angles
+}
+
+bool function IsEvacDropship( entity ent )
+{
+ return file.evacDropship == ent && IsValid( file.evacDropship )
+}
+
+void function Evac_OnEpilogue()
+{
+ if ( Evac_IsEnabled() )
+ thread EvacMain( GetOtherTeam( GameScore_GetWinningTeam() ) )
+}
+
+void function EvacMain( int winningTeam )
+{
+ if ( file.evacPoints.len() == 0 )
+ {
+ // automatically add evac locations if they aren't registered yet
+ int i = 1
+ entity current = null
+ while ( true )
+ {
+ current = GetEnt( "escape_node" + i )
+ print( current )
+
+ if ( current != null )
+ Evac_AddLocation( current.GetOrigin(), current.GetAngles() )
+ else
+ break
+
+ i++
+ }
+
+ if ( file.evacPoints.len() == 0 )
+ unreachable
+ }
+
+ if ( file.spacePosition.origin == < 0, 0, 0 > )
+ {
+ // automatically add a space node if not registered yet
+ entity defaultSpaceNode = GetEnt( "spaceNode" )
+ if ( defaultSpaceNode == null )
+ unreachable
+
+ Evac_SetSpacePosition( defaultSpaceNode.GetOrigin(), defaultSpaceNode.GetAngles() )
+ }
+
+ Point evacPoint = file.evacPoints[ RandomInt( file.evacPoints.len() ) ]
+
+ // create an entity for the evac point that clients will get
+ entity evacPointEntity = CreateEntity( MARKER_ENT_CLASSNAME )
+ evacPointEntity.SetOrigin( evacPoint.origin )
+ evacPointEntity.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( evacPointEntity )
+ evacPointEntity.DisableHibernation()
+
+ // set objectives
+ //SetTeamActiveObjective( winningTeam, "EG_DropshipExtract", Time() + EVAC_ARRIVAL_TIME, evacPointEntity )
+ //SetTeamActiveObjective( GetOtherTeam( winningTeam ), "EG_StopExtract", Time() + EVAC_ARRIVAL_TIME, evacPointEntity )
+
+ // wanted to do this with an actual dropship to calculate embarkStartDelay but spawning it before it should exist ingame is weird
+ // could probably do it with a dummy entity but effort
+ wait EVAC_ARRIVAL_TIME - 4.33333//embarkStartDelay
+
+ // create dropship
+ entity dropship = CreateDropship( winningTeam, evacPoint.origin, evacPoint.angles )
+ file.evacDropship = dropship
+
+ DispatchSpawn( dropship )
+
+ dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) // gotta do this after dispatch for some reason
+ vector startPos = dropship.Anim_GetStartForRefEntity( "cd_dropship_rescue_side_start", evacPointEntity, "origin" ).origin
+ dropship.SetOrigin( startPos ) // set origin so the dropship isn't in the map
+ dropship.EndSignal( "OnDestroy" )
+
+ // calculate time until idle
+ float sequenceDuration = dropship.GetSequenceDuration( "cd_dropship_rescue_side_start" )
+ float cycleFrac = dropship.GetScriptedAnimEventCycleFrac( "cd_dropship_rescue_side_start", "ReadyToLoad" )
+ float embarkStartDelay = sequenceDuration * cycleFrac
+
+ // play anim
+ thread PlayAnim( dropship, "cd_dropship_rescue_side_start", evacPointEntity )
+ wait embarkStartDelay
+
+ print( "evac flyin done! ready to load players" )
+
+ // set objectives again
+ SetTeamActiveObjective( winningTeam, "EG_DropshipExtract2", Time() + EVAC_WAIT_TIME, evacPointEntity )
+ SetTeamActiveObjective( GetOtherTeam( winningTeam ), "EG_StopExtract2", Time() + EVAC_WAIT_TIME, evacPointEntity )
+
+ thread EvacShipThink( dropship ) // let people enter it
+
+ wait EVAC_WAIT_TIME
+
+ // fly away
+ thread PlayAnim( dropship, "cd_dropship_rescue_side_end", evacPointEntity )
+
+ // set objectives again
+ SetTeamActiveObjective( winningTeam, "EG_DropshipExtractDropshipFlyingAway" )
+ SetTeamActiveObjective( GetOtherTeam( winningTeam ), "EG_StopExtractDropshipFlyingAway" )
+
+ wait dropship.GetSequenceDuration( "cd_dropship_rescue_side_end" ) - WARPINFXTIME
+
+ foreach ( entity player in file.evacPlayers )
+ {
+ Remote_CallFunction_Replay( player, "ServerCallback_PlayScreenFXWarpJump" )
+ }
+
+ // todo screen effects and shit
+ //WaittillAnimDone( dropship )
+ wait WARPINFXTIME
+
+ // space
+ dropship.SetOrigin( file.spacePosition.origin )
+ dropship.SetAngles( file.spacePosition.angles )
+ thread PlayAnim( dropship, "ds_space_flyby_dropshipA" )
+
+ // display player [Evacuated] in killfeed
+ foreach ( entity player in GetPlayerArray() )
+ {
+ foreach ( entity evacPlayer in file.evacPlayers )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_EvacObit", evacPlayer.GetEncodedEHandle() )
+ }
+
+ foreach ( entity player in file.evacPlayers )
+ {
+ // set skybox to space for all evac players
+ player.SetSkyCamera( GetEnt( "skybox_cam_intro" ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_DisableHudForEvac" )
+ }
+
+ wait 5.0
+
+ foreach ( entity player in GetPlayerArray() )
+ ScreenFadeToBlackForever( player, 2.0 )
+
+ wait 2.0
+
+ // end game lol
+ SetGameState( eGameState.Postmatch )
+}
+
+void function EvacShipThink( entity dropship )
+{
+ dropship.EndSignal( "OnDestroy" )
+
+ // this is the easiest way i could figure out to get a bounding box that's parented to the dropship
+ entity mover1 = CreateScriptMover( dropship.GetOrigin(), dropship.GetAngles() )
+ mover1.SetParent( dropship )
+ mover1.SetLocalOrigin( dropship.GetBoundingMaxs() - < 0, 0, 100> )
+
+ entity mover2 = CreateScriptMover( dropship.GetOrigin(), dropship.GetAngles() )
+ mover2.SetParent( dropship )
+ mover2.SetLocalOrigin( dropship.GetBoundingMins() - < 0, 0, 100 > )
+
+ while ( true )
+ {
+ foreach ( entity player in GetPlayerArrayOfTeam( dropship.GetTeam() ) )
+ {
+ if ( file.evacPlayers.contains( player ) || !IsAlive( player ) )
+ continue
+
+ vector playerPos = player.GetOrigin()
+
+ vector mover1Pos = mover1.GetOrigin()
+ vector mover2Pos = mover2.GetOrigin()
+ vector maxPos
+ maxPos.x = mover1Pos.x > mover2Pos.x ? mover1Pos.x : mover2Pos.x
+ maxPos.y = mover1Pos.y > mover2Pos.y ? mover1Pos.y : mover2Pos.y
+ maxPos.z = mover1Pos.z > mover2Pos.z ? mover1Pos.z : mover2Pos.z
+
+ vector minPos
+ minPos.x = mover1Pos.x < mover2Pos.x ? mover1Pos.x : mover2Pos.x
+ minPos.y = mover1Pos.y < mover2Pos.y ? mover1Pos.y : mover2Pos.y
+ minPos.z = mover1Pos.z < mover2Pos.z ? mover1Pos.z : mover2Pos.z
+
+ print( "\n" )
+ print( player )
+ print( playerPos )
+ print( minPos )
+ print( maxPos )
+
+ if ( playerPos.x > minPos.x && playerPos.y > minPos.y && playerPos.z > minPos.z &&
+ playerPos.x < maxPos.x && playerPos.y < maxPos.y && playerPos.z < maxPos.z )
+ {
+ print( player + " is evacuating!" )
+
+ file.evacPlayers.append( player )
+ player.SetParent( dropship )
+
+ // super duper temp
+ player.SetLocalOrigin( dropship.GetOrigin() - < 0, 10, 80 > )
+ }
+ }
+
+ WaitFrame()
+ }
+}
+
+/*void function TestEvac()
+{
+ if ( file.evacShipSpawns.len() == 0 )
+ Evac_AddLocation( GetEnt( "escape_node1" ).GetOrigin(), GetEnt( "escape_node1" ).GetAngles() )
+
+ Point shipSpawn = file.evacShipSpawns[ RandomInt( file.evacShipSpawns.len() ) ]
+
+ entity dropship = CreateDropship( GetPlayerArray()[0].GetTeam(), shipSpawn.origin, shipSpawn.angles )
+ file.evacDropship = dropship
+ DispatchSpawn( dropship )
+
+ dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+
+ print( dropship.GetSequenceDuration( "cd_dropship_rescue_side_start" ) )
+ print( dropship.GetScriptedAnimEventCycleFrac( "cd_dropship_rescue_side_start", "ReadyToLoad" ) )
+
+ float embarkStart = dropship.GetSequenceDuration( "cd_dropship_rescue_side_start" ) * dropship.GetScriptedAnimEventCycleFrac( "cd_dropship_rescue_side_start", "ReadyToLoad" )
+ print( embarkStart )
+
+ thread PlayAnim( dropship, "cd_dropship_rescue_side_start" )
+ wait embarkStart
+ print( "evac start anim done" )
+ thread TestEvacThink( dropship )
+ SetTeamActiveObjective( GetPlayerArray()[0].GetTeam(), "EG_DropshipExtract2", Time() + 30, dropship )
+
+ thread PlayAnim( dropship, "cd_dropship_rescue_side_idle", GetEnt( "escape_node1" ) )
+}
+
+void function TestEvacThink( entity dropship )
+{
+ dropship.EndSignal( "OnDestroy" )
+
+ // these numbers are probably innacurate but there's no real way of getting accurate ones and these are good enough
+ entity mover = CreateScriptMover( dropship.GetOrigin(), dropship.GetAngles() )
+ mover.SetParent( dropship )
+ mover.SetLocalOrigin( dropship.GetBoundingMaxs() - < 0, 0, 100> )
+
+ entity mover2 = CreateScriptMover( dropship.GetOrigin(), dropship.GetAngles() )
+ mover2.SetParent( dropship )
+ mover2.SetLocalOrigin( dropship.GetBoundingMins() - < 0, 0, 100> )
+
+ while ( true )
+ {
+ foreach ( entity player in GetPlayerArrayOfTeam( dropship.GetTeam() ) )
+ {
+ if ( !IsAlive( player ) )
+ continue
+
+ vector playerOrigin = player.GetOrigin()
+
+ vector dropshipMax = mover.GetOrigin()
+ vector dropshipMin = mover2.GetOrigin()
+
+ // temp, might be permenant but idk if box triggers are a thing in script
+ if ( playerOrigin.x > dropshipMin.x && playerOrigin.y > dropshipMin.y && playerOrigin.z > dropshipMin.z &&
+ playerOrigin.x < dropshipMax.x && playerOrigin.y < dropshipMax.y && playerOrigin.z < dropshipMax.z )
+ player.Die()
+ }
+
+ WaitFrame()
+ }
+}*/ \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/faction_xp.gnut b/Northstar.CustomServers/scripts/vscripts/faction_xp.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/faction_xp.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_ai_frontline.gnut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_ai_frontline.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_ai_frontline.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_ai_gamemodes.gnut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
new file mode 100644
index 000000000..cf7f7e150
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
@@ -0,0 +1,6 @@
+global function AiGameModes_Init
+
+void function AiGameModes_Init()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_capture_point.gnut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_capture_point.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_capture_point.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_frontline.gnut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_frontline.gnut
new file mode 100644
index 000000000..7ece7dc16
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_frontline.gnut
@@ -0,0 +1,159 @@
+untyped
+
+
+global function GetFrontline
+global function SetFrontline
+global function AddCalculateFrontlineCallback
+
+const DEBUG_FRONTLINE = false
+
+global struct Frontline
+{
+ vector origin = Vector( 0.0, 0.0, 0.0 )
+ vector combatDir = Vector( 0.0, 0.0, 0.0 )
+ vector line = Vector( 0.0, 0.0, 0.0 )
+ vector friendlyCenter = Vector( 0.0, 0.0, 0.0 )
+ vector enemyCenter = Vector( 0.0, 0.0, 0.0 )
+ float lastCalcTime = -1.0
+}
+
+struct
+{
+ Frontline frontline
+ array<void functionref()> calculateFrontlineCallbacks
+} file
+
+Frontline function GetFrontline( team )
+{
+ if ( file.frontline.lastCalcTime < Time() )
+ {
+ CalculateFrontline()
+ file.frontline.lastCalcTime = Time()
+ }
+
+ Frontline fl
+ fl = clone file.frontline
+
+ if ( team == TEAM_MILITIA )
+ {
+ fl.combatDir *= -1.0
+ vector temp = fl.friendlyCenter
+ fl.friendlyCenter = fl.enemyCenter
+ fl.enemyCenter = temp
+ }
+
+ return fl
+}
+
+void function AddCalculateFrontlineCallback( void functionref() callbackFunc )
+{
+ // Check if this function has already been added
+ #if DEV
+ foreach ( func in file.calculateFrontlineCallbacks )
+ {
+ Assert( func != callbackFunc )
+ }
+ #endif
+
+ file.calculateFrontlineCallbacks.append( callbackFunc )
+}
+
+void function CalculateFrontline()
+{
+ #if DEV
+ float debugTime = 0.2
+ #endif
+
+ if ( file.calculateFrontlineCallbacks.len() > 0 )
+ {
+ foreach ( callbackFunc in file.calculateFrontlineCallbacks )
+ {
+ callbackFunc()
+ }
+ }
+ else
+ {
+ vector militiaCenter = CalculateWeightedTeamCenter( TEAM_MILITIA )
+ vector imcCenter = CalculateWeightedTeamCenter( TEAM_IMC )
+
+ file.frontline.friendlyCenter = imcCenter // friendlyCenter is for TEAM_IMC by default
+ file.frontline.enemyCenter = militiaCenter
+
+ file.frontline.origin = ( militiaCenter + imcCenter ) * 0.5
+ file.frontline.combatDir = Normalize( militiaCenter - imcCenter ) // combatDir is for TEAM_IMC by default
+ file.frontline.line = CrossProduct( file.frontline.combatDir, Vector( 0.0, 0.0, 1.0 ) )
+
+ #if DEV
+ if ( DEBUG_FRONTLINE )
+ {
+ DrawBox( militiaCenter, Vector( -8.0, -8.0, -8.0 ), Vector( 8.0, 8.0, 8.0 ), 255, 102, 0, true, debugTime )
+ DrawBox( imcCenter, Vector( -8.0, -8.0, -8.0 ), Vector( 8.0, 8.0, 8.0 ), 0, 0, 255, true, debugTime )
+ DebugDrawLine( militiaCenter, imcCenter, 0, 255, 0, true, debugTime )
+ }
+ #endif
+ }
+
+ #if DEV
+ if ( DEBUG_FRONTLINE )
+ {
+ DrawBox( file.frontline.origin, Vector( -32.0, -32.0, -32.0 ), Vector( 32.0, 32.0, 32.0 ), 255, 0, 0, true, debugTime )
+ DebugDrawLine( file.frontline.origin - file.frontline.line * 500.0, file.frontline.origin + file.frontline.line * 500.0, 255, 0, 0, true, debugTime )
+ }
+ #endif
+}
+
+void function SetFrontline( vector origin, vector combatDir )
+{
+ file.frontline.origin = origin
+ file.frontline.combatDir = combatDir
+ file.frontline.line = CrossProduct( file.frontline.combatDir, Vector( 0.0, 0.0, 1.0 ) )
+}
+
+vector function CalculateWeightedTeamCenter( int team )
+{
+ array<entity> teamPlayers = GetPlayerArrayOfTeam_Alive( team )
+ int teamPlayersCount = teamPlayers.len()
+
+ if ( teamPlayersCount == 0 )
+ return Vector( 0.0, 0.0, 0.0 )
+
+ // find minimum distances between teammates
+ array<float> minTeammateDistances// = arrayofsize( teamPlayersCount, 99999.0 )
+ minTeammateDistances.resize( teamPlayersCount, 99999.0 )
+
+ for ( int i = 0; i < teamPlayersCount; i++ )
+ {
+ entity playerI = teamPlayers[ i ]
+
+ for ( int j = i + 1; j < teamPlayersCount; j++ )
+ {
+ entity playerJ = teamPlayers[ j ]
+ float distanceBetweenPlayers = Distance( playerI.GetOrigin(), playerJ.GetOrigin() )
+
+ if ( distanceBetweenPlayers < minTeammateDistances[ i ] )
+ minTeammateDistances[ i ] = distanceBetweenPlayers
+
+ if ( distanceBetweenPlayers < minTeammateDistances[ j ] )
+ minTeammateDistances[ j ] = distanceBetweenPlayers
+ }
+ }
+
+ vector weightedOrgSum = Vector( 0.0, 0.0, 0.0 )
+ float weightSum = 0.0
+ float weight = 0.0
+ float halfPi = 1.57 // passing a fraction of this value into sin which gives us the first part of a sin wave from 0 - 1
+ float maxPossibleDistance = MAX_WORLD_RANGE
+ float magicNumber = 14.0 // magic number gives the desired falloff
+
+ // calculate a weighted origin based on how close players are to teammates
+ foreach ( index, player in teamPlayers )
+ {
+ float radians = halfPi * ( minTeammateDistances[ index ] / maxPossibleDistance ) // radians will be a value between 0 - halfPi
+ weight = pow( ( 1.0 - sin( radians ) ), magicNumber ) // pow squashes the result so the curve has the falloff that's desired
+
+ weightedOrgSum += player.GetOrigin() * weight
+ weightSum += weight
+ }
+
+ return weightedOrgSum / weightSum
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
new file mode 100644
index 000000000..a30944cf3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
@@ -0,0 +1,12 @@
+global function GamemodeAITdm_Init
+global function RateSpawnpoints_Frontline
+
+void function GamemodeAITdm_Init()
+{
+
+}
+
+void function RateSpawnpoints_Frontline(int _0, array<entity> _1, int _2, entity _3)
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_at.nut
new file mode 100644
index 000000000..b75ed51b6
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_at.nut
@@ -0,0 +1,18 @@
+global function GamemodeAt_Init
+global function RateSpawnpoints_AT
+global function RateSpawnpoints_SpawnZones
+
+void function GamemodeAt_Init()
+{
+
+}
+
+void function RateSpawnpoints_AT( int checkclass, array<entity> spawnpoints, int team, entity player )
+{
+ RateSpawnpoints_Generic( checkclass, spawnpoints, team, player ) // temp
+}
+
+void function RateSpawnpoints_SpawnZones( int checkclass, array<entity> spawnpoints, int team, entity player )
+{
+ RateSpawnpoints_Generic( checkclass, spawnpoints, team, player ) // temp
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_coliseum.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_coliseum.nut
new file mode 100644
index 000000000..d8ccfc424
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_coliseum.nut
@@ -0,0 +1,12 @@
+global function GamemodeColiseum_Init
+global function GamemodeColiseum_CustomIntro
+
+void function GamemodeColiseum_Init()
+{
+
+}
+
+void function GamemodeColiseum_CustomIntro(entity _0)
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_cp.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_cp.nut
new file mode 100644
index 000000000..2fa7e4ebe
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_cp.nut
@@ -0,0 +1,12 @@
+global function GamemodeCP_Init
+global function RateSpawnpoints_CP
+
+void function GamemodeCP_Init()
+{
+
+}
+
+void function RateSpawnpoints_CP(int _0, array<entity> _1, int _2, entity _3)
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ctf.nut
new file mode 100644
index 000000000..e710a9118
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ctf.nut
@@ -0,0 +1,512 @@
+untyped
+// this needs a refactor lol
+
+global function CaptureTheFlag_Init
+global function RateSpawnpoints_CTF
+
+const array<string> SWAP_FLAG_MAPS = [
+ "mp_forwardbase_kodai",
+ "mp_lf_meadow"
+]
+
+struct {
+ entity imcFlagSpawn
+ entity imcFlag
+ entity imcFlagReturnTrigger
+
+ entity militiaFlagSpawn
+ entity militiaFlag
+ entity militiaFlagReturnTrigger
+
+ array<entity> imcCaptureAssistList
+ array<entity> militiaCaptureAssistList
+} file
+
+void function CaptureTheFlag_Init()
+{
+ PrecacheModel( CTF_FLAG_MODEL )
+ PrecacheModel( CTF_FLAG_BASE_MODEL )
+
+ CaptureTheFlagShared_Init()
+ SetSwitchSidesBased( true )
+ SetSuddenDeathBased( true )
+ //SetSpawnsUseFrontline( true )
+
+ AddCallback_OnClientConnected( CTFInitPlayer )
+
+ AddCallback_GameStateEnter( eGameState.Prematch, CreateFlags )
+ AddCallback_OnTouchHealthKit( "item_flag", OnFlagCollected )
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+ AddCallback_OnPilotBecomesTitan( DropFlagForBecomingTitan )
+
+ RegisterSignal( "FlagReturnEnded" )
+ RegisterSignal( "ResetDropTimeout" )
+
+ // setup stuff for the functions in sh_gamemode_ctf
+ // don't really like using level for stuff but just how it be
+ level.teamFlags <- {}
+
+ // setup score event earnmeter values
+ ScoreEvent_SetEarnMeterValues( "KillPilot", 0.05, 0.20 )
+ ScoreEvent_SetEarnMeterValues( "Headshot", 0.0, 0.02 )
+ ScoreEvent_SetEarnMeterValues( "FirstStrike", 0.0, 0.05 )
+ ScoreEvent_SetEarnMeterValues( "KillTitan", 0.0, 0.25 )
+ ScoreEvent_SetEarnMeterValues( "PilotBatteryStolen", 0.0, 0.35 )
+
+ ScoreEvent_SetEarnMeterValues( "FlagCarrierKill", 0.0, 0.20 )
+ ScoreEvent_SetEarnMeterValues( "FlagTaken", 0.0, 0.10 )
+ ScoreEvent_SetEarnMeterValues( "FlagCapture", 0.0, 0.30 )
+ ScoreEvent_SetEarnMeterValues( "FlagCaptureAssist", 0.0, 0.20 )
+ ScoreEvent_SetEarnMeterValues( "FlagReturn", 0.0, 0.20 )
+}
+
+void function RateSpawnpoints_CTF( int checkClass, array<entity> spawnpoints, int team, entity player )
+{
+ // ok this is the 3rd time rewriting this due to not understanding ctf spawns properly
+ // legit just
+ // if there are no enemies in base, spawn them in base
+ // if there are, spawn them outside of it ( but ideally still close )
+ // max distance away should be like, angel city markets
+
+ array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
+ array<entity> enemyPlayers = GetPlayerArrayOfTeam_Alive( GetOtherTeam( team ) )
+
+ vector startSpawnAverage
+ bool enemyInBase = false
+ foreach ( entity startSpawn in startSpawns )
+ {
+ startSpawnAverage += startSpawn.GetOrigin()
+
+ foreach ( entity enemy in enemyPlayers )
+ {
+ if ( Distance( startSpawn.GetOrigin(), enemy.GetOrigin() ) <= 1000.0 )
+ {
+ enemyInBase = true
+ break
+ }
+ }
+ }
+
+ startSpawnAverage /= startSpawns.len()
+
+ print( "spawn for " + player + " is there an enemy in base?" + enemyInBase )
+
+ foreach ( entity spawn in spawnpoints )
+ {
+ float rating = 0.0
+
+ bool isStart = false
+ foreach ( entity startSpawn in startSpawns )
+ {
+ if ( Distance2D( spawn.GetOrigin(), startSpawn.GetOrigin() ) < 1500.0 ) // this was for some reason the only distance i could get to work
+ {
+ isStart = true
+ break
+ }
+ }
+
+ if ( isStart )
+ {
+ if ( !enemyInBase )
+ rating = 1000 + RandomFloat( 100.0 )
+ else
+ rating = -1000.0
+ }
+ else if ( !isStart && enemyInBase )
+ {
+ entity friendlyFlag
+ entity enemyFlag
+ if ( team == TEAM_IMC )
+ {
+ friendlyFlag = file.imcFlagSpawn
+ enemyFlag = file.militiaFlagSpawn
+ }
+ else
+ {
+ friendlyFlag = file.militiaFlagSpawn
+ enemyFlag = file.imcFlagSpawn
+ }
+
+ float dist = Distance2D( spawn.GetOrigin(), enemyFlag.GetOrigin() )
+ float flagDist = Distance2D( startSpawnAverage, enemyFlag.GetOrigin() )
+
+ if ( dist < ( flagDist / 2 ) ) // spawns shouldn't be closer to enemies than they are to us
+ rating = -1000.0
+ if ( dist > flagDist * 1.1 ) // spawn is behind startspawns
+ rating = -1000.0
+ else
+ {
+ rating = dist // closer spawns are better
+
+ foreach( entity enemy in enemyPlayers ) // reduce rating if enemies are near by
+ if ( Distance( enemy.GetOrigin(), spawn.GetOrigin() ) < 500.0 )
+ rating /= 2
+ }
+ }
+
+ spawn.CalculateRating( checkClass, team, rating, rating )
+ }
+}
+
+void function CTFInitPlayer( entity player )
+{
+ if ( !IsValid( file.imcFlagSpawn ) )
+ return
+
+ vector imcSpawn = file.imcFlagSpawn.GetOrigin()
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z )
+
+ vector militiaSpawn = file.militiaFlagSpawn.GetOrigin()
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ).GetParent() == victim )
+ {
+ if ( victim != attacker && attacker.IsPlayer() )
+ AddPlayerScore( attacker, "FlagCarrierKill", victim )
+
+ DropFlag( victim )
+ }
+}
+
+void function CreateFlags()
+{
+ if ( IsValid( file.imcFlagSpawn ) )
+ {
+ file.imcFlagSpawn.Destroy()
+ file.imcFlag.Destroy()
+ file.imcFlagReturnTrigger.Destroy()
+
+ file.militiaFlagSpawn.Destroy()
+ file.militiaFlag.Destroy()
+ file.militiaFlagReturnTrigger.Destroy()
+ }
+
+ foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) )
+ {
+ // on some maps flags are on the opposite side from what they should be
+ // likely this is because respawn uses distance checks from spawns to check this in official
+ // but i don't like doing that so just using a list of maps to swap them on lol
+ bool switchedSides = HasSwitchedSides() == 1
+ bool shouldSwap = SWAP_FLAG_MAPS.contains( GetMapName() ) ? !switchedSides : switchedSides
+
+ int flagTeam = spawn.GetTeam()
+ if ( shouldSwap )
+ {
+ flagTeam = GetOtherTeam( flagTeam )
+ SetTeam( spawn, flagTeam )
+ }
+
+ // create flag base
+ entity base = CreatePropDynamic( CTF_FLAG_BASE_MODEL, spawn.GetOrigin(), spawn.GetAngles(), 0 )
+ SetTeam( base, spawn.GetTeam() )
+ svGlobal.flagSpawnPoints[ flagTeam ] = base
+
+ // create flag
+ entity flag = CreateEntity( "item_flag" )
+ flag.SetValueForModelKey( CTF_FLAG_MODEL )
+ SetTeam( flag, flagTeam )
+ flag.MarkAsNonMovingAttachment()
+ DispatchSpawn( flag )
+ flag.SetModel( CTF_FLAG_MODEL )
+ flag.SetOrigin( spawn.GetOrigin() + < 0, 0, base.GetBoundingMaxs().z * 2 > ) // ensure flag doesn't spawn clipped into geometry
+ flag.SetVelocity( < 0, 0, 1 > )
+
+ flag.s.canTake <- true
+ flag.s.playersReturning <- []
+
+ level.teamFlags[ flag.GetTeam() ] <- flag
+
+ entity returnTrigger = CreateEntity( "trigger_cylinder" )
+ SetTeam( returnTrigger, flagTeam )
+ returnTrigger.SetRadius( CTF_GetFlagReturnRadius() )
+ returnTrigger.SetAboveHeight( CTF_GetFlagReturnRadius() )
+ returnTrigger.SetBelowHeight( CTF_GetFlagReturnRadius() )
+
+ returnTrigger.SetEnterCallback( OnPlayerEntersFlagReturnTrigger )
+ returnTrigger.SetLeaveCallback( OnPlayerExitsFlagReturnTrigger )
+
+ DispatchSpawn( returnTrigger )
+
+ thread TrackFlagReturnTrigger( flag, returnTrigger )
+
+ if ( flagTeam == TEAM_IMC )
+ {
+ file.imcFlagSpawn = base
+ file.imcFlag = flag
+ file.imcFlagReturnTrigger = returnTrigger
+
+ SetGlobalNetEnt( "imcFlag", file.imcFlag )
+ SetGlobalNetEnt( "imcFlagHome", file.imcFlagSpawn )
+ }
+ else
+ {
+ file.militiaFlagSpawn = base
+ file.militiaFlag = flag
+ file.militiaFlagReturnTrigger = returnTrigger
+
+ SetGlobalNetEnt( "milFlag", file.militiaFlag )
+ SetGlobalNetEnt( "milFlagHome", file.militiaFlagSpawn )
+ }
+ }
+
+ foreach ( entity player in GetPlayerArray() )
+ CTFInitPlayer( player )
+}
+
+void function TrackFlagReturnTrigger( entity flag, entity returnTrigger )
+{
+ // this is a bit of a hack, it seems parenting the return trigger to the flag actually sets the pickup radius of the flag to be the same as the trigger
+ // this isn't wanted since only pickups should use that additional radius
+ flag.EndSignal( "OnDestroy" )
+
+ while ( true )
+ {
+ returnTrigger.SetOrigin( flag.GetOrigin() )
+ WaitFrame()
+ }
+}
+
+void function SetFlagStateForTeam( int team, int state )
+{
+ if ( state == eFlagState.Away ) // we tell the client the flag is the player carrying it if they're carrying it
+ SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", ( team == TEAM_IMC ? file.imcFlag : file.militiaFlag ).GetParent() )
+ else
+ SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", team == TEAM_IMC ? file.imcFlag : file.militiaFlag )
+
+ SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state )
+}
+
+bool function OnFlagCollected( entity player, entity flag )
+{
+ if ( !IsAlive( player ) || flag.GetParent() != null || player.IsTitan() || player.IsPhaseShifted() )
+ return false
+
+ if ( player.GetTeam() != flag.GetTeam() && flag.s.canTake )
+ GiveFlag( player, flag ) // pickup enemy flag
+ else if ( player.GetTeam() == flag.GetTeam() && IsFlagHome( flag ) && PlayerHasEnemyFlag( player ) )
+ CaptureFlag( player, GetFlagForTeam( GetOtherTeam( flag.GetTeam() ) ) ) // cap the flag
+
+ return false // don't wanna delete the flag entity
+}
+
+void function GiveFlag( entity player, entity flag )
+{
+ print( player + " picked up the flag!" )
+ flag.Signal( "ResetDropTimeout" )
+
+ flag.SetParent( player, "FLAG" )
+ thread DropFlagIfPhased( player, flag )
+
+ // do notifications
+ MessageToPlayer( player, eEventNotifications.YouHaveTheEnemyFlag )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_GrabFlag" )
+ AddPlayerScore( player, "FlagTaken", player )
+ PlayFactionDialogueToPlayer( "ctf_flagPickupYou", player )
+
+ MessageToTeam( player.GetTeam(), eEventNotifications.PlayerHasEnemyFlag, player, player )
+ EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamGrabFlag", player.GetTeam(), player )
+ PlayFactionDialogueToTeamExceptPlayer( "ctf_flagPickupFriendly", player.GetTeam(), player )
+
+ MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerHasFriendlyFlag, player, player )
+ EmitSoundOnEntityToTeam( flag, "UI_CTF_EnemyGrabFlag", flag.GetTeam() )
+
+ SetFlagStateForTeam( flag.GetTeam(), eFlagState.Away ) // used for held
+}
+
+void function DropFlagIfPhased( entity player, entity flag )
+{
+ player.EndSignal( "StartPhaseShift" )
+
+ OnThreadEnd( function() : ( player )
+ {
+ DropFlag( player, true )
+ })
+
+ while( flag.GetParent() == player )
+ WaitFrame()
+}
+
+void function DropFlagForBecomingTitan( entity pilot, entity titan )
+{
+ DropFlag( pilot, true )
+}
+
+void function DropFlag( entity player, bool realDrop = true )
+{
+ entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) )
+
+ if ( flag.GetParent() != player )
+ return
+
+ print( player + " dropped the flag!" )
+
+ flag.ClearParent()
+ flag.SetAngles( < 0, 0, 0 > )
+ flag.SetVelocity( < 0, 0, 0 > )
+
+ if ( realDrop )
+ {
+ // start drop timeout countdown
+ thread TrackFlagDropTimeout( flag )
+
+ // add to capture assists
+ if ( player.GetTeam() == TEAM_IMC )
+ file.imcCaptureAssistList.append( player )
+ else
+ file.militiaCaptureAssistList.append( player )
+
+ // do notifications
+ MessageToPlayer( player, eEventNotifications.YouDroppedTheEnemyFlag )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagDrop" )
+
+ MessageToTeam( player.GetTeam(), eEventNotifications.PlayerDroppedEnemyFlag, player, player )
+ // todo need a sound here maybe
+
+ MessageToTeam( GetOtherTeam( player.GetTeam() ), eEventNotifications.PlayerDroppedFriendlyFlag, player, player )
+ // todo need a sound here maybe
+ }
+
+ SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home ) // used for return prompt
+}
+
+void function TrackFlagDropTimeout( entity flag )
+{
+ flag.EndSignal( "ResetDropTimeout" )
+
+ wait CTF_GetDropTimeout()
+
+ ResetFlag( flag )
+}
+
+void function ResetFlag( entity flag )
+{
+ // ensure we can't pickup the flag after it's been dropped but before it's been reset
+ flag.s.canTake = false
+
+ if ( flag.GetParent() != null )
+ DropFlag( flag.GetParent(), false )
+
+ entity spawn
+ if ( flag.GetTeam() == TEAM_IMC )
+ spawn = file.imcFlagSpawn
+ else
+ spawn = file.militiaFlagSpawn
+
+ flag.SetOrigin( spawn.GetOrigin() + < 0, 0, spawn.GetBoundingMaxs().z + 1 > )
+
+ // we can take it again now
+ flag.s.canTake = true
+
+ SetFlagStateForTeam( flag.GetTeam(), eFlagState.None ) // used for home
+
+ flag.Signal( "ResetDropTimeout" )
+}
+
+void function CaptureFlag( entity player, entity flag )
+{
+ // reset flag
+ ResetFlag( flag )
+
+ print( player + " captured the flag!" )
+
+ // score
+ int team = player.GetTeam()
+ AddTeamScore( team, 1 )
+ AddPlayerScore( player, "FlagCapture", player )
+ player.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 ) // add 1 to captures on scoreboard
+
+ array<entity> assistList
+ if ( player.GetTeam() == TEAM_IMC )
+ assistList = file.imcCaptureAssistList
+ else
+ assistList = file.militiaCaptureAssistList
+
+ foreach( entity assistPlayer in assistList )
+ if ( player != assistPlayer )
+ AddPlayerScore( assistPlayer, "FlagCaptureAssist", player )
+
+ assistList.clear()
+
+ // notifs
+ MessageToPlayer( player, eEventNotifications.YouCapturedTheEnemyFlag )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" )
+
+ MessageToTeam( team, eEventNotifications.PlayerCapturedEnemyFlag, player, player )
+ EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamScore", player.GetTeam(), player )
+
+ MessageToTeam( GetOtherTeam( team ), eEventNotifications.PlayerCapturedFriendlyFlag, player, player )
+ EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyScore", flag.GetTeam() )
+
+ if ( GameRules_GetTeamScore( team ) == GameMode_GetRoundScoreLimit( GAMETYPE ) - 1 )
+ {
+ PlayFactionDialogueToTeam( "ctf_notifyWin1more", team )
+ PlayFactionDialogueToTeam( "ctf_notifyLose1more", GetOtherTeam( team ) )
+ }
+}
+
+void function OnPlayerEntersFlagReturnTrigger( entity trigger, entity player )
+{
+ entity flag
+ if ( trigger.GetTeam() == TEAM_IMC )
+ flag = file.imcFlag
+ else
+ flag = file.militiaFlag
+
+ if ( !player.IsPlayer() || !player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null )
+ return
+
+ thread TryReturnFlag( player, flag )
+}
+
+void function OnPlayerExitsFlagReturnTrigger( entity trigger, entity player )
+{
+ entity flag
+ if ( trigger.GetTeam() == TEAM_IMC )
+ flag = file.imcFlag
+ else
+ flag = file.militiaFlag
+
+ if ( !player.IsPlayer() || !player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null )
+ return
+
+ player.Signal( "FlagReturnEnded" )
+}
+
+void function TryReturnFlag( entity player, entity flag )
+{
+ // start return progress bar
+ Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StartReturnFlagProgressBar", Time() + CTF_GetFlagReturnTime() )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagReturnMeter" )
+
+ OnThreadEnd( function() : ( player )
+ {
+ // cleanup
+ Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StopReturnFlagProgressBar" )
+ StopSoundOnEntity( player, "UI_CTF_1P_FlagReturnMeter" )
+ })
+
+ player.EndSignal( "FlagReturnEnded" )
+ player.EndSignal( "OnDeath" )
+
+ wait CTF_GetFlagReturnTime()
+
+ // flag return succeeded
+ // return flag
+ ResetFlag( flag )
+
+ // do notifications for return
+ MessageToPlayer( player, eEventNotifications.YouReturnedFriendlyFlag )
+ AddPlayerScore( player, "FlagReturn", player )
+ player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 )
+
+ MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerReturnedFriendlyFlag, null, player )
+ EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_TeamReturnsFlag", flag.GetTeam() )
+ PlayFactionDialogueToTeam( "ctf_flagReturnedFriendly", flag.GetTeam() )
+
+ MessageToTeam( GetOtherTeam( flag.GetTeam() ), eEventNotifications.PlayerReturnedEnemyFlag, null, player )
+ EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyReturnsFlag", GetOtherTeam( flag.GetTeam() ) )
+ PlayFactionDialogueToTeam( "ctf_flagReturnedEnemy", GetOtherTeam( flag.GetTeam() ) )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_fd.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_fd.nut
new file mode 100644
index 000000000..b5f700e51
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_fd.nut
@@ -0,0 +1,12 @@
+global function GamemodeFD_Init
+global function RateSpawnpoints_FD
+
+void function GamemodeFD_Init()
+{
+
+}
+
+void function RateSpawnpoints_FD(int _0, array<entity> _1, int _2, entity _3)
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ffa.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ffa.nut
new file mode 100644
index 000000000..3292693a8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ffa.nut
@@ -0,0 +1,14 @@
+global function FFA_Init
+
+void function FFA_Init()
+{
+ Evac_SetEnabled( false )
+
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim != attacker && victim.IsPlayer() && attacker.IsPlayer() )
+ AddTeamScore( attacker.GetTeam(), 1 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_lts.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_lts.nut
new file mode 100644
index 000000000..18cf97359
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_lts.nut
@@ -0,0 +1,235 @@
+untyped
+global function GamemodeLts_Init
+
+struct {
+ entity lastDamageInfoVictim
+ entity lastDamageInfoAttacker
+ int lastDamageInfoMethodOfDeath
+ float lastDamageInfoTime
+
+ bool shouldDoHighlights
+} file
+
+void function GamemodeLts_Init()
+{
+ // gamemode settings
+ SetShouldUsePickLoadoutScreen( true )
+ SetRoundBased( true )
+ SetRespawnsEnabled( false )
+ Riff_ForceSetEliminationMode( eEliminationMode.PilotsTitans )
+ SetServerVar( "roundWinningKillReplayEnabled", true ) // really ought to get a function for setting this
+
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+ AddDeathCallback( "npc_titan", OnTitanKilled )
+
+ AddDamageCallback( "player", OnPlayerDamaged )
+ AddDamageCallback( "npc_titan", OnTitanDamaged )
+
+ AddCallback_OnPilotBecomesTitan( GamemodeLTS_RefreshHighlight )
+ AddCallback_OnTitanBecomesPilot( GamemodeLTS_RefreshHighlight )
+
+ ClassicMP_SetCustomIntro( GamemodeLTS_Intro, 0.0 )
+}
+
+void function GamemodeLTS_Intro()
+{
+ AddCallback_GameStateEnter( eGameState.Prematch, GamemodeLTS_IntroOnPrematchStart )
+}
+
+void function GamemodeLTS_IntroOnPrematchStart()
+{
+ ClassicMP_OnIntroStarted()
+
+ SetGameState( eGameState.Playing )
+ foreach ( entity player in GetPlayerArray() )
+ thread GamemodeLTS_IntroSpawnPlayer( player )
+
+ ClassicMP_OnIntroFinished()
+
+ SetKillcamsEnabled( true )
+ file.shouldDoHighlights = false
+ thread GamemodeLTS_PlayingThink()
+}
+
+void function GamemodeLTS_IntroSpawnPlayer( entity player )
+{
+ if ( IsAlive( player ) )
+ {
+ player.Die()
+ WaitFrame()
+ }
+
+ RespawnAsTitan( player, false )
+
+ while ( !player.IsTitan() )
+ WaitFrame()
+
+ TryGameModeAnnouncement( player )
+}
+
+void function GamemodeLTS_PlayingThink()
+{
+ WaitFrame() // due to how this is all written the prematch callbacks might not've run by the time this starts
+ // so we need to wait a frame to ensure they've been run so gameEndTime is set
+ svGlobal.levelEnt.EndSignal( "RoundEnd" ) // end this on round end
+
+ float endTime = expect float ( GetServerVar( "gameEndTime" ) )
+ print( "ENDTIME " + endTime )
+
+ // wait until 30sec left
+ wait endTime - 30 - Time()
+ foreach ( entity player in GetPlayerArray() )
+ {
+ // warn there's 30 seconds left
+ Remote_CallFunction_NonReplay( player, "ServerCallback_LTSThirtySecondWarning" )
+
+ // do highlights
+ file.shouldDoHighlights = true
+ GamemodeLTS_RefreshHighlight( player, null )
+ }
+
+ wait endTime - Time()
+ thread CheckTitansForDraw() // need to thread this so we don't accidentally signal roundend in the same thread that'll be ended when we hit roundend
+}
+
+void function GamemodeLTS_RefreshHighlight( entity player, entity titan )
+{
+ if ( !file.shouldDoHighlights )
+ return
+
+ Highlight_SetEnemyHighlight( player, "enemy_sonar" ) // i think this needs a different effect, this works for now tho
+
+ if ( player.GetPetTitan() != null )
+ Highlight_SetEnemyHighlight( player.GetPetTitan(), "enemy_sonar" )
+}
+
+void function CheckTeamTitans( int team )
+{
+ if ( GetGameState() != eGameState.Playing )
+ return
+
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( team )
+
+ int numLivingTitans = 0
+ int numLivingPlayers = 0
+ foreach ( entity player in teamPlayers )
+ {
+ // wouldn't it be easier just to only track and increment numLivingTitans if the owner is alive?
+ // yes it would
+ // but for some reason this is not how respawn does it
+ if ( IsAlive( player ) )
+ numLivingPlayers++
+
+ if ( IsAlive( player.GetPetTitan() ) || player.IsTitan() )
+ numLivingTitans++
+ }
+
+ if ( numLivingPlayers == 0 || numLivingTitans == 0 )
+ {
+ SetKillcamsEnabled( false ) // make sure killcams can't interrupt the round winning kill replay
+ //SetRoundWinningKillReplayInfo( file.lastDamageInfoVictim, file.lastDamageInfoAttacker, file.lastDamageInfoMethodOfDeath, file.lastDamageInfoTime )
+ SetWinner( GetOtherTeam( team ), "#GAMEMODE_ENEMY_TITANS_DESTROYED", "#GAMEMODE_FRIENDLY_TITANS_DESTROYED" )
+ }
+}
+
+void function CheckTitansForDraw()
+{
+ int militiaLivingTitans
+ int imcLivingTitans
+
+ float militiaCombinedHealth
+ float imcCombinedHealth
+
+ foreach ( entity player in GetPlayerArray() )
+ {
+ // only need to track titans for this, can assume that neither team has lost due to titan death if the round is still going
+ entity titan = IsAlive( player.GetPetTitan() ) ? player.GetPetTitan() : player
+ if ( titan.IsPlayer() && !titan.IsTitan() )
+ continue
+
+ if ( IsAlive( titan ) )
+ if ( player.GetTeam() == TEAM_MILITIA )
+ {
+ // doomed is counted as 0 health in this
+ militiaCombinedHealth += titan.GetTitanSoul().IsDoomed() ? 0.0 : GetHealthFrac( titan )
+ militiaLivingTitans++
+ }
+ else
+ {
+ // doomed is counted as 0 health in this
+ imcCombinedHealth += titan.GetTitanSoul().IsDoomed() ? 0.0 : GetHealthFrac( titan )
+ imcLivingTitans++
+ }
+ }
+
+ SetKillcamsEnabled( false )
+ //SetRoundWinningKillReplayInfo( null, null, 0, 0 ) // make sure we don't do a replay
+
+ // default if both teams are equal
+ int winner = TEAM_UNASSIGNED
+
+ string winnerSubstr
+ string loserSubstr
+
+ if ( militiaLivingTitans != imcLivingTitans ) // one team has a titan lead
+ {
+ winnerSubstr = "#GAMEMODE_TITAN_TITAN_ADVANTAGE"
+ loserSubstr = "#GAMEMODE_TITAN_TITAN_DISADVANTAGE"
+
+ winner = militiaLivingTitans > imcLivingTitans ? TEAM_MILITIA : TEAM_IMC
+ }
+ else if ( militiaCombinedHealth != imcCombinedHealth ) // one team has a health lead
+ {
+ winnerSubstr = "#GAMEMODE_TITAN_DAMAGE_ADVANTAGE"
+ loserSubstr = "#GAMEMODE_TITAN_DAMAGE_DISADVANTAGE"
+
+ winner = militiaCombinedHealth > imcCombinedHealth ? TEAM_MILITIA : TEAM_IMC
+ }
+
+ print( "CheckTitansForDraw(): " + winner )
+ SetWinner( winner, winnerSubstr, loserSubstr )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ file.lastDamageInfoVictim = victim
+ file.lastDamageInfoAttacker = DamageInfo_GetAttacker( damageInfo )
+ file.lastDamageInfoMethodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ file.lastDamageInfoTime = Time()
+
+ if ( !victim.isSpawning )
+ CheckTeamTitans( victim.GetTeam() )
+}
+
+void function OnTitanKilled( entity titan, var damageInfo )
+{
+ file.lastDamageInfoVictim = titan.GetOwner()
+ file.lastDamageInfoAttacker = DamageInfo_GetAttacker( damageInfo )
+ file.lastDamageInfoMethodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ file.lastDamageInfoTime = Time()
+
+ if ( IsPetTitan( titan ) && !titan.GetBossPlayer().isSpawning )
+ CheckTeamTitans( titan.GetTeam() )
+}
+
+void function AddToDamageStat( var damageInfo )
+{
+ // todo: this needs to not count selfdamage
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ float amount = DamageInfo_GetDamage( damageInfo )
+
+ if ( attacker.IsPlayer() )
+ attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, amount ) // titan damage on
+}
+
+void function OnPlayerDamaged( entity player, var damageInfo )
+{
+ if ( player.IsTitan() )
+ AddToDamageStat( damageInfo )
+}
+
+void function OnTitanDamaged( entity titan, var damageInfo )
+{
+ if ( IsPetTitan( titan ) )
+ AddToDamageStat( damageInfo )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_mfd.nut
new file mode 100644
index 000000000..6e8e9fa37
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_mfd.nut
@@ -0,0 +1,230 @@
+untyped
+global function GamemodeMfd_Init
+
+struct {
+ entity imcLastMark
+ entity militiaLastMark
+} file
+
+void function GamemodeMfd_Init()
+{
+ GamemodeMfdShared_Init()
+
+ RegisterSignal( "MarkKilled" )
+
+ AddCallback_OnPlayerKilled( UpdateMarksForKill )
+ AddCallback_GameStateEnter( eGameState.Playing, CreateInitialMarks )
+}
+
+void function CreateInitialMarks()
+{
+ entity imcMark = CreateEntity( MARKER_ENT_CLASSNAME )
+ imcMark.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ SetTeam( imcMark, TEAM_IMC )
+ SetTargetName( imcMark, MARKET_ENT_MARKED_NAME ) // why is it market_ent lol
+ DispatchSpawn( imcMark )
+ FillMFDMarkers( imcMark )
+
+ entity imcPendingMark = CreateEntity( MARKER_ENT_CLASSNAME )
+ imcPendingMark.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ SetTeam( imcPendingMark, TEAM_IMC )
+ SetTargetName( imcPendingMark, MARKET_ENT_PENDING_MARKED_NAME )
+ DispatchSpawn( imcPendingMark )
+ FillMFDMarkers( imcPendingMark )
+
+ entity militiaMark = CreateEntity( MARKER_ENT_CLASSNAME )
+ militiaMark.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ SetTeam( militiaMark, TEAM_MILITIA )
+ SetTargetName( militiaMark, MARKET_ENT_MARKED_NAME )
+ DispatchSpawn( militiaMark )
+ FillMFDMarkers( militiaMark )
+
+ entity militiaPendingMark = CreateEntity( MARKER_ENT_CLASSNAME )
+ militiaPendingMark.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ SetTeam( militiaPendingMark, TEAM_MILITIA )
+ SetTargetName( militiaPendingMark, MARKET_ENT_PENDING_MARKED_NAME )
+ DispatchSpawn( militiaPendingMark )
+ FillMFDMarkers( militiaPendingMark )
+
+ thread MFDThink()
+}
+
+void function MFDThink()
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ entity imcMark
+ entity militiaMark
+
+ while ( true )
+ {
+ if ( !TargetsMarkedImmediately() )
+ wait MFD_BETWEEN_MARKS_TIME
+
+ // wait for enough players to spawn
+ array<entity> imcPlayers
+ array<entity> militiaPlayers
+ while ( imcPlayers.len() == 0 || militiaPlayers.len() == 0 )
+ {
+ imcPlayers = GetPlayerArrayOfTeam( TEAM_IMC )
+ militiaPlayers = GetPlayerArrayOfTeam( TEAM_MILITIA )
+
+ WaitFrame()
+ }
+
+ // get marks, wanna increment the mark each mark, reset on player change
+ int imcIndex = imcPlayers.find( imcMark )
+ if ( imcIndex == -1 ) // last mark
+ imcIndex = 0
+ else
+ imcIndex = ( imcIndex + 1 ) % imcPlayers.len()
+
+ imcMark = imcPlayers[ imcIndex ]
+
+ int militiaIndex = militiaPlayers.find( imcMark )
+ if ( militiaIndex == -1 ) // last mark
+ militiaIndex = 0
+ else
+ militiaIndex = ( militiaIndex + 1 ) % militiaPlayers.len()
+
+ militiaMark = militiaPlayers[ militiaIndex ]
+
+ level.mfdPendingMarkedPlayerEnt[ TEAM_IMC ].SetOwner( imcMark )
+ level.mfdPendingMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( militiaMark )
+
+ foreach ( entity player in GetPlayerArray() )
+ {
+ Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_MFD_StartNewMarkCountdown", Time() + MFD_COUNTDOWN_TIME )
+ }
+
+ // reset if mark leaves
+ bool shouldReset
+ float endTime = Time() + MFD_COUNTDOWN_TIME
+ while ( endTime > Time() || ( !IsAlive( imcMark ) || !IsAlive( militiaMark ) ) )
+ {
+ if ( !IsValid( imcMark ) || !IsValid( militiaMark ) )
+ {
+ shouldReset = true
+ break
+ }
+
+ WaitFrame()
+ }
+
+ if ( shouldReset )
+ continue
+
+ waitthread MarkPlayers( imcMark, militiaMark )
+ }
+}
+
+void function MarkPlayers( entity imcMark, entity militiaMark )
+{
+ imcMark.EndSignal( "OnDestroy" )
+ imcMark.EndSignal( "Disconnected" )
+
+ militiaMark.EndSignal( "OnDestroy" )
+ militiaMark.EndSignal( "Disconnected" )
+
+ OnThreadEnd( function() : ( imcMark, militiaMark )
+ {
+ // clear marks
+ level.mfdActiveMarkedPlayerEnt[ TEAM_IMC ].SetOwner( null )
+ level.mfdActiveMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( null )
+
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" )
+ })
+
+ // clear pending marks
+ level.mfdPendingMarkedPlayerEnt[ TEAM_IMC ].SetOwner( null )
+ level.mfdPendingMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( null )
+
+ // set marks
+ level.mfdActiveMarkedPlayerEnt[ TEAM_IMC ].SetOwner( imcMark )
+ level.mfdActiveMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( militiaMark )
+
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" )
+
+ // wait until mark dies
+ entity deadMark = expect entity( svGlobal.levelEnt.WaitSignal( "MarkKilled" ).mark )
+
+ // award points
+ entity livingMark = GetMarked( GetOtherTeam( deadMark.GetTeam() ) )
+ livingMark.SetPlayerGameStat( PGS_DEFENSE_SCORE, livingMark.GetPlayerGameStat( PGS_DEFENSE_SCORE ) + 1 )
+ AddTeamScore( livingMark.GetTeam(), 1 )
+}
+
+void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim == GetMarked( victim.GetTeam() ) )
+ {
+ svGlobal.levelEnt.Signal( "MarkKilled", { mark = victim } )
+
+ if ( attacker.IsPlayer() )
+ attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 )
+ }
+}
+
+/*
+void function MarkPlayers()
+{
+ // todo: need to handle disconnecting marks
+ if ( !TargetsMarkedImmediately() )
+ wait MFD_BETWEEN_MARKS_TIME
+
+
+ // wait until we actually have 2 valid players
+ array<entity> imcPlayers
+ array<entity> militiaPlayers
+ while ( imcPlayers.len() == 0 || militiaPlayers.len() == 0 )
+ {
+ imcPlayers = GetPlayerArrayOfTeam( TEAM_IMC )
+ militiaPlayers = GetPlayerArrayOfTeam( TEAM_MILITIA )
+
+ WaitFrame()
+ }
+
+ // decide marks
+ entity imcMark = imcPlayers[ RandomInt( imcPlayers.len() ) ]
+ level.mfdPendingMarkedPlayerEnt[ TEAM_IMC ].SetOwner( imcMark )
+
+ entity militiaMark = militiaPlayers[ RandomInt( militiaPlayers.len() ) ]
+ level.mfdPendingMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( militiaMark )
+
+ foreach ( entity player in GetPlayerArray() )
+ {
+ Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_MFD_StartNewMarkCountdown", Time() + MFD_COUNTDOWN_TIME )
+ }
+
+ wait MFD_COUNTDOWN_TIME
+
+ while ( !IsAlive( imcMark ) || !IsAlive( militiaMark ) )
+ WaitFrame()
+
+ // clear pending marks
+ level.mfdPendingMarkedPlayerEnt[ TEAM_IMC ].SetOwner( null )
+ level.mfdPendingMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( null )
+
+ // set marks
+ level.mfdActiveMarkedPlayerEnt[ TEAM_IMC ].SetOwner( imcMark )
+ level.mfdActiveMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( militiaMark )
+
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" )
+
+ while ( IsAlive( imcMark ) && IsAlive( militiaMark ) )
+ WaitFrame()
+
+ // clear marks
+ level.mfdActiveMarkedPlayerEnt[ TEAM_IMC ].SetOwner( null )
+ level.mfdActiveMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( null )
+
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" )
+
+ thread MarkPlayers()
+}*/ \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ps.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ps.nut
new file mode 100644
index 000000000..3a852f918
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ps.nut
@@ -0,0 +1,12 @@
+global function GamemodePs_Init
+
+void function GamemodePs_Init()
+{
+ AddCallback_OnPlayerKilled( GiveScoreForPlayerKill )
+}
+
+void function GiveScoreForPlayerKill( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim != attacker && victim.IsPlayer() && attacker.IsPlayer() )
+ AddTeamScore( attacker.GetTeam(), 1 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_speedball.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_speedball.nut
new file mode 100644
index 000000000..9c70cfb90
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_speedball.nut
@@ -0,0 +1,137 @@
+global function GamemodeSpeedball_Init
+
+struct {
+ entity flagBase
+ entity flag
+ entity flagCarrier
+} file
+
+void function GamemodeSpeedball_Init()
+{
+ PrecacheModel( CTF_FLAG_MODEL )
+ PrecacheModel( CTF_FLAG_BASE_MODEL )
+
+ // gamemode settings
+ SetRoundBased( true )
+ SetRespawnsEnabled( false )
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ Riff_ForceSetEliminationMode( eEliminationMode.Pilots )
+ SetServerVar( "roundWinningKillReplayEnabled", true ) // really ought to get a function for setting this
+
+ AddSpawnCallbackEditorClass( "script_ref", "info_speedball_flag", CreateFlag )
+
+ AddCallback_GameStateEnter( eGameState.Playing, ResetFlag )
+ AddCallback_OnTouchHealthKit( "item_flag", OnFlagCollected )
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+ SetTimeoutWinnerDecisionFunc( TimeoutCheckFlagHolder )
+
+ ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, NOINTRO_INTRO_LENGTH )
+}
+
+void function CreateFlag( entity flagSpawn )
+{
+ entity flagBase = CreatePropDynamic( CTF_FLAG_BASE_MODEL, flagSpawn.GetOrigin(), flagSpawn.GetAngles() )
+
+ entity flag = CreateEntity( "item_flag" )
+ flag.SetValueForModelKey( CTF_FLAG_MODEL )
+ flag.MarkAsNonMovingAttachment()
+ DispatchSpawn( flag )
+ flag.SetModel( CTF_FLAG_MODEL )
+ flag.SetOrigin( flagBase.GetOrigin() + < 0, 0, flagBase.GetBoundingMaxs().z + 1 > )
+ flag.SetVelocity( < 0, 0, 1 > )
+
+ file.flag = flag
+ file.flagBase = flagBase
+}
+
+bool function OnFlagCollected( entity player, entity flag )
+{
+ if ( !IsAlive( player ) || flag.GetParent() != null || player.IsTitan() || player.IsPhaseShifted() )
+ return false
+
+ GiveFlag( player )
+ return false // so flag ent doesn't despawn
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( file.flagCarrier == victim )
+ DropFlag()
+
+ if ( victim.IsPlayer() && GetGameState() == eGameState.Playing )
+ {
+ // this REALLY ought to be an elimationmode thing rather than gamemode-based
+ int livingPlayers
+ foreach ( entity player in GetPlayerArrayOfTeam( victim.GetTeam() ) )
+ if ( IsAlive( player ) )
+ livingPlayers++
+
+ if ( livingPlayers == 0 )
+ SetWinner( GetOtherTeam( victim.GetTeam() ) )
+ else if ( livingPlayers == 1 )
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SPEEDBALL_LastPlayer", player.GetTeam() != victim.GetTeam() )
+ }
+}
+
+void function GiveFlag( entity player )
+{
+ file.flag.SetParent( player, "FLAG" )
+ file.flagCarrier = player
+ SetGlobalNetEnt( "flagCarrier", player )
+ thread DropFlagIfPhased( player )
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_GrabFlag" )
+ foreach ( entity otherPlayer in GetPlayerArray() )
+ {
+ MessageToPlayer( otherPlayer, eEventNotifications.SPEEDBALL_FlagPickedUp, player )
+
+ if ( otherPlayer.GetTeam() == player.GetTeam() )
+ EmitSoundOnEntityToTeamExceptPlayer( file.flag, "UI_CTF_3P_TeamGrabFlag", player.GetTeam(), player )
+ }
+}
+
+void function DropFlagIfPhased( entity player )
+{
+ player.EndSignal( "StartPhaseShift" )
+
+ OnThreadEnd( function() : ( player )
+ {
+ if ( file.flag.GetParent() == player )
+ DropFlag()
+ })
+
+ while( file.flag.GetParent() == player )
+ WaitFrame()
+}
+
+void function DropFlag()
+{
+ file.flag.ClearParent()
+ file.flag.SetAngles( < 0, 0, 0 > )
+ SetGlobalNetEnt( "flagCarrier", file.flag )
+ EmitSoundOnEntityOnlyToPlayer( file.flagCarrier, file.flagCarrier, "UI_CTF_1P_FlagDrop" )
+
+ foreach ( entity player in GetPlayerArray() )
+ MessageToPlayer( player, eEventNotifications.SPEEDBALL_FlagDropped, file.flagCarrier )
+
+ file.flagCarrier = null
+}
+
+void function ResetFlag()
+{
+ file.flag.ClearParent()
+ file.flag.SetAngles( < 0, 0, 0 > )
+ file.flag.SetVelocity( < 0, 0, 1 > ) // hack: for some reason flag won't have gravity if i don't do this
+ file.flag.SetOrigin( file.flagBase.GetOrigin() + < 0, 0, file.flagBase.GetBoundingMaxs().z * 2 > )
+ file.flagCarrier = null
+ SetGlobalNetEnt( "flagCarrier", file.flag )
+}
+
+int function TimeoutCheckFlagHolder()
+{
+ if ( file.flagCarrier == null )
+ return TEAM_UNASSIGNED
+
+ return file.flagCarrier.GetTeam()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_tdm.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_tdm.nut
new file mode 100644
index 000000000..9e80b8635
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_tdm.nut
@@ -0,0 +1,19 @@
+global function GamemodeTdm_Init
+global function RateSpawnpoints_Directional
+
+void function GamemodeTdm_Init()
+{
+ AddCallback_OnPlayerKilled( GiveScoreForPlayerKill )
+}
+
+void function GiveScoreForPlayerKill( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim != attacker && victim.IsPlayer() && attacker.IsPlayer() )
+ AddTeamScore( attacker.GetTeam(), 1 )
+}
+
+void function RateSpawnpoints_Directional( int checkclass, array<entity> spawnpoints, int team, entity player )
+{
+ // temp
+ RateSpawnpoints_Generic( checkclass, spawnpoints, team, player )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ttdm.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ttdm.nut
new file mode 100644
index 000000000..92119c1cc
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_gamemode_ttdm.nut
@@ -0,0 +1,6 @@
+global function GamemodeTTDM_Init
+
+void function GamemodeTTDM_Init()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_hardpoints.gnut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_hardpoints.gnut
new file mode 100644
index 000000000..dab433f1e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_hardpoints.gnut
@@ -0,0 +1,33 @@
+global function Hardpoints_Init
+global function CapturePoint_GetStartProgress
+global function CapturePoint_GetCappingTeam
+global function CapturePoint_GetOwningTeam
+global function CapturePoint_GetGoalProgress
+
+global array<entity> HARDPOINTS
+
+void function Hardpoints_Init()
+{
+
+
+}
+
+float function CapturePoint_GetStartProgress( entity hardpoint )
+{
+ return 0.5
+}
+
+int function CapturePoint_GetCappingTeam( entity hardpoint )
+{
+ return 0
+}
+
+int function CapturePoint_GetOwningTeam( entity hardpoint )
+{
+ return 0
+}
+
+float function CapturePoint_GetGoalProgress( entity hardpoint )
+{
+ return 1.0
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_riff_floor_is_lava.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_riff_floor_is_lava.nut
new file mode 100644
index 000000000..b660e89ff
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_riff_floor_is_lava.nut
@@ -0,0 +1,102 @@
+global function RiffFloorIsLava_Init
+
+void function RiffFloorIsLava_Init()
+{
+ AddCallback_OnPlayerRespawned( FloorIsLava_PlayerRespawned )
+
+ AddSpawnCallback( "env_fog_controller", InitLavaFogController )
+ AddCallback_EntitiesDidLoad( CreateCustomSpawns )
+}
+
+void function InitLavaFogController( entity fogController )
+{
+ fogController.kv.fogztop = GetVisibleFogTop()
+ fogController.kv.fogzbottom = GetVisibleFogBottom()
+ fogController.kv.foghalfdisttop = "60000"
+ fogController.kv.foghalfdistbottom = "200"
+ fogController.kv.fogdistoffset = "0"
+ fogController.kv.fogdensity = ".85"
+
+ fogController.kv.forceontosky = true
+ //fogController.kv.foghalfdisttop = "10000"
+}
+
+void function CreateCustomSpawns()
+{
+ thread CreateCustomSpawns_Threaded()
+}
+
+void function CreateCustomSpawns_Threaded()
+{
+ WaitEndFrame() // wait for spawns to clear
+
+ float raycastTop = GetLethalFogTop() + 2500.0
+ array< vector > raycastPositions
+ foreach ( entity hardpoint in GetEntArrayByClass_Expensive( "info_hardpoint" ) )
+ {
+ if ( !hardpoint.HasKey( "hardpointGroup" ) )
+ continue
+
+ //if ( hardpoint.kv.hardpointGroup != "A" && hardpoint.kv.hardpointGroup != "B" && hardpoint.kv.hardpointGroup != "C" )
+ if ( hardpoint.kv.hardpointGroup != "B" ) // roughly map center
+ continue
+
+ vector pos = hardpoint.GetOrigin()
+ for ( int x = -2000; x < 2000; x += 200 )
+ for ( int y = -2000; y < 2000; y += 200 )
+ raycastPositions.append( < x, y, raycastTop > )
+ }
+
+ int validSpawnsCreated = 0
+ foreach ( vector raycastPos in raycastPositions )
+ {
+ //vector hardpoint = validHardpoints[ RandomInt( validHardpoints.len() ) ].GetOrigin()
+ //float a = RandomFloat( 1 ) * 2 * PI
+ //float r = 1000.0 * sqrt( RandomFloat( 1 ) )
+ //
+ //vector castStart = < hardpoint.x + r * cos( a ), hardpoint.y + r * sin( a ), >
+ //vector castEnd = < hardpoint.x + r * cos( a ), hardpoint.y + r * sin( a ), GetLethalFogBottom() >
+
+ TraceResults trace = TraceLine( raycastPos, < raycastPos.x, raycastPos.y, GetLethalFogBottom() >, [], TRACE_MASK_SOLID, TRACE_COLLISION_GROUP_NONE ) // should only hit world
+ print( "raycast: " + trace.endPos )
+ if ( trace.endPos.z >= GetLethalFogTop() )
+ {
+ print( "creating floor is lava spawn at " + trace.endPos )
+ validSpawnsCreated++
+
+ // valid spot, create a spawn
+ entity spawnpoint = CreateEntity( "info_spawnpoint_human" )
+ spawnpoint.SetOrigin( trace.endPos )
+ spawnpoint.kv.ignoreGamemode = 1
+ DispatchSpawn( spawnpoint )
+ }
+ }
+}
+
+void function FloorIsLava_PlayerRespawned( entity player )
+{
+ thread FloorIsLava_ThinkForPlayer( player )
+}
+
+void function FloorIsLava_ThinkForPlayer( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+
+ while ( true )
+ {
+ WaitFrame()
+
+ if ( player.GetOrigin().z < GetLethalFogTop() )
+ {
+ // do damage
+ float damageMultiplier = 0.08
+ if ( player.IsTitan() )
+ damageMultiplier *= 0.05
+
+ player.TakeDamage( player.GetMaxHealth() * damageMultiplier, null, null, { damageSourceId = eDamageSourceId.floor_is_lava } )
+
+ wait 0.1
+ }
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/_spawnpoints.gnut b/Northstar.CustomServers/scripts/vscripts/gamemodes/_spawnpoints.gnut
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/_spawnpoints.gnut
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemode_mfd.nut b/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemode_mfd.nut
new file mode 100644
index 000000000..4410a5136
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemode_mfd.nut
@@ -0,0 +1,66 @@
+untyped
+
+global function GamemodeMfdShared_Init
+global function GetMarked
+global function GetPendingMarked
+global function FillMFDMarkers
+global function TargetsMarkedImmediately
+global function IsTitanMarkedForDeathMode
+
+void function GamemodeMfdShared_Init()
+{
+ // mfd mfdActiveMarkedPlayerEnt are server side entities with a boss player that marks the marked
+ level.mfdActiveMarkedPlayerEnt <- {}
+ level.mfdActiveMarkedPlayerEnt[ TEAM_IMC ] <- null
+ level.mfdActiveMarkedPlayerEnt[ TEAM_MILITIA ] <- null
+
+ level.mfdPendingMarkedPlayerEnt <- {}
+ level.mfdPendingMarkedPlayerEnt[ TEAM_IMC ] <- null
+ level.mfdPendingMarkedPlayerEnt[ TEAM_MILITIA ] <- null
+
+ SetWaveSpawnInterval( 8.0 )
+}
+
+entity function GetMarked( int team )
+{
+ if ( IsValid( level.mfdActiveMarkedPlayerEnt[ team ] ) )
+ return expect entity( level.mfdActiveMarkedPlayerEnt[ team ] ).GetOwner()
+
+ return null
+}
+
+entity function GetPendingMarked( int team )
+{
+ if ( IsValid( level.mfdPendingMarkedPlayerEnt[ team ] ) )
+ return expect entity( level.mfdPendingMarkedPlayerEnt[ team ] ).GetOwner()
+
+ return null
+}
+
+function FillMFDMarkers( entity ent ) //Ent used for kill replay related issues...
+{
+ print( "FillMFDMarkers " + ent )
+
+ if ( ent.GetTargetName() == MARKET_ENT_MARKED_NAME )
+ {
+ Assert( ent.GetTeam() != TEAM_UNASSIGNED )
+ level.mfdActiveMarkedPlayerEnt[ ent.GetTeam() ] = ent
+ }
+ else if ( ent.GetTargetName() == MARKET_ENT_PENDING_MARKED_NAME )
+ {
+ Assert( ent.GetTeam() != TEAM_UNASSIGNED )
+ level.mfdPendingMarkedPlayerEnt[ ent.GetTeam() ] = ent
+ }
+
+ return
+}
+
+function TargetsMarkedImmediately()
+{
+ return IsRoundBased() && IsPilotEliminationBased()
+}
+
+bool function IsTitanMarkedForDeathMode()
+{
+ return GetCurrentPlaylistVarInt( "titan_marked_for_death", 0 ) == 1
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemodes.gnut b/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemodes.gnut
new file mode 100644
index 000000000..df7acb78e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemodes.gnut
@@ -0,0 +1,816 @@
+
+global function GameModes_Init
+
+global function GameMode_Create
+global function GameMode_SetName
+global function GameMode_SetGameModeAttackAnnouncement
+global function GameMode_SetGameModeDefendAnnouncement
+global function GameMode_SetAttackDesc
+global function GameMode_SetDefendDesc
+global function GameMode_SetIcon
+global function GameMode_SetDefaultScoreLimits
+global function GameMode_AddScoreboardColumnData
+global function GameMode_SetGameModeAnnouncement
+global function GameMode_SetDefaultTimeLimits
+global function GameMode_SetDesc
+global function GameMode_SetColor
+global function GameMode_SetSuddenDeath
+
+global function GameMode_GetScoreLimit
+global function GameMode_GetRoundScoreLimit
+global function GameMode_GetTimeLimit
+global function GameMode_GetRoundTimeLimit
+global function GameMode_GetGameModeAnnouncement
+global function GameMode_GetGameModeAttackAnnouncement
+global function GameMode_GetGameModeDefendAnnouncement
+global function GameMode_GetDesc
+global function GameMode_GetName
+global function GameMode_GetIcon
+global function GameMode_GetColor
+global function GameMode_GetAttackDesc
+global function GameMode_GetDefendDesc
+global function GameMode_GetPilotSpawnpointsRatingFunc
+global function GameMode_GetTitanSpawnpointsRatingFunc
+global function GameMode_GetScoreCompareFunc
+global function GameMode_GetSuddenDeathEnabled
+global function GameMode_GetEvacEnabled
+global function GameMode_GetGameEndingWarning
+global function GameMode_GetGameEndingConversation
+global function GameMode_GetScoreboardColumnTitles
+global function GameMode_GetScoreboardColumnScoreTypes
+global function GameMode_GetScoreboardColumnNumDigits
+global function GameMode_GetCustomIntroAnnouncement
+global function GameMode_RunServerInits
+global function GameMode_RunClientInits
+global function GameMode_RunSharedInits
+global function GameMode_IsDefined
+
+global function GameMode_AddServerInit
+global function GameMode_AddClientInit
+global function GameMode_AddSharedInit
+global function GameMode_SetScoreCompareFunc
+global function GameMode_SetPilotSpawnpointsRatingFunc
+global function GameMode_SetTitanSpawnpointsRatingFunc
+global function GameMode_SetCustomIntroAnnouncement
+
+global function GameMode_GetGameModeId
+
+global function GameMode_SetEvacEnabled
+
+global function GameMode_GetLoadoutSelectTime
+
+global struct GamemodeSettings
+{
+ string name = ""
+ string name_localized = "Undefined Game Mode"
+ string desc_localized = "Undefined Game Mode Description"
+ string desc_attack = ""
+ string desc_defend = ""
+ string gameModeAnnoucement = ""
+ string gameModeAttackAnnoucement = ""
+ string gameModeDefendAnnoucement = ""
+ asset icon = $"ui/menu/playlist/classic"
+ array<int> color = [127, 127, 127, 255]
+ array< void functionref() > serverInits
+ array< void functionref() > clientInits
+ array< void functionref() > sharedInits
+ void functionref( int, array<entity>, int, entity ) pilotSpawnpointRatingFunc
+ void functionref( int, array<entity>, int, entity ) titanSpawnpointRatingFunc
+ IntFromEntityCompare scoreCompareFunc
+ int defaultScoreLimit = 100
+ int defaultTimeLimit = 10
+ int defaultRoundScoreLimit = 5
+ float defaultRoundTimeLimit = 5.0
+ bool evacEnabled = true
+ string gameModeEndingWarning = "#GAMEMODE_END_IN_N_SECONDS"
+ string gameModeEndingConversation = ""
+ bool suddenDeathEnabled = false
+ array<string> scoreboardColumnTitles
+ array<int> scoreboardColumnScoreTypes
+ array<int> scoreboardColumnNumDigits
+ void functionref(entity) customIntroAnnouncementFunc
+}
+
+
+
+// Don't remove items from this list once the game is in production
+// Durango online analytics needs the numbers for each mode to stay the same
+// DO NOT CHANGE THESE VALUES AFTER THEY HAVE GONE LIVE
+global enum eGameModes
+{
+ invalid = -1,
+ TEAM_DEATHMATCH_ID = 0,
+ CAPTURE_POINT_ID = 1,
+ ATTRITION_ID = 2,
+ CAPTURE_THE_FLAG_ID = 3,
+ MARKED_FOR_DEATH_ID = 4,
+ LAST_TITAN_STANDING_ID = 5,
+ WINGMAN_LAST_TITAN_STANDING_ID = 6,
+ PILOT_SKIRMISH_ID = 7,
+ MARKED_FOR_DEATH_PRO_ID = 8,
+ COOPERATIVE_ID = 9,
+ GAMEMODE_SP_ID = 10,
+ TITAN_BRAWL_ID = 11,
+ FFA_ID = 12,
+ PROTOTYPE2 = 13,
+ WINGMAN_PILOT_SKIRMISH_ID = 14,
+ PROTOTYPE3 = 15,
+ PROTOTYPE4 = 16,
+ FREE_AGENCY_ID = 17,
+ PROTOTYPE6 = 18,
+ COLISEUM_ID = 19,
+ PROTOTYPE7 = 20,
+ AI_TDM_ID = 21,
+ PROTOTYPE8 = 22,
+ PROTOTYPE9 = 23,
+ SPEEDBALL_ID = 24,
+ PROTOTYPE10 = 25,
+ PROTOTYPE11 = 26,
+ PROTOTYPE12 = 27,
+ FD_ID = 28,
+ PROTOTYPE14 = 29,
+}
+
+const table<string, int> gameModesStringToIdMap = {
+ [ TEAM_DEATHMATCH ] = eGameModes.TEAM_DEATHMATCH_ID,
+ [ PILOT_SKIRMISH ] = eGameModes.PILOT_SKIRMISH_ID,
+ [ CAPTURE_POINT ] = eGameModes.CAPTURE_POINT_ID,
+ [ ATTRITION ] = eGameModes.ATTRITION_ID,
+ [ CAPTURE_THE_FLAG ] = eGameModes.CAPTURE_THE_FLAG_ID,
+ [ LAST_TITAN_STANDING ] = eGameModes.LAST_TITAN_STANDING_ID,
+ [ GAMEMODE_SP ] = eGameModes.GAMEMODE_SP_ID,
+ [ FFA ] = eGameModes.FFA_ID,
+ [ COLISEUM ] = eGameModes.COLISEUM_ID,
+ [ AI_TDM ] = eGameModes.AI_TDM_ID,
+ [ SPEEDBALL ] = eGameModes.SPEEDBALL_ID,
+ [ MARKED_FOR_DEATH ] = eGameModes.MARKED_FOR_DEATH_ID,
+ [ TITAN_BRAWL ] = eGameModes.TITAN_BRAWL_ID,
+ [ FREE_AGENCY ] = eGameModes.FREE_AGENCY_ID,
+ [ FD ] = eGameModes.FD_ID,
+ [ FD_EASY ] = eGameModes.FD_ID,
+ [ FD_NORMAL ] = eGameModes.FD_ID,
+ [ FD_HARD ] = eGameModes.FD_ID,
+ [ FD_MASTER ] = eGameModes.FD_ID,
+ [ FD_INSANE ] = eGameModes.FD_ID,
+}
+
+struct
+{
+ table< string, GamemodeSettings > gameModeDefs
+} file
+
+void function GameModes_Init()
+{
+ string gameMode
+
+ gameMode = GAMEMODE_SP
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#GAMEMODE_SOLO" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/coop" ) //HACK TODO: get a sp icon
+ GameMode_SetDesc( gameMode, "#GAMEMODE_SOLO_HINT" )
+ GameMode_SetDefaultScoreLimits( gameMode, 0, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 0, 0.0 )
+
+ gameMode = CAPTURE_POINT
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_hardpoint" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "hp_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_CP" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_hardpoint_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/cp" )
+ GameMode_SetDefaultScoreLimits( gameMode, 500, 500 )
+ GameMode_SetDefaultTimeLimits( gameMode, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_ASSAULT", PGS_ASSAULT_SCORE, 4 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_DEFENSE", PGS_DEFENSE_SCORE, 4 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_SetColor( gameMode, [46, 188, 180, 255] )
+
+ gameMode = LAST_TITAN_STANDING
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_last_titan_standing" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "lts_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_LTS" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_last_titan_standing_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/lts" )
+ GameMode_SetDefaultScoreLimits( gameMode, 0, 4 )
+ GameMode_SetDefaultTimeLimits( gameMode, 5, 4.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_TITAN_KILLS", PGS_TITAN_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_TITAN_DAMAGE", PGS_ASSAULT_SCORE, 6 )
+ GameMode_SetColor( gameMode, [223, 94, 0, 255] )
+
+ gameMode = ATTRITION
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_attrition" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "bh_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_AT" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_attrition_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/at" )
+ GameMode_SetDefaultScoreLimits( gameMode, 5000, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 4 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_BONUS", PGS_SCORE, 4 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_SetColor( gameMode, [88, 172, 67, 255] )
+
+ gameMode = TEAM_DEATHMATCH
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_pilot_hunter" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "phunt_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_TDM" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_pilot_hunter_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/tdm" )
+ GameMode_SetDefaultScoreLimits( gameMode, 50, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_DEATHS", PGS_DEATHS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_ASSISTS", PGS_ASSISTS, 2 )
+ GameMode_SetColor( gameMode, [212, 83, 152, 255] )
+
+ gameMode = AI_TDM
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_aitdm" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "gnrc_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_TDM" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_aitdm_hint" )
+ GameMode_SetIcon( gameMode, FFA_MODE_ICON )
+ GameMode_SetDefaultScoreLimits( gameMode, 1, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 3 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_TITAN_KILLS", PGS_TITAN_KILLS, 1 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_GRUNT_KILLS", PGS_NPC_KILLS, 2 )
+ // GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_DEATHS", PGS_DEATHS, 2 )
+ GameMode_SetColor( gameMode, [200, 40, 40, 255] )
+
+ gameMode = COLISEUM
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_coliseum" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "gnrc_modeDesc" ) //TODO: This is just the mode name as opposed to instructions...
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_PS" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_coliseum_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/tdm" )
+ GameMode_SetDefaultScoreLimits( gameMode, 15, 2 )
+ GameMode_SetDefaultTimeLimits( gameMode, 0, 4.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_SetColor( gameMode, [151, 71, 175, 255] )
+
+ gameMode = PILOT_SKIRMISH
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_pilot_skirmish" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "pvp_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_PS" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_pilot_skirmish_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/tdm" )
+ GameMode_SetDefaultScoreLimits( gameMode, 100, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_DEATHS", PGS_DEATHS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_ASSISTS", PGS_ASSISTS, 2 )
+ GameMode_SetColor( gameMode, [207, 191, 59, 255] )
+
+ gameMode = CAPTURE_THE_FLAG
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_capture_the_flag" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "ctf_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_CTF" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_capture_the_flag_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/ctf" )
+ GameMode_SetSuddenDeath( gameMode, true )
+ GameMode_SetDefaultScoreLimits( gameMode, 0, 5 )
+ GameMode_SetDefaultTimeLimits( gameMode, 0, 3.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_CAPTURES", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_RETURNS", PGS_DEFENSE_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_SetColor( gameMode, [61, 117, 193, 255] )
+
+ gameMode = FFA
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_ffa" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "ffa_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_FFA" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_ffa_hint" )
+ GameMode_SetIcon( gameMode, FFA_MODE_ICON )
+ GameMode_SetDefaultScoreLimits( gameMode, 10, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 10, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_TITAN_KILLS", PGS_TITAN_KILLS, 2 )
+ GameMode_SetColor( gameMode, [147, 204, 57, 255] )
+
+ gameMode = FREE_AGENCY
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_free_agents" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "freea_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_FREE_AGENCY" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_free_agents_hint" )
+ GameMode_SetIcon( gameMode, FFA_MODE_ICON )
+ GameMode_SetDefaultScoreLimits( gameMode, 10, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_TITAN_KILLS", PGS_TITAN_KILLS, 2 )
+ GameMode_SetColor( gameMode, [127, 127, 127, 255] )
+
+ gameMode = SPEEDBALL
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_speedball" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "gnrc_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_CTF" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_speedball_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/ctf" )
+ GameMode_SetDefaultScoreLimits( gameMode, 0, 5 )
+ GameMode_SetDefaultTimeLimits( gameMode, 0, 1.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_FLAGS_SECURED", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_DEATHS", PGS_DEATHS, 2 )
+ GameMode_SetColor( gameMode, [225, 141, 8, 255] )
+
+ gameMode = MARKED_FOR_DEATH
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_marked_for_death" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "mfd_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_MFD" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_marked_for_death_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/mfd" )
+ GameMode_SetDefaultScoreLimits( gameMode, 10, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 10, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_MFD_SCORE", PGS_ASSAULT_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_MFD_MARKS_OUTLASTED", PGS_DEFENSE_SCORE, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_SetColor( gameMode, [127, 127, 127, 255] )
+
+ gameMode = TITAN_BRAWL
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_titan_brawl" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "lts_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_TTDM" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_titan_brawl_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/lts" )
+ GameMode_SetDefaultScoreLimits( gameMode, 30, 0 )
+ GameMode_SetDefaultTimeLimits( gameMode, 15, 0.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_KILLS", PGS_PILOT_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_DEATHS", PGS_DEATHS, 2 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_TITAN_DAMAGE", PGS_ASSAULT_SCORE, 6 )
+ GameMode_SetColor( gameMode, [83, 212, 152, 255] )
+
+ gameMode = FD
+ GameMode_Create( gameMode )
+ GameMode_SetName( gameMode, "#PL_fd" )
+ #if FACTION_DIALOGUE_ENABLED
+ GameMode_SetGameModeAnnouncement( gameMode, "fd_modeDesc" )
+ #else
+ GameMode_SetGameModeAnnouncement( gameMode, "GameModeAnnounce_PS" )
+ #endif
+ GameMode_SetDesc( gameMode, "#PL_fd_hint" )
+ GameMode_SetIcon( gameMode, $"ui/menu/playlist/tdm" )
+ GameMode_SetSuddenDeath( gameMode, true )
+ GameMode_SetDefaultScoreLimits( gameMode, 0, 5 )
+ GameMode_SetDefaultTimeLimits( gameMode, 0, 5.0 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_TOTAL_SCORE", PGS_DETONATION_SCORE, 4 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_COMBAT_SCORE", PGS_ASSAULT_SCORE, 4 )
+ //GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_HEALING_SCORE", PGS_DISTANCE_SCORE, 3 )
+ GameMode_AddScoreboardColumnData( gameMode, "#SCOREBOARD_SUPPORT_SCORE", PGS_DEFENSE_SCORE, 4 )
+
+ #if DEVSCRIPTS
+ DevGameModes_Init()
+ #endif
+
+ #if SERVER || CLIENT
+ InitCustomGamemodes()
+ GameModes_Init_SV_CL()
+ #endif
+
+ ////
+ GameMode_VerifyModes()
+}
+
+// TODO: scoreboards
+
+/*************************************************************
+ Setters
+*************************************************************/
+
+GamemodeSettings function GameMode_Create( string gameModeName )
+{
+ Assert( !(gameModeName in file.gameModeDefs), "Gametype already defined!" )
+
+ GamemodeSettings settings
+ file.gameModeDefs[gameModeName] <- settings
+
+ return file.gameModeDefs[gameModeName]
+}
+
+void function GameMode_SetName( string gameModeName, string nameText )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut (" + gameModeName + ")" )
+ file.gameModeDefs[gameModeName].name_localized = nameText
+}
+
+void function GameMode_SetGameModeAnnouncement( string gameModeName, string gameModeAnnoucement ) //Note: Still need to register the conversation
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].gameModeAnnoucement = gameModeAnnoucement
+}
+
+void function GameMode_SetGameModeAttackAnnouncement( string gameModeName, string gameModeAttackAnnoucement ) //Note: Still need to register the conversation
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].gameModeAttackAnnoucement = gameModeAttackAnnoucement
+}
+
+void function GameMode_SetGameModeDefendAnnouncement( string gameModeName, string gameModeDefendAnnoucement )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" ) //Note: Still need to register the conversation
+ file.gameModeDefs[gameModeName].gameModeDefendAnnoucement = gameModeDefendAnnoucement
+}
+
+void function GameMode_SetDesc( string gameModeName, string descText )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].desc_localized = descText
+}
+
+void function GameMode_SetAttackDesc( string gameModeName, string descText )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].desc_attack = descText
+}
+
+void function GameMode_SetDefendDesc( string gameModeName, string descText )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].desc_defend = descText
+}
+
+void function GameMode_SetIcon( string gameModeName, asset icon )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].icon = icon
+}
+
+void function GameMode_SetColor( string gameModeName, array<int> color )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].color = color
+}
+
+void function GameMode_SetSuddenDeath( string gameModeName, bool state )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].suddenDeathEnabled = state
+}
+
+void function GameMode_AddServerInit( string gameModeName, void functionref() func )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].serverInits.append( func )
+}
+
+void function GameMode_AddClientInit( string gameModeName, void functionref() func )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].clientInits.append( func )
+}
+
+void function GameMode_AddSharedInit( string gameModeName, void functionref() func )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].sharedInits.append( func )
+}
+
+void function GameMode_SetPilotSpawnpointsRatingFunc( string gameModeName, void functionref( int, array<entity>, int, entity ) func )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].pilotSpawnpointRatingFunc = func
+}
+
+void function GameMode_SetTitanSpawnpointsRatingFunc( string gameModeName, void functionref( int, array<entity>, int, entity ) func )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].titanSpawnpointRatingFunc = func
+}
+
+void function GameMode_SetScoreCompareFunc( string gameModeName, int functionref( entity, entity ) func )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].scoreCompareFunc = func
+}
+
+void function GameMode_SetDefaultScoreLimits( string gameModeName, int scoreLimit, int roundScoreLimit )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].defaultScoreLimit = scoreLimit
+ file.gameModeDefs[gameModeName].defaultRoundScoreLimit = roundScoreLimit
+}
+
+void function GameMode_SetDefaultTimeLimits( string gameModeName, int timeLimit, float roundTimeLimit )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].defaultTimeLimit = timeLimit
+ file.gameModeDefs[gameModeName].defaultRoundTimeLimit = roundTimeLimit
+}
+
+void function GameMode_AddScoreboardColumnData( string gameModeName, string title, int scoreType, int numDigits )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].scoreboardColumnTitles.append( title )
+ file.gameModeDefs[gameModeName].scoreboardColumnScoreTypes.append( scoreType )
+ file.gameModeDefs[gameModeName].scoreboardColumnNumDigits.append( numDigits )
+}
+
+void function GameMode_SetEvacEnabled( string gameModeName, bool value )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].evacEnabled = value
+}
+
+void function GameMode_SetGameEndingWarning( string gameModeName, string warning )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].gameModeEndingWarning = warning
+}
+
+void function GameMode_SetGameEndingConversation( string gameModeName, string conversation )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].gameModeEndingConversation = conversation
+}
+
+void function GameMode_SetCustomIntroAnnouncement( string gameModeName, void functionref(entity) func )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ file.gameModeDefs[gameModeName].customIntroAnnouncementFunc = func
+}
+
+/*************************************************************
+ Getters
+*************************************************************/
+
+int function GameMode_GetScoreLimit( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return GetCurrentPlaylistVarInt( "scorelimit", file.gameModeDefs[gameModeName].defaultScoreLimit )
+}
+
+int function GameMode_GetRoundScoreLimit( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return GetCurrentPlaylistVarInt( "roundscorelimit", file.gameModeDefs[gameModeName].defaultRoundScoreLimit )
+}
+
+int function GameMode_GetTimeLimit( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return GetCurrentPlaylistVarInt( "timelimit", file.gameModeDefs[gameModeName].defaultTimeLimit )
+}
+
+float function GameMode_GetRoundTimeLimit( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return GetCurrentPlaylistVarFloat( "roundtimelimit", file.gameModeDefs[gameModeName].defaultRoundTimeLimit )
+}
+
+string function GameMode_GetGameModeAnnouncement( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].gameModeAnnoucement
+}
+
+string function GameMode_GetGameModeAttackAnnouncement( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].gameModeAttackAnnoucement
+}
+
+string function GameMode_GetGameModeDefendAnnouncement( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].gameModeDefendAnnoucement
+}
+
+string function GameMode_GetDesc( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].desc_localized
+}
+
+string function GameMode_GetName( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].name_localized
+}
+
+asset function GameMode_GetIcon( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].icon
+}
+
+array<int> function GameMode_GetColor( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].color
+}
+
+string function GameMode_GetAttackDesc( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].desc_attack
+}
+
+string function GameMode_GetDefendDesc( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].desc_defend
+}
+
+void functionref( int, array<entity>, int, entity ) function GameMode_GetPilotSpawnpointsRatingFunc( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ Assert( file.gameModeDefs[gameModeName].pilotSpawnpointRatingFunc != null, "No respawn func set for " + gameModeName )
+ return file.gameModeDefs[gameModeName].pilotSpawnpointRatingFunc
+}
+
+void functionref( int, array<entity>, int, entity ) function GameMode_GetTitanSpawnpointsRatingFunc( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ Assert( file.gameModeDefs[gameModeName].titanSpawnpointRatingFunc != null, "No respawn func set for " + gameModeName )
+ return file.gameModeDefs[gameModeName].titanSpawnpointRatingFunc
+}
+
+IntFromEntityCompare function GameMode_GetScoreCompareFunc( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].scoreCompareFunc
+}
+
+bool function GameMode_GetSuddenDeathEnabled( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].suddenDeathEnabled
+}
+
+bool function GameMode_GetEvacEnabled( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].evacEnabled
+}
+
+string function GameMode_GetGameEndingWarning( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].gameModeEndingWarning
+}
+
+string function GameMode_GetGameEndingConversation( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].gameModeEndingConversation
+}
+
+array<string> function GameMode_GetScoreboardColumnTitles( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].scoreboardColumnTitles
+}
+
+array<int> function GameMode_GetScoreboardColumnScoreTypes( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].scoreboardColumnScoreTypes
+}
+
+array<int> function GameMode_GetScoreboardColumnNumDigits( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].scoreboardColumnNumDigits
+}
+
+void functionref(entity) function GameMode_GetCustomIntroAnnouncement( string gameModeName )
+{
+ Assert( gameModeName in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+ return file.gameModeDefs[gameModeName].customIntroAnnouncementFunc
+}
+
+/*************************************************************
+
+*************************************************************/
+void function GameMode_RunServerInits()
+{
+ Assert( GAMETYPE in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+
+ foreach ( initFunc in file.gameModeDefs[GAMETYPE].serverInits )
+ {
+ initFunc()
+ }
+}
+
+void function GameMode_RunClientInits()
+{
+ Assert( GAMETYPE in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+
+ foreach ( initFunc in file.gameModeDefs[GAMETYPE].clientInits )
+ {
+ initFunc()
+ }
+}
+
+void function GameMode_RunSharedInits()
+{
+ Assert( GAMETYPE in file.gameModeDefs, "No MP Gametype specified in _settings.nut" )
+
+ foreach ( initFunc in file.gameModeDefs[GAMETYPE].sharedInits )
+ {
+ initFunc()
+ }
+}
+
+void function GameMode_VerifyModes()
+{
+ foreach ( gameModeName, gameModeData in file.gameModeDefs )
+ {
+ int gameModeId = GameMode_GetGameModeId( gameModeName )
+ bool foundGameModeIdString = false
+ foreach ( idString, gameModeEnumId in eGameModes )
+ {
+ if ( gameModeEnumId != gameModeId )
+ continue
+
+ foundGameModeIdString = true
+ break
+ }
+ Assert( foundGameModeIdString, "GAMEMODE not defined properly in eGameModes!" )
+
+ GAMETYPE_TEXT[gameModeName] <- gameModeData.name_localized
+ GAMETYPE_DESC[gameModeName] <- gameModeData.desc_localized
+ GAMETYPE_ICON[gameModeName] <- gameModeData.icon
+ GAMETYPE_COLOR[gameModeName] <- gameModeData.color
+ #if CLIENT
+ PrecacheHUDMaterial( GAMETYPE_ICON[gameModeName] )
+ #endif
+ }
+}
+
+int function GameMode_GetGameModeId( string gameModeName )
+{
+ if ( gameModeName in gameModesStringToIdMap )
+ return gameModesStringToIdMap[gameModeName]
+
+ #if DEVSCRIPTS
+ if ( gameModeName in devGameModesStringToIdMap )
+ return devGameModesStringToIdMap[gameModeName]
+ #endif
+
+ Assert( false, "GAMEMODE " + gameModeName + " not defined in gameModesStringToIdMap" )
+
+ return 0
+}
+
+bool function GameMode_IsDefined( string gameModeName )
+{
+ return (gameModeName in file.gameModeDefs)
+}
+
+float function GameMode_GetLoadoutSelectTime()
+{
+ return GetCurrentPlaylistVarFloat( "pick_loadout_time", 5.0 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemodes_custom.gnut b/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemodes_custom.gnut
new file mode 100644
index 000000000..51f8bf9e3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/gamemodes/sh_gamemodes_custom.gnut
@@ -0,0 +1,20 @@
+untyped
+global function InitCustomGamemodes
+global function AddCallback_OnCustomGamemodesInit
+
+struct {
+ array<void functionref()> onCustomGamemodesInitCallbacks
+} file
+
+void function InitCustomGamemodes()
+{
+ print( "InitCustomGamemodes" )
+
+ foreach ( void functionref() callback in file.onCustomGamemodesInitCallbacks )
+ callback()
+}
+
+void function AddCallback_OnCustomGamemodesInit( void functionref() callback )
+{
+ file.onCustomGamemodesInitCallbacks.append( callback )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/item_inventory/sv_item_inventory.gnut b/Northstar.CustomServers/scripts/vscripts/item_inventory/sv_item_inventory.gnut
new file mode 100644
index 000000000..ff2a4c7cf
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/item_inventory/sv_item_inventory.gnut
@@ -0,0 +1,60 @@
+global function Sv_ItemInventory_Init
+global function PIN_Init
+global function SvPlayerInventory_ItemCount
+global function PlayerInventory_StartCriticalSection
+global function PlayerInventory_EndCriticalSectionForWeaponOnEndFrame
+global function PlayerInventory_CountTurrets
+global function PIN_PlayerAbility
+global function PIN_PlayerAbilityReady
+global function PIN_DamageDone
+global function PlayerInventory_RefreshEquippedState
+
+void function Sv_ItemInventory_Init()
+{
+
+}
+
+void function PIN_Init()
+{
+
+}
+
+int function SvPlayerInventory_ItemCount(entity player)
+{
+ return 0
+}
+
+void function PlayerInventory_StartCriticalSection(entity player)
+{
+
+}
+
+void function PlayerInventory_EndCriticalSectionForWeaponOnEndFrame(entity player)
+{
+
+}
+
+int function PlayerInventory_CountTurrets(entity owner)
+{
+ return 0
+}
+
+void function PIN_PlayerAbility(entity player, string name, string action, /*no idea what this type is supposed to be*/ var _0, float duration = 0)
+{
+
+}
+
+void function PIN_PlayerAbilityReady(entity player, string action)
+{
+
+}
+
+void function PIN_DamageDone(entity player, entity victim, var damageInfo)
+{
+
+}
+
+void function PlayerInventory_RefreshEquippedState( entity player )
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/lobby/_lobby.gnut b/Northstar.CustomServers/scripts/vscripts/lobby/_lobby.gnut
new file mode 100644
index 000000000..fd877f8cf
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/lobby/_lobby.gnut
@@ -0,0 +1,37 @@
+untyped
+global function Lobby_Init
+global function Lobby_OnClientConnectionStarted
+global function Lobby_OnClientConnectionCompleted
+
+void function Lobby_Init()
+{
+ // need to prevent a crash
+ Music_Init()
+
+ if ( IsPrivateMatch() || GetCurrentPlaylistName() == "private_match" ) // IsPrivateMatch() doesn't seem to be reliable on local server start
+ PrivateLobby_Init()
+ else
+ {
+ // non-private lobby clientcommands
+ AddClientCommandCallback( "StartPrivateMatchSearch", ClientCommandCallback_StartPrivateMatchSearch )
+ }
+}
+
+void function Lobby_OnClientConnectionStarted( entity player )
+{
+
+}
+
+void function Lobby_OnClientConnectionCompleted( entity player )
+{
+ FinishClientScriptInitialization( player )
+}
+
+bool function ClientCommandCallback_StartPrivateMatchSearch( entity player, array<string> args )
+{
+ // open lobby in private match mode
+ SetCurrentPlaylist( "private_match" ) // required for private match lobby to start properly
+ ServerCommand( "changelevel mp_lobby" )
+
+ return true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/lobby/_private_lobby.gnut b/Northstar.CustomServers/scripts/vscripts/lobby/_private_lobby.gnut
new file mode 100644
index 000000000..c428d3095
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/lobby/_private_lobby.gnut
@@ -0,0 +1,156 @@
+// TODO: could probably add some checks for whether player setting stuff is player 0 to check for host, might fail in dedicated tho
+
+global function PrivateLobby_Init
+
+struct {
+ int startState
+ string map = "mp_forwardbase_kodai"
+ string mode = "aitdm"
+} file
+
+void function PrivateLobby_Init()
+{
+ print( "PrivateLobby_Init()" )
+ ClearPlaylistVarOverrides()
+
+ AddClientCommandCallback( "PrivateMatchLaunch", ClientCommandCallback_PrivateMatchLaunch )
+ AddClientCommandCallback( "PrivateMatchSetMode", ClientCommandCallback_PrivateMatchSetMode )
+ AddClientCommandCallback( "SetCustomMap", ClientCommandCallback_SetCustomMap )
+ AddClientCommandCallback( "PrivateMatchSwitchTeams", ClientCommandCallback_PrivateMatchSwitchTeams )
+}
+
+bool function ClientCommandCallback_PrivateMatchLaunch( entity player, array<string> args )
+{
+ if ( file.startState == ePrivateMatchStartState.STARTING )
+ {
+ // cancel start if we're already mid-countdown
+ file.startState = ePrivateMatchStartState.READY
+ SetUIVar( level, "privatematch_starting", ePrivateMatchStartState.READY )
+ SetUIVar( level, "gameStartTime", null )
+ }
+ else
+ {
+ // start match
+ file.startState = ePrivateMatchStartState.STARTING
+ thread StartMatch()
+ }
+
+ return true
+}
+
+bool function ClientCommandCallback_PrivateMatchSetMode( entity player, array<string> args )
+{
+ if ( file.startState == ePrivateMatchStartState.STARTING )
+ return true
+
+ if ( args.len() != 1 )
+ return true
+
+ // todo: need to verify this value
+ file.mode = args[0]
+ //GameRules_SetGameMode( args[0] ) // can't do this here due to out of sync errors with new clients
+
+ RefreshPlayerTeams()
+
+ SetUIVar( level, "privatematch_mode", GetPrivateMatchModeIndex( args[0] ) )
+ return true
+}
+
+bool function ClientCommandCallback_SetCustomMap( entity player, array<string> args )
+{
+ if ( file.startState == ePrivateMatchStartState.STARTING )
+ return true
+
+ if ( args.len() != 1 )
+ return true
+
+ // todo: need to verify this value
+ file.map = args[0]
+
+ // todo: this should NOT be necessary, private matches should use an api to register maps in the future rather than hardcoded ids
+ // should be removed whenever possible really
+ SetUIVar( level, "privatematch_map", GetPrivateMatchMapIndex( args[0] ) )
+ return true
+}
+
+bool function ClientCommandCallback_PrivateMatchSwitchTeams( entity player, array<string> args )
+{
+ if ( file.startState == ePrivateMatchStartState.STARTING )
+ return true
+
+ // currently only support 2 teams in private matches
+ SetTeam( player, player.GetTeam() == 2 ? 3 : 2 )
+ return true
+}
+
+void function StartMatch()
+{
+ // set starting uivar
+ SetUIVar( level, "privatematch_starting", ePrivateMatchStartState.STARTING )
+
+ // start countdown
+ SetUIVar( level, "gameStartTime", Time() + 15 )
+ float countdownEndTime = Time() + 15.0
+
+ // can't use start here because we need to check stuff
+ while ( Time() < countdownEndTime )
+ {
+ // stop if the countdown's been cancelled
+ if ( file.startState != ePrivateMatchStartState.STARTING)
+ return
+
+ WaitFrame()
+ }
+
+ GameRules_SetGameMode( file.mode )
+ try
+ {
+ // todo: not every gamemode uses the same playlist as their name! need some code to resolve these manually
+ // would be nice if the gamemode api got some tweaks to allow for registering private match gamemodes maybe
+ SetCurrentPlaylist( file.mode )
+ }
+ catch ( exception )
+ {
+ // temp
+ if ( file.mode == "speedball" )
+ SetCurrentPlaylist( "lf" )
+
+ print( "couldn't find playlist for gamemode " + file.mode )
+ }
+
+ RefreshPlayerTeams()
+
+ SetPlaylistVarOverride( "return_to_private_lobby", "1" )
+ // TEMP for now: start game
+ ServerCommand( "changelevel " + file.map )
+}
+
+void function RefreshPlayerTeams()
+{
+ int maxTeams = GetGamemodeVarOrUseValue( file.mode, "max_teams", "2" ).tointeger()
+ int maxPlayers = GetGamemodeVarOrUseValue( file.mode, "max_players", "12" ).tointeger()
+
+ // special case for situations where we wrongly assume ffa teams because there's 2 teams/2 players
+ if ( maxPlayers == maxTeams && maxTeams > 2 )
+ {
+ array<entity> players = GetPlayerArray()
+ for ( int i = 0; i < players.len(); i++ )
+ SetTeam( players[ i ], i + 7 ) // 7 is the lowest ffa team
+ }
+ else
+ {
+ bool lastSetMilitia = false
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( player.GetTeam() == TEAM_MILITIA || player.GetTeam() == TEAM_IMC )
+ continue
+
+ if ( lastSetMilitia ) // ensure roughly evenish distribution
+ SetTeam( player, TEAM_IMC )
+ else
+ SetTeam( player, TEAM_MILITIA )
+
+ lastSetMilitia = !lastSetMilitia
+ }
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/melee/_melee.gnut b/Northstar.CustomServers/scripts/vscripts/melee/_melee.gnut
new file mode 100644
index 000000000..035caf9ec
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/melee/_melee.gnut
@@ -0,0 +1,89 @@
+global function Melee_Init
+
+//global function CodeCallback_NPCMeleeChargedPlayerOrNPC
+global function CodeCallback_OnMeleeKilled
+global function EnablePlantingOnEntity
+
+void function Melee_Init()
+{
+ MeleeShared_Init()
+}
+
+//File is pretty sparse for now. In all honesty a lot of existing functionality in _melee_shared should
+//belong here instead, but we'll wait until we try to do prediction (which requires running the same code
+//on client and server) before we try to split up functionality in the different script files any better.
+
+/*
+void function CodeCallback_NPCMeleeChargedPlayerOrNPC( entity ent, var damageInfo )
+{
+ vector damageForce = DamageInfo_GetDamageForce( damageInfo )
+
+ if ( DamageInfo_GetDamage( damageInfo ) > 0 )
+ {
+ vector dmgVelocity = damageForce
+ dmgVelocity.z *= 0.25
+
+ const float maxAdditionalVelocity = 1200.0
+ if ( LengthSqr( dmgVelocity ) > ( maxAdditionalVelocity * maxAdditionalVelocity ) )
+ {
+ dmgVelocity = Normalize( dmgVelocity )
+ dmgVelocity *= maxAdditionalVelocity
+ }
+
+ ent.SetVelocity( ent.GetVelocity() + dmgVelocity )
+ }
+}
+*/
+
+void function CodeCallback_OnMeleeKilled( entity target )
+{
+ if ( !IsAlive( target ) )
+ return
+
+ target.ClearInvulnerable()
+
+ int damageSourceId
+ if ( target.IsTitan() )
+ {
+ // I don't think this branch ever gets hit. Titan executions do something else.
+ damageSourceId = eDamageSourceId.titan_execution
+ }
+ else
+ {
+ damageSourceId = eDamageSourceId.human_execution
+ }
+
+ entity attacker
+ if ( IsValid( target.e.syncedMeleeAttacker ) )
+ {
+ attacker = target.e.syncedMeleeAttacker
+ }
+ else if ( IsValid( target.e.lastSyncedMeleeAttacker ) )
+ {
+ attacker = target.e.lastSyncedMeleeAttacker
+ }
+ else
+ {
+ attacker = null
+ }
+
+
+ int damageAmount = target.GetMaxHealth() + 1
+ target.TakeDamage( damageAmount , attacker, attacker, { forceKill = true, damageType = DMG_MELEE_EXECUTION, damageSourceId = damageSourceId, scriptType = DF_NO_INDICATOR } )
+}
+
+
+void function EnablePlantingOnEntity( entity titan )
+{
+ entity parentEnt = titan.GetParent()
+
+ if ( parentEnt == null )
+ return
+
+ if ( titan.GetGroundEntity() && titan.GetGroundEntity().HasPusherRootParent() )
+ return
+
+ titan.ClearParent()
+ PutEntityInSafeSpot( titan, parentEnt, null, parentEnt.GetOrigin(), titan.GetOrigin() )
+ titan.Anim_EnablePlanting()
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/melee/_melee_rewards.gnut b/Northstar.CustomServers/scripts/vscripts/melee/_melee_rewards.gnut
new file mode 100644
index 000000000..46b730d60
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/melee/_melee_rewards.gnut
@@ -0,0 +1,74 @@
+untyped
+
+global function MeleeRewards_Init
+
+
+function MeleeRewards_Init()
+{
+ AddSyncedMeleeServerCallback( GetSyncedMeleeChooser( "human", "human" ), GiveMeleeRewards )
+}
+
+void function GiveMeleeRewards( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity enemy )
+{
+ thread GiveMeleeRewards_Internal( player, enemy )
+}
+
+enum eMeleeReward
+{
+ NONE
+ AMMO
+ MAPHACK
+}
+
+function GiveMeleeRewards_Internal( entity player, entity enemy )
+{
+ player.EndSignal( "OnDeath" )
+
+ local reward = eMeleeReward.NONE
+
+ if ( enemy.IsPlayer() )
+ reward = eMeleeReward.MAPHACK
+ else if ( enemy.IsNPC() )
+ reward = eMeleeReward.AMMO
+
+ player.WaitSignal( "SyncedMeleeComplete" )
+
+ switch ( reward )
+ {
+ case eMeleeReward.MAPHACK:
+ ExecutionGivesMapHack( player )
+ break
+ case eMeleeReward.AMMO:
+ ExecutionGivesAmmo( player )
+ break
+ default:
+ break
+ }
+}
+
+function ExecutionGivesMapHack( entity player )
+{
+ printt( "melee gave map hack!" )
+ thread ScanMinimap( player, true )
+}
+
+function ExecutionGivesAmmo( entity player )
+{
+ printt( "melee gave ammo!" )
+ local grenadeWeapon = player.GetOffhandWeapon( 0 )
+
+ if ( !IsValid( grenadeWeapon ) )
+ return
+
+ local maxAmmoClip = player.GetWeaponAmmoMaxLoaded( grenadeWeapon )
+ local remainingAmmo = player.GetWeaponAmmoLoaded( grenadeWeapon )
+
+ if ( remainingAmmo == maxAmmoClip )
+ return
+
+ local ammo = remainingAmmo + 1
+
+ grenadeWeapon.SetWeaponPrimaryClipCount( ammo )
+
+ EmitSoundOnEntity( player, "Coop_AmmoBox_AmmoRefill" )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/melee/_melee_synced_human.gnut b/Northstar.CustomServers/scripts/vscripts/melee/_melee_synced_human.gnut
new file mode 100644
index 000000000..15a8aa3e3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/melee/_melee_synced_human.gnut
@@ -0,0 +1,588 @@
+untyped
+
+global function MeleeThread_PilotVsEnemy
+global function MeleeSyncedServer_Init
+
+void function MeleeSyncedServer_Init()
+{
+ RegisterSignal( "NpcDealsExecutionDamage" )
+}
+
+bool function MeleeThread_PilotVsEnemy( SyncedMelee action, entity attacker, entity target )
+{
+ // function off for reload scripts
+ return MeleeThread_PilotVsEnemyInternal( action, attacker, target )
+}
+
+bool function MeleeThread_PilotVsEnemyInternal( SyncedMelee action, entity attacker, entity target )
+{
+ Assert( IsHumanSized( target ), target + " is not human sized melee target" )
+ Assert( attacker.IsPlayer() && IsHumanSized( attacker ), attacker + " is not human sized player attacker" )
+ Assert( IsAlive( attacker ) )
+ Assert( IsAlive( target ) )
+
+ bool isAttackerRef = IsAttackerRef( action, target )
+
+ vector attackerOrigin = attacker.GetOrigin()
+ vector targetOrigin = target.GetOrigin()
+
+ attacker.EndSignal( "OnDestroy" )
+ target.EndSignal( "OnDestroy" )
+
+ if ( IsSingleplayer() )
+ {
+ if ( attacker.IsPlayer() )
+ {
+ if ( IsCloaked( attacker ) )
+ {
+ UnlockAchievement( attacker, achievements.CLOAK_TAKEDOWN )
+ }
+ }
+ }
+
+ OnThreadEnd(
+ function() : ( attacker, target, attackerOrigin, targetOrigin, action, isAttackerRef )
+ {
+ if ( IsValid( attacker ) )
+ attacker.ClearParent()
+
+ if ( IsValid( target ) )
+ target.ClearParent()
+
+
+ if ( IsValid( attacker ) )
+ {
+ attacker.PlayerMelee_SetState( PLAYER_MELEE_STATE_NONE )
+ }
+
+ // Note that the original attacker/target origins are not guarranteed to be a safe spot now because we have moving geo in the game.
+ // Whoever is the 'ref' will be in a safe position though, so we can always use the origin of the person who has been designated as the 'ref'.
+ if ( IsAlive( attacker ) )
+ {
+ if ( !isAttackerRef && IsValid( target ) )
+ {
+ PutEntityInSafeSpot( attacker, target, null, target.GetOrigin(), attacker.GetOrigin() )
+ }
+ else
+ {
+ PutEntityInSafeSpot( attacker, target, null, attacker.GetOrigin(), attacker.GetOrigin() )
+ }
+
+ }
+
+ if ( IsValid( target ) )
+ {
+ target.ClearParent()
+
+ if ( IsAlive( target ) )
+ {
+ // Note that the original target origin is not guarranteed to be a safe spot now because we have moving geo in the game now.
+ if ( isAttackerRef && IsValid( attacker ) )
+ {
+
+ PutEntityInSafeSpot( target, attacker, null, attacker.GetOrigin(), target.GetOrigin() )
+ }
+ else
+ {
+ PutEntityInSafeSpot( target, attacker, null, target.GetOrigin(), target.GetOrigin() )
+ }
+ }
+ }
+ }
+ )
+
+ thread MeleeThread_PilotVsEnemy_Attacker( action, attacker, target, isAttackerRef )
+ // target's sequence is longer
+ waitthread MeleeThread_PilotVsEnemy_Target( action, attacker, target, isAttackerRef )
+
+ attacker.Signal( "SyncedMeleeComplete" )
+ return true
+}
+
+struct PilotVsEnemyStruct
+{
+ bool clearInvulnerable = false
+ bool wasCloaked = false
+ float cloakEndTime = 0.0
+}
+
+void function DisableCloakBeforeMelee( entity player, PilotVsEnemyStruct dataStruct )
+{
+ if ( IsCloaked( player ) )
+ {
+ dataStruct.wasCloaked = true
+ dataStruct.cloakEndTime = player.GetCloakEndTime()
+ DisableCloak( player, 0.0 )
+ }
+}
+
+void function RestoreCloakAfterMelee( entity player, PilotVsEnemyStruct dataStruct )
+{
+ if ( !IsAlive( player ) )
+ return
+
+ if ( !dataStruct.wasCloaked )
+ return
+
+ float remainingCloakDuration = max( 0.0, dataStruct.cloakEndTime - Time() )
+ if ( remainingCloakDuration > CLOAK_FADE_IN ) //Has to be higher than fade in duration, otherwise will cloak forever
+ EnableCloak( player, remainingCloakDuration, CLOAK_FADE_IN )
+}
+
+void function HandleCloakExecutionWithCloakedAttacker( entity player, PilotVsEnemyStruct dataStruct, SyncedMelee action )
+{
+ if ( !IsCloaked( player ) )
+ return //No need to run DisableCloakBeforeMelee() either
+
+ float attackerSequenceEndTime = Time() + player.GetSequenceDuration( action.attackerAnimation3p )
+ float scheduledCloakEndTime = player.GetCloakEndTime()
+
+ //printt( "attackerSequenceEndTime: " + attackerSequenceEndTime + ", scheduledCloakEndTime: " + scheduledCloakEndTime )
+
+ if ( scheduledCloakEndTime > attackerSequenceEndTime )
+ {
+ //printt( "Cloak ability lasts longer than execution sequence, just doing DisableCloakBeforeMelee" )
+ player.SetCloakFlicker( 0.0, 0.0 ) //Turn off flicker; this is normally not a problem for other executions since cloak is turned off for the entirety of those executions
+ DisableCloakBeforeMelee( player, dataStruct )
+ }
+ else
+ {
+ //Cloak would normally run out during the animation of this execution, which is disruptive to the presentation of cloak animation, so just stop cloak now for good and prevent it from coming back.
+ //printt( "Cloak ability is shorter than execution sequence, DisableCloak now and stop it from coming back" )
+ dataStruct.wasCloaked = true //Have to do this to mark player was cloaked during start of execution, so we can track the stat correctly
+ dataStruct.cloakEndTime = Time()
+ DisableCloak( player, 0.0 )
+ player.Signal( "KillHandleCloakEnd" )
+ }
+}
+
+
+void function MeleeThread_PilotVsEnemy_Attacker( SyncedMelee action, entity attacker, entity target, bool isAttackerRef )
+{
+ attacker.EndSignal( "OnAnimationDone" )
+ attacker.EndSignal( "OnAnimationInterrupted" )
+ attacker.EndSignal( "OnDeath" )
+ attacker.EndSignal( "ScriptAnimStop" )
+
+ attacker.EndSignal( "OnDestroy" )
+ Assert( IsValid( target ) )
+ target.EndSignal( "OnDestroy" )
+
+
+ foreach ( AnimEventData animEventData in action.attacker3pAnimEvents )
+ {
+ AddAnimEvent( attacker, animEventData.eventName, animEventData.callback, animEventData.optionalVar )
+ }
+ AddAnimEvent( attacker, "synced_melee_enable_planting", EnablePlantingOnEntity )
+
+ PilotVsEnemyStruct dataStruct
+ OnThreadEnd(
+ function() : ( attacker, target, action, dataStruct )
+ {
+ if ( IsValid( attacker ) )
+ {
+ DeleteAnimEvent( attacker, "synced_melee_enable_planting" )
+
+ if ( dataStruct.clearInvulnerable )
+ {
+ attacker.ClearInvulnerable()
+ }
+
+ if ( attacker.IsPlayer() )
+ {
+ attacker.PlayerMelee_ExecutionEndAttacker()
+ ClearPlayerAnimViewEntity( attacker )
+ DeployAndEnableWeapons( attacker )
+
+ RestoreCloakAfterMelee( attacker, dataStruct )
+ #if MP
+ IncrementStatForPilotExecutionWhileCloaked( attacker, target, dataStruct )
+ #endif
+ }
+
+ foreach ( AnimEventData animEventData in action.attacker3pAnimEvents )
+ {
+ DeleteAnimEvent( attacker, animEventData.eventName )
+ }
+ }
+
+ if ( !IsAlive( attacker ) )
+ attacker.Anim_Stop()
+ }
+ )
+
+ FirstPersonSequenceStruct attackerSequence
+ attackerSequence.blendTime = 0.4
+ attackerSequence.attachment = "ref"
+ attackerSequence.thirdPersonAnim = action.attackerAnimation3p
+ attackerSequence.firstPersonAnim = action.attackerAnimation1p
+ attackerSequence.thirdPersonCameraAttachments = [action.thirdPersonCameraAttachment]
+ attackerSequence.thirdPersonCameraVisibilityChecks = true
+
+ if ( isAttackerRef )
+ {
+ attackerSequence.noParent = true
+ attackerSequence.playerPushable = true
+ attackerSequence.enablePlanting = true
+ }
+ else
+ {
+ attackerSequence.useAnimatedRefAttachment = true
+ }
+
+ float duration = attacker.GetSequenceDuration( attackerSequence.thirdPersonAnim )
+
+ if ( attacker.IsPlayer() )
+ {
+ float executionEndTime = Time() + duration
+ attacker.PlayerMelee_ExecutionStartAttacker( executionEndTime )
+ attacker.Lunge_ClearTarget()
+ HolsterViewModelAndDisableWeapons( attacker )
+
+ if ( action.ref == "execution_cloak" ) //Special case for cloak execution
+ {
+ HandleCloakExecutionWithCloakedAttacker( attacker, dataStruct, action )
+ }
+ else
+ {
+ DisableCloakBeforeMelee( attacker, dataStruct )
+ }
+
+ if ( IsSingleplayer() )
+ {
+ dataStruct.clearInvulnerable = true
+ attacker.SetInvulnerable()
+ thread LowerEnemyAccuracy( attacker, duration )
+ }
+ }
+
+ if ( isAttackerRef )
+ thread FirstPersonSequence( attackerSequence, attacker )
+ else
+ thread FirstPersonSequence( attackerSequence, attacker, target )
+
+ wait duration
+}
+
+
+void function MeleeThread_PilotVsEnemy_Target( SyncedMelee action, entity attacker, entity target, bool isAttackerRef )
+{
+ attacker.EndSignal( "OnAnimationDone" )
+ attacker.EndSignal( "OnAnimationInterrupted" )
+ attacker.EndSignal( "OnDeath" )
+ attacker.EndSignal( "ScriptAnimStop" )
+
+ attacker.EndSignal( "OnDestroy" )
+ Assert( IsValid( target ) )
+ target.EndSignal( "OnDestroy" )
+
+ foreach ( AnimEventData animEventData in action.target3pAnimEvents )
+ {
+ AddAnimEvent( target, animEventData.eventName, animEventData.callback, animEventData.optionalVar )
+ }
+ AddAnimEvent( target, "synced_melee_enable_planting", EnablePlantingOnEntity )
+
+ PilotVsEnemyStruct dataStruct
+
+ OnThreadEnd(
+ function() : ( attacker, target, action, dataStruct )
+ {
+ if ( IsValid( target ) )
+ {
+ if ( target.IsNPC() && IsMultiplayer() )
+ {
+ SetForceDrawWhileParented( target, false )
+ }
+
+ TargetClearedExecuted( target )
+ DeleteAnimEvent( target, "mark_for_death" )
+ DeleteAnimEvent( target, "phase_gib" )
+
+ foreach ( AnimEventData animEventData in action.target3pAnimEvents )
+ {
+ DeleteAnimEvent( target, animEventData.eventName )
+ }
+ DeleteAnimEvent( target, "synced_melee_enable_planting" )
+
+ bool isAlive = IsAlive( target )
+
+ if ( target.IsPlayer() )
+ {
+ EnableOffhandWeapons( target )
+ if ( isAlive )
+ target.DeployWeapon()
+ }
+
+ if ( isAlive )
+ {
+ if ( target.e.markedForExecutionDeath ) //Kill off target if he already reached blackout part of melee
+ {
+ entity killCreditAttacker = null //If the attacker disconnected, we don't have a player to give credit to, that's fine. Script will not error
+ if ( IsValid( target.e.syncedMeleeAttacker ) )
+ killCreditAttacker = target.e.syncedMeleeAttacker
+ //printt( "Killing off target " + target + " because he already reached blackout part of execution!" )
+
+ int damageAmount = target.GetMaxHealth() + 1
+ target.TakeDamage( damageAmount, killCreditAttacker, killCreditAttacker, { forceKill = true, damageType = DMG_MELEE_EXECUTION, damageSourceId = eDamageSourceId.human_execution } )
+ //markedForExecutionDeath will be cleared in MarkForDeath() which sets it in the first place
+ }
+
+ if ( target.IsPlayer() )
+ RestoreCloakAfterMelee( target, dataStruct )
+ }
+
+ if ( IsValid( target.e.syncedMeleeAttacker ) )
+ {
+ if ( IsValid( target.e.lastSyncedMeleeAttacker ) )
+ {
+ target.e.lastSyncedMeleeAttacker = null
+ }
+
+ target.e.lastSyncedMeleeAttacker = target.e.syncedMeleeAttacker
+ target.e.syncedMeleeAttacker = null
+ }
+ }
+ }
+ )
+
+ TargetSetExecutedBy( target, attacker )
+
+ AddAnimEvent( target, "mark_for_death", MarkForDeath )
+ AddAnimEvent( target, "phase_gib", PhaseGib )
+
+ FirstPersonSequenceStruct targetSequence
+ targetSequence.blendTime = 0.25
+ targetSequence.attachment = "ref"
+ targetSequence.thirdPersonAnim = action.targetAnimation3p
+ targetSequence.thirdPersonCameraAttachments = [action.thirdPersonCameraAttachment]
+ targetSequence.thirdPersonCameraVisibilityChecks = true
+
+ if ( isAttackerRef )
+ {
+ targetSequence.useAnimatedRefAttachment = true
+ if ( target.IsNPC() && IsMultiplayer() )
+ {
+ SetForceDrawWhileParented( target, true )
+ }
+ }
+ else
+ {
+ targetSequence.noParent = true
+ targetSequence.playerPushable = true
+ targetSequence.enablePlanting = true
+ }
+
+
+ if ( target.IsPlayer() )
+ {
+ HolsterViewModelAndDisableWeapons( target )
+ targetSequence.firstPersonAnim = action.targetAnimation1p
+ DisableCloakBeforeMelee( target, dataStruct )
+ }
+
+ if ( attacker.IsPlayer() )
+ {
+ if ( MeleeTargetrequiresDataKnife( target ) )
+ {
+ string tag = GetTagForKnifeMeleeTarget( target )
+ thread AttachPlayerModelForDuration( attacker, DATA_KNIFE_MODEL, tag, 2.2 )
+ }
+ else if ( action.attachTag1p != "" && action.attachModel1p != $"" )
+ {
+ thread AttachPlayerModelForDuration( attacker, action.attachModel1p, action.attachTag1p, 2.2 )
+ }
+ }
+
+ if ( isAttackerRef )
+ waitthread FirstPersonSequence( targetSequence, target, attacker )
+ else
+ waitthread FirstPersonSequence( targetSequence, target )
+}
+
+#if MP
+void function IncrementStatForPilotExecutionWhileCloaked( entity attacker, entity target, PilotVsEnemyStruct dataStruct )
+{
+ if ( !IsAlive( attacker ) )
+ return
+
+ if ( IsAlive( target ) )
+ return
+
+ if ( !target.IsPlayer() )
+ return
+
+ if ( !dataStruct.wasCloaked )
+ return
+
+ IncrementPlayerDidPilotExecutionWhileCloaked( attacker ) //Kinda clumsy we have to do it here instead of where all the other kill stats are incremented. Mainly because we turn cloak off at the start of execution so you can't do it where all the other kill stats are incremented
+}
+#endif
+
+void function TargetClearedExecuted( entity target )
+{
+ target.ClearParent()
+ target.Solid()
+ if ( target.ContextAction_IsMeleeExecution() )
+ target.PlayerMelee_ExecutionEndTarget()
+ if ( target.IsPlayer() )
+ ClearPlayerAnimViewEntity( target )
+}
+
+void function TargetSetExecutedBy( entity target, entity attacker )
+{
+ //Break out of context actions like hacking control panel etc
+ if ( target.ContextAction_IsActive() )
+ target.Anim_Stop()
+
+ target.PlayerMelee_ExecutionStartTarget( attacker )
+ target.e.syncedMeleeAttacker = attacker
+ target.NotSolid()
+}
+
+bool function MeleeTargetrequiresDataKnife( entity target )
+{
+ if ( IsProwler( target ) )
+ return true
+
+ if ( IsPilotElite( target ) )
+ return true
+
+ return false
+}
+
+string function GetTagForKnifeMeleeTarget( entity target )
+{
+ Assert( MeleeTargetrequiresDataKnife( target ) )
+
+ if ( IsProwler( target ) )
+ return "PROPGUN"
+
+ if ( IsPilotElite( target ) )
+ return "KNIFE"
+
+ unreachable
+}
+
+function AttachPlayerModelForDuration( var player, asset modelName, var tag, var time )
+{
+ expect entity( player )
+
+ if ( !IsValid( player ) )
+ return
+
+ Assert( IsValid( tag ), "No tag specified for player" )
+
+ entity viewModel = player.GetFirstPersonProxy() //JFS: Defensive fix for player not having view models sometimes
+ if ( !IsValid( viewModel ) )
+ return
+
+ if ( !EntHasModelSet( viewModel ) )
+ return
+
+ entity model = CreatePropDynamic( modelName )
+ model.SetParent( viewModel, tag, false, 0.0 )
+
+ OnThreadEnd(
+ function() : ( model )
+ {
+ if ( IsValid( model ) )
+ model.Destroy()
+ }
+ )
+
+ player.EndSignal( "OnDeath" )
+
+ wait time
+}
+
+void function MarkForDeath( entity target )
+{
+ if ( target.IsNPC() )
+ {
+ //printt("Killing marked for death npc " + target )
+ //Just kill off NPC now, otherwise it will play pain animations on death
+ CodeCallback_OnMeleeKilled( target )
+ return
+ }
+
+ //printt("marking player " + target + " for death")
+ target.e.markedForExecutionDeath = true //This will kill off the player even if the execution animation is interruped from this point forward
+
+ target.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( target )
+ {
+ target.e.markedForExecutionDeath = false
+ }
+ )
+
+ WaitForever()
+
+}
+
+void function PhaseGib( entity target )
+{
+ if ( !IsAlive( target ) )
+ return
+
+ target.ClearInvulnerable()
+
+ entity attacker
+ if ( IsValid( target.e.syncedMeleeAttacker ) )
+ {
+ attacker = target.e.syncedMeleeAttacker
+ }
+ else if ( IsValid( target.e.lastSyncedMeleeAttacker ) )
+ {
+ attacker = target.e.lastSyncedMeleeAttacker
+ }
+ else
+ {
+ attacker = null
+ }
+
+ int damageAmount = target.GetMaxHealth() + 1
+ target.TakeDamage( damageAmount , attacker, attacker, { forceKill = true, damageType = DMG_MELEE_EXECUTION, damageSourceId = eDamageSourceId.human_execution, scriptType = DF_NO_INDICATOR | DF_GIB } )
+}
+
+
+entity function CreateSyncedMeleeRef( entity attacker, entity target, SyncedMelee action )
+{
+ entity ref = CreateMeleeScriptMoverBetweenEnts( attacker, target )
+
+ vector angles = target.GetAngles()
+ angles.x = ref.GetAngles().x
+
+ ref.SetAngles( angles )
+ if ( action.animRefPos == "attacker" )
+ ref.SetOrigin( attacker.GetOrigin() )
+ else
+ ref.SetOrigin( target.GetOrigin() )
+ return ref
+}
+
+void function ApplyGruntExecutionDamage( entity ref, entity attacker, entity target, float damageDealt )
+{
+ ref.EndSignal( "OnDestroy" )
+ attacker.EndSignal( "OnDeath" )
+ target.EndSignal( "OnDeath" )
+
+ for ( ;; )
+ {
+ table results = attacker.WaitSignal( "NpcDealsExecutionDamage" )
+ float damage
+ switch ( results.parm )
+ {
+ case "lethal":
+ damage = float( target.GetMaxHealth() )
+ break
+
+ case "nonlethal":
+ damage = min( target.GetHealth() - 10, target.GetMaxHealth() * damageDealt )
+ break
+ }
+
+ target.TakeDamage( damage, attacker, attacker, { damageSourceId=eDamageSourceId.human_execution, scriptType = DF_RAGDOLL } )
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/melee/_melee_synced_titan.gnut b/Northstar.CustomServers/scripts/vscripts/melee/_melee_synced_titan.gnut
new file mode 100644
index 000000000..5c6285a9d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/melee/_melee_synced_titan.gnut
@@ -0,0 +1,1543 @@
+untyped
+
+global function MeleeSyncedTitan_Init
+
+const TITANARMMODEL = $"models/weapons/arms/atlaspov.mdl"
+const TEAM_JUMPJET_DBL = $"P_team_jump_jet_DBL"
+
+enum eTitanExecutionType
+{
+ fistThroughCockpit
+ dummy //not used yet
+}
+
+struct TitanExcutionData
+{
+ string attackerAnimation3p
+ string attackerAnimation3p_vsAutoTitan
+ table<string,string> attackerAnimation3pPilot
+ table<string,string> targetAnimation3p
+ table<string,string> targetAnimation3pPilot
+ string sound_1p
+ string sound_3p
+ array<string> thirdPersonCameraAttachments
+ array<string> linkedExecutions
+}
+
+struct
+{
+ table<string, TitanExcutionData> executionData_3p
+} file
+
+int RAGDOLL_IMPACT_TABLE_IDX = -1
+
+function MeleeSyncedTitan_Init()
+{
+ RAGDOLL_IMPACT_TABLE_IDX = PrecacheImpactEffectTable( "ragdoll_human" )
+ AddSyncedMeleeServerThink( GetSyncedMeleeChooser( "titan", "titan" ), MeleeThread_TitanVsTitan )
+
+ if ( GetBugReproNum() == 129802 )
+ {
+ AddDeathCallback( "npc_titan", OnNPCTitanDeath )
+ }
+
+ PrecacheWeapon( "mp_titanweapon_salvo_rockets" )
+ PrecacheParticleSystem( TEAM_JUMPJET_DBL )
+
+ Init3pExecutions()
+}
+
+void function Init3pExecutions()
+{
+ var dataTable = GetDataTable( $"datatable/titan_executions.rpak" )
+ int numRows = GetDatatableRowCount( dataTable )
+ for ( int row=0; row<numRows; row++ )
+ {
+ TitanExcutionData data = Create_3p_ExecutionData( dataTable, row )
+ string ref = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "ref" ) )
+ file.executionData_3p[ref] <- data
+ }
+}
+
+TitanExcutionData function Create_3p_ExecutionData( var dataTable, int row )
+{
+ string attackerAnimation3p = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "attackerAnim" ) )
+ string attackerAnimation3p_vsAutoTitan = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "attackerAnimVsAutoTitan" ) )
+ string targetAnimation3p_lt = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "victimAnim_lt" ) )
+ string targetAnimation3p_md = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "victimAnim_md" ) )
+ string targetAnimation3p_hv = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "victimAnim_hv" ) )
+ string targetAnimation3pPilot_lt = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "victimAnim_pt_lt" ) )
+ string targetAnimation3pPilot_md = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "victimAnim_pt_md" ) )
+ string targetAnimation3pPilot_hv = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "victimAnim_pt_hv" ) )
+
+ string attackerAnimation3pPilot_lt = ""
+ if ( GetDataTableColumnByName( dataTable, "attackerAnim_pt_lt" ) != -1 )
+ attackerAnimation3pPilot_lt = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "attackerAnim_pt_lt" ) )
+
+ string attackerAnimation3pPilot_md = ""
+ if ( GetDataTableColumnByName( dataTable, "attackerAnim_pt_mt" ) != -1 )
+ attackerAnimation3pPilot_md = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "attackerAnim_pt_mt" ) )
+
+ string attackerAnimation3pPilot_hv = ""
+ if ( GetDataTableColumnByName( dataTable, "attackerAnim_pt_ht" ) != -1 )
+ attackerAnimation3pPilot_hv = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "attackerAnim_pt_ht" ) )
+
+ string sound_1p = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "sound_1p" ) )
+ string sound_3p = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "sound_3p" ) )
+ string camAttach = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "camAttach" ) )
+
+ array<string> camAttachments = split( camAttach, " " )
+
+ array<string> linkedExecutionArray = SplitAndStripStringArray( GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "linkedExecutions" ) ) )
+
+ TitanExcutionData data
+ data.attackerAnimation3p = attackerAnimation3p
+ data.attackerAnimation3p_vsAutoTitan = attackerAnimation3p_vsAutoTitan
+ data.targetAnimation3p[ "stryder" ] <- targetAnimation3p_lt
+ data.targetAnimation3p[ "atlas" ] <- targetAnimation3p_md
+ data.targetAnimation3p[ "ogre" ] <- targetAnimation3p_hv
+ data.targetAnimation3pPilot[ "stryder" ] <- targetAnimation3pPilot_lt
+ data.targetAnimation3pPilot[ "atlas" ] <- targetAnimation3pPilot_md
+ data.targetAnimation3pPilot[ "ogre" ] <- targetAnimation3pPilot_hv
+ data.attackerAnimation3pPilot[ "stryder" ] <- attackerAnimation3pPilot_lt
+ data.attackerAnimation3pPilot[ "atlas" ] <- attackerAnimation3pPilot_md
+ data.attackerAnimation3pPilot[ "ogre" ] <- attackerAnimation3pPilot_hv
+ data.sound_1p = sound_1p
+ data.sound_3p = sound_3p
+ data.thirdPersonCameraAttachments = camAttachments
+ data.linkedExecutions = linkedExecutionArray
+ return data
+}
+
+array<string> function SplitAndStripStringArray( string combinedString )
+{
+ array<string> stringArray = split( combinedString, "," )
+
+ foreach ( i, value in stringArray )
+ {
+ stringArray[ i ] = strip( value )
+ }
+
+ return stringArray
+}
+
+
+struct MeleeThread_TitanVsTitanDataStruct
+{
+ bool setAttackerInvulnerable = false
+ bool setAttackerDemigod = false
+}
+
+bool function MeleeThread_TitanVsTitan( SyncedMelee action, entity attacker, entity target )
+{
+ // function off for reload scripts
+ return MeleeThread_TitanVsTitan_Internal( action, attacker, target )
+}
+
+bool function MeleeThread_TitanVsTitan_Internal( SyncedMelee action, entity attacker, entity target )
+{
+ Assert( target.IsTitan(), target + " is not Titan target" )
+ Assert( attacker.IsPlayer() && attacker.IsTitan(), attacker + " is not Titan attacker" )
+
+ #if SERVER
+ printt( "Player", attacker, "attempting to melee", target, "TitanVsTitanMelee" )
+ #endif
+
+ if ( attacker.ContextAction_IsActive() || target.ContextAction_IsActive() )
+ {
+ printt("Either attacker or target already in ContextAction! Exiting Titan Vs Titan melee attempt")
+ return false
+ }
+
+ if ( !IsAlive( attacker ) )
+ return false
+
+ if ( !IsAlive( target ) )
+ return false
+
+ void functionref( SyncedMelee action, entity attacker, entity target ) func
+ func = GetTitanSyncedMeleeFunc( attacker, target )
+ if ( func == null )
+ return false
+
+ attacker.GetTitanSoul().Signal( "OnSyncedMelee" ) //Need the signal on the soul to clean-up tether traps during synced executions.
+
+ // JFS: signals can kill things mid frame: R2DLC-311 SCRIPT ERROR: PHONE_HOME: [SERVER] Entity is null
+ if ( !IsAlive( attacker ) )
+ return false
+
+ if ( !IsAlive( target ) )
+ return false
+
+ target.GetTitanSoul().Signal( "OnSyncedMelee" )
+
+ // JFS: signals can kill things mid frame: R2DLC-311 SCRIPT ERROR: PHONE_HOME: [SERVER] Entity is null
+ if ( !IsAlive( attacker ) )
+ return false
+
+ if ( !IsAlive( target ) )
+ return false
+ //attacker.Signal( "OnSyncedMelee" )
+ //target.Signal( "OnSyncedMelee" )
+
+ MeleeThread_TitanVsTitanDataStruct dataStruct
+
+ OnThreadEnd(
+ function() : ( attacker, target, dataStruct )
+ {
+ if ( IsValid( attacker ) )
+ {
+ if ( dataStruct.setAttackerInvulnerable )
+ attacker.ClearInvulnerable()
+
+ if ( dataStruct.setAttackerDemigod )
+ DisableDemigod( attacker )
+
+ attacker.PlayerMelee_SetState( PLAYER_MELEE_STATE_NONE )
+ }
+ }
+ )
+
+ string titanSubClass = GetSoulTitanSubClass( attacker.GetTitanSoul() )
+
+ entity burnCardTarget
+ entity bossPlayer = target.GetBossPlayer()
+ if ( target.IsNPC() )
+ {
+ if ( IsValid( bossPlayer ) )
+ burnCardTarget = bossPlayer
+ }
+ else
+ {
+ burnCardTarget = target
+ }
+
+ attacker.PlayerMelee_ExecutionStartAttacker( 0 )
+ target.PlayerMelee_ExecutionStartTarget( attacker )
+
+ attacker.Lunge_ClearTarget()
+
+ ForceTitanSustainedDischargeEnd( target )
+
+ #if TITAN_EXECUTION_ATTACKER_IS_INVULNERABLE
+ dataStruct.setAttackerInvulnerable = true
+ attacker.SetInvulnerable()
+ #else
+ dataStruct.setAttackerDemigod = true
+ EnableDemigod( attacker )
+ #endif
+
+ waitthread func( action, attacker, target )
+
+ if ( !IsValid( attacker ) )
+ return true
+
+ attacker.Signal( "SyncedMeleeComplete" )
+ #if MP
+ if ( attacker.IsPlayer() )
+ AddPlayerScore( attacker, "Execution" )
+ #endif
+ return true
+}
+
+void functionref( SyncedMelee action, entity attacker, entity target ) function GetTitanSyncedMeleeFunc( entity attacker, entity target )
+{
+ if ( GetCurrentPlaylistVarInt( "titan_executions_always_short", 0 ) != 0 )
+ return MeleeThread_AtlasVsTitanShort
+
+ entity soul = attacker.GetTitanSoul()
+ #if SP
+ TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap()
+ #else
+ TitanLoadoutDef loadout = soul.soul.titanLoadout // GetActiveTitanLoadout( attacker )
+ #endif
+ string executionRef = loadout.titanExecution
+ if ( SoulHasPassive( soul, ePassives.PAS_VANGUARD_COREMETER ) )
+ executionRef = "execution_vanguard_kit"
+
+ if ( executionRef in file.executionData_3p )
+ return TitanVsTitan_3p
+
+ if ( target.IsNPC() )
+ {
+ entity bossPlayer = target.GetBossPlayer()
+ if ( IsValid( bossPlayer ) || !IsVDUTitan( target ) )
+ return MeleeThread_AtlasVsTitanShort
+ }
+
+ string attackerType = GetSoulTitanSubClass( soul )
+
+ switch ( attackerType )
+ {
+ case "stryder":
+ return MeleeThread_StyderVsTitan
+
+ case "ogre":
+ return MeleeThread_OgreVsTitan
+
+ case "atlas":
+ case "buddy":
+ return MeleeThread_AtlasVsTitan
+ }
+
+ return null
+}
+
+void function MeleeThread_AtlasVsTitanShort( SyncedMelee action, entity attacker, entity target )
+{
+ if ( !IsAlive( attacker ) )
+ return
+
+ if ( !IsAlive( target ) )
+ return
+
+ string attackerAnimation1p = "atpov_melee_sync_frontkill_autotitan"
+ string attackerAnimation3p = "at_melee_sync_frontkill_autotitan"
+ string targetAnimation3p = "at_melee_sync_frontdeath_autotitan"
+
+ target.Signal( "TitanStopsThinking" ) // in future, need to make titan scripted anims co-exist better and not require gotcha stuff like this -Mackey
+
+ local e = {}
+ e.attackerViewBody <- null
+
+ e.attackerStartOrg <- attacker.GetOrigin()
+
+ entity ref = CreateMeleeScriptMoverBetweenEnts( attacker, target )
+
+ FirstPersonSequenceStruct attackerSequence
+ attackerSequence.blendTime = 0.25
+ attackerSequence.attachment = "ref"
+
+ FirstPersonSequenceStruct targetSequence = clone attackerSequence
+
+ attackerSequence.thirdPersonAnim = attackerAnimation3p
+ // attackerSequence.thirdPersonAnimIdle = "at_melee_sync_frontkill_end_idle"
+
+ attackerSequence.firstPersonAnim = attackerAnimation1p
+ targetSequence.thirdPersonAnim = targetAnimation3p
+ targetSequence.blendTime = 0.25
+
+ target.e.syncedMeleeAttacker = attacker
+
+ // attacker.SetInvulnerable()
+ target.SetInvulnerable() //HACK: Have to SetInvulnerable first before attacker holsters weapon, because if the attacker is vortexing, holster will release bullets caught and kill off the victim if low enough health
+
+ //HACK! This function was originally for NPCs only, but now that it is being used for players, we need to holster their weapon
+ if ( target.IsPlayer() )
+ HolsterAndDisableWeapons( target )
+
+ if ( ShouldHolsterWeaponForSyncedMelee( attacker ) )
+ HolsterAndDisableWeapons( attacker )
+
+ local attackerViewBody
+
+ // needs shortened verions
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( "Titan_1p_Sync_Melee_vs_AutoTitan", "Titan_3p_Sync_Melee_vs_AutoTitan", attacker, attacker )
+
+ local soul = target.GetTitanSoul()
+ soul.SetInvalidHealthBarEnt( true )
+
+ AddAnimEvent( target, "rider_rodeo_over", ForceTitanRodeoToEnd )
+
+ target.SetInvulnerable() //Setting target of execution as invulnerable to prevent them dying mid-way
+
+ OnThreadEnd(
+ function() : ( ref, attacker, target, e )
+ {
+ if ( IsValid( ref ) )
+ {
+ if ( IsValid( attacker ) )
+ attacker.ClearParent()
+
+ if ( IsValid( target ) )
+ target.ClearParent()
+
+ AssertNoPlayerChildren( ref )
+ ref.Destroy()
+ }
+
+ if ( IsValid( attacker ) )
+ {
+ //attacker.ClearInvulnerable()
+ attacker.UnforceStand()
+ attacker.ClearParent()
+ ClearPlayerAnimViewEntity( attacker )
+ DeployAndEnableWeapons( attacker )
+ attacker.PlayerMelee_ExecutionEndAttacker()
+
+ if ( IsAlive( attacker ) )
+ {
+ // if we got into solid, teleport back to safe place
+ if ( !PutEntityInSafeSpot( attacker, null, null, expect vector( e.attackerStartOrg ), attacker.GetOrigin() ) )
+ {
+ printt( "PutEntityInSafeSpot failed, putting him back at the start origin" )
+ attacker.SetOrigin( expect vector( e.attackerStartOrg ) )
+ }
+
+ }
+ }
+
+ if ( IsValid( target ) )
+ {
+ if ( !target.IsNPC() )
+ {
+ target.PlayerMelee_ExecutionEndTarget()
+ ClearPlayerAnimViewEntity( target )
+ DeployAndEnableWeapons( target )
+ }
+
+
+ if ( IsAlive( target ) )
+ {
+ local attack = attacker
+ if ( !IsValid( attack ) )
+ attack = null
+
+ target.Die( attack, attack, { scriptType = 0, damageSourceId = eDamageSourceId.titan_execution } )
+ }
+
+ target.e.syncedMeleeAttacker = null
+
+ if ( HasAnimEvent( target, "rider_rodeo_over" ) )
+ DeleteAnimEvent( target, "rider_rodeo_over" )
+ }
+ }
+ )
+
+ thread FirstPersonSequence( targetSequence, target, ref )
+ waitthread FirstPersonSequence( attackerSequence, attacker, ref )
+
+ //wait ( 50.0 / 30.0 ) // 37 frames in
+}
+
+
+void function MeleeThread_StyderVsTitan( SyncedMelee action, entity attacker, entity target )
+{
+ table e
+ e.gib <- true
+ e.attackerAnimation1p <- "strypov_melee_sync_frontkill"
+ e.attackerAnimation3p <- "stry_melee_sync_frontkill"
+ e.targetAnimation3p <- "stry_melee_sync_frontdeath"
+ e.targetPilotAnimationForAttacker <- "pt_stry_melee_sync_front_pilotkill_1st"
+ e.targetPilotAnimationForObserver <- "pt_stry_melee_sync_front_pilotkill_3rd"
+ e.targetPilotAnimationForObserver1st <- "ptpov_stry_tvtmelee_targetdeath"
+ e.TitanSpecific1pSyncMeleeSound <- "Stryder_1p_Sync_Melee"
+ e.TitanSpecific3pSyncMeleeSound <- "Stryder_3p_Sync_Melee"
+
+ MeleeThread_TitanRipsPilot( e, action, attacker, target )
+}
+
+void function MeleeThread_AtlasVsTitan( SyncedMelee action, entity attacker, entity target )
+{
+ table e
+ e.gib <- false
+ e.attackerAnimation1p <- "atpov_melee_sync_frontkill"
+ e.attackerAnimation3p <- "at_melee_sync_frontkill"
+ e.targetAnimation3p <- "at_melee_sync_frontdeath"
+ e.targetPilotAnimationForAttacker <- "pt_melee_sync_front_pilotkill_1st"
+ e.targetPilotAnimationForObserver <- "pt_melee_sync_front_pilotkill_3rd"
+ e.targetPilotAnimationForObserver1st <- "ptpov_tvtmelee_targetdeath"
+ e.TitanSpecific1pSyncMeleeSound <- "Atlas_1p_Sync_Melee"
+ e.TitanSpecific3pSyncMeleeSound <- "Atlas_3p_Sync_Melee"
+
+ MeleeThread_TitanRipsPilot( e, action, attacker, target )
+}
+
+function MeleeThread_TitanRipsPilot( table e, SyncedMelee action, entity attacker, entity target )
+{
+ e.attackerViewBody <- null
+ e.attacker <- attacker
+ e.attackerStartOrg <- attacker.GetOrigin()
+
+ entity ref = CreateMeleeScriptMoverBetweenEnts( attacker, target )
+
+ FirstPersonSequenceStruct attackerSequence
+ attackerSequence.blendTime = 0.25
+ attackerSequence.attachment = "ref"
+
+ FirstPersonSequenceStruct targetSequence = clone attackerSequence
+
+ attackerSequence.thirdPersonAnim = expect string ( e.attackerAnimation3p )
+ // attackerSequence.thirdPersonAnimIdle = "at_melee_sync_frontkill_end_idle"
+
+ attackerSequence.firstPersonAnim = expect string( e.attackerAnimation1p )
+ targetSequence.thirdPersonAnim = expect string ( e.targetAnimation3p )
+ targetSequence.blendTime = 0.25
+
+ target.e.syncedMeleeAttacker = attacker
+
+ // attacker.SetInvulnerable()
+ target.SetInvulnerable() //HACK: Have to SetInvulnerable first before attacker holsters weapon, because if the attacker is vortexing, holster will release bullets caught and kill off the victim if low enough health
+ if ( ShouldHolsterWeaponForSyncedMelee( attacker ) )
+ HolsterAndDisableWeapons( attacker )
+
+ if ( !target.IsNPC() )
+ HolsterAndDisableWeapons( target )
+
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( expect string ( e.TitanSpecific1pSyncMeleeSound ), expect string ( e.TitanSpecific3pSyncMeleeSound ), attacker, attacker )
+
+ entity attackerViewBody
+ bool targetIsPlayer = target.IsPlayer()
+
+ if ( targetIsPlayer )
+ {
+ attackerViewBody = Wallrun_CreateCopyOfPilotModel( target ) //attackerViewBody is the model of the pilot getting ripped out of the cockpit
+ }
+ else
+ {
+ attackerViewBody = CreateNpcTitanPilotModel( target )
+ }
+
+ attackerViewBody.SetOrigin( ref.GetOrigin() )
+ e.attackerViewBody = attackerViewBody
+ attackerViewBody.SetOwner( attacker )
+ attackerViewBody.kv.VisibilityFlags = ENTITY_VISIBLE_TO_OWNER
+ attackerViewBody.SetRagdollImpactFX( RAGDOLL_IMPACT_TABLE_IDX )
+ attackerViewBody.SetContinueAnimatingAfterRagdoll( true )
+
+ FirstPersonSequenceStruct attackerBodySequence
+ attackerBodySequence.attachment = "ref"
+ attackerBodySequence.teleport = true
+ attackerBodySequence.thirdPersonAnim = expect string ( e.targetPilotAnimationForAttacker )
+
+ FirstPersonSequenceStruct targetBodySequence
+ targetBodySequence.attachment = "ref"
+ targetBodySequence.blendTime = 0.25
+ targetBodySequence.thirdPersonAnim = expect string ( e.targetPilotAnimationForObserver )
+ targetBodySequence.firstPersonAnim = expect string ( e.targetPilotAnimationForObserver1st )
+
+
+ entity targetSoul = target.GetTitanSoul()
+ targetSoul.SetInvalidHealthBarEnt( true )
+
+ entity targetTitan
+ if ( targetIsPlayer )
+ {
+ e.oldPlayerSettings <- target.s.storedPlayerSettings
+ //target.s.storedPlayerSettings = "pilot_titan_cockpit" // Makes player have titan cockpit temporarily. Turned off to avoid having extra checks all over in script
+ targetTitan = CreateAutoTitanForPlayer_ForTitanBecomesPilot( target ) //TargetTitan is the NPC Titan that is created temporarily during execution
+ DispatchSpawn( targetTitan )
+
+ TitanBecomesPilot( target, targetTitan )
+ DisableTitanRodeo( targetTitan )
+ targetTitan.SetOwner( target )
+ targetTitan.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) //owner cant see
+ targetTitan.PlayerMelee_ExecutionStartTarget( attacker )
+ e.target <- target
+ }
+ else
+ {
+ targetTitan = target
+
+ // target is now a random dude
+ target = CreateSoldier( target.GetTeam(), Vector(0,0,0), Vector(0,0,0) )
+ DispatchSpawn( target )
+ e.target <- target
+ }
+
+
+ AddAnimEvent( targetTitan, "rider_rodeo_over", ForceTitanRodeoToEnd )
+ AddAnimEvent( targetTitan, "melee_killed_ragdoll", MeleeKilledRagdoll, attacker )
+
+ targetTitan.SetInvulnerable() //Setting target of execution as invulnerable to prevent them dying mid-way
+
+ target.SetOwner( attacker )
+ target.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) //owner cant see
+ e.targetTitan <- targetTitan
+
+ if ( GetBugReproNum() == 129802 )
+ thread OnNPCTitanSignalDeath( targetTitan )
+
+ OnThreadEnd(
+ function() : ( ref, attacker, target, targetTitan, e )
+ {
+ if ( IsValid( ref ) )
+ {
+ if ( IsValid( attacker ) )
+ {
+ attacker.ClearParent()
+ }
+ else
+ {
+ TryClearParent( attacker )
+ }
+
+ if ( IsValid( target ) )
+ {
+ target.ClearParent()
+ }
+ else
+ {
+ TryClearParent( target )
+ }
+
+ AssertNoPlayerChildren( ref )
+ ref.Kill_Deprecated_UseDestroyInstead()
+ }
+
+ if ( IsValid( attacker ) )
+ {
+ attacker.UnforceStand()
+ attacker.ClearParent()
+ ClearPlayerAnimViewEntity( attacker )
+ DeployAndEnableWeapons( attacker )
+ attacker.PlayerMelee_ExecutionEndAttacker()
+
+ if ( IsAlive( attacker ) )
+ {
+ // if we got into solid, teleport back to safe place
+ PutEntityInSafeSpot( attacker, null, null, expect vector( e.attackerStartOrg ), attacker.GetOrigin() )
+ }
+ }
+
+ if ( IsValid( target ) )
+ {
+ if ( !target.IsNPC() )
+ {
+ target.PlayerMelee_ExecutionEndTarget()
+ ClearPlayerAnimViewEntity( target )
+ DeployAndEnableWeapons( target )
+ }
+
+ if ( HasAnimEvent( target, "pink_mist" ) )
+ DeleteAnimEvent( target, "pink_mist" )
+
+ if ( IsAlive( expect entity( e.target ) ) )
+ MeleePinkMist( e )
+
+ target.e.syncedMeleeAttacker = null
+ }
+
+ if ( IsValid( e.attackerViewBody ) )
+ e.attackerViewBody.Kill_Deprecated_UseDestroyInstead()
+
+ if ( GetBugReproNum() != 129802 && IsAlive( targetTitan ) )
+ {
+ if ( IsValid( attacker ) )
+ targetTitan.Die( attacker, attacker, { scriptType = DF_MELEE, damageSourceId = eDamageSourceId.titan_execution } )
+ else
+ targetTitan.Die()
+
+ if ( GetBugReproNum() == 129815 )
+ {
+ targetTitan.SetContinueAnimatingAfterRagdoll( true )
+ targetTitan.BecomeRagdoll( Vector(0,0,0), false )
+ }
+ }
+ }
+ )
+
+ target.EndSignal( "OnRespawnPlayer" )
+
+ waitthread TitanSyncedMeleeAnimationsPlay( attackerBodySequence, attackerViewBody, ref, targetBodySequence, target, attackerSequence, attacker, targetSequence, targetTitan, e )
+}
+
+entity function CreateNpcTitanPilotModel( entity titan )
+{
+ asset modelName = GetNpcTitanPilotModel( titan )
+ return CreatePropDynamic( modelName )
+}
+
+
+
+asset function GetNpcTitanPilotModel( entity titan )
+{
+ asset modelName = TEAM_IMC_GRUNT_MODEL
+
+ #if HAS_BOSS_AI
+ if ( IsBossTitan( titan ) )
+ {
+ modelName = GetBossTitanCharacterModel( titan )
+ }
+ #endif
+
+ return modelName
+}
+
+function TitanSyncedMeleeAnimationsPlay( FirstPersonSequenceStruct attackerBodySequence, entity attackerViewBody, entity ref, FirstPersonSequenceStruct targetBodySequence, entity target, FirstPersonSequenceStruct attackerSequence, entity attacker, FirstPersonSequenceStruct targetSequence, entity targetTitan, table e )
+{
+ e.thrown <- false
+ OnThreadEnd (
+ function () : ( targetTitan, target, attacker, e )
+ {
+ // insure visibility
+ if ( IsValid( targetTitan ) )
+ targetTitan.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+
+ if ( !IsAlive( attacker ) )
+ {
+ attacker.Anim_Stop()
+
+ if ( !e.thrown && IsAlive( target ) )
+ {
+ target.Anim_Stop()
+ target.SetOwner( null )
+ target.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ if ( target.IsPlayer() )
+ {
+ ClearPlayerAnimViewEntity( target )
+ target.GetFirstPersonProxy().Anim_Stop()
+ target.SetPlayerSettings( e.oldPlayerSettings )
+ }
+
+ }
+ }
+ }
+ )
+
+ attacker.EndSignal( "OnDeath" )
+ target.EndSignal( "OnDestroy" )
+ target.EndSignal( "OnRespawnPlayer" )
+
+ thread FirstPersonSequence( attackerBodySequence, attackerViewBody, ref )
+ if ( !target.IsPlayer() )
+ {
+ // don't do first person anims if we're not a player
+ targetBodySequence.firstPersonAnim = ""
+ targetBodySequence.firstPersonAnimIdle = ""
+ }
+
+ thread FirstPersonSequence( targetBodySequence, target, ref )
+ thread FirstPersonSequence( attackerSequence, attacker, ref )
+ thread FirstPersonSequence( targetSequence, targetTitan, ref )
+ targetTitan.Anim_AdvanceCycleEveryFrame( true )
+ local duration = attacker.GetSequenceDuration( attackerSequence.thirdPersonAnim )
+
+ if ( e.targetAnimation3p == "at_melee_sync_frontdeath" )
+ {
+ thread MeleeThrowIntoWallSplat( attacker, target, e )
+ }
+ else
+ {
+ AddAnimEvent( target, "pink_mist", MeleePinkMistAnimEvent, e )
+ }
+
+ float timer
+ string titanType = GetSoulTitanSubClass( attacker.GetTitanSoul() )
+ switch ( titanType )
+ {
+ case "stryder":
+ timer = 0.9
+ break
+ case "atlas":
+ case "buddy":
+ timer = 0.45
+ break
+ default:
+ Assert( 0, "Unknown titan type " + titanType )
+ }
+
+ wait timer
+
+ // first the victim cant see his titan, as a pilot, and then he can
+ targetTitan.SetNextThinkNow()
+ targetTitan.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ targetTitan.SetNextThinkNow()
+ wait duration - timer
+}
+
+void function MeleePinkMistAnimEvent( entity target ) //parameter isn't used, but function signature is like this because it's being called from an anim event
+{
+ table e = expect table( GetOptionalAnimEventVar( target, "pink_mist" ) )
+
+ MeleePinkMist( e )
+}
+
+void function MeleePinkMist( table e )
+{
+ entity target = expect entity( e.target )
+
+ if ( !IsAlive( target ) )
+ return
+
+ e.attackerViewBody.Dissolve( ENTITY_DISSOLVE_PINKMIST, Vector( 0, 0, 0 ), 0 )
+ if ( IsValid( e.attacker ) )
+ {
+ target.Die( e.attacker, e.attacker, { damageSourceId = eDamageSourceId.titan_execution, scriptType = DF_GIB } )
+ }
+ else
+ {
+ target.Die( e.target, target, { damageSourceId = eDamageSourceId.titan_execution, scriptType = DF_GIB } )
+ }
+
+ if ( target.IsPlayer() )
+ ClearPlayerAnimViewEntity( target )
+
+ target.ClearInvulnerable()
+}
+
+function MeleeThrowIntoWallSplat( entity attacker, entity target, e )
+{
+ OnThreadEnd(
+ function () : ( target, e )
+ {
+ if ( IsValid( target ) )
+ {
+ target.ClearParent()
+ target.Anim_Stop()
+ target.ClearInvulnerable()
+ }
+ }
+ )
+
+ target.EndSignal( "OnDeath" )
+
+ e.startOrigin <- target.GetOrigin()
+ wait 2.8
+ e.thrown = true
+
+
+ // attacker got killed? saved!
+ if ( !IsAlive( attacker ) )
+ return
+
+ local angles = attacker.GetAngles()
+ angles = AnglesCompose( angles, Vector( -15, 0, 0 ) )
+ local forward = AnglesToForward( angles )
+
+ local endPos
+ for ( ;; )
+ {
+ if ( !target.Anim_IsActive() )
+ break
+
+ local org = target.GetOrigin()
+ if ( IsAlive( attacker ) )
+ {
+ TraceResults titanPilotTrace = TraceLine( attacker.EyePosition(), org, attacker )
+
+ if ( titanPilotTrace.fraction < 1.0 )
+ {
+ endPos = titanPilotTrace.endPos
+ break
+ }
+ }
+
+
+ TraceResults result = TraceLine( org, org + forward * 200 )
+ if ( result.fraction < 1.0 )
+ {
+ wait result.fraction * 0.06
+ break
+ }
+
+ WaitFrame()
+ }
+
+ if ( endPos )
+ {
+ target.SetOrigin( endPos )
+ }
+
+ Assert( IsAlive( target ) )
+
+ target.ClearInvulnerable()
+
+ target.BecomeRagdoll( Vector(0,0,0), false )
+
+ WaitFrame() // ragdoll take hold!
+ EmitSoundOnEntity( target, "Titan_Victim_Wall_Splat" )
+
+ if ( e.gib )
+ {
+ local force = Vector(0,0,0)
+ if ( IsAlive( attacker ) )
+ {
+ local vec = target.GetOrigin() - attacker.GetOrigin()
+ vec.Norm()
+ force = vec
+ }
+ target.Die( attacker, attacker, { scriptType = DF_GIB | DF_KILLSHOT, force = force, damageSourceId = eDamageSourceId.titan_execution } )
+ }
+ else
+ {
+ target.Die( attacker, attacker, { scriptType = DF_KILLSHOT, damageSourceId = eDamageSourceId.titan_execution } )
+ }
+}
+
+
+function MeleeAnimThrow( attacker, target, throwDuration )
+{
+ attacker.EndSignal( "OnDeath" )
+ target.EndSignal( "OnDeath" )
+ wait throwDuration - 0.2
+
+ local angles = attacker.GetAngles()
+ local forward = AnglesToForward( angles )
+ target.ClearParent()
+ target.SetVelocity( forward * 500 )
+
+
+ target.Die( attacker, attacker, { scriptType = DF_KILLSHOT, damageSourceId = eDamageSourceId.titan_execution } )
+}
+
+///////////////////////////////////////
+// OGRE MELEES
+///////////////////////////////////////
+void function MeleeThread_OgreVsTitan( SyncedMelee action, entity attacker, entity target )
+{
+ string attackerAnimation1p = "ogpov_melee_armrip_attacker"
+ string attackerAnimation3p = "og_melee_armrip_attacker"
+ string targetAnimation1p = "ogpov_melee_armrip_victim"
+ string targetAnimation3p = "og_melee_armrip_victim"
+
+ table e = {}
+ e.attackerStartOrg <- attacker.GetOrigin()
+ e.lostArm <- false
+ e.targetStartOrg <- target.GetOrigin()
+
+ entity ref = CreateMeleeScriptMoverBetweenEnts( attacker, target )
+
+ FirstPersonSequenceStruct attackerSequence
+ attackerSequence.blendTime = 0.25
+ attackerSequence.attachment = "ref"
+
+ FirstPersonSequenceStruct targetSequence = clone attackerSequence
+
+ attackerSequence.thirdPersonAnim = attackerAnimation3p
+ attackerSequence.firstPersonAnim = attackerAnimation1p
+
+ if ( target.IsPlayer() )
+ targetSequence.firstPersonAnim = targetAnimation1p
+
+ targetSequence.thirdPersonAnim = targetAnimation3p
+ targetSequence.blendTime = 0.25
+
+ target.e.syncedMeleeAttacker = attacker
+ DisableWeapons( attacker, [] )
+ DisableWeapons( target, [] )
+
+ // attacker.SetInvulnerable()
+ target.SetInvulnerable()
+
+ entity soul = target.GetTitanSoul()
+ soul.SetInvalidHealthBarEnt( true )
+
+ OnThreadEnd(
+ function() : ( ref, attacker, target, e )
+ {
+ if ( IsValid( ref ) )
+ {
+ if ( IsValid( attacker ) )
+ attacker.ClearParent()
+
+ if ( IsValid( target ) )
+ target.ClearParent()
+
+ AssertNoPlayerChildren( ref )
+ ref.Kill_Deprecated_UseDestroyInstead()
+ }
+
+ if ( IsValid( attacker ) )
+ {
+ attacker.UnforceStand()
+ attacker.ClearParent()
+ ClearPlayerAnimViewEntity( attacker )
+ EnableWeapons( attacker, [] )
+ attacker.PlayerMelee_ExecutionEndAttacker()
+
+ if ( IsAlive( attacker ) )
+ {
+ // if we got into solid, teleport back to safe place
+ PutEntityInSafeSpot( attacker, null, null, expect vector( e.attackerStartOrg ), attacker.GetOrigin() )
+ }
+ }
+
+ if ( IsValid( target ) )
+ {
+ DeleteAnimEvent( target, "lost_arm" )
+
+ target.e.syncedMeleeAttacker = null
+
+ target.ClearParent()
+ target.ClearInvulnerable()
+ if ( target.IsPlayer() )
+ {
+ ClearPlayerAnimViewEntity( target )
+ }
+
+ EnableWeapons( target, [] )
+
+ if ( !target.IsNPC() )
+ target.PlayerMelee_ExecutionEndTarget()
+
+ if ( e.lostArm && IsAlive( target ) )
+ {
+ target.Die( attacker, attacker, { scriptType = DF_KILLSHOT, damageSourceId = eDamageSourceId.titan_execution } )
+ return
+ }
+ else if ( target.IsPlayer() )
+ {
+ PutEntityInSafeSpot( target, null, null, expect vector( e.targetStartOrg ), target.GetOrigin() )
+ }
+ }
+ }
+ )
+
+ attacker.EndSignal( "OnDeath" )
+
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( "Ogre_1p_Sync_Melee", "Ogre_3p_Sync_Melee", attacker, attacker )
+
+ AddAnimEvent( target, "lost_arm", TitanLostArm, e )
+
+
+ thread FirstPersonSequence( targetSequence, target, ref )
+ waitthread FirstPersonSequence( attackerSequence, attacker, ref )
+}
+
+//Very similar to the above function for now, eventually won't have the 1st person component at all.
+void function TitanVsTitan_3p( SyncedMelee action, entity attacker, entity target )
+{
+ if ( !IsAlive( attacker ) )
+ return
+
+ if ( !IsAlive( target ) )
+ return
+
+ entity attackerSoul = attacker.GetTitanSoul()
+ #if SP
+ TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap()
+ string executionRef = loadout.titanExecution
+ TitanExcutionData data = file.executionData_3p[ executionRef ]
+ #else
+ TitanLoadoutDef loadout = attackerSoul.soul.titanLoadout // GetActiveTitanLoadout( attacker )
+ string executionRef = loadout.titanExecution
+ TitanExcutionData data = file.executionData_3p[ executionRef ]
+ if ( data.linkedExecutions.len() > 0 )
+ {
+ array<string> clonedLinkedExecutions = clone data.linkedExecutions
+ for ( int i = clonedLinkedExecutions.len() - 1; i >= 0; i-- )
+ {
+ if ( GetItemRequiresPrime( clonedLinkedExecutions[ i ] ) == true && !HasPrimeToMatchExecutionType( attacker, GetItemType( clonedLinkedExecutions[ i ] ) ) )
+ clonedLinkedExecutions.remove( i )
+ }
+ executionRef = clonedLinkedExecutions.getrandom()
+ data = file.executionData_3p[ executionRef ]
+ }
+ #endif
+ bool shouldApplyBatteryAfterRodeo = false
+ if ( SoulHasPassive( attackerSoul, ePassives.PAS_VANGUARD_COREMETER ) )
+ {
+ executionRef = "execution_vanguard_kit"
+ data = file.executionData_3p[ executionRef ]
+ shouldApplyBatteryAfterRodeo = true
+ }
+
+ string victimType = GetSoulTitanSubClass( target.GetTitanSoul() )
+
+ table e = {}
+ e.attackerStartOrg <- attacker.GetOrigin()
+ e.lostArm <- false
+ e.targetStartOrg <- target.GetOrigin()
+
+ FirstPersonSequenceStruct attackerSequence
+ attackerSequence.blendTime = 0.25
+ attackerSequence.attachment = "ref"
+ attackerSequence.thirdPersonCameraAttachments = clone data.thirdPersonCameraAttachments
+ attackerSequence.thirdPersonCameraVisibilityChecks = true
+ attackerSequence.viewConeFunction = ViewConeZero
+ attackerSequence.noViewLerp = true
+
+ FirstPersonSequenceStruct targetSequence = clone attackerSequence
+
+ attackerSequence.thirdPersonAnim = data.attackerAnimation3p
+ attackerSequence.firstPersonAnim = ""
+
+ if ( target.IsPlayer() )
+ targetSequence.firstPersonAnim = ""
+
+ targetSequence.thirdPersonAnim = data.targetAnimation3p[ victimType ]
+ targetSequence.thirdPersonCameraEntity = target
+
+ target.e.syncedMeleeAttacker = attacker
+
+ // HACK FOR SP!!!
+ e.replacedPrimary <- false
+ string xo16 = "mp_titanweapon_xo16_shorty"
+ if ( IsSingleplayer() && attacker.IsPlayer() && data.attackerAnimation3p == "bt_synced_titan_execute_kickshoot_A" )
+ {
+ array<entity> weapons = attacker.GetMainWeapons()
+ if ( weapons.len() > 0 )
+ {
+ if ( weapons[0].GetWeaponClassName() != xo16 )
+ {
+ e.replacedPrimary = true
+ e.oldPrimary <- weapons[0].GetWeaponClassName()
+ attacker.SetActiveWeaponBySlot( 0 )
+ attacker.ReplaceActiveWeapon( xo16 ) //this assumes the active weapon is the weapon in slot 0 so we need to set active weapon to the one in slot 0
+ }
+ }
+ }
+ // END HACK FOR SP!!!
+
+ if ( !target.IsNPC() )
+ HolsterViewModelAndDisableWeapons( target ) //Melee anims need to use this to stop players from firing weapons but for the weapon to still show up in the 3p anims
+ else
+ DisableWeapons( target, [] )
+
+ if ( attacker.IsPlayer() )
+ {
+ HolsterViewModelAndDisableWeapons( attacker ) //Melee anims need to use this to stop players from firing weapons but for the weapon to still show up in the 3p anims
+ attacker.Anim_StopGesture( DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME )
+ }
+
+ // attacker.SetInvulnerable()
+ target.SetInvulnerable()
+
+ entity targetViewBody
+ FirstPersonSequenceStruct targetBodySequence
+ entity attackerViewBody
+ FirstPersonSequenceStruct attackerBodySequence
+
+ bool titanHasPilot = target.IsPlayer()
+ #if HAS_BOSS_AI
+ titanHasPilot = titanHasPilot || ( IsBossTitan( target ) )
+ #endif
+
+ if ( attacker.IsPlayer() )
+ {
+ Remote_CallFunction_Replay( attacker, "SCB_StopTitanCockpitSounds" )
+ }
+
+ if ( target.IsPlayer() )
+ {
+ Remote_CallFunction_Replay( target, "SCB_StopTitanCockpitSounds" )
+ }
+
+ if ( data.targetAnimation3pPilot[ victimType ] != "" && titanHasPilot )
+ {
+ if ( target.IsNPC() )
+ targetViewBody = CreateNpcTitanPilotModel( target )
+ else
+ targetViewBody = Wallrun_CreateCopyOfPilotModel( target )
+
+ targetViewBody.SetOrigin( target.GetOrigin() )
+ targetViewBody.SetRagdollImpactFX( RAGDOLL_IMPACT_TABLE_IDX )
+ targetViewBody.SetContinueAnimatingAfterRagdoll( true )
+
+ targetBodySequence.attachment = "ref"
+ targetBodySequence.teleport = true
+ targetBodySequence.thirdPersonAnim = data.targetAnimation3pPilot[ victimType ]
+
+ AddAnimEvent( targetViewBody, "pink_mist", MeleePinkMistFakeBody )
+ }
+
+ if ( data.attackerAnimation3pPilot[ victimType ] != "" && attacker.IsPlayer() )
+ {
+ attackerViewBody = Wallrun_CreateCopyOfPilotModel( attacker )
+
+ attackerViewBody.SetOrigin( attacker.GetOrigin() )
+ attackerViewBody.SetRagdollImpactFX( RAGDOLL_IMPACT_TABLE_IDX )
+ attackerViewBody.SetContinueAnimatingAfterRagdoll( true )
+
+ attackerBodySequence.attachment = "ref"
+ attackerBodySequence.teleport = true
+ attackerBodySequence.thirdPersonAnim = data.attackerAnimation3pPilot[ victimType ]
+ }
+
+ if ( !IsValid( targetViewBody ) )
+ {
+ attackerSequence.thirdPersonAnim = data.attackerAnimation3p_vsAutoTitan
+ }
+
+ entity soul = target.GetTitanSoul()
+ soul.SetInvalidHealthBarEnt( true )
+
+ bool isAttackerRef = false
+ if ( GetConVarBool( "melee_titan_execution_attacker_can_be_ref" ) )
+ {
+ isAttackerRef = IsAttackerRef( null, target )
+ }
+
+ OnThreadEnd(
+ function() : ( attacker, target, e, attackerViewBody, targetViewBody, shouldApplyBatteryAfterRodeo, isAttackerRef )
+ {
+ if ( IsValid( attacker ) )
+ {
+ DeleteAnimEvent( attacker, "synced_melee_enable_planting" )
+ DeleteAnimEvent( attacker, "rocket_pod_fire_left" )
+ DeleteAnimEvent( attacker, "rocket_pod_fire_right" )
+
+ attacker.UnforceStand()
+ attacker.ClearParent()
+ ClearPlayerAnimViewEntity( attacker )
+ attacker.PlayerMelee_ExecutionEndAttacker()
+ ForceTitanSustainedDischargeEnd( attacker )
+ DeployViewModelAndEnableWeapons( attacker ) //Melee anims need to use this to stop players from firing weapons but for the weapon to still show up in the 3p anims
+ if ( IsAlive( attacker ) )
+ {
+ if ( !isAttackerRef && IsValid( target ) )
+ {
+ PutEntityInSafeSpot( attacker, target, null, target.GetOrigin(), attacker.GetOrigin() )
+ }
+ else
+ {
+ PutEntityInSafeSpot( attacker, target, null, attacker.GetOrigin(), attacker.GetOrigin() )
+ }
+
+ if ( attacker.IsTitan() )
+ {
+ Remote_CallFunction_Replay( attacker, "SCB_PlayTitanCockpitSounds" )
+ #if TITAN_EXECUTION_GIVES_BATTERY
+ Rodeo_GiveExecutingTitanABattery( attacker )
+ #else
+ if ( shouldApplyBatteryAfterRodeo )
+ Rodeo_GiveExecutingTitanABattery( attacker )
+ #endif
+ }
+
+ if ( IsSingleplayer() )
+ {
+ if ( e.replacedPrimary )
+ {
+ attacker.ReplaceActiveWeapon( e.oldPrimary )
+ }
+ }
+ else
+ {
+ attacker.Anim_Stop() // if you are fighting an NPC, then they can get destroyed early the moment they explode. But sometimes, your animation isn't done playing yet so you can't move
+ }
+ }
+
+ }
+
+ if ( IsValid( target ) )
+ {
+ DeleteAnimEvent( target, "melee_killed_ragdoll" )
+ DeleteAnimEvent( target, "execution_battery_show" )
+ DeleteAnimEvent( target, "execution_battery_hide" )
+
+
+ if ( HasAnimEvent( target, "rider_rodeo_over" ) )
+ DeleteAnimEvent( target, "rider_rodeo_over" )
+
+ target.e.syncedMeleeAttacker = null
+
+ target.ClearParent()
+ target.ClearInvulnerable()
+ if ( target.IsPlayer() )
+ {
+ ClearPlayerAnimViewEntity( target )
+ DeployViewModelAndEnableWeapons( target ) //Melee anims need to use this to stop players from firing weapons but for the weapon to still show up in the 3p anims
+ }
+
+ if ( !target.IsNPC() && target.ContextAction_IsMeleeExecution() )
+ target.PlayerMelee_ExecutionEndTarget()
+
+ if ( IsAlive( target ) ) //Should have no need to PlayTitanCockpitSounds for target because the target is going to die
+ {
+ target.Die( attacker, attacker, { scriptType = DF_KILLSHOT, damageSourceId = eDamageSourceId.titan_execution } )
+ }
+ else if ( target.IsPlayer() )
+ {
+ if ( isAttackerRef && IsValid( attacker ) )
+ {
+ PutEntityInSafeSpot( target, attacker, null, attacker.GetOrigin(), target.GetOrigin() )
+ }
+ else
+ {
+ PutEntityInSafeSpot( target, attacker, null, target.GetOrigin(), target.GetOrigin() )
+ }
+ }
+ }
+
+ if ( IsValid( attackerViewBody ) )
+ {
+ //DeleteAnimEvent( attackerViewBody, "rodeo_battery_rip" )
+ DeleteAnimEvent( attackerViewBody, "execution_battery_pilot" )
+ DeleteAnimEvent( attackerViewBody, "execution_battery_pilot_jump_jets" )
+ attackerViewBody.Hide()
+ attackerViewBody.Destroy()
+ }
+
+ if ( IsValid( targetViewBody ) )
+ {
+ targetViewBody.Hide()
+ targetViewBody.Destroy()
+ }
+ }
+ )
+
+ attacker.EndSignal( "OnDeath" )
+ entity bossPlayer = target.GetBossPlayer()
+ if ( IsValid( bossPlayer ) ) //Executing an auto-Titan, when the pilot disconnects it destroys the auto-titan creating weird circumstances.
+ bossPlayer.EndSignal( "OnDestroy" )
+ target.EndSignal( "OnDestroy" )
+
+ if ( isAttackerRef )
+ {
+ thread ClearParentOnDeathOrDestroy( target, attacker )
+ }
+ else
+ {
+ thread ClearParentOnDeathOrDestroy( attacker, target )
+ }
+
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( data.sound_1p, data.sound_3p, attacker, attacker )
+
+ AddAnimEvent( target, "rider_rodeo_over", ForceTitanRodeoToEnd )
+ AddAnimEvent( target, "melee_killed_ragdoll", PredatorMeleeKilledRagdoll )
+ AddAnimEvent( attacker, "synced_melee_enable_planting", EnablePlantingOnEntity )
+ AddAnimEvent( attacker, "rocket_pod_fire_left", Northstar_Rocket_Pod_Left, target )
+ AddAnimEvent( attacker, "rocket_pod_fire_right", Northstar_Rocket_Pod_Right, target )
+ AddAnimEvent( target, "execution_battery_show", Execution_ShowBattery )
+ AddAnimEvent( target, "execution_battery_hide", Execution_HideBattery )
+ if ( attackerViewBody != null )
+ {
+ AddAnimEvent( attackerViewBody, "execution_battery_pilot", Execution_GivePilotBattery )
+ AddAnimEvent( attackerViewBody, "execution_battery_pilot_jump_jets", Execution_BatteryStealJumpJets )
+ }
+
+
+ if ( isAttackerRef )
+ {
+ attackerSequence.enablePlanting = true
+ attackerSequence.playerPushable = true
+ targetSequence.useAnimatedRefAttachment = true
+ }
+ else
+ {
+ targetSequence.enablePlanting = true
+ targetSequence.playerPushable = true
+ attackerSequence.useAnimatedRefAttachment = true
+ }
+
+ array<entity> ignoreEnts = [ attacker, target ]
+
+ vector refAngles = GetRefAnglesBetweenEnts( attacker, target )
+
+ if ( !attacker.IsOnGround() )
+ {
+ refAngles = <0,refAngles.y,0>
+ }
+
+ vector fwd = AnglesToForward( refAngles )
+ fwd *= -1
+ vector targetAngles = VectorToAngles( fwd )
+ if ( !target.IsNPC() )
+ {
+ targetAngles.x = 0
+ target.SetAngles( targetAngles )
+ }
+
+ target.SetAngles( targetAngles )
+
+ if ( attackerViewBody != null )
+ {
+ attackerBodySequence.useAnimatedRefAttachment = true
+ thread FirstPersonSequence( attackerBodySequence, attackerViewBody, attacker )
+ }
+
+ if ( targetViewBody != null )
+ {
+ targetBodySequence.useAnimatedRefAttachment = true
+ thread FirstPersonSequence( targetBodySequence, targetViewBody, target )
+ }
+
+ if ( isAttackerRef )
+ {
+ thread FirstPersonSequence( attackerSequence, attacker )
+ waitthread FirstPersonSequence( targetSequence, target, attacker )
+ }
+ else
+ {
+ thread FirstPersonSequence( targetSequence, target )
+ waitthread FirstPersonSequence( attackerSequence, attacker, target )
+ }
+}
+
+void function Execution_ShowBattery( entity titan )
+{
+ entity titanSoul = titan.GetTitanSoul()
+ if ( !IsValid( titanSoul ) ) //Out of bounds
+ return
+ string titanType = GetSoulTitanSubClass( titanSoul )
+ entity batteryContainer = titanSoul.soul.batteryContainer
+ Assert( IsValid( titanSoul.soul.batteryContainer ), " need to find the repro for this" )
+ if ( !IsValid( titanSoul.soul.batteryContainer ) )
+ return
+
+ batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up_idle" ) )
+}
+
+void function Execution_HideBattery( entity titan )
+{
+ entity titanSoul = titan.GetTitanSoul()
+ if ( !IsValid( titanSoul ) ) //Out of bounds
+ return
+ string titanType = GetSoulTitanSubClass( titanSoul )
+ entity batteryContainer = titanSoul.soul.batteryContainer
+ Assert( IsValid( titanSoul.soul.batteryContainer ), " need to find the repro for this" )
+ if ( !IsValid( titanSoul.soul.batteryContainer ) )
+ return
+
+ batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_down_idle" ) )
+ EmitSoundOnEntity( batteryContainer, GetAudioFromAlias( titanType, "rodeo_battery_steal_3p" ) )
+}
+
+void function Execution_GivePilotBattery( entity fakePilotModel )
+{
+ entity tempBattery3p = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ tempBattery3p.SetParent( fakePilotModel, "R_HAND", false, 0.0 )
+ tempBattery3p.RemoveFromSpatialPartition()
+ tempBattery3p.Show()
+ Battery_StartFX( tempBattery3p )
+}
+
+
+void function Execution_BatteryStealJumpJets( entity fakePilotModel )
+{
+ int attachmentIndex = fakePilotModel.LookupAttachment( "vent_left" )
+ int fxIndex = GetParticleSystemIndex( TEAM_JUMPJET_DBL )
+ StartParticleEffectOnEntity( fakePilotModel, fxIndex, FX_PATTACH_POINT_FOLLOW, attachmentIndex )
+
+ attachmentIndex = fakePilotModel.LookupAttachment( "vent_right" )
+ StartParticleEffectOnEntity( fakePilotModel, fxIndex, FX_PATTACH_POINT_FOLLOW, attachmentIndex )
+}
+
+/*
+void function RodeoBatteryRemoval( entity pilot )
+{
+ entity titan = GetTitanBeingRodeoed( pilot )
+ if ( !IsValid( titan ) )
+ return
+
+ // THROW RODEO RIDER OFF
+ entity soul = titan.GetTitanSoul()
+ string titanType = GetSoulTitanSubClass( soul )
+
+ soul.SetLastRodeoHitTime( Time() )
+
+ RodeoBatteryPackRemovalDamage( pilot, titan, soul )
+
+ if ( !PlayerHasBattery( pilot ) )
+ {
+ AddPlayerScore( pilot, "PilotBatteryStolen" )
+ entity battery = Rodeo_CreateBatteryPack( titan )
+ Rodeo_PilotPicksUpBattery( pilot, battery )
+ thread BatteryThiefHighlight( pilot )
+
+ if ( titan.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( titan, titan, TITAN_GOT_BATTERY_RIPPED_SOUND ) //Consider playing this in world once we get sounds that aren't just notification beeps
+ }
+ }
+
+ vector direction = CalculateDirectionToThrowOffBatteryThief( pilot, titan )
+
+ ThrowRiderOff( pilot, titan, direction ) //This signals RodeoOver
+}
+*/
+
+void function ClearParentOnDeathOrDestroy( entity clearParentEntity, entity onDeathOrDestroyEntity )
+{
+ Assert( IsValid( clearParentEntity ) )
+ Assert( IsAlive( clearParentEntity ) )
+
+ Assert( IsValid( onDeathOrDestroyEntity ) )
+ Assert( IsAlive( onDeathOrDestroyEntity ) )
+
+ OnThreadEnd(
+ function() : ( clearParentEntity, onDeathOrDestroyEntity )
+ {
+ if ( IsValid( clearParentEntity ) )
+ {
+ clearParentEntity.ClearParent()
+
+ if ( IsValid( onDeathOrDestroyEntity ) )
+ {
+ PutEntityInSafeSpot( clearParentEntity, onDeathOrDestroyEntity, null, onDeathOrDestroyEntity.GetOrigin(), clearParentEntity.GetOrigin() )
+ }
+ }
+ }
+ )
+
+ onDeathOrDestroyEntity.EndSignal( "OnDeath" )
+ onDeathOrDestroyEntity.WaitSignal( "OnDestroy" )
+}
+
+void function PredatorMeleeKilledRagdoll( entity titan )
+{
+ titan.e.forceRagdollDeath = true
+}
+
+void function MeleePinkMistFakeBody( entity target )
+{
+ target.Dissolve( ENTITY_DISSOLVE_PINKMIST, < 0, 0, 0 >, 0 )
+}
+
+void function TitanLostArm( entity titan )
+{
+ table e = expect table( GetOptionalAnimEventVar( titan, "lost_arm" ) )
+
+ e.lostArm = true
+}
+
+void function MeleeKilledRagdoll( entity titan )
+{
+ entity attacker = expect entity( GetOptionalAnimEventVar( titan, "melee_killed_ragdoll" ) )
+
+ if ( !IsValid( attacker ) )
+ return
+ titan.Die( attacker, attacker, { scriptType = DF_MELEE, damageSourceId = eDamageSourceId.titan_execution } )
+ titan.SetContinueAnimatingAfterRagdoll( true )
+ titan.BecomeRagdoll( < 0, 0, 0 >, false )
+}
+
+void function OnNPCTitanDeath( entity titan, var damageInfo ) //Debug function, for bug 129802
+{
+ PrintFunc()
+}
+
+void function OnNPCTitanSignalDeath( entity titan ) //Debug function, for bug 129802
+{
+ PrintFunc()
+
+ titan.WaitSignal( "OnDeath" )
+
+ printt( "titan : " + titan + " recieved OnDeath Signal in OnNPCTitanSignalDeath" )
+}
+
+
+void function Northstar_Rocket_Pod_Left( entity guy )
+{
+ entity victim = expect entity( GetOptionalAnimEventVar( guy, "rocket_pod_fire_left" ) )
+ Rocket_Pod( guy, "muzzle_flash", victim )
+}
+
+void function Northstar_Rocket_Pod_Right( entity guy )
+{
+ entity victim = expect entity( GetOptionalAnimEventVar( guy, "rocket_pod_fire_right" ) )
+ Rocket_Pod( guy, "muzzle_flash2", victim )
+}
+
+void function Rocket_Pod( entity guy, string tag, entity victim )
+{
+ entity oldOffhandWeapon = guy.GetOffhandWeapon( 0 )
+ guy.TakeOffhandWeapon( 0 )
+ guy.GiveOffhandWeapon( "mp_titanweapon_salvo_rockets", 0, [ "northstar_prime_execution" ] )
+
+ entity newOffhandWeapon = guy.GetOffhandWeapon( 0 )
+ int attachID = guy.LookupAttachment( tag )
+ vector angles = guy.GetAttachmentAngles( attachID )
+ WeaponPrimaryAttackParams params
+ params.pos = guy.GetAttachmentOrigin( attachID )
+ params.dir = AnglesToForward( angles )
+
+ if ( IsAlive( victim ) && victim.IsTitan() )
+ {
+ vector victimTagPos = victim.GetAttachmentOrigin( victim.LookupAttachment( "CHESTFOCUS" ) ) + RandomVec( 30 )
+ params.dir = Normalize( victimTagPos - params.pos )
+ StartParticleEffectInWorld(GetParticleSystemIndex( $"P_muzzleflash_predator" ), params.pos, VectorToAngles( params.dir ) )
+ }
+
+ // DebugDrawSphere(params.pos, 10, 255,0,0, true, 1.0 )
+ // DebugDrawLine( params.pos, params.pos + params.dir*200, 255,0,0, true, 1.0 )
+
+ thread OnWeaponPrimaryAttack_titanweapon_salvo_rockets( newOffhandWeapon, params )
+
+ guy.TakeOffhandWeapon( 0 )
+
+ if ( oldOffhandWeapon )
+ guy.GiveOffhandWeapon( oldOffhandWeapon.GetWeaponClassName(), 0, oldOffhandWeapon.GetMods() )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_ai_mp.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_ai_mp.gnut
new file mode 100644
index 000000000..ac0c309b7
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_ai_mp.gnut
@@ -0,0 +1,41 @@
+global function MpInitAILoadouts
+global function SetProficiency
+global function IsAutoPopulateEnabled
+global function SPMP_UpdateNPCProficiency
+global function SPMP_Callback_ForceAIMissPlayer
+
+void function MpInitAILoadouts()
+{
+
+}
+
+void function SetProficiency( entity soldier )
+{
+
+}
+
+bool function IsAutoPopulateEnabled( var team = null )
+{
+ if ( IsNPCSpawningEnabled() == false )
+ return false
+
+ if ( Flag( "disable_npcs" ) )
+ return false
+
+ if ( team == TEAM_MILITIA && Flag( "Disable_MILITIA" ) )
+ return false
+ if ( team == TEAM_IMC && Flag( "Disable_IMC" ) )
+ return false
+
+ return true
+}
+
+void function SPMP_UpdateNPCProficiency(entity ent)
+{
+
+}
+
+bool function SPMP_Callback_ForceAIMissPlayer(entity npc, entity player)
+{
+ return true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_ai_mp.nut b/Northstar.CustomServers/scripts/vscripts/mp/_ai_mp.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_ai_mp.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_ai_superspectre.nut b/Northstar.CustomServers/scripts/vscripts/mp/_ai_superspectre.nut
new file mode 100644
index 000000000..68e888f41
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_ai_superspectre.nut
@@ -0,0 +1,736 @@
+untyped
+
+global function AiSuperspectre_Init
+
+global function SuperSpectre_OnGroundSlamImpact
+global function SuperSpectre_OnGroundLandImpact
+global function SuperSpectreThink
+global function SuperSpectreOnLeeched
+global function SuperSpectre_WarpFall
+global function CreateExplosionInflictor
+global function FragDroneDeplyAnimation
+global function ForceTickLaunch
+
+global function Reaper_LaunchFragDrone_Think
+global function ReaperMinionLauncherThink
+
+//==============================================================
+// AI Super Spectre
+//
+// Super Spectre keeps an array of the minions it spawned.
+// Each of those minions has a reference back to it's "master."
+//==============================================================
+const FRAG_DRONE_BATCH_COUNT = 10
+const FRAG_DRONE_IN_FRONT_COUNT = 2
+const FRAG_DRONE_MIN_LAUNCH_COUNT = 4
+const FRAG_DRONE_LAUNCH_INTIAL_DELAY_MIN = 10
+const FRAG_DRONE_LAUNCH_INTIAL_DELAY_MAX = 20
+const FRAG_DRONE_LAUNCH_INTERVAL = 40
+const SPAWN_ENEMY_TOO_CLOSE_RANGE_SQR = 1048576 // Don't spawn guys if the target enemy is closer than this range (1024^2).
+const SPAWN_HIDDEN_ENEMY_WITHIN_RANGE_SQR = 1048576 // If the enemy can't bee seen, and they are within in this range (1024^2), spawn dudes to find him.
+const SPAWN_ENEMY_ABOVE_HEIGHT = 128 // If the enemy is at least this high up, then spawn dudes to find him.
+const SPAWN_FUSE_TIME = 2.0 // How long after being fired before the spawner explodes and spawns a spectre.
+const SPAWN_PROJECTILE_AIR_TIME = 3.0 // How long the spawn project will be in the air before hitting the ground.
+const SPECTRE_EXPLOSION_DMG_MULTIPLIER = 1.2 // +20%
+const DEV_DEBUG_PRINTS = false
+
+struct
+{
+ int activeMinions_GlobalArrayIdx = -1
+} file
+
+function AiSuperspectre_Init()
+{
+ PrecacheParticleSystem( $"P_sup_spectre_death" )
+ PrecacheParticleSystem( $"P_sup_spectre_death_nuke" )
+ PrecacheParticleSystem( $"P_xo_damage_fire_2" )
+ PrecacheParticleSystem( $"P_sup_spec_dam_vent_1" )
+ PrecacheParticleSystem( $"P_sup_spec_dam_vent_2" )
+ PrecacheParticleSystem( $"P_sup_spectre_dam_1" )
+ PrecacheParticleSystem( $"P_sup_spectre_dam_2" )
+ PrecacheParticleSystem( $"drone_dam_smoke_2" )
+ PrecacheParticleSystem( $"P_wpn_muzzleflash_sspectre" )
+
+ PrecacheImpactEffectTable( "superSpectre_groundSlam_impact" )
+ PrecacheImpactEffectTable( "superSpectre_megajump_land" )
+
+ RegisterSignal( "SuperSpectre_OnGroundSlamImpact" )
+ RegisterSignal( "SuperSpectre_OnGroundLandImpact" )
+ RegisterSignal( "SuperSpectreThinkRunning" )
+ RegisterSignal( "OnNukeBreakingDamage" ) // enough damage to break out or skip nuke
+ RegisterSignal( "death_explosion" )
+ RegisterSignal( "WarpfallComplete" )
+ RegisterSignal( "BeginLaunchAttack" )
+
+ AddDeathCallback( "npc_super_spectre", SuperSpectreDeath )
+ AddDamageCallback( "npc_super_spectre", SuperSpectre_OnDamage )
+ //AddPostDamageCallback( "npc_super_spectre", SuperSpectre_PostDamage )
+
+ file.activeMinions_GlobalArrayIdx = CreateScriptManagedEntArray()
+}
+
+void function SuperSpectre_OnDamage( entity npc, var damageInfo )
+{
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( damageSourceId == eDamageSourceId.suicideSpectreAoE )
+ {
+ // super spectre takes reduced damage from suicide spectres
+ DamageInfo_ScaleDamage( damageInfo, 0.666 )
+ }
+}
+
+void function SuperSpectre_PostDamage( entity npc, var damageInfo )
+{
+ float switchRatio = 0.33
+ float ratio = HealthRatio( npc )
+ if ( ratio < switchRatio )
+ return
+ float newRatio = ( npc.GetHealth() - DamageInfo_GetDamage( damageInfo ) ) / npc.GetMaxHealth()
+ if ( newRatio >= switchRatio )
+ return
+
+ // destroy body groups
+ int bodygroup
+ bodygroup = npc.FindBodyGroup( "lowerbody" )
+ npc.SetBodygroup( bodygroup, 1 )
+ bodygroup = npc.FindBodyGroup( "upperbody" )
+ npc.SetBodygroup( bodygroup, 1 )
+}
+
+void function SuperSpectreDeath( entity npc, var damageInfo )
+{
+ thread DoSuperSpectreDeath( npc, damageInfo )
+}
+
+void function SuperSpectreNukes( entity npc, entity attacker )
+{
+ npc.EndSignal( "OnDestroy" )
+ vector origin = npc.GetWorldSpaceCenter()
+ EmitSoundAtPosition( npc.GetTeam(), origin, "ai_reaper_nukedestruct_explo_3p" )
+ PlayFX( $"P_sup_spectre_death_nuke", origin, npc.GetAngles() )
+
+ thread SuperSpectreNukeDamage( npc.GetTeam(), origin, attacker )
+ WaitFrame() // so effect has time to grow and cover the swap to gibs
+ npc.Gib( <0,0,100> )
+}
+
+void function DoSuperSpectreDeath( entity npc, var damageInfo )
+{
+ // destroyed?
+ if ( !IsValid( npc ) )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ const int SUPER_SPECTRE_NUKE_DEATH_THRESHOLD = 300
+
+ bool giveBattery = ( npc.ai.shouldDropBattery && IsSingleplayer() )
+
+ if ( !ShouldNukeOnDeath( npc ) || !npc.IsOnGround() || !npc.IsInterruptable() || DamageInfo_GetDamage( damageInfo ) > SUPER_SPECTRE_NUKE_DEATH_THRESHOLD || ( IsValid( attacker ) && attacker.IsTitan() ) )
+ {
+ // just boom
+ vector origin = npc.GetWorldSpaceCenter()
+ EmitSoundAtPosition( npc.GetTeam(), origin, "ai_reaper_explo_3p" )
+ npc.Gib( DamageInfo_GetDamageForce( damageInfo ) )
+ if ( giveBattery )
+ SpawnTitanBatteryOnDeath( npc, null )
+
+ return
+ }
+
+ npc.ai.killShotSound = false
+ npc.EndSignal( "OnDestroy" )
+
+ entity nukeFXInfoTarget = CreateEntity( "info_target" )
+ nukeFXInfoTarget.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( nukeFXInfoTarget )
+
+ nukeFXInfoTarget.SetParent( npc, "HIJACK" )
+
+ EmitSoundOnEntity( nukeFXInfoTarget, "ai_reaper_nukedestruct_warmup_3p" )
+
+ AI_CreateDangerousArea_DamageDef( damagedef_reaper_nuke, nukeFXInfoTarget, TEAM_INVALID, true, true )
+
+ OnThreadEnd(
+ function() : ( nukeFXInfoTarget, npc, attacker, giveBattery )
+ {
+ if ( IsValid( nukeFXInfoTarget ) )
+ {
+ StopSoundOnEntity( nukeFXInfoTarget, "ai_reaper_nukedestruct_warmup_3p" )
+ nukeFXInfoTarget.Destroy()
+ }
+
+
+ if ( IsValid( npc ) )
+ {
+ thread SuperSpectreNukes( npc, attacker )
+ if ( giveBattery )
+ {
+ SpawnTitanBatteryOnDeath( npc, null )
+ }
+ }
+ }
+ )
+
+ //int bodygroup = npc.FindBodyGroup( "upperbody" )
+ //npc.SetBodygroup( bodygroup, 1 )
+
+ // TODO: Add death sound
+
+ WaitSignalOnDeadEnt( npc, "death_explosion" )
+}
+
+entity function CreateExplosionInflictor( vector origin )
+{
+ entity inflictor = CreateEntity( "script_ref" )
+ inflictor.SetOrigin( origin )
+ inflictor.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( inflictor )
+ return inflictor
+}
+
+void function SuperSpectreNukeDamage( int team, vector origin, entity attacker )
+{
+ // all damage must have an inflictor currently
+ entity inflictor = CreateExplosionInflictor( origin )
+
+ OnThreadEnd(
+ function() : ( inflictor )
+ {
+ if ( IsValid( inflictor ) )
+ inflictor.Destroy()
+ }
+ )
+
+ int explosions = 8
+ float time = 1.0
+
+ for ( int i = 0; i < explosions; i++ )
+ {
+ entity explosionOwner
+ if ( IsValid( attacker ) )
+ explosionOwner = attacker
+ else
+ explosionOwner = GetTeamEnt( team )
+
+ RadiusDamage_DamageDefSimple(
+ damagedef_reaper_nuke,
+ origin, // origin
+ explosionOwner, // owner
+ inflictor, // inflictor
+ 0 ) // dist from attacker
+
+ wait RandomFloatRange( 0.01, 0.21 )
+ }
+}
+
+void function SuperSpectre_OnGroundLandImpact( entity npc )
+{
+ PlayImpactFXTable( npc.GetOrigin(), npc, "superSpectre_megajump_land", SF_ENVEXPLOSION_INCLUDE_ENTITIES )
+}
+
+
+void function SuperSpectre_OnGroundSlamImpact( entity npc )
+{
+ PlayGroundSlamFX( npc )
+}
+
+
+function PlayGroundSlamFX( entity npc )
+{
+ int attachment = npc.LookupAttachment( "muzzle_flash" )
+ vector origin = npc.GetAttachmentOrigin( attachment )
+ PlayImpactFXTable( origin, npc, "superSpectre_groundSlam_impact", SF_ENVEXPLOSION_INCLUDE_ENTITIES )
+}
+
+
+bool function EnemyWithinRangeSqr( entity npc, entity enemy, float range )
+{
+ vector pos = npc.GetOrigin()
+ vector enemyPos = enemy.GetOrigin()
+ float distance = DistanceSqr( pos, enemyPos )
+
+ return distance <= range
+}
+
+bool function ShouldLaunchFragDrones( entity npc, int activeMinions_EntArrayID )
+{
+// printt( "active " + GetScriptManagedEntArrayLen( activeMinions_EntArrayID ) )
+ if ( !npc.ai.superSpectreEnableFragDrones )
+ return false
+
+ // check global minions
+ if ( GetScriptManagedEntArrayLen( file.activeMinions_GlobalArrayIdx ) > 5 )
+ return false
+
+ // only launch if all minions are dead
+ if ( GetScriptManagedEntArrayLen( activeMinions_EntArrayID ) > 5 )
+ return false
+
+ entity enemy = npc.GetEnemy()
+
+ // Only spawn dudes if we have an enemy
+ if ( !IsValid( enemy ) )
+ return false
+
+ vector ornull lkp = npc.LastKnownPosition( enemy )
+ if ( lkp == null )
+ return false
+
+ expect vector( lkp )
+
+ // Don't spawn if the enemy is too far away
+ if ( Distance( npc.GetOrigin(), lkp ) > 1500 )
+ return false
+
+ return true
+}
+
+function SuperSpectreOnLeeched( npc, player )
+{
+ local maxHealth = npc.GetMaxHealth()
+ npc.SetHealth( maxHealth * 0.5 ) // refill to half health
+}
+
+function SuperSpectreThink( entity npc )
+{
+ npc.EndSignal( "OnDeath" )
+
+ int team = npc.GetTeam()
+
+ int activeMinions_EntArrayID = CreateScriptManagedEntArray()
+ if ( npc.kv.squadname == "" )
+ SetSquad( npc, UniqueString( "super_spec_squad" ) )
+
+ npc.ai.superSpectreEnableFragDrones = expect int( npc.Dev_GetAISettingByKeyField( "enable_frag_drones" ) ) == 1
+
+ OnThreadEnd (
+ function() : ( activeMinions_EntArrayID, npc, team )
+ {
+ entity owner
+ if ( IsValid( npc ) )
+ owner = npc
+
+ foreach ( minion in GetScriptManagedEntArray( activeMinions_EntArrayID ) )
+ {
+ // Self destruct the suicide spectres if applicable
+ if ( minion.GetClassName() != "npc_frag_drone" )
+ continue
+
+ if ( minion.ai.suicideSpectreExplodingAttacker == null )
+ minion.TakeDamage( minion.GetHealth(), owner, owner, { scriptType = DF_DOOMED_HEALTH_LOSS, damageSourceId = eDamageSourceId.mp_weapon_super_spectre } )
+ }
+ }
+ )
+
+ wait RandomFloatRange( FRAG_DRONE_LAUNCH_INTIAL_DELAY_MIN, FRAG_DRONE_LAUNCH_INTIAL_DELAY_MAX )
+
+ npc.kv.doScheduleChangeSignal = true
+
+ while ( 1 )
+ {
+ if ( ShouldLaunchFragDrones( npc, activeMinions_EntArrayID ) )
+ waitthread SuperSpectre_LaunchFragDrone_Think( npc, activeMinions_EntArrayID )
+
+ wait FRAG_DRONE_LAUNCH_INTERVAL
+ }
+}
+
+void function SuperSpectre_LaunchFragDrone_Think( entity npc, int activeMinions_EntArrayID )
+{
+ array<vector> targetOrigins = GetFragDroneTargetOrigins( npc, npc.GetOrigin(), 200, 2000, 64, FRAG_DRONE_BATCH_COUNT )
+
+ if ( targetOrigins.len() < FRAG_DRONE_MIN_LAUNCH_COUNT )
+ return
+
+ npc.RequestSpecialRangeAttack( targetOrigins.len() + FRAG_DRONE_IN_FRONT_COUNT )
+
+ // wait for first attack signal
+ npc.WaitSignal( "OnSpecialAttack" )
+ npc.EndSignal( "OnDeath" )
+ npc.EndSignal( "OnScheduleChange" ) // kv.doScheduleChangeSignal = true
+
+ // drop a few in front of enemy view
+ entity enemy = npc.GetEnemy()
+ if ( enemy )
+ {
+ vector searchOrigin = enemy.GetOrigin() + ( enemy.GetForwardVector() * 400 )
+ array<vector> frontOfEnemyOrigins = GetFragDroneTargetOrigins( npc, searchOrigin, 0, 500, 16, FRAG_DRONE_IN_FRONT_COUNT )
+
+ foreach ( targetOrigin in frontOfEnemyOrigins )
+ {
+ thread LaunchSpawnerProjectile( npc, targetOrigin, activeMinions_EntArrayID )
+ //DebugDrawBox( targetOrigin, Vector(-10, -10, 0), Vector(10, 10, 10), 255, 0, 0, 255, 5 )
+ npc.WaitSignal( "OnSpecialAttack" )
+ }
+ }
+
+ // drop rest in pre-searched spots
+ foreach ( targetOrigin in targetOrigins )
+ {
+ thread LaunchSpawnerProjectile( npc, targetOrigin, activeMinions_EntArrayID )
+ npc.WaitSignal( "OnSpecialAttack" )
+ }
+}
+
+void function ReaperMinionLauncherThink( entity reaper )
+{
+ if ( GetBugReproNum() != 221936 )
+ reaper.kv.squadname = ""
+
+ StationaryAIPosition launchPos = GetClosestAvailableStationaryPosition( reaper.GetOrigin(), 8000, eStationaryAIPositionTypes.LAUNCHER_REAPER )
+ launchPos.inUse = true
+
+ OnThreadEnd(
+ function () : ( launchPos )
+ {
+ launchPos.inUse = false
+ }
+ )
+
+ reaper.EndSignal( "OnDeath" )
+ reaper.AssaultSetFightRadius( 96 )
+ reaper.AssaultSetGoalRadius( reaper.GetMinGoalRadius() )
+
+ while ( true )
+ {
+ WaitFrame()
+
+ if ( Distance( reaper.GetOrigin(), launchPos.origin ) > 96 )
+ {
+ printt( reaper," ASSAULT:", launchPos.origin, Distance( reaper.GetOrigin(), launchPos.origin ) )
+ reaper.AssaultPoint( launchPos.origin )
+ table signalData = WaitSignal( reaper, "OnFinishedAssault", "OnEnterGoalRadius", "OnFailedToPath" )
+ printt( reaper," END ASSAULT:", launchPos.origin, signalData.signal )
+ if ( signalData.signal == "OnFailedToPath" )
+ continue
+ }
+
+ printt( reaper," LAUNCH:", launchPos.origin )
+ waitthread Reaper_LaunchFragDrone_Think( reaper, "npc_frag_drone_fd" )
+ printt( reaper," END LAUNCH:", launchPos.origin )
+ while ( GetScriptManagedEntArrayLen( reaper.ai.activeMinionEntArrayID ) > 2 )
+ WaitFrame()
+ }
+}
+
+void function Reaper_LaunchFragDrone_Think( entity reaper, string fragDroneSettings = "" )
+{
+ if ( reaper.ai.activeMinionEntArrayID < 0 )
+ reaper.ai.activeMinionEntArrayID = CreateScriptManagedEntArray()
+
+ int activeMinions_EntArrayID = reaper.ai.activeMinionEntArrayID
+
+ const int MAX_TICKS = 4
+
+ int currentMinions = GetScriptManagedEntArray( reaper.ai.activeMinionEntArrayID ).len()
+ int minionsToSpawn = MAX_TICKS - currentMinions
+
+ if ( minionsToSpawn <= 0 )
+ return
+
+ array<vector> targetOrigins = GetFragDroneTargetOrigins( reaper, reaper.GetOrigin(), 200, 2000, 64, MAX_TICKS )
+
+ if ( targetOrigins.len() < minionsToSpawn )
+ return
+
+ if ( IsAlive( reaper.GetEnemy() ) && ( reaper.GetEnemy().IsPlayer() || reaper.GetEnemy().IsNPC() ) && reaper.CanSee( reaper.GetEnemy() ) )
+ return
+
+ OnThreadEnd(
+ function() : ( reaper )
+ {
+ if ( IsValid( reaper ) )
+ {
+ reaper.Anim_Stop()
+ }
+ }
+ )
+
+ printt( reaper, " BEGIN LAUNCHING: ", minionsToSpawn, reaper.GetCurScheduleName() )
+
+ reaper.EndSignal( "OnDeath" )
+
+ while ( !reaper.IsInterruptable() )
+ WaitFrame()
+
+ waitthread PlayAnim( reaper, "sspec_idle_to_speclaunch" )
+
+ while ( minionsToSpawn > 0 )
+ {
+ // drop rest in pre-searched spots
+ foreach ( targetOrigin in targetOrigins )
+ {
+ if ( minionsToSpawn <= 0 )
+ break
+
+ printt( reaper, " LAUNCHING: ", minionsToSpawn )
+ thread LaunchSpawnerProjectile( reaper, targetOrigin, activeMinions_EntArrayID, fragDroneSettings )
+ minionsToSpawn--
+
+ if ( minionsToSpawn <= 0 )
+ break
+
+ waitthread PlayAnim( reaper, "sspec_speclaunch_fire" )
+ }
+ }
+
+ waitthread PlayAnim( reaper, "sspec_speclaunch_to_idle" )
+}
+
+
+
+array<vector> function GetFragDroneTargetOrigins( entity npc, vector origin, float minRadius, float maxRadius, int randomCount, int desiredCount )
+{
+ array<vector> targetOrigins
+/*
+ vector angles = npc.GetAngles()
+ angles.x = 0
+ angles.z = 0
+
+ vector origin = npc.GetOrigin() + Vector( 0, 0, 1 )
+ float arc = 0
+ float dist = 200
+
+ for ( ;; )
+ {
+ if ( dist > 2000 || targetOrigins.len() >= 12 )
+ break
+
+ angles = AnglesCompose( angles, <0,arc,0> )
+ arc += 35
+ arc %= 360
+ dist += 200
+
+ vector ornull tryOrigin = TryCreateFragDroneLaunchTrajectory( npc, origin, angles, dist )
+ if ( tryOrigin == null )
+ continue
+ expect vector( tryOrigin )
+ targetOrigins.append( tryOrigin )
+ }
+*/
+ float traceFrac = TraceLineSimple( origin, origin + <0, 0, 200>, npc )
+ if ( traceFrac < 1 )
+ return targetOrigins;
+
+ array< vector > randomSpots = NavMesh_RandomPositions_LargeArea( origin, HULL_HUMAN, randomCount, minRadius, maxRadius )
+
+ int numFragDrones = 0
+ foreach( spot in randomSpots )
+ {
+ targetOrigins.append( spot )
+ numFragDrones++
+ if ( numFragDrones == desiredCount )
+ break
+ }
+
+ return targetOrigins
+}
+
+vector ornull function TryCreateFragDroneLaunchTrajectory( entity npc, vector origin, vector angles, float dist )
+{
+ vector forward = AnglesToForward( angles )
+ vector targetOrigin = origin + forward * dist
+
+ vector ornull clampedPos = NavMesh_ClampPointForHullWithExtents( targetOrigin, HULL_HUMAN, < 300, 300, 100 > )
+
+ if ( clampedPos == null )
+ return null
+
+ vector vel = GetVelocityForDestOverTime( origin, expect vector( clampedPos ), SPAWN_PROJECTILE_AIR_TIME )
+ float traceFrac = TraceLineSimple( origin, origin + vel, npc )
+ //DebugDrawLine( origin, origin + vel, 255, 0, 0, true, 5.0 )
+ if ( traceFrac >= 0.5 )
+ return clampedPos
+ return null
+}
+
+void function FragDroneDeplyAnimation( entity drone, float minDelay = 0.5, float maxDelay = 2.5 )
+{
+ Assert( !drone.ai.fragDroneArmed, "Armed drone was told to play can animation. Spawn drone with CreateFragDroneCan()" )
+ drone.EndSignal( "OnDeath" )
+
+ drone.SetInvulnerable()
+ OnThreadEnd(
+ function() : ( drone )
+ {
+ drone.ClearInvulnerable()
+ }
+ )
+
+ drone.Anim_ScriptedPlay( "sd_closed_idle" )
+ wait RandomFloatRange( minDelay, maxDelay )
+
+ #if MP
+ while ( !drone.IsInterruptable() )
+ {
+ WaitFrame()
+ }
+ #endif
+
+ drone.Anim_ScriptedPlay( "sd_closed_to_open" )
+
+ // Wait for P_drone_frag_open_flicker FX to play inside sd_closed_to_open
+ wait 0.6
+}
+
+void function LaunchSpawnerProjectile( entity npc, vector targetOrigin, int activeMinions_EntArrayID, string droneSettings = "" )
+{
+ //npc.EndSignal( "OnDeath" )
+
+ entity weapon = npc.GetOffhandWeapon( 0 )
+
+ if ( !IsValid( weapon ) )
+ return
+
+ int id = npc.LookupAttachment( "launch" )
+ vector launchPos = npc.GetAttachmentOrigin( id )
+ int team = npc.GetTeam()
+ vector launchAngles = npc.GetAngles()
+ string squadname = expect string( npc.kv.squadname )
+ vector vel = GetVelocityForDestOverTime( launchPos, targetOrigin, SPAWN_PROJECTILE_AIR_TIME )
+
+// DebugDrawLine( npc.GetOrigin() + <3,3,3>, launchPos + <3,3,3>, 255, 0, 0, true, 5.0 )
+ float armTime = SPAWN_PROJECTILE_AIR_TIME + RandomFloatRange( 1.0, 2.5 )
+ entity nade = weapon.FireWeaponGrenade( launchPos, vel, <200,0,0>, armTime, damageTypes.dissolve, damageTypes.explosive, PROJECTILE_NOT_PREDICTED, true, true )
+
+ AddToScriptManagedEntArray( activeMinions_EntArrayID, nade )
+ AddToScriptManagedEntArray( file.activeMinions_GlobalArrayIdx, nade )
+
+ nade.SetOwner( npc )
+ nade.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( nade, team, activeMinions_EntArrayID, squadname, droneSettings )
+ {
+ vector origin = nade.GetOrigin()
+ vector angles = nade.GetAngles()
+
+ vector ornull clampedPos = NavMesh_ClampPointForHullWithExtents( origin, HULL_HUMAN, < 100, 100, 100 > )
+ if ( clampedPos == null )
+ return
+
+ entity drone = CreateFragDroneCan( team, expect vector( clampedPos ), < 0, angles.y, 0 > )
+ SetSpawnOption_SquadName( drone, squadname )
+ if ( droneSettings != "" )
+ {
+ SetSpawnOption_AISettings( drone, droneSettings )
+ }
+ drone.kv.spawnflags = SF_NPC_ALLOW_SPAWN_SOLID // clamped to navmesh no need to check solid
+ DispatchSpawn( drone )
+
+ thread FragDroneDeplyAnimation( drone )
+
+ AddToScriptManagedEntArray( activeMinions_EntArrayID, drone )
+ AddToScriptManagedEntArray( file.activeMinions_GlobalArrayIdx, drone )
+ }
+ )
+
+ Grenade_Init( nade, weapon )
+
+ EmitSoundOnEntity( npc, "SpectreLauncher_AI_WpnFire" )
+ WaitForever()
+
+// wait SPAWN_PROJECTILE_AIR_TIME + SPAWN_FUSE_TIME
+}
+
+
+// Seriously don't use this unless absolutely necessary! Used for scripted moment in Reapertown.
+// Bypasses all of the tick launch rules and sends a request for launching ticks to code immediately.
+void function ForceTickLaunch( entity npc )
+{
+ SuperSpectre_LaunchFragDrone_Think( npc, file.activeMinions_GlobalArrayIdx )
+}
+
+
+/************************************************************************************************\
+######## ######## ####### ######## ####### ######## ## ## ######## ########
+## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ## ## #### ## ## ##
+######## ######## ## ## ## ## ## ## ## ######## ######
+## ## ## ## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ## ## ## ##
+## ## ## ####### ## ####### ## ## ## ########
+\************************************************************************************************/
+
+
+function SuperSpectre_WarpFall( entity ai )
+{
+ ai.EndSignal( "OnDestroy" )
+
+ vector origin = ai.GetOrigin()
+ entity mover = CreateOwnedScriptMover( ai )
+ ai.SetParent( mover, "", false, 0 )
+ ai.Hide()
+ ai.SetEfficientMode( true )
+ ai.SetInvulnerable()
+
+ WaitFrame() // give AI time to hide before moving
+
+ vector warpPos = origin + < 0, 0, 1000 >
+ mover.SetOrigin( warpPos )
+
+ #if GRUNTCHATTER_ENABLED
+ GruntChatter_TryIncomingSpawn( ai, origin )
+ #endif
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "Titan_1P_Warpfall_Start" )
+
+ local e = {}
+ e.warpfx <- PlayFX( TURBO_WARP_FX, warpPos + < 0, 0, -104 >, mover.GetAngles() )
+ e.smokeFx <- null
+
+ OnThreadEnd(
+ function() : ( e, mover, ai )
+ {
+ if ( IsAlive( ai ) )
+ {
+ ai.ClearParent()
+ ai.SetVelocity( <0,0,0> )
+ ai.Signal( "WarpfallComplete" )
+ }
+ if ( IsValid( e.warpfx ) )
+ e.warpfx.Destroy()
+ if ( IsValid( e.smokeFx ) )
+ e.smokeFx.Destroy()
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ }
+ )
+ wait 0.5
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, "Titan_3P_Warpfall_WarpToLanding" )
+
+ wait 0.4
+
+ ai.Show()
+
+ e.smokeFx = PlayFXOnEntity( TURBO_WARP_COMPANY, ai, "", <0.0, 0.0, 152.0> )
+
+ local time = 0.2
+ mover.MoveTo( origin, time, 0, 0 )
+ wait time
+
+ ai.SetEfficientMode( false )
+ ai.ClearInvulnerable()
+
+ e.smokeFx.Destroy()
+ PlayFX( $"droppod_impact", origin )
+
+ Explosion_DamageDefSimple(
+ damagedef_reaper_fall,
+ origin,
+ ai, // attacker
+ ai, // inflictor
+ origin )
+
+ wait 0.1
+}
+
+bool function ShouldNukeOnDeath( entity ent )
+{
+ if ( IsMultiplayer() )
+ return false
+
+ return ent.Dev_GetAISettingByKeyField( "nuke_on_death" ) == 1
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_base_gametype.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_base_gametype.gnut
new file mode 100644
index 000000000..a4c6e187b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_base_gametype.gnut
@@ -0,0 +1,2179 @@
+untyped
+
+globalize_all_functions
+
+//********************************************************************************************
+// Base Gametype
+//********************************************************************************************
+const DEATH_CHAT_DELAY = 0.3
+
+global struct OutOfBoundsDataStruct //Have to globalize it because all functions are globalized in this file :/
+{
+ int outOfBoundsTriggersTouched = 0
+ float timeBackInBound = 0
+ float timeLeftBeforeDyingFromOutOfBounds = OUT_OF_BOUNDS_TIME_LIMIT
+}
+
+struct
+{
+ PilotLoadoutDef& playbackBotLoadout
+ array<entity> outOfBoundsTriggers = []
+ array<entity> hurtTriggers = []
+ bool functionref( entity, entity, var ) isProtectedFromFriendlyFire
+ table< entity, OutOfBoundsDataStruct > outOfBoundsTable
+} file
+
+function BaseGametype_Init()
+{
+ FlagInit( "APlayerHasSpawned" )
+ FlagInit( "PilotBot" )
+
+ if ( !reloadingScripts )
+ {
+ level.gameTypeText <- null
+ level.classTypeText <- null
+
+ level.titanAlwaysAvailableForTeam <- [ 0, 0, 0, 0 ]
+
+ level.missingPlayersTimeout <- null
+
+ CreateTeamColorControlPoints()
+
+ AddClientCommandCallback( "CC_SelectRespawn", ClientCommand_SelectRespawn )
+ AddClientCommandCallback( "CC_RespawnPlayer", ClientCommand_RespawnPlayer )
+
+ AddCallback_NPCLeeched( OnNPCLeeched )
+
+ MarkTeamsAsBalanced_Off()
+ }
+
+ if ( IsSingleplayer() )
+ {
+ file.isProtectedFromFriendlyFire = IsProtectedFromFriendlyFire_SP
+ }
+ else
+ {
+ file.isProtectedFromFriendlyFire = IsProtectedFromFriendlyFire_MP
+ }
+
+ RegisterSignal( "OnDamageNotify" )
+ RegisterSignal( "OnRespawned" )
+ RegisterSignal( "ChoseToSpawnAsTitan" )
+ RegisterSignal( "OutOfBounds" )
+ RegisterSignal( "BackInBounds" )
+ RegisterSignal( "PlayerKilled" )
+ RegisterSignal( "RespawnMe" )
+ RegisterSignal( "SimulateGameScore" )
+ RegisterSignal( "ObserverThread" )
+ RegisterSignal( "CE_FLAGS_CHANGED" )
+
+ RegisterSignal( "Stop_OnStartTouch_EntityOutOfBounds" )
+ RegisterSignal( "Stop_OnEndTouch_EntityBackInBounds" )
+
+ RegisterSignal( "OnRespawnSelect" )
+
+ AddCallback_EntitiesDidLoad( BaseGametypeEntitiesDidLoad )
+
+ BaseGametype_Init_MPSP()
+
+ AddCallback_OnTitanBecomesPilot( OnTitanBecomesPilot_OutOfBoundsCheck )
+}
+
+void function BaseGametypeEntitiesDidLoad()
+{
+ OutOfBoundsSetup()
+ TriggerHurtSetup()
+}
+
+function CreateTeamColorControlPoints()
+{
+ Assert( !( "fx_CP_color_enemy" in level ) )
+ Assert( !( "fx_CP_color_friendly" in level ) )
+
+ entity enemy = CreateEntity( "info_placement_helper" )
+ SetTargetName( enemy, UniqueString( "teamColorControlPoint_enemy" ) )
+ enemy.kv.start_active = 1
+ DispatchSpawn( enemy )
+
+ enemy.SetOrigin( ENEMY_COLOR_FX )
+ svGlobal.fx_CP_color_enemy = enemy
+
+ entity friendly = CreateEntity( "info_placement_helper" )
+ SetTargetName( friendly, UniqueString( "teamColorControlPoint_friendly" ) )
+ friendly.kv.start_active = 1
+ DispatchSpawn( friendly )
+
+ friendly.SetOrigin( FRIENDLY_COLOR_FX )
+ svGlobal.fx_CP_color_friendly = friendly
+
+ entity neutral = CreateEntity( "info_placement_helper" )
+ SetTargetName( neutral, UniqueString( "teamColorControlPoint_neutral" ) )
+ neutral.kv.start_active = 1
+ DispatchSpawn( neutral )
+
+ neutral.SetOrigin( NEUTRAL_COLOR_FX )
+ svGlobal.fx_CP_color_neutral = neutral
+}
+
+const SOLDIER_SOUND_PAIN = "npc_grunt_pain"
+
+void function CodeCallback_OnPrecache()
+{
+ if ( IsLobby() )
+ return
+
+ Assert( IsSingleplayer() || GAMETYPE in GAMETYPE_TEXT )
+
+ // these should be level specific in SP
+ PrecacheEntity( "npc_soldier" )
+ PrecacheEntity( "turret" )
+
+ PrecacheEntity( "npc_dropship", DROPSHIP_MODEL )
+
+ //Scavenger ore models. Need to precache here instead of in gamemode scripts for vpk builds
+ //Removing for build
+ /*level.scavengerSmallRocks <- [
+ $"models/rocks/rock_01_sandstone.mdl"
+ //$"models/rocks/rock_02_sandstone.mdl"
+ //$"models/rocks/rock_03_sandstone.mdl"
+ //$"models/rocks/single_rock_01.mdl"
+ //$"models/rocks/single_rock_02.mdl"
+ //$"models/rocks/single_rock_03.mdl"
+ //$"models/rocks/single_rock_04.mdl"
+ ]
+
+ level.scavengerLargeRocks <- [
+ $"models/rocks/rock_boulder_large_01.mdl"
+ //$"models/rocks/sandstone_rock01.mdl"
+ //$"models/rocks/sandstone_rock02.mdl"
+ //$"models/rocks/sandstone_rock03.mdl"
+ //$"models/rocks/sandstone_rock04.mdl"
+ //$"models/rocks/sandstone_rock05.mdl"
+ ]
+
+ foreach ( model in level.scavengerSmallRocks )
+ {
+ PrecacheModel( model )
+ }
+
+ foreach ( model in level.scavengerLargeRocks )
+ {
+ PrecacheModel( model )
+ }*/
+
+ if ( !IsMenuLevel() )
+ {
+ InitGameState()
+ SetGameState( eGameState.WaitingForPlayers )
+ }
+
+ level.ui.disableDev = IsMatchmakingServer()
+}
+
+function AddFlinch( entity attackedEnt, damageInfo )
+{
+ Assert( IsValid_ThisFrame( attackedEnt ) )
+
+ //if ( !( "nextFlinchTime" in attackedEnt.s ) )
+ // attackedEnt.s.nextFlinchTime <- 0
+ //if ( Time() < attackedEnt.s.nextFlinchTime )
+ // return
+ //attackedEnt.s.nextFlinchTime = Time() + RandomFloatRange( 2.0, 4.0 )
+
+ vector damageAngles = VectorToAngles( DamageInfo_GetDamageForce( damageInfo ) )
+ vector entAngles = attackedEnt.EyeAngles()
+
+ float damageYaw = (damageAngles.y + 180) - entAngles.y
+
+ damageYaw = AngleNormalize( damageYaw )
+
+ if ( damageYaw < 0 )
+ damageYaw += 360
+
+ if ( damageYaw < 45 )
+ DamageInfo_SetFlinchDirection( damageInfo, FLINCH_DIRECTION_BACKWARDS );
+ else if ( damageYaw < 135 )
+ DamageInfo_SetFlinchDirection( damageInfo, FLINCH_DIRECTION_RIGHT );
+ else if ( damageYaw < 225 )
+ DamageInfo_SetFlinchDirection( damageInfo, FLINCH_DIRECTION_FORWARDS );
+ else if ( damageYaw < 315 )
+ DamageInfo_SetFlinchDirection( damageInfo, FLINCH_DIRECTION_LEFT );
+ else
+ DamageInfo_SetFlinchDirection( damageInfo, FLINCH_DIRECTION_BACKWARDS );
+}
+
+
+bool function IsProtectedFromFriendlyFire_MP( entity attacker, entity ent, var damageInfo )
+{
+ // no suicide protection
+ if ( attacker == ent )
+ return false
+
+ if ( attacker.GetTeam() != ent.GetTeam() )
+ return false
+
+ if ( DamageIgnoresFriendlyFire( damageInfo ) )
+ return false
+
+ if ( ent.GetOwner() != attacker && ent.GetBossPlayer() != attacker )
+ return true
+
+ if ( ent.e.noOwnerFriendlyFire == true )
+ return true
+
+ if ( ent.IsNPC() && ent.ai.preventOwnerDamage )
+ return true
+
+ return false
+}
+
+bool function IsProtectedFromNPCFire( entity attacker, entity ent )
+{
+ if ( attacker == ent )
+ return false
+ if ( attacker.IsNPC() && ent.IsNPC() && ent.ai.invulnerableToNPC == true )
+ return true
+ return false
+}
+
+
+bool function IsProtectedFromFriendlyFire_SP( entity attacker, entity ent, var damageInfo )
+{
+ // no suicide protection
+ if ( attacker == ent )
+ return false
+
+ if ( attacker.GetTeam() == ent.GetTeam() )
+ {
+ if ( attacker.IsNPC() )
+ {
+ // dont titanfall me!
+ if ( ent.IsPlayer() )
+ return true
+
+ // bullets dont damage same team of npcs
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_BULLET )
+ return true
+ }
+ else if ( attacker.IsPlayer() )
+ {
+ if ( ent.IsNPC() )
+ {
+ if ( ent.IsTitan() )
+ return true
+
+ return !ent.AISetting_ShootableByFriendlyPlayer()
+ }
+ if ( ent.IsProjectile() )
+ return false
+ return true
+ }
+
+ if ( DamageIgnoresFriendlyFire( damageInfo ) )
+ return false
+
+ if ( ent.IsNPC() && ent.ai.preventOwnerDamage )
+ {
+ if ( attacker == ent.GetOwner() || attacker == ent.GetBossPlayer() )
+ return true
+ }
+ }
+
+ return false
+}
+
+bool function DamageIgnoresFriendlyFire( damageInfo )
+{
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return true
+
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ switch ( damageSourceID )
+ {
+ case eDamageSourceId.switchback_trap:
+ case eDamageSourceId.suicideSpectreAoE:
+ case eDamageSourceId.mp_titanweapon_stun_laser: // for energy transfer functionality. Preventing FF damage in the callback.
+ case eDamageSourceId.mp_titanability_smoke: // For FD Vanguard Shield Upgrades. Preventing FF damage in the callback.
+ return true
+ }
+
+ return false
+}
+
+bool function ScriptCallback_ShouldEntTakeDamage( entity ent, damageInfo )
+{
+ if ( ent.IsInvulnerable() )
+ return false
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ bool entIsPlayer = ent.IsPlayer()
+
+ if ( !attacker )
+ return false
+
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+
+ if ( attacker == ent || IsValid( inflictor ) && inflictor == ent )
+ {
+ if ( (damageType & DF_NO_SELF_DAMAGE) > 0 )
+ return false
+ }
+
+ if ( file.isProtectedFromFriendlyFire( attacker, ent, damageInfo ) )
+ return false
+
+ if ( IsProtectedFromNPCFire( attacker, ent ) )
+ return false
+
+ if ( !ShouldEntTakeDamage_SPMP( ent, damageInfo ) )
+ return false
+
+ if ( ent.IsTitan() )
+ {
+ const int BULLET_VORTEX_FLAGS = (DF_VORTEX_REFIRE | DF_BULLET)
+ if ( ((damageType & BULLET_VORTEX_FLAGS) == BULLET_VORTEX_FLAGS) && (ent == attacker) )
+ return false // don't let vortex-refiring titan hit themselves with bullet or bullet splash damage
+
+ if ( IsTitanWithinBubbleShield( ent ) && TitanHasBubbleShieldWeapon( ent ) && !(damageType & DF_DOOMED_HEALTH_LOSS) )
+ return false
+ }
+
+ if ( IsTitanCrushDamage( damageInfo ) )
+ {
+ if ( attacker.IsPhaseShifted() )
+ return false
+ }
+
+ if ( (inflictor != null) )
+ {
+ if ( inflictor.IsProjectile() )
+ {
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( attacker == ent )
+ {
+ bool shouldDamageOwner = inflictor.GetProjectileWeaponSettingBool( eWeaponVar.explosion_damages_owner )
+ if ( !shouldDamageOwner )
+ return false
+
+ if ( entIsPlayer )
+ {
+ array<string> mods = inflictor.ProjectileGetMods()
+ foreach ( mod in mods )
+ {
+ if ( mod == "jump_kit" )
+ {
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+ damageAmount *= 0.75
+ DamageInfo_SetDamage( damageInfo, damageAmount )
+ // DamageInfo_SetDamageForce( damageInfo, DamageInfo_GetDamageForce( damageInfo ) * 2.0 )
+ }
+ }
+ }
+ }
+ }
+
+ if ( inflictor.e.onlyDamageEntitiesOnce == true && inflictor.e.damagedEntities.contains( ent ) )
+ return false
+
+ if ( inflictor.e.onlyDamageEntitiesOncePerTick == true )
+ {
+ float currentTime = Time()
+ if ( currentTime != inflictor.e.lastDamageTickTime )
+ {
+ inflictor.e.damagedEntities.clear()
+ inflictor.e.lastDamageTickTime = currentTime
+ }
+ else if ( inflictor.e.damagedEntities.contains( ent ) )
+ {
+ return false
+ }
+ }
+ }
+
+ if ( ent.IsPlayer() )
+ {
+ return ShouldPlayerTakeDamage( ent, damageInfo )
+ }
+
+ return true
+}
+
+bool function ShouldPlayerTakeDamage( entity player, damageInfo )
+{
+ if ( player.IsGodMode() )
+ return false
+
+ if ( player.IsPhaseShifted()
+ && !(DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS)
+ && !IsDamageFromDamageTrigger( damageInfo ) )
+ return false
+
+ if ( player.IsInvulnerable() )
+ return false
+
+ if ( player.IsTitan() )
+ {
+ return true
+ }
+ else
+ {
+ //Rodeo cases
+ entity titanSoul = player.GetTitanSoulBeingRodeoed()
+ if ( IsValid( titanSoul ) )
+ {
+ entity titan = titanSoul.GetTitan()
+ //Stop being stepped on by the guy you are rodeoing
+ if ( IsTitanCrushDamage( damageInfo ) && ( titan == DamageInfo_GetAttacker( damageInfo ) ) )
+ return false
+ else
+ return true
+ }
+ else
+ {
+ return true
+ }
+ }
+
+ unreachable
+}
+
+
+void function HandlePainSounds( entity ent, var damageInfo )
+{
+ //exit if the thing is dead
+ if ( ent.GetHealth() < DamageInfo_GetDamage( damageInfo ) )
+ return
+
+ PlayPainSounds( ent, damageInfo )
+}
+
+float function GetHeadshotDamageMultiplierFromDamageInfo( var damageInfo )
+{
+ entity weapon = DamageInfo_GetWeapon( damageInfo )
+ if ( weapon )
+ {
+ float result = weapon.GetWeaponSettingFloat( eWeaponVar.damage_headshot_scale )
+ return result
+ }
+
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ if ( inflictor && inflictor.IsProjectile() )
+ {
+ float result = inflictor.GetProjectileWeaponSettingFloat( eWeaponVar.damage_headshot_scale )
+ return result
+ }
+
+ return 1.0
+}
+
+function HandleLocationBasedDamage( entity ent, var damageInfo )
+{
+ // Don't allow non-players to get headshots or any other location bonuses
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) || !attacker.IsPlayer() )
+ return
+
+ bool debugPrints = false
+ int hitGroup = DamageInfo_GetHitGroup( damageInfo )
+
+ if ( debugPrints )
+ {
+ printt( "---------------------" )
+ printt( "LOCATION BASED DAMAGE" )
+ printt( "HIDGROUP ID:", hitGroup )
+ if ( hitGroup == HITGROUP_GENERIC )
+ printt( "HITGROUP: HITGROUP_GENERIC" )
+ else if ( hitGroup == HITGROUP_HEAD )
+ printt( "HITGROUP: HITGROUP_HEAD" )
+ else if ( hitGroup == HITGROUP_CHEST )
+ printt( "HITGROUP: HITGROUP_CHEST" )
+ else if ( hitGroup == HITGROUP_STOMACH )
+ printt( "HITGROUP: HITGROUP_STOMACH" )
+ else if ( hitGroup == HITGROUP_LEFTARM )
+ printt( "HITGROUP: HITGROUP_LEFTARM" )
+ else if ( hitGroup == HITGROUP_RIGHTARM )
+ printt( "HITGROUP: HITGROUP_RIGHTARM" )
+ else if ( hitGroup == HITGROUP_LEFTLEG )
+ printt( "HITGROUP: HITGROUP_LEFTLEG" )
+ else if ( hitGroup == HITGROUP_RIGHTLEG )
+ printt( "HITGROUP: HITGROUP_RIGHTLEG" )
+ else if ( hitGroup == HITGROUP_GEAR )
+ printt( "HITGROUP: HITGROUP_GEAR" )
+ else
+ printt( "HITGROUP: UNKNOWN" )
+ }
+
+ bool isValidHeadShot = IsValidHeadShot( damageInfo, ent )
+ if ( isValidHeadShot )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_HEADSHOT )
+
+ float damageMult_location = 1.0
+
+ var weaponName // TODO: If set to type string, will cause errors because weaponName can be ""
+ if ( DamageInfo_GetWeapon( damageInfo ) )
+ weaponName = DamageInfo_GetWeapon( damageInfo ).GetWeaponClassName()
+ else if ( DamageInfo_GetInflictor( damageInfo ) && (DamageInfo_GetInflictor( damageInfo ) instanceof CProjectile ) )
+ weaponName = DamageInfo_GetInflictor( damageInfo ).ProjectileGetWeaponClassName()
+
+ if ( ent.IsTitan() )
+ {
+ damageMult_location = GetCriticalScaler( ent, damageInfo )
+ }
+ else if ( IsSuperSpectre( ent ) )
+ {
+ if ( CritWeaponInDamageInfo( damageInfo ) && IsCriticalHit( attacker, ent, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) ) )
+ {
+ damageMult_location = GetCriticalScaler( ent, damageInfo )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+ }
+ }
+ else if ( IsStalker( ent ) )
+ {
+ // note: stalker location based damage is done in _ai_stalker.gnut.
+ switch ( hitGroup )
+ {
+ case HITGROUP_GEAR:
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+ break
+ }
+ }
+ else if ( isValidHeadShot )
+ {
+ damageMult_location = GetHeadshotDamageMultiplierFromDamageInfo( damageInfo )
+ }
+
+ // modify damage value based on where we hit
+ if ( damageMult_location != 1.0 )
+ {
+ if ( debugPrints )
+ {
+ printt( "Multiplier:", damageMult_location )
+ printt( "---------------------" )
+ }
+
+ DamageInfo_ScaleDamage( damageInfo, damageMult_location )
+ }
+}
+
+function PlayerDamageFeedback( entity ent, damageInfo )
+{
+// printt( "player damage feedback for " + ent )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ Assert( attacker.IsPlayer() )
+
+ int customDamageType = DamageInfo_GetCustomDamageType( damageInfo )
+
+ if ( IsMaxRangeShot( damageInfo ) )
+ customDamageType = customDamageType | DF_MAX_RANGE
+
+ if ( ent.GetHealth() - DamageInfo_GetDamage( damageInfo ) <= 0 )
+ {
+ if ( !ent.IsNPC() || ent.ai.killShotSound )
+ customDamageType = customDamageType | DF_KILLSHOT
+ }
+
+ attacker.NotifyDidDamage( ent, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamagePosition( damageInfo ), customDamageType, DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageFlags( damageInfo ), DamageInfo_GetHitGroup( damageInfo ), DamageInfo_GetWeapon( damageInfo ), DamageInfo_GetDistFromAttackOrigin( damageInfo ) )
+}
+
+void function UpdateLastDamageTime( entity ent )
+{
+ if ( !ent.IsPlayer() )
+ return
+
+ ent.p.lastDamageTime = Time()
+}
+
+void function PlayerDealtTitanDamage( entity attacker, entity victim, float savedDamage, var damageInfo )
+{
+ if ( attacker != victim )
+ {
+ attacker.p.titanDamageDealt += savedDamage
+
+#if MP
+ UpdateTitanWeaponDamageStat( attacker, savedDamage, damageInfo )
+
+ if ( attacker.IsTitan() )
+ {
+ attacker.p.titanDamageDealt_Stat += savedDamage
+ if ( attacker.p.titanDamageDealt_Stat >= 500 ) // buffer the titan stat damage so that we don't spam damage callbacks
+ {
+ UpdateTitanDamageStat( attacker, attacker.p.titanDamageDealt_Stat, damageInfo )
+ attacker.p.titanDamageDealt_Stat = 0
+ }
+ }
+#endif
+ }
+}
+
+function UpdateAttackerInfo( entity ent, entity attacker, damage )
+{
+ entity attackerPlayer = GetPlayerFromEntity( attacker )
+ if ( !attackerPlayer )
+ return
+
+ // cannot be your own last attacker
+ if ( attackerPlayer == ent )
+ return
+
+ if ( !damage || damage <= 0 )
+ return
+
+ if ( !("attackerInfo" in ent.s) )
+ ent.s.attackerInfo <- {}
+ else if ( ent.GetHealth() == ent.GetMaxHealth() )
+ ent.s.attackerInfo.clear()
+
+ if ( !(attackerPlayer.weakref() in ent.s.attackerInfo ) )
+ ent.s.attackerInfo[attackerPlayer.weakref()] <- 0
+
+ ent.s.attackerInfo[attackerPlayer.weakref()] += damage
+
+ ent.e.lastAttacker = attackerPlayer
+}
+
+entity function GetAttackerPlayerOrBossPlayer( entity attacker )
+{
+ if ( !IsValid( attacker ) )
+ return null
+
+ if ( attacker.IsPlayer() )
+ return attacker
+
+ entity bossPlayer = attacker.GetBossPlayer()
+ if ( !IsValid( bossPlayer ) )
+ return null
+
+ return bossPlayer
+}
+
+entity function GetAttackerOrLastAttacker( entity ent, damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( ShouldGetLastAttacker( ent, attacker ) == false )
+ return attacker
+
+ entity lastAttacker = GetLastAttacker( ent ) //Attacker doesn't work, get last attacker
+
+ if ( IsValid( lastAttacker ) == true )
+ return lastAttacker
+
+ //last attacker doesn't work, get latestAssistingPlayerInfo
+ AssistingPlayerStruct attackerInfo = GetLatestAssistingPlayerInfo( ent )
+ if ( IsValid( attackerInfo.player ) )
+ return attackerInfo.player
+
+ if ( IsValid( attacker ) ) //No Last Attacker and No Lastest Assisting Player, e.g. when you suicide before taking damage. Just return the attacker if valid
+ return attacker
+
+ return null
+}
+
+bool function ShouldGetLastAttacker( entity ent, entity attacker )
+{
+ if ( IsValid( attacker ) == false )
+ return true
+
+ if ( attacker == ent ) //suicide
+ return true
+
+ if ( attacker.IsPlayer() == false && attacker.IsNPC() == false ) //Environmental damage
+ return true
+
+ return false
+}
+
+function ClearLastAttacker( entity ent )
+{
+ ent.e.lastAttacker = null
+}
+
+entity function GetLastAttacker( entity ent )
+{
+ if ( ent.IsTitan() && IsValid( ent.GetTitanSoul() ) ) // JFS: second check is defensive
+ {
+ entity soul = ent.GetTitanSoul()
+ if ( soul.lastAttackInfo && "attacker" in soul.lastAttackInfo && IsValid( soul.lastAttackInfo.attacker ) )
+ return expect entity( soul.lastAttackInfo.attacker )
+ }
+
+ if ( !IsValid( ent.e.lastAttacker ) )
+ return null
+
+ return ent.e.lastAttacker
+}
+
+bool function PlayerOrNPCKilled( entity ent, var damageInfo )
+{
+ bool gamePlayingOrSuddenDeath = GamePlayingOrSuddenDeath() // Storing this off here, the game state can change in the callbacks below which may cause kills to not count
+
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ if ( damageSourceID == eDamageSourceId.round_end )
+ return false
+
+ entity attacker = GetAttackerOrLastAttacker( ent, damageInfo )
+ if ( !IsValid( attacker ) )
+ return false
+
+ if ( ent.IsPlayer() )
+ {
+ LogPlayerMatchStat_Death( ent )
+
+ if ( attacker.IsPlayer() && (attacker != ent) )
+ LogPlayerMatchStat_KilledAPilot( attacker )
+ }
+
+ if ( ent.IsNPC() && !IsValidNPCTarget( ent ) )
+ return false
+
+ if ( !attacker.IsPlayer() )
+ {
+ entity newAttacker = GetPlayerFromEntity( attacker )
+ if ( IsValid( newAttacker ) )
+ attacker = newAttacker
+ }
+
+ if ( ent.IsPlayer() )
+ {
+ //Do callbacks. Main reason we call this here as opposed to CodeCallback_OnPlayerKilled() is legacy script compatibility reasons.
+ //For example: In script immediately above this we change the attacker to get the player behind the kill, e.g. owner of a pet titan, etc. Bunch of registered callbacks depends on this.
+ foreach( callbackFunc in svGlobal.onPlayerKilledCallbacks )
+ callbackFunc( ent, attacker, damageInfo )
+ }
+ else if ( ent.IsNPC() )
+ {
+ //Do callbacks. Main reason we call this here as opposed to CodeCallback_OnNPCKilled() is legacy script compatibility reasons.
+ //For example: In script immediately above this we change the attacker to get the player behind the kill, e.g. owner of a pet titan, etc. Bunch of registered callbacks depends on this.
+ foreach( callbackFunc in svGlobal.onNPCKilledCallbacks )
+ {
+ callbackFunc( ent, attacker, damageInfo )
+ }
+ }
+
+ if ( ent.IsTitan() )
+ {
+ thread TitanVO_DelayedTitanDown( ent )
+ }
+
+ if ( !attacker.IsPlayer() )
+ {
+ // This gets the last player that did damage to the entity so that we can give him the kill
+ AssistingPlayerStruct attackerInfo = GetLatestAssistingPlayerInfo( ent )
+ attacker = attackerInfo.player
+
+ if ( !IsValid( attacker ) )
+ return true
+
+ // Hack - attacker history isn't on client to calculate if a player should get credit for a kill when AI steals the final killing shot while a player is damaging them.
+ array<entity> playerArray = GetPlayerArray()
+ foreach ( player in playerArray )
+ {
+ Remote_CallFunction_Replay( player, "ServerCallback_SetAssistInformation", attackerInfo.damageSourceId, attacker.GetEncodedEHandle(), ent.GetEncodedEHandle(), attackerInfo.assistTime )
+ }
+ }
+
+ // player attacker only from here down
+
+ PreScoreEventUpdateStats( attacker, ent )
+ if ( ent.GetTeam() != attacker.GetTeam() )
+ {
+ if ( ent.IsPlayer() )
+ ScoreEvent_PlayerKilled( ent, attacker, damageInfo )
+ else if ( ent.IsTitan() && ent.IsNPC() )
+ ScoreEvent_TitanKilled( ent, attacker, damageInfo )
+ else
+ ScoreEvent_NPCKilled( ent, attacker, damageInfo )
+ }
+ PostScoreEventUpdateStats( attacker, ent )
+
+ if ( ent.GetTeam() == attacker.GetTeam() )
+ {
+ return false
+ }
+
+ // Respawn Kill INFECTION!!
+ if ( ent.IsPlayer() && attacker.IsPlayer() )
+ {
+ if ( ent.GetPersistentVar( "respawnKillInfected" ) && !attacker.GetPersistentVar( "respawnKillInfected" ) )
+ attacker.SetPersistentVar( "respawnKillInfected", true )
+ }
+
+ if ( gamePlayingOrSuddenDeath )
+ {
+ if ( ent.IsPlayer() )
+ {
+ if ( ent.IsTitan() )
+ {
+ //if we killed a player in a titan count two kills (one for the pilot, one for the titan )
+ attacker.AddToPlayerGameStat( PGS_KILLS, 2 )
+ attacker.AddToPlayerGameStat( PGS_TITAN_KILLS, 1 )
+ attacker.AddToPlayerGameStat( PGS_PILOT_KILLS, 1 )
+ }
+ else
+ {
+ attacker.AddToPlayerGameStat( PGS_KILLS, 1 )
+ attacker.AddToPlayerGameStat( PGS_PILOT_KILLS, 1 )
+ }
+ }
+ else
+ {
+ if ( ent.IsTitan() )
+ attacker.AddToPlayerGameStat( PGS_TITAN_KILLS, 1 )
+
+ if( !IsMarvin( ent ) && !ent.IsTitan() )
+ attacker.AddToPlayerGameStat( PGS_NPC_KILLS, 1 )
+ }
+ }
+
+ return true
+}
+
+// used to calculate build time credit in special cases. Cloak Drones and Suicide Spectres use it for now.
+float function CalculateBuildTimeCredit( entity attacker, entity target, float damage, int health, int maxHealth, string playlistVarStr, float defaultCredit )
+{
+ float titanSpawnDelay = GetTitanBuildTime( attacker )
+ float timerCredit = 0
+
+ health = maxint( 0, health ) // health should never be less then 0
+ if ( titanSpawnDelay && IsAlive( target ) )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( playlistVarStr, defaultCredit )
+
+ float dealtDamage = min( health, damage )
+ timerCredit = timerCredit * (dealtDamage / maxHealth )
+ }
+
+ return timerCredit
+}
+
+function UpdateNextRespawnTime( entity player, float time )
+{
+ player.nv.nextRespawnTime = time
+}
+
+bool function ShouldSetObserverTarget( entity attacker )
+{
+ if ( !IsAlive( attacker ) )
+ return false
+
+ if ( attacker.IsPlayer() && attacker.IsObserver() )
+ return false
+
+ return true
+}
+
+float function CalculateLengthOfKillReplay( entity player, int methodOfDeath ) //Meant to be called on the same frame player dies
+{
+ return GetDeathCamLength( player ) + GetKillReplayBeforeTime( player, methodOfDeath ) + GetKillReplayAfterTime( player )
+}
+
+float function GetKillReplayBeforeTime( entity player, int methodOfDeath )
+{
+ switch ( methodOfDeath )
+ {
+ case eDamageSourceId.damagedef_titan_fall:
+ case eDamageSourceId.damagedef_titan_hotdrop:
+ case eDamageSourceId.damagedef_reaper_fall:
+ case eDamageSourceId.droppod_impact:
+ return KILL_REPLAY_BEFORE_KILL_TIME_DROPPOD
+ }
+
+ if ( !GamePlayingOrSuddenDeath() )
+ return KILL_REPLAY_BEFORE_KILL_TIME_SHORT
+
+ float titanKillReplayTime = KILL_REPLAY_BEFORE_KILL_TIME_TITAN
+ float pilotKillReplayTime = KILL_REPLAY_BEFORE_KILL_TIME_PILOT
+ switch ( methodOfDeath )
+ {
+ case eDamageSourceId.titan_execution:
+ return titanKillReplayTime + 3.0
+
+ case eDamageSourceId.switchback_trap:
+ if ( player.IsTitan() )
+ return titanKillReplayTime + 6.0
+ else
+ return pilotKillReplayTime + 8.0
+ }
+
+ if ( player.IsTitan() )
+ return titanKillReplayTime
+
+ // titan recently?
+ if ( Time() - player.lastTitanTime < 5.0 )
+ return titanKillReplayTime
+
+ return pilotKillReplayTime
+}
+
+function TrackDestroyTimeForReplay( entity attacker, table replayTracker )
+{
+ float startTime = Time()
+ // tracks the time until the attacker becomes invalid
+ EndSignal( replayTracker, "OnDestroy" )
+
+ OnThreadEnd(
+ function () : ( replayTracker, startTime )
+ {
+ replayTracker.validTime = Time() - startTime
+ }
+ )
+
+ string signal = "OnDestroy"
+
+ if ( IsAlive( attacker ) )
+ attacker.WaitSignal( signal )
+ else
+ WaitSignalOnDeadEnt( attacker, signal )
+}
+
+#if MP
+function PlayerWatchesKillReplay( entity player, int inflictorEHandle, int attackerViewIndex, float timeSinceAttackerSpawned, float timeOfDeath, float beforeTime, table replayTracker )
+{
+ OnThreadEnd(
+ function () : ( player, replayTracker )
+ {
+ Signal( replayTracker, "OnDestroy" )
+ }
+ )
+
+ player.EndSignal( "RespawnMe" )
+
+ float timeBeforeKill = beforeTime
+ float timeAfterKill = GetKillReplayAfterTime( player )
+
+ if ( timeBeforeKill > timeSinceAttackerSpawned )
+ timeBeforeKill = timeSinceAttackerSpawned
+
+ float replayDelay = timeBeforeKill + ( Time() - timeOfDeath )
+ if ( replayDelay < 0 )
+ {
+ print( "PlayerWatchesKillReplay(): replayDelay is < 0 (" + replayDelay + "). Aborting kill replay.\n" )
+ return
+ }
+
+ player.SetKillReplayDelay( replayDelay, THIRD_PERSON_KILL_REPLAY_ALWAYS )
+ player.SetKillReplayInflictorEHandle( inflictorEHandle )
+ player.SetKillReplayVictim( player )
+ player.SetViewIndex( attackerViewIndex )
+
+ wait timeBeforeKill
+
+ if ( replayTracker.validTime != null && replayTracker.validTime < timeAfterKill )
+ {
+ float waitTime = expect float( replayTracker.validTime ) - 0.1 // cut off just before ent becomes invalid in the past
+ if ( waitTime > 0 )
+ wait waitTime
+ }
+ else
+ {
+ wait timeAfterKill
+ }
+}
+#endif // #if MP
+
+bool function ClientCommand_SelectRespawn( entity player, array<string> args )
+{
+ if ( IsAlive( player ) )
+ return true
+
+ if ( args.len() == 0 )
+ return true
+
+ int index = args[ 0 ].tointeger()
+
+ switch ( index )
+ {
+ case 1:
+ player.SetPersistentVar( "spawnAsTitan", true )
+ break
+ case 2:
+ player.SetPersistentVar( "spawnAsTitan", false )
+ break
+ }
+
+ return true
+}
+
+
+bool function ClientCommand_RespawnPlayer( entity player, array<string>args )
+{
+ if ( IsSingleplayer() )
+ return true
+
+ if ( IsAlive( player ) )
+ return true
+
+ if ( args.len() != 1 )
+ return true
+
+ string opParm = args[ 0 ]
+
+ if ( opParm.find( "burncard" ) != null )
+ {
+ //int burnCard = opParm.tointeger()
+ //SetPlayerBurnCardSlotToActivate( player, burnCard )
+ return true
+ }
+ else if ( opParm == "Titan" )
+ {
+ player.SetPersistentVar( "spawnAsTitan", true )
+ }
+ else if ( opParm == "Pilot" )
+ {
+ player.SetPersistentVar( "spawnAsTitan", false )
+ }
+
+ float deathCamLength = GetDeathCamLength( player )
+ float skipBufferTime = 0.5
+ if ( Time() > (player.p.postDeathThreadStartTime + deathCamLength) - skipBufferTime )
+ {
+ player.s.respawnSelectionDone = true
+ player.Signal( "RespawnMe" )
+ }
+
+ return true
+}
+
+function AIChatter( string alias, int team, vector origin )
+{
+ array<entity> ai = GetNearbyFriendlyGrunts( origin, team )
+
+ if ( ai.len() > 0 )
+ {
+ PlaySquadConversationToAll( alias, ai[0] )
+ }
+}
+
+const MAX_ACTIVITY_DISABLED = 0
+const MAX_ACTIVITY_PILOTS = 1
+const MAX_ACTIVITY_TITANS = 2
+const MAX_ACTIVITY_PILOTS_AND_TITANS = 3
+const MAX_ACTIVITY_CONGER_MODE = 4
+
+bool function GetPilotBotFlag()
+{
+ // IMPORTANT: Please call this consistently instead of Flag( "PilotBot" )
+ // Force titan or pilot bots according to max activity mode if it is enabled.
+ // Otherwise, leave the "pilotBot" flag alone and do what the game mode wants.
+ int max_activity_mode = GetConVarInt( "max_activity_mode" )
+ if ( max_activity_mode == MAX_ACTIVITY_PILOTS || max_activity_mode == MAX_ACTIVITY_PILOTS_AND_TITANS )
+ return true
+ else if ( max_activity_mode == MAX_ACTIVITY_TITANS )
+ return false
+ else if ( max_activity_mode == MAX_ACTIVITY_CONGER_MODE )
+ return rand() % 2 != 0 // conger mode: 50/50 pilot and titan bots!
+ else
+ return Flag( "PilotBot" )
+
+ unreachable
+}
+
+
+function DoRespawnPlayer( entity player, entity spawnPoint )
+{
+ player.p.lastSpawnPoint = spawnPoint
+ player.RespawnPlayer( spawnPoint ) //This will send "OnRespawned" signal, killing the thread if started from PostDeathThread
+}
+
+function SetupPilotSpawnOnRematch( entity player )
+{
+ // clear respawn countdown message
+ if ( GetWaveSpawnType() == eWaveSpawnType.DROPSHIP )
+ MessageToPlayer( player, eEventNotifications.Clear )
+
+ player.SetOrigin( player.p.rematchOrigin )
+
+ if ( GetWaveSpawnType() == eWaveSpawnType.DISABLED )
+ wait 0.9
+
+ if ( IsAlive( player ) )//HACK: This seems terrible, we shouldn't have to do this
+ {
+ printt( "This happened one time, in retail." )
+ return
+ }
+
+ if ( ShouldGivePlayerInfoOnSpawn() )
+ thread GivePlayerInfoOnSpawn( player )
+
+ return
+}
+
+bool function ShouldGivePlayerInfoOnSpawn()
+{
+ return GetCurrentPlaylistVarInt( "minimap_sonar_pulse_on_respawn", 0 ) > 0
+}
+
+function GivePlayerInfoOnSpawn( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ //PrintFunc()
+
+ while( player.IsWatchingKillReplay() )
+ WaitFrame()
+
+ //printt( " GivePlayerInfoOnSpawn Player isn't watching kill replay anymore!" )
+
+ wait 0.2 //Hack: Have to wait even though player should not be watching kill replay anymore...
+
+ //This needed a wait, probably because at this time we haven't given them loadouts yet, so when we do give them loadouts it strips out the passive?
+ thread ScanMinimap( player, true, 0.5 ) //x second minimap pulse
+}
+
+bool function ShouldStartSpawn( entity player )
+{
+ if ( Riff_FloorIsLava() )
+ return false
+
+ if ( Flag( "ForceStartSpawn" ) )
+ return true
+
+ if ( Flag( "IgnoreStartSpawn" ) )
+ return false
+
+ if ( GetGameState() <= eGameState.Prematch )
+ return true
+
+ if ( player.s.respawnCount )
+ return false
+
+ return GameTime_PlayingTime() < START_SPAWN_GRACE_PERIOD
+}
+
+void function PlayerSpawnsIntoPetTitan( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ entity titan = player.GetPetTitan()
+
+ vector origin = titan.GetOrigin() + Vector( 0, 0, 600 )
+ vector angles = titan.GetAngles()
+
+ entity camera = CreateTitanDropCamera( origin, Vector(90,angles.y,0) )
+ player.SetViewEntity( camera, false )
+
+ player.isSpawning = true // set this to prevent .isSpawning checks from returning false
+
+ angles.x = 70
+
+ player.SetOrigin( origin )
+ player.SnapEyeAngles( angles )
+ player.SetVelocity( Vector( 0.0, 0.0, 0.0 ) )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ {
+ player.ClearViewEntity()
+ player.ClearSpawnPoint()
+ player.isSpawning = null
+ }
+ }
+ )
+
+ wait 0.2
+
+ local criteria = {
+ embark = "above_close",
+ titanCanStandRequired = true
+ }
+
+ local embarkAction
+ embarkAction = FindEmbarkActionForCriteria( criteria )
+ if ( embarkAction == null )
+ embarkAction = GetRandomEmbarkAction()
+
+ if ( IsValid( camera ) )
+ {
+ // camera can be invalid for a moment when server shuts down
+ // camera.FireNow( "Disable", "!activator", null, player )
+ camera.Destroy()
+ }
+
+ DoRespawnPlayer( player, null )
+
+ if ( PlayerCanSpawnIntoTitan( player ) )
+ {
+ table action = expect table( GenerateEmbarkActionTable( player, titan, embarkAction ) )
+ PlayerEmbarksTitan( player, titan, action )
+ }
+}
+
+entity function CreateTitanDropCamera( origin, angles )
+{
+ entity viewControl = CreateEntity( "point_viewcontrol" )
+ viewControl.kv.spawnflags = 56 // infinite hold time, snap to goal angles, make player non-solid
+
+ viewControl.SetOrigin( origin )
+ viewControl.SetAngles( angles )
+ DispatchSpawn( viewControl )
+
+ return viewControl
+}
+
+entity function CreateDropPodViewController( entity pod )
+{
+ entity viewControl = CreateEntity( "point_viewcontrol" )
+ viewControl.kv.spawnflags = 56 // infinite hold time, snap to goal angles, make player non-solid
+
+ viewControl.SetOrigin( pod.GetOrigin() + Vector( 44, -64, 520 ) )
+ float yaw = pod.GetAngles().y
+ viewControl.SetAngles( Vector( 90, yaw + 10, 0 ) )
+ DispatchSpawn( viewControl )
+
+ viewControl.SetParent( pod )
+
+ return viewControl
+}
+
+
+function ClearEntInUseOnDestroy( dropPoint, dropPod )
+{
+ dropPod.WaitSignal( "OnDestroy" )
+ dropPoint.e.spawnPointInUse = false
+}
+
+float function GetPlayerLastRespawnTime( entity player )
+{
+ return expect float( player.s.respawnTime )
+}
+
+entity function GetEmbarkPlayer( entity titan )
+{
+ if ( "embarkingPlayer" in titan.s )
+ return expect entity( titan.s.embarkingPlayer )
+
+ return null
+}
+
+entity function GetDisembarkPlayer( entity titan )
+{
+ if ( "disembarkingPlayer" in titan.s )
+ return expect entity( titan.s.disembarkingPlayer )
+
+ return null
+}
+
+entity function GetEmbarkDisembarkPlayer( entity titan )
+{
+ entity result = GetEmbarkPlayer( titan )
+
+ if ( IsValid( result ) )
+ return result
+
+ result = GetDisembarkPlayer( titan )
+ if ( IsValid( result ) )
+ return result
+
+ return null
+}
+
+void function CodeCallback_OnNPCKilled( entity npc, var damageInfo )
+{
+ if ( IsSingleplayer() )
+ {
+ OnNPCKilled_SP( npc, damageInfo )
+ return
+ }
+
+ HandleDeathPackage( npc, damageInfo )
+
+ if ( npc.IsTitan() )
+ {
+ // if a player is getting in, kill him too
+ entity player = GetEmbarkPlayer( npc )
+ if ( IsAlive( player ) )
+ {
+ // kill the embarking player
+ //printt( "Killed embarking player" )
+ KillFromInfo( player, damageInfo )
+ }
+
+ if ( !GetDoomedState( npc ) )
+ {
+ // Added via AddCallback_OnTitanDoomed
+ foreach ( callbackFunc in svGlobal.onTitanDoomedCallbacks )
+ {
+ callbackFunc( npc, damageInfo )
+ }
+ }
+ }
+
+ PlayerOrNPCKilled( npc, damageInfo )
+}
+
+void function OnNPCKilled_SP( entity npc, var damageInfo )
+{
+ HandleDeathPackage( npc, damageInfo )
+
+ if ( npc.IsTitan() )
+ {
+ // if a player is getting in, kill him too
+ entity player = GetEmbarkPlayer( npc )
+ if ( IsAlive( player ) )
+ {
+ // kill the embarking player
+ //printt( "Killed embarking player" )
+ KillFromInfo( player, damageInfo )
+ }
+
+ if ( !GetDoomedState( npc ) )
+ {
+ // Added via AddCallback_OnTitanDoomed
+ foreach ( callbackFunc in svGlobal.onTitanDoomedCallbacks )
+ {
+ callbackFunc( npc, damageInfo )
+ }
+ }
+ }
+
+ entity attacker = GetAttackerOrLastAttacker( npc, damageInfo )
+ if ( !IsValid( attacker ) )
+ return
+
+ if ( !attacker.IsPlayer() )
+ {
+ entity newAttacker = GetPlayerFromEntity( attacker )
+ if ( IsValid( newAttacker ) )
+ attacker = newAttacker
+ }
+
+ foreach( callbackFunc in svGlobal.onNPCKilledCallbacks )
+ {
+ callbackFunc( npc, attacker, damageInfo )
+ }
+
+ if ( npc.IsTitan() )
+ thread TitanVO_DelayedTitanDown( npc )
+}
+
+void function CodeCallback_OnEntityDestroyed( entity ent )
+{
+ // Must do ent.SetDoDestroyCallback( true ) to get this callback
+// print( "OnEntityDestroyed " + ent.entindex() + "\n" )
+
+ if ( "onEntityDestroyedCallbacks" in ent.s )
+ {
+ foreach ( callbackFunc in ent.s.onEntityDestroyedCallbacks )
+ {
+ callbackFunc( ent )
+ }
+ }
+}
+
+function AddEntityDestroyedCallback( ent, callbackFunc )
+{
+ AssertParameters( callbackFunc, 1, "entity" )
+
+ if ( !( "onEntityDestroyedCallbacks" in ent.s ) )
+ ent.s.onEntityDestroyedCallbacks <- []
+
+ ent.s.onEntityDestroyedCallbacks.append( callbackFunc )
+
+ // set this or else the ent won't run CodeCallback_OnEntityDestroyed at all
+ ent.SetDoDestroyCallback( true )
+}
+
+bool function WeaponInterruptsCloak( entity weapon )
+{
+ if ( !IsValid( weapon ) )
+ return false
+
+ return weapon.GetWeaponInfoFileKeyField( "does_not_interrupt_cloak" ) != 1
+}
+
+void function CodeCallback_WeaponFireInCloak( entity player )
+{
+ if ( !WeaponInterruptsCloak( player.GetActiveWeapon() ) )
+ return
+
+ if ( player.IsTitan() ) // Fix timing issue with auto-eject cloak and firing your weapon as a Titan cancelling it. This assumes we never want cloaked titans!
+ return
+
+ // if ( player.cloakedForever )
+ // {
+ // player.SetCloakFlicker( 1.0, 2.0 )
+ // return
+ // }
+
+ // // Check if we are allowed some cloaked shots based on ability selection
+ // if ( player.s.cloakedShotsAllowed > 0 )
+ // {
+ // player.s.cloakedShotsAllowed--
+ // return
+ // }
+
+ if ( IsMultiplayer() )
+ {
+ //player.SetCloakFlicker( 1.0, 2.0 )
+
+ DisableCloak( player, 0.5 )
+ entity weapon = player.GetOffhandWeapon( OFFHAND_LEFT )
+ //printt( "weapon", weapon.GetWeaponClassName() )
+ // JFS; need code feature to properly reset next attack time/cooldown stuff
+ if ( IsValid( weapon ) && weapon.GetWeaponClassName() == "mp_ability_cloak" )
+ {
+ player.TakeOffhandWeapon( OFFHAND_LEFT )
+ player.GiveOffhandWeapon( "mp_ability_cloak", OFFHAND_LEFT )
+ weapon = player.GetOffhandWeapon( OFFHAND_LEFT )
+ weapon.SetWeaponPrimaryClipCountAbsolute( 0 )
+ }
+ }
+ else
+ {
+ DisableCloak( player, 0.5 )
+ }
+}
+
+// need "you will change class next time" message
+function OnPlayerCloseClassMenu( entity player )
+{
+ if ( GetGameState() <= eGameState.Prematch )
+ return
+
+ if ( player.IsEntAlive() )
+ return
+
+ if ( player.s.inPostDeath )
+ return
+
+ if ( IsValid( player.isSpawning ) )
+ return
+
+ thread DecideRespawnPlayer( player ) // there is a wait that happens later when using rematch burncard in Frontier Defense.
+}
+
+// playerconnected Reload
+void function CodeCallback_OnClientReloadConnectionCompleted( entity player )
+{
+ FinishClientScriptInitialization( player )
+}
+
+
+bool function ShouldPlayerHaveLossProtection( entity player )
+{
+ if ( level.nv.matchProgress < GetCurrentPlaylistVarInt( "matchLossProtectionThreshold", 10 ) )
+ return false
+
+ if ( IsPrivateMatch() )
+ return false
+
+ if ( IsFFAGame() )
+ return true
+
+ int team = player.GetTeam()
+ int otherTeam = GetOtherTeam( team )
+ int teamScore = IsRoundBased() ? GameRules_GetTeamScore2( team ) : GameRules_GetTeamScore( team )
+ int otherTeamScore = IsRoundBased() ? GameRules_GetTeamScore2( otherTeam ) : GameRules_GetTeamScore( otherTeam )
+
+ if ( teamScore < otherTeamScore )
+ return true
+
+ return false
+}
+
+// This server will recieve this command from the client once they have loaded/run all of their scripts
+// Any client hud initialization should be done here
+function FinishClientScriptInitialization( entity player )
+{
+ printt( "Player client script initialization complete: " + player );
+
+ player.p.clientScriptInitialized = true
+
+ SyncServerVars( player )
+ SyncEntityVars( player )
+ SyncUIVars( player )
+
+ Remote_CallFunction_Replay( player, "ServerCallback_ClientInitComplete" )
+}
+
+function NotifyClientsOfConnection( entity player, state )
+{
+ int playerEHandle = player.GetEncodedEHandle()
+ array<entity> players = GetPlayerArray()
+ foreach ( ent in players )
+ {
+ if ( ent != player )
+ Remote_CallFunction_Replay( ent, "ServerCallback_PlayerConnectedOrDisconnected", playerEHandle, state )
+ }
+}
+
+function NotifyClientsOfTeamChange( entity player, int oldTeam, int newTeam )
+{
+ int playerEHandle = player.GetEncodedEHandle()
+ array<entity> players = GetPlayerArray()
+ foreach ( ent in players )
+ {
+ //if ( ent != player )
+ Remote_CallFunction_Replay( ent, "ServerCallback_PlayerChangedTeams", playerEHandle, oldTeam, newTeam )
+ }
+}
+
+
+bool function IsValidNPCTarget( entity ent )
+{
+ switch ( ent.GetClassName() )
+ {
+ case "npc_marvin":
+ case "npc_soldier":
+ case "npc_spectre":
+ case "npc_stalker":
+ case "npc_super_spectre":
+ case "npc_prowler":
+ case "npc_drone":
+ case "npc_titan":
+ case "npc_turret_sentry":
+ case "npc_turret_mega":
+ case "npc_dropship":
+ return true
+ }
+
+ return false
+}
+
+int function CodeCallback_GetWeaponDamageSourceId( entity weapon )
+{
+ string classname = weapon.GetWeaponClassName()
+
+ #if DEV
+ if ( ("devWeapons" in level) && classname in level.devWeapons )
+ return 0
+
+ #endif
+ //Filter out abilities for now
+ if ( !(classname in eDamageSourceId) )
+ return damagedef_unknown
+
+ //Assert( classname in getconsttable().eDamageSourceId, classname + " not added to eDamageSourceId enum" )
+ int damageSourceInt = eDamageSourceId[ classname ]
+ return damageSourceInt
+}
+
+
+
+
+function TriggerHurtSetup()
+{
+ file.hurtTriggers.extend( GetEntArrayByClass_Expensive( "trigger_hurt" ) )
+ foreach( trigger in file.hurtTriggers )
+ {
+ trigger.ConnectOutput( "OnStartTouch", TriggerHurtEnter )
+ }
+}
+
+void function TriggerHurtEnter( entity trigger, entity ent, entity caller, var value )
+{
+ if ( ent.e.destroyTriggerHurt )
+ ent.Destroy()
+}
+
+#if MP
+table< entity, table< entity, bool > > oob_triggerEntPairs
+
+void function SetupOutOfBoundsTrigger( entity trigger )
+{
+ if ( !(trigger in oob_triggerEntPairs) )
+ oob_triggerEntPairs[trigger] <- {}
+}
+#endif
+
+function OutOfBoundsSetup()
+{
+ file.outOfBoundsTriggers.extend( GetEntArrayByClass_Expensive( "trigger_out_of_bounds" ) )
+ foreach( trigger in file.outOfBoundsTriggers )
+ {
+ #if MP
+ SetupOutOfBoundsTrigger( trigger )
+ trigger.ConnectOutput( "OnStartTouch", EntityEnterOutOfBoundsTrig )
+ trigger.ConnectOutput( "OnEndTouch", EntityLeaveOutOfBoundsTrig )
+ #else
+ trigger.ConnectOutput( "OnStartTouch", EntityOutOfBounds )
+ trigger.ConnectOutput( "OnEndTouch", EntityBackInBounds )
+ #endif
+ }
+
+ AddCallback_GameStateEnter( eGameState.Postmatch, OutOfBoundsDisable )
+}
+
+void function OutOfBoundsDisable()
+{
+ foreach( trigger in file.outOfBoundsTriggers )
+ {
+ #if MP
+ foreach ( ent, val in oob_triggerEntPairs[trigger] )
+ oob_triggerEntPairs[trigger][ent] = false
+ trigger.DisconnectOutput( "OnStartTouch", EntityEnterOutOfBoundsTrig )
+ trigger.DisconnectOutput( "OnEndTouch", EntityLeaveOutOfBoundsTrig )
+ #else
+ trigger.DisconnectOutput( "OnStartTouch", EntityOutOfBounds )
+ trigger.DisconnectOutput( "OnEndTouch", EntityBackInBounds )
+ #endif
+ }
+}
+
+bool function IsPointOutOfBounds( vector point )
+{
+ foreach ( trigger in file.outOfBoundsTriggers )
+ {
+ if ( trigger.ContainsPoint( point ) )
+ return true
+ }
+ return false
+}
+
+#if MP
+void function EntityEnterOutOfBoundsTrig( entity trigger, entity ent, entity caller, var value )
+{
+ if ( !IsValid( ent ) || !ent.IsPlayer() )
+ {
+ EntityOutOfBounds( trigger, ent, null, null )
+ return
+ }
+
+ if ( !(ent in oob_triggerEntPairs[trigger]) )
+ {
+ oob_triggerEntPairs[trigger][ent] <- true
+ thread EntityCheckOutOfBoundsThread( trigger, ent )
+ }
+ else
+ {
+ oob_triggerEntPairs[trigger][ent] = true
+ // thread is already running
+ }
+}
+
+void function EntityLeaveOutOfBoundsTrig( entity trigger, entity ent, entity caller, var value )
+{
+ if ( !(ent in oob_triggerEntPairs[trigger]) )
+ {
+ EntityBackInBounds( trigger, ent, null, null )
+ return
+ }
+
+ oob_triggerEntPairs[trigger][ent] = false // tell thread to stop
+}
+
+bool function TriggerIsTouchingPlayerHullAtPoint( entity player, entity trigger, float triggerminz, vector pos, float radius )
+{
+ if ( trigger.GetClassName() == "trigger_cylinder" )
+ {
+ array<entity> touchingEnts = trigger.GetTouchingEntities()
+ return touchingEnts.contains( player )
+ }
+ else
+ {
+ return BrushTriggerIsTouchingPlayerHullAtPoint( trigger, triggerminz, pos, radius )
+ }
+
+ unreachable
+}
+
+bool function BrushTriggerIsTouchingPlayerHullAtPoint( entity trigger, float triggerminz, vector pos, float radius )
+{
+ if ( pos.z < triggerminz )
+ return false
+
+ radius *= 1.0824 // expand by 1/cos(22.5) so that an octagon circumscribes the circle
+
+ if ( trigger.ContainsPoint( pos ) ||
+ trigger.ContainsPoint( pos + <radius,0,0> ) ||
+ trigger.ContainsPoint( pos + < -radius,0,0> ) ||
+ trigger.ContainsPoint( pos + <0,radius,0> ) ||
+ trigger.ContainsPoint( pos + <0,-radius,0> ) )
+ return true
+
+ float radius45 = radius * 0.7071
+
+ if ( trigger.ContainsPoint( pos + <radius45,radius45,0> ) ||
+ trigger.ContainsPoint( pos + < -radius45,-radius45,0> ) ||
+ trigger.ContainsPoint( pos + <radius45,-radius45,0> ) ||
+ trigger.ContainsPoint( pos + < -radius45,radius45,0> ) )
+ return true
+
+ return false
+}
+
+void function EntityCheckOutOfBoundsThread( entity trigger, entity ent )
+{
+ float minz = trigger.GetOrigin().z + trigger.GetBoundingMins().z
+ float radius = ent.GetBoundingMaxs().x
+
+ bool wasTouching = false
+ for ( ;; )
+ {
+ wait 0.099
+
+ if ( !IsValid( ent ) )
+ break
+
+ if ( !oob_triggerEntPairs[trigger][ent] )
+ break
+
+ bool isTouching
+ if ( ent.IsOnGround() )
+ {
+ if ( ent.IsWallRunning() && !ent.IsWallHanging() )
+ {
+ isTouching = TriggerIsTouchingPlayerHullAtPoint( ent, trigger, minz, ent.GetOrigin() + <0,0,10>, radius )
+ }
+ else
+ {
+ isTouching = true
+ }
+ }
+ else
+ {
+ vector startpos = ent.GetOrigin()
+ vector endpos = startpos
+ endpos.z -= 2048
+
+ TraceResults result = TraceHull( startpos, endpos, ent.GetBoundingMins(), ent.GetBoundingMaxs(), ent, TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER )
+ if ( result.startSolid || result.fraction >= 1 || TriggerIsTouchingPlayerHullAtPoint( ent, trigger, minz, result.endPos + <0,0,40>, radius ) )
+ {
+ //DebugDrawLine( startpos, result.endPos, 255,255,255, true, 3.0 )
+ isTouching = true
+ }
+ else
+ {
+ //DebugDrawLine( startpos, result.endPos, 255,0,0, true, 3.0 )
+ isTouching = false
+ }
+ }
+
+ if ( isTouching == wasTouching )
+ continue
+
+ wasTouching = isTouching
+ if ( isTouching )
+ {
+ EntityOutOfBounds( trigger, ent, null, null )
+ }
+ else
+ {
+ EntityBackInBounds( trigger, ent, null, null )
+ }
+ }
+
+ if ( wasTouching )
+ {
+ EntityBackInBounds( trigger, ent, null, null )
+ }
+
+ delete oob_triggerEntPairs[trigger][ent]
+}
+#endif
+
+void function EntityOutOfBounds( entity trigger, entity ent, entity caller, var value )
+{
+ //printt( "ENTITY", ent, "IS OUT OF BOUNDS ON TRIGGER", trigger )
+
+ if ( ent.e.destroyOutOfBounds )
+ ent.Destroy()
+
+ if ( !IsValidOutOfBoundsEntity( ent, trigger ) )
+ return
+
+ //printt( "Valid Out OfBounds Entity, EntityOutOfBounds" )
+
+ if ( !(ent in file.outOfBoundsTable) ) //Note that we never remove the ent from the table after adding it
+ {
+ OutOfBoundsDataStruct initialDataStruct
+ initialDataStruct.timeBackInBound = max( 0, Time() - OUT_OF_BOUNDS_DECAY_TIME )
+
+ ManageAddEntToOutOfBoundsTable( ent, initialDataStruct )
+ }
+
+ OutOfBoundsDataStruct dataStruct = file.outOfBoundsTable[ ent ]
+
+ dataStruct.outOfBoundsTriggersTouched++
+
+ Assert( dataStruct.outOfBoundsTriggersTouched > 0 )
+
+ // Not already touching another trigger
+ if ( dataStruct.outOfBoundsTriggersTouched == 1 )
+ {
+ float decayTime = max( 0, Time() - dataStruct.timeBackInBound - OUT_OF_BOUNDS_DECAY_DELAY )
+ float outOfBoundsTimeRegained = decayTime * ( OUT_OF_BOUNDS_TIME_LIMIT / OUT_OF_BOUNDS_DECAY_TIME )
+ float deadTime = clamp( dataStruct.timeLeftBeforeDyingFromOutOfBounds + outOfBoundsTimeRegained, 0.0, OUT_OF_BOUNDS_TIME_LIMIT )
+
+ //printt( "Decay Time: " + decayTime + ", outOfBoundsTimeRegained:" + outOfBoundsTimeRegained + ", timeLeftBeforeDyingFromOutOfBounds: " + dataStruct.timeLeftBeforeDyingFromOutOfBounds + ", deadTime: " + deadTime )
+
+ dataStruct.timeLeftBeforeDyingFromOutOfBounds = deadTime
+
+ ent.SetOutOfBoundsDeadTime( Time() + deadTime )
+
+ thread KillEntityOutOfBounds( ent, trigger )
+ }
+
+ //printt( "ent.GetOutOfBoundsDeadTime():", ent.GetOutOfBoundsDeadTime() )
+}
+
+bool function EntityIsOutOfBounds( entity ent )
+{
+ if ( !( ent in file.outOfBoundsTable ) )
+ return false
+ return file.outOfBoundsTable[ ent ].outOfBoundsTriggersTouched > 0
+}
+
+void function EntityBackInBounds( entity trigger, entity ent, entity caller, var value )
+{
+ //printt( "ENTITY", ent, "IS BACK IN BOUNDS OF TRIGGER", trigger )
+
+ if ( !IsValidOutOfBoundsEntity( ent, trigger ) )
+ return
+
+ //printt( "Valid Out OfBounds Entity, EntityBackInBounds" )
+
+ if ( !(ent in file.outOfBoundsTable) ) //Can go back in bounds even though we went out of bounds as an invalid ent, e.g. in a dropship
+ {
+ OutOfBoundsDataStruct initialDataStruct
+ ManageAddEntToOutOfBoundsTable( ent, initialDataStruct )
+
+ ent.SetOutOfBoundsDeadTime( 0.0 )
+ ent.Signal( "BackInBounds" )
+
+ return
+ }
+ else
+ {
+ OutOfBoundsDataStruct dataStruct = file.outOfBoundsTable[ ent ]
+
+ dataStruct.outOfBoundsTriggersTouched--
+ if ( dataStruct.outOfBoundsTriggersTouched < 0 ) //You can exit from bounds while being an invalid ent from out of bounds on the way in, e.g. during dropship anims, etc
+ dataStruct.outOfBoundsTriggersTouched = 0
+
+ if ( dataStruct.outOfBoundsTriggersTouched == 0 )
+ {
+ dataStruct.timeBackInBound = Time()
+ dataStruct.timeLeftBeforeDyingFromOutOfBounds = max( 0, ent.GetOutOfBoundsDeadTime() - Time() )
+ ent.SetOutOfBoundsDeadTime( 0.0 )
+ ent.Signal( "BackInBounds" )
+ return
+ }
+ }
+}
+
+void function KillEntityOutOfBounds( entity ent, entity trigger )
+{
+ if ( GetGameState() < eGameState.Playing )
+ return
+
+ Assert( ent.GetOutOfBoundsDeadTime() != 0 )
+ Assert( Time() <= ent.GetOutOfBoundsDeadTime() )
+
+ ent.EndSignal( "OnDeath" )
+ ent.Signal( "OutOfBounds" )
+ ent.EndSignal( "OutOfBounds" )
+ ent.EndSignal( "BackInBounds" )
+
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ if ( IsValid( ent ) && !IsAlive( ent ) )
+ {
+ file.outOfBoundsTable[ ent ].outOfBoundsTriggersTouched = 0
+ ent.SetOutOfBoundsDeadTime( 0 )
+ }
+ }
+ )
+
+ wait ent.GetOutOfBoundsDeadTime() - Time()
+
+ if ( !IsValidOutOfBoundsEntity( ent, trigger ) )
+ return
+
+ if ( ent.GetOutOfBoundsDeadTime() == 0 )
+ return
+
+ ent.Die( svGlobal.worldspawn, svGlobal.worldspawn, { scriptType = DF_INSTANT, damageSourceId = eDamageSourceId.outOfBounds } )
+}
+
+bool function IsValidOutOfBoundsEntity( entity ent, entity trigger )
+{
+ if ( !IsValid( ent ) )
+ return false
+
+ if ( !IsAlive( ent ) )
+ return false
+
+ int triggerTeam = expect int( trigger.kv.teamnumber.tointeger() )
+
+ Assert( triggerTeam >= 0 )
+
+ if ( triggerTeam != 0 && ent.GetTeam() != triggerTeam )
+ return false
+
+ // Temp hack for tday intro, might not keep this
+ if ( "disableOutOfBounds" in level && level.disableOutOfBounds == true )
+ return false
+
+ if ( ent.IsPlayer() )
+ {
+ if ( ent.IsNoclipping() && !ent.Anim_IsActive() ) //Need to check for Anim_IsActive because PlayAnim() calls will set IsNoclipping() to true. This caused a bug with ejecting out of a OutOfBounds trigger
+ return false
+
+ entity parentEnt = ent.GetParent()
+ if ( IsValid( parentEnt ) && IsDropship( parentEnt ) )
+ return false
+
+ return true
+ }
+
+ if ( ent.IsNPC() && ent.IsTitan() )
+ return true
+
+ return false
+}
+
+void function OnTitanBecomesPilot_OutOfBoundsCheck( entity pilot, entity npc_titan )
+{
+ if ( pilot.GetOutOfBoundsDeadTime() == 0 )
+ return
+
+ npc_titan.SetOrigin( npc_titan.GetOrigin() ) //Kinda a hack to force redetection of the Titan touching the out of bounds trigger
+}
+
+void function ManageAddEntToOutOfBoundsTable( entity ent, OutOfBoundsDataStruct dataStruct ) //Might be overkill, but: suggested by Haggerty to avoid leak of constantly adding ents to the file table without removing them
+{
+ //First clean up dead references in table
+ table< entity, OutOfBoundsDataStruct> tempTable = clone file.outOfBoundsTable
+
+ foreach( ent, dataStruct in tempTable )
+ {
+ if ( !IsValid( ent ) )
+ {
+ delete file.outOfBoundsTable[ ent ]
+ }
+ }
+
+ //Now add the new ent
+
+ file.outOfBoundsTable[ ent ] <- dataStruct
+}
+
+bool function PlayerCanSpawn( entity player )
+{
+ if ( IsAlive( player ) )
+ return false
+
+ if ( player.isSpawning )
+ return false
+
+ return true
+}
+
+function SetTitanAvailable( entity player )
+{
+ Assert( player.entindex() < 32 )
+ int shiftIndex = player.entindex() - 1
+ int elimMask = (1 << shiftIndex)
+
+ level.nv.titanAvailableBits = level.nv.titanAvailableBits | elimMask
+
+ #if MP
+ PIN_PlayerAbilityReady( player, "titanfall" )
+ #endif
+}
+
+function ClearTitanAvailable( entity player )
+{
+ Assert( player.entindex() < 32 )
+ int shiftIndex = player.entindex() - 1
+ int elimMask = (1 << shiftIndex)
+
+ level.nv.titanAvailableBits = level.nv.titanAvailableBits & (~elimMask)
+}
+
+
+
+function SetRespawnAvailable( entity player )
+{
+ Assert( player.entindex() < 32 )
+ int shiftIndex = player.entindex() - 1
+ int elimMask = (1 << shiftIndex)
+
+ level.nv.respawnAvailableBits = level.nv.respawnAvailableBits | elimMask
+}
+
+
+function ClearRespawnAvailable( entity player )
+{
+ Assert( player.entindex() < 32 )
+ int shiftIndex = player.entindex() - 1
+ int elimMask = (1 << shiftIndex)
+
+ level.nv.respawnAvailableBits = level.nv.respawnAvailableBits & (~elimMask)
+}
+
+
+void function SetPlayerEliminated( entity player )
+{
+ player.SetPlayerGameStat( PGS_ELIMINATED, 1 )
+}
+
+void function ClearPlayerEliminated( entity player )
+{
+ player.SetPlayerGameStat( PGS_ELIMINATED, 0 )
+}
+
+bool function IsPlayerEliminated( entity player )
+{
+ return (player.GetPlayerGameStat( PGS_ELIMINATED ) > 0)
+}
+
+bool function IsTeamEliminated( int team )
+{
+ array<entity> players = GetPlayerArrayOfTeam( team )
+
+ foreach ( player in players )
+ {
+ if ( IsPlayerEliminated( player ) != true )
+ return false
+ }
+
+ return true
+}
+
+// Clears all scoreboard data for the player to make sure we never use old data
+void function ClearPostGameScoreboardData( entity player )
+{
+ if ( !IsValid( player ) || !player.IsPlayer() )
+ return
+
+ player.SetPersistentVar( "isPostGameScoreboardValid", false )
+ player.SetPersistentVar( "isFDPostGameScoreboardValid", false )
+}
+
+bool function ShouldShowLossProtectionOnEOG( entity player )
+{
+ if ( player.p.hasMatchLossProtection != true )
+ return false
+
+ if ( player.GetTeam() == GetWinningTeam() )
+ return false
+
+ if ( IsPrivateMatch() )
+ return false
+
+ return true
+}
+
+bool function GameModeRemove( entity ent )
+{
+ string gameMode = GameRules_GetGameMode()
+ switch ( gameMode )
+ {
+ // These game modes have checkboxes in leveled
+ case LAST_TITAN_STANDING:
+ case TEAM_DEATHMATCH:
+ case ATTRITION:
+ case CAPTURE_POINT:
+ case CAPTURE_THE_FLAG:
+ case FORT_WAR:
+ case FFA:
+ case FD:
+ break
+
+ // These game modes use tdm spawns
+ case PILOT_SKIRMISH:
+ case WINGMAN_PILOT_SKIRMISH:
+ case MARKED_FOR_DEATH_PRO:
+ case MARKED_FOR_DEATH:
+ case T_DAY:
+ case AI_TDM:
+ case BOMB:
+ case HARDCORE_TDM:
+ case COLISEUM:
+ case HUNTED:
+ case DON:
+ case TITAN_BRAWL:
+ case SPEEDBALL:
+ gameMode = TEAM_DEATHMATCH
+ break
+
+ case RAID:
+ case ATCOOP:
+ case CONQUEST:
+ case PVE_SANDBOX:
+ gameMode = ATTRITION
+ break
+
+ case LTS_BOMB:
+ case WINGMAN_LAST_TITAN_STANDING:
+ gameMode = LAST_TITAN_STANDING
+ break
+
+ case FREE_AGENCY:
+ gameMode = FFA
+ break
+
+ default:
+ // If a game mode is not handled in here, spawnpoints won't have checkboxes that correspond to it, so all spawnpoints will be used in that mode, which is probably bad.
+ Assert( false, "Game mode " + gameMode + " not handled in GameModeRemove()" )
+ }
+
+ AT_CollisionCleanup( ent )
+
+ string gamemodeKey = "gamemode_" + gameMode
+ if ( ent.HasKey( gamemodeKey ) && (ent.kv[gamemodeKey] == "0" || ent.kv[gamemodeKey] == "") )
+ {
+ // printt( "Removing ent " + ent.GetClassName() + " with " + gamemodeKey + " = \"" + ent.kv[gamemodeKey] + "\" at " + ent.GetOrigin() )
+ ent.Destroy()
+ return true
+ }
+ //printt( "keeping ent", ent.GetClassName() )
+
+ return false
+}
+
+void function AT_CollisionCleanup( entity spawnPoint )
+{
+ if ( spawnPoint.GetScriptName() == "at_mega_turret" )
+ {
+ if ( spawnPoint.GetLinkEnt() != null ) // assuming this is func_brush_navmesh_separator
+ {
+ entity brush = spawnPoint.GetLinkEnt()
+ brush.NotSolid()
+ }
+ }
+}
+
+
+void function EntityFire( entity ent, string fire )
+{
+ ent.Fire( fire )
+}
+
+void function EntityFireDelayed( entity ent, string fire, string parm, float delay )
+{
+ ent.Fire( fire, parm, delay )
+}
+
+#if MP
+void function AddOutOfBoundsTriggerWithParams( vector org, float radius = 250.0, float height = 250.0 )
+{
+ entity trigger = CreateEntity( "trigger_cylinder" )
+ trigger.SetRadius( radius )
+ trigger.SetAboveHeight( height ) //Still not quite a sphere, will see if close enough
+ trigger.SetBelowHeight( height )
+ trigger.SetOrigin( org )
+ DispatchSpawn( trigger )
+ SetupOutOfBoundsTrigger( trigger )
+ trigger.SetEnterCallback( OnOOBTriggerEnter )
+ trigger.SetLeaveCallback( OnOOBTriggerLeave )
+}
+
+void function OnOOBTriggerEnter( entity trigger, entity ent )
+{
+ EntityEnterOutOfBoundsTrig( trigger, ent, null, 0 )
+}
+
+void function OnOOBTriggerLeave( entity trigger, entity ent )
+{
+ EntityLeaveOutOfBoundsTrig( trigger, ent, null, 0 )
+}
+#endif \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_base_gametype_mp.gnut
new file mode 100644
index 000000000..244d323ea
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_base_gametype_mp.gnut
@@ -0,0 +1,586 @@
+untyped
+global function BaseGametype_Init_MPSP
+global function CodeCallback_OnClientConnectionStarted
+global function CodeCallback_OnClientConnectionCompleted
+global function CodeCallback_OnClientDisconnected
+global function CodeCallback_OnPlayerRespawned
+global function CodeCallback_OnPlayerKilled
+global function DecideRespawnPlayer
+global function RespawnAsPilot
+global function RespawnAsTitan
+global function TryGameModeAnnouncement
+
+global function SetKillcamsEnabled
+global function KillcamsEnabled
+
+global function ShouldEntTakeDamage_SPMP
+global function GetTitanBuildTime
+global function TitanPlayerHotDropsIntoLevel
+
+struct {
+ bool killcamsEnabled = true
+
+ entity intermissionCamera
+ array<entity> specCams
+} file
+
+void function BaseGametype_Init_MPSP()
+{
+ AddSpawnCallback( "info_intermission", SetIntermissionCamera )
+ AddCallback_EntitiesDidLoad( SetSpecCams )
+
+ RegisterSignal( "ObserverTargetChanged" )
+ AddClientCommandCallback( "spec_next", ClientCommandCallback_spec_next )
+ AddClientCommandCallback( "spec_prev", ClientCommandCallback_spec_prev )
+ AddClientCommandCallback( "spec_mode", ClientCommandCallback_spec_mode )
+}
+
+void function SetIntermissionCamera( entity camera )
+{
+ file.intermissionCamera = camera
+}
+
+void function SetSpecCams()
+{
+ // spec cams are called spec_cam1,2,3 etc by default, so this is the easiest way to get them imo
+ int camNum = 1
+ entity lastCam = null
+ do {
+ lastCam = GetEnt( "spec_cam" + camNum++ )
+
+ if ( lastCam != null )
+ file.specCams.append( lastCam )
+ } while ( lastCam != null )
+}
+
+void function CodeCallback_OnClientConnectionStarted( entity player )
+{
+ // not a real player?
+ #if DEV
+ if ( player.GetPlayerName() == "Replay" )
+ return
+ #endif
+
+ if ( IsLobby() )
+ {
+ Lobby_OnClientConnectionStarted( player )
+ return
+ }
+
+// ScreenFade( player, 0, 0, 0, 255, 2.0, 0.5, FFADE_IN | FFADE_PURGE )
+
+ SetTargetName( player, "player" + player.entindex() )
+
+ player.p.controllableProjectiles_scriptManagedID = CreateScriptManagedEntArray()
+ player.p.npcFollowersArrayID = CreateScriptManagedEntArray()
+
+ player.s = {}
+ player.s.attackerInfo <- {}
+ player.p.clientScriptInitialized = player.IsBot()
+ player.s.inPostDeath <- null
+ player.s.respawnCount <- 0
+ player.s.respawnTime <- 0
+ player.s.lostTitanTime <- 0
+ player.s.cloakedShotsAllowed <- 0
+ player.s.startDashMeleeTime <- 0
+ player.s.respawnSelectionDone <- true // this gets set to false in postdeaththread but we need it to be true when connecting
+ player.s.waveSpawnProtection <- false
+
+ player.s.nextStatUpdateFunc <- null
+
+ player.s.activeTrapArrayId <- CreateScriptManagedEntArray()
+
+ player.s.restartBurnCardEffectOnSpawn <- false
+ player.s.replacementDropInProgress <- false
+
+ player.s.inGracePeriod <- true
+
+ // should I just add these when playing coop?
+ player.s.usedLoadoutCrate <- false
+ player.s.restockAmmoTime <- 0
+ player.s.restockAmmoCrate <- null
+
+ player.s.autoTitanLastEngageCalloutTime <- 0
+ player.s.autoTitanLastEngageCallout <- null
+ player.s.lastAIConversationTime <- {} // when was a conversation last played?
+
+ player.s.updatedPersistenceOnDisconnect <- false
+
+ player.s.lastFriendlySpawnedOn <- null
+ player.s.nextWaveSpawnTime <- 0.0
+
+ player.s.meleeSlowMoEndTime <- 0.0
+
+ player.p.connectTime = Time()
+
+ Assert( !player._entityVars )
+ InitEntityVars( player )
+
+ // Added via AddCallback_OnClientConnecting
+ foreach ( callbackFunc in svGlobal.onClientConnectingCallbacks )
+ {
+ callbackFunc( player )
+ }
+
+ printl( "Player connect started: " + player )
+
+ InitPassives( player )
+}
+
+// playerconnected
+void function CodeCallback_OnClientConnectionCompleted( entity player )
+{
+ if ( IsLobby() )
+ {
+ Lobby_OnClientConnectionCompleted( player )
+ return
+ }
+
+ player.hasConnected = true
+
+ InitMeleeAnimEventCallbacks( player )
+ ZiplineInit( player )
+
+ UpdateMinimapStatus( player )
+ UpdateMinimapStatusToOtherPlayers( player )
+ MinimapPlayerConnected( player )
+ NotifyClientsOfConnection( player, 1 )
+ PlayCurrentTeamMusicEventsOnPlayer( player )
+ SetCurrentTeamObjectiveForPlayer( player )
+
+ FinishClientScriptInitialization( player )
+
+ // Added via AddCallback_OnClientConnected
+ foreach ( callbackFunc in svGlobal.onClientConnectedCallbacks )
+ {
+ callbackFunc( player )
+ }
+
+ if ( !Flag( "PlayerDidSpawn") )
+ __PlayerDidSpawn( player )
+
+ svGlobal.levelEnt.Signal( "PlayerDidSpawn", { player = player } )
+
+ // handle spawning late joiners
+ if ( GetGameState() == eGameState.Playing )
+ {
+ if ( RespawnsEnabled() )
+ {
+ // likely temp, deffo needs some work
+ if ( Riff_SpawnAsTitan() == 1 ) // spawn as titan
+ thread RespawnAsTitan( player )
+ else // spawn as pilot
+ RespawnAsPilot( player )
+ }
+ else
+ thread PlayerBecomesSpectator( player )
+ }
+}
+
+void function CodeCallback_OnClientDisconnected( entity player, string reason )
+{
+ if ( IsLobby() )
+ {
+ player.Signal( "_disconnectedInternal" )
+ UpdateBadRepPresent()
+ return
+ }
+
+ if ( !player.hasConnected )
+ return
+
+ // Added via AddCallback_OnClientDisconnected
+ foreach ( callbackFunc in svGlobal.onClientDisconnectedCallbacks )
+ {
+ callbackFunc( player )
+ }
+
+ player.Disconnected()
+ player.p.isDisconnected = true
+ player.CleanupMPClasses()
+}
+
+void function CodeCallback_OnPlayerRespawned( entity player )
+{
+ player.Signal( "OnRespawned" ) // kill any postdeaththreads that could be running
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_YouRespawned" )
+ player.s.respawnTime = Time()
+
+ Loadouts_TryGivePilotLoadout( player )
+
+ foreach ( void functionref( entity ) callback in svGlobal.onPlayerRespawnedCallbacks )
+ callback( player )
+}
+
+void function CodeCallback_OnPlayerKilled( entity player, var damageInfo )
+{
+ PlayerOrNPCKilled( player, damageInfo )
+
+ if ( player.IsTitan() )
+ SoulDies( player.GetTitanSoul(), damageInfo ) // cleanup some titan stuff, no idea where else to put this
+
+ thread PostDeathThread_MP( player, damageInfo )
+}
+
+void function PostDeathThread_MP( entity player, var damageInfo ) // based on gametype_sp: postdeaththread_sp
+{
+ // honestly this feels jank af, it's messy and the sp code it's based off is a bit of a pain imo, needs a rewrite at some point
+ // also this likely needs an onthreadend to set a couple values
+
+ //if ( player.p.watchingPetTitanKillReplay )
+ // return
+
+ if ( player.s.inPostDeath )
+ return
+
+ float timeOfDeath = Time()
+ player.p.postDeathThreadStartTime = Time()
+
+ Assert( IsValid( player ), "Not a valid player" )
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnRespawned" )
+
+ player.p.deathOrigin = player.GetOrigin()
+ player.p.deathAngles = player.GetAngles()
+
+ player.s.inPostDeath = true
+ player.s.respawnSelectionDone = false
+
+ player.cloakedForever = false
+ player.stimmedForever = false
+ player.SetNoTarget( false )
+ player.SetNoTargetSmartAmmo( false )
+ player.ClearExtraWeaponMods()
+
+ ClearRespawnAvailable( player )
+
+ OnThreadEnd( function() : ( player )
+ {
+ if ( !IsValid( player ) )
+ return
+
+ player.SetPredictionEnabled( true )
+ player.s.inPostDeath = false
+ })
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ int methodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ player.p.rematchOrigin = player.p.deathOrigin
+ if ( IsValid( attacker ) && methodOfDeath == eDamageSourceId.titan_execution )
+ {
+ // execution can throw you out of the map
+ player.p.rematchOrigin = attacker.GetOrigin()
+ }
+
+ player.Signal( "RodeoOver" )
+ player.ClearParent()
+
+ // do some pre-replay stuff if we're gonna do a replay
+ float replayLength = CalculateLengthOfKillReplay( player, methodOfDeath )
+ bool shouldDoReplay = Replay_IsEnabled() && KillcamsEnabled() && ShouldDoReplay( player, attacker, replayLength, methodOfDeath )
+ table replayTracker = { validTime = null }
+ if ( shouldDoReplay )
+ thread TrackDestroyTimeForReplay( attacker, replayTracker )
+
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( damageSource == eDamageSourceId.fall )
+ {
+ // this is straight up just incorrect lol, based off tf1 stuff
+
+ player.SetObserverModeStaticPosition( player.GetOrigin() )
+ player.SetObserverModeStaticAngles( player.GetVelocity() * -1 )
+
+ player.StartObserverMode( OBS_MODE_STATIC_LOCKED )
+ player.SetObserverTarget( null )
+ }
+ else
+ {
+ player.StartObserverMode( OBS_MODE_DEATHCAM )
+ if ( ShouldSetObserverTarget( attacker ) )
+ player.SetObserverTarget( attacker )
+ else
+ player.SetObserverTarget( null )
+ }
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_YouDied", attacker.GetEncodedEHandle(), GetHealthFrac( attacker ), methodOfDeath )
+
+ float deathcamLength = GetDeathCamLength( player )
+ wait deathcamLength
+
+ // use info_intermission camera after deathcam, if it exists
+ if ( file.intermissionCamera != null )
+ {
+ player.SetObserverModeStaticPosition( file.intermissionCamera.GetOrigin() )
+ player.SetObserverModeStaticAngles( file.intermissionCamera.GetAngles() )
+ player.StartObserverMode( OBS_MODE_STATIC_LOCKED )
+ player.SetObserverTarget( null )
+ }
+
+ // quick note: in cases where player.Die() is called: e.g. for round ends, player == attacker
+ if ( shouldDoReplay )
+ {
+ player.SetPredictionEnabled( false )
+
+ player.watchingKillreplayEndTime = Time() + replayLength
+ float beforeTime = GetKillReplayBeforeTime( player, methodOfDeath )
+
+ replayTracker.validTime <- null
+
+ float respawnTime = Time() - 2 // seems to get the killreplay to end around the actual kill
+ if ( "respawnTime" in attacker.s )
+ respawnTime = Time() - expect float ( attacker.s.respawnTime )
+
+ thread PlayerWatchesKillReplay( player, attacker.GetEncodedEHandle(), attacker.GetIndexForEntity(), respawnTime, timeOfDeath, beforeTime, replayTracker )
+ thread EndReplayOnTime( player, replayLength )
+ }
+
+ player.SetPlayerSettings( "spectator" ) // prevent a crash with going from titan => pilot on respawn
+
+ if ( RespawnsEnabled() )
+ {
+ // is it a good idea to do respawn code in postdeaththread? fuck if i know lol
+ float respawnDelay = max( 0, GetCurrentPlaylistVarFloat( "respawn_delay", 0.0 ) - deathcamLength )
+
+ print( "respawn delay " + respawnDelay )
+
+ UpdateNextRespawnTime( player, Time() + respawnDelay )
+ SetRespawnAvailable( player )
+
+ wait respawnDelay
+
+ player.SetPredictionEnabled( true )
+
+ player.WaitSignal( "RespawnMe" ) // set in base_gametype: ClientCommand_RespawnPlayer
+ ClearRespawnAvailable( player ) // need so the respawn icon doesn't show for like a frame on next death
+
+ if ( ( expect bool( player.GetPersistentVar( "spawnAsTitan" ) ) && IsTitanAvailable( player ) ) || Riff_SpawnAsTitan() == 1 ) // spawn as titan
+ thread RespawnAsTitan( player )
+ else // spawn as pilot
+ RespawnAsPilot( player )
+ }
+ else
+ {
+ thread PlayerBecomesSpectator( player )
+ }
+}
+
+void function EndReplayOnTime( entity player, float replayLength )
+{
+ player.EndSignal( "RespawnMe" )
+ player.EndSignal( "OnRespawned" )
+
+ wait replayLength
+ if ( IsValid( player ) && KillcamsEnabled() )
+ {
+ player.ClearReplayDelay()
+ player.ClearViewEntity()
+ player.SetPredictionEnabled( true )
+
+ player.SetObserverTarget( null )
+ }
+}
+
+void function DecideRespawnPlayer( entity player )
+{
+ // this isn't even used atm, could likely be removed if some vanilla code didn't rely on it
+
+ Assert( IsValid( player ), player + " is invalid!!" )
+ Assert( !IsAlive( player ), player + " is already alive" )
+ Assert( player.hasConnected, player + "isn't connected" )
+
+ if ( GetClassicMPMode() && GetGameState() < eGameState.Playing )
+ return // let intro functions handle spawning if we're in classicmp and not spawned yet
+
+
+}
+
+void function RespawnAsPilot( entity player, bool manualPosition = false )
+{
+ player.RespawnPlayer( FindSpawnPoint( player, false, ShouldStartSpawn( player ) && !IsFFAGame() ) )
+}
+
+void function RespawnAsTitan( entity player, bool manualPosition = false )
+{
+ player.isSpawning = true
+
+ entity spawnpoint = FindSpawnPoint( player, true, ShouldStartSpawn( player ) && !IsFFAGame() )
+
+ TitanLoadoutDef titanLoadout = GetTitanLoadoutForPlayer( player )
+
+ asset model = GetPlayerSettingsAssetForClassName( titanLoadout.setFile, "bodymodel" )
+ Attachment warpAttach = GetAttachmentAtTimeFromModel( model, "at_hotdrop_01", "offset", spawnpoint.GetOrigin(), spawnpoint.GetAngles(), 0 )
+ PlayFX( TURBO_WARP_FX, warpAttach.position, warpAttach.angle )
+
+ player.RespawnPlayer( null ) // spawn player as pilot so they get their pilot loadout on embark
+
+ entity titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, titanLoadout, spawnpoint.GetOrigin(), spawnpoint.GetAngles() )
+ DispatchSpawn( titan )
+ player.SetPetTitan( null ) // prevent embark prompt from showing up
+
+ AddCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD ) // hide hud
+ player.HolsterWeapon() // hide crosshair
+
+ // do titanfall scoreevent
+ AddPlayerScore( player, "Titanfall", player )
+
+ entity camera = CreateTitanDropCamera( spawnpoint.GetAngles(), < 90, 10, 0 > )
+ camera.SetParent( titan )
+
+ // calc offset for spawnpoint angle
+ // todo this seems bad but too lazy to figure it out rn
+ //vector xyOffset = RotateAroundOrigin2D( < 44, 0, 0 >, < 0, 0, 0>, spawnpoint.GetAngles().y )
+ //xyOffset.z = 520 // < 44, 0, 520 > at 0,0,0, seems to be the offset used in tf2
+ //print( xyOffset )
+
+ vector xyOffset = RotateAroundOrigin2D( < 44, 0, 520 >, < 0, 0, 0 >, spawnpoint.GetAngles().y )
+
+ camera.SetLocalOrigin( xyOffset )
+ camera.SetLocalAngles( < camera.GetAngles().x, spawnpoint.GetAngles().y, camera.GetAngles().z > )
+ camera.Fire( "Enable", "!activator", 0, player )
+
+ waitthread TitanHotDrop( titan, "at_hotdrop_01", spawnpoint.GetOrigin(), spawnpoint.GetAngles(), player, camera ) // do hotdrop anim
+
+ camera.Fire( "Disable", "!activator", 0, player ) // stop using the camera
+ camera.Destroy()
+ RemoveCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD ) // show hud
+ player.DeployWeapon() // let them use weapons again
+ player.isSpawning = false
+
+ PilotBecomesTitan( player, titan ) // make player titan
+ titan.Destroy() // pilotbecomestitan leaves an npc titan that we need to delete
+}
+
+
+// spectator stuff
+
+void function PlayerBecomesSpectator( entity player )
+{
+ player.StartObserverMode( OBS_MODE_CHASE )
+
+ player.EndSignal( "OnRespawned" )
+ player.EndSignal( "OnDestroy" )
+
+ int targetIndex = 0
+
+ while ( true )
+ {
+ table result = player.WaitSignal( "ObserverTargetChanged" )
+
+ array<entity> targets
+
+ targets.append( file.intermissionCamera )
+ foreach( entity cam in file.specCams )
+ targets.append( cam )
+
+
+ array<entity> targetPlayers
+ if ( IsFFAGame() )
+ targetPlayers = GetPlayerArray_Alive()
+ else
+ targetPlayers = GetPlayerArrayOfTeam_Alive( player.GetTeam() )
+
+ foreach( entity player in targetPlayers )
+ targets.append( player )
+
+ if ( result.next )
+ targetIndex = ( targetIndex + 1 ) % targets.len()
+ else
+ {
+ if ( targetIndex == 0 )
+ targetIndex = ( targets.len() - 1 )
+ else
+ targetIndex--
+ }
+
+ entity target = targets[ targetIndex ]
+
+ player.StopObserverMode()
+ player.SetSpecReplayDelay( 0.0 ) // clear spectator replay
+
+ if ( target.IsPlayer() )
+ {
+ player.SetObserverTarget( target )
+ player.StartObserverMode( OBS_MODE_CHASE )
+ }
+ else
+ {
+ player.SetObserverModeStaticPosition( target.GetOrigin() )
+ player.SetObserverModeStaticAngles( target.GetAngles() )
+ player.StartObserverMode( OBS_MODE_STATIC )
+ }
+ }
+}
+
+bool function ClientCommandCallback_spec_next( entity player, array<string> args )
+{
+ if ( player.GetObserverMode() == OBS_MODE_CHASE || player.GetObserverMode() == OBS_MODE_STATIC || player.GetObserverMode() == OBS_MODE_IN_EYE )
+ player.Signal( "ObserverTargetChanged", { next = true } )
+
+ return true
+}
+
+bool function ClientCommandCallback_spec_prev( entity player, array<string> args )
+{
+ if ( player.GetObserverMode() == OBS_MODE_CHASE || player.GetObserverMode() == OBS_MODE_STATIC || player.GetObserverMode() == OBS_MODE_IN_EYE )
+ player.Signal( "ObserverTargetChanged", { next = false } )
+
+ return true
+}
+
+bool function ClientCommandCallback_spec_mode( entity player, array<string> args )
+{
+ // currently unsure how this actually gets called on client, works through console and has references in client.dll tho
+ if ( player.GetObserverMode() == OBS_MODE_CHASE )
+ {
+ // set to first person spectate
+ player.SetSpecReplayDelay( FIRST_PERSON_SPECTATOR_DELAY )
+ player.SetViewEntity( player.GetObserverTarget(), true )
+ player.StartObserverMode( OBS_MODE_IN_EYE )
+ }
+ else if ( player.GetObserverMode() == OBS_MODE_IN_EYE )
+ {
+ // set to third person spectate
+ player.SetSpecReplayDelay( 0.0 )
+ player.StartObserverMode( OBS_MODE_CHASE )
+ }
+
+ return true
+}
+
+
+void function TryGameModeAnnouncement( entity player ) // only putting this here because it's here in gametype_sp lol
+{
+ Remote_CallFunction_NonReplay( player, "ServerCallback_GameModeAnnouncement" )
+ PlayFactionDialogueToPlayer( GameMode_GetGameModeAnnouncement( GAMETYPE ), player )
+}
+
+void function SetKillcamsEnabled( bool enabled )
+{
+ file.killcamsEnabled = enabled
+}
+
+bool function KillcamsEnabled()
+{
+ return file.killcamsEnabled
+}
+
+// stuff to change later
+
+
+
+bool function ShouldEntTakeDamage_SPMP( entity ent, var damageInfo )
+{
+ return true
+}
+
+float function GetTitanBuildTime(entity player)
+{
+ return 100.0
+}
+
+void function TitanPlayerHotDropsIntoLevel( entity player )
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_battery_port.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_battery_port.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_battery_port.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut
new file mode 100644
index 000000000..2192b4b1e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut
@@ -0,0 +1,403 @@
+//Bleed Out Mechanic Shared by several game modes.
+global function Bleedout_Init
+global function Bleedout_StartPlayerBleedout
+global function Bleedout_SetCallback_OnPlayerStartBleedout
+global function Bleedout_SetCallback_OnPlayerGiveFirstAid
+global function Bleedout_ShouldAIMissBleedingPlayer
+
+const asset FX_BLOODTRAIL = $"skit_blood_decal_LG"
+const float BLEEDOUT_MAX_USE_DIST2_MOD = 64 * 64
+
+struct
+{
+ table<entity,bool> isBleeding
+ table<entity, entity> IsGettingFirstAidFrom
+ table<entity,entity> lastAttacker
+ void functionref(entity) Callback_OnPlayerStartBleedout
+ void functionref(entity) Callback_OnPlayerGiveFirstAid
+ int firstAidAttemptID = 0 //The ID that identifies the first aid attempt. Used to distinguish between simultainous healing attempts on the client
+} file
+
+void function Bleedout_Init()
+{
+ RegisterSignal( "BleedOut_StopBleeding" )
+ RegisterSignal( "BleedOut_OnRevive" )
+ RegisterSignal( "BleedOut_OnStartDying" )
+ RegisterSignal( "OnContinousUseStopped" )
+
+ AddCallback_OnClientConnected( Bleedout_OnClientConnected )
+ AddCallback_OnClientDisconnected( Bleedout_OnClientDisconnected )
+
+ PrecacheParticleSystem( FX_BLOODTRAIL )
+}
+
+void function Bleedout_OnClientConnected( entity player )
+{
+ file.isBleeding[ player ] <- false
+ file.IsGettingFirstAidFrom[ player ] <- null
+ file.lastAttacker[ player ] <- svGlobal.worldspawn
+}
+
+void function Bleedout_OnClientDisconnected( entity player )
+{
+ delete file.isBleeding[ player ]
+ delete file.IsGettingFirstAidFrom[ player ]
+ delete file.lastAttacker[ player ]
+}
+
+void function Bleedout_SetCallback_OnPlayerStartBleedout( void functionref(entity) callback )
+{
+ file.Callback_OnPlayerStartBleedout = callback
+}
+
+void function Bleedout_SetCallback_OnPlayerGiveFirstAid( void functionref(entity) callback )
+{
+ file.Callback_OnPlayerGiveFirstAid = callback
+}
+
+void function Bleedout_StartPlayerBleedout( entity player, entity attacker )
+{
+ //if the player is already bleeding don't restart bleeding logic.
+ if ( file.isBleeding[ player ] )
+ return
+
+ player.Signal( "BleedOut_StopBleeding" )
+ player.Signal( "BleedOut_OnStartDying" )
+
+ file.lastAttacker[ player ] = attacker
+
+ if ( IsValid( file.Callback_OnPlayerStartBleedout ) && !file.isBleeding[ player ] )
+ file.Callback_OnPlayerStartBleedout( player )
+
+ thread BloodTrail( player )
+ thread PlayerDying( player )
+ thread EnablePlayerRes( player )
+
+ //Start selfhealing thread if enabled.
+ if ( Bleedout_GetSelfResEnabled() )
+ thread EnablePlayerSelfRes( player )
+
+ if ( Bleedout_GetDeathOnTeamBleedout() )
+ CheckForTeamBleedout( player.GetTeam() )
+}
+
+void function PlayerDying( entity player )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "BleedOut_OnRevive" )
+ player.EndSignal( "BleedOut_OnStartDying" )
+
+ float bleedoutTime = Bleedout_GetBleedoutTime()
+ bool forceHolster = Bleedout_GetForceWeaponHolster()
+
+ array<int> ids = []
+ ids.append( StatusEffect_AddEndless( player, eStatusEffect.move_slow, 0.25 ) )
+ ids.append( StatusEffect_AddEndless( player, eStatusEffect.turn_slow, 0.3 ) )
+
+ if ( bleedoutTime > 0 )
+ ids.append( StatusEffect_AddEndless( player, eStatusEffect.bleedoutDOF, 1.0 ) )
+
+ file.isBleeding[ player ] = true
+
+ player.ForceCrouch()
+ player.SetOneHandedWeaponUsageOn()
+
+ if ( forceHolster )
+ HolsterAndDisableWeapons( player )
+
+ OnThreadEnd(
+ function() : ( player, ids, forceHolster )
+ {
+ if ( IsValid( player ) )
+ {
+ foreach ( id in ids )
+ StatusEffect_Stop( player, id )
+
+ file.isBleeding[ player ] = false
+ file.lastAttacker[ player ] = svGlobal.worldspawn
+
+ player.UnforceCrouch()
+ player.SetOneHandedWeaponUsageOff()
+ //Remote_CallFunction_NonReplay( player, "ServerCallback_BLEEDOUT_PlayerRevivedDOF" )
+
+ if ( forceHolster )
+ DeployAndEnableWeapons( player )
+
+ //Hide wounded icon for wounded player's allies
+ int woundedPlayerEHandle = player.GetEncodedEHandle()
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( player.GetTeam() )
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( teamPlayer == player )
+ continue
+ Remote_CallFunction_NonReplay( teamPlayer, "ServerCallback_BLEEDOUT_HideWoundedMarker", woundedPlayerEHandle )
+ }
+ }
+ }
+ )
+
+ //if ( bleedoutTime > 0 )
+ // StatusEffect_AddTimed( player, eStatusEffect.bleedoutDOF, 1.0, bleedoutTime, 0.0 )
+ //Remote_CallFunction_NonReplay( player, "ServerCallback_BLEEDOUT_StartDyingDOF", bleedoutTime )
+
+ //Show wounded icon for wounded player's allies
+ int woundedPlayerEHandle = player.GetEncodedEHandle()
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( player.GetTeam() )
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( teamPlayer == player )
+ continue
+
+ Remote_CallFunction_NonReplay( teamPlayer, "ServerCallback_BLEEDOUT_ShowWoundedMarker", woundedPlayerEHandle, Time(), Time() + bleedoutTime )
+ }
+
+ if ( bleedoutTime > 0 )
+ wait bleedoutTime
+ else
+ WaitForever()
+
+ PlayerDiesFromBleedout( player, file.lastAttacker[ player ] )
+}
+
+void function EnablePlayerRes( entity player )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "BleedOut_OnStartDying" )
+ player.EndSignal( "BleedOut_OnRevive" )
+
+ Highlight_SetFriendlyHighlight( player, "interact_object_los_line" )
+
+ if ( IsPilotEliminationBased() )
+ SetPlayerEliminated( player )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ {
+ player.UnsetUsable()
+ Highlight_ClearFriendlyHighlight( player )
+ }
+ }
+ )
+
+ while ( true )
+ {
+ //If the player is not currently being treated or is self healing. (Team healing should always override self-healing)
+ if ( !IsPlayerGettingFirstAid( player ) || IsPlayerSelfHealing( player ) )
+ {
+ player.SetUsableByGroup( "friendlies pilot" )
+ player.SetUsePrompts( "#BLEEDOUT_USE_TEAMMATE_RES", "#BLEEDOUT_USE_TEAMMATE_RES_PC" )
+
+ entity playerHealer = expect entity ( player.WaitSignal( "OnPlayerUse" ).player )
+ player.UnsetUsable()
+
+ //Player can only res other players if they are not bleeding out themselves.
+ if ( !file.isBleeding[ playerHealer ] && ( !IsPlayerGettingFirstAid( player ) || IsPlayerSelfHealing( player ) ) )
+ waitthread PlayerAttemptRes( playerHealer, player )
+ }
+ else
+ {
+ WaitFrame()
+ }
+ }
+}
+
+void function EnablePlayerSelfRes( entity player )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "BleedOut_OnStartDying" )
+ player.EndSignal( "BleedOut_OnRevive" )
+
+ while ( true )
+ {
+ if ( !IsPlayerGettingFirstAid( player ) )
+ MessageToPlayer( player, eEventNotifications.BLEEDOUT_SelfHealPrompt )
+
+ if ( player.UseButtonPressed() && !IsPlayerGettingFirstAid( player ) )
+ {
+ MessageToPlayer( player, eEventNotifications.Clear )
+ waitthread PlayerAttemptRes( player, player )
+ }
+
+ WaitFrame()
+ }
+}
+
+void function PlayerAttemptRes( entity playerHealer, entity playerToRes )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ playerToRes.EndSignal( "OnDeath" )
+ playerHealer.EndSignal( "OnDeath" )
+ playerHealer.EndSignal( "OnContinousUseStopped" )
+
+ HolsterAndDisableWeapons( playerHealer )
+
+ playerHealer.MovementDisable()
+ playerToRes.MovementDisable()
+
+ float firstAidTime = playerHealer == playerToRes ? Bleedout_GetFirstAidTimeSelf() : Bleedout_GetFirstAidTime()
+ float firstAidHealPercent = Bleedout_GetFirstAidHealPercent()
+
+ float endTime = Time() + firstAidTime
+
+ int playerEHandle = playerToRes.GetEncodedEHandle()
+ int healerEHandle = playerHealer.GetEncodedEHandle()
+ int attemptID = GetNewFirstAidAttemptID()
+
+ Remote_CallFunction_NonReplay( playerToRes, "ServerCallback_BLEEDOUT_StartFirstAidProgressBar", endTime, playerEHandle, healerEHandle, attemptID )
+ Remote_CallFunction_NonReplay( playerHealer, "ServerCallback_BLEEDOUT_StartFirstAidProgressBar", endTime, playerEHandle, healerEHandle, attemptID )
+ file.IsGettingFirstAidFrom[ playerToRes ] = playerHealer
+
+ OnThreadEnd(
+ function() : ( playerHealer, playerToRes, attemptID )
+ {
+ if ( IsValid( playerHealer ) )
+ {
+ DeployAndEnableWeapons( playerHealer )
+ playerHealer.MovementEnable()
+ Remote_CallFunction_NonReplay( playerHealer, "ServerCallback_BLEEDOUT_StopFirstAidProgressBar", attemptID )
+ }
+
+ if ( IsValid( playerToRes ) )
+ {
+ file.IsGettingFirstAidFrom[ playerToRes ] = null
+ playerToRes.MovementEnable()
+ Remote_CallFunction_NonReplay( playerToRes, "ServerCallback_BLEEDOUT_StopFirstAidProgressBar", attemptID )
+ }
+ }
+ )
+
+ waitthread TrackContinuousUse( playerHealer, playerToRes, firstAidTime, true )
+
+ //Heal player health
+ playerToRes.SetHealth( playerToRes.GetMaxHealth() * firstAidHealPercent )
+ file.isBleeding[ playerToRes ] = false
+ file.lastAttacker[ playerToRes ] = svGlobal.worldspawn
+ if ( IsPilotEliminationBased() )
+ ClearPlayerEliminated( playerToRes )
+
+ if ( IsValid( file.Callback_OnPlayerGiveFirstAid ) )
+ {
+ //Do not run this callback if player is self healing.
+ if ( playerHealer != playerToRes )
+ file.Callback_OnPlayerGiveFirstAid( playerHealer )
+ }
+
+ playerToRes.Signal( "BleedOut_OnRevive" )
+
+}
+
+void function BloodTrail( entity player )
+{
+ player.EndSignal( "BleedOut_StopBleeding" )
+ player.EndSignal( "BleedOut_OnRevive" )
+ player.EndSignal( "OnDeath")
+
+ while ( true )
+ {
+ float interval = RandomFloatRange( 0.25, 0.5 )
+ PlayFXOnEntity( FX_BLOODTRAIL, player )
+ wait interval
+ }
+}
+
+void function PlayerDiesFromBleedout( entity player, entity attacker )
+{
+ if ( IsValid( attacker ) )
+ {
+ player.Die( attacker, attacker, { damageSourceId = eDamageSourceId.bleedout } )
+ //player.BecomeRagdoll( Vector(0,0,0), false )
+ }
+ else
+ {
+ player.Die( svGlobal.worldspawn, svGlobal.worldspawn, { damageSourceId = eDamageSourceId.bleedout } )
+ //player.BecomeRagdoll( Vector(0,0,0), false )
+ }
+
+
+}
+
+//This function checks to see if all players on a team are dead or bleeding out.
+//If all the players are dead/bleeding out, it kills the surviving team players.
+void function CheckForTeamBleedout( int team )
+{
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( team )
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( IsAlive( teamPlayer ) && !file.isBleeding[ teamPlayer ] )
+ return
+ }
+
+ //All players on team are bleeding out
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( IsAlive( teamPlayer ) )
+ PlayerDiesFromBleedout( teamPlayer, file.lastAttacker[ teamPlayer ] )
+ }
+}
+
+bool function Bleedout_ShouldAIMissBleedingPlayer( entity player )
+{
+ //If the player is not bleeding
+ if ( !file.isBleeding[ player ] )
+ return false
+
+ //If the bleedout settings don't affect AI accuracy.
+ if ( !Bleedout_ShouldAIMissPlayer() )
+ return false
+
+ return true
+}
+
+bool function IsPlayerGettingFirstAid( entity player )
+{
+ return file.IsGettingFirstAidFrom[ player ] != null
+}
+
+bool function IsPlayerSelfHealing( entity player )
+{
+ return file.IsGettingFirstAidFrom[ player ] == player
+}
+
+//////////////
+//Utilities
+//////////////
+void function TrackContinuousUse( entity player, entity useTarget, float useTime, bool doRequireUseButtonHeld )
+{
+ player.EndSignal( "OnDeath" )
+ useTarget.EndSignal( "OnDeath" )
+ useTarget.EndSignal( "OnDestroy" )
+
+ table result = {}
+ result.success <- false
+
+ float maxDist2 = DistanceSqr( player.GetOrigin(), useTarget.GetOrigin() ) + BLEEDOUT_MAX_USE_DIST2_MOD
+
+ OnThreadEnd
+ (
+ function() : ( player, result )
+ {
+ if ( !result.success )
+ {
+ player.Signal( "OnContinousUseStopped" )
+ }
+ }
+ )
+
+ float startTime = Time()
+ while ( Time() < startTime + useTime && (!doRequireUseButtonHeld || player.UseButtonPressed()) && !player.IsPhaseShifted() && DistanceSqr( player.GetOrigin(), useTarget.GetOrigin() ) <= maxDist2 )
+ WaitFrame()
+
+ if ( ( !doRequireUseButtonHeld || player.UseButtonPressed() ) && DistanceSqr( player.GetOrigin(), useTarget.GetOrigin() ) <= maxDist2 )
+ result.success = true
+}
+
+int function GetNewFirstAidAttemptID()
+{
+ file.firstAidAttemptID += 1
+ return file.firstAidAttemptID
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_challenges.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_challenges.gnut
new file mode 100644
index 000000000..466a50425
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_challenges.gnut
@@ -0,0 +1,6 @@
+global function InitChallenges
+
+void function InitChallenges()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_changemap.nut b/Northstar.CustomServers/scripts/vscripts/mp/_changemap.nut
new file mode 100644
index 000000000..4d6dc3937
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_changemap.nut
@@ -0,0 +1,24 @@
+global function CodeCallback_MatchIsOver
+
+
+void function CodeCallback_MatchIsOver()
+{
+ foreach ( entity player in GetPlayerArray() )
+ SavePdataForEntityIndex( player.GetPlayerIndex() )
+
+ if ( !IsPrivateMatch() && IsMatchmakingServer() )
+ SetUIVar( level, "putPlayerInMatchmakingAfterDelay", true )
+ else
+ SetUIVar( level, "putPlayerInMatchmakingAfterDelay", false )
+
+ if ( GetCurrentPlaylistVarInt( "return_to_private_lobby", 0 ) == 1 ) // set in _private_lobby.gnut, temp lol
+ {
+ SetCurrentPlaylist( "private_match" ) // needed for private lobby to load
+ ServerCommand( "changelevel mp_lobby" )
+ }
+
+#if DEV
+ if ( !IsMatchmakingServer() )
+ GameRules_ChangeMap( "mp_lobby", GAMETYPE )
+#endif // #if DEV
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp.nut b/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp.nut
new file mode 100644
index 000000000..d6ac8f55a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp.nut
@@ -0,0 +1,67 @@
+untyped
+global function ClassicMp_Init
+global function ClassicMP_TryDefaultIntroSetup // called in mp_sh_init
+global function ClassicMP_SetCustomIntro
+global function ClassicMP_OnIntroStarted
+global function ClassicMP_OnIntroFinished
+global function ClassicMP_GetIntroLength
+global function GetClassicMPMode
+
+struct {
+ void functionref() introSetupFunc
+ float introLength
+} file
+
+void function ClassicMp_Init()
+{
+ // literally nothing to do here atm lol
+}
+
+void function ClassicMP_TryDefaultIntroSetup()
+{
+ if ( file.introSetupFunc == null )
+ {
+ if ( IsFFAGame() )
+ ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, NOINTRO_INTRO_LENGTH )
+ else
+ ClassicMP_SetCustomIntro( ClassicMP_DefaultDropshipIntro_Setup, DROPSHIP_INTRO_LENGTH )
+ }
+
+ thread DelayedDoDefaultIntroSetup()
+}
+
+void function DelayedDoDefaultIntroSetup()
+{
+ // wait a frame for CodeCallback_MapInit to run which generally sets custom intros
+ WaitFrame()
+ file.introSetupFunc()
+}
+
+void function ClassicMP_SetCustomIntro( void functionref() setupFunc, float introLength )
+{
+ file.introSetupFunc = setupFunc
+ file.introLength = introLength
+}
+
+void function ClassicMP_OnIntroStarted()
+{
+ print( "started intro!" )
+ SetServerVar( "gameStartTime", Time() + file.introLength )
+ SetServerVar( "roundStartTime", Time() + file.introLength )
+}
+
+void function ClassicMP_OnIntroFinished()
+{
+ print( "intro finished!" )
+ SetGameState( eGameState.Playing )
+}
+
+float function ClassicMP_GetIntroLength()
+{
+ return file.introLength
+}
+
+bool function GetClassicMPMode()
+{
+ return GetCurrentPlaylistVarInt( "classic_mp", 1 ) == 1
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
new file mode 100644
index 000000000..20455c696
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
@@ -0,0 +1,230 @@
+untyped
+global function ClassicMP_DefaultDropshipIntro_Setup
+
+const array<string> DROPSHIP_IDLE_ANIMS = [ "Classic_MP_flyin_exit_playerA_idle",
+ "Classic_MP_flyin_exit_playerB_idle",
+ "Classic_MP_flyin_exit_playerC_idle",
+ "Classic_MP_flyin_exit_playerD_idle" ]
+
+const array<string> DROPSHIP_IDLE_ANIMS_POV = [ "Classic_MP_flyin_exit_povA_idle",
+ "Classic_MP_flyin_exit_povB_idle",
+ "Classic_MP_flyin_exit_povC_idle",
+ "Classic_MP_flyin_exit_povD_idle" ]
+
+const array<string> DROPSHIP_JUMP_ANIMS = [ "Classic_MP_flyin_exit_playerA_jump",
+ "Classic_MP_flyin_exit_playerB_jump",
+ "Classic_MP_flyin_exit_playerC_jump",
+ "Classic_MP_flyin_exit_playerD_jump" ]
+
+const array<string> DROPSHIP_JUMP_ANIMS_POV = [ "Classic_MP_flyin_exit_povA_jump",
+ "Classic_MP_flyin_exit_povB_jump",
+ "Classic_MP_flyin_exit_povC_jump",
+ "Classic_MP_flyin_exit_povD_jump" ]
+
+const array<int> DROPSHIP_ANIMS_YAW = [ -18, 8, 8, -16 ]
+
+global const float DROPSHIP_INTRO_LENGTH = 15.0 // TODO tweak this
+
+struct IntroDropship
+{
+ entity dropship
+
+ int playersInDropship
+ entity[4] players
+}
+
+struct {
+ IntroDropship[2] militiaDropships
+ IntroDropship[2] imcDropships
+
+ float introStartTime
+ int numPlayersInIntro
+} file
+
+
+void function ClassicMP_DefaultDropshipIntro_Setup()
+{
+ AddCallback_OnClientConnected( DropshipIntro_OnClientConnected )
+ AddCallback_OnClientDisconnected( DropshipIntro_OnClientDisconnected )
+
+ AddCallback_GameStateEnter( eGameState.Prematch, OnPrematchStart )
+}
+
+void function DropshipIntro_OnClientConnected( entity player )
+{
+ // find the player's team's dropships
+ IntroDropship[2] teamDropships = player.GetTeam() == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
+
+ // find a dropship with an empty slot
+ foreach ( IntroDropship dropship in teamDropships )
+ if ( dropship.playersInDropship < 4 )
+ // we've found a valid dropship
+ // find an empty player slot
+ for ( int i = 0; i < dropship.players.len(); i++ )
+ if ( dropship.players[ i ] == null ) // empty slot
+ {
+ dropship.players[ i ] = player
+ dropship.playersInDropship++
+
+ // spawn player into intro if we're already doing intro
+ if ( GetGameState() == eGameState.Prematch )
+ thread SpawnPlayerIntoDropship( player )
+
+ return
+ }
+
+}
+
+void function DropshipIntro_OnClientDisconnected( entity player )
+{
+ // find the player's dropship
+ IntroDropship[2] teamDropships = player.GetTeam() == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
+
+ // find the player
+ foreach ( IntroDropship dropship in teamDropships )
+ for ( int i = 0; i < dropship.players.len(); i++ )
+ if ( dropship.players[ i ] == player )
+ {
+ // we've found the player, remove them
+ dropship.players[ i ] = null
+ dropship.playersInDropship--
+
+ return
+ }
+}
+
+void function OnPrematchStart()
+{
+ ClassicMP_OnIntroStarted()
+
+ print( "starting dropship intro!" )
+ file.introStartTime = Time()
+
+ // spawn dropships
+ array<entity> dropshipSpawns = GetEntArrayByClass_Expensive( "info_spawnpoint_dropship_start" )
+ foreach ( entity dropshipSpawn in dropshipSpawns )
+ {
+ if ( GameModeRemove( dropshipSpawn ) )
+ continue
+
+ // todo: possibly make this only spawn dropships if we've got enough players to need them
+ int createTeam = GetServerVar( "switchedSides" ) != 1 ? dropshipSpawn.GetTeam() : GetOtherTeam( dropshipSpawn.GetTeam() )
+ IntroDropship[2] teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
+ int dropshipIndex = !IsValid( teamDropships[ 0 ].dropship ) ? 0 : 1
+
+ // create entity
+ entity dropship = CreateDropship( createTeam, dropshipSpawn.GetOrigin(), dropshipSpawn.GetAngles() )
+
+ teamDropships[ dropshipIndex ].dropship = dropship
+ AddAnimEvent( dropship, "dropship_warpout", WarpoutEffect )
+
+ DispatchSpawn( dropship )
+
+ // have to do this after dispatch otherwise it won't work for some reason
+ dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+ // could also use $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl", unsure which
+
+ thread PlayAnim( dropship, "dropship_classic_mp_flyin" )
+ }
+
+ foreach ( entity player in GetPlayerArray() )
+ thread SpawnPlayerIntoDropship( player )
+}
+
+void function SpawnPlayerIntoDropship( entity player )
+{
+ if ( IsAlive( player ) )
+ player.Die() // kill them so we don't have any issues respawning them later
+
+ WaitFrame()
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "Disconnected" )
+
+ file.numPlayersInIntro++
+
+ // find the player's dropship and seat
+ IntroDropship[2] teamDropships = player.GetTeam() == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
+ IntroDropship playerDropship
+ int playerDropshipIndex
+ foreach ( IntroDropship dropship in teamDropships )
+ for ( int i = 0; i < dropship.players.len(); i++ )
+ if ( dropship.players[ i ] == player )
+ {
+ playerDropship = dropship
+ playerDropshipIndex = i
+
+ break
+ }
+
+ // figure out what anims we're using for idle
+ string idleAnim = DROPSHIP_IDLE_ANIMS[ playerDropshipIndex ]
+ string idleAnimPov = DROPSHIP_IDLE_ANIMS_POV[ playerDropshipIndex ]
+
+ FirstPersonSequenceStruct idleSequence
+ idleSequence.firstPersonAnim = idleAnimPov
+ idleSequence.thirdPersonAnim = idleAnim
+ idleSequence.attachment = "ORIGIN"
+ idleSequence.teleport = true
+ idleSequence.viewConeFunction = ViewConeRampFree
+ idleSequence.hideProxy = true
+ idleSequence.setInitialTime = Time() - file.introStartTime
+
+ // respawn player and holster their weapons so they aren't out
+ player.RespawnPlayer( null )
+ player.DisableWeaponViewModel()
+
+ // hide hud and fade screen out from black
+ AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
+ ScreenFadeFromBlack( player, 0.5, 0.5 )
+ // faction leaders are done clientside, spawn them here
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.dropship.GetEncodedEHandle(), file.introStartTime )
+ thread FirstPersonSequence( idleSequence, player, playerDropship.dropship )
+
+ // wait until the anim is done
+ WaittillAnimDone( player ) // unsure if this is the best way to do this
+ // todo: possibly rework this to actually get the time the idle anim takes and start the starttime of the jump sequence for very late joiners using that
+
+ // honestly go rewrite alot of this too it's messy
+
+ // figure out what anims we're using for jump
+ string jumpAnim = DROPSHIP_JUMP_ANIMS[ playerDropshipIndex ]
+ string jumpAnimPov = DROPSHIP_JUMP_ANIMS_POV[ playerDropshipIndex ]
+
+ FirstPersonSequenceStruct jumpSequence
+ jumpSequence.firstPersonAnim = jumpAnimPov
+ jumpSequence.thirdPersonAnim = jumpAnim
+ jumpSequence.attachment = "ORIGIN"
+ //jumpSequence.setInitialTime = Time() - ( file.introStartTime + player.GetSequenceDuration( idleAnim ) )
+ jumpSequence.setInitialTime = Time() - ( file.introStartTime + 10.9 ) // pretty sure you should do this with GetScriptedAnimEventCycleFrac?
+ // idk unsure how to use that, all i know is getsequenceduration > the length it actually should be
+
+ thread FirstPersonSequence( jumpSequence, player, playerDropship.dropship )
+ WaittillAnimDone( player )
+
+ // unparent player and their camera from the dropship
+ player.ClearParent()
+ ClearPlayerAnimViewEntity( player )
+
+ file.numPlayersInIntro--
+ if ( file.numPlayersInIntro == 0 )
+ ClassicMP_OnIntroFinished() // set intro as finished
+
+ // wait for intro timer to be fully done
+ wait( Time() - ( file.introStartTime + DROPSHIP_INTRO_LENGTH ) )
+ player.MovementDisable() // disable all movement but let them look around still
+ player.ConsumeDoubleJump() // movementdisable doesn't prevent double jumps
+
+ // wait for player to hit the ground
+ while ( !player.IsOnGround() && !player.IsWallRunning() && !player.IsWallHanging() ) // todo this needs tweaking
+ WaitFrame()
+
+ // show weapon viewmodel and hud and let them move again
+ player.MovementEnable()
+ player.EnableWeaponViewModel()
+ RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
+
+ if ( GetServerVar( "switchedSides" ) != 1 )
+ TryGameModeAnnouncement( player )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp_no_intro.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp_no_intro.gnut
new file mode 100644
index 000000000..50e9e9a07
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_classic_mp_no_intro.gnut
@@ -0,0 +1,46 @@
+untyped
+
+global function ClassicMP_DefaultNoIntro_Setup
+global const float NOINTRO_INTRO_LENGTH = 10.0
+
+void function ClassicMP_DefaultNoIntro_Setup()
+{
+ AddCallback_OnClientConnected( ClassicMP_DefaultNoIntro_SpawnPlayer )
+ AddCallback_GameStateEnter( eGameState.Prematch, ClassicMP_DefaultNoIntro_Start )
+}
+
+void function ClassicMP_DefaultNoIntro_Start()
+{
+ ClassicMP_OnIntroStarted()
+
+ foreach ( entity player in GetPlayerArray() )
+ thread ClassicMP_DefaultNoIntro_SpawnPlayer( player )
+
+ wait NOINTRO_INTRO_LENGTH
+
+ foreach ( entity player in GetPlayerArray() )
+ {
+ player.UnfreezeControlsOnServer()
+ RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
+ TryGameModeAnnouncement( player )
+ }
+
+ ClassicMP_OnIntroFinished()
+}
+
+void function ClassicMP_DefaultNoIntro_SpawnPlayer( entity player )
+{
+ if ( GetGameState() != eGameState.Prematch )
+ return
+
+ if ( IsAlive( player ) )
+ {
+ player.Die()
+ WaitFrame()
+ }
+
+ RespawnAsPilot( player )
+ player.FreezeControlsOnServer()
+ AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
+ ScreenFadeFromBlack( player, 0.5, 0.5 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut
new file mode 100644
index 000000000..2e5651422
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut
@@ -0,0 +1,999 @@
+untyped
+
+
+global function CodeCallback_Init
+global function CodeCallback_DamagePlayerOrNPC
+global function GameModeRulesShouldGiveTimerCredit
+global function SetGameModeRulesShouldGiveTimerCredit
+global function SetGameModeRulesEarnMeterOnDamage
+global function GetDamageOrigin
+global function CodeCallBack_ShouldTriggerSniperCam
+global function CodeCallback_ForceAIMissPlayer
+global function CodeCallback_OnTouchHealthKit
+global function CodeCallback_OnPlayerGrappled
+global function CodeCallback_OnProjectileGrappled
+global function DamageInfo_ScaleDamage
+global function CodeCallback_CheckPassThroughAddsMods
+global function SetTitanMeterGainScale
+
+#if MP
+global function CodeCallback_OnServerAnimEvent
+#endif
+
+struct AccumulatedDamageData
+{
+ float accumulatedDamage
+ float lastDamageTime
+}
+
+struct
+{
+ float titanMeterGainScale = 0.0001
+ bool functionref( entity, entity, var ) ShouldGiveTimerCreditGameModeRules
+ void functionref( entity, entity, TitanDamage, float ) earnMeterOnDamageGameModeRulesCallback
+
+ table<entity, AccumulatedDamageData> playerAccumulatedDamageData
+} file
+
+void function CodeCallback_Init()
+{
+ file.ShouldGiveTimerCreditGameModeRules = ShouldGiveTimerCredit_Default
+ file.earnMeterOnDamageGameModeRulesCallback = GameModeRulesEarnMeterOnDamage_Default
+ RegisterSignal( "DamagedPlayerOrNPC" )
+ RegisterSignal( "UpdateAccumulatedDamageAfterDelay" )
+
+ AddCallback_OnClientConnected( OnClientConnected )
+}
+
+void function OnClientConnected( entity player )
+{
+ AccumulatedDamageData damageData
+ file.playerAccumulatedDamageData[player] <- damageData
+}
+
+// TODO: Get an equivalent callback happening on the client, so we can stop using ServerCallback_PlayerTookDamage which is always out of date to some degree.
+void function CodeCallback_DamagePlayerOrNPC( entity ent, var damageInfo )
+{
+ bool entIsPlayer = ent.IsPlayer()
+ bool entIsTitan = ent.IsTitan()
+ bool entIsNPC = ent.IsNPC()
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+
+ bool attackerIsPlayer = false
+ bool attackerIsTitan = false
+ bool attackerIsNPC = false
+
+ if ( IsValid( attacker ) )
+ {
+ attackerIsPlayer = attacker.IsPlayer()
+ attackerIsTitan = attacker.IsTitan()
+ attackerIsNPC = attacker.IsNPC()
+ }
+
+ // Set damage source correctly when npc grunts or titans try to melee us
+ if ( attackerIsNPC && DamageInfo_GetCustomDamageType( damageInfo ) & DF_MELEE )
+ {
+ if ( IsValid( attacker ) )
+ {
+ if ( attackerIsTitan )
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.auto_titan_melee )
+ }
+ else if ( IsSpectre( attacker ) )
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.spectre_melee )
+ }
+ else if ( IsProwler( attacker ) )
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.prowler_melee )
+ }
+ else if ( IsSuperSpectre( attacker ) )
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.super_spectre_melee )
+ }
+ else
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.grunt_melee )
+ }
+ }
+ }
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( "CodeCallback_DamagePlayerOrNPC ent:", ent )
+ printt( " Attacker:", DamageInfo_GetAttacker( damageInfo ) )
+ printt( " Inflictor:", DamageInfo_GetInflictor( damageInfo ) )
+ printt( " Distance:", DamageInfo_GetDistFromAttackOrigin( damageInfo ) )
+ printt( " Original damage:", DamageInfo_GetDamage( damageInfo ) )
+ printt( " Hitbox:", DamageInfo_GetHitBox( damageInfo ) )
+ int sourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ printt( " SourceID:", sourceID )
+ if ( sourceID == -1 )
+ printt( " SourceID: From Code (npc melee, etc)" )
+ else
+ printt( " SourceID:", GetObitFromDamageSourceID( sourceID ) )
+
+ PrintDamageFlags( DamageInfo_GetCustomDamageType( damageInfo ) )
+ #endif
+
+ if ( !ScriptCallback_ShouldEntTakeDamage( ent, damageInfo ) )
+ {
+ // EMP triggers on damage, but in some cases players are invlunerable (embark, disembark, etc...)
+ if ( entIsPlayer && DamageInfo_GetDamageSourceIdentifier( damageInfo ) in level._empForcedCallbacks )
+ {
+ if ( ShouldPlayEMPEffectEvenWhenDamageIsZero( ent, attacker ) )
+ EMP_DamagedPlayerOrNPC( ent, damageInfo )
+ }
+
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ if ( ( IsAirDrone( ent ) ) && ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) in level._empForcedCallbacks ) )
+ {
+ EMP_DamagedPlayerOrNPC( ent, damageInfo )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == damagedef_titan_step )
+ HandleFootstepDamage( ent, damageInfo )
+
+ // HACK helps trap/grenade weapons do damage to the correct entities (player who deployed it as well as the team opposite his)
+ if ( IsValid( inflictor ) && "originalOwner" in inflictor.s )
+ {
+ local ogOwner = inflictor.s.originalOwner
+ if ( IsValid( ogOwner ) )
+ {
+ // if the victim is the guy who damaged the trap, and he is not the ogOwner...
+ if ( ent == attacker && ent != ogOwner )
+ {
+ // HACK to do this legit we need DamageInfo_SetAttacker( damageInfo )
+ // victim should take damage from the original owner instead of the satchel attacker so he gets a kill credit
+ ent.TakeDamage( DamageInfo_GetDamage( damageInfo ), ogOwner, inflictor, { weapon = DamageInfo_GetWeapon( damageInfo ), origin = DamageInfo_GetDamagePosition( damageInfo ), force = DamageInfo_GetDamageForce( damageInfo ), scriptType = DamageInfo_GetCustomDamageType( damageInfo ), damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo ) } )
+
+ // now zero out the normal damage and return
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+ }
+ }
+
+ if ( IsValid( inflictor ) )
+ {
+ if ( inflictor.IsProjectile() && entIsPlayer )
+ {
+ if ( inflictor.proj.damageScale != 1.0 )
+ {
+ DamageInfo_ScaleDamage( damageInfo, inflictor.proj.damageScale )
+ }
+
+ // Don't take damage from projectiles created before you where spawned.
+ if ( inflictor.GetProjectileCreationTime() < ent.s.respawnTime && ( Time() - ent.s.respawnTime ) < 2.0 )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+ }
+
+ if ( inflictor.e.onlyDamageEntitiesOnce == true || inflictor.e.onlyDamageEntitiesOncePerTick == true )
+ {
+ Assert( !inflictor.e.damagedEntities.contains(ent) )
+ inflictor.e.damagedEntities.append( ent )
+ }
+ }
+
+ // Round damage to nearest full value
+ DamageInfo_SetDamage( damageInfo, floor( DamageInfo_GetDamage( damageInfo ) + 0.5 ) )
+ if ( DamageInfo_GetDamage( damageInfo ) <= 0 )
+ return
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " rounded damage amount:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ HandleLocationBasedDamage( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after location based damage:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ //PROTO Defensive AI Chip. Ideally less invisible gameplay, but something that can combo with other chips.
+ if ( ent.IsTitan() && entIsNPC )
+ {
+ entity soul = ent.GetTitanSoul()
+ if ( IsValid( soul ) && SoulHasPassive( soul, ePassives.PAS_GUARDIAN_CHIP ) )
+ {
+ DamageInfo_SetDamage( damageInfo, DamageInfo_GetDamage( damageInfo ) * 0.8 )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( "After guardian chip :", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+ }
+ }
+
+ RunClassDamageCallbacks( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after class damage callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
+ // use AddDamageByCallback( "classname", function ) to registed functions
+ if ( IsValid( attacker ) )
+ {
+ if ( attackerIsTitan )
+ {
+ entity soul = attacker.GetTitanSoul()
+ if ( IsValid( soul ) )
+ {
+ float damageAmpScale = 1.0 + StatusEffect_Get( soul, eStatusEffect.titan_damage_amp )
+ if ( damageAmpScale != 1.0 )
+ DamageInfo_ScaleDamage( damageInfo, damageAmpScale )
+ }
+ }
+
+ string attackerClassName = attacker.GetClassName()
+ if ( attackerClassName in svGlobal.damageByCallbacks )
+ {
+ foreach ( callbackFunc in svGlobal.damageByCallbacks[attackerClassName] )
+ {
+ callbackFunc( ent, damageInfo )
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+ }
+ }
+ }
+
+ float damageMultiplier = 1.0 + StatusEffect_Get( ent, eStatusEffect.damage_received_multiplier )
+ if ( damageMultiplier != 1.0 )
+ DamageInfo_ScaleDamage( damageInfo, damageMultiplier )
+
+ // Added via AddEntityCallback_OnDamaged
+ foreach ( callbackFunc in ent.e.entDamageCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after AddEntityCallback_OnDamaged callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ // use AddDamageCallbackSourceID( "classname", function ) to registed functions
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( damageSourceId in shGlobal.damageSourceIdCallbacks )
+ {
+ foreach ( callbackFunc in shGlobal.damageSourceIdCallbacks[ damageSourceId ] )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+ }
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after damageSourceID callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
+ RunClassDamageFinalCallbacks( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after class damage final callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ DamageInfo_AddDamageFlags( damageInfo, DAMAGEFLAG_NOPAIN )
+
+ float savedDamage = DamageInfo_GetDamage( damageInfo )
+
+ TitanDamage titanDamage
+ if ( entIsPlayer )
+ {
+ PlayerTookDamage( ent, damageInfo, attacker, inflictor, damageSourceId, titanDamage )
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 && entIsTitan )
+ {
+ EarnMeterDamageConversion( damageInfo, attacker, ent, 0, titanDamage )
+ return
+ }
+
+ if ( attackerIsPlayer )
+ PlayerDamageFeedback( ent, damageInfo )
+ savedDamage = DamageInfo_GetDamage( damageInfo )
+
+ if ( !entIsTitan )
+ ent.SetCloakFlicker( 0.5, 0.65 )
+ }
+ else
+ {
+ Assert( entIsNPC )
+ bool clearedDamage
+ if ( ent.ai.buddhaMode )
+ {
+ float currentDamage = DamageInfo_GetDamage( damageInfo )
+ int remainingHealth = ent.GetHealth()
+
+ if ( currentDamage >= remainingHealth - ( DOOMED_MIN_HEALTH + 1 ) )
+ {
+ currentDamage = max( remainingHealth - ( DOOMED_MIN_HEALTH + 1 ), 0 )
+ DamageInfo_SetDamage( damageInfo, currentDamage )
+ clearedDamage = currentDamage == 0
+ }
+ }
+
+ if ( !clearedDamage )
+ {
+ if ( entIsTitan )
+ {
+ Titan_NPCTookDamage( ent, damageInfo, titanDamage )
+ savedDamage = DamageInfo_GetDamage( damageInfo )
+ }
+ else
+ {
+ Generic_NPCTookDamage( ent, damageInfo, titanDamage )
+ }
+ }
+
+ if ( attackerIsPlayer )
+ PlayerDamageFeedback( ent, damageInfo )
+ }
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " After player damage mod:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ if ( titanDamage.shieldDamage > 0 )
+ printt( " Shield Damage:", titanDamage.shieldDamage )
+ #endif
+
+ // Added via AddEntityCallback_OnPostDamaged
+ foreach ( callbackFunc in ent.e.entPostDamageCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+
+ UpdateLastDamageTime( ent )
+
+ //pain sounds _base_gametype.nut, death sounds in _death_package.nut
+ UpdateDamageState( ent, damageInfo )
+ HandlePainSounds( ent, damageInfo )
+
+ UpdateAttackerInfo( ent, attacker, savedDamage )
+
+ if ( !(DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS) )
+ {
+ if ( attackerIsPlayer )
+ {
+ if ( entIsTitan )
+ {
+ PlayerDealtTitanDamage( attacker, ent, savedDamage, damageInfo )
+
+ entity entSoul = ent.GetTitanSoul()
+ if ( attacker.p.currentTargetPlayerOrSoul_Ent != entSoul )
+ {
+ attacker.p.currentTargetPlayerOrSoul_Ent = ent.GetTitanSoul()
+
+ TitanVO_TellPlayersThatAreAlsoFightingThisTarget( attacker, entSoul )
+ }
+ attacker.p.currentTargetPlayerOrSoul_LastHitTime = Time()
+ }
+ else if ( entIsPlayer )
+ {
+ attacker.p.currentTargetPlayerOrSoul_Ent = ent
+ attacker.p.currentTargetPlayerOrSoul_LastHitTime = Time()
+ }
+ }
+ }
+
+ EarnMeterDamageConversion( damageInfo, attacker, ent, savedDamage, titanDamage )
+
+ if ( entIsTitan )
+ {
+ TitanDamageFlinch( ent, damageInfo )
+
+ if ( TitanDamageRewardsTitanCoreTime() && entIsPlayer && attacker.GetTeam() != ent.GetTeam() )
+ AddCreditToTitanCoreBuilderForTitanDamageReceived( ent, savedDamage )
+ }
+
+ if ( entIsPlayer && !entIsTitan )
+ PilotDamageFlinch( ent, damageInfo )
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " final damage done:", DamageInfo_GetDamage( damageInfo ) )
+ printt( " health: " + ent.GetHealth() )
+ #endif
+
+ RunClassPostDamageCallbacks( ent, damageInfo )
+
+ #if SERVER && MP
+ Stats_OnPlayerDidDamage( ent, damageInfo )
+ PIN_DamageDone( attacker, ent, DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ attacker.Signal( "DamagedPlayerOrNPC" )
+}
+
+void function EarnMeterDamageConversion( var damageInfo, entity attacker, entity ent, float savedDamage, TitanDamage titanDamage )
+{
+ if ( !(DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS) )
+ {
+ bool shouldGiveTimerCredit = file.ShouldGiveTimerCreditGameModeRules( attacker, ent, damageInfo )
+ if ( attacker.IsPlayer() )
+ {
+ float titanSpawnDelay = GetTitanBuildTime( attacker )
+ float timerCredit = 0.0
+
+ if ( shouldGiveTimerCredit )
+ {
+ file.earnMeterOnDamageGameModeRulesCallback( attacker, ent, titanDamage, savedDamage )
+
+ // Timer Credit seems unused. Need to investigate if all DecrementBuildTimer functions are worthless.
+ if ( titanSpawnDelay && IsAlive( ent ) && GetCurrentPlaylistVarInt( "titan_build_credit_enabled", 1 ) == 1 )
+ {
+ if ( ent.IsTitan() )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "titan_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( attacker, SFLAG_HUNTER_TITAN ) )
+ timerCredit *= 2.0
+ }
+ else
+ {
+ if ( ent.IsPlayer() )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "player_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( attacker, SFLAG_HUNTER_PILOT ) )
+ timerCredit *= 2.5
+ }
+ else
+ {
+ if ( IsGrunt( ent ) )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "ai_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( attacker, SFLAG_HUNTER_GRUNT ) )
+ timerCredit *= 2.5
+ }
+ else
+ if ( IsSpectre( ent ) )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( attacker, SFLAG_HUNTER_SPECTRE ) )
+ timerCredit *= 2.5
+ }
+ else
+ if ( IsTurret( ent ) )
+ {
+
+ timerCredit = GetCurrentPlaylistVarFloat( "megaturret_kill_credit", 0.5 )
+ //No 2x burn card for shooting mega turret
+ }
+ #if HAS_EVAC
+ else
+ if ( IsEvacDropship( ent ) )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "evac_dropship_kill_credit", 0.5 )
+ }
+ #endif
+ }
+ }
+
+ float dealtDamage = min( ent.GetHealth(), (savedDamage + titanDamage.shieldDamage) )
+ timerCredit = timerCredit * (dealtDamage / ent.GetMaxHealth().tofloat())
+ }
+
+ if ( IsPilot( attacker ) && PlayerHasPassive( attacker, ePassives.PAS_AT_HUNTER ) )
+ timerCredit *= 1.1
+
+ if ( timerCredit && (!TitanDamageRewardsTitanCoreTime() || !attacker.IsTitan() ) )
+ DecrementBuildTimer( attacker, timerCredit )
+ }
+ }
+
+ if ( shouldGiveTimerCredit //Primary Check
+ && TitanDamageRewardsTitanCoreTime() //Playlist var check
+ && ent.IsTitan()
+ && attacker.IsTitan()
+ && attacker.GetTeam() != ent.GetTeam()
+ && !attacker.ContextAction_IsMeleeExecution() // Some melee executions deal A LOT of damage
+ )
+ AddCreditToTitanCoreBuilderForTitanDamageInflicted( attacker, savedDamage + titanDamage.shieldDamage )
+ }
+}
+
+
+bool function ShouldUseNonTitanHeavyArmorDamageScale( entity victim )
+{
+ if ( (victim.GetArmorType() != ARMOR_TYPE_HEAVY) )
+ return false
+
+ if ( victim.IsTitan() )
+ return false
+
+ if ( IsDropship( victim ) )
+ return false
+
+ return true
+}
+
+void function GameModeRulesEarnMeterOnDamage_Default( entity attacker, entity victim, TitanDamage titanDamage, float savedDamage )
+{
+ #if MP
+ if ( victim.IsTitan() && !attacker.IsTitan() && !IsValid( attacker.GetPetTitan() ) )
+ {
+ float damage = min( victim.GetHealth(), (savedDamage + titanDamage.shieldDamage) )
+ float meterAmount = damage * file.titanMeterGainScale
+ if ( PlayerHasPassive( attacker, ePassives.PAS_AT_HUNTER ) )
+ meterAmount *= 1.1
+ PlayerEarnMeter_AddOwnedFrac( attacker, meterAmount )
+
+ AccumulatedDamageData damageData = file.playerAccumulatedDamageData[attacker]
+ damageData.lastDamageTime = Time()
+ damageData.accumulatedDamage += meterAmount
+
+ if ( damageData.accumulatedDamage >= 0.01 )
+ {
+ attacker.Signal( "UpdateAccumulatedDamageAfterDelay" )
+ AddPlayerScore( attacker, "DamageTitan", null, "", int( damageData.accumulatedDamage * 100 ) )
+ damageData.accumulatedDamage = 0
+ }
+ else
+ {
+ thread UpdateAccumulatedDamageAfterDelay( attacker )
+ }
+ }
+ #endif
+}
+
+void function SetTitanMeterGainScale( float scalar )
+{
+ file.titanMeterGainScale = scalar
+}
+
+#if MP
+void function UpdateAccumulatedDamageAfterDelay( entity attacker )
+{
+ attacker.EndSignal( "OnDeath" )
+ attacker.Signal( "UpdateAccumulatedDamageAfterDelay" )
+ attacker.EndSignal( "UpdateAccumulatedDamageAfterDelay" )
+
+ wait 0.25
+
+ AccumulatedDamageData damageData = file.playerAccumulatedDamageData[attacker]
+
+ if ( damageData.accumulatedDamage == 0 )
+ return
+
+ AddPlayerScore( attacker, "DamageTitan", null, "", int( max( damageData.accumulatedDamage * 100, 1 ) ) )
+ damageData.accumulatedDamage = 0
+}
+#endif
+
+void function SetGameModeRulesEarnMeterOnDamage( void functionref( entity, entity, TitanDamage, float ) rules )
+{
+ file.earnMeterOnDamageGameModeRulesCallback = rules
+}
+
+bool function ShouldGiveTimerCredit_Default( entity player, entity victim, var damageInfo )
+{
+ if ( player == victim )
+ return false
+
+ if ( player.IsTitan() && !IsCoreAvailable( player ) )
+ return false
+
+ if ( GAMETYPE == FREE_AGENCY && !player.IsTitan() )
+ return false
+
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ switch ( damageSourceID )
+ {
+ case eDamageSourceId.mp_titancore_flame_wave:
+ case eDamageSourceId.mp_titancore_flame_wave_secondary:
+ case eDamageSourceId.mp_titancore_salvo_core:
+ case damagedef_titan_fall:
+ case damagedef_nuclear_core:
+ return false
+ }
+
+ return true
+}
+
+bool function GameModeRulesShouldGiveTimerCredit( entity player, entity victim, var damageInfo )
+{
+ return file.ShouldGiveTimerCreditGameModeRules( player, victim, damageInfo )
+}
+
+void function SetGameModeRulesShouldGiveTimerCredit( bool functionref( entity, entity, var ) rules )
+{
+ file.ShouldGiveTimerCreditGameModeRules = rules
+}
+
+function TitanDamageFlinch( entity ent, damageInfo )
+{
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ if ( TitanStagger( ent, damageInfo ) )
+ return
+
+ if ( DamageInfo_GetDamage( damageInfo ) >= TITAN_ADDITIVE_FLINCH_DAMAGE_THRESHOLD )
+ AddFlinch( ent, damageInfo )
+}
+
+function PilotDamageFlinch( entity ent, damageInfo )
+{
+ //if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ // return
+
+ float damage = DamageInfo_GetDamage( damageInfo )
+ if ( damage >= 5 )
+ AddFlinch( ent, damageInfo )
+}
+
+vector function GetDamageOrigin( damageInfo, entity victim = null )
+{
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+
+ if ( inflictor == svGlobal.worldspawn )
+ return DamageInfo_GetDamagePosition( damageInfo )
+
+ vector damageOrigin = IsValid( inflictor ) ? inflictor.GetOrigin() : DamageInfo_GetDamagePosition( damageInfo )
+
+ switch ( damageSourceId )
+ {
+ case eDamageSourceId.mp_weapon_satchel:
+ case eDamageSourceId.mp_weapon_proximity_mine:
+ case eDamageSourceId.mp_titanweapon_arc_pylon:
+ break
+
+ case damagedef_nuclear_core:
+ case eDamageSourceId.mp_titanability_smoke:
+ //if ( IsValid( victim ) && victim.IsPlayer() && IsValid( victim.GetTitanSoulBeingRodeoed() ) )
+ {
+ damageOrigin += (RandomVecInDome( Vector( 0, 0, -1 ) ) * 300.0)
+ damageOrigin += Vector( 0, 0, 128 )
+ }
+ break
+
+ case eDamageSourceId.switchback_trap:
+ if ( IsValid( victim ) && victim.IsPlayer() )
+ damageOrigin = victim.EyePosition() + (RandomVecInDome( Vector( 0, 0, -1 ) ) * 300.0)
+ break
+
+ default:
+ if ( DamageInfo_GetAttacker( damageInfo ) )
+ {
+ inflictor = DamageInfo_GetAttacker( damageInfo )
+ damageOrigin = inflictor.GetWorldSpaceCenter()
+ }
+ break
+ }
+
+ return damageOrigin
+}
+
+/*
+function TrackDPS( ent )
+{
+ ent.s.dpsTracking <- {}
+ ent.s.dpsTracking.damage <- 0
+
+ local startTime = Time()
+
+ ent.WaitSignal( "Doomed" )
+
+ local duration = Time() - startTime
+
+ printt( "DPS:", ent.s.dpsTracking.damage / duration, duration )
+
+ delete ent.s.dpsTracking
+}
+
+function UpdateDPS( ent, damageInfo )
+{
+ if ( GetDoomedState( ent ) )
+ return
+
+ if ( !( "dpsTracking" in ent.s ) )
+ thread TrackDPS( ent )
+
+ ent.s.dpsTracking.damage += DamageInfo_GetDamage( damageInfo )
+}
+*/
+
+
+void function PlayerTookDamage( entity player, var damageInfo, entity attacker, entity inflictor, int damageSourceId, TitanDamage titanDamage )
+{
+ int hitBox = DamageInfo_GetHitBox( damageInfo )
+
+ bool critHit = false
+
+ if ( CritWeaponInDamageInfo( damageInfo ) )
+ critHit = IsCriticalHit( attacker, player, hitBox, DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) )
+
+ if ( critHit )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+
+ array<string> weaponMods = GetWeaponModsFromDamageInfo( damageInfo )
+
+ local eModSourceID = null
+ foreach ( mod in weaponMods )
+ {
+ local modSourceID = GetModSourceID( mod )
+ if ( modSourceID != null && modSourceID in modNameStrings )
+ eModSourceID = modSourceID
+ }
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == eDamageSourceId.fall )
+ DamageInfo_SetForceKill( damageInfo, true )
+
+ bool isTitan = player.IsTitan()
+
+ if ( isTitan )
+ Titan_PlayerTookDamage( player, damageInfo, attacker, critHit, titanDamage )
+ else
+ Wallrun_PlayerTookDamage( player, damageInfo, attacker )
+
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+ bool isKillShot = (damageAmount >= player.GetHealth())
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ if ( isKillShot )
+ damageType = (damageType | DF_KILLSHOT)
+
+ if ( isTitan && (DamageInfo_GetDamage( damageInfo ) == 0) )
+ {
+ if ( titanDamage.doomedNow ) // to make kill card come up for you even if you have auto-eject. In Titan_PlayerTookDamage we set damage to 0 if you have Auto-Eject and are doomed
+ TellClientPlayerTookDamage( player, damageInfo, attacker, eModSourceID, damageType, damageSourceId, titanDamage )
+ }
+
+ vector attackerOrigin = Vector( 0, 0, 0 )
+ if ( IsValid( attacker ) )
+ attackerOrigin = attacker.GetOrigin()
+
+ if ( IsAlive( player ) )
+ {
+ float storeTime = MAX_DAMAGE_HISTORY_TIME
+ entity storeEnt
+ if ( isTitan )
+ {
+ storeEnt = player.GetTitanSoul()
+ }
+ else
+ {
+ storeEnt = player
+ if ( IsSingleplayer() )
+ storeTime = 30.0
+ }
+
+ StoreDamageHistoryAndUpdate( storeEnt, storeTime, DamageInfo_GetDamage( damageInfo ), attackerOrigin, damageType, damageSourceId, attacker, weaponMods )
+ }
+
+ if ( !(DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS) )
+ TellClientPlayerTookDamage( player, damageInfo, attacker, eModSourceID, damageType, damageSourceId, titanDamage )
+}
+
+function TellClientPlayerTookDamage( entity player, damageInfo, entity attacker, eModSourceID, int damageType, int damageSourceId, TitanDamage titanDamage )
+{
+ if ( !player.hasConnected )
+ return
+
+ local attackerEHandle = IsValid( attacker ) ? attacker.GetEncodedEHandle() : null
+ local weaponEHandle = IsValid( DamageInfo_GetWeapon( damageInfo ) ) ? DamageInfo_GetWeapon( damageInfo ).GetEncodedEHandle() : null
+ local damageOrigin = GetDamageOrigin( damageInfo, player )
+
+ if ( player.IsTitan() )
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanTookDamage", DamageInfo_GetDamage( damageInfo ), damageOrigin.x, damageOrigin.y, damageOrigin.z, damageType, damageSourceId, attackerEHandle, eModSourceID, titanDamage.doomedNow, titanDamage.doomedDamage )
+ else
+ Remote_CallFunction_Replay( player, "ServerCallback_PilotTookDamage", DamageInfo_GetDamage( damageInfo ), damageOrigin.x, damageOrigin.y, damageOrigin.z, damageType, damageSourceId, attackerEHandle, eModSourceID )
+}
+
+// This only handles damage events. Whizbys can still cause snipercam to trigger without passing through this check.
+function CodeCallBack_ShouldTriggerSniperCam( damageInfo )
+{
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ case damagedef_titan_step:
+ case eDamageSourceId.super_electric_smoke_screen:
+ return false
+ }
+
+ return true
+}
+
+bool function CodeCallback_ForceAIMissPlayer( entity npc, entity player )
+{
+ return SPMP_Callback_ForceAIMissPlayer( npc, player )
+}
+
+bool function CodeCallback_OnTouchHealthKit( entity player, entity ent )
+{
+ string entityClassName = ent.GetClassName()
+
+ Assert( entityClassName in svGlobal.onTouchHealthKitCallbacks )
+
+ array<bool functionref( entity player, entity healthpack )> callbackFuncs = svGlobal.onTouchHealthKitCallbacks[ entityClassName ]
+ foreach ( callbackFunc in callbackFuncs )
+ {
+ bool result = callbackFunc( player, ent )
+ if ( result )
+ return result
+ }
+
+ return false
+}
+
+bool function ShouldPlayEMPEffectEvenWhenDamageIsZero( entity ent, entity attacker )
+{
+ if ( ent.IsTitan() && IsTitanWithinBubbleShield( ent ) )
+ return false
+
+ if ( !IsValid( attacker ) )
+ return true
+
+ if ( attacker.GetTeam() != ent.GetTeam() )
+ return true
+
+ return false
+}
+
+void function CodeCallback_OnPlayerGrappled( entity player, entity victim )
+{
+ if ( victim.GetTeam() != player.GetTeam() )
+ {
+ if ( victim.p.lastGrappledTime + TITAN_GRAPPLE_DEBOUNCE_TIME < Time() )
+ {
+ if ( player.IsTitan() )
+ {
+ victim.TakeDamage( TITAN_GRAPPLE_DAMAGE, player, player, { origin = victim.EyePosition(), scriptType = DF_GIB, damageSourceId = eDamageSourceId.titan_grapple } )
+
+ if ( victim.IsTitan() )
+ {
+ entity soul = victim.GetTitanSoul()
+ if ( soul == null )
+ soul = victim
+
+ float fadeTime = 0.5
+ StatusEffect_AddTimed( soul, eStatusEffect.dodge_speed_slow, 0.75, 0.9 + fadeTime, fadeTime )
+ StatusEffect_AddTimed( soul, eStatusEffect.move_slow, 0.75, 0.9 + fadeTime, fadeTime )
+ }
+ }
+
+ if ( victim.IsPlayer() )
+ {
+ if ( player.IsTitan() )
+ MessageToPlayer( victim, eEventNotifications.Grapple_WasGrappled_ByTitan )
+ else
+ MessageToPlayer( victim, eEventNotifications.Grapple_WasGrappled_ByPilot )
+ }
+ }
+
+ victim.p.lastGrappledTime = Time()
+ }
+}
+
+void function CodeCallback_OnProjectileGrappled( entity player, entity projectile )
+{
+
+}
+
+void function DamageInfo_ScaleDamage( var damageInfo, float scalar )
+{
+ DamageInfo_SetDamage( damageInfo, DamageInfo_GetDamage( damageInfo ) * scalar )
+}
+
+string function CodeCallback_CheckPassThroughAddsMods( entity player, entity hitEnt, string currWeaponName )
+{
+ if ( !IsValid( player ) )
+ return ""
+
+ if ( StatusEffect_Get( hitEnt, eStatusEffect.pass_through_amps_weapon ) > 0 )
+ {
+ array<string> mods = GetWeaponBurnMods( currWeaponName )
+ if ( mods.len() > 0 )
+ return mods[0]
+ }
+ return ""
+}
+
+void function Generic_NPCTookDamage( entity npc, damageInfo, TitanDamage titanDamage )
+{
+ Assert( !npc.IsTitan() )
+ Assert( DamageInfo_GetDamage( damageInfo ) > 0 )
+ Assert( IsAlive( npc ) )
+
+ bool critHit = false
+ if ( CritWeaponInDamageInfo( damageInfo ) )
+ critHit = IsCriticalHit( DamageInfo_GetAttacker( damageInfo ), npc, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) )
+
+ if ( critHit )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+
+ titanDamage.shieldDamage = NPCShieldHealthUpdate( npc, damageInfo, critHit )
+}
+
+
+int function NPCShieldHealthUpdate( entity npc, damageInfo, bool critHit )
+{
+ if ( npc.GetShieldHealth() <= 0 )
+ return 0
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == damagedef_suicide )
+ return 0
+
+ if ( DamageInfo_GetForceKill( damageInfo ) )
+ {
+ npc.SetShieldHealth( 0 )
+ return 0
+ }
+ else if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_BYPASS_SHIELD )
+ {
+ return 0
+ }
+
+ DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )
+ return int( ShieldModifyDamage( npc, damageInfo ) )
+}
+
+#if MP
+// taken from sp/sh_sp_dialogue.gnut, with some sp-exclusive stuff removed
+
+// called by code when an animation does { event AE_SV_VSCRIPT_CALLBACK FrameNumber "some string" }
+// and by a script function OnFootstep, apparently.
+void function CodeCallback_OnServerAnimEvent( entity ent, string eventName )
+{
+ PerfStart( PerfIndexServer.CB_OnServerAnimEvent )
+ if ( HasAnimEvent( ent, eventName ) )
+ thread RunAnimEventCallbacks( ent, eventName )
+
+ if ( eventName in svGlobal.globalAnimEventCallbacks )
+ {
+ thread svGlobal.globalAnimEventCallbacks[ eventName ]( ent )
+ PerfEnd( PerfIndexServer.CB_OnServerAnimEvent )
+ return
+ }
+
+
+ // couldn't find this eventName on the ent or the global anim events,
+ // so try breaking it down. If we didn't find it, it means
+ // script needs to handle the event, even if it is just to
+ // do nothing with it
+
+ array<string> tokens = split( eventName, ":" )
+ string tokenName = tokens[0]
+
+ switch ( tokenName )
+ {
+ case "worldsound":
+ GlobalAnimEventWithStringParameter_WorldSound( ent, tokens[1] )
+ break
+
+ case "signal":
+ SendSignalFromTokens( ent, tokens )
+ break
+
+ case "flagset":
+ GlobalAnimEventWithStringParameter_FlagSet( ent, tokens[1] )
+ break
+
+ //case "dialogue":
+ // // Make sure that animation triggered dialogue uses the correct priority and skips the queue
+ // string name = tokens[1]
+ // Assert( file.registeredDialogIDs.find( name ) >= 0, "Dialogue line " + name + " is not registered" )
+ // int aliasID = file.registeredDialogIDs.find( name )
+ // DialogueData data = file.registeredDialog[ aliasID ]
+ // Assert( data.priority == PRIORITY_NO_QUEUE, "Dialogue " + name + " triggered via qc must use PRIORITY_NO_QUEUE" )
+ // thread PlayDialogue( name, ent )
+ // break
+
+ case "fireViperSalvo":
+ int value = tokens[1].tointeger()
+ ent.Signal( "fireSalvo", { num = value } )
+ break
+
+ //case "conversation":
+ // thread PlayerConversation( tokens[1], GetPlayerArray()[0], ent )
+ // break
+ }
+
+ PerfEnd( PerfIndexServer.CB_OnServerAnimEvent )
+}
+#endif // #if MP \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_dropship_spawn_common.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_dropship_spawn_common.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_dropship_spawn_common.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_gamestate.nut b/Northstar.CustomServers/scripts/vscripts/mp/_gamestate.nut
new file mode 100644
index 000000000..603c38fa2
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_gamestate.nut
@@ -0,0 +1,144 @@
+untyped
+
+global function GameState_Init
+global function InitGameState
+
+global function SetRoundBased
+global function SetCustomIntroLength
+
+global function SetGetDifficultyFunc
+global function GetDifficultyLevel
+
+//********************************************************************************************
+// Game State
+//********************************************************************************************
+global const PREMATCH_TIMER_INTRO_DEFAULT = 46
+global const PREMATCH_TIMER_NO_INTRO = 7 //shows 5 when fade from black
+global const CLEAR_PLAYERS_BUFFER = 2.0
+
+global const ENDROUND_FREEZE = 0
+global const ENDROUND_MOVEONLY = 1
+global const ENDROUND_FREE = 3
+
+global const NO_DETERMINED_WINNING_TEAM_YET = -1
+
+struct
+{
+ int functionref() difficultyFunc
+} file
+
+global enum eWinReason
+{
+ DEFAULT,
+ SCORE_LIMIT,
+ TIME_LIMIT,
+ ELIMINATION
+}
+
+
+function GameState_Init()
+{
+ FlagInit( "GamePlaying" )
+ FlagInit( "DisableTimeLimit" )
+ FlagInit( "DisableScoreLimit" )
+ FlagInit( "AnnounceWinnerEnabled", true )
+ FlagInit( "AnnounceProgressEnabled", true )
+ FlagInit( "DefendersWinDraw" )
+
+ RegisterSignal( "RoundEnd" )
+ RegisterSignal( "GameEnd" )
+ RegisterSignal( "GameStateChanged" )
+ RegisterSignal( "CatchUpFallBehindVO" )
+ RegisterSignal( "ClearedPlayers" )
+
+
+ level.devForcedWin <- false //For dev purposes only. Used to check if we forced a win through dev command
+ level.devForcedTimeLimit <- false
+
+ level.lastTimeLeftSeconds <- null
+
+ level.lastScoreSwapVOTime <- null
+
+ level.nextMatchProgressAnnouncementLevel <- MATCH_PROGRESS_EARLY //When we make a matchProgressAnnouncement, this variable is set
+
+ level.endOfRoundPlayerState <- ENDROUND_FREEZE
+
+ level._swapGameStateOnNextFrame <- false
+ level.clearedPlayers <- false
+
+ level.customEpilogueDuration <- null
+
+ level.lastTeamTitans <- {}
+ level.lastTeamTitans[TEAM_IMC] <- null
+ level.lastTeamTitans[TEAM_MILITIA] <- null
+ level.lastTeamPilots <- {}
+ level.lastTeamPilots[TEAM_IMC] <- null
+ level.lastTeamPilots[TEAM_MILITIA] <- null
+
+ level.firstTitanfall <- false
+
+ level.lastPlayingEmptyTeamCheck <- 0
+
+ level.doneWaitingForPlayersTimeout <- 0
+
+ level.attackDefendBased <- false
+
+ level.roundBasedUsingTeamScore <- false
+
+ level.roundBasedTeamScoreNoReset <- false
+
+ level.customIntroLength <- null
+
+ level.sendingPlayersAway <- false
+
+ level.forceNoMoreRounds <- false
+
+ // prevents ties... need an option to disable in the future
+ level.firstToScoreLimit <- TEAM_UNASSIGNED
+ level.allowPointsOverLimit <- false
+
+ file.difficultyFunc = DefaultDifficultyFunc
+
+ #if MP
+ AddCallback_EntitiesDidLoad( GameState_EntitiesDidLoad )
+ #endif
+}
+
+
+int function DefaultDifficultyFunc()
+{
+ return 0
+}
+
+void function SetGetDifficultyFunc( int functionref() difficultyFunc )
+{
+ Assert( file.difficultyFunc == DefaultDifficultyFunc )
+
+ file.difficultyFunc = difficultyFunc
+}
+
+
+// This function is meant to init stuff that _gamestate uses, as opposed
+// to stuff that any particular gamestate like Playing uses
+function InitGameState()
+{
+ #if MP
+ PIN_GameStart()
+ #endif
+}
+
+function SetRoundBased( state )
+{
+ level.nv.roundBased = state
+}
+
+function SetCustomIntroLength( time )
+{
+ level.customIntroLength = time
+}
+
+int function GetDifficultyLevel()
+{
+ return file.difficultyFunc()
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/scripts/vscripts/mp/_gamestate_mp.nut
new file mode 100644
index 000000000..2fa24e9da
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_gamestate_mp.nut
@@ -0,0 +1,707 @@
+untyped
+
+global function PIN_GameStart
+global function SetGameState
+global function GameState_EntitiesDidLoad
+global function WaittillGameStateOrHigher
+
+global function SetShouldUsePickLoadoutScreen
+global function SetSwitchSidesBased
+global function SetSuddenDeathBased
+global function SetShouldUseRoundWinningKillReplay
+global function SetRoundWinningKillReplayKillClasses
+global function SetRoundWinningKillReplayAttacker
+global function SetWinner
+global function SetTimeoutWinnerDecisionFunc
+global function AddTeamScore
+
+global function GameState_GetTimeLimitOverride
+global function IsRoundBasedGameOver
+global function ShouldRunEvac
+global function GiveTitanToPlayer
+global function GetTimeLimit_ForGameMode
+
+struct {
+ // used for togglable parts of gamestate
+ bool usePickLoadoutScreen
+ bool switchSidesBased
+ bool suddenDeathBased
+ int functionref() timeoutWinnerDecisionFunc
+
+ // for waitingforplayers
+ int numPlayersFullyConnected
+
+ bool hasSwitchedSides
+
+ int announceRoundWinnerWinningSubstr
+ int announceRoundWinnerLosingSubstr
+
+ bool roundWinningKillReplayTrackPilotKills = true
+ bool roundWinningKillReplayTrackTitanKills = false
+
+ entity roundWinningKillReplayVictim
+ entity roundWinningKillReplayAttacker
+ int roundWinningKillReplayMethodOfDeath
+ float roundWinningKillReplayTimeOfDeath
+ float roundWinningKillReplayHealthFrac
+} file
+
+void function PIN_GameStart()
+{
+ // todo: using the pin telemetry function here is weird and was done veeery early on before i knew how this all worked, should use a different one
+
+ // called from InitGameState
+ //FlagInit( "ReadyToStartMatch" )
+
+ SetServerVar( "switchedSides", 0 )
+ SetServerVar( "winningTeam", -1 )
+
+ AddCallback_GameStateEnter( eGameState.WaitingForCustomStart, GameStateEnter_WaitingForCustomStart )
+ AddCallback_GameStateEnter( eGameState.WaitingForPlayers, GameStateEnter_WaitingForPlayers )
+ AddCallback_OnClientConnected( WaitingForPlayers_ClientConnected )
+ AddCallback_OnClientDisconnected( WaitingForPlayers_ClientDisconnected )
+
+ AddCallback_GameStateEnter( eGameState.PickLoadout, GameStateEnter_PickLoadout )
+ AddCallback_GameStateEnter( eGameState.Prematch, GameStateEnter_Prematch )
+ AddCallback_GameStateEnter( eGameState.Playing, GameStateEnter_Playing )
+ AddCallback_GameStateEnter( eGameState.WinnerDetermined, GameStateEnter_WinnerDetermined )
+ AddCallback_GameStateEnter( eGameState.SwitchingSides, GameStateEnter_SwitchingSides )
+ AddCallback_GameStateEnter( eGameState.SuddenDeath, GameStateEnter_SuddenDeath )
+ AddCallback_GameStateEnter( eGameState.Postmatch, GameStateEnter_Postmatch )
+
+ AddCallback_OnClientConnected( SetSkyCam ) // had no idea where to put this lol
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+ AddDeathCallback( "npc_titan", OnTitanKilled )
+}
+
+void function SetGameState( int newState )
+{
+ SetServerVar( "gameStateChangeTime", Time() )
+ SetServerVar( "gameState", newState )
+ svGlobal.levelEnt.Signal( "GameStateChanged" )
+
+ // added in AddCallback_GameStateEnter
+ foreach ( callbackFunc in svGlobal.gameStateEnterCallbacks[ newState ] )
+ callbackFunc()
+}
+
+void function GameState_EntitiesDidLoad()
+{
+ // nothing of importance to put here, this is referenced in _gamestate though so need it
+}
+
+void function WaittillGameStateOrHigher( int gameState )
+{
+ while ( GetGameState() < gameState )
+ svGlobal.levelEnt.WaitSignal( "GameStateChanged" )
+}
+
+
+// logic for individual gamestates:
+
+
+// eGameState.WaitingForCustomStart
+void function GameStateEnter_WaitingForCustomStart()
+{
+ // unused in release, comments indicate this was supposed to be used for an e3 demo
+ // perhaps games in this demo were manually started by an employee? no clue really
+}
+
+
+// eGameState.WaitingForPlayers
+void function GameStateEnter_WaitingForPlayers()
+{
+ foreach ( entity player in GetPlayerArray() )
+ WaitingForPlayers_ClientConnected( player )
+
+ thread WaitForPlayers( GetPendingClientsCount() + file.numPlayersFullyConnected ) // like 90% sure there should be a way to get number of loading clients on server but idk it
+}
+
+void function WaitForPlayers( int wantedNum )
+{
+ // note: atm if someone disconnects as this happens the game will just wait forever
+ print( "WaitForPlayers(): " + wantedNum + " players" )
+ float endTime = Time() + 120.0
+
+ while ( endTime > Time() )
+ {
+ if ( file.numPlayersFullyConnected >= wantedNum )
+ break
+
+ WaitFrame()
+ }
+
+ print( "done waiting!" )
+
+ wait 1.0 // bit nicer
+
+ if ( file.usePickLoadoutScreen )
+ SetGameState( eGameState.PickLoadout )
+ else
+ SetGameState( eGameState.Prematch )
+}
+
+void function WaitingForPlayers_ClientConnected( entity player )
+{
+ if ( GetGameState() == eGameState.WaitingForPlayers )
+ ScreenFadeToBlackForever( player, 0.0 )
+
+ file.numPlayersFullyConnected++
+}
+
+void function WaitingForPlayers_ClientDisconnected( entity player )
+{
+ file.numPlayersFullyConnected--
+}
+
+
+// eGameState.PickLoadout
+void function GameStateEnter_PickLoadout()
+{
+ thread GameStateEnter_PickLoadout_Threaded()
+}
+
+void function GameStateEnter_PickLoadout_Threaded()
+{
+ float pickloadoutLength = 20.0 // may need tweaking
+ SetServerVar( "minPickLoadOutTime", Time() + pickloadoutLength )
+
+ // titan selection menu can change minPickLoadOutTime so we need to wait manually until we hit the time
+ while ( Time() < GetServerVar( "minPickLoadOutTime" ) )
+ WaitFrame()
+
+ SetGameState( eGameState.Prematch )
+}
+
+
+// eGameState.Prematch
+void function GameStateEnter_Prematch()
+{
+ int timeLimit = GameMode_GetTimeLimit( GAMETYPE ) * 60
+ if ( file.switchSidesBased )
+ timeLimit /= 2 // endtime is half of total per side
+
+ SetServerVar( "gameEndTime", Time() + timeLimit + ClassicMP_GetIntroLength() )
+ SetServerVar( "roundEndTime", Time() + ClassicMP_GetIntroLength() + GameMode_GetRoundTimeLimit( GAMETYPE ) * 60 )
+}
+
+
+// eGameState.Playing
+void function GameStateEnter_Playing()
+{
+ thread GameStateEnter_Playing_Threaded()
+}
+
+void function GameStateEnter_Playing_Threaded()
+{
+ WaitFrame() // ensure timelimits are all properly set
+
+ while ( GetGameState() == eGameState.Playing )
+ {
+ // could cache these, but what if we update it midgame?
+ float endTime
+ if ( IsRoundBased() )
+ endTime = expect float( GetServerVar( "roundEndTime" ) )
+ else
+ endTime = expect float( GetServerVar( "gameEndTime" ) )
+
+ // time's up!
+ if ( Time() >= endTime )
+ {
+ int winningTeam
+ if ( file.timeoutWinnerDecisionFunc != null )
+ winningTeam = file.timeoutWinnerDecisionFunc()
+ else
+ winningTeam = GameScore_GetWinningTeam()
+
+ if ( file.switchSidesBased && !file.hasSwitchedSides )
+ SetGameState( eGameState.SwitchingSides )
+ else if ( file.suddenDeathBased && winningTeam == TEAM_UNASSIGNED ) // suddendeath if we draw and suddendeath is enabled and haven't switched sides
+ SetGameState( eGameState.SuddenDeath )
+ else
+ SetWinner( winningTeam )
+ }
+
+ WaitFrame()
+ }
+}
+
+
+// eGameState.WinnerDetermined
+// these are likely innacurate
+const float ROUND_END_FADE_KILLREPLAY = 1.0
+const float ROUND_END_DELAY_KILLREPLAY = 3.0
+const float ROUND_END_FADE_NOKILLREPLAY = 8.0
+const float ROUND_END_DELAY_NOKILLREPLAY = 10.0
+
+void function GameStateEnter_WinnerDetermined()
+{
+ thread GameStateEnter_WinnerDetermined_Threaded()
+}
+
+void function GameStateEnter_WinnerDetermined_Threaded()
+{
+ WaitFrame() // wait a frame so other scripts can setup killreplay stuff
+
+ entity replayAttacker = file.roundWinningKillReplayAttacker
+ bool doReplay = Replay_IsEnabled() && Evac_IsEnabled() && IsRoundWinningKillReplayEnabled() && IsValid( replayAttacker )
+
+ float replayLength = 2.0 // extra delay if no replay
+ if ( doReplay )
+ {
+ replayLength = min( ROUND_WINNING_KILL_REPLAY_LENGTH_OF_REPLAY, Time() - replayAttacker.s.respawnTime ) // 7.5s unless player lifetime < that
+ SetServerVar( "roundWinningKillReplayEntHealthFrac", file.roundWinningKillReplayHealthFrac )
+ }
+
+ foreach ( entity player in GetPlayerArray() )
+ thread PlayerWatchesRoundWinningKillReplay( player, doReplay, replayLength )
+
+ wait replayLength + ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME
+
+ if ( IsRoundBased() )
+ {
+ svGlobal.levelEnt.Signal( "RoundEnd" )
+ SetServerVar( "roundsPlayed", GetServerVar( "roundsPlayed" ) + 1 )
+
+ if ( max( GameRules_GetTeamScore( TEAM_IMC ), GameRules_GetTeamScore( TEAM_MILITIA ) ) >= GameMode_GetRoundScoreLimit( GAMETYPE ) )
+ SetGameState( eGameState.Postmatch )
+ else if ( file.switchSidesBased && !file.hasSwitchedSides )
+ SetGameState( eGameState.SwitchingSides )
+ else if ( file.usePickLoadoutScreen )
+ SetGameState( eGameState.PickLoadout )
+ else
+ SetGameState ( eGameState.Prematch )
+ }
+ else
+ {
+ if ( Evac_IsEnabled() )
+ SetGameState( eGameState.Epilogue )
+ else
+ SetGameState( eGameState.Postmatch )
+ }
+}
+
+void function PlayerWatchesRoundWinningKillReplay( entity player, bool doReplay, float replayLength )
+{
+ player.FreezeControlsOnServer()
+
+ int winningTeam = GetWinningTeam()
+ int announcementSubstr
+ if ( winningTeam != TEAM_UNASSIGNED )
+ announcementSubstr = player.GetTeam() == winningTeam ? file.announceRoundWinnerWinningSubstr : file.announceRoundWinnerLosingSubstr
+
+ if ( IsRoundBased() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AnnounceRoundWinner", winningTeam, announcementSubstr, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME, GameRules_GetTeamScore2( TEAM_MILITIA ), GameRules_GetTeamScore2( TEAM_IMC ) )
+ else
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AnnounceWinner", winningTeam, announcementSubstr, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME )
+
+ if ( IsRoundBased() || !Evac_IsEnabled() ) // if we're doing evac, then no fades or killreplay
+ {
+ ScreenFadeToBlackForever( player, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME )
+ wait ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME
+
+ // do this after screen goes black so people can't see the titan dying
+ // don't use .die since that makes explosions and that
+ // todo: need a function specifically for cleaning up npcs and stuff on round end, this is imperfect
+ if ( IsAlive( player.GetPetTitan() ) )
+ player.GetPetTitan().Destroy()
+
+ if ( doReplay )
+ {
+ player.SetPredictionEnabled( false ) // prediction fucks with replays
+
+ entity attacker = file.roundWinningKillReplayAttacker
+ player.SetKillReplayDelay( Time() - replayLength, THIRD_PERSON_KILL_REPLAY_ALWAYS )
+ player.SetKillReplayInflictorEHandle( attacker.GetEncodedEHandle() )
+ player.SetKillReplayVictim( file.roundWinningKillReplayVictim )
+ player.SetViewIndex( attacker.GetIndexForEntity() )
+ player.SetIsReplayRoundWinning( true )
+
+ if ( replayLength >= ROUND_WINNING_KILL_REPLAY_LENGTH_OF_REPLAY - 0.5 ) // only do fade if close to full length replay
+ {
+ // this doesn't work because fades don't work on players that are in a replay, unsure how official servers do this
+ wait replayLength - 2.0
+ ScreenFadeToBlackForever( player, 2.0 )
+
+ wait 2.0
+ }
+ else
+ wait replayLength
+ }
+ else
+ wait replayLength // this will just be extra delay if no replay
+
+ player.SetPredictionEnabled( true )
+ player.ClearReplayDelay()
+ player.ClearViewEntity()
+ player.UnfreezeControlsOnServer()
+ }
+}
+
+
+// eGameState.SwitchingSides
+void function GameStateEnter_SwitchingSides()
+{
+ thread GameStateEnter_SwitchingSides_Threaded()
+}
+
+void function GameStateEnter_SwitchingSides_Threaded()
+{
+ entity replayAttacker = file.roundWinningKillReplayAttacker
+ bool doReplay = Replay_IsEnabled() && IsRoundWinningKillReplayEnabled() && IsValid( replayAttacker ) && !IsRoundBased() // for roundbased modes, we've already done the replay
+
+ float replayLength = SWITCHING_SIDES_DELAY_REPLAY // extra delay if no replay
+ if ( doReplay )
+ {
+ replayLength = min( SWITCHING_SIDES_DELAY, Time() - replayAttacker.s.respawnTime ) // 6s unless player lifetime < that
+ SetServerVar( "roundWinningKillReplayEntHealthFrac", file.roundWinningKillReplayHealthFrac )
+ }
+
+ foreach ( entity player in GetPlayerArray() )
+ thread PlayerWatchesSwitchingSidesKillReplay( player, doReplay, replayLength )
+
+ wait SWITCHING_SIDES_DELAY_REPLAY + replayLength
+
+ file.hasSwitchedSides = true
+ SetServerVar( "switchedSides", 1 )
+ file.roundWinningKillReplayAttacker = null // reset this after replay
+
+ if ( file.usePickLoadoutScreen )
+ SetGameState( eGameState.PickLoadout )
+ else
+ SetGameState ( eGameState.Prematch )
+}
+
+void function PlayerWatchesSwitchingSidesKillReplay( entity player, bool doReplay, float replayLength )
+{
+ player.FreezeControlsOnServer()
+
+ ScreenFadeToBlackForever( player, SWITCHING_SIDES_DELAY_REPLAY ) // automatically cleared
+ wait SWITCHING_SIDES_DELAY_REPLAY
+
+ // do this after screen goes black so people can't see the titan dying
+ // don't use .die since that makes explosions and that
+ if ( IsAlive( player.GetPetTitan() ) )
+ player.GetPetTitan().Destroy()
+
+ if ( doReplay )
+ {
+ player.SetPredictionEnabled( false ) // prediction fucks with replays
+
+ entity attacker = file.roundWinningKillReplayAttacker
+ player.SetKillReplayDelay( Time() - replayLength, THIRD_PERSON_KILL_REPLAY_ALWAYS )
+ player.SetKillReplayInflictorEHandle( attacker.GetEncodedEHandle() )
+ player.SetKillReplayVictim( file.roundWinningKillReplayVictim )
+ player.SetViewIndex( attacker.GetIndexForEntity() )
+ player.SetIsReplayRoundWinning( true )
+
+ if ( replayLength >= SWITCHING_SIDES_DELAY - 0.5 ) // only do fade if close to full length replay
+ {
+ // this doesn't work because fades don't work on players that are in a replay, unsure how official servers do this
+ wait replayLength - ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME
+ ScreenFadeToBlackForever( player, ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME )
+
+ wait ROUND_WINNING_KILL_REPLAY_SCREEN_FADE_TIME
+ }
+ else
+ wait replayLength
+ }
+ else
+ wait SWITCHING_SIDES_DELAY_REPLAY // extra delay if no replay
+
+ player.SetPredictionEnabled( true )
+ player.ClearReplayDelay()
+ player.ClearViewEntity()
+ player.UnfreezeControlsOnServer()
+}
+
+
+// eGameState.SuddenDeath
+void function GameStateEnter_SuddenDeath()
+{
+ // disable respawns, suddendeath calling is done on a kill callback
+ SetRespawnsEnabled( false )
+}
+
+void function GameStateEnter_SuddenDeath_Threaded()
+{
+ while ( GetGameState() == eGameState.SuddenDeath )
+ {
+ // todo this really ought to work for ffa in the future
+ int imcPlayers
+ int militiaPlayers
+
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( IsAlive( player ) )
+ {
+ if ( player.GetTeam() == TEAM_IMC )
+ imcPlayers++
+ else
+ militiaPlayers++
+ }
+ }
+
+ if ( imcPlayers == 0 )
+ SetWinner( TEAM_MILITIA )
+ else if ( militiaPlayers == 0 )
+ SetWinner( TEAM_IMC )
+
+ WaitFrame()
+ }
+}
+
+
+// eGameState.Postmatch
+void function GameStateEnter_Postmatch()
+{
+ foreach ( entity player in GetPlayerArray() )
+ {
+ player.FreezeControlsOnServer()
+ thread ForceFadeToBlack( player )
+ }
+
+ thread GameStateEnter_Postmatch_Threaded()
+}
+
+void function GameStateEnter_Postmatch_Threaded()
+{
+ wait GAME_POSTMATCH_LENGTH
+
+ GameRules_EndMatch()
+}
+
+void function ForceFadeToBlack( entity player )
+{
+ // hack until i figure out what deathcam stuff is causing fadetoblacks to be cleared
+ while ( true )
+ {
+ WaitFrame()
+ ScreenFadeToBlackForever( player, 0.0 )
+ }
+}
+
+
+// shared across multiple gamestates
+void function SetSkyCam( entity player )
+{
+ entity skycam = GetEnt( "skybox_cam_level" )
+
+ if ( skycam != null )
+ player.SetSkyCamera( skycam )
+}
+
+void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( !GamePlayingOrSuddenDeath() )
+ return
+
+ // set round winning killreplay info here if no custom replaydelay
+ if ( file.roundWinningKillReplayTrackPilotKills )
+ {
+ file.roundWinningKillReplayVictim = victim
+ file.roundWinningKillReplayAttacker = attacker
+ file.roundWinningKillReplayMethodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ file.roundWinningKillReplayTimeOfDeath = Time()
+ file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker )
+ }
+
+ // note: pilotstitans is just win if enemy team runs out of either pilots or titans
+ if ( IsPilotEliminationBased() || GetGameState() == eGameState.SuddenDeath )
+ {
+ if ( GetPlayerArrayOfTeam_Alive( victim.GetTeam() ).len() == 0 )
+ {
+ // for ffa we need to manually get the last team alive
+ if ( IsFFAGame() )
+ {
+ array<int> teamsWithLivingPlayers
+ foreach ( entity player in GetPlayerArray_Alive() )
+ {
+ if ( !teamsWithLivingPlayers.contains( player.GetTeam() ) )
+ teamsWithLivingPlayers.append( player.GetTeam() )
+ }
+
+ if ( teamsWithLivingPlayers.len() == 1 )
+ SetWinner( teamsWithLivingPlayers[ 0 ], "#GAMEMODE_ENEMY_PILOTS_ELIMINATED", "#GAMEMODE_FRIENDLY_PILOTS_ELIMINATED" )
+ else if ( teamsWithLivingPlayers.len() == 0 ) // failsafe: only team was the dead one
+ SetWinner( TEAM_UNASSIGNED, "#GAMEMODE_ENEMY_PILOTS_ELIMINATED", "#GAMEMODE_FRIENDLY_PILOTS_ELIMINATED" ) // this is fine in ffa
+ }
+ else
+ SetWinner( GetOtherTeam( victim.GetTeam() ), "#GAMEMODE_ENEMY_PILOTS_ELIMINATED", "#GAMEMODE_FRIENDLY_PILOTS_ELIMINATED" )
+ }
+ }
+
+ if ( ( Riff_EliminationMode() == eEliminationMode.Titans || Riff_EliminationMode() == eEliminationMode.PilotsTitans ) && victim.IsTitan() ) // need an extra check for this
+ OnTitanKilled( victim, damageInfo )
+}
+
+void function OnTitanKilled( entity victim, var damageInfo )
+{
+ if ( !GamePlayingOrSuddenDeath() )
+ return
+
+ // set round winning killreplay info here if no custom replaydelay
+ if ( file.roundWinningKillReplayTrackTitanKills )
+ {
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ file.roundWinningKillReplayVictim = victim
+ file.roundWinningKillReplayAttacker = attacker
+ file.roundWinningKillReplayMethodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ file.roundWinningKillReplayTimeOfDeath = Time()
+ file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker )
+ }
+
+ // note: pilotstitans is just win if enemy team runs out of either pilots or titans
+ if ( IsTitanEliminationBased() )
+ {
+ int livingTitans
+ foreach ( entity titan in GetTitanArrayOfTeam( victim.GetTeam() ) )
+ livingTitans++
+
+ if ( livingTitans == 0 )
+ {
+ // for ffa we need to manually get the last team alive
+ if ( IsFFAGame() )
+ {
+ array<int> teamsWithLivingTitans
+ foreach ( entity titan in GetTitanArray() )
+ {
+ if ( !teamsWithLivingTitans.contains( titan.GetTeam() ) )
+ teamsWithLivingTitans.append( titan.GetTeam() )
+ }
+
+ if ( teamsWithLivingTitans.len() == 1 )
+ SetWinner( teamsWithLivingTitans[ 0 ], "#GAMEMODE_ENEMY_TITANS_DESTROYED", "#GAMEMODE_FRIENDLY_TITANS_DESTROYED" )
+ else if ( teamsWithLivingTitans.len() == 0 ) // failsafe: only team was the dead one
+ SetWinner( TEAM_UNASSIGNED, "#GAMEMODE_ENEMY_TITANS_DESTROYED", "#GAMEMODE_FRIENDLY_TITANS_DESTROYED" ) // this is fine in ffa
+ }
+ else
+ SetWinner( GetOtherTeam( victim.GetTeam() ), "#GAMEMODE_ENEMY_TITANS_DESTROYED", "#GAMEMODE_FRIENDLY_TITANS_DESTROYED" )
+ }
+ }
+}
+
+
+
+// stuff for gamemodes to call
+
+void function SetShouldUsePickLoadoutScreen( bool shouldUse )
+{
+ file.usePickLoadoutScreen = shouldUse
+}
+
+void function SetSwitchSidesBased( bool switchSides )
+{
+ file.switchSidesBased = switchSides
+}
+
+void function SetSuddenDeathBased( bool suddenDeathBased )
+{
+ file.suddenDeathBased = suddenDeathBased
+}
+
+void function SetShouldUseRoundWinningKillReplay( bool shouldUse )
+{
+ SetServerVar( "roundWinningKillReplayEnabled", shouldUse )
+}
+
+// is this necessary? idk really
+void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan )
+{
+ file.roundWinningKillReplayTrackPilotKills = pilot
+ file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this
+}
+
+void function SetRoundWinningKillReplayAttacker( entity target )
+{
+ file.roundWinningKillReplayAttacker = target
+}
+
+void function SetWinner( int team, string winningReason = "", string losingReason = "" )
+{
+ SetServerVar( "winningTeam", team )
+
+ if ( winningReason.len() == 0 )
+ file.announceRoundWinnerWinningSubstr = 0
+ else
+ file.announceRoundWinnerWinningSubstr = GetStringID( winningReason )
+
+ if ( losingReason.len() == 0 )
+ file.announceRoundWinnerLosingSubstr = 0
+ else
+ file.announceRoundWinnerLosingSubstr = GetStringID( losingReason )
+
+ if ( IsRoundBased() )
+ {
+ if ( team != TEAM_UNASSIGNED )
+ {
+ GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + 1 )
+ GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + 1 )
+ }
+
+ SetGameState( eGameState.WinnerDetermined )
+ }
+ else
+ SetGameState( eGameState.WinnerDetermined )
+}
+
+void function AddTeamScore( int team, int amount )
+{
+ GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + amount )
+ GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + amount )
+
+ int scoreLimit
+ if ( IsRoundBased() )
+ scoreLimit = GameMode_GetRoundScoreLimit( GAMETYPE )
+ else
+ scoreLimit = GameMode_GetScoreLimit( GAMETYPE )
+
+ int score = GameRules_GetTeamScore( team )
+ if ( score >= scoreLimit || GetGameState() == eGameState.SuddenDeath )
+ SetWinner( team )
+ else if ( ( file.switchSidesBased && !file.hasSwitchedSides ) && score >= ( scoreLimit.tofloat() / 2.0 ) )
+ SetGameState( eGameState.SwitchingSides )
+}
+
+void function SetRoundWinningKillReplayInfo( entity victim, entity attacker, int methodOfDeath, float timeOfDeath ) // can't just pass in a damageinfo because they seem to die over time somehow
+{
+ file.roundWinningKillReplayVictim = victim
+ file.roundWinningKillReplayAttacker = attacker
+ file.roundWinningKillReplayMethodOfDeath = methodOfDeath
+ file.roundWinningKillReplayTimeOfDeath = timeOfDeath
+ if ( attacker != null )
+ file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker )
+}
+
+void function SetTimeoutWinnerDecisionFunc( int functionref() callback )
+{
+ file.timeoutWinnerDecisionFunc = callback
+}
+
+// idk
+
+float function GameState_GetTimeLimitOverride()
+{
+ return 100
+}
+
+bool function IsRoundBasedGameOver()
+{
+ return false
+}
+
+bool function ShouldRunEvac()
+{
+ return true
+}
+
+void function GiveTitanToPlayer(entity player)
+{
+
+}
+
+float function GetTimeLimit_ForGameMode()
+{
+ return 100.0
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_goblin_dropship.nut b/Northstar.CustomServers/scripts/vscripts/mp/_goblin_dropship.nut
new file mode 100644
index 000000000..fe36e6681
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_goblin_dropship.nut
@@ -0,0 +1,784 @@
+untyped
+
+global function GoblinDropship_Init
+
+#if MP
+ global function GetZiplineDropshipSpawns
+#endif //MP
+global function RunDropshipDropoff
+global function DropshipFindDropNodes
+global function AnaylsisFuncDropshipFindDropNodes
+global function AddTurret
+global function SetDropTableSpawnFuncs
+
+const LINEGEN_DEBUG = 0
+global const bool FLIGHT_PATH_DEBUG = false
+const LINEGEN_TIME = 600.0
+
+const OPTIMAL_ZIPNODE_DIST_SQRD = 16384 //128 sqrd
+// 4096 64 sqrd
+// 65536 256 sqrd
+
+struct
+{
+ array<entity> ziplineDropshipSpawns
+
+ table < var, var > dropshipSound = {
+ [ TEAM_IMC ] = {
+ [ DROPSHIP_STRAFE ] = "Goblin_IMC_TroopDeploy_Flyin",
+ [ DROPSHIP_VERTICAL ] = "Goblin_Dropship_Flyer_Attack_Vertical_Succesful",
+ [ DROPSHIP_FLYER_ATTACK_ANIM_VERTICAL ] = "Goblin_Flyer_Dropshipattack_Vertical",
+ [ DROPSHIP_FLYER_ATTACK_ANIM ] = "Goblin_Flyer_Dropshipattack"
+ },
+ [ TEAM_MILITIA ] = {
+ [ DROPSHIP_STRAFE ] = "Crow_MCOR_TroopDeploy_Flyin",
+ [ DROPSHIP_VERTICAL ] = "Crow_Dropship_Flyer_Attack_Vertical_Succesful",
+ [ DROPSHIP_FLYER_ATTACK_ANIM_VERTICAL ] = "Crow_Flyer_Dropshipattack_Vertical",
+ [ DROPSHIP_FLYER_ATTACK_ANIM ] = "Crow_Flyer_Dropshipattack"
+ }
+ }
+
+} file
+
+function GoblinDropship_Init()
+{
+ RegisterSignal( "OnDropoff" )
+ RegisterSignal( "embark" )
+ RegisterSignal( "WarpedIn" )
+ PrecacheImpactEffectTable( "dropship_dust" )
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+}
+
+void function EntitiesDidLoad()
+{
+ //Generate a list of valid zipline dropship drop off points.
+ #if MP
+ BuildZiplineDropshipSpawnPoints()
+ #endif //MP
+}
+
+#if MP
+void function BuildZiplineDropshipSpawnPoints()
+{
+ array<entity> spawnPoints = SpawnPoints_GetDropPod()
+ file.ziplineDropshipSpawns = []
+
+ foreach ( entity spawnPoint in spawnPoints )
+ {
+ if ( !DropshipCanZiplineDropAtSpawnPoint( spawnPoint ) )
+ continue
+
+ file.ziplineDropshipSpawns.append( spawnPoint )
+ }
+
+ //Assert( file.dropshipSpawns.len() > 0, "No valid zipline dropship spawns exist in this map." )
+}
+
+//Function returns an array of level droppod spawns that have been pretested to ensure they have the space for zipline deployments.
+array<entity> function GetZiplineDropshipSpawns()
+{
+ return clone file.ziplineDropshipSpawns
+}
+#endif //MP
+
+bool function AnaylsisFuncDropshipFindDropNodes( FlightPath flightPath, vector origin, float yaw )
+{
+ return DropshipFindDropNodes( flightPath, origin, yaw, "both", false, IsLegalFlightPath ).len() != 0
+}
+
+// run from TryAnalysisAtOrigin
+table<string,table<string,NodeFP> > function DropshipFindDropNodes( FlightPath flightPath, vector origin, float yaw, string side = "both", ignoreCollision = false, bool functionref( FlightPath, vector, vector, vector, bool = 0 ) legalFlightFunc = null, bool amortize = false )
+{
+ // find nodes to deploy to
+ table<string,table<string,NodeFP> > foundNodes
+
+ vector angles = Vector( 0, yaw, 0 )
+ vector forward = AnglesToForward( angles )
+ vector right = AnglesToRight( angles )
+ Point start = GetWarpinPosition( flightPath.model, flightPath.anim, origin, angles )
+ if ( fabs( start.origin.x ) > MAX_WORLD_COORD )
+ return {}
+ if ( fabs( start.origin.y ) > MAX_WORLD_COORD )
+ return {}
+ if ( fabs( start.origin.z ) > MAX_WORLD_COORD )
+ return {}
+
+ if ( !ignoreCollision )
+ {
+ if ( !legalFlightFunc( flightPath, origin, forward, right, FLIGHT_PATH_DEBUG && !IsActiveNodeAnalysis() ) )
+ return {}
+ }
+
+ Point deployPoint = GetPreviewPoint( flightPath )
+ vector deployOrigin = GetOriginFromPoint( deployPoint, origin, forward, right )
+ vector deployAngles = GetAnglesFromPoint( deployPoint, angles )
+ float deployYaw = deployAngles.y
+
+ // flatten it
+ deployAngles.x = 0
+ deployAngles.z = 0
+
+ float pitch = 50
+ vector deployRightAngles = AnglesCompose( deployAngles, Vector( 0, -90, 0 ) )
+ deployRightAngles = AnglesCompose( deployRightAngles, Vector( pitch, 0, 0 ) )
+
+ vector deployLeftAngles = AnglesCompose( deployAngles, Vector( 0, 90, 0 ) )
+ deployLeftAngles = AnglesCompose( deployLeftAngles, Vector( pitch, 0, 0 ) )
+
+ table<int,NodeFP> nodeTable
+ bool foundRightNodes = false
+ bool foundLeftNodes = false
+
+ if ( side == "right" || side == "both" || side == "either" )
+ {
+ nodeTable = FindDropshipDeployNodes( deployOrigin, deployRightAngles, amortize )
+ if ( LINEGEN_DEBUG )
+ {
+ foreach( node in nodeTable )
+ DebugDrawLine( deployOrigin, node.origin, 200, 200, 200, true, 30.0 )
+ }
+
+ if ( nodeTable.len() )
+ {
+ if ( amortize )
+ WaitFrame()
+ foundRightNodes = FindBestDropshipNodesForSide( foundNodes, nodeTable, "right", flightPath, origin, forward, right, angles, deployOrigin, deployRightAngles, amortize )
+ }
+
+ if ( !foundRightNodes && side != "either" )
+ return {}
+
+ if ( amortize )
+ WaitFrame()
+ }
+
+ if ( side == "left" || side == "both" || side == "either" )
+ {
+ nodeTable = FindDropshipDeployNodes( deployOrigin, deployLeftAngles, amortize )
+ if ( nodeTable.len() )
+ {
+ if ( amortize )
+ WaitFrame()
+ foundLeftNodes = FindBestDropshipNodesForSide( foundNodes, nodeTable, "left", flightPath, origin, forward, right, angles, deployOrigin, deployLeftAngles, amortize )
+ }
+
+ if ( !foundLeftNodes && side != "either" )
+ return {}
+ }
+
+ if ( !foundRightNodes && !foundLeftNodes )
+ return {}
+
+ if ( LINEGEN_DEBUG || FLIGHT_PATH_DEBUG )
+ {
+ DrawArrow( origin, angles, 15.0, 250 )
+ float time = 500.0
+ foreach ( side, nodes in foundNodes )
+ {
+ //DebugDrawText( nodes.centerNode.origin + Vector(0,0,55), nodes.centerNode.fraction + "", true, time )
+ //DebugDrawText( nodes.centerNode.origin, "" + nodes.centerNode.dot, true, time )
+ DebugDrawLine( nodes.centerNode.origin, nodes.centerNode.attachOrigin, 120, 255, 120, true, time )
+ DebugDrawCircle( nodes.centerNode.origin, Vector( 0,0,0 ), 15, 120, 255, 120, true, time )
+
+ //DebugDrawText( nodes.leftNode.origin + Vector(0,0,55), nodes.leftNode.fraction + "", true, time )
+ //DebugDrawText( nodes.leftNode.origin, "" + nodes.leftNode.dot, true, time )
+ DebugDrawLine( nodes.leftNode.origin, nodes.leftNode.attachOrigin, 255, 120, 120, true, time )
+ DebugDrawCircle( nodes.leftNode.origin, Vector( 0,0,0 ), 15, 255, 120, 120, true, time )
+
+ //DebugDrawText( nodes.rightNode.origin + Vector(0,0,55), nodes.rightNode.fraction + "", true, time )
+ //DebugDrawText( nodes.rightNode.origin, "" + nodes.rightNode.dot, true, time )
+ DebugDrawLine( nodes.rightNode.origin, nodes.rightNode.attachOrigin, 120, 120, 255, true, time )
+ DebugDrawCircle( nodes.rightNode.origin, Vector( 0,0,0 ), 15, 120, 120, 255, true, time )
+
+ //DebugDrawLine( nodes.rightNode.origin, nodes.centerNode.origin, 200, 200, 200, true, time )
+ //DebugDrawText( nodes.rightNode.origin + Vector(0,0,20), "dist: " + Distance( nodes.rightNode.origin, nodes.centerNode.origin ), true, time )
+ //DebugDrawLine( nodes.leftNode.origin, nodes.centerNode.origin, 200, 200, 200, true, time )
+ //DebugDrawText( nodes.leftNode.origin + Vector(0,0,20), "dist: " + Distance( nodes.leftNode.origin, nodes.centerNode.origin ), true, time )
+
+ //DebugDrawLine( origin, origin + deployForward * 200, 50, 255, 50, true, time )
+
+ // foreach ( node in nodes.rightNodes )
+ // {
+ // DebugDrawText( node.origin + Vector(0,0,25), "R", true, 15 )
+ // }
+ //
+ // foreach ( node in nodes.leftNodes )
+ // {
+ // DebugDrawText( node.origin + Vector(0,0,25), "L", true, 15 )
+ // }
+ }
+
+// IsLegalFlightPath( flightPath, origin, forward, right, true )
+ }
+
+ return foundNodes
+}
+
+
+table<int,NodeFP> function FindDropshipDeployNodes( vector deployOrigin, vector deployAngles, bool amortize = false )
+{
+ vector deployForward = AnglesToForward( deployAngles )
+
+ vector end = deployOrigin + deployForward * 3000
+ TraceResults result = TraceLine( deployOrigin, end, null, TRACE_MASK_NPCWORLDSTATIC )
+
+ if ( LINEGEN_DEBUG )
+ {
+ DebugDrawLine( deployOrigin, result.endPos, 255, 255, 255, true, LINEGEN_TIME )
+ DebugDrawText( result.endPos + Vector( 0,0,10 ), "test", true, LINEGEN_TIME )
+ DebugDrawCircle( result.endPos, Vector( 0,0,0 ), 35, 255, 255, 255, true, LINEGEN_TIME )
+ }
+ // no hit?
+ if ( result.fraction >= 1.0 )
+ return {}
+
+ int node = GetNearestNodeToPos( result.endPos )
+ if ( node == -1 )
+ return {}
+
+ if ( LINEGEN_DEBUG )
+ {
+ DebugDrawText( GetNodePos( node ) + Vector(0,0,10), "nearest node", true, 15.0 )
+ DebugDrawCircle( GetNodePos( node ), Vector( 0,0,0 ), 20, 60, 60, 255, true, LINEGEN_TIME )
+ }
+
+ array<vector> neighborPositions = NavMesh_GetNeighborPositions( GetNodePos( node ), HULL_HUMAN, 20 )
+
+ if ( amortize )
+ WaitFrame()
+
+ table<int,NodeFP> nodeTable = {}
+ int uniqueID = -2
+ foreach ( pos in neighborPositions )
+ {
+ NodeFP attachPoint
+ attachPoint.origin = pos
+ attachPoint.uniqueID = uniqueID
+
+ nodeTable[ uniqueID ] <- attachPoint
+ uniqueID--
+ }
+
+ return nodeTable
+}
+
+void function AddDirectionVec( array<NodeFP> nodeArray, vector origin )
+{
+ // different direction vecs because we want a node to the left, center, and straight
+ foreach ( node, tab in nodeArray )
+ {
+ vector vec
+ vec = tab.origin - origin
+ vec.Norm()
+ tab.vec = vec
+ }
+}
+
+void function AddDirectionVecFromDir( array<NodeFP> nodeArray, vector origin, vector dir )
+{
+ // different direction vecs because we want a node to the left, center, and straight
+ foreach ( node, tab in nodeArray )
+ {
+ vector vec
+ vec = ( tab.origin + dir * 50 ) - origin
+ vec.Norm()
+ tab.vec = vec
+ }
+}
+
+bool function FindBestDropshipNodesForSide( table<string,table<string,NodeFP> > foundNodes, table<int,NodeFP> nodeTable, string side, FlightPath flightPath, vector origin, vector forward, vector right, vector angles, vector deployOrigin, vector deployAngles, bool amortize )
+{
+ vector deployForward = AnglesToForward( deployAngles )
+ vector deployRight = AnglesToRight( deployAngles )
+
+ float RatioForLeftRight = 0.2
+ vector RightDeployForward = ( ( deployForward * ( 1.0 - RatioForLeftRight ) ) + ( deployRight * RatioForLeftRight * -1 ) )
+ RightDeployForward.Norm()
+ vector LeftDeployForward = ( ( deployForward * ( 1.0 - RatioForLeftRight ) ) + ( deployRight * RatioForLeftRight ) )
+ LeftDeployForward.Norm()
+
+ if ( amortize )
+ WaitFrame()
+
+ foundNodes[ side ] <- {}
+ array<AttachPoint> attachPoints = GetAttachPoints( flightPath, side )
+
+ array<NodeFP> centerNodes = GetNodeArrayFromTable( nodeTable )
+ AddDirectionVec( centerNodes, deployOrigin )
+ NodeFP centerNode = GetBestDropshipNode( attachPoints[2], centerNodes, origin, deployForward, forward, right, angles, NullNodeFP )
+ if ( centerNode == NullNodeFP )
+ return false
+ delete nodeTable[ centerNode.uniqueID ]
+
+ if ( amortize )
+ WaitFrame()
+
+ array<NodeFP> leftNodes = GetCulledNodes( nodeTable, deployRight * -1 )
+ AddDirectionVecFromDir( leftNodes, deployOrigin, deployRight * -1 )
+ NodeFP leftNode = GetBestDropshipNode( attachPoints[1], leftNodes, origin, RightDeployForward, forward, right, angles, centerNode )
+ if ( leftNode == NullNodeFP )
+ return false
+ delete nodeTable[ leftNode.uniqueID ]
+
+ if ( amortize )
+ WaitFrame()
+
+ array<NodeFP> rightNodes = GetCulledNodes( nodeTable, deployRight )
+ AddDirectionVecFromDir( rightNodes, deployOrigin, deployRight )
+ NodeFP rightNode = GetBestDropshipNode( attachPoints[0], rightNodes, origin, LeftDeployForward, forward, right, angles, centerNode )
+ if ( rightNode == NullNodeFP )
+ return false
+
+ table<string,NodeFP> Table
+ Table.centerNode <- centerNode
+ Table.leftNode <- leftNode
+ Table.rightNode <- rightNode
+
+ //Table.rightNodes <- rightNodes // for debug
+ //Table.leftNodes <- leftNodes // for debug
+
+ foundNodes[ side ] = Table
+ return true
+}
+
+array<NodeFP> function GetNodeArrayFromTable( table<int,NodeFP> nodeTable )
+{
+ array<NodeFP> Array
+ foreach ( Table in nodeTable )
+ {
+ Array.append( Table )
+ }
+
+ return Array
+}
+
+array<NodeFP> function GetCulledNodes( table<int,NodeFP> nodeTable, vector right )
+{
+ table<int,NodeFP> leftNodes
+ // get the nodes on the left
+ foreach ( nod, tab in nodeTable )
+ {
+ float dot = DotProduct( tab.vec, right )
+ if ( dot >= 0.0 )
+ {
+ leftNodes[ nod ] <- tab
+ }
+ }
+
+ return GetNodeArrayFromTable( leftNodes )
+}
+
+NodeFP function GetBestDropshipNode( AttachPoint attachPoint, array<NodeFP> nodeArray, vector origin, vector deployForward, vector forward, vector right, vector angles, NodeFP centerNode, bool showdebug = false )
+{
+ foreach ( node in nodeArray )
+ {
+ node.dot = DotProduct( node.vec, deployForward )
+ if ( showdebug )
+ {
+ DebugDrawText( node.origin, "dot: " + node.dot, true, 15.0 )
+ int green = 0
+ int red = 255
+ if ( node.dot > 0.9 )
+ {
+ float frac = ( 1.0 - node.dot ) / 0.1
+ frac = 1.0 - frac
+
+ green = int( frac * 255 )
+ red -= green
+ }
+
+ DebugDrawLine( node.origin, node.origin + ( node.vec * -1000 ), red, green, 0, true, 15.0 )
+ DebugDrawCircle( node.origin, Vector( 0,0,0 ), 25, red, green, 0, true, 15.0 )
+ }
+ }
+
+ if ( !nodeArray.len() )
+ return NullNodeFP
+
+ vector attachOrigin = GetOriginFromAttachPoint( attachPoint, origin, forward, right )
+ vector attachAngles = GetAnglesFromAttachPoint( attachPoint, angles )
+ vector attachForward = AnglesToForward( attachAngles )
+ vector attachRight = AnglesToRight( attachAngles )
+
+ FlightPath offsetAnalysis = GetAnalysisForModel( TEAM_IMC_GRUNT_MODEL, ZIPLINE_IDLE_ANIM )
+ Point offsetPoint = GetPreviewPoint( offsetAnalysis )
+ vector offsetOrigin = GetOriginFromPoint( offsetPoint, attachOrigin, attachForward, attachRight )
+// DebugDrawLine( offsetOrigin, attachOrigin, 255, 255, 0, true, 15 )
+
+ nodeArray.sort( SortHighestDot )
+
+ vector mins = GetBoundsMin( HULL_HUMAN )
+ vector maxs = GetBoundsMax( HULL_HUMAN )
+
+ array<NodeFP> passedNodes
+
+ for ( int i = 0; i < nodeArray.len(); i++ )
+ {
+ NodeFP node = nodeArray[i]
+
+ // beyond the allowed dot
+ if ( node.dot < 0.3 )
+ return NullNodeFP
+
+ // trace to see if the ai could drop to the node from here
+ TraceResults result = TraceHull( offsetOrigin, node.origin, mins, maxs, null, TRACE_MASK_NPCWORLDSTATIC, TRACE_COLLISION_GROUP_NONE )
+ if ( result.fraction < 1.0 )
+ continue //return
+
+ // trace to insure that there will be a good place to hook the zipline
+ if ( !GetHookOriginFromNode( offsetOrigin, node.origin, attachOrigin ) )
+ continue
+
+ node.fraction = result.fraction
+ node.attachOrigin = offsetOrigin
+ node.attachName = attachPoint.name
+
+ if ( centerNode != NullNodeFP )
+ {
+ //test for distance, not too close, not too far
+ local distSqr = DistanceSqr( centerNode.origin, node.origin )
+ node.rating = fabs( OPTIMAL_ZIPNODE_DIST_SQRD - distSqr )
+ passedNodes.append( node )
+ continue
+ }
+
+ return node
+ }
+
+ if ( centerNode != NullNodeFP && passedNodes.len() )
+ {
+ passedNodes.sort( SortLowestRating )
+ return passedNodes[ 0 ]
+ }
+
+ return NullNodeFP
+}
+
+int function SortHighestDot( NodeFP a, NodeFP b )
+{
+ if ( a.dot > b.dot )
+ return -1
+
+ if ( a.dot < b.dot )
+ return 1
+
+ return 0
+}
+
+int function SortLowestRating( NodeFP a, NodeFP b )
+{
+ if ( a.rating > b.rating )
+ return 1
+
+ if ( a.rating < b.rating )
+ return -1
+
+ return 0
+}
+
+void function SetDropTableSpawnFuncs( CallinData drop, entity functionref( int, vector, vector ) spawnFunc, int count )
+{
+ array<entity functionref( int, vector, vector )> spawnFuncArray
+ //spawnFuncArray.resize( count, spawnFunc )
+ for ( int i = 0; i < count; i++ )
+ {
+ spawnFuncArray.append( spawnFunc )
+ }
+ drop.npcSpawnFuncs = spawnFuncArray
+}
+
+asset function GetTeamDropshipModel( int team, bool hero = false )
+{
+ if ( hero )
+ {
+ if ( team == TEAM_IMC )
+ return GetFlightPathModel( "fp_dropship_hero_model" )
+ else
+ return GetFlightPathModel( "fp_crow_hero_model" )
+ }
+ else
+ {
+ if ( team == TEAM_IMC )
+ return GetFlightPathModel( "fp_dropship_model" )
+ else
+ return GetFlightPathModel( "fp_crow_model" )
+ }
+
+ unreachable
+}
+
+//This function tests to see if the given spawn point has enough clearance for a dropship to deploy zipline grunts.
+bool function DropshipCanZiplineDropAtSpawnPoint( entity spawnPoint )
+{
+ CallinData drop
+ drop.origin = spawnPoint.GetOrigin()
+ drop.yaw = spawnPoint.GetAngles().y
+ drop.dist = 768
+ SetCallinStyle( drop, eDropStyle.ZIPLINE_NPC )
+ int style = drop.style
+
+ bool validSpawn = false
+ array<string> anims = GetRandomDropshipDropoffAnims()
+
+ string animation
+ FlightPath flightPath
+
+ foreach ( anim in anims )
+ {
+ animation = anim
+ flightPath = GetAnalysisForModel( DROPSHIP_MODEL, anim )
+
+ if ( style == eDropStyle.NONE )
+ {
+ if ( !drop.yawSet )
+ {
+ style = eDropStyle.NEAREST
+ }
+ else
+ {
+ style = eDropStyle.NEAREST_YAW
+ }
+ }
+
+ validSpawn = TestSpawnPointForStyle( flightPath, drop )
+
+ if ( validSpawn )
+ return true
+ }
+
+ return false
+}
+
+function RunDropshipDropoff( CallinData Table )
+{
+ vector origin = Table.origin
+ float yaw = Table.yaw
+ int team = Table.team
+ entity owner = Table.owner
+ string squadname = Table.squadname
+ string side = Table.side
+ array<entity functionref( int, vector, vector )> npcSpawnFuncs = Table.npcSpawnFuncs
+ int style = Table.style
+ int health = 7800
+
+ if ( Table.dropshipHealth != 0 )
+ health = Table.dropshipHealth
+ Table.success = false
+
+ if ( Flag( "DisableDropships" ) )
+ return
+
+ if ( team == 0 )
+ {
+ if ( owner )
+ team = owner.GetTeam()
+ else
+ team = 0
+ }
+
+ SpawnPointFP spawnPoint
+ array<string> anims = GetRandomDropshipDropoffAnims()
+
+ // Override anim, level scripter takes responsibility for it working in this location or not
+ if ( Table.anim != "" )
+ {
+ anims.clear()
+ anims.append( Table.anim )
+ }
+
+ string animation
+ FlightPath flightPath
+ bool wasPlayerOwned = IsValid( owner ) && IsValidPlayer( owner )
+
+ foreach ( anim in anims )
+ {
+ animation = anim
+ flightPath = GetAnalysisForModel( DROPSHIP_MODEL, anim )
+
+ if ( style == eDropStyle.NONE )
+ {
+ if ( !Table.yawSet )
+ {
+ style = eDropStyle.NEAREST
+ }
+ else
+ {
+ style = eDropStyle.NEAREST_YAW
+ }
+ }
+
+ spawnPoint = GetSpawnPointForStyle( flightPath, Table )
+
+ if ( spawnPoint.valid )
+ break
+ }
+
+ if ( !spawnPoint.valid )
+ {
+ printt( "Couldn't find good spawn location for dropship" )
+ return
+ }
+
+ Table.success = true
+
+ entity ref = CreateScriptRef()
+ if ( Table.forcedPosition )
+ {
+ ref.SetOrigin( Table.origin )
+ ref.SetAngles( Vector( 0, Table.yaw, 0 ) )
+ }
+ else
+ {
+ ref.SetOrigin( spawnPoint.origin )
+ ref.SetAngles( spawnPoint.angles )
+ }
+
+ // used for when flyers attack dropships
+ if ( "nextDropshipAttackedByFlyers" in level && level.nextDropshipAttackedByFlyers )
+ animation = FlyersAttackDropship( ref, animation )
+
+ Assert( IsNewThread(), "Must be threaded off" )
+
+ DropTable dropTable
+
+ if ( Table.dropTable.valid )
+ {
+ dropTable = Table.dropTable
+ }
+ else
+ {
+ bool ignoreCollision = true // = style == eDropStyle.FORCED
+ thread FindDropshipZiplineNodes( dropTable, flightPath, ref.GetOrigin(), ref.GetAngles(), side, ignoreCollision, true )
+ }
+
+ asset model = GetTeamDropshipModel( team )
+ waitthread WarpinEffect( model, animation, ref.GetOrigin(), ref.GetAngles() )
+ entity dropship = CreateDropship( team, ref.GetOrigin(), ref.GetAngles() )
+ SetSpawnOption_SquadName( dropship, squadname )
+ dropship.kv.solid = SOLID_VPHYSICS
+ DispatchSpawn( dropship )
+ Table.dropship = dropship
+ //dropship.SetPusher( true )
+ dropship.SetHealth( health )
+ dropship.SetMaxHealth( health )
+ Table.dropship = dropship
+ dropship.EndSignal( "OnDeath" )
+ dropship.Signal( "WarpedIn" )
+ ref.Signal( "WarpedIn" )
+ Signal( Table, "WarpedIn" )
+
+ AddDropshipDropTable( dropship, dropTable ) // this is where the ai will drop to
+
+ if ( IsValid( owner ) )
+ {
+ dropship.SetCanCloak( false )
+ dropship.SetOwner( owner )
+ if ( owner.IsPlayer() )
+ dropship.SetBossPlayer( owner )
+ }
+
+ local dropshipSound = GetTeamDropshipSound( team, animation )
+ if ( Table.customSnd != "" )
+ dropshipSound = Table.customSnd
+
+ OnThreadEnd(
+ function() : ( dropship, ref, Table, dropshipSound )
+ {
+ ref.Destroy()
+ if ( IsValid( dropship ) )
+ StopSoundOnEntity( dropship, dropshipSound )
+ if ( IsAlive( dropship ) )
+ {
+ dropship.Destroy()
+ }
+
+ Signal( Table, "OnDropoff", { guys = null } )
+ }
+ )
+
+ array<entity> guys
+ if ( !wasPlayerOwned || IsValidPlayer( owner ) )
+ {
+ guys = CreateNPCSForDropship( dropship, Table.npcSpawnFuncs, side )
+
+ foreach ( guy in guys )
+ {
+ if ( IsAlive( guy ) )
+ {
+ if ( IsValidPlayer( owner ) )
+ {
+ NPCFollowsPlayer( guy, owner )
+ }
+ }
+ }
+ }
+
+ //thread DropshipMissiles( dropship )
+ dropship.Hide()
+ EmitSoundOnEntity( dropship, dropshipSound ) //HACK: Note that the anims can play sounds too! For R3 just make it consistent so it's all played in script or all played in anims
+ thread ShowDropship( dropship )
+ thread PlayAnimTeleport( dropship, animation, ref, 0 )
+
+ ArrayRemoveDead( guys )
+
+ Signal( Table, "OnDropoff", { guys = guys } )
+
+ WaittillAnimDone( dropship )
+ wait 2.0
+}
+
+void function FindDropshipZiplineNodes( DropTable dropTable, FlightPath flightPath, vector origin, vector angles, string side = "both", bool ignoreCollision = false, bool amortize = false )
+{
+ dropTable.nodes = DropshipFindDropNodes( flightPath, origin, angles.y, side, ignoreCollision, IsLegalFlightPath_OverTime, amortize )
+ dropTable.valid = true
+}
+
+function ShowDropship( dropship )
+{
+ dropship.EndSignal( "OnDestroy" )
+ wait 0.16
+ dropship.Show()
+}
+
+entity function AddTurret( entity dropship, int team, string turretWeapon, string attachment, int health = 700 )
+{
+ entity turret = CreateEntity( "npc_turret_sentry" )
+ turret.kv.TurretRange = 1500
+ turret.kv.AccuracyMultiplier = 1.0
+ turret.kv.FieldOfView = 0.4
+ turret.kv.FieldOfViewAlert = 0.4
+ SetSpawnOption_Weapon( turret, turretWeapon )
+ turret.SetOrigin( Vector(0,0,0) )
+ turret.SetTitle( "#NPC_DROPSHIP" )
+ turret.s.skipTurretFX <- true
+ DispatchSpawn( turret )
+
+ SetTargetName( turret, "DropshipTurret" )
+ turret.SetHealth( health)
+ turret.SetMaxHealth( health )
+ turret.Hide()
+ //turret.Show()
+ entity weapon = turret.GetActiveWeapon()
+ weapon.Hide()
+ SetTeam( turret, team )
+ turret.SetParent( dropship, attachment, false )
+ turret.EnableTurret()
+ turret.SetOwner( dropship.GetOwner() )
+ turret.SetAimAssistAllowed( false )
+
+ entity bossPlayer = dropship.GetBossPlayer()
+ if ( IsValidPlayer( bossPlayer ) )
+ turret.SetBossPlayer( dropship.GetBossPlayer() )
+
+ HideName( turret )
+ return turret
+}
+
+function GetTeamDropshipSound( team, animation )
+{
+ Assert( team in file.dropshipSound )
+ Assert( animation in file.dropshipSound[ team ] )
+
+ return file.dropshipSound[ team ][ animation ]
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_lasermesh.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_lasermesh.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_lasermesh.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_loadout_crate.nut b/Northstar.CustomServers/scripts/vscripts/mp/_loadout_crate.nut
new file mode 100644
index 000000000..d987c774c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_loadout_crate.nut
@@ -0,0 +1,183 @@
+untyped
+
+global function LoadoutCrate_Init
+
+const LOADOUT_CRATE_MODEL = $"models/containers/pelican_case_ammobox.mdl"
+global function AddLoadoutCrate
+global function DestroyAllLoadoutCrates
+
+function LoadoutCrate_Init()
+{
+ level.loadoutCrateManagedEntArrayID <- CreateScriptManagedEntArray()
+ PrecacheModel( LOADOUT_CRATE_MODEL )
+
+ AddSpawnCallback( "prop_dynamic", LoadoutCreatePrePlaced )
+}
+
+function AddLoadoutCrate( team, vector origin, vector angles, bool showOnMinimap = true, entity crate = null )
+{
+ expect int( team )
+
+ local crateCount = GetScriptManagedEntArray( level.loadoutCrateManagedEntArrayID ).len()
+ Assert( crateCount < MAX_LOADOUT_CRATE_COUNT, "Can't have more than " + MAX_LOADOUT_CRATE_COUNT + " Loadout Crates" )
+
+ angles += Vector( 0, -90, 0 )
+
+ if ( !IsValid( crate ) )
+ {
+ crate = CreatePropScript( LOADOUT_CRATE_MODEL, origin, angles, 6 )
+ SetTargetName( crate, "loadoutCrate" )
+ }
+
+ SetTeam( crate, team )
+ crate.SetUsable()
+ if ( team == TEAM_MILITIA || team == TEAM_IMC )
+ crate.SetUsableByGroup( "friendlies pilot" )
+ else
+ crate.SetUsableByGroup( "pilot" )
+
+ crate.SetUsePrompts( "#LOADOUT_CRATE_HOLD_USE", "#LOADOUT_CRATE_PRESS_USE" )
+ crate.SetForceVisibleInPhaseShift( true )
+
+ if ( showOnMinimap )
+ {
+ #if R1_VGUI_MINIMAP
+ crate.Minimap_SetDefaultMaterial( $"vgui/hud/coop/coop_ammo_locker_icon" )
+ #endif
+ crate.Minimap_SetObjectScale( MINIMAP_LOADOUT_CRATE_SCALE )
+ crate.Minimap_SetAlignUpright( true )
+ crate.Minimap_AlwaysShow( TEAM_IMC, null )
+ crate.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ crate.Minimap_SetHeightTracking( true )
+ crate.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ crate.Minimap_SetCustomState( eMinimapObject_prop_script.FD_LOADOUT_CHEST )
+ }
+
+ AddToScriptManagedEntArray( level.loadoutCrateManagedEntArrayID, crate )
+
+ //thread LoadoutCrateMarkerThink( "LoadoutCrateMarker" + string( crateCount ), crate )
+ thread LoadoutCrateThink( crate )
+ thread LoadoutCrateRestockAmmoThink( crate )
+
+ Highlight_SetNeutralHighlight( crate, "interact_object_los" )
+}
+
+void function LoadoutCreatePrePlaced( entity ent )
+{
+ if ( ent.GetTargetName().find( "loot_crate" ) == 0 )
+ {
+ ent.Destroy()
+ return
+ }
+
+ if ( ent.GetTargetName().find( "loadout_crate" ) != 0 )
+ return
+
+ if ( IsSingleplayer() )
+ return
+
+ vector angles = ent.GetAngles() + Vector( 0, 90, 0 )
+ AddLoadoutCrate( TEAM_BOTH, ent.GetOrigin(), angles, false, ent )
+}
+
+function LoadoutCrateMarkerThink( marker, crate )
+{
+ crate.EndSignal( "OnDestroy" )
+ crate.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( marker )
+ {
+ ClearMarker( marker )
+ }
+ )
+
+ while ( 1 )
+ {
+ if ( GetGameState() <= eGameState.Prematch )
+ ClearMarker( marker )
+ else
+ SetMarker( marker, crate )
+
+ svGlobal.levelEnt.WaitSignal( "GameStateChanged" )
+ }
+}
+
+function LoadoutCrateThink( crate )
+{
+ crate.EndSignal( "OnDestroy" )
+ while ( true )
+ {
+ var player = crate.WaitSignal( "OnPlayerUse" ).player
+
+ if ( player.IsPlayer() )
+ {
+ thread UsingLoadoutCrate( crate, player )
+ wait 1 // debounce on using the crate to minimize the risk of using it twice before the menu opens.
+ }
+ }
+}
+
+function LoadoutCrateRestockAmmoThink( crate )
+{
+ crate.EndSignal( "OnDestroy" )
+ local distSqr
+ local crateOrigin = crate.GetOrigin()
+ local triggerDistSqr = 96 * 96
+ local resetDistSqr = 384 * 384
+
+ while ( true )
+ {
+ wait 1 // check every second
+ array<entity> playerArray = GetPlayerArray_Alive()
+ foreach( player in playerArray )
+ {
+ if ( player.IsTitan() )
+ continue
+
+ if ( player.ContextAction_IsBusy() )
+ continue
+
+ distSqr = DistanceSqr( crateOrigin, player.GetOrigin() )
+ if ( distSqr <= triggerDistSqr && player.s.restockAmmoTime < Time() )
+ {
+ if ( TraceLineSimple( player.EyePosition(), crate.GetOrigin() + Vector( 0.0, 0.0, 24.0 ), crate ) == 1.0 )
+ {
+ player.s.restockAmmoCrate = crate
+ player.s.restockAmmoTime = Time() + 10 // debounce time before you can get new ammo again if you stay next to the crate.
+ //MessageToPlayer( player, eEventNotifications.CoopAmmoRefilled, null, null )
+ RestockPlayerAmmo( player )
+ }
+ }
+
+ if ( distSqr > resetDistSqr && player.s.restockAmmoTime > 0 && player.s.restockAmmoCrate == crate )
+ {
+ player.s.restockAmmoCrate = null
+ player.s.restockAmmoTime = 0
+ }
+ }
+ }
+}
+
+function UsingLoadoutCrate( crate, player )
+{
+ expect entity( player )
+
+ player.p.usingLoadoutCrate = true
+ player.s.usedLoadoutCrate = true
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Coop_AmmoBox_Open" )
+ Remote_CallFunction_UI( player, "ServerCallback_OpenPilotLoadoutMenu" )
+}
+
+// should be called if we enter an epilogue ... maybe?
+function DestroyAllLoadoutCrates()
+{
+ local crateArray = GetScriptManagedEntArray( level.loadoutCrateManagedEntArrayID )
+ foreach( crate in crateArray )
+ crate.Destroy()
+
+ //dissolve didn't work
+ //Dissolve( ENTITY_DISSOLVE_CHAR, Vector( 0, 0, 0 ), 0 )
+ //ENTITY_DISSOLVE_CORE
+ //ENTITY_DISSOLVE_NORMAL
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_mp_mapspawn.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_mp_mapspawn.gnut
new file mode 100644
index 000000000..6860d8176
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_mp_mapspawn.gnut
@@ -0,0 +1,65 @@
+//todo change this to be map-based
+global function SPMP_MapSpawn_Init
+global struct SvSpawnGlobals
+{
+ array<entity> allNormalSpawnpoints
+}
+
+global SvSpawnGlobals svSpawnGlobals
+
+void function SPMP_MapSpawn_Init()
+{
+ printl( "Code Script: _mp_mapspawn" )
+
+ svGlobal.npcsSpawnedThisFrame_scriptManagedArray[ TEAM_IMC ] <- CreateScriptManagedEntArray()
+ svGlobal.npcsSpawnedThisFrame_scriptManagedArray[ TEAM_MILITIA ] <- CreateScriptManagedEntArray()
+
+ level.spawnActions <- {} // these run after all initial spawn functions have run
+ svGlobal.levelEnt = CreateEntity( "info_landmark" )
+ SetTargetName( svGlobal.levelEnt, "Level Ent" )
+ DispatchSpawn( svGlobal.levelEnt )
+ level.isTestmap <- false
+
+ FlagInit( "EntitiesDidLoad" )
+ FlagInit( "PlayerDidSpawn" )
+
+ level.privateMatchForcedEnd <- null
+ level.defenseTeam <- TEAM_IMC
+
+ level.onRodeoStartedCallbacks <- [] // runs when a player starts rodeoing a titan
+ level.onRodeoEndedCallbacks <- [] // runs when a player stops rodeoing a titan
+
+ FlagInit( "FireteamAutoSpawn" )
+ FlagInit( "DebugFoundEnemy" )
+ FlagInit( "OldAnimRefStyle" )
+ FlagInit( "EarlyCatch" )
+ FlagInit( "ForceStartSpawn" )
+ FlagInit( "IgnoreStartSpawn" )
+ FlagInit( "ReadyToStartMatch" ) // past waiting for players, in prematch
+
+ RegisterSignal( "OnChangedPlayerClass" )
+ RegisterSignal( "Disconnected" )
+ RegisterSignal( "_disconnectedInternal" )
+ RegisterSignal( "TeamChange" )
+ RegisterSignal( "LeftClass" )
+ RegisterSignal( "forever" )
+ RegisterSignal( "waitOver" )
+ RegisterSignal( "HitSky" )
+
+ AddSpawnCallback( "trigger_hurt", InitDamageTriggers )
+
+ AddSpawnCallbackEditorClass( "func_brush", "func_brush_navmesh_separator", NavmeshSeparatorThink )
+
+ //AddCallback_EntitiesDidLoad( ActivateSkyBox )
+
+ AddSpawnCallback( "player", MP_PlayerPostInit )
+
+ // unsure if this should be done here, but it's required for mp to load
+ PrecacheModel( $"models/menu/default_environment.mdl" )
+
+ //if ( IsMultiplayer() && GetClassicMPMode() && !IsLobby() )
+ // ClassicMP_TryDefaultIntroSetup()
+
+ //InitDefaultLoadouts()
+ SPMP_Shared_Init()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_music.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_music.gnut
new file mode 100644
index 000000000..443203361
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_music.gnut
@@ -0,0 +1,107 @@
+global function Music_Init
+global function CreateTeamMusicEvent
+global function PlayCurrentTeamMusicEventsOnPlayer
+global function CreateLevelIntroMusicEvent
+global function PlayMusicToCompletion
+global function PlayMusicToAll
+global function CreateLevelWinnerDeterminedMusicEvent
+
+const int MUSIC_EVENT_UNINITIALIZED = -1
+
+
+struct MusicEvent
+{
+ int musicPieceID = MUSIC_EVENT_UNINITIALIZED
+ float timeMusicStarted
+ bool shouldSeek
+}
+
+struct
+{
+ table< int, MusicEvent > musicEvents
+} file
+
+
+void function Music_Init()
+{
+ MusicEvent imcMusicEvent
+ MusicEvent militiaMusicEvent
+ file.musicEvents[ TEAM_IMC ] <- imcMusicEvent
+ file.musicEvents[ TEAM_MILITIA ] <- militiaMusicEvent
+
+ AddCallback_GameStateEnter( eGameState.Prematch, CreateLevelIntroMusicEvent )
+}
+
+void function CreateTeamMusicEvent( int team, int musicPieceID, float timeMusicStarted, bool shouldSeek = true )
+{
+ Assert( !( shouldSeek == false && timeMusicStarted > 0 ), "Don't pass in timeMusicStarted when creating a TeamMusicEvent with shouldSeek set to false!" )
+
+ MusicEvent musicEvent
+ musicEvent.musicPieceID = musicPieceID
+ musicEvent.timeMusicStarted = timeMusicStarted
+ musicEvent.shouldSeek = shouldSeek
+
+ file.musicEvents[ team ] = musicEvent
+}
+
+void function PlayCurrentTeamMusicEventsOnPlayer( entity player )
+{
+ int team = player.GetTeam()
+ MusicEvent musicEvent
+
+ if ( team in file.musicEvents )
+ musicEvent = file.musicEvents[ team ]
+ else
+ musicEvent = file.musicEvents[ TEAM_MILITIA ] //This normally means we're in FFA. Fine to failsafe to use any music event
+
+ if ( musicEvent.musicPieceID == MUSIC_EVENT_UNINITIALIZED ) //No current music event
+ return
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayTeamMusicEvent", musicEvent.musicPieceID, musicEvent.timeMusicStarted, musicEvent.shouldSeek )
+}
+
+void function CreateLevelIntroMusicEvent()
+{
+ //printt( "Creating LevelIntroMusicEvent" )
+ CreateTeamMusicEvent( TEAM_IMC, eMusicPieceID.LEVEL_INTRO, Time() )
+ CreateTeamMusicEvent( TEAM_MILITIA, eMusicPieceID.LEVEL_INTRO, Time() )
+}
+
+void function PlayMusicToCompletion( int musicID )
+{
+ array<entity> players = GetPlayerArray()
+ foreach ( entity player in players )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayMusicToCompletion", musicID )
+ }
+}
+
+void function PlayMusicToAll( int musicID )
+{
+ array<entity> players = GetPlayerArray()
+ foreach ( entity player in players )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_PlayMusic", musicID )
+ }
+}
+
+void function CreateLevelWinnerDeterminedMusicEvent()
+{
+ //printt( "Creating CreateLevelWinnerDeterminedMusicEvent" )
+ if ( IsFFAGame() )
+ return
+
+ int winningTeam = GetWinningTeam()
+
+ if ( winningTeam )
+ {
+ int losingTeam = GetOtherTeam( winningTeam )
+ CreateTeamMusicEvent( winningTeam, eMusicPieceID.LEVEL_WIN, Time() )
+ CreateTeamMusicEvent( losingTeam, eMusicPieceID.LEVEL_LOSS, Time() )
+ }
+ else
+ {
+ CreateTeamMusicEvent( TEAM_MILITIA, eMusicPieceID.LEVEL_DRAW, Time() )
+ CreateTeamMusicEvent( TEAM_IMC, eMusicPieceID.LEVEL_DRAW, Time() )
+ }
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut
new file mode 100644
index 000000000..ecf9b3e51
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut
@@ -0,0 +1,1195 @@
+untyped
+
+global function Pickups_Init
+global function AddCollectible
+global function UpdateCollectiblesAfterLoadSaveGame
+global function CreateWeaponPickup
+global function CreatePickup
+global function WaitUntilPlayerPicksUp
+global function AddClipToWeapon
+global function AddRoundsToWeapon
+global function AddRoundToWeapon
+global function AddClipToMainWeapons
+global function AddTwoClipToMainWeapons
+global function AddRoundToOrdnance
+global function AddRoundsToTactical
+global function CreateScriptWeapon
+global function GetAllLeveledScriptWeapons
+global function TitanLoadoutWaitsForPickup // Needed only to be global to spawn pickups from dev menu.
+
+#if DEV
+ global function Dev_ResetCollectiblesProgress_Level
+#endif
+
+const HEALTH_PICKUP_AMOUNT = 3000
+global const PICKUP_GLOW_FX = $"P_ar_titan_droppoint"
+//const HEALTH_MODEL = $"models/vehicle/droppod_fireteam/droppod_fireteam_collision.mdl"
+//const HEALTH_MODEL = $"models/domestic/trash_can_green_closed.mdl"
+//const HEALTH_MODEL = $"models/weapons/bullets/projectile_rocket_large.mdl"
+const HEALTH_MODEL = $"models/gameplay/health_pickup_small.mdl"
+const HEALTH_MODEL_LARGE = $"models/gameplay/health_pickup_large.mdl"
+
+const GRENADE_AMMO_MODEL = $"models/Weapons/ammoboxes/ammobox_01.mdl"
+const LION_MODEL = $"models/statues/lion_statue_bronze_green_small.mdl"
+const HELMET_COLLECTIBLE_MODEL = $"models/humans/heroes/mlt_hero_jack_helmet_static.mdl"
+const COLLECTIBLE_GLOW_EFFECT = $"P_item_bluelion"
+
+
+global struct LeveledScriptedWeapons
+{
+ table<string, bool> foundScriptWeapons
+ array<entity> infoTargets
+}
+
+struct HealthPickup
+{
+ float healAmount
+ float healTime
+ string pickupSound
+ string healSound
+ string endSound
+ asset model
+}
+
+struct Collectible
+{
+ entity ent
+ int id
+ vector pos
+}
+
+struct
+{
+ int nextHealthDropSmall
+ int nextHealthDropLarge
+ float lastHealthDropTime
+ table<string, HealthPickup> healthPickups
+ array<Collectible> collectibles
+ int testMapCollectibleValue
+} file
+
+function Pickups_Init()
+{
+ HealthPickup small
+ small.healAmount = 0.4
+ small.healTime = 1.0
+ small.pickupSound = "Pilot_HealthPack_Small_Pickup"
+ small.healSound = "Pilot_HealthPack_Small_Healing"
+ small.endSound = "Pilot_HealthPack_Small_Healing_End"
+ small.model = HEALTH_MODEL
+ file.healthPickups[ "health_pickup_small" ] <- small
+
+ HealthPickup large
+ large.healAmount = 0.8
+ large.healTime = 2.0
+ large.pickupSound = "Pilot_HealthPack_Large_Pickup"
+ large.healSound = "Pilot_HealthPack_Large_Healing"
+ large.endSound = "Pilot_HealthPack_Large_Healing_End"
+ large.model = HEALTH_MODEL_LARGE
+ file.healthPickups[ "health_pickup_large" ] <- large
+
+
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_health", HealthPickup_OnSpawned )
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_health_large", HealthPickupLarge_OnSpawned )
+ AddSpawnCallbackEditorClass( "script_mover_lightweight", "script_collectible", AddCollectible )
+ AddSpawnCallbackEditorClass( "script_ref", "script_pickup_weapon", CreateWeaponPickup )
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_grenades", CreateGrenadeAmmoPickup )
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_ammo", CreateGrenadeAmmoPickup )
+ AddSpawnCallbackEditorClass( "script_ref", "script_pickup_titan", CreateTitanPickup )
+
+ PrecacheModel( HEALTH_MODEL )
+ PrecacheModel( HEALTH_MODEL_LARGE )
+ PrecacheModel( GRENADE_AMMO_MODEL )
+ PrecacheModel( LION_MODEL )
+ PrecacheModel( HELMET_COLLECTIBLE_MODEL )
+ PrecacheParticleSystem( COLLECTIBLE_PICKUP_EFFECT )
+ PrecacheParticleSystem( COLLECTIBLE_GLOW_EFFECT )
+
+ RegisterSignal( "NewHealthPickup" )
+ RegisterSignal( "CollectibleEndThink" )
+
+ SetNextHealthDropSmall()
+ SetNextHealthDropLarge()
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad_Pickups )
+}
+
+void function CreateScriptWeapon( entity point )
+{
+ CreateWeaponPickup( point )
+ array<entity> linkParents = point.GetLinkParentArray()
+ foreach ( entity linkParent in linkParents )
+ {
+ linkParent.UnlinkFromEnt( point )
+ }
+ //point.Destroy()
+}
+
+void function EntitiesDidLoad_Pickups()
+{
+ if ( shGlobal.proto_pilotHealthPickupsEnabled )
+ {
+ AddDeathCallback( "npc_soldier", OnNPCKilled_DropHealth )
+ AddDeathCallback( "npc_spectre", OnNPCKilled_DropHealth )
+ }
+
+ SetupCollectibles()
+}
+
+void function SetNextHealthDropSmall()
+{
+ if ( shGlobal.proto_pilotHealthRegenDisabled )
+ file.nextHealthDropSmall = RandomInt( 6 ) + 6
+ else
+ file.nextHealthDropSmall = RandomInt( 4 ) + 4
+
+ file.lastHealthDropTime = Time()
+}
+
+void function SetNextHealthDropLarge()
+{
+ file.nextHealthDropLarge = RandomIntRange( 3, 6 )
+}
+
+void function OnNPCKilled_DropHealth( entity npc, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) )
+ return
+
+ switch ( npc.GetClassName() )
+ {
+ case "npc_soldier":
+ case "npc_spectre":
+ case "npc_stalker":
+ break
+
+ default:
+ return
+ }
+
+ OnNPCKilled_DropHealth_Internal( npc, attacker, damageInfo )
+}
+
+void function OnNPCKilled_DropHealth_Internal( entity npc, entity attacker, var damageInfo )
+{
+ if ( npc.GetTeam() != TEAM_IMC )
+ return
+
+ file.nextHealthDropSmall--
+
+ if ( !DropHealthFromDeath( npc, attacker ) )
+ return
+
+ SetNextHealthDropSmall()
+
+ vector angles = npc.GetAngles()
+ angles.x = 0
+
+ vector forward = AnglesToForward( angles )
+
+ vector origin = npc.GetWorldSpaceCenter() + forward * 16
+
+ entity health
+ file.nextHealthDropLarge--
+ if ( file.nextHealthDropLarge < 0 && shGlobal.proto_pilotHealthRegenDisabled )
+ {
+ SetNextHealthDropLarge()
+ health = CreateHealthPickupSized( origin, angles, "health_pickup_large" )
+ }
+ else
+ {
+ health = CreateHealthPickupSized( origin, angles, "health_pickup_small" )
+ }
+
+ EmitSoundOnEntity( health, "Pilot_HealthPack_Drop" )
+ health.Fire( "Kill", "", 180 )
+}
+
+bool function DropHealthFromDeath( entity npc, entity attacker )
+{
+ if ( !attacker.IsPlayer() )
+ return file.nextHealthDropSmall < 0
+
+ if ( Time() - file.lastHealthDropTime < 3.0 )
+ return false
+
+ float healthRatio = float( attacker.GetHealth() ) / attacker.GetMaxHealth()
+
+ float chanceFromNextHealth = GraphCapped( file.nextHealthDropSmall, 0, 5, 0.4, 1.1 )
+ float chanceFromLowHealth = GraphCapped( healthRatio, 0.25, 0.80, 0.4, 1.1 )
+ float dropChance = chanceFromNextHealth * chanceFromLowHealth
+
+ return RandomFloat( 1.0 ) > dropChance
+
+ /*
+ if ( file.nextHealthDropSmall <= 1 )
+ {
+ float ratio = float( attacker.GetHealth() ) / attacker.GetMaxHealth() - 0.15
+ float random = RandomFloat( 1.0 )
+ if ( random > ratio )
+ return true
+ }
+
+ if ( Time() - file.lastHealthDropTime < 5.0 )
+ return false
+
+ if ( float( attacker.GetHealth() ) / attacker.GetMaxHealth() > 0.25 )
+ return false
+
+ return Distance( attacker.GetOrigin(), npc.GetOrigin() ) < 2000
+ */
+}
+
+void function CreateTitanPickup( entity ent )
+{
+ entity mover = CreatePickup( ent, LION_MODEL, DropTitanPickedUp )
+
+ mover.EndSignal( "OnDestroy" )
+ wait 0.2 // for some buggy reason!?
+ EmitSoundOnEntity( mover, "health_pickup_loopsound_far" )
+ EmitSoundOnEntity( mover, "health_pickup_loopsound_near" )
+ return
+}
+
+bool function DropTitanPickedUp( entity player )
+{
+ if ( player.IsTitan() )
+ return false
+
+ AddPlayerScore( player, "PilotHealthPickup" )
+ EmitSoundOnEntity( player, "titan_energyshield_up" )
+ player.SetNextTitanRespawnAvailable( 0 )
+
+ return true
+}
+
+function DisplayTempNameText( entity ent, string text )
+{
+ ent.EndSignal( "OnDestroy" )
+ for ( ;; )
+ {
+ array<entity> players = GetPlayerArray()
+ if ( players.len() )
+ {
+ entity nearestPlayer = GetClosest( players, ent.GetOrigin(), 2300 )
+ if ( nearestPlayer != null )
+ DebugDrawText( ent.GetWorldSpaceCenter(), text, true, 1 )
+ }
+ wait 0.9
+ }
+}
+
+entity function CreateHealthPickupSized( vector origin, vector angles, string healthType )
+{
+ HealthPickup pickup = file.healthPickups[ healthType ]
+ entity ent = CreatePropPhysics( pickup.model, origin, angles )
+ ent.NotSolid()
+
+
+ angles = AnglesCompose( angles, < -45,0,0> )
+ vector forward = AnglesToForward( angles )
+ ent.SetVelocity( forward * 200 )
+
+ ent.SetAngularVelocity( RandomFloatRange( 300, 500 ), RandomFloatRange( -100, 100 ), 0 )
+
+ thread HealthPickupWaitsForPickup( ent, pickup )
+ Highlight_SetNeutralHighlight( ent, "health_pickup" )
+ return ent
+}
+
+void function HealthPickup_OnSpawned( entity ent )
+{
+ //if ( shGlobal.proto_pilotHealthRegenDisabled )
+ {
+ CreateHealthPickupSized( ent.GetOrigin(), ent.GetAngles(), "health_pickup_small" )
+ ent.Destroy()
+ return
+ }
+
+}
+
+void function HealthPickupLarge_OnSpawned( entity ent )
+{
+ if ( shGlobal.proto_pilotHealthRegenDisabled )
+ {
+ CreateHealthPickupSized( ent.GetOrigin(), ent.GetAngles(), "health_pickup_small" )
+ ent.Destroy()
+ //HealthPickup_OnSpawned( ent )
+ return
+ }
+
+ CreateHealthPickupSized( ent.GetOrigin(), ent.GetAngles(), "health_pickup_large" )
+ ent.Destroy()
+}
+
+void function CreateHealthRegenField( entity ent )
+{
+ ent.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ )
+
+ PickupGlow pickupGlow = CreatePickupGlow( ent, 0, 255, 0 )
+
+ float healthRemainingMax = 1000
+ float healthRemainingCurrent = healthRemainingMax
+ float useIncrement = 9
+ float refillRate = 0
+ float nextRegenTime = 0
+
+ bool available
+ entity player
+ entity lastPlayer
+
+ for ( ;; )
+ {
+ WaitFrame()
+
+ float ratio = healthRemainingCurrent / healthRemainingMax
+ int green = int( Graph( ratio, 0, 1, 0, 255 ) )
+ PickupGlow_SetColor( pickupGlow, 0, green, 0 )
+
+ if ( Time() > nextRegenTime )
+ {
+ healthRemainingCurrent = min( healthRemainingCurrent + refillRate, healthRemainingMax )
+ nextRegenTime = Time() + 1.2
+ }
+
+ if ( healthRemainingCurrent < useIncrement )
+ continue
+
+ player = GetHealthPickupPlayer( ent )
+
+ if ( lastPlayer != player )
+ {
+ if ( IsValid( lastPlayer ) )
+ {
+ //EmitSoundOnEntity( lastPlayer, "Pilot_Stimpack_Deactivate" )
+ StopSoundOnEntity( lastPlayer, "Pilot_Stimpack_Loop" )
+ }
+
+ if ( player != null )
+ {
+ // new player powers up
+ EmitSoundOnEntity( player, "Pilot_Stimpack_Activate" )
+ EmitSoundOnEntity( player, "Pilot_Stimpack_Loop" )
+ }
+
+ lastPlayer = player
+ }
+
+ if ( player == null )
+ continue
+
+ // recent damage reduces healing effect, so you cant abuse it
+ float recentDamage = TotalDamageOverTime_BlendedOut( player, 0.5, 5.0 )
+
+ // damage is ramped down based on how much damage was taken recently
+ float damageMod = GraphCapped( recentDamage, 0, 40, 1.0, 0.35 )
+ float healthGain = useIncrement * damageMod
+
+ healthRemainingCurrent -= healthGain
+ int newHealth = int( min( player.GetHealth() + healthGain, player.GetMaxHealth() ) )
+ player.SetHealth( newHealth )
+
+ nextRegenTime = Time() + 10
+ }
+}
+
+entity function GetHealthPickupPlayer( entity ent )
+{
+ // try to heal the player
+ entity player = GetPickupPlayer( ent )
+ if ( player == null )
+ return null
+ if ( !IsPilot( player ) )
+ return null
+ if ( player.GetHealth() >= player.GetMaxHealth() )
+ return null
+
+ return player
+}
+
+void function CreateGrenadeAmmoPickup( entity ent )
+{
+ thread DisplayTempNameText( ent, "Ammo" )
+ CreatePickup( ent, GRENADE_AMMO_MODEL, GenericAmmoPickup )
+ CreatePickupGlow( ent, 13, 104, 255 )
+}
+
+entity function CreatePickup( entity ent, asset model, bool functionref( entity ) pickupFunc )
+{
+ entity mover = CreateEntity( "script_mover" )
+ mover.kv.solid = 0
+ mover.SetValueForModelKey( model )
+ mover.SetFadeDistance( 5000 )
+ mover.kv.SpawnAsPhysicsMover = 0
+ mover.SetOrigin( ent.GetOrigin() )
+ mover.SetAngles( ent.GetAngles() )
+ DispatchSpawn( mover )
+
+ ent.EndSignal( "OnDestroy" )
+
+ mover.SetOwner( ent )
+
+ ent.SetParent( mover )
+ thread PickupWaitsForPickup( ent, mover, pickupFunc )
+
+ return mover
+}
+
+void function PickupWaitsForPickup( entity ent, entity mover, bool functionref( entity ) pickupFunc )
+{
+ ent.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function() : ( mover, ent )
+ {
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ )
+
+// local colorVec = Vector(r,g,b)
+// local cpoint = CreateEntity( "info_placement_helper" )
+// SetTargetName( cpoint, UniqueString( "pickup_controlpoint" ) )
+// DispatchSpawn( cpoint )
+// cpoint.SetOrigin( colorVec )
+// local glowFX = PlayFXWithControlPoint( PICKUP_GLOW_FX, mover.GetOrigin(), cpoint, null, null, null, C_PLAYFX_LOOP )
+//
+// OnThreadEnd(
+// function() : ( ent, mover, glowFX, cpoint )
+// {
+// cpoint.Fire( "Kill", "", 1.0 )
+// if ( IsValid(glowFX) )
+// {
+// glowFX.Fire( "StopPlayEndCap" )
+// glowFX.Fire( "Kill", "", 1.0 )
+// }
+// mover.Destroy()
+// ent.Destroy()
+// }
+// )
+
+ for ( ;; )
+ {
+ entity player = WaitUntilPlayerPicksUp( ent )
+ if ( pickupFunc( player ) )
+ return
+ WaitFrame()
+ }
+}
+
+void function HealthPickupWaitsForPickup( entity ent, HealthPickup pickup )
+{
+ ent.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ )
+
+ for ( ;; )
+ {
+ WaitFrame()
+
+ entity player = WaitUntilPlayerPicksUp( ent )
+ if ( player.IsTitan() )
+ continue
+ if ( player.GetHealth() >= player.GetMaxHealth() )
+ continue
+
+ thread HealPlayerOverTime( player, pickup )
+ return
+ }
+}
+
+
+void function TitanLoadoutWaitsForPickup( entity ent, bool functionref( entity, entity ) pickupFunc )
+{
+ ent.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ )
+
+ for ( ;; )
+ {
+ entity player = WaitUntilPlayerPicksUp( ent )
+ if ( pickupFunc( player, ent ) )
+ return
+ WaitFrame()
+ }
+}
+
+
+entity function GetPickupPlayer( entity ent )
+{
+ array<entity> players = GetPlayerArray()
+
+ vector entOrigin = ent.GetCenter()
+
+ foreach ( player in players )
+ {
+ if ( !IsAlive( player ) )
+ continue
+
+ int pickupDist
+ if ( player.IsTitan() )
+ pickupDist = 256 * 256
+ else
+ pickupDist = 96 * 96
+
+ if ( GetEditorClass( ent ) == "script_collectible" )
+ pickupDist = 72 * 72
+
+ vector playerOrigin = player.GetOrigin()
+ if ( DistanceSqr( playerOrigin, entOrigin ) < pickupDist )
+ {
+ TraceResults trace
+ trace = TraceLine( entOrigin, playerOrigin, [ player, ent ], TRACE_MASK_SOLID, TRACE_COLLISION_GROUP_NONE )
+ if ( trace.fraction >= 0.99 || trace.hitEnt == ent )
+ return player
+ }
+ }
+
+ return null
+}
+
+entity function WaitUntilPlayerPicksUp( entity ent )
+{
+ while ( true )
+ {
+ entity player = GetPickupPlayer( ent )
+ if ( player != null )
+ return player
+ WaitFrame()
+ }
+
+ unreachable
+}
+
+function PickupHover( mover )
+{
+ mover.EndSignal( "OnDestroy" )
+
+ int direction = 1
+
+ while ( 1 )
+ {
+ mover.MoveTo( mover.GetOrigin() + Vector( 0, 0, 20*direction ), 1, 0.4, 0.4 )
+ mover.RotateTo( mover.GetAngles() + Vector( 0, 90, 0 ), 1, 0, 0 )
+ direction *= -1
+ wait 1
+ }
+}
+
+int function AddClipToMainWeapons( entity player )
+{
+ int gainedAmmo
+ foreach ( weapon in player.GetMainWeapons() )
+ {
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ }
+ return gainedAmmo
+}
+
+int function AddTwoClipToMainWeapons( entity player )
+{
+ int gainedAmmo
+ for ( int i = 0; i < 2; i++ )
+ {
+ foreach ( weapon in player.GetMainWeapons() )
+ {
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ }
+ }
+ return gainedAmmo
+}
+
+int function AddRoundToOrdnance( entity player )
+{
+ int gainedAmmo
+ entity ordnance = player.GetOffhandWeapon( OFFHAND_ORDNANCE )
+ if ( IsValid( ordnance ) )
+ gainedAmmo += AddRoundToWeapon( player, ordnance )
+ return gainedAmmo
+}
+
+int function AddRoundsToTactical( entity player, int count = 1 )
+{
+ int gainedAmmo
+ entity ordnance = player.GetOffhandWeapon( OFFHAND_SPECIAL )
+ if ( IsValid( ordnance ) )
+ gainedAmmo += AddRoundsToWeapon( player, ordnance, count )
+ return gainedAmmo
+}
+
+int function AddClipToWeapon( entity player, entity weapon )
+{
+ int ammoPerClip = weapon.GetWeaponPrimaryClipCountMax()
+ int gainedAmmo = 0
+
+ switch ( weapon.GetWeaponInfoFileKeyField( "fire_mode" ) )
+ {
+ case "offhand_hybrid":
+ case "offhand":
+ case "offhand_instant":
+
+ // offhand weapons typically cant store ammo, so refill the current clip
+ if ( ammoPerClip > 0 )
+ {
+ int primaryClipCount = weapon.GetWeaponPrimaryClipCount()
+ weapon.SetWeaponPrimaryClipCount( ammoPerClip )
+ gainedAmmo = weapon.GetWeaponPrimaryClipCount() - primaryClipCount
+ }
+ break
+
+ default:
+ int primaryAmmoCount = weapon.GetWeaponPrimaryAmmoCount()
+ // this weapon has off-clip ammo storage, so add ammo to storage
+ int stockpile = player.GetWeaponAmmoStockpile( weapon )
+ weapon.SetWeaponPrimaryAmmoCount( primaryAmmoCount + ammoPerClip )
+ gainedAmmo = player.GetWeaponAmmoStockpile( weapon ) - stockpile
+ break
+ }
+
+ return gainedAmmo
+}
+
+int function AddRoundToWeapon( entity player, entity weapon )
+{
+ return AddRoundsToWeapon( player, weapon, 1 )
+}
+
+int function AddRoundsToWeapon( entity player, entity weapon, int rounds )
+{
+ int ammoPerClip = weapon.GetWeaponPrimaryClipCountMax()
+ int gainedAmmo = 0
+
+ switch ( weapon.GetWeaponInfoFileKeyField( "fire_mode" ) )
+ {
+ case "offhand_hybrid":
+ case "offhand":
+ case "offhand_instant":
+
+ // offhand weapons typically cant store ammo, so refill the current clip
+ if ( ammoPerClip > 0 )
+ {
+ int primaryAmmoInClipCount = weapon.GetWeaponPrimaryClipCount()
+ int newAmmo = minint( ammoPerClip, primaryAmmoInClipCount + rounds )
+ if ( newAmmo > primaryAmmoInClipCount )
+ {
+ weapon.SetWeaponPrimaryClipCount( newAmmo )
+ gainedAmmo = weapon.GetWeaponPrimaryClipCount() - primaryAmmoInClipCount
+ }
+ }
+ break
+
+
+ default:
+ int primaryAmmoCount = weapon.GetWeaponPrimaryAmmoCount()
+ // this weapon has off-clip ammo storage, so add ammo to storage
+ int stockpile = player.GetWeaponAmmoStockpile( weapon )
+ weapon.SetWeaponPrimaryAmmoCount( primaryAmmoCount + rounds )
+ gainedAmmo = player.GetWeaponAmmoStockpile( weapon ) - stockpile
+ break
+ }
+
+ return gainedAmmo
+}
+
+bool function GenericAmmoPickup( entity player )
+{
+ if ( player.IsTitan() )
+ return false
+
+ Assert( player.IsPlayer() )
+
+ int gainedAmmo = 0
+ foreach ( weapon in player.GetMainWeapons() )
+ {
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ }
+ foreach ( weapon in player.GetOffhandWeapons() )
+ {
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ }
+
+ if ( gainedAmmo > 0 )
+ {
+ AddPlayerScore( player, "PilotAmmoPickup" )
+ EmitSoundOnEntity( player, "BurnCard_GrenadeRefill_Refill" )
+ EmitSoundOnEntity( player, "titan_energyshield_up" )
+ return true
+ }
+
+ return false
+}
+
+
+bool function GrenadeAmmoPickedUp( entity player )
+{
+ if ( player.IsTitan() )
+ return false
+
+ Assert( player.IsPlayer() )
+
+ entity weapon = player.GetOffhandWeapon( 0 )
+ if ( !IsValid( weapon ) )
+ return false
+
+ int ammo = weapon.GetWeaponPrimaryClipCount()
+ int newAmmo = minint( player.GetWeaponAmmoMaxLoaded( weapon ), ammo + 2 )
+ weapon.SetWeaponPrimaryClipCount( newAmmo )
+
+ bool pickup = newAmmo > ammo
+
+ if ( pickup )
+ {
+ AddPlayerScore( player, "PilotAmmoPickup" )
+ EmitSoundOnEntity( player, "BurnCard_GrenadeRefill_Refill" )
+ EmitSoundOnEntity( player, "titan_energyshield_up" )
+ return true
+ }
+
+ return false
+}
+
+void function HealPlayerOverTime( entity player, HealthPickup pickup )
+{
+ //StimPlayer( player, pickup.healTime * 2.0 )
+
+ Assert( IsNewThread(), "Must be threaded off" )
+ Assert( player.IsPlayer() )
+ //AddPlayerScore( player, "PilotHealthPickup" )
+ //EmitSoundOnEntity( player, "titan_energyshield_up" )
+
+ // cycle old effects
+ //player.Signal( "NewHealthPickup" )
+ //player.EndSignal( "NewHealthPickup" )
+
+ Assert( IsAlive( player ) )
+ player.EndSignal( "OnDeath" )
+
+ EmitSoundOnEntity( player, pickup.pickupSound )
+ EmitSoundOnEntity( player, pickup.healSound )
+
+
+ int frames = 10 * int( pickup.healTime )
+ float amount = player.GetMaxHealth() * pickup.healAmount
+ float healthPerFrame = amount / frames
+ float midTime = Time() + pickup.healTime * 0.5
+ float healthRemainder = 0
+
+ for ( int i = 0; i < frames; i++ )
+ {
+ WaitFrame()
+
+ float healthThisFrame
+ if ( Time() < midTime )
+ healthThisFrame = healthPerFrame * 0.6
+ else
+ healthThisFrame = healthPerFrame * 1.4
+
+ healthRemainder += healthThisFrame % 1
+ healthThisFrame -= healthThisFrame % 1
+ healthThisFrame += ( healthRemainder - healthRemainder % 1 )
+ healthRemainder %= 1
+// printt( "healththisframe is " + healthThisFrame + " healthRemainder is " + healthRemainder )
+
+ float newHealth = min( player.GetHealth() + healthThisFrame, player.GetMaxHealth() )
+ player.SetHealth( newHealth )
+ }
+
+ EmitSoundOnEntity( player, pickup.endSound )
+}
+
+LeveledScriptedWeapons function GetAllLeveledScriptWeapons()
+{
+ LeveledScriptedWeapons leveledScriptedWeapons
+
+ table<string, bool> tableAllWeapons
+
+ foreach ( weaponName in GetAllSPWeapons() )
+ {
+ tableAllWeapons[ weaponName ] <- true
+ }
+
+ foreach ( ent in GetEntArrayByClass_Expensive( "info_target" ) )
+ {
+ if ( !ent.HasKey( "editorclass" ) )
+ continue
+
+ string editorclass = expect string( ent.kv.editorclass )
+ if ( !( editorclass in tableAllWeapons ) )
+ continue
+
+ leveledScriptedWeapons.infoTargets.append( ent )
+ leveledScriptedWeapons.foundScriptWeapons[ editorclass ] <- true
+ }
+
+ // legacy support
+ foreach ( ent in GetEntArrayByClass_Expensive( "script_ref" ) )
+ {
+ if ( !ent.HasKey( "editorclass" ) )
+ continue
+ if ( ent.kv.editorclass != "script_pickup_weapon" )
+ continue
+
+ Assert( ent.HasKey( "script_weapon" ) )
+ string weapon = expect string( ent.kv.script_weapon )
+ if ( !( weapon in tableAllWeapons ) )
+ continue
+
+ //leveledScriptedWeapons.infoTargets.append( ent )
+ leveledScriptedWeapons.foundScriptWeapons[ weapon ] <- true
+ }
+
+ AddSpawnCallbackEditorClass( "script_ref", "script_pickup_weapon", CreateWeaponPickup )
+
+ return leveledScriptedWeapons
+}
+
+void function CreateWeaponPickup( entity ent )
+{
+ Assert( ent.HasKey( "script_weapon" ) )
+ string weaponClass = ent.GetValueForKey( "script_weapon" )
+
+ #if DEV
+ if ( !IsTestMap() )
+ {
+ if ( !WeaponIsPrecached( weaponClass ) )
+ {
+ CodeWarning( "Weapon " + weaponClass + " is not precached, re-export auto precache script" )
+ return
+ }
+
+ if ( !GetWeaponInfoFileKeyField_Global( weaponClass, "leveled_pickup" ) )
+ {
+ CodeWarning( "Tried to place illegal " + weaponClass + " in leveled at " + ent.GetOrigin() )
+ return
+ }
+ }
+
+ VerifyWeaponPickupModel( ent, weaponClass )
+
+ if ( GetWeaponInfoFileKeyField_Global( weaponClass, "offhand_default_inventory_slot" ) == OFFHAND_LEFT )
+ {
+ CodeWarning( "Illegal pickup " + weaponClass + " at " + ent.GetOrigin() )
+ return
+ }
+
+ #endif
+
+
+ bool doMarkAsLoadoutPickup = false
+ int loadoutIndex = GetSPTitanLoadoutIndexForWeapon( weaponClass )
+ if ( loadoutIndex >= 0 )
+ {
+ if ( IsBTLoadoutUnlocked( loadoutIndex ) )
+ return
+ else
+ doMarkAsLoadoutPickup = true
+ }
+
+ bool constrain = !ent.HasKey( "start_constrained" ) || ( ent.HasKey( "start_constrained" ) && ent.GetValueForKey( "start_constrained" ) == "1" )
+ entity weapon
+
+ if ( constrain ) // make all weapons constrained for now
+ {
+ weapon = CreateWeaponEntityByNameConstrained( weaponClass, ent.GetOrigin(), ent.GetAngles() )
+ }
+ else
+ {
+ weapon = CreateWeaponEntityByNameWithPhysics( weaponClass, ent.GetOrigin(), ent.GetAngles() )
+ weapon.SetVelocity( <0,0,0> )
+ }
+
+ SetTargetName( weapon, "leveled_" + weaponClass )
+ if ( ent.HasKey( "fadedist" ) )
+ {
+ weapon.kv.fadedist = ent.kv.fadedist
+ }
+ else
+ {
+ weapon.kv.fadedist = -1
+ }
+
+ ApplyWeaponModsFromEnt( ent, weapon )
+
+ if ( ent.HasKey( "script_name" ) )
+ {
+ weapon.kv.script_name = ent.kv.script_name
+ }
+
+ if ( doMarkAsLoadoutPickup )
+ {
+ weapon.MarkAsLoadoutPickup()
+ thread CreateTitanWeaponPickupHintTrigger( weapon )
+ thread TitanLoadoutWaitsForPickup( weapon, SPTitanLoadoutPickup )
+ }
+
+ HighlightWeapon( weapon )
+
+ // for s2s -mo
+ // for sp_training (pickups travel with moving gun racks) -sean
+ if ( ent.GetParent() )
+ {
+ weapon.SetParent( ent.GetParent(), "", true )
+ }
+
+ // for sp_training, to replenish the weapon when it's picked up -sean
+ ent.e.attachedEnts.append( weapon )
+}
+
+void function VerifyWeaponPickupModel( entity ent, string weaponClass )
+{
+ var playermodel
+ var playermodel1 = GetWeaponInfoFileKeyFieldAsset_Global( weaponClass, "droppedmodel" )
+ var playermodel2 = GetWeaponInfoFileKeyFieldAsset_Global( weaponClass, "playermodel" )
+ if ( playermodel1 != $"" )
+ playermodel = playermodel1
+ else
+ playermodel = playermodel2
+ playermodel = playermodel.tolower()
+ expect asset( playermodel )
+
+ asset modelName = ent.GetModelName().tolower()
+ if ( modelName != $"" && playermodel != modelName )
+ CodeWarning( "Incorrect Model on weapon " + weaponClass + " at " + ent.GetOrigin() + ", replace with real weapon for auto fix. ( " + modelName + " != " + playermodel + ")" )
+}
+
+void function ApplyWeaponModsFromEnt( entity ent, entity weapon )
+{
+ if ( ent.HasKey( "script_mods" ) )
+ {
+ array<string> mods = split( ent.GetValueForKey( "script_mods" ), " " )
+ if ( mods.len() > 0 )
+ {
+ weapon.SetMods( mods )
+ return
+ }
+ }
+
+ array<string> mods = GetWeaponModsForCurrentLevel( weapon.GetWeaponClassName() )
+ if ( mods.len() )
+ {
+ weapon.SetMods( [ mods.getrandom() ] )
+ }
+}
+
+
+void function AddCollectible( entity ent )
+{
+ Assert( ent.GetClassName() == "script_mover_lightweight" )
+
+ if ( ent.GetModelName() != HELMET_COLLECTIBLE_MODEL )
+ ent.SetModel( HELMET_COLLECTIBLE_MODEL )
+
+ ent.DisableFastPathRendering() // Workaround for glow effect not drawing (bug #177177)
+
+ Collectible collectible
+ collectible.ent = ent
+ collectible.pos = ent.GetOrigin()
+ file.collectibles.append( collectible )
+
+ // Drop to ground
+ if ( !collectible.ent.HasKey( "hover" ) || collectible.ent.kv.hover == "0" )
+ {
+ vector groundPos = OriginToGround( collectible.ent.GetOrigin() + <0,0,1> )
+ collectible.ent.SetOrigin( groundPos + < 0, 0, 32 > )
+ collectible.pos = collectible.ent.GetOrigin()
+ }
+
+ // Effect and not solid
+ collectible.ent.DisableHibernation()
+ collectible.ent.NotSolid()
+ collectible.ent.EnableRenderAlways()
+ collectible.ent.kv.fadedist = 100000
+}
+
+void function SetupCollectibles()
+{
+ // Make sure that the number of collectibles in the level matches the hardcoded value in sh_consts so that SP menus know total number per level.
+ string mapName = GetMapName()
+ int saveIndex = GetCollectibleLevelIndex( mapName )
+ Assert( saveIndex < 0 || file.collectibles.len() == GetMaxLionsInLevel( mapName ), "Collectibles count mismatch. Update LEVEL_UNLOCKS_COUNT in sh_consts.gnut to " + file.collectibles.len() )
+
+ // Index the collectibles so each is unique and it's status can be stored in a cvar. They are sorted by distance from map center to keep consistent on each map load
+ file.collectibles.sort( SortCollectiblesFunc )
+
+ foreach ( int i, Collectible collectible in file.collectibles )
+ {
+ collectible.id = 1 << i
+ thread CollectibleThink( collectible )
+ }
+}
+
+int function SortCollectiblesFunc( Collectible a, Collectible b )
+{
+ float distA = DistanceSqr( a.ent.GetOrigin(), <0,0,0> )
+ float distB = DistanceSqr( b.ent.GetOrigin(), <0,0,0> )
+ if ( distA > distB )
+ return 1
+ else if ( distA < distB )
+ return -1
+ return 0
+}
+
+void function UpdateCollectiblesAfterLoadSaveGame()
+{
+ // This has to run when a save game is loaded because the collectible may be there in the save game, but it was picked up after the save, so we need to delete the ones the player picked up
+ foreach ( Collectible collectible in file.collectibles )
+ {
+ if ( HasCollectible( collectible ) )
+ {
+ //DebugDrawSphere( collectible.pos, 40.0, 255, 0, 0, true, 600.0 )
+ if ( IsValid( collectible.ent ) )
+ collectible.ent.Destroy()
+ Signal( collectible, "CollectibleEndThink" )
+ }
+ }
+
+ // Delete all the weapons already unlocked in the level
+ array<string> unlockedLoadouts = GetSPTitanLoadoutsUnlocked()
+ SPTitanLoadout_RemoveOwnedLoadoutPickupsInLevel( unlockedLoadouts )
+}
+
+#if DEV
+ void function Dev_ResetCollectiblesProgress_Level()
+ {
+ printt( "RESETTING COLLECTIBLE PROGRESS (LEVEL)" )
+ string mapName = GetMapName()
+ int saveIndex = GetCollectibleLevelIndex( mapName )
+ if ( saveIndex == -1 )
+ return
+ printt( " sp_unlocks_level_" + saveIndex, 0 )
+ SetConVarInt( "sp_unlocks_level_" + saveIndex, 0 )
+ }
+#endif
+
+void function CollectibleThink( Collectible collectible )
+{
+ EndSignal( collectible, "CollectibleEndThink" )
+
+ if ( HasCollectible( collectible ) )
+ {
+ //printt( "Player already has collectible", collectible.id )
+ //DebugDrawSphere( collectible.ent.GetOrigin(), 25.0, 150, 0, 0, true, 600.0 )
+ collectible.ent.Destroy()
+ return
+ }
+
+ entity glowEffect = StartParticleEffectOnEntity_ReturnEntity( collectible.ent, GetParticleSystemIndex( COLLECTIBLE_GLOW_EFFECT ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 )
+
+ // Rotate the collectible
+ collectible.ent.NonPhysicsRotate( < 0, 0, 1 >, 35.0 )
+
+ WaitFrame() // emit sound doesn't work on first frame so we have to wait a frame so sound will play. Player can't pickup collectible in frame 1 anyways
+
+ EmitSoundOnEntity( collectible.ent, "Emit_PilotHelmet_Collectible" )
+
+ //wait 1.0
+ //DebugDrawText( collectible.ent.GetOrigin(), string(collectible.id), true, 600.0 )
+ //DebugDrawSphere( collectible.ent.GetOrigin(), 25.0, 255, 0, 0, true, 600.0 )
+
+ // Wait until it's touched by a player
+ entity player = WaitUntilPlayerPicksUp( collectible.ent )
+
+ // Remove collectible
+ EmitSoundOnEntity( player, "Pilot_Collectible_Pickup" )
+ if ( IsValid( glowEffect ) )
+ EffectStop( glowEffect )
+ collectible.ent.Destroy()
+
+ // Save to player profile
+ string mapName = GetMapName()
+ int saveIndex = GetCollectibleLevelIndex( mapName )
+ int bitMask
+ if ( saveIndex >= 0 )
+ {
+ // If it's a real map we store it to player profile
+ string unlockVar = "sp_unlocks_level_" + saveIndex
+ bitMask = GetConVarInt( unlockVar )
+ bitMask = bitMask | collectible.id
+ //printt( "Saving collectible state", unlockVar, bitMask )
+ SetConVarInt( unlockVar, bitMask )
+ }
+ else
+ {
+ // Not a real map, we store it to a file var that wont persist, just so we can pick them up and have kind of working collectibles in test maps
+ CodeWarning( "Collectible state not being saved because this map is not shipping" )
+ file.testMapCollectibleValue = file.testMapCollectibleValue | collectible.id
+ bitMask = file.testMapCollectibleValue
+ }
+
+ // See how many collectibles are found now to pass to the RUI
+ int numCollectiblesFound = GetCollectiblesFoundForLevel( mapName )
+ int maxCollectibles = GetMaxLionsInLevel( mapName )
+
+ // Show message on HUD
+ Remote_CallFunction_NonReplay( player, "ServerCallback_CollectibleFoundMessage", numCollectiblesFound, maxCollectibles )
+
+ CollectiblePickupRumble( player )
+
+ UpdateHeroStatsForPlayer( player )
+
+ int totalLionsCollectedForGame = GetTotalLionsCollected()
+
+ if ( totalLionsCollectedForGame >= GetTotalLionsInGame() )
+ UnlockAchievement( player, achievements.COLLECTIBLES_3 )
+
+ if ( totalLionsCollectedForGame >= ACHIEVEMENT_COLLECTIBLES_2_COUNT )
+ UnlockAchievement( player, achievements.COLLECTIBLES_2 )
+
+ if ( totalLionsCollectedForGame >= ACHIEVEMENT_COLLECTIBLES_1_COUNT )
+ UnlockAchievement( player, achievements.COLLECTIBLES_1 )
+}
+
+void function CollectiblePickupRumble( entity player )
+{
+ float rumbleAmplitude = 200.0
+ float rumbleFrequency = 90.0
+ float rumbleDuration = 2.2
+
+ CreateAirShakeRumbleOnly( player.GetOrigin(), rumbleAmplitude, rumbleFrequency, rumbleDuration )
+}
+
+bool function HasCollectible( Collectible collectible )
+{
+ string mapName = GetMapName()
+ int saveIndex = GetCollectibleLevelIndex( mapName )
+
+ // Not a shipping map, so there is no saved var for this level. Just always make it available
+ if ( saveIndex == -1 )
+ return false
+
+ string unlockVar = "sp_unlocks_level_" + saveIndex
+ int bitMask = GetConVarInt( unlockVar )
+
+ return bool(bitMask & collectible.id)
+}
+
+
+
+
+
+
+
+
+
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_pickups_glow.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_pickups_glow.gnut
new file mode 100644
index 000000000..f1fe4ecce
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_pickups_glow.gnut
@@ -0,0 +1,53 @@
+global function CreatePickupGlow
+global function PickupGlow_SetColor
+
+global struct PickupGlow
+{
+ entity cpoint
+ entity glowFX
+}
+
+PickupGlow function CreatePickupGlow( entity ent, int r, int g, int b )
+{
+ vector origin = ent.GetOrigin()
+ entity cpoint = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpoint, UniqueString( "pickup_controlpoint" ) )
+ DispatchSpawn( cpoint )
+ cpoint.SetOrigin( Vector(r,g,b) )
+ entity glowFX = PlayFXWithControlPoint( COLLECTIBLE_PICKUP_EFFECT, origin, cpoint, -1, null, null, C_PLAYFX_LOOP )
+
+ PickupGlow pickupGlow
+ pickupGlow.cpoint = cpoint
+ pickupGlow.glowFX = glowFX
+ thread PickupGlowCleanup( ent, pickupGlow )
+ return pickupGlow
+}
+
+void function PickupGlowCleanup( entity ent, PickupGlow pickupGlow )
+{
+ OnThreadEnd(
+ function() : ( pickupGlow )
+ {
+ StopPickupGlow( pickupGlow )
+ }
+ )
+
+ ent.WaitSignal( "OnDestroy" )
+}
+
+void function StopPickupGlow( PickupGlow pickupGlow )
+{
+ if ( IsValid( pickupGlow.cpoint ) )
+ pickupGlow.cpoint.Destroy()
+
+ if ( IsValid(pickupGlow.glowFX) )
+ {
+ EntityFire( pickupGlow.glowFX, "StopPlayEndCap" )
+ pickupGlow.glowFX.Destroy()
+ }
+}
+
+void function PickupGlow_SetColor( PickupGlow pickupGlow, int r, int g, int b )
+{
+ pickupGlow.cpoint.SetOrigin( Vector( r, g, b ) )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_playlist.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_playlist.gnut
new file mode 100644
index 000000000..dfceab412
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_playlist.gnut
@@ -0,0 +1,6 @@
+global function Playlist_Init
+
+void function Playlist_Init()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut
new file mode 100644
index 000000000..b2f5c4674
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut
@@ -0,0 +1,352 @@
+untyped
+
+
+global function Revive_Init
+
+global function PlayerRevivesOrBleedsOut
+global function DeathPackage_PlayerRevive
+global function ShouldRevivePlayer
+
+const float REVIVE_BLEED_OUT_TIME = 15.0
+global const float REVIVE_DEATH_TIME = 2.0
+const float REVIVE_DIST_OUTER = 135.0
+const float REVIVE_DIST_INNER = 120.0
+
+struct
+{
+ table fakePlayers
+} file
+
+function Revive_Init()
+{
+ if ( !ReviveEnabled() )
+ return
+
+ RegisterSignal( "KillReviveNag" )
+ RegisterSignal( "DoneBleedingOut" )
+ RegisterSignal( "ReviveSucceeded" )
+ RegisterSignal( "ReviveFailed" )
+ RegisterSignal( "ForceBleedOut" )
+
+ AddCallback_OnClientDisconnected( ReviveOnClientDisconnect )
+}
+
+void function PlayerRevivesOrBleedsOut( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "ForceBleedOut" )
+ svGlobal.levelEnt.EndSignal( "RoundEnd" )
+
+ table e = { revived = false }
+ //thread PlayerReviveVONag( player, 0.5 )
+
+ OnThreadEnd(
+ function() : ( player, e )
+ {
+ if ( !IsValid( player ) )
+ return
+
+ player.Signal( "KillReviveNag" )
+ player.Signal( "DoneBleedingOut" )
+ player.nv.reviveBleedingOut = -1.0 //-1 means off
+
+ if ( e.revived )
+ {
+ player.Signal( "ReviveSucceeded")
+ thread PlayerStandsBackUp( player )
+ }
+ else
+ {
+ file.fakePlayers[ player ].Destroy()
+ player.Signal( "ReviveFailed" )
+ DecideRespawnPlayer( player )
+ }
+ }
+ )
+
+ wait( REVIVE_DEATH_TIME )
+ player.StartObserverMode( OBS_MODE_DEATHCAM )
+
+ ForceRespawnIfEntireTeamIsDead( player )
+
+ float endTime = Time() + REVIVE_BLEED_OUT_TIME
+ player.nv.reviveBleedingOut = endTime
+
+ bool reviving = false
+ float doneReviveTime = Time() + 100
+
+ float distOuterSqr = pow( REVIVE_DIST_OUTER, 2 )
+ float distInnerSqr = pow( REVIVE_DIST_INNER, 2 )
+
+ while ( true )
+ {
+ array<entity> healers = Revive_GetAvailablePlayerHealers( player )
+
+ //we were reviving but aren't anymore - set revive to false.
+ if ( reviving && !FriendlyIsReviving( healers, player, distOuterSqr ) )
+ {
+ //thread PlayerReviveVONag( player )
+ reviving = false
+ player.nv.reviveHealedTime = -1.0 //-1 means off
+ }
+ //we were not reviving but now we are? set the new revive done time.
+ else if ( !reviving && FriendlyIsReviving( healers, player, distInnerSqr ) )
+ {
+ player.Signal( "KillReviveNag" )
+ doneReviveTime = Time() + REVIVE_TIME_TO_REVIVE
+ player.nv.reviveHealedTime = doneReviveTime
+ reviving = true
+ }
+
+ //are we done reviving? then set the value and return
+ if ( reviving && Time() > doneReviveTime )
+ {
+ e.revived = true
+ return
+ }
+
+ //we didn't make it
+ if ( !reviving && Time() > endTime )
+ return
+
+ wait 0.2
+ }
+}
+
+void function ForceRespawnIfEntireTeamIsDead( entity player )
+{
+ int playerTeam = player.GetTeam()
+ array<entity> victimTeamMembers = GetPlayerArrayOfTeam( playerTeam )
+ foreach ( member in victimTeamMembers )
+ {
+ if ( member.p.isReviving || IsAlive( member ) )
+ return
+ }
+ foreach ( member in victimTeamMembers )
+ {
+ if ( player != member && member.p.isReviving == false )
+ member.Signal( "ForceBleedOut" )
+ }
+ MessageToTeam( GetOtherTeam( playerTeam ), eEventNotifications.EnemyTeamEliminated )
+ player.Signal( "ForceBleedOut" )
+}
+
+void function PlayerReviveVONag( entity player, float delay = 0.5 )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "KillReviveNag" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ StopSoundOnEntity( player, "diag_coop_bleedout_help" )
+ }
+ )
+
+ if ( delay > 0 )
+ wait delay
+
+ while ( true )
+ {
+ float time = EmitSoundOnEntity( player, "diag_coop_bleedout_help" )
+ wait time
+
+ wait RandomFloatRange( 10, 15 )
+ }
+}
+
+bool function FriendlyIsReviving( array<entity> healers, entity player, float distSqr )
+{
+ vector origin = player.GetOrigin()
+
+ foreach ( friend in healers )
+ {
+ if ( !IsAlive( friend ) )
+ continue
+
+ if ( DistanceSqr( friend.GetOrigin(), origin ) < distSqr )
+ return true
+ }
+
+ return false
+}
+
+array<entity> function Revive_GetAvailablePlayerHealers( entity player )
+{
+ int team = player.GetTeam()
+ array<entity> players = GetPlayerArrayOfTeam( team )
+ array<entity> playersCanRevive = []
+ foreach ( player in players )
+ {
+ if ( !IsAlive( player ) )
+ continue
+
+ playersCanRevive.append( player )
+ }
+
+ return playersCanRevive
+}
+
+bool function ShouldRevivePlayer( entity player, var damageInfo )
+{
+ if ( !ReviveEnabled() )
+ return false
+
+ if ( !GamePlaying() )
+ return false
+
+ if ( player.ContextAction_IsMeleeExecution() )
+ return false
+
+ if ( player.IsTitan() )
+ return false
+
+ int source = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ if ( source == eDamageSourceId.fall ||
+ source == eDamageSourceId.submerged ||
+ source == eDamageSourceId.outOfBounds ||
+ source == eDamageSourceId.indoor_inferno )
+ return false
+
+ return true
+}
+
+entity function SpawnFakePlayer( entity player, int team, vector origin, vector angles, asset weaponModel, asset model )
+{
+ float fadeDist = 10000.0
+ int solidType = 0// 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only
+
+ entity fakePlayer = CreatePropDynamic( model, origin, angles, solidType, fadeDist )
+ if ( !( player in file.fakePlayers ) )
+ {
+ file.fakePlayers[ player ] <- null
+ }
+ file.fakePlayers[ player ] = fakePlayer
+
+ thread FakePlayerTrack( fakePlayer, player )
+
+ if ( weaponModel != $"" )
+ {
+ entity gun = CreatePropDynamic( weaponModel, origin, angles, 0, fadeDist )
+ gun.SetParent( fakePlayer, "PROPGUN" )
+ }
+
+ return fakePlayer
+}
+
+void function FakePlayerTrack( entity fakePlayer, entity player )
+{
+ fakePlayer.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDestroy" )
+ vector lastPlayerOrg = Vector( 0.0, 0.0, 0.0 )
+
+ while ( true )
+ {
+ if ( player.GetOrigin() == lastPlayerOrg )
+ player.SetVelocity( Vector( 0.0, 0.0, 0.0 ) )
+ lastPlayerOrg = player.GetOrigin()
+
+ fakePlayer.SetOrigin( player.GetOrigin() )
+ WaitFrame()
+ }
+}
+
+void function DeathPackage_PlayerRevive( entity player )
+{
+ player.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY
+
+ vector deathOrg = player.GetOrigin()
+
+ vector mins = Vector( -16.0, -16.0, 0.0 )
+ vector maxs = Vector( 16.0, 16.0, 72.0 )
+ TraceResults result = TraceHull( deathOrg + Vector( 0.0, 0.0, 8.0 ), deathOrg + Vector( 0.0, 0.0, -16000.0 ), mins, maxs, player, ( TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS ), TRACE_COLLISION_GROUP_NONE )
+
+ player.SetVelocity( Vector( 0.0, 0.0, 0.0 ) )
+ thread ReviveLerpToOrigin( player, deathOrg, result.endPos )
+
+ entity activeWeapon = player.GetActiveWeapon()
+
+ asset weaponModel = activeWeapon == null ? $"" : activeWeapon.GetModelName()
+
+ entity fakePlayer = SpawnFakePlayer( player, player.GetTeam(), deathOrg, player.GetAngles(), weaponModel, player.GetModelName() )
+ fakePlayer.Anim_Play( "pt_wounded_drag_zinger_A_idle" )
+ player.Anim_Play( "pt_wounded_drag_zinger_A_idle" )
+}
+
+void function ReviveLerpToOrigin( entity player, vector deathOrg, vector endPos )
+{
+ player.EndSignal( "DoneBleedingOut" )
+ player.EndSignal( "OnDestroy" )
+
+ entity mover = CreateScriptMover()
+
+ OnThreadEnd(
+ function() : ( player, mover )
+ {
+ if ( IsValid( player ) )
+ player.ClearParent()
+
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ }
+ )
+
+ mover.SetOrigin( deathOrg )
+ player.SetOrigin( deathOrg )
+ player.SetParent( mover )
+
+ float moveTime = GraphCapped( deathOrg.z - endPos.z, 0.0, 768.0, 0.1, 2.0 )
+
+ mover.NonPhysicsMoveTo( endPos, moveTime, moveTime, 0.0 )
+ wait( moveTime )
+ player.ClearParent()
+
+ while ( true )
+ {
+ player.SetOrigin( endPos )
+ WaitFrame()
+ }
+}
+
+void function PlayerStandsBackUp( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ svGlobal.levelEnt.EndSignal( "RoundEnd" )
+
+ entity fakePlayer = expect entity( file.fakePlayers[ player ] )
+ file.fakePlayers[ player ] = null
+
+ player.p.isReviving = true
+
+ OnThreadEnd(
+ function() : ( player, fakePlayer )
+ {
+ if ( IsValid( fakePlayer ) )
+ fakePlayer.Destroy()
+
+ if ( IsValid( player ) )
+ player.p.isReviving = false
+ }
+ )
+
+ fakePlayer.Anim_Play( "CQB_knockback_pain_react" )
+ fakePlayer.Anim_SetInitialTime( 2.0 )
+ wait( 1.5 )
+
+ var settings = player.GetPlayerSettings()
+ player.SetPlayerSettings( "spectator" )
+ player.SetPlayerSettings( settings )
+ player.RespawnPlayer( null )
+}
+
+void function ReviveOnClientDisconnect( entity player )
+{
+ if ( player in file.fakePlayers )
+ {
+ if ( IsValid( file.fakePlayers[ player ] ) )
+ file.fakePlayers[ player ].Destroy()
+ delete file.fakePlayers[ player ]
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_score.nut b/Northstar.CustomServers/scripts/vscripts/mp/_score.nut
new file mode 100644
index 000000000..b8ea6074b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_score.nut
@@ -0,0 +1,133 @@
+untyped
+
+global function Score_Init
+
+global function AddPlayerScore
+global function ScoreEvent_PlayerKilled
+global function ScoreEvent_TitanDoomed
+global function ScoreEvent_TitanKilled
+global function ScoreEvent_NPCKilled
+
+global function ScoreEvent_SetEarnMeterValues
+global function ScoreEvent_SetupEarnMeterValuesForMixedModes
+
+struct {
+ bool firstStrikeDone = false
+} file
+
+void function Score_Init()
+{
+
+}
+
+void function AddPlayerScore( entity targetPlayer, string scoreEventName, entity associatedEnt = null, string noideawhatthisis = "", int pointValueOverride = -1 )
+{
+ ScoreEvent event = GetScoreEvent( scoreEventName )
+
+ if ( !event.enabled )
+ return
+
+ var associatedHandle = 0
+ if ( associatedEnt != null )
+ associatedHandle = associatedEnt.GetEncodedEHandle()
+
+ if ( pointValueOverride != -1 )
+ event.pointValue = pointValueOverride
+
+ float scale = targetPlayer.IsTitan() ? event.coreMeterScalar : 1.0
+ float earnValue = event.earnMeterEarnValue * scale
+ float ownValue = event.earnMeterOwnValue * scale
+
+ PlayerEarnMeter_AddEarnedAndOwned( targetPlayer, earnValue * scale, ownValue * scale )
+
+ Remote_CallFunction_NonReplay( targetPlayer, "ServerCallback_ScoreEvent", event.eventId, event.pointValue, event.displayType, associatedHandle, ownValue, earnValue )
+
+ if ( event.displayType & eEventDisplayType.CALLINGCARD ) // callingcardevents are shown to all players
+ {
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( player == targetPlayer ) // targetplayer already gets this in the scorevent callback
+ continue
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_CallingCardEvent", event.eventId, associatedHandle )
+ }
+ }
+
+ if ( ScoreEvent_HasConversation( event ) )
+ thread Delayed_PlayConversationToPlayer( event.conversation, targetPlayer, event.conversationDelay )
+}
+
+void function ScoreEvent_PlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ AddPlayerScore( attacker, "KillPilot", victim )
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_HEADSHOT )
+ AddPlayerScore( attacker, "Headshot", victim )
+
+ if ( !file.firstStrikeDone )
+ {
+ file.firstStrikeDone = true
+ AddPlayerScore( attacker, "FirstStrike", attacker )
+ }
+
+ if ( victim.IsTitan() )
+ ScoreEvent_TitanKilled( victim, attacker, damageInfo )
+}
+
+void function ScoreEvent_TitanDoomed( entity titan, entity attacker, var damageInfo )
+{
+ // will this handle npc titans with no owners well? i have literally no idea
+
+ if ( titan.IsNPC() )
+ AddPlayerScore( attacker, "DoomAutoTitan", titan )
+ else
+ AddPlayerScore( attacker, "DoomTitan", titan )
+}
+
+void function ScoreEvent_TitanKilled( entity victim, entity attacker, var damageInfo )
+{
+ // will this handle npc titans with no owners well? i have literally no idea
+
+ if ( attacker.IsTitan() )
+ AddPlayerScore( attacker, "TitanKillTitan", victim.GetTitanSoul().GetOwner() )
+ else
+ AddPlayerScore( attacker, "KillTitan", victim.GetTitanSoul().GetOwner() )
+}
+
+void function ScoreEvent_NPCKilled( entity victim, entity attacker, var damageInfo )
+{
+ AddPlayerScore( attacker, ScoreEventForNPCKilled( victim, damageInfo ), victim )
+}
+
+
+
+void function ScoreEvent_SetEarnMeterValues( string eventName, float earned, float owned, float coreScale = 1.0 )
+{
+ ScoreEvent event = GetScoreEvent( eventName )
+ event.earnMeterEarnValue = earned
+ event.earnMeterOwnValue = owned
+ event.coreMeterScalar = coreScale
+}
+
+void function ScoreEvent_SetupEarnMeterValuesForMixedModes() // mixed modes in this case means modes with both pilots and titans
+{
+ // todo needs earn/overdrive values
+ // player-controlled stuff
+ ScoreEvent_SetEarnMeterValues( "KillPilot", 0.0, 0.05 )
+ ScoreEvent_SetEarnMeterValues( "KillTitan", 0.0, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "TitanKillTitan", 0.0, 0.0 ) // unsure
+ ScoreEvent_SetEarnMeterValues( "PilotBatteryStolen", 0.0, 0.35 )
+
+ // ai
+ ScoreEvent_SetEarnMeterValues( "KillGrunt", 0.0, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "KillSpectre", 0.0, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "LeechSpectre", 0.0, 0.02 )
+ ScoreEvent_SetEarnMeterValues( "KillStalker", 0.0, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "KillSuperSpectre", 0.0, 0.1, 0.5 )
+}
+
+void function ScoreEvent_SetupEarnMeterValuesForTitanModes()
+{
+ // todo needs earn/overdrive values
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_serverflags.nut b/Northstar.CustomServers/scripts/vscripts/mp/_serverflags.nut
new file mode 100644
index 000000000..a665463da
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_serverflags.nut
@@ -0,0 +1,35 @@
+untyped
+
+globalize_all_functions
+
+function GiveServerFlag( player, passive )
+{
+ if ( !( player.serverFlags & passive ) )
+ {
+ player.serverFlags = player.serverFlags | passive
+ }
+
+ // enter/exit functions for specific passives
+ switch ( passive )
+ {
+ }
+}
+
+function TakeServerFlag( player, passive )
+{
+ if ( !PlayerHasServerFlag( player, passive ) )
+ return
+
+ player.serverFlags = player.serverFlags & ( ~passive )
+
+ // enter/exit functions for specific passives
+ switch ( passive )
+ {
+ }
+
+}
+
+bool function PlayerHasServerFlag( player, passive )
+{
+ return bool( player.serverFlags & passive )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_sniper_spectres.nut b/Northstar.CustomServers/scripts/vscripts/mp/_sniper_spectres.nut
new file mode 100644
index 000000000..ce513259b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_sniper_spectres.nut
@@ -0,0 +1,485 @@
+untyped
+
+const MAXNODES_PER_SNIPERSPOT = 4
+const MAX_SNIPERSPOTS = 30 // for speed of iterating through the array
+const SNIPERSPOT_RADIUSCHECK = 200
+const SNIPERSPOT_HEIGHTCHECK = 160
+const SNIPERNODE_TOOCLOSE_SQR = 2500//50x50
+
+global function SniperSpectres_Init
+global function TowerDefense_AddSniperLocation
+global function Dev_AddSniperLocation
+global function DebugDrawSniperLocations
+global function Sniper_MoveToNewLocation
+global function Sniper_FreeSniperNodeOnDeath
+global function SniperCloak
+global function SniperDeCloak
+
+
+function SniperSpectres_Init()
+{
+ FlagInit( "TD_SniperLocationsInit" )
+
+ level.TowerDefense_SniperNodes <- []
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+}
+
+void function EntitiesDidLoad()
+{
+ thread SniperLocationsInit()
+}
+
+/************************************************************************************************\
+
+######## ####### ####### ## ######
+ ## ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ######
+ ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ## ##
+ ## ####### ####### ######## ######
+
+\************************************************************************************************/
+function TowerDefense_AddSniperLocation( origin, yaw, heightCheck = SNIPERSPOT_HEIGHTCHECK )
+{
+ Assert( !Flag( "TD_SniperLocationsInit" ), "sniper locations added too late" )
+ Assert( ( level.TowerDefense_SniperNodes.len() < MAX_SNIPERSPOTS ), "adding too many snper locations, max is " + MAX_SNIPERSPOTS )
+
+ local loc = CreateSniperLocation( origin, yaw, heightCheck )
+
+ level.TowerDefense_SniperNodes.append( loc )
+}
+
+function Dev_AddSniperLocation( origin, yaw, heightCheck = SNIPERSPOT_HEIGHTCHECK )
+{
+ thread __AddSniperLocationInternal( origin, yaw, heightCheck )
+}
+
+function __AddSniperLocationInternal( origin, yaw, heightCheck )
+{
+ local loc = CreateSniperLocation( origin, yaw, heightCheck )
+ SniperLocationSetup( loc )
+ DebugDrawSingleSniperLocation( loc, 4.0 )
+}
+
+function DebugDrawSniperLocations()
+{
+ foreach ( loc in level.TowerDefense_SniperNodes )
+ DebugDrawSingleSniperLocation( loc, 600.0 )
+}
+
+function DebugDrawSingleSniperLocation( loc, float time )
+{
+ if ( !loc.maxGuys )
+ {
+ DebugDrawSniperSpot( expect vector( loc.pos ), [ 32.0, 40.0, 48.0 ], 255, 0, 0, time, loc.yaw )
+ return
+ }
+
+ DebugDrawSniperSpot( expect vector( loc.pos ), [ 28.0 ], 20, 20, 20, time, loc.yaw )
+
+ foreach ( node in loc.coverNodes )
+ DebugDrawSniperSpot( expect vector( node.pos ), [ 16.0, 24.0, 32.0 ], 50, 50, 255, time, null, loc.pos )
+
+ foreach ( node in loc.extraNodes )
+ DebugDrawSniperSpot( expect vector( node.pos ), [ 14.0, 22.0, 30.0 ], 255, 0, 255, time, null, loc.pos )
+}
+
+function DebugDrawSniperSpot( vector pos, array<float> radii, int r, int g, int b, float time, yaw = null, pos2 = null )
+{
+ foreach ( radius in radii )
+ DebugDrawCircle( pos, Vector( 0, 0, 0 ), radius, r, g, b, true, time )
+
+ if ( yaw != null )
+ {
+ local angles = Vector( 0, yaw, 0 )
+ local forward = AnglesToForward( angles )
+ local right = AnglesToRight( angles )
+ local length = radii[ radii.len() - 1 ]
+ local endPos = pos + ( forward * ( length * 1.75 ) )
+ local rightPos = pos + ( right * length )
+ local leftPos = pos + ( right * -length )
+ DebugDrawLine( pos, endPos, r, g, b, true, time )
+ DebugDrawLine( rightPos, endPos, r, g, b, true, time )
+ DebugDrawLine( leftPos, endPos, r, g, b, true, time )
+
+ local ring = GetDesirableRing( pos, yaw )
+ DebugDrawCircle( expect vector( ring.pos ), Vector( 0, 0, 0 ), expect float( ring.radius ), r, g, b, true, time )
+ }
+
+ if ( pos2 != null )
+ DebugDrawLine( pos, pos2, r, g, b, true, time )
+}
+
+/************************************************************************************************\
+
+######## ### ######## ## ## #### ## ## ######
+## ## ## ## ## ## ## ## ### ## ## ##
+## ## ## ## ## ## ## ## #### ## ##
+######## ## ## ## ######### ## ## ## ## ## ####
+## ######### ## ## ## ## ## #### ## ##
+## ## ## ## ## ## ## ## ### ## ##
+## ## ## ## ## ## #### ## ## ######
+
+\************************************************************************************************/
+//HACK -> this should probably move into code
+function Sniper_MoveToNewLocation( entity sniper )
+{
+ sniper.EndSignal( "OnDeath" )
+ sniper.EndSignal( "OnDestroy" )
+
+ delaythread( 2 ) SniperCloak( sniper )
+
+ //go searching for nodes that are up somewhere
+ local sniperNode = GetRandomSniperNodeWithin( sniper, 3000 )
+
+ Sniper_FreeSniperNode( sniper )//free his current node
+ Sniper_TakeSniperNode( sniper, sniperNode )
+ Sniper_AssaultLocation( sniper, sniperNode )
+
+ WaitSignal( sniper, "OnFinishedAssault", "OnDeath", "OnDestroy", "AssaultTimeOut" )
+
+ SniperDeCloak( sniper )
+}
+
+function Sniper_TakeSniperNode( sniper, sniperNode )
+{
+ Assert( sniper.s.sniperNode == null ) // didn't free the last one
+ sniper.s.sniperNode = sniperNode
+
+ Assert( sniperNode.locked == false )// someone else already has it?
+ sniperNode.locked = true
+
+ local loc = sniperNode.loc
+ loc.numGuys++
+}
+
+function Sniper_FreeSniperNode( sniper )
+{
+ local sniperNode = sniper.s.sniperNode
+ if ( sniperNode == null )
+ return
+
+ sniper.s.sniperNode = null
+
+ local loc = sniperNode.loc
+ loc.numGuys--
+ sniperNode.locked = false
+}
+
+function Sniper_FreeSniperNodeOnDeath( entity sniper )
+{
+ sniper.WaitSignal( "OnDeath" )
+ Sniper_FreeSniperNode( sniper )
+}
+
+void function SniperCloak( entity sniper )
+{
+ if ( !IsAlive( sniper ) )
+ return
+
+ if ( !sniper.CanCloak() )
+ return
+
+ sniper.kv.allowshoot = 0
+ sniper.SetCloakDuration( 3.0, -1, 0 )
+ sniper.Minimap_Hide( TEAM_IMC, null )
+ sniper.Minimap_Hide( TEAM_MILITIA, null )
+}
+
+void function SniperDeCloak( entity sniper )
+{
+ if ( !IsAlive( sniper ) )
+ return
+
+ sniper.kv.allowshoot = 1
+ sniper.SetCloakDuration( 0, 0, 1.5 )
+ sniper.Minimap_AlwaysShow( TEAM_IMC, null )
+ sniper.Minimap_AlwaysShow( TEAM_MILITIA, null )
+}
+
+function Sniper_AssaultLocation( sniper, sniperNode )
+{
+ Assert( sniper.s.sniperNode == sniperNode ) // didn't get the right one
+
+ local origin = sniperNode.pos
+ local loc = sniperNode.loc
+ local angles = Vector( 0, loc.yaw, 0 )
+
+ Assert( "assaultPoint" in sniper.s )
+ sniper.AssaultPoint( origin )
+ sniper.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE )
+}
+
+function GetRandomSniperNodeWithin( sniper, maxDist )
+{
+ Assert( level.TowerDefense_SniperNodes.len() >= 2 )
+
+ local origin = sniper.GetOrigin()
+ local locations = SniperNodeWithin( level.TowerDefense_SniperNodes, origin, maxDist )
+
+ if ( locations.len() )
+ locations.randomize()
+
+ local goalNode = FindFreeSniperNode( locations )
+ if ( goalNode != null )
+ return goalNode
+
+ //if we get here it's because there are no free nodes within the maxDist
+ locations = SniperNodeClosest( level.TowerDefense_SniperNodes, origin )
+
+ goalNode = FindFreeSniperNode( locations )
+ Assert ( goalNode != null )
+
+ return goalNode
+}
+
+function FindFreeSniperNode( locations )
+{
+ foreach( loc in locations )
+ {
+ //is if filled up?
+ if ( loc.numGuys >= loc.maxGuys )
+ continue
+
+ //grab the first unlocked cover node
+ foreach ( node in loc.coverNodes )
+ {
+ if ( node.locked )
+ continue
+
+ return node
+ }
+
+ //ok then grab the first unlocked extra node
+ foreach ( node in loc.extraNodes )
+ {
+ if ( node.locked )
+ continue
+
+ return node
+ }
+ }
+
+ return null
+}
+
+//ArrayWithin() copy
+function SniperNodeWithin( Array, origin, maxDist )
+{
+ maxDist *= maxDist
+
+ local resultArray = []
+ foreach( loc in Array )
+ {
+ local testspot = null
+ testspot = loc.pos
+
+ local dist = DistanceSqr( origin, testspot )
+ if ( dist <= maxDist )
+ resultArray.append( loc )
+ }
+ return resultArray
+}
+
+//ArrayClosest() copy
+function SniperNodeClosest( Array, origin )
+{
+ Assert( type( Array ) == "array" )
+ local allResults = SniperArrayDistanceResults( Array, origin )
+
+ allResults.sort( SniperArrayDistanceCompare )
+
+ local returnEntities = []
+
+ foreach ( index, result in allResults )
+ {
+ returnEntities.insert( index, result.loc )
+ }
+
+ // the actual distances aren't returned
+ return returnEntities
+}
+
+function SniperArrayDistanceResults( Array, origin )
+{
+ Assert( type( Array ) == "array" )
+ local allResults = []
+
+ foreach ( loc in Array )
+ {
+ local results = {}
+ local testspot = null
+
+ testspot = loc.pos
+
+ results.distanceSqr <- LengthSqr( testspot - origin )
+ results.loc <- loc
+ allResults.append( results )
+ }
+
+ return allResults
+}
+
+function SniperArrayDistanceCompare( a, b )
+{
+ if ( a.distanceSqr > b.distanceSqr )
+ return 1
+ else if ( a.distanceSqr < b.distanceSqr )
+ return -1
+
+ return 0;
+}
+
+
+
+/************************************************************************************************\
+
+######## ######## ######## ###### ### ## ######
+## ## ## ## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ## ##
+######## ######## ###### ####### ## ## ## ## ##
+## ## ## ## ## ######### ## ##
+## ## ## ## ## ## ## ## ## ## ##
+## ## ## ######## ###### ## ## ######## ######
+
+\************************************************************************************************/
+function CreateSniperLocation( origin, yaw, heightCheck )
+{
+ local loc = {}
+ loc.pos <- origin
+ loc.yaw <- yaw
+ loc.heightCheck <- heightCheck
+ loc.numGuys <- 0
+ loc.maxGuys <- 0
+ loc.coverNodes <- []
+ loc.extraNodes <- []
+
+ return loc
+}
+
+function CreateSniperNode( location, origin )
+{
+ local node = {}
+ node.locked <- false
+ node.loc <- location
+ node.pos <- origin
+
+ return node
+}
+
+function SniperLocationsInit()
+{
+ FlagSet( "TD_SniperLocationsInit" )
+ local time = Time()
+
+ foreach ( loc in level.TowerDefense_SniperNodes )
+ {
+ SniperLocationSetup( loc )
+ wait 0.1 //space out all the slow stuff so it doesn't happen on the same frame
+ }
+
+ printt( "<<<<<***********************************************************>>>>>" )
+ printt( "SniperLocationsInit() took ", Time() - time, " seconds to complete" )
+ printt( "<<<<<***********************************************************>>>>>" )
+}
+
+function SniperLocationSetup( loc )
+{
+ array<vector> extraPos = GetNeighborPositionsAroundSniperLocation( expect vector( loc.pos ), expect float( loc.yaw ), expect float( loc.heightCheck ), MAXNODES_PER_SNIPERSPOT )
+ foreach ( origin in extraPos )
+ {
+ local node = CreateSniperNode( loc, origin )
+ loc.extraNodes.append( node )
+ }
+
+ loc.maxGuys = loc.coverNodes.len() + loc.extraNodes.len()
+ if ( loc.maxGuys == 0 )
+ printt( "sniper spot at [ " + loc.pos + " ] has no nodes around it within " + SNIPERSPOT_RADIUSCHECK + " units." )
+ Assert( loc.maxGuys <= MAXNODES_PER_SNIPERSPOT )
+}
+
+array<vector> function GetNeighborPositionsAroundSniperLocation( vector pos, float yaw, float heightCheck, int max )
+{
+ local height = pos.z
+ local isSpectre = true
+ local radius = SNIPERSPOT_RADIUSCHECK
+ array<vector> goalPos = []
+
+ array<vector> neighborPos = NavMesh_GetNeighborPositions( pos, HULL_HUMAN, MAXNODES_PER_SNIPERSPOT )
+ neighborPos = SortPositionsByClosestToPos( neighborPos, pos, yaw )
+ foreach ( origin in neighborPos )
+ {
+ if ( fabs( origin.z - height ) > heightCheck )
+ continue
+
+ if ( !IsMostDesireablePos( origin, pos, yaw ) )
+ continue
+
+ if ( IsPosTooCloseToOtherPositions( origin, goalPos ) )
+ continue
+
+ goalPos.append( origin )
+ if ( goalPos.len() == max )
+ break
+ }
+
+ return goalPos
+}
+
+array<vector> function SortPositionsByClosestToPos( array<vector> neighborPos, vector pos, float yaw )
+{
+ table ring = GetDesirableRing( pos, yaw )
+ vector testPos = expect vector( ring.pos )
+
+ array<vector> returnOrigins = ArrayClosestVector( neighborPos, testPos )
+ return returnOrigins
+}
+
+bool function IsPosTooCloseToOtherPositions( vector testPos, array<vector> positions )
+{
+ foreach ( pos in positions )
+ {
+ if ( DistanceSqr( pos, testPos ) <= SNIPERNODE_TOOCLOSE_SQR )
+ return true
+ }
+ return false
+}
+
+function IsMostDesireablePos( testPos, sniperPos, yaw )
+{
+ /*
+ what this function does is actually draw a circle out infront of the position based on the yaw.
+ then it checks to see if the node is within that circle.
+ Since most sniper positions are on EDGES of buildings, windows, etc, this techinique helps grab more nodes along the edge
+ */
+
+ table ring = GetDesirableRing( sniperPos, yaw )
+ local radiusSqr = ring.radius * ring.radius
+
+ if ( Distance2DSqr( testPos, ring.pos ) <= radiusSqr )
+ return true
+
+ return false
+}
+
+table function GetDesirableRing( pos, yaw )
+{
+ local dist = 200
+ local radius = 300
+
+ local vec = AnglesToForward( Vector( 0, yaw, 0 ) ) * dist
+ local testPos = pos + vec
+
+ table ring = {}
+ ring.pos <- testPos
+ ring.radius <- radius
+ return ring
+}
+
+
+
+
+
+
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_spawn_functions.nut b/Northstar.CustomServers/scripts/vscripts/mp/_spawn_functions.nut
new file mode 100644
index 000000000..3d9b84f3a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_spawn_functions.nut
@@ -0,0 +1,60 @@
+untyped
+
+global function SpawnFunctions_Init
+
+function SpawnFunctions_Init()
+{
+ if ( IsLobby() )
+ return
+
+ // shared OnSpawned callbacks
+ AddSpawnCallback( "script_mover", SpawnScriptMover )
+ AddSpawnCallback( "path_track", SpawnPathTrack )
+ AddSpawnCallback( "info_hint", SpawnInfoHint )
+ AddDeathCallback( "npc_titan", EmptyDeathCallback ) // so death info gets sent to client
+
+ // Arc Cannon Targets
+ foreach ( classname, val in ArcCannonTargetClassnames )
+ {
+ AddSpawnCallback( classname, AddToArcCannonTargets )
+ }
+
+ foreach ( classname, val in ProximityTargetClassnames )
+ {
+ AddSpawnCallback( classname, AddToProximityTargets )
+ }
+}
+
+void function EmptyDeathCallback( entity _1, var _2 )
+{
+}
+
+
+void function SpawnPathTrack( entity node )
+{
+ if ( node.HasKey( "WaitSignal" ) )
+ RegisterSignal( node.kv.WaitSignal )
+
+ if ( node.HasKey( "SendSignal" ) )
+ RegisterSignal( node.kv.SendSignal )
+
+ if ( node.HasKey( "WaitFlag" ) )
+ FlagInit( expect string( node.kv.WaitFlag ) )
+
+ if ( node.HasKey( "SetFlag" ) )
+ FlagInit( expect string( node.kv.SetFlag ) )
+}
+
+void function SpawnScriptMover( entity ent )
+{
+ if ( ent.HasKey( "custom_health" ) )
+ {
+ //printt( "setting health on " + ent + " to " + ent.kv.custom_health.tointeger() )
+ ent.SetHealth( ent.kv.custom_health.tointeger() )
+ }
+}
+
+void function SpawnInfoHint( entity ent )
+{
+ Assert( !ent.HasKey( "hotspot" ) || ent.kv.hotspot.tolower() in level.hotspotHints, "info_hint at " + ent.GetOrigin() + " has unknown hotspot hint: " + ent.kv.hotspot.tolower() )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_spectre_rack.nut b/Northstar.CustomServers/scripts/vscripts/mp/_spectre_rack.nut
new file mode 100644
index 000000000..a76c0fc9c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_spectre_rack.nut
@@ -0,0 +1,395 @@
+
+global function SpectreRack_Init
+global function IsStalkerRack
+global function SpawnFromStalkerRack
+global function AddSpectreRackCallback
+global function GetSpectreRackFromEnt
+global function SetupSpectreRack
+global function SpectreRackActivationEffects
+global function TrackFriendlySpectre
+
+const FX_GREEN_GLOW = $"P_spectre_rack_glow_idle"
+const WARNING_LIGHT_BLINK = $"warning_light_orange_blink"
+const SPECTRE_RACK_ACHIEVEMENT_COUNT = 6
+
+global struct SpectreRackSpectre
+{
+ string attachName
+ entity dummyModel
+ entity glowFX
+ entity spawner
+}
+
+global struct SpectreRack
+{
+ entity rackEnt
+ array<SpectreRackSpectre> spectreRackSpectres
+}
+
+struct
+{
+ int playersSpectreArrayIdx
+ array<string> spectreRackTypes
+ array<SpectreRack> spectreRacks
+ array<void functionref( entity, entity )> callbackFuncs
+} file
+
+void function AddSpectreRackCallback( void functionref( entity, entity ) func )
+{
+ Assert( !file.callbackFuncs.contains( func ) )
+ file.callbackFuncs.append( func )
+}
+
+void function SpectreRack_Init()
+{
+ if ( reloadingScripts )
+ return
+
+ file.spectreRackTypes.append( "npc_spectre_rack_wall" )
+ file.spectreRackTypes.append( "npc_spectre_rack_multi" )
+ file.spectreRackTypes.append( "npc_spectre_rack_triple" )
+ //file.spectreRackTypes.append( "npc_spectre_rack_portable" )
+ //file.spectreRackTypes.append( "npc_spectre_rack_palette" )
+
+ PrecacheParticleSystem( FX_GREEN_GLOW )
+ PrecacheParticleSystem( WARNING_LIGHT_BLINK )
+
+ foreach ( string rackType in file.spectreRackTypes )
+ {
+ AddSpawnCallbackEditorClass( "prop_dynamic", rackType, SetupSpectreRack )
+ }
+
+ if ( IsSingleplayer() )
+ {
+ file.playersSpectreArrayIdx = CreateScriptManagedEntArray()
+ AddSpectreRackCallback( TrySpectreAchievement )
+ }
+}
+
+bool function IsStalkerRack( entity ent )
+{
+ if ( !ent.HasKey( "editorclass" ) )
+ return false
+ string editorclass = ent.GetValueForKey( "editorclass" )
+ return file.spectreRackTypes.contains( editorclass )
+}
+
+void function SetupSpectreRack( entity rack )
+{
+ SpectreRack spectreRack
+ spectreRack.rackEnt = rack
+
+ // Get attach point info from the model being used
+ while( true )
+ {
+ int attachIndex = spectreRack.spectreRackSpectres.len() + 1
+ string attachment = "spectre_attach_" + attachIndex
+
+ int id = rack.LookupAttachment( attachment )
+ if ( id == 0 )
+ break
+
+ SpectreRackSpectre spectreRackSpectre
+ spectreRackSpectre.attachName = attachment
+ spectreRack.spectreRackSpectres.append( spectreRackSpectre )
+ }
+
+ // Get linked spawner
+ array<entity> linkedEnts = rack.GetLinkEntArray()
+ int spawnerCount = 0
+ foreach ( index, ent in linkedEnts )
+ {
+ if ( IsSpawner( ent ) )
+ {
+ spectreRack.spectreRackSpectres[index].spawner = ent
+ spawnerCount++
+ }
+ }
+ Assert( spawnerCount == spectreRack.spectreRackSpectres.len(), "Spectre rack " + rack + " at: " + rack.GetOrigin() + " " + rack.GetValueForKey( "editorclass" ) + " must link to exactly " + spectreRack.spectreRackSpectres.len() + " spawner" )
+
+ // Create dummy spectre models to idle on the rack
+ foreach ( spectreRackSpectre in spectreRack.spectreRackSpectres )
+ {
+ int attachID = rack.LookupAttachment( spectreRackSpectre.attachName )
+ vector origin = rack.GetAttachmentOrigin( attachID )
+ vector angles = rack.GetAttachmentAngles( attachID )
+
+ var spawnerKeyValues = spectreRackSpectre.spawner.GetSpawnEntityKeyValues()
+ expect table( spawnerKeyValues )
+ asset model = spectreRackSpectre.spawner.GetSpawnerModelName()
+ int skin
+ if ( "skin" in spawnerKeyValues )
+ {
+ skin = int( spawnerKeyValues.skin )
+ }
+
+ string idleAnim = GetIdleAnimForSpawner( spectreRackSpectre.spawner )
+ entity dummySpectre = CreatePropDynamic( model, origin, angles )
+ dummySpectre.SetSkin( skin )
+ dummySpectre.SetParent( rack, spectreRackSpectre.attachName )
+ thread PlayAnimTeleport( dummySpectre, idleAnim, rack, spectreRackSpectre.attachName )
+
+ spectreRackSpectre.dummyModel = dummySpectre
+ }
+
+ // Create effects on the rack
+ if ( !rack.HasKey( "DisableStatusLights" ) || rack.GetValueForKey( "DisableStatusLights" ) == "0" )
+ SpectreRackCreateFx( spectreRack, FX_GREEN_GLOW )
+
+ file.spectreRacks.append( spectreRack )
+}
+
+void function SpawnFromStalkerRack( entity rack, entity activator = null )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ SpectreRack spectreRack = GetSpectreRackFromEnt( rack )
+ Assert( IsValid( spectreRack.rackEnt ) )
+
+ thread SpectreRackActivationEffects( spectreRack )
+ thread SpectreRackActivationSpawners( spectreRack, activator )
+
+ if ( IsValid( activator ) && activator.IsPlayer() )
+ UnlockAchievement( activator, achievements.HACK_STALKERS )
+}
+
+void function SpectreRackActivationEffects( SpectreRack spectreRack )
+{
+ EndSignal( spectreRack, "OnDestroy" )
+ EndSignal( spectreRack.rackEnt, "OnDestroy" )
+
+ OnThreadEnd
+ (
+ function() : ( spectreRack )
+ {
+ if ( IsValid( spectreRack ) )
+ SpectreRackDestroyFx( spectreRack )
+ }
+ )
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, spectreRack.rackEnt.GetOrigin() + Vector( 0, 0, 72), "colony_spectre_initialize_beep" )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, spectreRack.rackEnt.GetOrigin() + Vector( 0, 0, 72), "corporate_spectrerack_activate" )
+
+ SpectreRackDestroyFx( spectreRack )
+ SpectreRackCreateFx( spectreRack, WARNING_LIGHT_BLINK )
+
+ // Let the flash FX linger a bit longer, then the thread end will kill the fx
+ wait 6
+}
+
+void function SpectreRackActivationSpawners( SpectreRack spectreRack, entity activator = null )
+{
+ EndSignal( spectreRack, "OnDestroy" )
+ EndSignal( spectreRack.rackEnt, "OnDestroy" )
+
+ array<int> spawnOrder
+ for ( int i = 0 ; i < spectreRack.spectreRackSpectres.len() ; i++ )
+ {
+ spawnOrder.append(i)
+ }
+
+ spawnOrder.randomize()
+
+ foreach ( int index in spawnOrder )
+ {
+ thread SpectreRackReleaseSpectre( spectreRack, index, activator )
+
+ wait RandomFloatRange( 0.0, 0.25 )
+ }
+}
+
+void function SpectreRackReleaseSpectre( SpectreRack spectreRack, int index, entity activator = null )
+{
+ SpectreRackSpectre spectreRackSpectre = spectreRack.spectreRackSpectres[ index ]
+ if ( !IsValid( spectreRackSpectre.dummyModel ) )
+ return
+
+ entity rackEnt = spectreRack.rackEnt
+
+ entity dummy = spectreRackSpectre.dummyModel
+ Assert( IsValid ( dummy ) )
+
+ entity spawner = spectreRackSpectre.spawner
+ Assert( IsValid ( spawner ) )
+
+ EndSignal( spectreRackSpectre, "OnDestroy" )
+ EndSignal( rackEnt, "OnDestroy" )
+
+ var spawnerKeyValues = spawner.GetSpawnEntityKeyValues()
+ expect table( spawnerKeyValues )
+ if ( "script_delay" in spawnerKeyValues )
+ {
+ float delay = float( spawnerKeyValues.script_delay )
+ wait delay
+ }
+
+ if ( IsValid( dummy ) )
+ dummy.Destroy()
+ entity spectre = spawner.SpawnEntity()
+ DispatchSpawn( spectre )
+ spectre.ContextAction_SetBusy()
+
+ if ( IsValid( activator ) )
+ {
+ SetTeam( spectre, activator.GetTeam() )
+ //spectre.DisableBehavior( "Assault" )
+ /*
+ if ( activator.IsPlayer() )
+ {
+ NPCFollowsPlayer( spectre, activator )
+ }
+ else if ( activator.IsNPC() )
+ {
+ NPCFollowsNPC( spectre, activator )
+ }
+ */
+ }
+
+ string deployAnim = GetDeployAnimForSpawner( spectreRackSpectre.spawner )
+ string idleAnim = GetIdleAnimForSpawner( spectreRackSpectre.spawner )
+
+ EndSignal( spectre, "OnDeath" )
+
+ string attachment = spectreRackSpectre.attachName
+ spectre.SetParent( rackEnt, attachment )
+ thread PlayAnimTeleport( spectre, idleAnim, rackEnt, attachment )
+
+ if ( CoinFlip() )
+ EmitSoundOnEntity( spectre, "diag_stalker_generic" )
+
+ spectre.SetNoTarget( true )
+ waitthread PlayAnim( spectre, deployAnim, rackEnt, attachment )
+ spectre.ClearParent()
+ float yaw = spectre.GetAngles().y
+ spectre.SetAngles( <0,yaw,0> )//spectres released on moving platforms angle correctly
+
+ foreach ( func in file.callbackFuncs )
+ {
+ thread func( spectre, activator )
+ }
+
+ spectre.SetTitle( spectre.GetSettingTitle() )
+ Highlight_SetFriendlyHighlight( spectre, "sp_friendly_pilot" )
+ ShowName( spectre )
+ wait 1
+ spectre.SetNoTarget( false )
+ spectre.ContextAction_ClearBusy()
+}
+
+void function SpectreRackCreateFx( SpectreRack spectreRack, asset fxName )
+{
+ for ( int i = 0 ; i < spectreRack.spectreRackSpectres.len() ; i++ )
+ {
+ string attachment = "glow_" + i
+ int id = spectreRack.rackEnt.LookupAttachment( attachment )
+ Assert( id != 0, "Missing attachment \"" + attachment + "\" in model " + spectreRack.rackEnt.GetModelName() )
+
+ entity fx = PlayLoopFXOnEntity( fxName, spectreRack.rackEnt, attachment )
+ Assert( !IsValid( spectreRack.spectreRackSpectres[i].glowFX ) )
+ spectreRack.spectreRackSpectres[i].glowFX = fx
+ }
+}
+
+void function SpectreRackDestroyFx( SpectreRack spectreRack )
+{
+ foreach ( spectreRackSpectre in spectreRack.spectreRackSpectres )
+ {
+ entity fx = spectreRackSpectre.glowFX
+ if ( !IsValid_ThisFrame( fx ) )
+ continue
+ fx.ClearParent()
+ fx.Destroy()
+ }
+}
+
+SpectreRack function GetSpectreRackFromEnt( entity rack )
+{
+ // Get the spectre rack struct from the placed entity
+ foreach ( SpectreRack rackStruct in file.spectreRacks )
+ {
+ if ( rackStruct.rackEnt == rack )
+ return rackStruct
+ }
+ SpectreRack rackStruct
+ return rackStruct
+}
+
+string function GetIdleAnimForSpawner( entity spawner )
+{
+ string idleAnim
+ string spawnClassName = GetEditorClass( spawner )
+ if ( spawnClassName == "" )
+ spawnClassName = spawner.GetSpawnEntityClassName()
+
+ switch( spawnClassName )
+ {
+ case "npc_stalker":
+ case "npc_stalker_zombie":
+ case "npc_stalker_zombie_mossy":
+ idleAnim = "st_medbay_idle_armed"
+ break
+ case "npc_spectre":
+ idleAnim = "sp_med_bay_dropidle_A"
+ break
+ default:
+ idleAnim = "st_medbay_idle_armed"
+ break
+ }
+
+ return idleAnim
+}
+
+string function GetDeployAnimForSpawner( entity spawner )
+{
+ string deployAnim
+ string spawnClassName = GetEditorClass( spawner )
+ if ( spawnClassName == "" )
+ spawnClassName = spawner.GetSpawnEntityClassName()
+
+ switch( spawnClassName )
+ {
+ case "npc_stalker":
+ case "npc_stalker_zombie":
+ case "npc_stalker_zombie_mossy":
+ deployAnim = "st_medbay_drop_armed"
+ break
+ case "npc_spectre_suicide":
+ deployAnim = "sp_med_bay_drop_unarmed"
+ break
+ case "npc_spectre":
+ deployAnim = "sp_med_bay_drop_A"
+ break
+ default:
+ deployAnim = "st_medbay_drop_armed"
+ break
+ }
+
+ return deployAnim
+}
+
+void function TrySpectreAchievement( entity npc, entity activator )
+{
+ if ( !IsValid( activator ) )
+ return
+
+ if ( !activator.IsPlayer() )
+ return
+
+ TrackFriendlySpectre( npc, activator )
+}
+
+void function TrackFriendlySpectre( entity npc, entity player )
+{
+ if ( player.GetTeam() != npc.GetTeam() )
+ return
+
+ if ( IsStalker( npc ) )
+ AddToScriptManagedEntArray( file.playersSpectreArrayIdx, npc )
+ else
+ return
+
+ printt( "Achievment tracking stalker: " + GetScriptManagedEntArrayLen( file.playersSpectreArrayIdx ) )
+ if ( GetScriptManagedEntArrayLen( file.playersSpectreArrayIdx ) >= SPECTRE_RACK_ACHIEVEMENT_COUNT )
+ {
+ UnlockAchievement( player, achievements.HACK_ROBOTS )
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/scripts/vscripts/mp/_stats.nut
new file mode 100644
index 000000000..0e8b58f45
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_stats.nut
@@ -0,0 +1,78 @@
+global function Stats_Init
+global function AddStatCallback
+global function Stats_SaveStatDelayed
+global function PlayerStat_GetCurrentInt
+global function PlayerStat_GetCurrentFloat
+global function UpdatePlayerStat
+global function IncrementPlayerDidPilotExecutionWhileCloaked
+global function UpdateTitanDamageStat
+global function UpdateTitanWeaponDamageStat
+global function UpdateTitanCoreEarnedStat
+global function PreScoreEventUpdateStats
+global function PostScoreEventUpdateStats
+global function Stats_OnPlayerDidDamage
+
+void function Stats_Init()
+{
+
+}
+
+void function AddStatCallback(string statCategory, string statAlias, string statSubAlias, void functionref(entity, float, string) callback, string subRef)
+{
+
+}
+
+void function Stats_SaveStatDelayed(entity player, string statCategory, string statAlias, string statSubAlias)
+{
+
+}
+
+int function PlayerStat_GetCurrentInt(entity player, string statCategory, string statAlias, string statSubAlias)
+{
+ return 0
+}
+
+float function PlayerStat_GetCurrentFloat(entity player, string statCategory, string statAlias, string statSubAlias)
+{
+ return 0
+}
+
+void function UpdatePlayerStat(entity player, string statCategory, string subStat, int count = 0)
+{
+
+}
+
+void function IncrementPlayerDidPilotExecutionWhileCloaked(entity player)
+{
+
+}
+
+void function UpdateTitanDamageStat(entity attacker, float savedDamage, var damageInfo)
+{
+
+}
+
+void function UpdateTitanWeaponDamageStat(entity attacker, float savedDamage, var damageInfo)
+{
+
+}
+
+void function UpdateTitanCoreEarnedStat( entity player, entity titan )
+{
+
+}
+
+void function PreScoreEventUpdateStats(entity attacker, entity ent)
+{
+
+}
+
+void function PostScoreEventUpdateStats(entity attacker, entity ent)
+{
+
+}
+
+void function Stats_OnPlayerDidDamage(entity player, var damageInfo)
+{
+
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_titan_npc.nut b/Northstar.CustomServers/scripts/vscripts/mp/_titan_npc.nut
new file mode 100644
index 000000000..582850875
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_titan_npc.nut
@@ -0,0 +1,818 @@
+untyped
+
+global function TitanNPC_Init
+
+global function CodeCallback_PlayerRequestClimbInNPCTitan
+global function ResetTitanLoadoutFromPrimary
+
+global function NPCTitanNextMode
+global function NPCTitanInitModeOnPlayerRespawn
+global function SetupAutoTitan
+global function SetupNPC_TitanTitle
+global function SetPlayerPetTitan
+global function AutoTitanChangedEnemy
+global function PlayAutoTitanConversation
+global function CreateTitanModelAndSkinSetup
+global function SetWeaponCooldowns
+
+global function ResetTitanBuildTime
+
+global function CreateNPCTitanFromSettings
+
+global function FreeAutoTitan
+
+global function GetRandomTitanWeapon
+
+global function SpawnTitanBatteryOnDeath
+global function CreateTitanBattery
+
+global function WaitForHotdropToEnd
+global function ResetCoreKillCounter
+
+const TITAN_USE_HOLD_PROMPT = "Hold [USE] to Pilot||Hold [USE] to Rodeo"
+const TITAN_USE_PRESS_PROMPT = "Press [USE] to Pilot||Press [USE] to Rodeo"
+
+const int BATTERY_DROP_BOSS = 4
+
+const float BATTERY_DROP_HEALTH_FRAC_SURE = 0.2
+const float BATTERY_DROP_HEALTH_FRAC_MID = 0.5
+
+const int BATTERY_DROP_MID_CHANCE = 70
+const int BATTERY_DROP_LOW_CHANCE = 40
+
+struct
+{
+ int coreKillCounter = 0
+} file
+
+function TitanNPC_Init()
+{
+ RegisterSignal( "ChangedTitanMode" )
+ RegisterSignal( "PROTO_WeaponPickup" )
+
+ AddSoulDeathCallback( AutoTitanDestroyedCheck )
+
+ #if R1_VGUI_MINIMAP
+ Minimap_PrecacheMaterial( $"vgui/HUD/threathud_titan_friendlyself" )
+ Minimap_PrecacheMaterial( $"vgui/HUD/threathud_titan_friendlyself_guard" )
+ #endif
+
+ if ( IsSingleplayer() )
+ {
+ AddSpawnCallbackEditorClass( "script_ref", "script_titan_battery", SpawnTitanBattery )
+ AddDeathCallback( "npc_titan", SpawnTitanBatteryOnDeath )
+ AddDeathCallback( "npc_titan", TitanAchievementTracking_SP )
+ }
+}
+
+void function AutoTitanDestroyedCheck( entity soul, var damageInfo )
+{
+ entity titan = soul.GetTitan()
+ if ( !IsValid( titan ) )
+ return
+
+ entity player = soul.GetBossPlayer()
+ if ( !IsValid( player ) )
+ return
+
+ SetActiveTitanLoadoutIndex( player, -1 )
+
+ if ( player.GetPetTitan() == titan )
+ player.SetPetTitan( null )
+
+ if ( soul.IsEjecting() )
+ return
+
+ // has another titan?
+ if ( GetPlayerTitanInMap( player ) )
+ return
+
+ switch ( Riff_TitanAvailability() )
+ {
+ case eTitanAvailability.Default:
+ break
+
+ default:
+ if ( !Riff_IsTitanAvailable( player ) )
+ return
+ }
+
+ if ( GAMETYPE == SST )
+ return
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == eDamageSourceId.round_end )
+ return
+
+ thread PlayConversationToPlayer( "AutoTitanDestroyed", player )
+}
+
+
+
+//////////////////////////////////////////////////////////
+function SetupNPC_TitanTitle( npcTitan, player )
+{
+ npcTitan.SetBossPlayer( player )
+
+ #if R1_VGUI_MINIMAP
+ switch ( player.GetPetTitanMode() )
+ {
+ case eNPCTitanMode.FOLLOW:
+ npcTitan.Minimap_SetBossPlayerMaterial( $"vgui/HUD/threathud_titan_friendlyself" )
+ break;
+
+ //case eNPCTitanMode.ROAM:
+ // break;
+
+ case eNPCTitanMode.STAY:
+ npcTitan.Minimap_SetBossPlayerMaterial( $"vgui/HUD/threathud_titan_friendlyself_guard" )
+ break;
+ }
+ #endif
+}
+
+//////////////////////////////////////////////////////////
+void function NPCTitanNextMode( entity npcTitan, entity player )
+{
+ entity soul = npcTitan.GetTitanSoul()
+ if ( !SoulHasPassive( soul, ePassives.PAS_ENHANCED_TITAN_AI ) && PROTO_AutoTitansDisabled() )
+ return
+
+ NPCTitanDisableCurrentMode( npcTitan, player )
+
+ local mode = player.GetPetTitanMode() + 1
+ if ( mode == eNPCTitanMode.MODE_COUNT )
+ mode = eNPCTitanMode.FOLLOW
+
+ player.SetPetTitanMode( mode )
+ npcTitan.Signal( "ChangedTitanMode" )
+
+ SetupNPC_TitanTitle( npcTitan, player )
+ NPCTitanEnableCurrentMode( npcTitan, player )
+}
+
+//////////////////////////////////////////////////////////
+function NPCTitanSetBehaviorForMode( entity npcTitan, entity player )
+{
+ entity soul = npcTitan.GetTitanSoul()
+ if ( soul == null)
+ soul = player.GetTitanSoul()
+
+ switch ( player.GetPetTitanMode() )
+ {
+ case eNPCTitanMode.FOLLOW:
+ if ( soul && SoulHasPassive( soul, ePassives.PAS_ENHANCED_TITAN_AI ) )
+ npcTitan.SetBehaviorSelector( "behavior_mp_auto_titan_enhanced" )
+ else
+ npcTitan.SetBehaviorSelector( "behavior_mp_auto_titan" )
+ break;
+
+ //case eNPCTitanMode.ROAM:
+ // break;
+
+ case eNPCTitanMode.STAY:
+ if ( soul && SoulHasPassive( soul, ePassives.PAS_ENHANCED_TITAN_AI ) )
+ npcTitan.SetBehaviorSelector( "behavior_mp_auto_titan_enhanced_guard" )
+ else
+ npcTitan.SetBehaviorSelector( "behavior_mp_auto_titan_guard" )
+ break;
+ }
+}
+
+//////////////////////////////////////////////////////////
+function NPCTitanDisableCurrentMode( entity npcTitan, entity player )
+{
+ switch ( player.GetPetTitanMode() )
+ {
+ case eNPCTitanMode.FOLLOW:
+ npcTitan.DisableBehavior( "Follow" )
+ break;
+
+ //case eNPCTitanMode.ROAM:
+ // break;
+
+ case eNPCTitanMode.STAY:
+ npcTitan.DisableBehavior( "Assault" )
+ break;
+ }
+}
+
+
+//////////////////////////////////////////////////////////
+function NPCTitanEnableCurrentMode( entity npcTitan, entity player )
+{
+ switch ( player.GetPetTitanMode() )
+ {
+ case eNPCTitanMode.FOLLOW:
+ NPCTitanFollowPilotInit( npcTitan, player )
+ break;
+
+ //case eNPCTitanMode.ROAM:
+ // break;
+
+ case eNPCTitanMode.STAY:
+ {
+
+ local traceStart = player.EyePosition()
+ local forward = AnglesToForward( player.EyeAngles() )
+ local traceEnd = traceStart + ( forward * 12000 )
+
+ TraceResults result = TraceLine( traceStart, traceEnd, player, TRACE_MASK_BLOCKLOS, TRACE_COLLISION_GROUP_NONE )
+
+ local dir = result.endPos - npcTitan.EyePosition()
+
+ // DebugDrawLine( result.endPos, npcTitan.EyePosition(), 255, 0, 0, true, 5 )
+
+ local titanAngles;
+ if ( LengthSqr( dir ) > 100 )
+ titanAngles = VectorToAngles( dir )
+ else
+ titanAngles = player.GetAngles()
+
+ titanAngles.z = 0;
+
+ npcTitan.AssaultPointClamped( npcTitan.GetOrigin() )
+ npcTitan.AssaultSetAngles( titanAngles, true )
+ break;
+ }
+ }
+
+ NPCTitanSetBehaviorForMode( npcTitan, player )
+}
+
+
+void function AutoTitanChangedEnemy( entity titan )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ entity enemy = titan.GetEnemy()
+
+ if ( !IsAlive( enemy ) )
+ return
+
+ if ( !titan.CanSee( enemy ) )
+ return
+
+ string aliasSuffix
+ if ( enemy.IsTitan() )
+ aliasSuffix = "autoEngageTitan"
+ else if ( IsGrunt( enemy ) )
+ aliasSuffix = "autoEngageGrunt"
+ else if ( enemy.IsHuman() && enemy.IsPlayer() )
+ aliasSuffix = "autoEngagePilot"
+
+ if ( aliasSuffix == "" )
+ return
+
+ PlayAutoTitanConversation( titan, aliasSuffix )
+}
+
+function AutoTitanShouldSpeak( entity titan, entity owner, aliasSuffix )
+{
+ if ( IsForcedDialogueOnly( owner ) )
+ return false
+
+ if ( "disableAutoTitanConversation" in titan.s )
+ {
+ return false
+ }
+ //Shut Auto Titans up when game isn't active anymore
+ if ( GetGameState() >= eGameState.Postmatch )
+ {
+ return false
+ }
+
+ entity owner
+
+ if ( titan.IsPlayer() )
+ {
+ owner = titan
+ }
+ else
+ {
+ owner = GetPetTitanOwner( titan )
+ if ( !IsValid( owner ) )
+ return
+ }
+
+ if ( owner.s.autoTitanLastEngageCallout == aliasSuffix )
+ {
+ // just did this line, so significant time has to pass before we will use it again
+ return Time() > owner.s.autoTitanLastEngageCalloutTime + 28
+ }
+
+ // this is a new line, so just make sure we haven't spoken too recently
+ return Time() > owner.s.autoTitanLastEngageCalloutTime + 7
+}
+
+void function PlayAutoTitanConversation( entity titan, string aliasSuffix )
+{
+ entity owner
+
+ if ( titan.IsPlayer() )
+ {
+ owner = titan
+ }
+ else
+ {
+ owner = GetPetTitanOwner( titan )
+ if ( !IsValid( owner ) )
+ return
+ }
+
+ if ( !AutoTitanShouldSpeak( titan, owner, aliasSuffix ) ) //Only use the suffix since that's the distinguishing part of the alias, i.e. "engage_titans"
+ return
+
+ owner.s.autoTitanLastEngageCalloutTime = Time()
+ owner.s.autoTitanLastEngageCallout = aliasSuffix //Only use the suffix since that's the distinguishing part of the alias, i.e. "engage_titans"
+
+ int conversationID = GetConversationIndex( aliasSuffix )
+ Remote_CallFunction_Replay( owner, "ServerCallback_PlayTitanConversation", conversationID )
+}
+
+
+void function FreeAutoTitan( entity npcTitan )
+{
+ //npcTitan.SetEnemyChangeCallback( "" )
+
+ local bossPlayer = npcTitan.GetBossPlayer()
+
+ if ( !IsValid( bossPlayer ) )
+ return
+
+ bossPlayer.SetPetTitan( null )
+
+ local soul = npcTitan.GetTitanSoul()
+
+ npcTitan.ClearBossPlayer()
+ soul.ClearBossPlayer()
+
+ npcTitan.SetTitle( "" )
+
+ npcTitan.Signal( "TitanStopsThinking" )
+ npcTitan.UnsetUsable()
+
+ thread TitanKneel( npcTitan )
+}
+
+
+//////////////////////////////////////////////////////////
+function SetupAutoTitan( entity npcTitan, entity player )
+{
+ #if SP
+ npcTitan.SetUsePrompts( "#HOLD_TO_EMBARK_SP", "#PRESS_TO_EMBARK_SP" )
+ #endif
+
+ #if MP
+ npcTitan.SetUsePrompts( "#HOLD_TO_EMBARK", "#PRESS_TO_EMBARK" )
+ #endif
+
+ npcTitan.SetUsableByGroup( "owner pilot" )
+
+ NPCTitanFollowPilotInit( npcTitan, player )
+
+ NPCTitanGuardModeInit( npcTitan )
+
+ npcTitan.SetEnemyChangeCallback( AutoTitanChangedEnemy )
+
+ NPCTitanEnableCurrentMode( npcTitan, player )
+
+ npcTitan.DisableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE )
+ npcTitan.EnableNPCFlag( NPC_NEW_ENEMY_FROM_SOUND )
+ UpdateEnemyMemoryFromTeammates( npcTitan )
+
+ SetPlayerPetTitan( player, npcTitan )
+
+ SetupNPC_TitanTitle( npcTitan, player )
+
+ ShowName( npcTitan )
+
+ SPMP_UpdateNPCProficiency( npcTitan )
+}
+
+function SetPlayerPetTitan( entity player, entity npcTitan )
+{
+ if ( npcTitan == player.GetPetTitan() )
+ return
+
+ entity previousOwner = GetPetTitanOwner( npcTitan )
+ if ( IsValid( previousOwner ) )
+ {
+ previousOwner.SetPetTitan( null )
+ }
+
+ if ( IsAlive( player.GetPetTitan() ) )
+ {
+ Assert( !player.s.replacementDropInProgress, "Tried to give us a titan when we were executing a Titanfall" )
+ // kill old pet titan
+ player.GetPetTitan().Die( null, null, { scriptType = DF_INSTANT, damageSourceId = damagedef_suicide } )
+ }
+
+ // HACK: not really a hack, but this could be optimized to only render always for a given client
+ npcTitan.EnableRenderAlways()
+ player.SetPetTitan( npcTitan )
+ #if HAS_TITAN_EARNING
+ ClearTitanAvailable( player )
+ #endif
+ SetTeam( npcTitan, player.GetTeam() )
+ entity soul = npcTitan.GetTitanSoul()
+ if ( soul == null )
+ soul = player.GetTitanSoul()
+
+ string settings = GetSoulPlayerSettings( soul )
+ var maintainTitle = Dev_GetPlayerSettingByKeyField_Global( settings, "keep_title_on_autotitan" )
+ if ( maintainTitle != null && maintainTitle == 1 )
+ {
+ string title = expect string( GetPlayerSettingsFieldForClassName( settings, "printname" ) )
+ npcTitan.SetTitle( title )
+ }
+ else if ( SoulHasPassive( soul, ePassives.PAS_ENHANCED_TITAN_AI ) )
+ {
+ npcTitan.SetTitle( "#NPC_AUTO_TITAN_ENHANCED" )
+ }
+ else
+ {
+ npcTitan.SetTitle( "#NPC_AUTO_TITAN" )
+ }
+
+ npcTitan.DisableHibernation()
+}
+
+
+//////////////////////////////////////////////////////////
+function NPCTitanFollowPilotInit( npcTitan, player )
+{
+ int followBehavior = GetDefaultNPCFollowBehavior( npcTitan )
+ npcTitan.InitFollowBehavior( player, followBehavior )
+
+ if ( IsMultiplayer() )
+ {
+ npcTitan.SetFollowGoalTolerance( 700 )
+ npcTitan.SetFollowGoalCombatTolerance( 700 )
+ npcTitan.SetFollowTargetMoveTolerance( 200 )
+ }
+ else
+ {
+ npcTitan.SetFollowGoalTolerance( 500 )
+ npcTitan.SetFollowGoalCombatTolerance( 1200 )
+ npcTitan.SetFollowTargetMoveTolerance( 150 )
+ }
+
+ npcTitan.EnableBehavior( "Follow" )
+ npcTitan.DisableBehavior( "Assault" )
+}
+
+//////////////////////////////////////////////////////////
+function NPCTitanGuardModeInit( npcTitan )
+{
+#if DEV // Bug 110047
+ Assert( IsValid( npcTitan ) )
+ if ( !npcTitan.IsTitan() && !npcTitan.IsNPC() )
+ printl( "npcTitan is " + npcTitan.GetClassName() )
+#endif
+
+ npcTitan.AssaultSetFightRadius( 0 )
+
+ if ( IsSingleplayer() )
+ {
+ npcTitan.AssaultSetGoalRadius( 512 )
+ npcTitan.AssaultSetArrivalTolerance( 300 )
+ }
+ else
+ {
+ npcTitan.AssaultSetGoalRadius( 400 )
+ npcTitan.AssaultSetArrivalTolerance( 200 )
+ }
+}
+
+//////////////////////////////////////////////////////////
+function NPCTitanInitModeOnPlayerRespawn( player )
+{
+ if ( IsValid( player.GetPetTitan() ) )
+ {
+ local titan = player.GetPetTitan()
+
+ switch ( player.GetPetTitanMode() )
+ {
+ case eNPCTitanMode.FOLLOW:
+ NPCTitanFollowPilotInit( titan, player )
+ break;
+
+ default:
+ // nothing to do for other modes
+ break;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////
+function CodeCallback_PlayerRequestClimbInNPCTitan( npcTitan, player )
+{
+}
+
+
+
+
+//////////////////////////////////////////////////////////
+entity function CreateNPCTitanFromSettings( string settings, int team, vector origin, vector angles )
+{
+ entity npc = CreateNPCTitan( settings, team, origin, angles )
+ DispatchSpawn( npc )
+ return npc
+}
+
+function CreateTitanModelAndSkinSetup( entity npc )
+{
+ asset currentModel = npc.GetModelName()
+
+ if ( IsSingleplayer() )
+ {
+ switch ( currentModel )
+ {
+ case $"":
+ case $"models/titans/buddy/titan_buddy.mdl":
+ case $"models/titans/light/sp_titan_light_locust.mdl":
+ case $"models/titans/light/sp_titan_light_raptor.mdl":
+ case $"models/titans/heavy/sp_titan_heavy_deadbolt.mdl":
+ case $"models/titans/heavy/sp_titan_heavy_ogre.mdl":
+ case $"models/titans/medium/sp_titan_medium_ajax.mdl":
+ case $"models/titans/medium/sp_titan_medium_wraith.mdl":
+ break
+
+ default:
+ CodeWarning( "NPC titan at " + npc.GetOrigin() + " had non-sp titan model " + currentModel )
+ break
+ }
+ }
+
+ string settings = npc.ai.titanSettings.titanSetFile
+ asset model = GetPlayerSettingsAssetForClassName( settings, "bodymodel" )
+ npc.SetValueForModelKey( model )
+}
+
+// NEW TITAN STUFF BROUGHT OVER FROM TOWER DEFENSE R1
+string function GetRandomTitanWeapon()
+{
+ TitanLoadoutDef loadout = GetAllowedTitanLoadouts().getrandom()
+ return loadout.primary
+}
+
+void function ResetTitanBuildTime( entity player )
+{
+ if ( player.IsTitan() )
+ {
+ player.SetTitanBuildTime( GetCoreBuildTime( player ) )
+ return
+ }
+
+ player.SetTitanBuildTime( GetTitanBuildTime( player ) )
+}
+
+
+/* SP */
+
+void function SpawnTitanBattery( entity batteryRef )
+{
+ vector origin = batteryRef.GetOrigin()
+ entity battery = CreateTitanBattery( origin )
+ batteryRef.Destroy()
+}
+
+void function SpawnTitanBatteryOnDeath( entity titan, var damageInfo )
+{
+ if ( !titan.ai.shouldDropBattery || titan.GetTeam() == TEAM_MILITIA )
+ return
+ // if ( RandomFloatRange( 0, 100 ) < 50 )
+ // return
+ int attachID = titan.LookupAttachment( "CHESTFOCUS" )
+ vector origin = titan.GetAttachmentOrigin( attachID )
+
+ int numBatt = 0
+
+ if ( titan.IsTitan() && titan.ai.bossTitanType == TITAN_MERC )
+ {
+ numBatt = BATTERY_DROP_BOSS
+ }
+ else
+ {
+ if ( Flag( "PlayerDidSpawn" ) )
+ {
+ entity player = GetPlayerArray()[0]
+ entity playerTitan = GetTitanFromPlayer( player )
+
+ if ( IsValid( playerTitan ) &&
+ (
+ GetDoomedState( playerTitan ) ||
+ RandomDropBatteryBasedOnHealth( playerTitan )
+ )
+ )
+ {
+ numBatt = 1
+ }
+ }
+ }
+
+ for ( int i=0; i<numBatt; i++ )
+ {
+ vector vec = RandomVec( 150 )
+ if ( numBatt == 1 )
+ vec = < 0,0,0 >
+ entity battery = CreateTitanBattery( origin )
+ battery.SetVelocity( < vec.x, vec.y, 400 > )
+ }
+}
+
+entity function CreateTitanBattery( vector origin )
+{
+ entity battery = Rodeo_CreateBatteryPack()
+ battery.SetOrigin( origin )
+ //Highlight_SetNeutralHighlight( battery, "power_up" )
+ // if ( IsValid( battery ) )
+ // {
+ // PickupGlow glow = CreatePickupGlow( battery, 0, 255, 0 )
+ // glow.glowFX.SetParent( battery, "", true, 0 )
+ // }
+ return battery
+}
+
+void function SetWeaponCooldowns( entity player, array<entity> weapons, float cooldown )
+{
+ foreach ( weapon in weapons )
+ {
+ int max = weapon.GetWeaponPrimaryClipCountMax()
+ if ( max <= 0 )
+ continue
+ int current = int( max * cooldown )
+ weapon.SetWeaponPrimaryClipCountAbsolute( current )
+
+ if ( weapon.IsChargeWeapon() )
+ {
+ float chargeCooldownTime = weapon.GetWeaponSettingFloat( eWeaponVar.charge_cooldown_time )
+ if ( chargeCooldownTime > 1.0 )
+ {
+ weapon.SetWeaponPrimaryClipCountAbsolute( max )
+ weapon.SetWeaponChargeFractionForced( 1.0 - cooldown )
+ }
+ }
+ }
+}
+
+void function ResetTitanLoadoutFromPrimary( entity titan )
+{
+ Assert( titan.IsTitan() )
+ Assert( IsAlive( titan ) )
+
+// EmitSoundOnEntity( player, "Coop_AmmoBox_AmmoRefill" )
+ entity soul = titan.GetTitanSoul()
+ // not a real titan, swapping in/out of titan etc
+ if ( soul == null )
+ return
+
+ array<entity> weapons = GetPrimaryWeapons( titan )
+
+ foreach ( weapon in weapons )
+ {
+ TitanLoadoutDef ornull titanLoadout = GetTitanLoadoutForPrimary( weapon.GetWeaponClassName() )
+ if ( titanLoadout == null )
+ continue
+ expect TitanLoadoutDef( titanLoadout )
+
+ float coreValue = SoulTitanCore_GetNextAvailableTime( soul )
+
+ ReplaceTitanLoadoutWhereDifferent( titan, titanLoadout )
+
+ SoulTitanCore_SetNextAvailableTime( soul, coreValue )
+
+ if ( titan.IsPlayer() )
+ {
+// Remote_CallFunction_Replay( titan, "ServerCallback_NotifyLoadout", titan.GetEncodedEHandle() )
+ Remote_CallFunction_Replay( titan, "ServerCallback_UpdateTitanModeHUD" )
+ }
+ break
+ }
+}
+
+void function WaitForHotdropToEnd( entity titan )
+{
+ // Wait until player sees the boss titan
+ while ( titan.e.isHotDropping )
+ {
+ WaitFrame()
+ }
+}
+
+bool function RandomDropBatteryBasedOnHealth( entity playerTitan )
+{
+ float healthFrac = GetHealthFrac( playerTitan )
+ int randomPercent = RandomIntRange( 0, 100 )
+
+ if ( healthFrac <= BATTERY_DROP_HEALTH_FRAC_SURE )
+ {
+ return true
+ }
+ else if ( healthFrac <= BATTERY_DROP_HEALTH_FRAC_MID )
+ {
+ return randomPercent <= BATTERY_DROP_MID_CHANCE
+ }
+ else
+ {
+ return randomPercent <= BATTERY_DROP_LOW_CHANCE
+ }
+
+ return false
+}
+
+void function TitanAchievementTracking_SP( entity titan, var damageInfo )
+{
+ entity player = DamageInfo_GetAttacker( damageInfo )
+
+ if ( !titan.IsTitan() )
+ return
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( !player.IsPlayer() )
+ return
+
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ switch ( damageSourceId )
+ {
+ case eDamageSourceId.mp_titancore_salvo_core:
+ case eDamageSourceId.mp_titancore_laser_cannon:
+ case eDamageSourceId.mp_titancore_flame_wave:
+ case eDamageSourceId.mp_titancore_flame_wave_secondary:
+ case eDamageSourceId.mp_titancore_shift_core:
+ case eDamageSourceId.mp_titanweapon_flightcore_rockets:
+ case eDamageSourceId.mp_titancore_amp_core:
+ file.coreKillCounter++
+ break
+ case eDamageSourceId.mp_titanweapon_predator_cannon:
+ array<string> weaponMods = GetWeaponModsFromDamageInfo( damageInfo )
+ if ( weaponMods.contains( "Smart_Core" ) )
+ {
+ file.coreKillCounter++
+ }
+ break
+ #if HAS_BOSS_AI
+ case eDamageSourceId.titan_execution:
+ if ( IsMercTitan( titan ) )
+ {
+ UnlockAchievement( player, achievements.EXECUTE_BOSS )
+ }
+ break
+ #endif
+ }
+
+ if ( file.coreKillCounter >= 3 )
+ {
+ UnlockAchievement( player, achievements.CORE_MULTIKILL )
+ }
+
+ if ( !player.IsTitan() )
+ {
+ UnlockAchievement( player, achievements.PILOT_TITANKILL )
+ }
+
+ // don't count vortex refire for core kills
+ int scriptDamageType = DamageInfo_GetCustomDamageType( damageInfo )
+ if ( scriptDamageType & DF_VORTEX_REFIRE )
+ return
+
+ switch ( damageSourceId )
+ {
+ case eDamageSourceId.mp_titancore_salvo_core:
+ UnlockAchievement( player, achievements.CORE_SALVO )
+ break
+ case eDamageSourceId.mp_titancore_laser_cannon:
+ UnlockAchievement( player, achievements.CORE_LASER )
+ break
+ case eDamageSourceId.mp_titancore_flame_wave:
+ case eDamageSourceId.mp_titancore_flame_wave_secondary:
+ UnlockAchievement( player, achievements.CORE_FLAME )
+ break
+ case eDamageSourceId.mp_titancore_shift_core:
+ UnlockAchievement( player, achievements.CORE_SWORD )
+ break
+ case eDamageSourceId.mp_titanweapon_flightcore_rockets:
+ UnlockAchievement( player, achievements.CORE_FLIGHT )
+ break
+ case eDamageSourceId.mp_titancore_amp_core:
+ UnlockAchievement( player, achievements.CORE_BURST )
+ break
+ case eDamageSourceId.mp_titanweapon_predator_cannon:
+ array<string> weaponMods = GetWeaponModsFromDamageInfo( damageInfo )
+ if ( weaponMods.contains( "Smart_Core" ) )
+ {
+ UnlockAchievement( player, achievements.CORE_SMART )
+ }
+ break
+ }
+}
+
+// this gets called whenever a core is started
+void function ResetCoreKillCounter()
+{
+ file.coreKillCounter = 0
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_titan_tether.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_titan_tether.gnut
new file mode 100644
index 000000000..b088651ab
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_titan_tether.gnut
@@ -0,0 +1,307 @@
+global function TitanTether_Init
+global function AddTitanTether
+global function TetherFlyIn
+global function PROTO_GetActiveTethers
+global function CodeCallback_OnTetherRemove
+global function CodeCallback_OnTetherDamageMilestone
+global function AddOnTetherCallback
+
+struct TetherData
+{
+ entity owner
+ entity[2] endpointEnts
+ array<entity> tetherEnts = []
+ entity anchor
+ entity endEntForPlayer
+ entity endEntForOthers
+ int teamNum
+ int codeTetherID
+}
+
+
+struct
+{
+ array<TetherData> activeTitanTethers = []
+ array< void functionref( entity, entity ) > onTetherCallbacks = []
+} file
+
+void function TitanTether_Init()
+{
+ PrecacheImpactEffectTable( "exp_tether_trap" ) //Needs to match damagedef_fd_tether_trap
+}
+
+void function AddOnTetherCallback( void functionref( entity, entity ) callback )
+{
+ file.onTetherCallbacks.append( callback )
+}
+
+void function AddTitanTether( entity owner, entity startEnt, entity endEnt, array<entity> tetherEnts, entity anchor, entity tetherEndEntForPlayer, entity tetherEndEntForOthers, bool isExplosiveTether )
+{
+ //Run callbacks for tether trap activation.
+ foreach ( callback in file.onTetherCallbacks )
+ {
+ callback( owner, endEnt )
+ }
+
+ TetherData tetherData
+ tetherData.owner = owner
+
+ tetherData.teamNum = owner.GetTeam()
+
+ Assert( !startEnt.IsTitan() )
+ Assert( endEnt.IsTitan()|| IsSuperSpectre( endEnt ) )
+
+ if ( endEnt.IsTitan() || IsSuperSpectre( endEnt ) )
+ {
+ tetherData.codeTetherID = endEnt.AddTether( startEnt.GetOrigin() )
+ if ( owner.IsPlayer() )
+ EmitSoundOnEntityExceptToPlayer( startEnt, owner, "Wpn_TetherTrap_PopOpen_3p" )//Spring Sound
+ else
+ EmitSoundOnEntity( startEnt, "Wpn_TetherTrap_PopOpen_3p" )//Spring Sound
+
+ if ( endEnt.IsTitan() )
+ endEnt = endEnt.GetTitanSoul()
+ }
+
+ tetherData.endpointEnts[0] = startEnt
+ tetherData.endpointEnts[1] = endEnt
+
+ tetherData.tetherEnts = tetherEnts
+
+ tetherData.anchor = anchor
+ tetherData.endEntForPlayer = tetherEndEntForPlayer
+ tetherData.endEntForOthers = tetherEndEntForOthers
+
+ file.activeTitanTethers.append( tetherData )
+
+ thread TetherCleanup( owner, startEnt, endEnt, tetherData, isExplosiveTether )
+}
+
+void function TetherCleanup( entity owner, entity startEnt, entity endEnt, TetherData tetherData, bool isExplosiveTether )
+{
+ startEnt.EndSignal( "OnDestroy" )
+ endEnt.EndSignal( "OnDestroy" )
+ endEnt.EndSignal( "OnSyncedMelee" )
+
+ int tetherID = tetherData.codeTetherID
+// int statusEffectId = StatusEffect_AddEndless( endEnt, eStatusEffect.tethered, 1.0 )
+ int statusEffectId = StatusEffect_AddTimed( endEnt, eStatusEffect.tethered, 1.0, 5.0, 0.0 )
+
+ vector anchorOrigin = tetherData.anchor.GetOrigin()
+
+ OnThreadEnd(
+ function() : ( owner, anchorOrigin, endEnt, tetherID, statusEffectId, isExplosiveTether )
+ {
+ if ( isExplosiveTether && IsValid( owner ) && IsValid( endEnt ) )
+ {
+ Explosion_DamageDefSimple( damagedef_fd_tether_trap, anchorOrigin,owner, owner, anchorOrigin + < 0, 0, 32 > )
+ }
+
+ foreach ( index, tetherData in file.activeTitanTethers )
+ {
+ if ( tetherData.codeTetherID == tetherID )
+ {
+ thread TitanTether_Remove( tetherData )
+ break
+ }
+ }
+
+ if ( IsValid( endEnt ) )
+ StatusEffect_Stop( endEnt, statusEffectId )
+ }
+ )
+
+ WaitForever()
+}
+
+void function TetherFlyIn( entity flyFrom, entity flyTo, entity rope, entity owner )
+{
+ flyTo.EndSignal( "OnDestroy" )
+ vector destLocal = flyTo.GetLocalOrigin()
+ flyTo.SetAbsOrigin( flyFrom.GetOrigin() )
+ flyTo.NonPhysicsMoveInWorldSpaceToLocalPos( destLocal, 0.3, 0, 0 )
+ wait 0.3
+ if ( IsValid( owner ) && owner.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( flyTo, owner, "Weapon_TetherGun_Attach_1P_VS_3P" )
+ EmitSoundOnEntityExceptToPlayer( flyTo, owner, "Weapon_TetherGun_Attach_3P_VS_3P" )
+ }
+ else
+ {
+ EmitSoundOnEntity( flyTo, "Weapon_TetherGun_Attach_3P_VS_3P" )
+ }
+}
+
+
+void function TitanTether_Remove( TetherData tetherData )
+{
+ entity endEnt = tetherData.endpointEnts[1]
+ if ( IsValid( endEnt ) )
+ {
+ if ( IsSoul( endEnt ) )
+ endEnt = endEnt.GetTitan()
+
+ if ( endEnt.IsValidTetherID( tetherData.codeTetherID ) )
+ endEnt.RemoveTether( tetherData.codeTetherID )
+ }
+
+ vector angvel = < RandomFloatRange( 50, 1000 ), RandomFloatRange( -200, 200 ), RandomFloatRange( -200, 200 )>
+
+ vector velForPlayer
+ vector velForOthers
+ vector rotaxis
+ float rotspeed
+ if ( IsValid( endEnt ) )
+ {
+ vector forward = endEnt.GetPlayerOrNPCViewVector()
+ velForPlayer = forward * 200
+
+ rotaxis = endEnt.GetPlayerOrNPCViewRight()
+ rotaxis += forward * RandomFloatRange( -0.4, 0.4 )
+ rotaxis += endEnt.GetPlayerOrNPCViewUp() * RandomFloatRange( -0.4, 0.4 )
+ rotspeed = RandomFloatRange( -2000, -1000 )
+ }
+ else
+ {
+ rotaxis = RandomVec( 1 )
+ rotspeed = RandomFloatRange( -2000, 2000 )
+ }
+
+ vector pullDirForPlayer
+ vector pullDirForOthers
+
+ bool endEntForPlayerIsValid = IsValid( tetherData.endEntForPlayer )
+ bool endEntForOthersIsValid = IsValid( tetherData.endEntForOthers )
+
+ if ( IsValid( tetherData.anchor ) )
+ {
+ if ( endEntForPlayerIsValid )
+ pullDirForPlayer = Normalize( tetherData.anchor.GetOrigin() - tetherData.endEntForPlayer.GetOrigin() )
+ if ( endEntForOthersIsValid )
+ pullDirForOthers = Normalize( tetherData.anchor.GetOrigin() - tetherData.endEntForOthers.GetOrigin() )
+ }
+
+ velForPlayer += < RandomFloatRange(-100,100), RandomFloatRange(-100,100), 0> + pullDirForPlayer * 100
+ float pullDirForOthersZ = pullDirForOthers.z
+ pullDirForOthers.z = 0
+ pullDirForOthers += < RandomFloatRange(-1,1), RandomFloatRange(-1,1), 0>
+ velForOthers = Normalize( pullDirForOthers )
+ velForOthers.x *= RandomFloatRange( 100, 200 )
+ velForOthers.y *= RandomFloatRange( 100, 200 )
+ velForOthers.z = pullDirForOthersZ * 200
+
+ rotaxis = Normalize( rotaxis )
+
+ // Since we're unparenting the tethers, we need to change how they control who they're visible to
+ if ( IsValid( endEnt ) )
+ {
+ if ( endEntForPlayerIsValid )
+ {
+ tetherData.endEntForPlayer.kv.VisibilityFlags = ENTITY_VISIBLE_TO_OWNER
+ tetherData.endEntForPlayer.SetOwner( endEnt )
+ }
+ if ( endEntForOthersIsValid )
+ {
+ tetherData.endEntForOthers.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY
+ tetherData.endEntForOthers.SetOwner( endEnt )
+ }
+ }
+ else
+ {
+ if ( endEntForPlayerIsValid )
+ tetherData.endEntForPlayer.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY
+ if ( endEntForOthersIsValid )
+ tetherData.endEntForOthers.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ }
+
+ if ( endEntForPlayerIsValid )
+ {
+ tetherData.endEntForPlayer.ClearParent()
+ tetherData.endEntForPlayer.NonPhysicsRotate( rotaxis, rotspeed )
+ tetherData.endEntForPlayer.NonPhysicsMoveWithGravity( velForPlayer, < 0, 0, -750> )
+ tetherData.endEntForPlayer.RenderWithViewModels( false )
+ tetherData.endEntForPlayer.Dissolve( ENTITY_DISSOLVE_NORMAL, <0,0,0>, 100 )
+ }
+
+ if ( endEntForOthersIsValid )
+ {
+ tetherData.endEntForOthers.ClearParent()
+ tetherData.endEntForOthers.NonPhysicsRotate( rotaxis, rotspeed )
+ tetherData.endEntForOthers.NonPhysicsMoveWithGravity( velForOthers, < 0, 0, -750> )
+ tetherData.endEntForOthers.Dissolve( ENTITY_DISSOLVE_NORMAL, <0,0,0>, 100 )
+ }
+
+ wait 0.5
+
+ foreach ( index, ent in tetherData.endpointEnts )
+ {
+ if ( !IsValid( ent ) )
+ continue
+
+ if ( ent instanceof CBaseGrenade )
+ ent.Destroy()
+ }
+
+ foreach ( entity ent in tetherData.tetherEnts )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+}
+
+
+int function PROTO_GetActiveTethers( entity owner )
+{
+// _PruneActiveTitanTethers()
+
+ int activeTethers = 0
+ foreach ( TetherData tetherData in file.activeTitanTethers )
+ {
+ if ( tetherData.owner == owner )
+ activeTethers++
+ }
+
+ return activeTethers
+}
+
+
+bool removingTether
+
+void function CodeCallback_OnTetherRemove( entity guy, int tetherID )
+{
+ Assert( !removingTether )
+ removingTether = true
+
+ foreach ( index, tetherData in file.activeTitanTethers )
+ {
+ if ( tetherData.codeTetherID == tetherID )
+ {
+ thread TitanTether_Remove( tetherData )
+ file.activeTitanTethers.fastremove( index )
+ break
+ }
+ }
+
+ removingTether = false
+}
+
+TetherData function GetTetherDataForCodeID( int codeTetherID )
+{
+ foreach ( index, tetherData in file.activeTitanTethers )
+ {
+ if ( tetherData.codeTetherID == codeTetherID )
+ return tetherData
+ }
+
+ unreachable
+}
+
+void function CodeCallback_OnTetherDamageMilestone( entity guy, int tetherID, int damageMilestoneIndex, float health )
+{
+ float healthFrac = 1.0 - health / 1000.0
+
+ TetherData tetherData = GetTetherDataForCodeID( tetherID )
+
+ vector ang = tetherData.endEntForPlayer.GetLocalAngles()
+ tetherData.endEntForPlayer.SetLocalAngles( < healthFrac * 45, ang.y, ang.z> )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_titan_transfer.nut b/Northstar.CustomServers/scripts/vscripts/mp/_titan_transfer.nut
new file mode 100644
index 000000000..7b126cd0c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_titan_transfer.nut
@@ -0,0 +1,641 @@
+untyped
+
+global function TitanTransfer_Init
+
+global function PilotBecomesTitan
+global function TitanBecomesPilot
+global function CreateAutoTitanForPlayer_ForTitanBecomesPilot
+global function CreateAutoTitanForPlayer_FromTitanLoadout
+global function CopyWeapons
+global function StorePilotWeapons
+global function RetrievePilotWeapons
+global function SetTitanSettings
+
+global function StoreWeapons
+global function GiveWeaponsFromStoredArray
+
+global function TitanCoreEffectTransfer_threaded
+global function ForceTitanSustainedDischargeEnd
+
+function TitanTransfer_Init()
+{
+ // these vars transfer from player titan to npc titan and vice versa
+
+ RegisterSignal( "PlayerEmbarkedTitan" )
+ RegisterSignal( "PlayerDisembarkedTitan" )
+
+ AddSoulTransferFunc( TitanCoreEffectTransfer )
+ AddCallback_OnTitanBecomesPilot( OnClassChangeBecomePilot )
+ AddCallback_OnPilotBecomesTitan( OnClassChangeBecomeTitan )
+}
+
+void function TitanCoreEffectTransfer( entity soul, entity titan, entity oldTitan )
+{
+ thread TitanCoreEffectTransfer_threaded( soul, titan, oldTitan )
+}
+function TitanCoreEffectTransfer_threaded( entity soul, entity titan, entity oldTitan )
+{
+ WaitEndFrame() // because the titan aint a titan yet
+
+ if ( !IsValid( soul ) || !IsValid( titan ) )
+ return
+
+ if ( !( "coreEffect" in soul.s ) )
+ return
+
+ soul.s.coreEffect.ent.Kill_Deprecated_UseDestroyInstead()
+ soul.s.coreEffect.ent = soul.s.coreEffect.func( titan, soul.s.coreEffect.parameter )
+}
+
+void function OnClassChangeBecomePilot( entity player, entity titan ) //Stuff here used to be in old CPlayer:OnChangedPlayerClass, for turning into Pilot class
+{
+ player.ClearDoomed()
+ player.UnsetUsable()
+ player.lastTitanTime = Time()
+
+ player.Minimap_SetHeightTracking( true )
+ ResetTitanBuildTime( player )
+ RandomizeHead( player )
+}
+
+void function OnClassChangeBecomeTitan( entity player, entity titan ) //Stuff here used to be in old CPlayer:OnChangedPlayerClass, for turning into Titan class
+{
+ CodeCallback_PlayerInTitanCockpit( player, player )
+ player.Minimap_SetHeightTracking( false )
+ ResetTitanBuildTime( player )
+}
+
+function CopyWeapons( entity fromEnt, entity toEnt )
+{
+ entity activeWeapon = fromEnt.GetActiveWeapon()
+ if ( IsValid( activeWeapon ) )
+ {
+ if ( activeWeapon.IsWeaponOffhand() )
+ fromEnt.ClearOffhand()
+ }
+
+ array<entity> weapons = fromEnt.GetMainWeapons()
+ foreach ( weapon in weapons )
+ {
+ entity giveWeapon = fromEnt.TakeWeapon_NoDelete( weapon.GetWeaponClassName() )
+ toEnt.GiveExistingWeapon( giveWeapon )
+ }
+
+ for ( int i = 0; i < OFFHAND_COUNT; i++ )
+ {
+ entity offhandWeapon
+ offhandWeapon = fromEnt.TakeOffhandWeapon_NoDelete( i )
+
+ // maintain offhand index
+ if ( offhandWeapon )
+ toEnt.GiveExistingOffhandWeapon( offhandWeapon, i )
+ }
+
+ if ( activeWeapon )
+ {
+ string name = activeWeapon.GetWeaponClassName()
+ toEnt.SetActiveWeaponByName( name )
+ }
+}
+
+array<StoredWeapon> function StoreWeapons( entity player )
+{
+ array<StoredWeapon> storedWeapons
+
+ entity activeWeapon = player.GetActiveWeapon()
+
+ array<entity> mainWeapons = player.GetMainWeapons()
+
+ foreach ( i, weapon in mainWeapons )
+ {
+ StoredWeapon sw
+
+ if ( weapon.GetScriptFlags0() & WEAPONFLAG_AMPED )
+ {
+ weapon.Signal( "StopAmpedWeapons" )
+ }
+
+ sw.name = weapon.GetWeaponClassName()
+ sw.weaponType = eStoredWeaponType.main
+ sw.activeWeapon = ( weapon == activeWeapon )
+ sw.inventoryIndex = i
+ sw.mods = weapon.GetMods()
+ sw.modBitfield = weapon.GetModBitField()
+ sw.ammoCount = weapon.GetWeaponPrimaryAmmoCount()
+ sw.clipCount = weapon.GetWeaponPrimaryClipCount()
+ sw.nextAttackTime = weapon.GetNextAttackAllowedTime()
+ sw.skinIndex = weapon.GetSkin()
+ sw.camoIndex = weapon.GetCamo()
+ sw.isProScreenOwner = weapon.GetProScreenOwner() == player
+
+ storedWeapons.append( sw )
+ }
+
+ array<entity> offhandWeapons = player.GetOffhandWeapons()
+
+ foreach ( weapon in offhandWeapons )
+ {
+ StoredWeapon sw
+
+ sw.name = weapon.GetWeaponClassName()
+ sw.weaponType = eStoredWeaponType.offhand
+ sw.activeWeapon = ( weapon == activeWeapon )
+ sw.inventoryIndex = weapon.GetInventoryIndex()
+ sw.mods = weapon.GetMods()
+ sw.ammoCount = weapon.GetWeaponPrimaryAmmoCount()
+ sw.clipCount = weapon.GetWeaponPrimaryClipCount()
+ sw.nextAttackTime = weapon.GetNextAttackAllowedTime()
+ #if MP
+ sw.burnReward = weapon.e.burnReward
+ #endif
+
+ if ( sw.activeWeapon )
+ storedWeapons.insert( 0, sw )
+ else
+ storedWeapons.append( sw )
+ }
+
+ return storedWeapons
+}
+
+void function GiveWeaponsFromStoredArray( entity player, array<StoredWeapon> storedWeapons )
+{
+ int activeWeaponSlot = 0
+ foreach ( i, storedWeapon in storedWeapons )
+ {
+ entity weapon
+
+ switch ( storedWeapon.weaponType )
+ {
+ case eStoredWeaponType.main:
+ weapon = player.GiveWeapon( storedWeapon.name, storedWeapon.mods )
+ weapon.SetWeaponSkin( storedWeapon.skinIndex )
+ weapon.SetWeaponCamo( storedWeapon.camoIndex )
+ #if MP
+ if ( storedWeapon.isProScreenOwner )
+ {
+ weapon.SetProScreenOwner( player )
+ UpdateProScreen( player, weapon )
+ }
+
+ string weaponCategory = GetWeaponInfoFileKeyField_GlobalString( weapon.GetWeaponClassName(), "menu_category" )
+ if ( weaponCategory == "at" || weaponCategory == "special" ) // refill AT/grenadier ammo stockpile
+ {
+ int defaultTotal = weapon.GetWeaponSettingInt( eWeaponVar.ammo_default_total )
+ int clipSize = weapon.GetWeaponSettingInt( eWeaponVar.ammo_clip_size )
+
+ weapon.SetWeaponPrimaryAmmoCount( defaultTotal - clipSize )
+
+ if ( weapon.GetWeaponPrimaryClipCountMax() > 0 )
+ weapon.SetWeaponPrimaryClipCount( storedWeapon.clipCount )
+ }
+ else
+ {
+ weapon.SetWeaponPrimaryAmmoCount( storedWeapon.ammoCount )
+ if ( weapon.GetWeaponPrimaryClipCountMax() > 0 )
+ weapon.SetWeaponPrimaryClipCount( storedWeapon.clipCount )
+ }
+ #else
+ weapon.SetWeaponPrimaryAmmoCount( storedWeapon.ammoCount )
+ if ( weapon.GetWeaponPrimaryClipCountMax() > 0 )
+ weapon.SetWeaponPrimaryClipCount( storedWeapon.clipCount )
+ #endif
+
+
+ if ( storedWeapon.activeWeapon )
+ activeWeaponSlot = i
+
+ break
+
+ case eStoredWeaponType.offhand:
+ player.GiveOffhandWeapon( storedWeapon.name, storedWeapon.inventoryIndex, storedWeapon.mods )
+
+ weapon = player.GetOffhandWeapon( storedWeapon.inventoryIndex )
+ weapon.SetNextAttackAllowedTime( storedWeapon.nextAttackTime )
+ weapon.SetWeaponPrimaryAmmoCount( storedWeapon.ammoCount )
+ if ( weapon.GetWeaponPrimaryClipCountMax() > 0 )
+ weapon.SetWeaponPrimaryClipCount( storedWeapon.clipCount )
+ #if MP
+ weapon.e.burnReward = storedWeapon.burnReward
+ #endif
+
+ break
+
+ default:
+ unreachable
+ }
+ }
+
+ #if MP
+ PlayerInventory_RefreshEquippedState( player )
+ #endif
+
+ player.SetActiveWeaponBySlot( activeWeaponSlot )
+}
+
+void function RetrievePilotWeapons( entity player )
+{
+ TakeAllWeapons( player )
+ GiveWeaponsFromStoredArray( player, player.p.storedWeapons )
+ SetPlayerCooldowns( player )
+ player.p.storedWeapons.clear()
+}
+
+function StorePilotWeapons( entity player )
+{
+ player.p.storedWeapons = StoreWeapons( player )
+ StoreOffhandData( player, false )
+ TakeAllWeapons( player )
+}
+
+function TransferHealth( srcEnt, destEnt )
+{
+ destEnt.SetMaxHealth( srcEnt.GetMaxHealth() )
+ destEnt.SetHealth( srcEnt.GetHealth() )
+ //destEnt.SetHealthPerSegment( srcEnt.GetHealthPerSegment() )
+}
+
+entity function CreateAutoTitanForPlayer_FromTitanLoadout( entity player, TitanLoadoutDef loadout, vector origin, vector angles )
+{
+ int team = player.GetTeam()
+
+ player.titansBuilt++
+ ResetTitanBuildTime( player )
+
+ entity npcTitan = CreateNPCTitan( loadout.setFile, team, origin, angles, loadout.setFileMods )
+ SetTitanSpawnOptionsFromLoadout( npcTitan, loadout )
+ SetSpawnOption_OwnerPlayer( npcTitan, player )
+
+ if ( IsSingleplayer() )
+ {
+ npcTitan.EnableNPCFlag( NPC_IGNORE_FRIENDLY_SOUND )
+ }
+ #if MP
+ string titanRef = GetTitanCharacterNameFromSetFile( loadout.setFile )
+ npcTitan.SetTargetInfoIcon( GetTitanCoreIcon( titanRef ) )
+ #endif
+
+ return npcTitan
+}
+
+entity function CreateAutoTitanForPlayer_ForTitanBecomesPilot( entity player, bool hidden = false )
+{
+ vector origin = player.GetOrigin()
+ vector angles = player.GetAngles()
+ TitanLoadoutDef loadout = GetTitanLoadoutFromPlayerInventory( player )
+
+ int team = player.GetTeam()
+ entity npcTitan = CreateNPCTitan( loadout.setFile, team, origin, angles, loadout.setFileMods )
+ npcTitan.s.spawnWithoutSoul <- true
+ SetTitanSpawnOptionsFromLoadout( npcTitan, loadout )
+ SetSpawnOption_OwnerPlayer( npcTitan, player )
+ npcTitan.SetSkin( player.GetSkin() )
+ npcTitan.SetCamo( player.GetCamo() )
+ npcTitan.SetDecal( player.GetDecal() )
+
+ if ( IsSingleplayer() )
+ npcTitan.EnableNPCFlag( NPC_IGNORE_FRIENDLY_SOUND )
+
+ return npcTitan
+}
+
+void function SetTitanSpawnOptionsFromLoadout( entity titan, TitanLoadoutDef loadout )
+{
+ titan.ai.titanSpawnLoadout = loadout
+}
+
+TitanLoadoutDef function GetTitanLoadoutFromPlayerInventory( entity player ) //TODO: Examine necessity for this? Was needed in R1 where TItans could pick up weapons off the ground, but may not be needed in R2 anymore. Might just be fine to call GetTitanLoadoutForPlayer()
+{
+ TitanLoadoutDef loadout = GetTitanLoadoutForPlayer( player )
+ loadout.setFile = player.GetPlayerSettings()
+ loadout.setFileMods = UntypedArrayToStringArray( player.GetPlayerSettingsMods() )
+
+ array mainWeapons = player.GetMainWeapons()
+ if ( mainWeapons.len() )
+ {
+ entity wep = player.GetMainWeapons()[0]
+ loadout.primary = wep.GetWeaponClassName()
+ loadout.primaryMods = wep.GetMods()
+ }
+
+ entity ord = player.GetOffhandWeapon(OFFHAND_ORDNANCE)
+ if ( ord )
+ {
+ loadout.ordnance = ord.GetWeaponClassName()
+ loadout.ordnanceMods = ord.GetMods()
+ }
+
+ entity tac = player.GetOffhandWeapon(OFFHAND_SPECIAL)
+ if ( tac )
+ {
+ loadout.special = tac.GetWeaponClassName()
+ loadout.specialMods = tac.GetMods()
+ }
+
+ entity antirodeo = player.GetOffhandWeapon(OFFHAND_ANTIRODEO)
+ if ( antirodeo )
+ {
+ loadout.antirodeo = antirodeo.GetWeaponClassName()
+ loadout.antirodeoMods = antirodeo.GetMods()
+ }
+
+ entity melee = player.GetMeleeWeapon()
+ if ( melee )
+ loadout.melee = melee.GetWeaponClassName()
+
+ return loadout
+}
+
+void function ForceTitanSustainedDischargeEnd( entity player )
+{
+ // To disable core's while disembarking
+ local weapons = player.GetOffhandWeapons()
+ foreach ( weapon in weapons )
+ {
+ if ( weapon.IsChargeWeapon() )
+ weapon.ForceChargeEndNoAttack()
+
+ if ( weapon.IsSustainedDischargeWeapon() && weapon.IsDischarging() )
+ weapon.ForceSustainedDischargeEnd();
+ }
+}
+
+function TitanBecomesPilot( entity player, entity titan )
+{
+ Assert( IsAlive( player ), player + ": Player is not alive" )
+ Assert( player.IsTitan(), player + " is not a titan" )
+
+ Assert( IsAlive( titan ), titan + " is not alive." )
+ Assert( titan.IsTitan(), titan + " is not alive." )
+
+ asset model = player.GetModelName()
+ int skin = player.GetSkin()
+ int camo = player.GetCamo()
+ int decal = player.GetDecal()
+ titan.SetModel( model )
+ titan.SetSkin( skin )
+ titan.SetCamo( camo )
+ titan.SetDecal( decal )
+ titan.SetPoseParametersSameAs( player )
+ titan.SequenceTransitionFromEntity( player )
+
+ ForceTitanSustainedDischargeEnd( player )
+
+ TransferHealth( player, titan )
+ //Transfer children before player becomes pilot model
+ player.TransferChildrenTo( titan )
+ player.TransferTethersToEntity( titan )
+ entity soul = player.GetTitanSoul()
+ SetSoulOwner( soul, titan )
+ Assert( player.GetTitanSoul() == null )
+
+ // this must happen before changing the players settings
+ TransferDamageStates( player, titan )
+
+ // cant have a titan passive when you're not a titan
+ TakeAllTitanPassives( player )
+
+ player.SetPlayerSettingsWithMods( player.s.storedPlayerSettings, player.s.storedPlayerSettingsMods )
+ player.SetPlayerSettingPosMods( PLAYERPOSE_STANDING, player.s.storedPlayerStandMods )
+ player.SetPlayerSettingPosMods( PLAYERPOSE_CROUCHING, player.s.storedPlayerCrouchMods )
+ player.SetSkin( player.s.storedPlayerSkinIndex )
+ player.SetCamo( player.s.storedPlayerCamoIndex )
+
+ delete player.s.storedPlayerSettings
+ delete player.s.storedPlayerSettingsMods
+ delete player.s.storedPlayerStandMods
+ delete player.s.storedPlayerCrouchMods
+ delete player.s.storedPlayerSkinIndex
+ delete player.s.storedPlayerCamoIndex
+
+ TakeAllWeapons( titan )
+ CopyWeapons( player, titan )
+
+ player.SetTitle( "" )
+
+ RetrievePilotWeapons( player )
+
+ if ( Riff_AmmoLimit() != eAmmoLimit.Default )
+ {
+ switch ( Riff_AmmoLimit() )
+ {
+ case eAmmoLimit.Limited:
+ local weapons = player.GetMainWeapons()
+ foreach ( weapon in weapons )
+ {
+ local clipAmmo = player.GetWeaponAmmoMaxLoaded( weapon )
+
+ if ( clipAmmo > 0 )
+ weapon.SetWeaponPrimaryAmmoCount( clipAmmo * 2 )
+ }
+
+ local offhand = player.GetOffhandWeapon( 0 )
+ if ( offhand )
+ {
+ local ammoLoaded = player.GetWeaponAmmoMaxLoaded( offhand )
+ offhand.SetWeaponPrimaryClipCount( max( 1, ammoLoaded - 2 ) )
+ }
+ break
+ }
+ }
+
+ // Added via AddCallback_OnTitanBecomesPilot
+ foreach ( callbackFunc in svGlobal.onTitanBecomesPilotCallbacks )
+ {
+ callbackFunc( player, titan )
+ }
+
+ // Ensure rodeo doesn't happen straight away, if a nearby Titan runs by
+ Rodeo_SetCooldown( player )
+
+ if ( player.cloakedForever )
+ {
+ // infinite cloak active
+ EnableCloakForever( player )
+ }
+ if ( player.stimmedForever )
+ {
+ StimPlayer( player, USE_TIME_INFINITE )
+ }
+
+ soul.Signal( "PlayerDisembarkedTitan", { player = player } )
+
+ // no longer owned
+ if ( soul.capturable )
+ {
+ soul.ClearBossPlayer()
+ titan.ClearBossPlayer()
+ titan.SetUsableByGroup( "friendlies pilot" )
+ titan.DisableBehavior( "Follow" )
+ player.SetPetTitan( null )
+ if ( !player.s.savedTitanBuildTimer )
+ ResetTitanBuildTime( player )
+ else
+ player.SetNextTitanRespawnAvailable( player.s.savedTitanBuildTimer )
+ return
+ }
+ return titan
+}
+
+function PilotBecomesTitan( entity player, entity titan, bool fullCopy = true )
+{
+ player.SetPetTitan( null )
+
+ // puts the weapons into a table
+ StorePilotWeapons( player )
+
+ #if HAS_TITAN_WEAPON_SWAPPING
+ {
+ foreach ( weapon in titan.GetMainWeapons() )
+ {
+ // the pilot's weapons will recent entirely in sp if this doesn't match
+ player.p.lastPrimaryWeaponEnt = weapon
+ break
+ }
+ }
+ #endif
+
+ if ( fullCopy )
+ {
+ CopyWeapons( titan, player )
+ }
+
+ //Should only be the first time a player embarks into a Titan that Titan's life.
+ //Check with differ if there is a better way than .e.var on the soul.
+ //TitanLoadoutDef loadout = GetTitanLoadoutForPlayer( player )
+ //PROTO_DisplayTitanLoadouts( player, titan, loadout )
+
+ entity soul = titan.GetTitanSoul()
+ soul.soul.lastOwner = player
+
+ player.s.storedPlayerSettings <- player.GetPlayerSettings()
+ player.s.storedPlayerSettingsMods <- player.GetPlayerSettingsMods()
+ player.s.storedPlayerStandMods <- player.GetPlayerModsForPos( PLAYERPOSE_STANDING )
+ player.s.storedPlayerCrouchMods <- player.GetPlayerModsForPos( PLAYERPOSE_CROUCHING )
+ player.s.storedPlayerSkinIndex <- player.GetSkin()
+ player.s.storedPlayerCamoIndex <- player.GetCamo()
+ printt( player.GetSkin(), player.GetCamo() )
+
+ string settings = GetSoulPlayerSettings( soul )
+ var titanTint = Dev_GetPlayerSettingByKeyField_Global( settings, "titan_tint" )
+
+ if ( titanTint != null )
+ {
+ expect string( titanTint )
+ Highlight_SetEnemyHighlight( player, titanTint )
+ }
+ else
+ {
+ Highlight_ClearEnemyHighlight( player )
+ }
+
+ if ( !player.GetParent() )
+ {
+ player.SetOrigin( titan.GetOrigin() )
+ player.SetAngles( titan.GetAngles() )
+ player.SetVelocity( Vector( 0,0,0 ) )
+ }
+
+ if ( soul.capturable )
+ {
+ printt( player.GetPetTitan(), player.GetNextTitanRespawnAvailable() )
+ if ( IsValid( player.GetPetTitan() ) || player.s.replacementDropInProgress )
+ player.s.savedTitanBuildTimer <- null
+ else
+ player.s.savedTitanBuildTimer <- player.GetNextTitanRespawnAvailable()
+
+ if ( GameRules_GetGameMode() == "ctt" )
+ {
+ titan.Minimap_AlwaysShow( 0, null )
+ player.Minimap_AlwaysShow( TEAM_IMC, null )
+ player.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ player.SetHudInfoVisibilityTestAlwaysPasses( true )
+ }
+ }
+
+ SetSoulOwner( soul, player )
+
+ if ( soul.GetBossPlayer() != player )
+ SoulBecomesOwnedByPlayer( soul, player )
+
+ foreach ( int passive, _ in level.titanPassives )
+ {
+ if ( SoulHasPassive( soul, passive ) )
+ {
+ GiveTitanPassiveLifeLong( player, passive )
+ }
+ }
+
+ asset model = titan.GetModelName()
+ int skin = titan.GetSkin()
+ int camo = titan.GetCamo()
+ int decal = titan.GetDecal()
+ TitanSettings titanSettings = titan.ai.titanSettings
+ array<string> mods = titanSettings.titanSetFileMods
+
+ player.SetPlayerSettingsFromDataTable( { playerSetFile = titanSettings.titanSetFile, playerSetFileMods = mods } )
+ var title = GetPlayerSettingsFieldForClassName( settings, "printname" )
+
+ if ( title != null )
+ {
+ player.SetTitle( expect string( title ) )
+ }
+
+ if ( IsAlive( player ) )
+ TransferHealth( titan, player )
+
+ player.SetModel( model )
+ player.SetSkin( skin )
+ player.SetCamo( camo )
+ player.SetDecal( decal )
+ player.SetPoseParametersSameAs( titan )
+ player.SequenceTransitionFromEntity( titan )
+
+ // no cloak titan
+ player.SetCloakDuration( 0, 0, 0 )
+
+ // this must happen after changing the players settings
+ TransferDamageStates( titan, player )
+
+ titan.TransferTethersToEntity( player )
+
+ //We parent the player to the titan in the process of embarking
+ //Must clear parent when transfering children to avoid parenting the player to himself
+ if ( player.GetParent() == titan )
+ player.ClearParent()
+ //Transfer children after player has become titan model.
+ titan.TransferChildrenTo( player )
+
+ player.SetOrigin( titan.GetOrigin() )
+ player.SetAngles( titan.GetAngles() )
+ player.SetVelocity( Vector( 0,0,0 ) )
+
+ soul.e.embarkCount++
+ soul.Signal( "PlayerEmbarkedTitan", { player = player } )
+ player.Signal( "PlayerEmbarkedTitan", { titan = titan } )
+ titan.Signal( "TitanStopsThinking" )
+
+ // Added via AddCallback_OnPilotBecomesTitan
+ foreach ( callbackFunc in svGlobal.onPilotBecomesTitanCallbacks )
+ {
+ callbackFunc( player, titan )
+ }
+
+ #if DEV
+ thread Dev_CheckTitanIsDeletedAtEndOfPilotBecomesTitan( titan )
+ #endif
+}
+
+void function SetTitanSettings( TitanSettings titanSettings, string titanSetFile, array<string> mods = [] )
+{
+ Assert( titanSettings.titanSetFile == "", "Tried to set titan settings to " + titanSetFile + ", but its already set to " + titanSettings.titanSetFile )
+ titanSettings.titanSetFile = titanSetFile
+ titanSettings.titanSetFileMods = mods
+}
+
+void function Dev_CheckTitanIsDeletedAtEndOfPilotBecomesTitan( entity titan )
+{
+ WaitEndFrame()
+
+ Assert( !IsValid( titan ), "Titan should be deleted at end of PilotBecomesTitan" )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_tonecontroller.nut b/Northstar.CustomServers/scripts/vscripts/mp/_tonecontroller.nut
new file mode 100644
index 000000000..786eda23c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_tonecontroller.nut
@@ -0,0 +1,189 @@
+untyped
+
+global function ToneController_Init
+
+global function UpdateToneSettings
+
+global function SetAutoExposureMin
+global function SetAutoExposureMax
+global function SetAutoExposureCompensation
+global function SetAutoExposureCompensationBias
+global function SetAutoExposureRate
+global function UseDefaultAutoExposure
+global function SetBloomScale
+
+function ToneController_Init()
+{
+ level.toneController <- CreateEntity( "env_tonemap_controller" )
+ DispatchSpawn( level.toneController )
+
+ AddCallback_EntitiesDidLoad( UpdateToneSettings )
+}
+
+void function UpdateToneSettings()
+{
+ string mapName = GetMapName()
+
+ UseDefaultAutoExposure()
+
+ switch ( mapName )
+ {
+ case "mp_angel_city":
+ SetAutoExposureMin( 1.11 )
+ SetAutoExposureMax( 1.5 )
+ break;
+
+ case "mp_boneyard":
+ SetAutoExposureMin( 1.3 )
+ SetAutoExposureMax( 2.3 )
+ break;
+
+ case "mp_lagoon":
+ SetAutoExposureMin( 0.8 )
+ SetAutoExposureMax( 2.0 )
+ break;
+
+ case "mp_o2":
+ SetAutoExposureMin( 1.0 )
+ SetAutoExposureMax( 2.0 )
+ break;
+
+ case "mp_fracture":
+ SetAutoExposureMin( 1.25 )
+ SetAutoExposureMax( 4.0 )
+ break;
+
+ case "mp_training_ground":
+ SetAutoExposureMin( 0.6 )
+ SetAutoExposureMax( 2.5 )
+ break;
+
+ case "mp_relic":
+ SetAutoExposureMin( 0.9 )
+ SetAutoExposureMax( 2.0 )
+ break;
+
+ case "mp_smugglers_cove":
+ SetAutoExposureMin( 0.5 )
+ SetAutoExposureMax( 0.7 )
+ break;
+
+ case "mp_swampland":
+ SetAutoExposureMin( 0.5 )
+ SetAutoExposureMax( 0.8 )
+ break;
+
+ case "mp_runoff":
+ SetAutoExposureMin( 0.5 )
+ SetAutoExposureMax( 1.0 )
+ break;
+
+ case "mp_wargames":
+ SetAutoExposureMin( 1.0 )
+ SetAutoExposureMax( 1.75 )
+ break;
+
+ case "mp_harmony_mines":
+ SetAutoExposureMin( 1.0 )
+ SetAutoExposureMax( 1.75 )
+ break;
+
+ case "mp_switchback":
+ SetAutoExposureMin( 1.0 )
+ SetAutoExposureMax( 1.75 )
+ break;
+
+ case "mp_sandtrap":
+ SetAutoExposureMin( 0.5 )
+ SetAutoExposureMax( 1.15 )
+ break;
+
+ case "mp_taube_rock_photo_test":
+ SetAutoExposureMin( 1.2 )
+ SetAutoExposureMax( 2.0 )
+ SetBloomScale (1.0)
+ break;
+
+ case "mp_taube_forest_test":
+ SetAutoExposureMin( 1.2 )
+ SetAutoExposureMax( 2.0 )
+ SetBloomScale (1.0)
+ break;
+
+ case "mp_pbr_ball_test":
+ SetAutoExposureMin( 1.2 )
+ SetAutoExposureMax( 2.0 )
+ break;
+
+ case "mp_mendoko_taube_style":
+ SetBloomScale (1.0)
+ break;
+
+ case "mp_kodai_josh_style_01":
+ SetBloomScale (1.0)
+ break;
+
+ case "mp_fake_sky_taube_01":
+ SetBloomScale (1.0)
+ break;
+
+ case "sp_beacon_taube_style":
+ SetBloomScale (1.0)
+ break;
+
+ case "sp_trainer":
+ SetBloomScale( 0.2 )
+ SetAutoExposureMin( 0.8 )
+ SetAutoExposureMax( 0.8 )
+ break
+
+ case "sp_beacon":
+ SetAutoExposureMax( 5.0 )
+ break;
+
+ case "sp_beacon_spoke0":
+ SetAutoExposureMax( 5.0 )
+ break;
+
+ default:
+ UseDefaultAutoExposure()
+ break
+ }
+}
+
+
+
+function SetAutoExposureMin( float value )
+{
+ level.toneController.Fire( "SetAutoExposureMin", value )
+}
+
+function SetAutoExposureMax( float value )
+{
+ level.toneController.Fire( "SetAutoExposureMax", value )
+}
+
+function SetAutoExposureCompensation( float value )
+{
+ level.toneController.Fire( "SetAutoExposureCompensation", value )
+}
+
+function SetAutoExposureCompensationBias( float value )
+{
+ level.toneController.Fire( "SetAutoExposureCompensationBias", value )
+}
+
+function SetAutoExposureRate( float value )
+{
+ level.toneController.Fire( "SetAutoExposureRate", value )
+}
+
+function UseDefaultAutoExposure()
+{
+ level.toneController.Fire( "UseDefaultAutoExposure" )
+}
+
+function SetBloomScale( float value )
+{
+ level.toneController.Fire( "SetBloomScale", value )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_utility_mp.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_utility_mp.gnut
new file mode 100644
index 000000000..ea7d9d447
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_utility_mp.gnut
@@ -0,0 +1,18 @@
+global function Utility_MP_Init
+global function ClientCommand_OnDevnetBugScreenshot
+global function SafeForTitanFall
+
+void function Utility_MP_Init()
+{
+
+}
+
+bool function ClientCommand_OnDevnetBugScreenshot( entity player, array<string> args )
+{
+ return true
+}
+
+bool function SafeForTitanFall( vector dropPoint )
+{
+ return true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_vr.nut b/Northstar.CustomServers/scripts/vscripts/mp/_vr.nut
new file mode 100644
index 000000000..b9759ddf5
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_vr.nut
@@ -0,0 +1,66 @@
+untyped
+
+global function VR_Init
+global function VR_GroundTroopsDeathCallback
+
+struct {
+ string vr_settings = ""
+} file
+
+function VR_Init( string settings = "", bool enableDropships = false )
+{
+ if ( reloadingScripts )
+ return
+
+ if ( !enableDropships )
+ FlagSet( "DisableDropships" )
+
+ file.vr_settings = settings
+
+ //AddDeathCallback( "npc_soldier", VR_GroundTroopsDeathCallback )
+ //AddDeathCallback( "npc_spectre", VR_GroundTroopsDeathCallback )
+ //AddDeathCallback( "npc_marvin", VR_GroundTroopsDeathCallback )
+ //AddDeathCallback( "player", VR_GroundTroopsDeathCallback )
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+}
+
+void function EntitiesDidLoad()
+{
+ if ( file.vr_settings.find( "no_evac" ) != null )
+ svGlobal.evacEnabled = false
+
+ if ( file.vr_settings.find( "no_npc" ) != null )
+ {
+ disable_npcs()
+ }
+
+ if ( file.vr_settings.find( "no_titan" ) != null )
+ {
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ FlagSet( "PilotBot" )
+ }
+}
+
+void function VR_GroundTroopsDeathCallback( entity guy, var damageInfo )
+{
+ EmitSoundAtPosition( TEAM_UNASSIGNED, guy.GetOrigin(), "Object_Dissolve" )
+
+ if ( ShouldDoDissolveDeath( guy, damageInfo ) )
+ guy.Dissolve( ENTITY_DISSOLVE_CHAR, Vector( 0, 0, 0 ), 0 )
+}
+
+function ShouldDoDissolveDeath( guy, damageInfo )
+{
+ if ( !guy.IsPlayer() )
+ return true
+
+ // can't dissolve players when they're not playing the game, otherwise when the game starts again they're invisible
+ local gs = GetGameState()
+ if ( gs != eGameState.Playing && gs != eGameState.SuddenDeath && gs != eGameState.Epilogue )
+ {
+ printt( "Skipping player dissolve death because game is not active ( player:", guy, ")" )
+ return false
+ }
+
+ return true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/_lf_maps_shared.gnut b/Northstar.CustomServers/scripts/vscripts/mp/levels/_lf_maps_shared.gnut
new file mode 100644
index 000000000..69ec56fb5
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/_lf_maps_shared.gnut
@@ -0,0 +1,8 @@
+global function SetupLiveFireMaps
+
+// live fire maps don't support alot of things like intros and titans, this makes sure those things are disabled
+void function SetupLiveFireMaps()
+{
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+ ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, NOINTRO_INTRO_LENGTH )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_angel_city.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_angel_city.nut
new file mode 100644
index 000000000..3a5b637fa
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_angel_city.nut
@@ -0,0 +1,11 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+ Evac_AddLocation( < 2527.889893, -2865.360107, 753.002991 >, < 0, -80.54, 0 > )
+ Evac_AddLocation( < 1253.530029, -554.075012, 811.125 >, < 0, 180, 0 > )
+ Evac_AddLocation( < 2446.989990, 809.364014, 576.0 >, < 0, 90.253, 0 > )
+ Evac_AddLocation( < -2027.430054, 960.395020, 609.007996 >, < 0, 179.604, 0 > )
+
+ Evac_SetSpacePosition( < -1700, -5500, -7600 >, < -3.620642, 270.307129, 0 > )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_angel_city_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_angel_city_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_angel_city_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_black_water_canal.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_black_water_canal.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_black_water_canal.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_black_water_canal_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_black_water_canal_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_black_water_canal_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_coliseum.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_coliseum.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_coliseum.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_coliseum_column.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_coliseum_column.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_coliseum_column.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_colony02.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_colony02.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_colony02.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_colony02_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_colony02_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_colony02_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_complex3.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_complex3.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_complex3.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_crashsite3.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_crashsite3.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_crashsite3.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_drydock.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_drydock.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_drydock.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_drydock_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_drydock_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_drydock_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_eden.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_eden.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_eden.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_forwardbase_kodai.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_forwardbase_kodai.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_forwardbase_kodai.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_forwardbase_kodai_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_forwardbase_kodai_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_forwardbase_kodai_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_glitch.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_glitch.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_glitch.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_glitch_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_glitch_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_glitch_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_grave.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_grave.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_grave.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_grave_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_grave_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_grave_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_homestead.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_homestead.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_homestead.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_homestead_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_homestead_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_homestead_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_deck.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_deck.nut
new file mode 100644
index 000000000..398b2fc55
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_deck.nut
@@ -0,0 +1,6 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+ SetupLiveFireMaps()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_meadow.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_meadow.nut
new file mode 100644
index 000000000..398b2fc55
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_meadow.nut
@@ -0,0 +1,6 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+ SetupLiveFireMaps()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_stacks.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_stacks.nut
new file mode 100644
index 000000000..398b2fc55
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_stacks.nut
@@ -0,0 +1,6 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+ SetupLiveFireMaps()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_township.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_township.nut
new file mode 100644
index 000000000..398b2fc55
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_township.nut
@@ -0,0 +1,6 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+ SetupLiveFireMaps()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_traffic.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_traffic.nut
new file mode 100644
index 000000000..398b2fc55
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_traffic.nut
@@ -0,0 +1,6 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+ SetupLiveFireMaps()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_uma.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_uma.nut
new file mode 100644
index 000000000..398b2fc55
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_lf_uma.nut
@@ -0,0 +1,6 @@
+global function CodeCallback_MapInit
+
+void function CodeCallback_MapInit()
+{
+ SetupLiveFireMaps()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_relic02.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_relic02.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_relic02.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_relic02_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_relic02_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_relic02_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_rise.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_rise.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_rise.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_rise_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_rise_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_rise_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_thaw.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_thaw.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_thaw.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_thaw_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_thaw_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_thaw_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_wargames.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_wargames.nut
new file mode 100644
index 000000000..9252cced5
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_wargames.nut
@@ -0,0 +1,415 @@
+untyped
+
+const POD_ATTACHPOINT = "REF"
+
+const FX_LIGHT_ORANGE = "runway_light_orange"
+const FX_LIGHT_GREEN = "runway_light_green"
+const FX_POD_LASER = "P_pod_scan_laser_FP"
+const FX_POD_GLOWLIGHT = "P_pod_door_glow_FP"
+const FX_POD_SCREEN_IN = "P_pod_screen_lasers_IN"
+const FX_POD_SCREEN_OUT = "P_pod_screen_lasers_OUT"
+const FX_POD_DLIGHT_CONSOLE1 = $"P_pod_Dlight_console1"
+const FX_POD_DLIGHT_CONSOLE2 = $"P_pod_Dlight_console2"
+const FX_POD_DLIGHT_BACKLIGHT_SIDE = "P_pod_Dlight_backlight_side"
+const FX_POD_DLIGHT_BACKLIGHT_TOP = "P_pod_Dlight_backlight_top"
+const FX_TITAN_COCKPIT_LIGHT = "xo_cockpit_dlight"
+
+struct TrainingPod_dLightMapping
+{
+ string scriptAlias
+ asset fxName
+ string attachName
+ entity fxHandle
+}
+
+struct TrainingPod_LaserEmitter
+{
+ entity ent
+ string attachName
+ vector ogAng
+ bool sweepDone = false
+ entity fxHandle
+}
+
+struct TrainingPod_GlowLightRow
+{
+ array<string> fxSpotsL
+ array<string> fxSpotsR
+}
+
+global function CodeCallback_MapInit
+global function WargamesIntroStart
+
+struct {
+ entity militiaTrainingPod
+ entity imcTrainingPod
+ array< TrainingPod_dLightMapping >[2] podDLightMappings
+ array< TrainingPod_LaserEmitter >[2] podLaserEmitters
+
+ array< entity > playersWatchingIntro
+ string currentPodAnim
+ float currentPodAnimStartTime
+} file
+
+void function CodeCallback_MapInit()
+{
+ //FlagInit( "TrainingPodSetupDone" )
+ //FlagInit( "IntroRunning" )
+ //
+ //FlagSet( "DogFights" )
+ //FlagSet( "StratonFlybys" )
+ //
+ //ClassicMP_SetIntroLevelSetupFunc( Wargames_Intro_LevelSetupFunc )
+ //ClassicMP_SetIntroPlayerSpawnFunc( Wargames_ClassicMPIntroSpawn )
+ //ClassicMP_SetPrematchSpawnPlayersFunc( Wargames_PrematchSpawnPlayersFunc )
+ //
+ //AddCallback_GameStateEnter( eGameState.WaitingForPlayers, WargamesIntroStart ) // This starts the main intro control thread
+ //AddCallback_GameStateEnter( eGameState.PickLoadout, Wargames_GameStateEnterFunc_PickLoadout )
+ //AddCallback_GameStateEnter( eGameState.Prematch, Wargames_GameStateEnterFunc_PrematchCallback )
+}
+
+// setup
+
+bool function Wargames_Intro_LevelSetupFunc()
+{
+ array< entity > militiaTrainingPods = GetEntArrayByName_Expensive( "training_pod" )
+ Assert( militiaTrainingPods.len() == 1 )
+ file.militiaTrainingPod = militiaTrainingPods[0]
+ file.militiaTrainingPod.s.teamIdx <- TEAM_MILITIA
+
+ array< entity > imcTrainingPods = GetEntArrayByName_Expensive( "training_pod_imc" )
+ Assert( imcTrainingPods.len() == 1 )
+ file.imcTrainingPod = imcTrainingPods[0]
+ file.imcTrainingPod.s.teamIdx <- TEAM_IMC
+
+ SetupTrainingPod( file.militiaTrainingPod )
+ SetupTrainingPod( file.imcTrainingPod )
+
+ FlagSet( "TrainingPodSetupDone" )
+ return true
+}
+
+void function SetupTrainingPod( entity pod )
+{
+ pod.DisableHibernation()
+ pod.s.glowLightFXHandles <- []
+ pod.s.dLights <- []
+
+ // FUN hack because can't assign struct => var
+ int podStructIndex = pod.s.teamIdx == TEAM_MILITIA ? 0 : 1
+
+ array< TrainingPod_dLightMapping > tempLightMappings
+
+ TrainingPod_dLightMapping m1
+ m1.scriptAlias = "console1"
+ m1.fxName = FX_POD_DLIGHT_CONSOLE1
+ m1.attachName = "light_console1"
+ tempLightMappings.append( m1 )
+
+ TrainingPod_dLightMapping m2
+ m2.scriptAlias = "console2"
+ m2.fxName = FX_POD_DLIGHT_CONSOLE2
+ m2.attachName = "light_console2"
+ tempLightMappings.append( m2 )
+
+ file.podDLightMappings[ podStructIndex ] = tempLightMappings
+
+ array< string > laserAttachNames = [ "fx_laser_L", "fx_laser_R" ]
+
+ foreach ( string attachName in laserAttachNames )
+ {
+ entity emitterEnt = CreateScriptMover( pod.GetOrigin(), pod.GetAngles() )
+ int attachID = pod.LookupAttachment( attachName )
+ vector attachOrg = pod.GetAttachmentOrigin( attachID )
+ vector attachAng = pod.GetAttachmentAngles( attachID )
+
+ TrainingPod_LaserEmitter emitter
+ emitter.ent = emitterEnt
+ emitter.attachName = attachName
+ emitter.ogAng = attachAng
+
+ file.podLaserEmitters[ podStructIndex ].append( emitter )
+ }
+
+ // HACK we do this later as well to reset the emitter positions, so it's a separate function
+ TrainingPod_SnapLaserEmittersToAttachPoints( pod )
+}
+
+void function TrainingPod_SnapLaserEmittersToAttachPoints( entity pod )
+{
+ foreach ( TrainingPod_LaserEmitter emitter in file.podLaserEmitters[ pod.s.teamIdx == TEAM_MILITIA ? 0 : 1 ] )
+ {
+ int attachID = pod.LookupAttachment( emitter.attachName )
+ vector attachOrg = pod.GetAttachmentOrigin( attachID )
+ vector attachAng = pod.GetAttachmentAngles( attachID )
+
+ emitter.ent.ClearParent()
+ emitter.ent.SetOrigin( attachOrg ) // HACK set this to ANYTHING (even 0, 0, 0) and the position is correct, otherwise it's offset from the attachpoint when parented
+ emitter.ent.SetParent( pod, emitter.attachName )
+ }
+}
+
+// spawning/intro sequence
+
+void function Wargames_ClassicMPIntroSpawn( entity player )
+{
+ Assert( !IsAlive( player ) )
+
+ thread PlayerWatchPodIntro( player )
+ // return true
+}
+
+void function Wargames_PrematchSpawnPlayersFunc( array< entity > players )
+{
+
+}
+
+void function PlayerWatchPodIntro( entity player )
+{
+ player.EndSignal( "Disconnected" )
+ player.EndSignal( "OnDeath" )
+
+ int team = player.GetTeam()
+
+ entity pod
+ if ( team == TEAM_MILITIA )
+ pod = file.militiaTrainingPod
+ else
+ pod = file.imcTrainingPod
+
+ AddCinematicFlag( player, CE_FLAG_INTRO )
+ file.playersWatchingIntro.append( player )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ {
+ printt( "PlayerWatchPodIntro: clearing up player:", player )
+
+ //ArrayRemove( file.playersWatchingIntro, player )
+ file.playersWatchingIntro.remove( file.playersWatchingIntro.find( player ) )
+
+ player.Anim_Stop()
+ //player.ClearAnimViewEntity()
+ //player.ClearParent()
+ player.UnforceStand()
+ player.EnableWeaponViewModel()
+ player.kv.VisibilityFlags = 7 // All can see now that intro is over
+
+ FadeOutSoundOnEntity( player, "Amb_Wargames_Pod_Ambience", 0.13 )
+
+ // turns hud back on
+ if ( HasCinematicFlag( player, CE_FLAG_INTRO ) )
+ RemoveCinematicFlag( player, CE_FLAG_INTRO )
+ }
+ }
+ )
+
+ // FIRST SPAWN
+ int attachID = pod.LookupAttachment( POD_ATTACHPOINT )
+ vector podRefOrg = pod.GetAttachmentOrigin( attachID )
+ vector podRefAng = pod.GetAttachmentAngles( attachID )
+ player.SetOrigin( podRefOrg )
+ player.SetAngles( podRefAng )
+ player.RespawnPlayer( null )
+
+ player.kv.VisibilityFlags = 1 // visible to player only, so others don't see his viewmodel during the anim
+ player.DisableWeaponViewModel()
+ player.ForceStand()
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Amb_Wargames_Pod_Ambience" )
+ thread Intro_StartPlayerFirstPersonSequence( player )
+
+ FlagWait( "IntroRunning" )
+ player.UnfreezeControlsOnServer()
+
+ // wait for intro to finish for OnThreadEnd cleanup
+ FlagWaitClear( "IntroRunning" )
+ printt( "PlayerWatchPodIntro: intro finished normally for player", player )
+}
+
+void function Intro_StartPlayerFirstPersonSequence( entity player )
+{
+ player.EndSignal( "Disconnected" )
+ player.EndSignal( "OnDeath" )
+
+ entity pod
+ if ( player.GetTeam() == TEAM_MILITIA )
+ pod = file.militiaTrainingPod
+ else
+ pod = file.imcTrainingPod
+
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.blendTime = 0.0
+ playerSequence.attachment = POD_ATTACHPOINT
+ playerSequence.renderWithViewModels = true
+ playerSequence.firstPersonAnimIdle = "ptpov_trainingpod_idle"
+
+ //FirstPersonSequenceStruct podSequence
+ //podSequence.blendTime = 0.25
+ //podSequence.thirdPersonAnim = "trainingpod_doors_close"
+ //podSequence.thirdPersonAnimIdle = "trainingpod_doors_close_idle"
+ //podSequence.renderWithViewModels = true
+
+ void functionref( entity ) viewconeFunc = null
+ viewconeFunc = TrainingPod_ViewConeLock_SemiStrict
+
+ // i don't get this whole animevent system tbh, might just skip it lol
+ entity viewmodel = player.GetFirstPersonProxy()
+ //if ( !HasAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut" ) )
+ //{
+ printt( "adding anim event... this probably won't work", player )
+ AddAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut", PlaySound_SimPod_DoorShut )
+ AddAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut", PlaySound_SimPod_DoorShut )
+ AddAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut", PlaySound_SimPod_DoorShut )
+ AddAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut", PlaySound_SimPod_DoorShut )
+ AddAnimEvent( viewmodel, "PlaySound_SimPod_DoorShut", PlaySound_SimPod_DoorShut )
+
+ print( "this SHOULD have caused errors in console due to registering it multiple times but doesn't????" )
+ //}
+
+ if ( file.currentPodAnim == "closing" )
+ {
+ playerSequence.firstPersonAnim = "ptpov_trainingpod_doors_close"
+ }
+
+ // TODO: correct viewcone funcs and catchup stuff
+
+ //thread FirstPersonSequence( podSequence, pod )
+ thread FirstPersonSequence( playerSequence, player, pod )
+
+ if ( viewconeFunc != null )
+ viewconeFunc( player )
+
+ FlagClear( "IntroRunning" )
+}
+
+void function Intro_StartPlayerFirstPersonSequenceForAll()
+{
+ foreach ( entity player in file.playersWatchingIntro )
+ thread Intro_StartPlayerFirstPersonSequence( player )
+}
+
+void function TrainingPod_ViewConeLock_SemiStrict( entity player )
+{
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -10 )
+ player.PlayerCone_SetMaxYaw( 10 )
+ player.PlayerCone_SetMinPitch( -10 )
+ player.PlayerCone_SetMaxPitch( 10 )
+}
+
+void function PlaySound_SimPod_DoorShut( entity playerFirstPersonProxy )
+{
+ print( "playing anim event!" )
+ // never called lol
+
+ entity player = playerFirstPersonProxy.GetOwner()
+ if ( !IsValid( player ) )
+ return
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "NPE_Scr_SimPod_DoorShut" )
+}
+
+void function WargamesIntroStart()
+{
+ thread WargamesIntroStartThreaded()
+}
+
+void function WargamesIntroStartThreaded()
+{
+ FlagWait( "TrainingPodSetupDone" )
+
+ // early connecting players start idling
+ file.currentPodAnim = "idle"
+
+ file.militiaTrainingPod.s.ambienceLoud <- "Wargames_Emit_MCOR_Intro_HighPass"
+ file.militiaTrainingPod.s.ambienceQuiet <- "Wargames_Emit_MCOR_Intro_LowPass"
+
+ file.imcTrainingPod.s.ambienceLoud <- "Wargames_Emit_IMC_Intro_HighPass"
+ file.imcTrainingPod.s.ambienceQuiet <- "Wargames_Emit_IMC_Intro_LowPass"
+
+ OnThreadEnd(
+ function() : ()
+ {
+
+ }
+ )
+
+ // wait for prematch, after waiting for players
+ WaittillGameStateOrHigher( eGameState.Prematch )
+
+ EmitSoundOnEntity( file.militiaTrainingPod, "Wargames_Emit_MCOR_Intro_HighPass" )
+ EmitSoundOnEntity( file.militiaTrainingPod, "Wargames_Emit_MCOR_Intro_LowPass" )
+
+ EmitSoundOnEntity( file.imcTrainingPod, "Wargames_Emit_IMC_Intro_HighPass")
+ EmitSoundOnEntity( file.imcTrainingPod, "Wargames_Emit_IMC_Intro_LowPass" )
+
+ // todo: intro skits
+
+ FlagSet( "IntroRunning" )
+ float startTime = Time()
+
+ file.currentPodAnimStartTime = Time()
+ FirstPersonSequenceStruct podSequence
+ podSequence.blendTime = 0.0
+ podSequence.thirdPersonAnimIdle = "trainingpod_doors_open_idle"
+ podSequence.renderWithViewModels = true
+
+ // turn on top lights
+ TrainingPod_TurnOnInteriorDLight( "console1", file.militiaTrainingPod )
+ TrainingPod_TurnOnInteriorDLight( "console2", file.militiaTrainingPod )
+ thread FirstPersonSequence( podSequence, file.militiaTrainingPod )
+
+ TrainingPod_TurnOnInteriorDLight( "console1", file.imcTrainingPod )
+ TrainingPod_TurnOnInteriorDLight( "console2", file.imcTrainingPod )
+ thread FirstPersonSequence( podSequence, file.imcTrainingPod )
+
+ //thread Intro_PlayersHearPodVO()
+
+ Intro_StartPlayerFirstPersonSequenceForAll()
+ wait 8
+
+ file.currentPodAnim = "closing"
+ file.currentPodAnimStartTime = Time()
+ podSequence.blendTime = 0.0
+ podSequence.thirdPersonAnim = "trainingpod_doors_close"
+ podSequence.thirdPersonAnimIdle = "trainingpod_doors_close_idle"
+
+ Intro_StartPlayerFirstPersonSequenceForAll()
+
+ thread FirstPersonSequence( podSequence, file.militiaTrainingPod )
+ waitthread FirstPersonSequence( podSequence, file.imcTrainingPod )
+
+ file.currentPodAnim = "done"
+
+ wait 4.5
+
+
+ FlagClear( "IntroRunning" )
+}
+
+void function TrainingPod_TurnOnInteriorDLight( string alias, entity pod )
+{
+ int teamIdx = pod.s.teamIdx == TEAM_MILITIA ? 0 : 1
+ foreach ( TrainingPod_dLightMapping mapping in file.podDLightMappings[ teamIdx ] )
+ {
+ if ( mapping.scriptAlias == alias )
+ {
+ PlayLoopFXOnEntity( mapping.fxName, pod, mapping.attachName )
+ pod.s.dLights.append( mapping.fxName )
+ break
+ }
+ }
+}
+
+void function Wargames_GameStateEnterFunc_PickLoadout()
+{
+ level.nv.minPickLoadOutTime = Time() + 0.5
+}
+
+void function Wargames_GameStateEnterFunc_PrematchCallback()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_wargames_fd.nut b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_wargames_fd.nut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/levels/mp_wargames_fd.nut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/pintelemetry.gnut b/Northstar.CustomServers/scripts/vscripts/mp/pintelemetry.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/pintelemetry.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/player_cloak.nut b/Northstar.CustomServers/scripts/vscripts/mp/player_cloak.nut
new file mode 100644
index 000000000..8ef7dcd93
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/player_cloak.nut
@@ -0,0 +1,184 @@
+untyped //TODO: get rid of player.s.cloakedShotsAllowed. (Referenced in base_gametype_sp, so remove for R5)
+
+global function PlayerCloak_Init
+
+global const CLOAK_FADE_IN = 1.0
+global const CLOAK_FADE_OUT = 1.0
+
+global function EnableCloak
+global function DisableCloak
+global function EnableCloakForever
+global function DisableCloakForever
+
+//=========================================================
+// player_cloak
+//
+//=========================================================
+
+void function PlayerCloak_Init()
+{
+ RegisterSignal( "OnStartCloak" )
+ RegisterSignal( "KillHandleCloakEnd" ) //Somewhat awkward, mainly to smooth out weird interactions with cloak ability and cloak execution
+
+ AddCallback_OnPlayerKilled( AbilityCloak_OnDeath )
+ AddSpawnCallback( "npc_titan", SetCannotCloak )
+}
+
+void function SetCannotCloak( entity ent )
+{
+ ent.SetCanCloak( false )
+}
+
+void function PlayCloakSounds( entity player )
+{
+ EmitSoundOnEntityOnlyToPlayer( player, player, "cloak_on_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "cloak_on_3P" )
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "cloak_sustain_loop_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "cloak_sustain_loop_3P" )
+}
+
+void function EnableCloak( entity player, float duration, float fadeIn = CLOAK_FADE_IN )
+{
+ if ( player.cloakedForever )
+ return
+
+ thread AICalloutCloak( player )
+
+ PlayCloakSounds( player )
+
+ float cloakDuration = duration - fadeIn
+
+ Assert( cloakDuration > 0.0, "Not valid cloak duration. Check that duration is larger than the fadeinTime. When this is not true it will cause the player to be cloaked forever. If you want to do that use EnableCloakForever instead" )
+
+ player.SetCloakDuration( fadeIn, cloakDuration, CLOAK_FADE_OUT )
+
+ player.s.cloakedShotsAllowed = 0
+
+ Battery_StopFXAndHideIconForPlayer( player )
+
+ thread HandleCloakEnd( player )
+}
+
+void function AICalloutCloak( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ wait CLOAK_FADE_IN //Give it a beat after cloak has finishing cloaking in
+
+ array<entity> nearbySoldiers = GetNPCArrayEx( "npc_soldier", TEAM_ANY, player.GetTeam(), player.GetOrigin(), 1000 ) //-1 for distance parameter means all spectres in map
+ foreach ( entity grunt in nearbySoldiers )
+ {
+ if ( !IsAlive( grunt ) )
+ continue
+
+ if ( grunt.GetEnemy() == player )
+ {
+ ScriptDialog_PilotCloaked( grunt, player )
+ return //Only need one guy to say this instead of multiple guys
+ }
+ }
+}
+
+void function EnableCloakForever( entity player )
+{
+ player.SetCloakDuration( CLOAK_FADE_IN, -1, CLOAK_FADE_OUT )
+
+ player.cloakedForever = true
+
+ thread HandleCloakEnd( player )
+ PlayCloakSounds( player )
+}
+
+
+void function DisableCloak( entity player, float fadeOut = CLOAK_FADE_OUT )
+{
+ StopSoundOnEntity( player, "cloak_sustain_loop_1P" )
+ StopSoundOnEntity( player, "cloak_sustain_loop_3P" )
+
+ bool wasCloaked = player.IsCloaked( CLOAK_INCLUDE_FADE_IN_TIME )
+
+ if ( fadeOut < CLOAK_FADE_OUT && wasCloaked )
+ {
+ EmitSoundOnEntityOnlyToPlayer( player, player, "cloak_interruptend_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "cloak_interruptend_3P" )
+
+ StopSoundOnEntity( player, "cloak_warningtoend_1P" )
+ StopSoundOnEntity( player, "cloak_warningtoend_3P" )
+ }
+
+ player.SetCloakDuration( 0, 0, fadeOut )
+}
+
+void function DisableCloakForever( entity player, float fadeOut = CLOAK_FADE_OUT )
+{
+ DisableCloak( player, fadeOut )
+ player.cloakedForever = false
+}
+
+
+void function HandleCloakEnd( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnEMPPilotHit" )
+ player.EndSignal( "OnChangedPlayerClass" )
+ player.Signal( "OnStartCloak" )
+ player.EndSignal( "OnStartCloak" )
+ player.EndSignal( "KillHandleCloakEnd" ) //Calling DisableCloak() after EnableCloak() doesn't kill this thread by design (to allow attacking through cloak etc), so this signal is for when you want to kill this thread
+
+ float duration = player.GetCloakEndTime() - Time()
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( !IsValid( player ) )
+ return
+
+ if ( PlayerHasBattery( player ) )
+ Battery_StartFX( GetBatteryOnBack( player ) )
+
+ StopSoundOnEntity( player, "cloak_sustain_loop_1P" )
+ StopSoundOnEntity( player, "cloak_sustain_loop_3P" )
+ if ( !IsCloaked( player ) )
+ return
+
+ if ( !IsAlive( player ) || !player.IsHuman() )
+ {
+ DisableCloak( player )
+ return
+ }
+
+ float duration = player.GetCloakEndTime() - Time()
+ if ( duration <= 0 )
+ {
+ DisableCloak( player )
+ }
+ }
+ )
+
+ float soundBufferTime = 3.45
+
+ if ( duration > soundBufferTime )
+ {
+ wait ( duration - soundBufferTime )
+ if ( !IsCloaked( player ) )
+ return
+ EmitSoundOnEntityOnlyToPlayer( player, player, "cloak_warningtoend_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "cloak_warningtoend_3P" )
+
+ wait soundBufferTime
+ }
+ else
+ {
+ wait duration
+ }
+}
+
+
+void function AbilityCloak_OnDeath( entity player, entity attacker, var damageInfo )
+{
+ if ( !IsCloaked( player ) )
+ return
+
+ DisableCloak( player, 0 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/scripts/vscripts/mp/spawn.nut
new file mode 100644
index 000000000..b7a50453e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/spawn.nut
@@ -0,0 +1,428 @@
+untyped
+
+global function InitRatings // temp for testing
+
+global function Spawn_Init
+global function SetSpawnsUseFrontline
+global function SetRespawnsEnabled
+global function RespawnsEnabled
+global function SetSpawnpointGamemodeOverride
+global function CreateNoSpawnArea
+global function DeleteNoSpawnArea
+
+global function GetCurrentFrontline
+global function FindSpawnPoint
+
+global function RateSpawnpoints_Generic
+
+struct NoSpawnArea
+{
+ string id
+ int blockedTeam
+ int blockOtherTeams
+ vector position
+ float lifetime
+ float radius
+}
+
+struct {
+ bool respawnsEnabled = true
+ string spawnpointGamemodeOverride
+
+ array<vector> preferSpawnNodes
+ table<string, NoSpawnArea> noSpawnAreas
+ bool sidesSwitched = false
+
+ bool frontlineBased = false
+ float lastImcFrontlineRatingTime
+ float lastMilitiaFrontlineRatingTime
+ Frontline& lastImcFrontline
+ Frontline& lastMilitiaFrontline
+} file
+
+void function Spawn_Init()
+{
+ AddCallback_GameStateEnter( eGameState.SwitchingSides, OnSwitchingSides )
+ AddCallback_EntitiesDidLoad( InitPreferSpawnNodes )
+
+ AddSpawnCallback( "info_spawnpoint_human", InitSpawnpoint )
+ AddSpawnCallback( "info_spawnpoint_human_start", InitSpawnpoint )
+ AddSpawnCallback( "info_spawnpoint_titan", InitSpawnpoint )
+ AddSpawnCallback( "info_spawnpoint_titan_start", InitSpawnpoint )
+}
+
+void function InitPreferSpawnNodes()
+{
+ foreach ( entity hardpoint in GetEntArrayByClass_Expensive( "info_hardpoint" ) )
+ {
+ if ( !hardpoint.HasKey( "hardpointGroup" ) )
+ continue
+
+ if ( hardpoint.kv.hardpointGroup != "A" && hardpoint.kv.hardpointGroup != "B" && hardpoint.kv.hardpointGroup != "C" )
+ continue
+
+ file.preferSpawnNodes.append( hardpoint.GetOrigin() )
+ }
+
+ //foreach ( entity frontline in GetEntArrayByClass_Expensive( "info_frontline" ) )
+ // file.preferSpawnNodes.append( frontline.GetOrigin() )
+}
+
+void function InitSpawnpoint( entity spawnpoint )
+{
+ spawnpoint.s.lastUsedTime <- -999
+}
+
+void function SetRespawnsEnabled( bool enabled )
+{
+ file.respawnsEnabled = enabled
+}
+
+bool function RespawnsEnabled()
+{
+ return file.respawnsEnabled
+}
+
+string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam, vector position, float lifetime, float radius )
+{
+ NoSpawnArea noSpawnArea
+ noSpawnArea.blockedTeam = blockSpecificTeam
+ noSpawnArea.blockOtherTeams = blockEnemiesOfTeam
+ noSpawnArea.position = position
+ noSpawnArea.lifetime = lifetime
+ noSpawnArea.radius = radius
+
+ // generate an id
+ noSpawnArea.id = UniqueString( "noSpawnArea" )
+
+ thread NoSpawnAreaLifetime( noSpawnArea )
+
+ return noSpawnArea.id
+}
+
+void function NoSpawnAreaLifetime( NoSpawnArea noSpawnArea )
+{
+ wait noSpawnArea.lifetime
+ DeleteNoSpawnArea( noSpawnArea.id )
+}
+
+void function DeleteNoSpawnArea( string noSpawnIdx )
+{
+ try // unsure if the trycatch is necessary but better safe than sorry
+ {
+ delete file.noSpawnAreas[ noSpawnIdx ]
+ }
+ catch ( exception )
+ {}
+}
+
+void function SetSpawnpointGamemodeOverride( string gamemode )
+{
+ file.spawnpointGamemodeOverride = gamemode
+}
+
+void function SetSpawnsUseFrontline( bool useFrontline )
+{
+ file.frontlineBased = useFrontline
+}
+
+bool function InitRatings( entity player, int team )
+{
+ Frontline frontline = GetCurrentFrontline( team )
+ print( team )
+ print( frontline.friendlyCenter )
+
+ vector offsetOrigin = frontline.friendlyCenter + frontline.combatDir * 256
+ SpawnPoints_InitFrontlineData( offsetOrigin, frontline.combatDir, frontline.line, frontline.friendlyCenter, 2.0 ) // temp
+
+ if ( player != null )
+ SpawnPoints_InitRatings( player, team ) // no idea what the second arg supposed to be lol
+
+ return frontline.friendlyCenter == < 0, 0, 0 > && file.frontlineBased // if true, use startspawns
+}
+
+Frontline function GetCurrentFrontline( int team )
+{
+ float lastFrontlineRatingTime
+ Frontline lastFrontline
+ if ( team == TEAM_IMC )
+ {
+ lastFrontline = file.lastImcFrontline
+ lastFrontlineRatingTime = file.lastImcFrontlineRatingTime
+ }
+ else
+ {
+ lastFrontline = file.lastMilitiaFrontline
+ lastFrontlineRatingTime = file.lastMilitiaFrontlineRatingTime
+ }
+
+ // current frontline is old, get a new one
+ if ( lastFrontlineRatingTime + 20.0 < Time() || lastFrontline.friendlyCenter == < 0, 0, 0 > )
+ {
+ print( "rerating frontline..." )
+ Frontline frontline = GetFrontline( team )
+
+ // this doesn't work lol
+ /*if ( frontline.friendlyCenter == < 0, 0, 0 > )
+ {
+ // recalculate to startspawnpoint positions
+ array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
+
+ vector averagePos
+ vector averageDir
+ foreach ( entity spawnpoint in startSpawns )
+ {
+ averagePos.x += spawnpoint.GetOrigin().x
+ averagePos.y += spawnpoint.GetOrigin().y
+ averagePos.z += spawnpoint.GetOrigin().z
+
+ averageDir.x += spawnpoint.GetAngles().x
+ averageDir.y += spawnpoint.GetAngles().y
+ averageDir.z += spawnpoint.GetAngles().z
+ }
+
+ averagePos.x /= startSpawns.len()
+ averagePos.y /= startSpawns.len()
+ averagePos.z /= startSpawns.len()
+
+ averageDir.x /= startSpawns.len()
+ averageDir.y /= startSpawns.len()
+ averageDir.z /= startSpawns.len()
+
+ print( "average " + averagePos )
+
+ frontline.friendlyCenter = averagePos
+ frontline.origin = averagePos
+ frontline.combatDir = averageDir * -1
+ }*/
+
+ if ( team == TEAM_IMC )
+ {
+ file.lastImcFrontlineRatingTime = Time()
+ file.lastImcFrontline = frontline
+ }
+ else
+ {
+ file.lastMilitiaFrontlineRatingTime = Time()
+ file.lastMilitiaFrontline = frontline
+ }
+
+ lastFrontline = frontline
+ }
+
+ return lastFrontline
+}
+
+entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnpoint )
+{
+ int team = player.GetTeam()
+ if ( file.sidesSwitched )
+ team = GetOtherTeam( team )
+
+ useStartSpawnpoint = InitRatings( player, player.GetTeam() ) || useStartSpawnpoint // force startspawns if no frontline
+ print( "useStartSpawnpoint: " + useStartSpawnpoint )
+
+ array<entity> spawnpoints
+ if ( useStartSpawnpoint )
+ spawnpoints = isTitan ? SpawnPoints_GetTitanStart( team ) : SpawnPoints_GetPilotStart( team )
+ else
+ spawnpoints = isTitan ? SpawnPoints_GetTitan() : SpawnPoints_GetPilot()
+
+ void functionref( int, array<entity>, int, entity ) ratingFunc = isTitan ? GameMode_GetTitanSpawnpointsRatingFunc( GAMETYPE ) : GameMode_GetPilotSpawnpointsRatingFunc( GAMETYPE )
+ ratingFunc( isTitan ? TD_TITAN : TD_PILOT, spawnpoints, team, player )
+
+ if ( isTitan )
+ {
+ if ( useStartSpawnpoint )
+ SpawnPoints_SortTitanStart()
+ else
+ SpawnPoints_SortTitan()
+
+ spawnpoints = useStartSpawnpoint ? SpawnPoints_GetTitanStart( team ) : SpawnPoints_GetTitan()
+ }
+ else
+ {
+ if ( useStartSpawnpoint )
+ SpawnPoints_SortPilotStart()
+ else
+ SpawnPoints_SortPilot()
+
+ spawnpoints = useStartSpawnpoint ? SpawnPoints_GetPilotStart( team ) : SpawnPoints_GetPilot()
+ }
+
+ entity spawnpoint = GetBestSpawnpoint( player, spawnpoints )
+
+ spawnpoint.s.lastUsedTime = Time()
+ player.SetLastSpawnPoint( spawnpoint )
+
+ return spawnpoint
+}
+
+entity function GetBestSpawnpoint( entity player, array<entity> spawnpoints )
+{
+ // not really 100% sure on this randomisation, needs some thought
+ array<entity> validSpawns
+ foreach ( entity spawnpoint in spawnpoints )
+ {
+ if ( IsSpawnpointValid( spawnpoint, player.GetTeam() ) )
+ {
+ validSpawns.append( spawnpoint )
+
+ if ( validSpawns.len() == 3 ) // arbitrary small sample size
+ break
+ }
+ }
+
+ if ( validSpawns.len() == 0 )
+ {
+ // no valid spawns, very bad, so dont care about spawns being valid anymore
+ print( "found no valid spawns! spawns may be subpar!" )
+ foreach ( entity spawnpoint in spawnpoints )
+ {
+ validSpawns.append( spawnpoint )
+
+ if ( validSpawns.len() == 3 ) // arbitrary small sample size
+ break
+ }
+ }
+
+ return validSpawns[ RandomInt( validSpawns.len() ) ] // slightly randomize it
+}
+
+bool function IsSpawnpointValid( entity spawnpoint, int team )
+{
+ //if ( !spawnpoint.HasKey( "ignoreGamemode" ) || ( spawnpoint.HasKey( "ignoreGamemode" ) && spawnpoint.kv.ignoreGamemode == "0" ) ) // used by script-spawned spawnpoints
+ //{
+ // if ( file.spawnpointGamemodeOverride != "" )
+ // {
+ // string gamemodeKey = "gamemode_" + file.spawnpointGamemodeOverride
+ // if ( spawnpoint.HasKey( gamemodeKey ) && ( spawnpoint.kv[ gamemodeKey ] == "0" || spawnpoint.kv[ gamemodeKey ] == "" ) )
+ // return false
+ // }
+ // else if ( GameModeRemove( spawnpoint ) )
+ // return false
+ //}
+
+ if ( Riff_FloorIsLava() && spawnpoint.GetOrigin().z < GetLethalFogTop() )
+ return false
+
+ int compareTeam = spawnpoint.GetTeam()
+ if ( file.sidesSwitched && ( compareTeam == TEAM_MILITIA || compareTeam == TEAM_IMC ) )
+ compareTeam = GetOtherTeam( compareTeam )
+
+ if ( spawnpoint.GetTeam() > 0 && compareTeam != team && !IsFFAGame() )
+ return false
+
+ if ( spawnpoint.IsOccupied() )
+ return false
+
+ foreach ( k, NoSpawnArea noSpawnArea in file.noSpawnAreas )
+ {
+ if ( Distance( noSpawnArea.position, spawnpoint.GetOrigin() ) > noSpawnArea.radius )
+ continue
+
+ if ( noSpawnArea.blockedTeam != TEAM_INVALID && noSpawnArea.blockedTeam == team )
+ return false
+
+ if ( noSpawnArea.blockOtherTeams != TEAM_INVALID && noSpawnArea.blockOtherTeams != team )
+ return false
+ }
+
+ array<entity> projectiles = GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), 400 )
+ foreach ( entity projectile in projectiles )
+ if ( projectile.GetTeam() != team )
+ return false
+
+ if ( Time() - spawnpoint.s.lastUsedTime <= 1.0 )
+ return false
+
+ return true
+}
+
+void function RateSpawnpoints_Generic( int checkClass, array<entity> spawnpoints, int team, entity player )
+{
+ // calculate ratings for preferred nodes
+ // this tries to prefer nodes with more teammates, then activity on them
+ // todo: in the future it might be good to have this prefer nodes with enemies up to a limit of some sort
+ // especially in ffa modes i could deffo see this falling apart a bit rn
+ // perhaps dead players could be used to calculate some sort of activity rating? so high-activity points with an even balance of friendly/unfriendly players are preferred
+ array<float> preferSpawnNodeRatings
+ foreach ( vector preferSpawnNode in file.preferSpawnNodes )
+ {
+ float currentRating
+
+ // this seems weird, not using rn
+ //Frontline currentFrontline = GetCurrentFrontline( team )
+ //if ( !IsFFAGame() || currentFrontline.friendlyCenter != < 0, 0, 0 > )
+ // currentRating += max( 0.0, ( 1000.0 - Distance2D( currentFrontline.origin, preferSpawnNode ) ) / 200 )
+
+ foreach ( entity nodePlayer in GetPlayerArray() )
+ {
+ float currentChange = 0.0
+
+ // the closer a player is to a node the more they matter
+ float dist = Distance2D( preferSpawnNode, nodePlayer.GetOrigin() )
+ if ( dist > 600.0 )
+ continue
+
+ currentChange = ( 600.0 - dist ) / 5
+ if ( player == nodePlayer )
+ currentChange *= -3 // always try to stay away from places we've already spawned
+ else if ( !IsAlive( nodePlayer ) ) // dead players mean activity which is good, but they're also dead so they don't matter as much as living ones
+ currentChange *= 0.6
+ if ( nodePlayer.GetTeam() != player.GetTeam() ) // if someone isn't on our team and alive they're probably bad
+ {
+ if ( IsFFAGame() ) // in ffa everyone is on different teams, so this isn't such a big deal
+ currentChange *= -0.2
+ else
+ currentChange *= -0.6
+ }
+
+ currentRating += currentChange
+ }
+
+ preferSpawnNodeRatings.append( currentRating )
+ }
+
+ foreach ( entity spawnpoint in spawnpoints )
+ {
+ float currentRating
+ float petTitanModifier
+ // scale how much a given spawnpoint matters to us based on how far it is from each node
+ bool spawnHasRecievedInitialBonus = false
+ for ( int i = 0; i < file.preferSpawnNodes.len(); i++ )
+ {
+ // bonus if autotitan is nearish
+ if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), file.preferSpawnNodes[ i ] ) < 1200.0 )
+ petTitanModifier += 10.0
+
+ float dist = Distance2D( spawnpoint.GetOrigin(), file.preferSpawnNodes[ i ] )
+ if ( dist > 750.0 )
+ continue
+
+ if ( dist < 600.0 && !spawnHasRecievedInitialBonus )
+ {
+ currentRating += 10.0
+ spawnHasRecievedInitialBonus = true // should only get a bonus for simply being by a node once to avoid over-rating
+ }
+
+ currentRating += ( preferSpawnNodeRatings[ i ] * ( ( 750.0 - dist ) / 75 ) ) + max( RandomFloat( 1.25 ), 0.9 )
+ if ( dist < 250.0 ) // shouldn't get TOO close to an active node
+ currentRating *= 0.7
+
+ if ( spawnpoint.s.lastUsedTime < 10.0 )
+ currentRating *= 0.7
+ }
+
+ float rating = spawnpoint.CalculateRating( checkClass, team, currentRating, currentRating + petTitanModifier )
+ //print( "spawnpoint at " + spawnpoint.GetOrigin() + " has rating: " + )
+
+ if ( rating != 0.0 || currentRating != 0.0 )
+ print( "rating = " + rating + ", internal rating = " + currentRating )
+ }
+}
+
+void function OnSwitchingSides()
+{
+ file.sidesSwitched = true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/spawn_debug.gnut b/Northstar.CustomServers/scripts/vscripts/mp/spawn_debug.gnut
new file mode 100644
index 000000000..75ec8cf24
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/spawn_debug.gnut
@@ -0,0 +1,6 @@
+global function SpawnDebug_Init
+
+void function SpawnDebug_Init()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/spawn_on_friendly.gnut b/Northstar.CustomServers/scripts/vscripts/mp/spawn_on_friendly.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/spawn_on_friendly.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/spawn_wave.gnut b/Northstar.CustomServers/scripts/vscripts/mp/spawn_wave.gnut
new file mode 100644
index 000000000..b8895c55c
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/spawn_wave.gnut
@@ -0,0 +1,6 @@
+global function SpawnWave_Init
+
+void function SpawnWave_Init()
+{
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/spawn_wave_dropship.gnut b/Northstar.CustomServers/scripts/vscripts/mp/spawn_wave_dropship.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/spawn_wave_dropship.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/pilot/_leeching.gnut b/Northstar.CustomServers/scripts/vscripts/pilot/_leeching.gnut
new file mode 100644
index 000000000..c9d1f9dda
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/pilot/_leeching.gnut
@@ -0,0 +1,493 @@
+global function Leeching_Init
+
+global function DoLeechAnimEvent
+global function DoLeech
+global function TryLeechAbortCallback
+global function StartLeechingProgress
+global function StopLeechingProgress
+global function EnableLeeching
+global function DisableLeeching
+global function MarvinWeaponsFree
+global function GetLeechedEnts
+global function DataKnifeSuccessSounds
+global function DataKnifeCanceledSounds
+
+global function GetTeamLeechedEnts
+
+// _leeching.nut
+// sets up global stuff for leeching
+global const MARVIN_EMOTE_SOUND_HAPPY = "diag_spectre_gs_LeechEnd_01_1"
+global const MARVIN_EMOTE_SOUND_SAD = "diag_spectre_gs_LeechAborted_01_1"
+global const MARVIN_EMOTE_SOUND_PAIN = "diag_spectre_gs_LeechStart_01_1"
+
+#if MP
+global const bool WIFI_HACK_OVERFLOW_DIES = false // Kill a random leeched ent from within the team, exluding the current target, to create a new team member when hacked
+#elseif SP
+global const bool WIFI_HACK_OVERFLOW_DIES = true
+#endif
+
+struct LeechFuncInfo
+{
+ string classname
+ void functionref(entity,entity) DoLeech
+ void functionref(entity,entity) LeechStart
+ void functionref(entity,entity) LeechAbort
+}
+
+struct
+{
+ table<string, LeechFuncInfo> leechFuncs
+} file
+
+void function Leeching_Init()
+{
+ RegisterSignal( "OnLeeched" )
+ RegisterSignal( "OnStartLeech" )
+ RegisterSignal( "OnStopLeeched" )
+ RegisterSignal( "EnableLeeching" )
+
+ // Spectre leech
+ LeechFuncInfo spectre
+ spectre.classname = "npc_spectre"
+ spectre.DoLeech = LeechGeneric
+ spectre.LeechStart = LeechStartGeneric
+ spectre.LeechAbort = LeechAbortGeneric
+ file.leechFuncs[spectre.classname] <- spectre
+
+ // Reaper leech
+ LeechFuncInfo reaper
+ reaper.classname = "npc_super_spectre"
+ reaper.DoLeech = LeechGeneric
+ reaper.LeechStart = LeechStartGeneric
+ reaper.LeechAbort = LeechAbortGeneric
+ file.leechFuncs[reaper.classname] <- reaper
+
+ // Drone leech
+ LeechFuncInfo drone
+ drone.classname = "npc_drone"
+ drone.DoLeech = LeechGeneric
+ drone.LeechStart = LeechStartGeneric
+ drone.LeechAbort = LeechAbortGeneric
+ file.leechFuncs[drone.classname] <- drone
+
+ // Gunship leech
+ LeechFuncInfo gunship
+ gunship.classname = "npc_gunship"
+ gunship.DoLeech = LeechGeneric
+ gunship.LeechStart = LeechStartGeneric
+ gunship.LeechAbort = LeechAbortGeneric
+ file.leechFuncs[gunship.classname] <- gunship
+
+ LeechFuncInfo relay
+ relay.classname = "logic_relay"
+ relay.DoLeech = Leech_LogicRelay
+ file.leechFuncs[relay.classname] <- relay
+
+ LeechFuncInfo physbox
+ physbox.classname = "func_physbox"
+ physbox.DoLeech = Leech_FuncPhysbox
+ file.leechFuncs[physbox.classname] <- physbox
+}
+
+void function EnableLeeching( entity self )
+{
+ self.SetUsePrompts( "#DEFAULT_HACK_HOLD_PROMPT", "#DEFAULT_HACK_HOLD_PROMPT" )
+
+ Leech_SetLeechable( self )
+}
+
+void function DisableLeeching( entity self )
+{
+ if ( !IsValid_ThisFrame( self ) )
+ return
+
+ self.SetUsePrompts( " ", " " )
+
+ Leech_ClearLeechable( self )
+}
+
+void function StartLeechingProgress( entity self, entity leecher )
+{
+ self.Signal( "OnStartLeech" )
+ leecher.Signal( "OnStartLeech" )
+ self.ai.leechInProgress = true
+ self.ai.leechStartTime = Time()
+
+ TryLeechStartCallback( self, leecher )
+}
+
+void function StopLeechingProgress( entity self )
+{
+ self.ai.leechInProgress = false
+ self.ai.leechStartTime = -1
+}
+
+// called when any entity gets leeched
+void function DoLeechAnimEvent( entity self )
+{
+ entity leecher = expect entity( GetOptionalAnimEventVar( self, "leech_switchteam" ) )
+
+ DoLeech( self, leecher )
+}
+
+void function DoLeech( entity self, entity leecher )
+{
+ if ( !IsLeechable( self ) )
+ EnableLeeching( self )
+
+ Assert( "s" in self, "Self " + self + " has no .s" )
+ Assert( leecher )
+
+ // DEPRECATED- no scripts are currently using the results.player functionality- slayback
+ // logic_relays get Triggered when the object is leeched
+ //local results = {}
+ //results.player <- leecher
+ //self.Signal( "OnLeeched", results )
+ //leecher.Signal( "OnLeeched", results )
+
+ Signal( self, "OnLeeched" )
+ Signal( leecher, "OnLeeched" )
+
+ //DisableLeeching( self )
+
+ //_EnableLeechedPointMessage()
+
+ if ( leecher.IsPlayer() )
+ {
+ if ( self.IsNPC() )
+ self.SetBossPlayer( leecher )
+
+ TableRemoveDeadByKey( leecher.p.leechedEnts )
+
+ leecher.p.leechedEnts[ self ] <- self
+
+ // this will kill a random leeched ent from within the team, exluding the current target.
+ if ( WIFI_HACK_OVERFLOW_DIES )
+ ReleaseLeechOverflow( leecher, self )
+ }
+
+ if ( self.IsNPC() )
+ {
+ SetTeam( self, leecher.GetTeam() )
+ SetSquad( self, "" )
+ self.SetAutoSquad()
+ self.ClearPotentialThreatPos()
+ self.DisableBehavior( "Assault" )
+
+ foreach ( trigger in self.e.sonarTriggers )
+ {
+ OnSonarTriggerLeaveInternal( trigger, self )
+ }
+
+ #if DEV
+ // if crosshair spawned, switch squad so he isn't mixed in a squad with opposing team spectres
+ string squadname = expect string( self.kv.squadname )
+ if ( squadname.find( "crosshairSpawnSquad" ) != null )
+ self.SetSquad( "crosshairSpawnSquad_team_" + self.GetTeam() + "_" + self.GetClassName() )
+ #endif
+ }
+
+ // call a class specific leeching function for custom behavior
+ string targetCN = self.GetClassName()
+ if ( targetCN in file.leechFuncs )
+ {
+ LeechFuncInfo info = file.leechFuncs[ targetCN ]
+
+ // Assert( "DoLeech" in file.leechFuncs[ targetCN ] ) // not sure how to check legit functionref -slayback
+ void functionref(entity,entity) classLeechingFunc = file.leechFuncs[ targetCN ].DoLeech
+ thread classLeechingFunc( self, leecher )
+ }
+}
+
+array<entity> function GetTeamLeechedEnts( int team )
+{
+ array<entity> players = GetPlayerArrayOfTeam( team )
+ int totalCount = 0
+
+ array<entity> leechedArray
+ foreach ( player in players )
+ {
+ if ( IsValid( player ) && !player.IsBot() )
+ leechedArray.extend( GetLeechedEnts( player ) )
+ }
+
+ return leechedArray
+}
+
+array<entity> function GetLeechedEnts( entity leecher = null )
+{
+ array<entity> ents
+
+ foreach ( entity ent in leecher.p.leechedEnts )
+ {
+ if ( IsAlive( ent ) )
+ ents.append( ent )
+ }
+
+ return ents
+}
+
+void function TryLeechStartCallback( entity self, entity leecher )
+{
+ string leechtargetCN = self.GetClassName()
+ if ( leechtargetCN in file.leechFuncs )
+ {
+ if ( "LeechStart" in file.leechFuncs[ leechtargetCN ] )
+ {
+ void functionref(entity,entity) leechStartFunc = file.leechFuncs[ leechtargetCN ].LeechStart
+ thread leechStartFunc( self, leecher )
+ }
+ }
+}
+
+void function TryLeechAbortCallback( entity self, entity leecher )
+{
+ string leechtargetCN = self.GetClassName()
+ if ( leechtargetCN in file.leechFuncs )
+ {
+ if ( "LeechAbort" in file.leechFuncs[ leechtargetCN ] )
+ {
+ void functionref(entity,entity) leechAbortFunc = file.leechFuncs[ leechtargetCN ].LeechAbort
+ thread leechAbortFunc( self, leecher )
+ }
+ }
+}
+
+void function DataKnifeSuccessSounds( entity player )
+{
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( "dataknife_complete", "dataknife_complete_3p", player, player )
+}
+
+void function DataKnifeCanceledSounds( entity player )
+{
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( "dataknife_aborted", "dataknife_aborted_3p", player, player )
+}
+
+
+// --- CLASS SPECIFIC LEECH FUNCTIONS ---
+void function Leech_LogicRelay( entity self, entity leecher )
+{
+ Assert( self.GetClassName() == "logic_relay" )
+
+ // logic_relays get Triggered when the object is leeched
+ EntFire( self, "Trigger" )
+}
+
+void function Leech_FuncPhysbox( entity self, entity leecher )
+{
+ Assert( self.GetClassName() == "func_physbox" )
+
+ EntFire( self, "FireUser1" )
+}
+
+
+void function MarvinWeaponsFree( entity self )
+{
+ Assert( IsAlive( self ), self + " is dead, not alive!" )
+
+ // already have a weapon
+ if ( !self.GetActiveWeapon() )
+ return
+
+ self.EndSignal( "OnStopLeeched" )
+ self.EndSignal( "OnDeath" )
+ self.EndSignal( "OnTakeWeapon" )
+
+ OnThreadEnd(
+ function () : ( self )
+ {
+ if ( !IsAlive( self ) )
+ return
+ }
+ )
+
+ // its combat, time to get the hate on
+ EntFire( self, "UnholsterWeapon" )
+
+ WaitForever()
+}
+
+
+void function LeechStart_Marvin( entity self, entity leecher )
+{
+ //self.SetSkin( 4 )
+ EmitSoundOnEntity( self, MARVIN_EMOTE_SOUND_PAIN )
+}
+
+void function LeechAbort_Marvin( entity self, entity leecher )
+{
+ //self.SetSkin( 1 ) // happy
+ EmitSoundOnEntity( self, MARVIN_EMOTE_SOUND_SAD )
+}
+
+
+// Spectre leech
+
+void function Leech_Spectre( entity self, entity leecher )
+{
+ thread Leech_SpectreThread( self, leecher )
+}
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function Leech_SpectreThread( entity self, entity leecher )
+{
+ Assert( self.GetClassName() == "npc_spectre" )
+
+ self.EndSignal( "OnDestroy" )
+ self.EndSignal( "OnDeath" )
+ self.EndSignal( "OnLeeched" )
+
+ EmitSoundOnEntity( self, MARVIN_EMOTE_SOUND_HAPPY )
+
+ Assert( leecher.IsPlayer() )
+
+ leecher.EndSignal( "OnDestroy" )
+
+ AddPlayerScore( leecher, "LeechSpectre" )
+
+ float timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( leecher, SFLAG_HUNTER_SPECTRE ) )
+ timerCredit *= 2.5
+ DecrementBuildTimer( leecher, timerCredit )
+
+ NPCFollowsPlayer( self, leecher )
+
+ OnThreadEnd(
+ function() : ( self, leecher )
+ {
+ // leecher is still connected so don't kill the spectre
+ if ( IsValid( leecher ) && !IsDisconnected( leecher ) )
+ return
+
+ // leecher is disconnected so kill the spectre
+ if ( IsAlive( self ) )
+ self.Die()
+ }
+ )
+
+ foreach ( callbackFunc in svGlobal.onLeechedCustomCallbackFunc )
+ {
+ callbackFunc( self, leecher )
+ }
+
+ WaitForever()
+}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+void function LeechGeneric( entity self, entity leecher )
+{
+ thread LeechGenericThread( self, leecher )
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Run after the npc is successfully leeched
+// HACK: Should make this used by all leeched ents to avoid copy/pasted duplication. Will switch Spectre over to use this soon
+void function LeechGenericThread( entity self, entity leecher )
+{
+ Assert( IsValid( self ) )
+ self.EndSignal( "OnDestroy" )
+ self.EndSignal( "OnDeath" )
+ self.EndSignal( "OnLeeched" )
+
+ Assert( leecher.IsPlayer() )
+ leecher.EndSignal( "OnDestroy" )
+
+ string leechSound
+ float timerCredit
+
+ //--------------------------------------------
+ // Handle class-specific stuff
+ //---------------------------------------------
+ switch ( self.GetClassName() )
+ {
+ case "npc_spectre":
+ leechSound = MARVIN_EMOTE_SOUND_HAPPY
+ AddPlayerScore( leecher, "LeechSpectre" )
+ timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( leecher, SFLAG_HUNTER_SPECTRE ) )
+ timerCredit *= 2.5
+ break
+ case "npc_super_spectre":
+ leechSound = MARVIN_EMOTE_SOUND_HAPPY
+ AddPlayerScore( leecher, "LeechSuperSpectre" )
+ timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
+ break
+ case "npc_drone":
+ leechSound = MARVIN_EMOTE_SOUND_HAPPY
+ AddPlayerScore( leecher, "LeechDrone" )
+ timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
+ break
+ case "npc_gunship":
+ leechSound = MARVIN_EMOTE_SOUND_HAPPY
+ timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
+ break
+ default:
+ Assert( 0, "Unhandled hacked entity: " + self.GetClassName() )
+ }
+
+ EmitSoundOnEntity( self, leechSound )
+ DecrementBuildTimer( leecher, timerCredit )
+
+ // Multiplayer the leeched NPCs still follow the player, but in SP we don't want them to
+ if ( IsMultiplayer() )
+ NPCFollowsPlayer( self, leecher )
+
+ //--------------------------------------------
+ // Any leech custom callback funcs?
+ //---------------------------------------------
+ foreach ( callbackFunc in svGlobal.onLeechedCustomCallbackFunc )
+ {
+ callbackFunc( self, leecher )
+ }
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+void function LeechStartGeneric( entity self, entity leecher )
+{
+ string leechStartSound
+
+ switch( self.GetClassName() )
+ {
+ case "npc_spectre":
+ leechStartSound = MARVIN_EMOTE_SOUND_PAIN
+ break
+ case "npc_super_spectre":
+ leechStartSound = MARVIN_EMOTE_SOUND_PAIN
+ break
+ case "npc_drone":
+ leechStartSound = MARVIN_EMOTE_SOUND_PAIN
+ break
+ case "npc_gunship":
+ leechStartSound = MARVIN_EMOTE_SOUND_PAIN
+ break
+ }
+ Assert( leechStartSound != "", "Couldn't find leechStartSound for: " + self )
+
+ EmitSoundOnEntity( self, leechStartSound )
+}
+/////////////////////////////////////////////////////////////////////////////////////
+void function LeechAbortGeneric( entity self, entity leecher )
+{
+ string leechAbortSound
+
+ switch( self.GetClassName() )
+ {
+ case "npc_spectre":
+ leechAbortSound = MARVIN_EMOTE_SOUND_SAD
+ break
+ case "npc_super_spectre":
+ leechAbortSound = MARVIN_EMOTE_SOUND_SAD
+ break
+ case "npc_drone":
+ leechAbortSound = MARVIN_EMOTE_SOUND_SAD
+ break
+ case "npc_gunship":
+ leechAbortSound = MARVIN_EMOTE_SOUND_SAD
+ break
+ }
+ Assert( leechAbortSound != "", "Couldn't find leechAbortSound for: " + self )
+
+ EmitSoundOnEntity( self, leechAbortSound )
+
+}
+
+// --- END CLASS SPECIFIC LEECH FUNCTIONS --- \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/pilot/_pilot_leeching.gnut b/Northstar.CustomServers/scripts/vscripts/pilot/_pilot_leeching.gnut
new file mode 100644
index 000000000..596ca7118
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/pilot/_pilot_leeching.gnut
@@ -0,0 +1,610 @@
+global function PlayerLeeching_Init
+
+global function LeechSurroundingSpectres
+global function CodeCallback_LeechStart
+global function LeechPropagate
+global function ReleaseLeechOverflow
+global function IsBeingLeeched
+
+// 384 ~ 32 feet
+// 256 ~ 21.3 feet
+// 192 ~ 16 feet
+// 128 ~ 10.6 feet
+const float SPECTRE_LEECH_SURROUNDING_RANGE = 384.0
+
+#if MP
+const int GLOBAL_LEECH_LIMIT = 100 // per team
+const int MAX_LEECHABLE = 100 // per player
+const bool PROPAGATE_ON_LEECH = true
+#elseif SP
+const int GLOBAL_LEECH_LIMIT = 8
+const int MAX_LEECHABLE = 4
+const bool PROPAGATE_ON_LEECH = false
+#endif
+
+void function PlayerLeeching_Init()
+{
+ #if SERVER
+ PrecacheModel( DATA_KNIFE_MODEL )
+ #endif
+}
+
+void function PlayerStopLeeching( entity player, entity target )
+{
+ Assert( target != null )
+ Assert( player.p.leechTarget == target )
+
+ StopLeechingProgress( player.p.leechTarget )
+
+ player.p.leechTarget = null
+}
+
+void function CodeCallback_LeechStart( entity player, entity target )
+{
+ thread LeechStartThread( player, target )
+}
+
+void function LeechStartThread( entity player, entity target )
+{
+ if ( !IsAlive( target ) )
+ return
+
+ if ( !IsAlive( player ) )
+ return
+
+ LeechActionInfo action = FindLeechAction( player, target )
+ if ( !action.isValid )
+ return
+
+/*
+ if ( player.ContextAction_IsActive()
+ || player.ContextAction_IsActive()
+ || target.ContextAction_IsActive() )
+ {
+ return
+ }
+*/
+
+ player.EndSignal( "ScriptAnimStop" )
+ player.EndSignal( "OnDeath" )
+ target.EndSignal( "OnDestroy" )
+ target.EndSignal( "OnDeath" )
+ target.EndSignal( "ScriptAnimStop" )
+
+ StartLeechingProgress( target, player )
+
+ LeechData e
+ e.playerStartOrg = player.GetOrigin()
+ e.targetStartPos = target.GetOrigin()
+
+ OnThreadEnd
+ (
+ function() : ( e, player, target )
+ {
+ if ( IsValid( player ) )
+ {
+ player.SetSyncedEntity( null )
+ if ( player.ContextAction_IsLeeching() )
+ player.Event_LeechEnd()
+
+ // reset to start position in case animation moves us at all
+ //player.SetOrigin( e.playerStartOrg )
+ player.Anim_Stop()
+ player.UnforceStand()
+
+ // done with first person anims
+ ClearPlayerAnimViewEntity( player )
+ DeployAndEnableWeapons( player )
+ }
+
+ if ( IsValid( target ) )
+ {
+ if ( !e.success )
+ {
+ if ( IsValid( player ) )
+ {
+ TryLeechAbortCallback( target, player ) //Make "failed leech" sounds play here after exiting leech animation
+ }
+ }
+
+ #if MP
+ target.SetUsable()
+ #endif
+ target.SetNoTarget( false )
+ target.SetNoTargetSmartAmmo( false )
+ target.Anim_Stop()
+ target.ClearParent()
+ if ( IsAlive( target ) )
+ {
+ // Note that e.targetStartPos is not guarranteed to be a safe spot since we can have moving geo in the game now
+ PutEntityInSafeSpot( target, null, null, target.GetOrigin(), target.GetOrigin() )
+ }
+
+ if ( target.ContextAction_IsLeeching() )
+ target.Event_LeechEnd()
+
+ }
+
+ foreach ( knife in e.knives )
+ {
+ if ( IsValid( knife ) )
+ {
+ knife.Destroy()
+ }
+
+ }
+
+ if ( IsValid( player ) && player.p.leechTarget )
+ {
+ PlayerStopLeeching( player, player.p.leechTarget )
+ }
+
+ if ( IsValid( e.ref ) )
+ {
+ if ( IsValid( player ) )
+ player.ClearParent()
+
+ if ( IsValid( target ) )
+ target.ClearParent()
+
+ //printt( "kill the ref" )
+ if ( IsValid( e.ref ) && !e.ref.IsPlayer() )
+ e.ref.Destroy()
+ }
+ }
+ )
+
+ Assert( player.p.leechTarget == null )
+ player.p.leechTarget = target
+ player.Event_LeechStart()
+ target.Event_LeechStart()
+ player.ForceStand()
+ HolsterAndDisableWeapons( player )
+
+ float leechTime = svGlobal.defaultPilotLeechTime
+ if ( PlayerHasPassive( player, ePassives.PAS_FAST_HACK ) )
+ leechTime *= 0.85
+
+ e.leechTime = leechTime
+
+ #if MP
+ target.UnsetUsable()
+ #endif
+ target.SetNoTarget( true )
+ target.SetNoTargetSmartAmmo( true )
+
+ if ( IsSpectre( target ) )
+ TellSquadmatesSpectreIsGettingLeeched( target, player )
+
+ waitthread PlayerLeechTargetAnimation( player, target, action, e )
+
+ e.leechStartTime = Time()
+ Remote_CallFunction_Replay( player, "ServerCallback_DataKnifeStartLeech", e.leechTime )
+ waitthread WaittillFinishedLeeching( player, target, e )
+
+ if ( e.success )
+ {
+ thread DataKnifeSuccessSounds( player )
+
+ DoLeech( target, player )
+ PlayerStopLeeching( player, target )
+
+ // this will kill a random leeched ent from within the team, exluding the current target. When it's not done elsewhere
+ if ( !WIFI_HACK_OVERFLOW_DIES )
+ ReleaseLeechOverflow( player, target )
+
+ //this is called when the player leeches - not when the system is leeching other spectres
+ if ( PROPAGATE_ON_LEECH && IsSpectre( target ) )
+ LeechSurroundingSpectres( target.GetOrigin(), player )
+ }
+ else
+ {
+ DataKnifeCanceledSounds( player )
+ Remote_CallFunction_Replay( player, "ServerCallback_DataKnifeCancelLeech" )
+ PlayerStopLeeching( player, player.p.leechTarget )
+ }
+
+ waitthread PlayerExitLeechingAnim( player, target, action, e )
+}
+
+void function TellSquadmatesSpectreIsGettingLeeched( entity spectre, entity player )
+{
+ string squadName = expect string( spectre.kv.squadname )
+ if ( squadName == "" )
+ return
+
+ array<entity> squad = GetNPCArrayBySquad( squadName )
+ squad.removebyvalue( spectre )
+
+ foreach ( squadMate in squad )
+ {
+ //printt( "Setting enemy of " + squadMate + " to player: " + player )
+ squadMate.SetEnemyLKP( player, player.GetOrigin() )
+ }
+}
+
+void function ReleaseLeechOverflow( entity player, entity lastLeeched )
+{
+ array<entity> teamLeechedEnts = GetTeamLeechedEnts( player.GetTeam() )
+ array<entity> leechedEnts = GetLeechedEnts( player )
+ int globalOverflow = GLOBAL_LEECH_LIMIT - teamLeechedEnts.len()
+ int playerOverflow = MAX_LEECHABLE - leechedEnts.len()
+
+ int overflow = minint( globalOverflow, playerOverflow )
+
+ if ( overflow >= 0 )
+ return
+
+ overflow = abs( overflow )
+
+ teamLeechedEnts.randomize()
+ foreach ( ent in teamLeechedEnts )
+ {
+ if ( lastLeeched == ent )
+ continue
+
+ entity owner = ent.GetBossPlayer()
+ Assert( owner.IsPlayer() )
+
+
+ // I think it's better to kill the overflow then have it become an enemy again.
+ ent.Die()
+
+ delete owner.p.leechedEnts[ ent ]
+ overflow--
+
+ if ( overflow == 0 )
+ break
+ }
+
+ Assert( overflow == 0 )
+}
+
+
+int function GetMaxNumberOfLeechedEnts( entity player )
+{
+ int teamLeechedCount = GetTeamLeechedEnts( player.GetTeam() ).len()
+ int leechedEntsCount = GetLeechedEnts( player ).len()
+ int teamLimit = maxint( 0, GLOBAL_LEECH_LIMIT - teamLeechedCount )
+ int maxSize = maxint( 0, MAX_LEECHABLE - leechedEntsCount )
+ maxSize = minint( teamLimit, maxSize )
+
+ return maxSize
+}
+
+void function LeechSurroundingSpectres( vector origin, entity player )
+{
+ array<entity> enemySpectreArray = GetNPCArrayEx( "npc_spectre", TEAM_ANY, player.GetTeam(), player.GetOrigin(), SPECTRE_LEECH_SURROUNDING_RANGE )
+
+ if ( !enemySpectreArray.len() )
+ return
+
+ // don't resize the array if we should kill the overflow instead
+ if ( !WIFI_HACK_OVERFLOW_DIES )
+ {
+ int maxSize = GetMaxNumberOfLeechedEnts( player )
+ int newSize = minint( enemySpectreArray.len(), maxSize )
+
+ enemySpectreArray.resize( newSize, null )
+ }
+
+ foreach ( spectre in enemySpectreArray )
+ {
+ thread LeechPropagate( spectre, player )
+ }
+
+ if ( enemySpectreArray.len() )
+ {
+ if ( PlayerHasPassive( player, ePassives.PAS_WIFI_SPECTRE ) )
+ {
+ EmitSoundOnEntity( player, "BurnCard_WiFiVirus_TurnSpectre" )
+ printt( "play BurnCard_WiFiVirus_TurnSpectre" )
+ }
+ }
+}
+
+void function LeechPropagate( entity spectre, entity player )
+{
+ if ( spectre.ContextAction_IsActive() )
+ return
+
+ if ( !spectre.IsInterruptable() )
+ return
+
+ if ( spectre.GetParent() )
+ return
+
+ if ( !Leech_IsLeechable( spectre ) )
+ return
+
+ player.EndSignal( "OnDestroy" )
+ spectre.EndSignal( "OnDestroy" )
+ spectre.EndSignal( "OnDeath" )
+
+ spectre.Event_LeechStart()
+
+ AddAnimEvent( spectre, "leech_switchteam", DoLeechAnimEvent, player )
+
+ OnThreadEnd(
+ function() : ( spectre )
+ {
+ if ( IsValid( spectre ) )
+ {
+ DeleteAnimEvent( spectre, "leech_switchteam" )
+
+ if ( spectre.ContextAction_IsLeeching() )
+ spectre.Event_LeechEnd()
+ }
+ }
+ )
+
+ spectre.Anim_Stop()
+ waitthread PlayAnim( spectre, "sp_reboot" )
+ spectre.SetVelocity( Vector(0,0,0) )
+}
+
+void function WaittillFinishedLeeching( entity player, entity target, LeechData e )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "ScriptAnimStop" )
+ target.EndSignal( "OnDeath" )
+
+ if ( !player.UseButtonPressed() )
+ return
+
+ float waitTime = e.leechTime
+ float timePassed = Time() - e.leechStartTime
+ waitTime -= timePassed
+ if ( waitTime > 0 )
+ {
+ float startTime = Time()
+ while ( Time() < startTime + waitTime && player.UseButtonPressed() )
+ {
+ WaitFrame()
+ }
+ }
+
+ if ( player.UseButtonPressed() )
+ e.success = true
+}
+
+/////////////////////////////////////////////////////////////
+bool function IsLeechTargetUsedAsAnimNode( entity target )
+{
+ return target.AISetting_LeechAnimTag() != ""
+}
+
+/////////////////////////////////////////////////////////////
+void function PlayerLeechTargetAnimation( entity player, entity target, LeechActionInfo action, LeechData e )
+{
+ Assert( action.isValid )
+ vector targetStartOrg = target.GetOrigin()
+ vector targetStartAng = target.GetAngles()
+
+ vector initialPlayerPosition = player.GetOrigin()
+ vector initialTargetPosition = target.GetOrigin()
+
+ vector endOrigin = target.GetOrigin()
+ vector startOrigin = player.GetOrigin()
+ vector refVec = endOrigin - startOrigin
+ string animTag
+
+ FirstPersonSequenceStruct playerSequence
+
+ //---------------------------------------------------------
+ // Leech anims played on the leech target, or at player position?
+ //---------------------------------------------------------
+ if ( IsLeechTargetUsedAsAnimNode( target ) )
+ {
+ e.ref = CreateLeechingScriptMoverBetweenEnts( player, target )
+ animTag = target.AISetting_LeechAnimTag()
+ Assert( animTag != "" )
+ e.ref.SetOrigin( target.GetOrigin() )
+ e.ref.SetParent( target, animTag )
+ }
+ else
+ {
+ e.ref = player
+ e.ref.SetOrigin( e.playerStartOrg )
+ playerSequence.playerPushable = true
+ }
+
+ e.ref.EndSignal( "OnDestroy" )
+
+ //-----------------------------------------------------------------
+ // Player FirstPersonSequence for the leeching
+ //-----------------------------------------------------------------
+ playerSequence.blendTime = 0.25
+ playerSequence.attachment = "ref"
+
+ //-----------------------------------------------------------------
+ // Only create FirstPersonSequence for leech target if anims exist
+ //-----------------------------------------------------------------
+ bool haveTargetSequence = false
+ FirstPersonSequenceStruct targetSequence
+
+ if ( action.targetAnimation3pStart != "" )
+ {
+ targetSequence = clone playerSequence
+ haveTargetSequence = true
+ }
+
+ playerSequence.thirdPersonAnim = action.playerAnimation3pStart
+ playerSequence.thirdPersonAnimIdle = action.playerAnimation3pIdle
+ playerSequence.firstPersonAnim = action.playerAnimation1pStart
+ playerSequence.firstPersonAnimIdle = action.playerAnimation1pIdle
+
+ entity viewmodel = player.GetFirstPersonProxy()
+
+ if ( !HasAnimEvent( viewmodel, "PlaySound_DataKnife_Hack_Spectre_Pt1" ) )
+ AddAnimEvent( viewmodel, "PlaySound_DataKnife_Hack_Spectre_Pt1", PlaySound_DataKnife_Hack_Spectre_Pt1 )
+
+ if ( !HasAnimEvent( viewmodel, "PlaySound_DataKnife_Hack_Spectre_Pt2" ) )
+ AddAnimEvent( viewmodel, "PlaySound_DataKnife_Hack_Spectre_Pt2", PlaySound_DataKnife_Hack_Spectre_Pt2 )
+
+ if ( !HasAnimEvent( viewmodel, "PlaySound_DataKnife_Hack_Spectre_Pt3" ) )
+ AddAnimEvent( viewmodel, "PlaySound_DataKnife_Hack_Spectre_Pt3", PlaySound_DataKnife_Hack_Spectre_Pt3 )
+
+ if ( !HasAnimEvent( viewmodel, "PlaySound_Spectre_Servo_Heavy_Short" ) )
+ AddAnimEvent( viewmodel, "PlaySound_Spectre_Servo_Heavy_Short", PlaySound_Spectre_Servo_Heavy_Short )
+
+ if ( !HasAnimEvent( viewmodel, "PlaySound_DataKnife_Hack_Spectre_ArmorRattle" ) )
+ AddAnimEvent( viewmodel, "PlaySound_DataKnife_Hack_Spectre_ArmorRattle", PlaySound_DataKnife_Hack_Spectre_ArmorRattle )
+
+ if ( haveTargetSequence )
+ {
+ targetSequence.thirdPersonAnim = action.targetAnimation3pStart
+ targetSequence.thirdPersonAnimIdle = action.targetAnimation3pIdle
+ }
+
+ playerSequence.noParent = true
+
+ //-----------------------------------
+ // Data knife
+ //-----------------------------------
+ asset model = DATA_KNIFE_MODEL
+
+ string knifeTag = GetTagForDataknife( target )
+ entity thirdPersonKnife = CreatePropDynamic( model )
+ SetTargetName( thirdPersonKnife, "thirdPersonKnife" )
+ thirdPersonKnife.SetParent( player, knifeTag, false, 0.0 )
+ e.knives.append( thirdPersonKnife )
+
+ SetForceDrawWhileParented( target, true )
+
+ //------------------------------------------------------------------------------
+ // Play leech anim sequence for player, but only for target if leech anims exist
+ //-------------------------------------------------------------------------------
+ player.SetSyncedEntity( target )
+ entity ref = e.ref
+ if ( haveTargetSequence )
+ thread Animate_PlayerLeechTarget( targetSequence, target, ref )
+
+ waitthread FirstPersonSequence( playerSequence, player, null )
+}
+
+
+//Basically copy pasted from CreateMeleeScriptMoverBetweenEnts
+entity function CreateLeechingScriptMoverBetweenEnts( entity attacker, entity target )
+{
+ vector endOrigin = target.GetOrigin()
+ vector startOrigin = attacker.GetOrigin()
+ vector refVec = endOrigin - startOrigin
+
+ vector refAng = VectorToAngles( refVec )
+ float pitch = refAng.x
+ if ( pitch > 180 )
+ pitch -= 360
+ if ( fabs( pitch ) > 35 ) //If pitch is too much, use angles from target
+ refAng = target.GetAngles() // Leech does it from behind target, so use target's angles.
+
+ vector refPos = endOrigin - refVec * 0.5
+
+ entity ref = CreateOwnedScriptMover( attacker )
+ ref.SetOrigin( refPos )
+ ref.SetAngles( refAng )
+
+ return ref
+}
+
+void function Animate_PlayerLeechTarget( FirstPersonSequenceStruct targetSequence, entity target, entity ref )
+{
+ ref.EndSignal( "OnDestroy" )
+ target.EndSignal( "OnDestroy" )
+ waitthread FirstPersonSequence( targetSequence, target, ref )
+}
+
+void function PlayerExitLeechingAnim( entity player, entity target, LeechActionInfo action, LeechData e )
+{
+ FirstPersonSequenceStruct playerSequence
+ playerSequence.blendTime = 0.3
+ playerSequence.attachment = "ref"
+ playerSequence.teleport = false
+ playerSequence.noParent = true
+ playerSequence.playerPushable = true
+
+ //--------------------------------------
+ // Target animates only if he has anims
+ //---------------------------------------
+ bool hasTargetSequence = false
+ FirstPersonSequenceStruct targetSequence
+ if ( action.targetAnimation3pEnd != "" )
+ {
+ targetSequence = clone playerSequence
+ hasTargetSequence = true
+ }
+
+ playerSequence.thirdPersonAnim = action.playerAnimation3pEnd
+ playerSequence.firstPersonAnim = action.playerAnimation1pEnd
+ playerSequence.snapPlayerFeetToEyes = false
+
+ entity ref = e.ref
+
+ if ( hasTargetSequence )
+ {
+ targetSequence.thirdPersonAnim = action.targetAnimation3pEnd
+ thread FirstPersonSequence( targetSequence, target, ref )
+ }
+ waitthread FirstPersonSequence( playerSequence, player, null )
+
+ //-------------------------------------------------------------
+ // Detach from rodeo if applicable (drones, superspectres, etc)
+ //-------------------------------------------------------------
+ if ( Rodeo_IsAttached( player ) )
+ player.Signal( "RodeoOver" )
+}
+
+bool function IsBeingLeeched( entity npc )
+{
+ return npc.ai.leechInProgress
+}
+
+void function PlaySound_DataKnife_Hack_Spectre_Pt1( entity playerFirstPersonProxy )
+{
+ entity player = playerFirstPersonProxy.GetOwner()
+ if ( !IsValid( player ) )
+ return
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "DataKnife_Hack_Spectre_Pt1" )
+
+}
+
+void function PlaySound_DataKnife_Hack_Spectre_Pt2( entity playerFirstPersonProxy )
+{
+ entity player = playerFirstPersonProxy.GetOwner()
+ if ( !IsValid( player ) )
+ return
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "DataKnife_Hack_Spectre_Pt2" )
+
+}
+
+void function PlaySound_DataKnife_Hack_Spectre_Pt3( entity playerFirstPersonProxy )
+{
+ entity player = playerFirstPersonProxy.GetOwner()
+ if ( !IsValid( player ) )
+ return
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "DataKnife_Hack_Spectre_Pt3" )
+
+}
+
+void function PlaySound_Spectre_Servo_Heavy_Short( entity playerFirstPersonProxy )
+{
+ entity player = playerFirstPersonProxy.GetOwner()
+ if ( !IsValid( player ) )
+ return
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Spectre.Servo.Heavy.Short" )
+
+}
+
+void function PlaySound_DataKnife_Hack_Spectre_ArmorRattle( entity playerFirstPersonProxy )
+{
+ entity player = playerFirstPersonProxy.GetOwner()
+ if ( !IsValid( player ) )
+ return
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "DataKnife_Hack_Spectre_ArmorRattle" )
+
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/pilot/_slamzoom.nut b/Northstar.CustomServers/scripts/vscripts/pilot/_slamzoom.nut
new file mode 100644
index 000000000..83ee3916d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/pilot/_slamzoom.nut
@@ -0,0 +1,85 @@
+untyped
+
+global function ShouldSlamzoomSpawn
+global function SlammzoomSpawn
+
+const SLAMZOOM_WHOOSH_SOUND = "UI_InGame_MarkedForDeath_PlayerUnmarked"
+const SLAMZOOM_COLOR_CORRECTION = "materials/correction/player_electric_damage.raw"
+
+function ShouldSlamzoomSpawn()
+{
+ return ( GetCurrentPlaylistVarInt( "enable_slamzoom_spawn", 0 ) == 1 )
+}
+
+function SlammzoomSpawn( entity player, vector origin, vector angles, entity friendlyPilot = null )
+{
+ player.EndSignal( "OnDestroy" )
+ player.SetOrigin( origin )
+ player.SnapEyeAngles( angles )
+
+ vector landorigin = player.EyePosition()
+
+ // slamzoom
+ int slamzoom_height = 4000
+ float slamzoom_time1 = 0.5
+ float slamzoom_time2 = 0.7
+ float slamzoom_rotate_time = 0.3
+ int slamzoom_angle = 90
+ int enter_angle = 90 - slamzoom_angle
+
+ vector start_angles = Vector( -90-enter_angle, angles.y, 0 )
+ vector start_vector = AnglesToForward( start_angles )
+
+ // origin = origin + Vector(0,0,48)
+
+ entity camera = CreateTitanDropCamera( origin + start_vector * slamzoom_height, Vector( slamzoom_angle, angles.y, 0.0) )
+ camera.Fire( "Enable", "!activator", 0, player )
+
+ entity mover = CreateScriptMover()
+ if ( IsValid( camera ) )
+ {
+ // camera can be invalid for a moment when server shuts down
+ mover.SetOrigin( camera.GetOrigin() )
+ mover.SetAngles( camera.GetAngles() )
+ camera.SetParent( mover )
+ }
+
+ OnThreadEnd(
+ function() : ( mover, camera )
+ {
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ if ( IsValid( camera ) )
+ camera.Destroy()
+ }
+ )
+
+ player.isSpawning = mover
+ mover.MoveTo( mover.GetOrigin() + (start_vector * 100), slamzoom_time1, slamzoom_time1*0.4, slamzoom_time1*0.4 )
+ wait slamzoom_time1
+ EmitSoundOnEntityOnlyToPlayer( player, player, SLAMZOOM_WHOOSH_SOUND )
+ mover.MoveTo( landorigin, slamzoom_time2, slamzoom_time2*0.5, slamzoom_time2*0.2 )
+ wait slamzoom_time2 - slamzoom_rotate_time
+ mover.RotateTo( angles, slamzoom_rotate_time, slamzoom_rotate_time*0.2, slamzoom_rotate_time*0.2 )
+ wait slamzoom_rotate_time
+ player.isSpawning = null
+ wait 0.1
+ if ( IsValid( camera ) )
+ {
+ // camera can be invalid for a moment when server shuts down
+ camera.FireNow( "Disable", "!activator", null, player )
+ }
+
+ if ( IsValid( friendlyPilot ) )
+ {
+ MessageToPlayer( friendlyPilot, eEventNotifications.FriendlyPlayerSpawnedOnYou, player )
+ MessageToPlayer( player, eEventNotifications.SpawnedOnFriendlyPlayer, friendlyPilot )
+ }
+
+ if ( ShouldGivePlayerInfoOnSpawn() )
+ thread GivePlayerInfoOnSpawn( player )
+
+ player.SetOrigin( origin )
+ player.SnapEyeAngles( angles )
+ player.RespawnPlayer( null )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/pilot/_zipline.gnut b/Northstar.CustomServers/scripts/vscripts/pilot/_zipline.gnut
new file mode 100644
index 000000000..a129c4794
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/pilot/_zipline.gnut
@@ -0,0 +1,838 @@
+untyped
+
+global function Zipline_Init
+
+global function GuyZiplinesToGround
+global function GetZiplineSpawns
+global function GetHookOriginFromNode
+global function ZiplineInit
+
+global function CodeCallback_ZiplineMount
+global function CodeCallback_ZiplineStart
+global function CodeCallback_ZiplineMove
+global function CodeCallback_ZiplineStop
+
+global function AddCallback_ZiplineStart
+global function AddCallback_ZiplineStop
+
+global function TrackMoverDirection
+global function CreateRopeEntities
+global function SpawnZiplineEntities
+global function GetZiplineLandingAnims
+global function AnimDoneStuckInSolidFailSafe
+
+struct {
+ array<string> zipLineLandingAnimations = [
+ "pt_zipline_dismount_standF",
+ "pt_zipline_dismount_crouchF",
+ "pt_zipline_dismount_crouch180",
+ "pt_zipline_dismount_breakright",
+ "pt_zipline_land"
+ "pt_zipline_land"
+ "pt_zipline_land"
+ "pt_zipline_land"
+ "pt_zipline_land"
+ "pt_zipline_land"
+ ]
+
+ array<string> zipLinePlayerLandingAnimations = [
+ "pt_zipline_dismount_standF"
+ ]
+
+ array<string> zipLineReadyAnimations = [
+ "pt_zipline_ready_idleA",
+ "pt_zipline_ready_idleB"
+ ]
+} file
+
+//typedef EntitiesDidLoadCallbackType void functionref(entity)
+array<void functionref(entity,entity)> _ZiplineStartCallbacks
+array<void functionref(entity)> _ZiplineStopCallbacks
+
+function Zipline_Init()
+{
+ if ( reloadingScripts )
+ return
+
+ RegisterSignal( "deploy" )
+ RegisterSignal( "stop_deploy" )
+ RegisterSignal( "npc_deployed" )
+
+ VehicleDropshipNew_Init()
+
+ level.MIN_ZIPLINE_LAND_DIST_SQRD <- 128 * 128
+ level.MIN_ZIPLINE_HOOK_DIST_SQRD <- 256 * 256
+ level._info_spawnpoint_dropships <- {}
+ AddSpawnCallback( "info_spawnpoint_dropship", AddToSpawnpointDropships )
+
+ PrecacheParticleSystem( $"hmn_mcorps_jump_jet_wallrun_full" )
+ PrecacheParticleSystem( $"hmn_imc_jump_jet_wallrun_full" )
+ PrecacheParticleSystem( $"P_Zipline_hld_1" )
+
+}
+
+void function AddToSpawnpointDropships( entity self )
+{
+ level._info_spawnpoint_dropships[ self ] <- self
+}
+
+function GetZiplineSpawns()
+{
+ local targets = []
+ foreach ( ent in clone level._info_spawnpoint_dropships )
+ {
+ if ( IsValid( ent ) )
+ {
+ targets.append( ent )
+ continue
+ }
+
+ delete level._info_spawnpoint_dropships[ ent ]
+ }
+
+ return targets
+}
+
+
+function GuyZiplinesToGround( guy, Table )
+{
+ expect entity( guy )
+
+ OnThreadEnd(
+ function() : ( guy )
+ {
+ if ( IsValid( guy ) )
+ guy.SetEfficientMode( false )
+ }
+ )
+
+ local ship = Table.ship
+ local dropPos = GetDropPos( Table )
+
+ // ship didn't find a drop spot
+ if ( dropPos == null )
+ WaitForever()
+
+ //DebugDrawLine( guy.GetOrigin(), dropPos, 255, 0, 0, true, 8.0 )
+
+ local attachOrigin = ship.GetAttachmentOrigin( Table.attachIndex )
+ local nodeOrigin = dropPos
+ local hookOrigin = GetHookOriginFromNode( guy.GetOrigin(), nodeOrigin, attachOrigin )
+
+ // couldn't find a place to hook it? This needs to be tested on precompile
+ if ( !hookOrigin )
+ {
+ printt( "WARNING! Bad zipline dropship position!" )
+ WaitForever()
+ }
+
+ Table.hookOrigin <- hookOrigin
+
+ // Track the movement of the script mover that moves the guy to the ground
+ local e = {}
+
+ waitthread GuyRidesZiplineToGround( guy, Table, e, dropPos )
+
+ //DebugDrawLine( guy.GetOrigin(), dropPos, 255, 0, 135, true, 5.0 )
+
+ if ( !( "forward" in Table ) )
+ {
+ // the sequence ended before the guy reached the ground
+ local start = guy.GetOrigin()
+ // this needs functionification
+ local end = Table.hookOrigin + Vector( 0,0,-80 )
+ TraceResults result = TraceLine( start, end, guy )
+ local angles = guy.GetAngles()
+ Table.forward <- AnglesToForward( angles )
+ Table.origin <- result.endPos
+ }
+
+ // the guy detaches and falls to the ground
+ string landingAnim = file.zipLineLandingAnimations.getrandom()
+ //DrawArrow( guy.GetOrigin(), guy.GetAngles(), 5.0, 80 )
+
+ if ( !guy.IsInterruptable() )
+ return
+
+ guy.Anim_ScriptedPlay( landingAnim )
+ guy.Anim_EnablePlanting()
+
+ ShowName( guy )
+
+ local vec = e.currentOrigin - e.oldOrigin
+
+ guy.SetVelocity( vec * 15 )
+
+ thread AnimDoneStuckInSolidFailSafe( guy )
+}
+
+function AnimDoneStuckInSolidFailSafe( entity guy )
+{
+ guy.EndSignal( "OnDeath" )
+ guy.WaitSignal( "OnAnimationDone" )
+
+ if ( EntityInSolid( guy ) )
+ {
+ vector ornull clampedPos
+ clampedPos = NavMesh_ClampPointForAIWithExtents( guy.GetOrigin(), guy, < 400, 400, 400 > )
+
+ if ( clampedPos != null )
+ {
+ guy.SetOrigin( expect vector( clampedPos ) )
+ printt( guy + " was in solid, teleported" )
+ }
+ }
+}
+
+function TrackMoverDirection( mover, e )
+{
+ mover.EndSignal( "OnDestroy" )
+ // track the way the mover movers, so we can do the
+ // correct velocity on the falling guy
+ local origin = mover.GetOrigin()
+ e.oldOrigin <- origin
+ e.currentOrigin <- origin
+
+ for ( ;; )
+ {
+ WaitFrame()
+ e.oldOrigin = e.currentOrigin
+ e.currentOrigin = mover.GetOrigin()
+ }
+}
+
+function GuyRidesZiplineToGround( entity guy, zipline, e, dropPos )
+{
+ entity mover = CreateOwnedScriptMover( guy )
+ mover.EndSignal( "OnDestroy" )
+
+ thread TrackMoverDirection( mover, e )
+
+ OnThreadEnd(
+ function() : ( mover, zipline, guy )
+ {
+ thread ZiplineRetracts( zipline )
+
+ if ( IsValid( guy ) )
+ {
+ guy.ClearParent()
+ StopSoundOnEntity( guy, "3p_zipline_loop" )
+ EmitSoundOnEntity( guy, "3p_zipline_detach" )
+ }
+
+ if ( IsValid( mover ) )
+ mover.Kill_Deprecated_UseDestroyInstead()
+ }
+ )
+
+
+ local rideDist = Distance( guy.GetOrigin(), zipline.hookOrigin )
+
+ // how long it takes the zipline to travel 1000 units
+ zipline.pinTime <- Graph( rideDist, 0, 1000, 0, 0.4 )
+
+ // how long it takes the zipline to retract,
+ zipline.retractTime <- Graph( rideDist, 0, 1000, 0, 0.5 )
+
+ // how long it takes the rider to ride 1000 units
+ float rideTime = Graph( rideDist, 0, 1000, 0, 2.5 )
+
+
+ // orient the script_mover in the direction its going
+ local angles = guy.GetAngles()
+ local forward = AnglesToForward( angles )
+ local right = AnglesToRight( angles )
+
+ CreateRopeEntities( zipline )
+
+ local zipAttachOrigin = zipline.ship.GetAttachmentOrigin( zipline.attachIndex )
+ zipline.end.SetOrigin( zipAttachOrigin )
+
+ zipline.start.SetParent( zipline.ship, zipline.shipAttach )
+ zipline.mid.SetParent( zipline.ship, zipline.shipAttach )
+
+ // now that the origin is set we can spawn the zipline, otherwise we
+ // see the zipline lerp in
+ SpawnZiplineEntities( zipline )
+
+
+ // the zipline shoots out
+ ZiplineMover( expect entity( zipline.end ), zipline.hookOrigin, zipline.pinTime )
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, zipAttachOrigin, "dropship_zipline_zipfire" )
+ delaythread( zipline.pinTime ) ZiplineMoveCleanup( zipline )
+
+// wait zipline.pinTime * 0.37
+ wait zipline.pinTime
+ EmitSoundAtPosition( TEAM_UNASSIGNED, zipline.hookOrigin, "dropship_zipline_impact" )
+
+ zipline.mid.SetParent( mover, "ref", false )
+ thread MoverMovesToGround( zipline, mover, rideTime )
+
+ if ( !IsAlive( guy ) || !guy.IsInterruptable() )
+ return
+
+ guy.SetParent( mover, "ref", false, 0.0 )
+
+ EmitSoundOnEntity( guy, "3p_zipline_attach" )
+ waitthread PlayAnim( guy, "pt_zipline_ready2slide", mover )
+ EmitSoundOnEntity( guy, "3p_zipline_loop" )
+
+ if ( !IsAlive( guy ) || !guy.IsInterruptable() || guy.GetParent() != mover )
+ return
+
+ // Anim_PlayWithRefPoint requires that the guy be parented to the ref point.
+ thread PlayAnim( guy, ZIPLINE_IDLE_ANIM, mover, "ref" )
+
+ //thread ZiplineAutoClipsToGeo( zipline, mover )
+
+ //wait 0.4 // some time to clear the lip
+
+ local nodeOrigin = dropPos
+ //DebugDrawLine( guy.GetOrigin(), nodeOrigin, 200, 255, 50, true, 8.0 )
+
+ rideDist = Distance( guy.GetOrigin(), nodeOrigin )
+ rideDist -= 100 // for animation at end
+ if ( rideDist < 0 )
+ rideDist = 0
+ rideTime = Graph( rideDist, 0, 100, 0, 0.15 )
+/*
+ printt( "ride time " + rideTime )
+ local endTime = Time() + rideTime
+ for ( ;; )
+ {
+ if ( Time() >= endTime )
+ return
+
+ DebugDrawLine( guy.GetOrigin(), nodeOrigin, 255, 0, 0, true, 0.15 )
+ DebugDrawText( nodeOrigin, ( endTime - Time() ) + "", true, 0.15 )
+ WaitFrame()
+ }
+*/
+ wait rideTime
+
+ thread ZiplineStuckFailsafe( guy, nodeOrigin )
+}
+
+function ZiplineStuckFailsafe( guy, nodeOrigin )
+{
+ TimeOut( 15.0 )
+
+ guy.EndSignal( "OnDeath" )
+
+ guy.WaitSignal( "OnFailedToPath" )
+
+ guy.SetOrigin( nodeOrigin )
+ printt( "Warning: AI Path failsafe at " + nodeOrigin )
+}
+
+function ZiplineMoveCleanup( zipline )
+{
+ // work around for moveto bug
+ if ( IsValid( zipline.end ) )
+ {
+ zipline.end.SetOrigin( zipline.hookOrigin )
+ }
+}
+
+function MoverMovesToGround( zipline, mover, timeTotal )
+{
+ // this handles the start point moving.
+ mover.EndSignal( "OnDestroy" )
+ zipline.ship.EndSignal( "OnDestroy" )
+
+ local origin = zipline.ship.GetAttachmentOrigin( zipline.attachIndex )
+ local angles = zipline.ship.GetAttachmentAngles( zipline.attachIndex )
+ mover.SetOrigin( origin )
+ mover.SetAngles( angles )
+
+ local start = zipline.start.GetOrigin()
+ local end = zipline.hookOrigin + Vector( 0,0,-180 )
+
+ local blendTime = 0.5
+ if ( timeTotal <= blendTime )
+ blendTime = 0
+
+ angles = VectorToAngles( end - start )
+ angles.x = 0
+ angles.z = 0
+
+ mover.MoveTo( end, timeTotal, blendTime, 0 )
+ mover.RotateTo( angles, 0.2 )
+}
+
+
+function WaitUntilZiplinerNearsGround( guy, zipline )
+{
+ local start, end, frac
+ local angles = guy.GetAngles()
+ local forward = AnglesToForward( angles )
+
+ local zipAngles, zipForward, dropDist
+
+ if ( guy.IsNPC() )
+ dropDist = 150
+ else
+ dropDist = 10 //much closer for player
+
+ local mins = guy.GetBoundingMins()
+ local maxs = guy.GetBoundingMaxs()
+
+ TraceResults result
+
+ for ( ;; )
+ {
+ start = guy.GetOrigin()
+ end = start + Vector(0,0,-dropDist)
+ end += forward * dropDist
+// TraceResults result = TraceLine( start, end, guy )
+ result = TraceHull( start, end, mins, maxs, guy, TRACE_MASK_NPCSOLID_BRUSHONLY, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( start, end, 255, 0, 0, true, 0.2 )
+
+ if ( result.fraction < 1.0 )
+ break
+
+ start = guy.GetOrigin()
+ end = zipline.hookOrigin + Vector( 0,0,-80 )
+
+ zipForward = ( end - start )
+ zipForward.Norm()
+ zipForward *= 250
+
+ end = start + zipForward
+ //DebugDrawLine( start, end, 255, 0, 0, true, 0.1 )
+
+// result = TraceLine( start, end, guy )
+ //DebugDrawLine( start, end, 255, 150, 0, true, 0.2 )
+ result = TraceHull( start, end, mins, maxs, guy, TRACE_MASK_NPCSOLID_BRUSHONLY, TRACE_COLLISION_GROUP_NONE )
+
+ if ( result.fraction < 1.0 )
+ break
+
+ WaitFrame()
+ }
+
+ zipline.origin <- result.endPos
+ zipline.forward <- forward
+}
+
+
+function ZiplineRetracts( zipline )
+{
+ if ( !IsValid( zipline.start ) )
+ return
+ if ( !IsValid( zipline.mid ) )
+ return
+ if ( !IsValid( zipline.end ) )
+ return
+
+ OnThreadEnd(
+ function() : ( zipline )
+ {
+ if ( IsValid( zipline.start ) )
+ zipline.start.Kill_Deprecated_UseDestroyInstead()
+
+ if ( IsValid( zipline.mid ) )
+ zipline.mid.Kill_Deprecated_UseDestroyInstead()
+
+ // is the only one that's not parented and only gets deleted here
+ zipline.end.Kill_Deprecated_UseDestroyInstead()
+ }
+ )
+
+ // IsValid check succeeds, even if a delete brought us here.
+ // IsValid should've failed.
+ if ( !IsAlive( expect entity( zipline.ship ) ) )
+ return
+
+ zipline.ship.EndSignal( "OnDestroy" )
+
+ zipline.start.EndSignal( "OnDestroy" )
+ zipline.mid.EndSignal( "OnDestroy" )
+ zipline.end.EndSignal( "OnDestroy" )
+
+ local start, end, mid
+ local startDist
+ local endDist
+ local totalDist
+ local progress
+ local newMidPoint
+ local midRetractProgress
+
+ local startTime = Time()
+ local endTime = startTime + 0.3
+
+ zipline.mid.ClearParent()
+
+ start = zipline.start.GetOrigin()
+ end = zipline.end.GetOrigin()
+ mid = zipline.mid.GetOrigin()
+
+ startDist = Distance( mid, start )
+ endDist = Distance( mid, end )
+ totalDist = startDist + endDist
+
+ if ( totalDist <= 0 )
+ return
+
+ progress = startDist / totalDist
+// newMidPoint = end * progress + start * ( 1 - progress )
+//
+// // how far from the midpoint we are, vertically
+// local mid_z_offset = newMidPoint.z - mid.z
+// local addOffset
+
+ for ( ;; )
+ {
+ start = zipline.start.GetOrigin()
+ end = zipline.end.GetOrigin()
+
+ newMidPoint = end * progress + start * ( 1 - progress )
+
+ midRetractProgress = GraphCapped( Time(), startTime, endTime, 0, 1 )
+ if ( midRetractProgress >= 1.0 )
+ break
+
+ newMidPoint = mid * ( 1 - midRetractProgress ) + newMidPoint * midRetractProgress
+ //addOffset = mid_z_offset * ( 1 - midRetractProgress )
+ //newMidPoint.z -= addOffset
+ //DebugDrawLine( zipline.mid.GetOrigin(), newMidPoint, 255, 0, 0, true, 0.2 )
+
+ if ( !IsValid( zipline.mid ) )
+ {
+ printt( "Invalid zipline mid! Impossible!" )
+ }
+ else
+ {
+ zipline.mid.SetOrigin( newMidPoint )
+ }
+
+
+// startDist = Distance( mid, start )
+// endDist = Distance( mid, end )
+// totalDist = startDist + endDist
+// progress = startDist / totalDist
+ WaitFrame()
+ }
+
+// DebugDrawLine( zipline.start.GetOrigin(), zipline.mid.GetOrigin(), 255, 100, 50, true, 5.0 )
+// DebugDrawLine( zipline.end.GetOrigin(), zipline.mid.GetOrigin(), 60, 100, 244, true, 5.0 )
+ local moveTime = 0.4
+ ZiplineMover( expect entity( zipline.start ), zipline.end.GetOrigin(), moveTime )
+ ZiplineMover( expect entity( zipline.mid ), zipline.end.GetOrigin(), moveTime )
+
+ wait moveTime
+/*
+ startTime = Time()
+ endTime = startTime + zipline.retractTime
+ end = zipline.end.GetOrigin()
+
+ if ( !IsValid( zipline.mid ) )
+ return
+ mid = zipline.mid.GetOrigin()
+
+ local org
+
+ for ( ;; )
+ {
+ start = zipline.start.GetOrigin()
+
+ progress = Graph( Time(), startTime, endTime )
+ if ( progress >= 1.0 )
+ break
+
+ org = end * ( 1 - progress ) + start * progress
+ zipline.end.SetOrigin( org )
+
+ org = mid * ( 1 - progress ) + start * progress
+
+ if ( !IsValid( zipline.mid ) )
+ return
+ zipline.mid.SetOrigin( org )
+
+ WaitFrame()
+ }
+*/
+}
+
+function CreateRopeEntities( Table )
+{
+ local subdivisions = 8 // 25
+ local slack = 100 // 25
+ string midpointName = UniqueString( "rope_midpoint" )
+ string endpointName = UniqueString( "rope_endpoint" )
+
+ entity rope_start = CreateEntity( "move_rope" )
+ rope_start.kv.NextKey = midpointName
+ rope_start.kv.MoveSpeed = 64
+ rope_start.kv.Slack = slack
+ rope_start.kv.Subdiv = subdivisions
+ rope_start.kv.Width = "2"
+ rope_start.kv.TextureScale = "1"
+ rope_start.kv.RopeMaterial = "cable/cable.vmt"
+ rope_start.kv.PositionInterpolator = 2
+
+ entity rope_mid = CreateEntity( "keyframe_rope" )
+ SetTargetName( rope_mid, midpointName )
+ rope_mid.kv.NextKey = endpointName
+ rope_mid.kv.MoveSpeed = 64
+ rope_mid.kv.Slack = slack
+ rope_mid.kv.Subdiv = subdivisions
+ rope_mid.kv.Width = "2"
+ rope_mid.kv.TextureScale = "1"
+ rope_mid.kv.RopeMaterial = "cable/cable.vmt"
+
+ entity rope_end = CreateEntity( "keyframe_rope" )
+ SetTargetName( rope_end, endpointName )
+ rope_end.kv.MoveSpeed = 64
+ rope_end.kv.Slack = slack
+ rope_end.kv.Subdiv = subdivisions
+ rope_end.kv.Width = "2"
+ rope_end.kv.TextureScale = "1"
+ rope_end.kv.RopeMaterial = "cable/cable.vmt"
+
+ Table.start <- rope_start
+ Table.mid <- rope_mid
+ Table.end <- rope_end
+
+ return Table
+}
+
+function SpawnZiplineEntities( Table )
+{
+ // after origins are set
+ DispatchSpawn( Table.start )
+ DispatchSpawn( Table.mid )
+ DispatchSpawn( Table.end )
+ return Table
+}
+
+function GetDropPos( zipline )
+{
+ entity ship = expect entity( zipline.ship )
+ if ( !HasDropshipDropTable( ship ) )
+ return null
+
+ DropTable dropTable = GetDropshipDropTable( ship )
+
+ foreach ( side, nodeTables in dropTable.nodes )
+ {
+ foreach ( nodeTable in nodeTables )
+ {
+ if ( nodeTable.attachName == zipline.shipAttach )
+ return nodeTable.origin
+ }
+ }
+
+ return null
+}
+
+function GetHookOriginFromNode( origin, nodeOrigin, attachOrigin )
+{
+ // need to use the slope of guy to node to get the slope for the zipline, then launch it from the attachment origin
+ local dropVec = nodeOrigin - origin
+ local dropDist = Length( dropVec )
+ dropVec.Norm()
+
+// DrawArrow( nodeOrigin, Vector(0,0,0), 5, 100 )
+ local attachEnd = attachOrigin + dropVec * ( dropDist + 1500 ) // some buffer
+ TraceResults zipTrace = TraceLine( attachOrigin, attachEnd, null, TRACE_MASK_NPCWORLDSTATIC )
+
+// DebugDrawLine( attachOrigin, zipTrace.endPos, 0, 255, 0, true, 5.0 )
+// DebugDrawLine( zipTrace.endPos, attachEnd, 255, 0, 0, true, 5.0 )
+
+ // zipline didn't connect with anything
+ if ( zipTrace.fraction == 1.0 )
+ {
+// DebugDrawLine( attachOrigin, attachEnd, 255, 255, 0, true, 5.0 )
+ return null
+ }
+
+ if ( Distance( zipTrace.endPos, attachOrigin ) < 300 )
+ return null
+
+ return zipTrace.endPos
+}
+
+function ZiplineInit( entity player )
+{
+ player.s.ziplineEffects <- []
+}
+
+function CreateZiplineJetEffects( entity player )
+{
+ asset jumpJetEffectFriendlyName = $"hmn_imc_jump_jet_wallrun_full"
+ asset jumpJetEffectEnemyName = $"hmn_mcorps_jump_jet_wallrun_full"
+ int playerTeam = player.GetTeam()
+
+ //HACK!
+ //Create 2 sets of jump jet effects, 1 visible to friendly, 1 visible to enemy
+ //Doing this for a myriad of reasons on the server as opposed to on the client like the rest
+ //of the jump jet effects. Since ziplining isn't all that common an action it should be fine
+
+ //create left jump jetfriendly
+ entity leftJumpJetFriendly = CreateEntity( "info_particle_system" )
+ leftJumpJetFriendly.kv.start_active = 1
+ leftJumpJetFriendly.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
+ leftJumpJetFriendly.SetValueForEffectNameKey( jumpJetEffectFriendlyName )
+ SetTargetName( leftJumpJetFriendly, UniqueString() )
+ leftJumpJetFriendly.SetParent( player, "vent_left_back", false, 0 )
+ SetTeam( leftJumpJetFriendly, playerTeam )
+ leftJumpJetFriendly.SetOwner( player)
+ DispatchSpawn( leftJumpJetFriendly )
+
+ //now create right jump jet for friendly
+ entity rightJumpJetFriendly = CreateEntity( "info_particle_system" )
+ rightJumpJetFriendly.kv.start_active = 1
+ rightJumpJetFriendly.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
+ rightJumpJetFriendly.SetValueForEffectNameKey( jumpJetEffectFriendlyName )
+ SetTargetName( rightJumpJetFriendly, UniqueString() )
+ rightJumpJetFriendly.SetParent( player, "vent_right_back", false, 0 )
+ SetTeam( rightJumpJetFriendly, playerTeam )
+ rightJumpJetFriendly.SetOwner( player)
+ DispatchSpawn( rightJumpJetFriendly )
+
+ //create left jump jet for enemy
+ entity leftJumpJetEnemy = CreateEntity( "info_particle_system" )
+ leftJumpJetEnemy.kv.start_active = 1
+ leftJumpJetEnemy.kv.VisibilityFlags = ENTITY_VISIBLE_TO_ENEMY
+ leftJumpJetEnemy.SetValueForEffectNameKey( jumpJetEffectEnemyName )
+ SetTargetName( leftJumpJetEnemy, UniqueString() )
+ leftJumpJetEnemy.SetParent( player, "vent_left_back", false, 0 )
+ SetTeam( leftJumpJetEnemy, playerTeam )
+ leftJumpJetEnemy.SetOwner( player)
+ DispatchSpawn( leftJumpJetEnemy )
+
+ //now create right jump jet for enemy
+ entity rightJumpJetEnemy = CreateEntity( "info_particle_system" )
+ rightJumpJetEnemy.kv.start_active = 1
+ rightJumpJetEnemy.kv.VisibilityFlags = ENTITY_VISIBLE_TO_ENEMY
+ rightJumpJetEnemy.SetValueForEffectNameKey( jumpJetEffectEnemyName )
+ SetTargetName( rightJumpJetEnemy, UniqueString() )
+ rightJumpJetEnemy.SetParent( player, "vent_right_back", false, 0 )
+ SetTeam( rightJumpJetEnemy, playerTeam )
+ rightJumpJetEnemy.SetOwner( player)
+ DispatchSpawn( rightJumpJetEnemy )
+
+ //sparks from the hand
+ entity handSparks = CreateEntity( "info_particle_system" )
+ handSparks.kv.start_active = 1
+ handSparks.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY
+ handSparks.SetValueForEffectNameKey( $"P_Zipline_hld_1" )
+ SetTargetName( handSparks, UniqueString() )
+ handSparks.SetParent( player, "L_HAND", false, 0 )
+ handSparks.SetOwner( player)
+ DispatchSpawn( handSparks )
+
+ //Do it again for greater intensity!
+ entity handSparks2 = CreateEntity( "info_particle_system" )
+ handSparks2.kv.start_active = 1
+ handSparks2.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY
+ handSparks2.SetValueForEffectNameKey( $"P_Zipline_hld_1" )
+ SetTargetName( handSparks2, UniqueString() )
+ handSparks2.SetParent( player, "L_HAND", false, 0 )
+ handSparks2.SetOwner( player)
+ DispatchSpawn( handSparks2 )
+
+ player.s.ziplineEffects.append( leftJumpJetFriendly )
+ player.s.ziplineEffects.append( rightJumpJetFriendly )
+ player.s.ziplineEffects.append( leftJumpJetEnemy )
+ player.s.ziplineEffects.append( rightJumpJetEnemy )
+
+ player.s.ziplineEffects.append( handSparks )
+ player.s.ziplineEffects.append( handSparks2 )
+}
+
+void function CodeCallback_ZiplineMount( entity player, entity zipline )
+{
+ // printl( "Mounting zipline")
+ #if SERVER
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( "player_zipline_attach", "3p_zipline_attach", player, player )
+ #endif
+
+}
+
+void function CodeCallback_ZiplineStart( entity player, entity zipline )
+{
+ #if SERVER
+ CreateZiplineJetEffects( player )
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( "player_zipline_loop", "3p_zipline_loop", player, player )
+ foreach ( callback in _ZiplineStartCallbacks )
+ thread callback( player, zipline )
+ #endif
+}
+
+void function CodeCallback_ZiplineMove( entity player, entity zipline )
+{
+ #if SERVER
+ if ( player.IsPhaseShifted() )
+ {
+ foreach( effect in player.s.ziplineEffects )
+ {
+ IsValid( effect )
+ effect.Destroy()
+ }
+ player.s.ziplineEffects.clear()
+ }
+ else if ( player.s.ziplineEffects.len() <= 0 )
+ {
+ CreateZiplineJetEffects( player );
+ }
+ #endif
+}
+
+void function CodeCallback_ZiplineStop( entity player )
+{
+ #if SERVER
+ foreach( effect in player.s.ziplineEffects )
+ {
+ IsValid( effect )
+ effect.Destroy()
+ }
+ player.s.ziplineEffects.clear()
+
+ StopSoundOnEntity( player, "player_zipline_loop" )
+ StopSoundOnEntity( player, "3p_zipline_loop" )
+
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( "player_zipline_detach", "3p_zipline_detach", player, player )
+
+ foreach ( callback in _ZiplineStopCallbacks )
+ thread callback( player )
+ #endif
+}
+
+void function AddCallback_ZiplineStart( void functionref(entity,entity) callback )
+{
+ _ZiplineStartCallbacks.append( callback )
+}
+
+void function AddCallback_ZiplineStop( void functionref(entity) callback )
+{
+ _ZiplineStopCallbacks.append( callback )
+}
+
+function ZiplineMover( entity ent, end, timeTotal, blendIn = 0, blendOut = 0 )
+{
+ Assert( !IsThreadTop(), "This should not be waitthreaded off, it creates timing issues." )
+ entity mover = CreateOwnedScriptMover( ent )
+ ent.SetParent( mover )
+
+ OnThreadEnd(
+ function() : ( ent, mover )
+ {
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ }
+ )
+
+ mover.MoveTo( end, timeTotal, blendIn, blendOut )
+ wait timeTotal
+
+ if ( IsValid( ent ) )
+ ent.ClearParent()
+}
+
+array<string> function GetZiplineLandingAnims()
+{
+ return file.zipLineLandingAnimations
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/pilot/class_wallrun.gnut b/Northstar.CustomServers/scripts/vscripts/pilot/class_wallrun.gnut
new file mode 100644
index 000000000..58de59c18
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/pilot/class_wallrun.gnut
@@ -0,0 +1,224 @@
+untyped
+
+global function ClassWallrun_Init
+
+global function Wallrun_AddPlayer
+global function Wallrun_OnPlayerSpawn
+global function Wallrun_OnPlayerDeath
+global function Wallrun_PlayerTookDamage
+global function Wallrun_EnforceWeaponDefaults
+global function Wallrun_CreateCopyOfPilotModel
+
+function ClassWallrun_Init()
+{
+
+ // Make weapons less effective when playing at higher difficulty.
+ level.lethalityMods <- {}
+}
+
+function Wallrun_AddPlayer( player )
+{
+ player.playerClassData[level.pilotClass] <- {}
+}
+
+
+function Wallrun_EnforceWeaponDefaults( player )
+{
+ if ( player.playerClassData[ level.pilotClass ].primaryWeapon )
+ {
+ // settings already exist
+ return
+ }
+
+ player.playerClassData[ level.pilotClass ].primaryWeapon = "mp_weapon_r97"
+ player.playerClassData[ level.pilotClass ].secondaryWeapon = "mp_weapon_sniper"
+
+ local offhandWeaponTable = {}
+ offhandWeaponTable[0] <- { weapon = "mp_weapon_frag_grenade", mods = [] }
+ offhandWeaponTable[1] <- { weapon = "mp_ability_heal", mods = [] }
+
+ player.playerClassData[ level.pilotClass ].offhandWeapons = offhandWeaponTable
+ player.playerClassData[ level.pilotClass ].playerSetFile = DEFAULT_PILOT_SETTINGS
+}
+
+
+function Wallrun_OnPlayerSpawn( player )
+{
+}
+
+
+function Wallrun_PlayerTookDamage( entity player, damageInfo, entity attacker )
+{
+ if ( IsDemigod( player ) )
+ {
+ EntityDemigod_TryAdjustDamageInfo( player, damageInfo )
+ return
+ }
+
+ AdjustDamageForRodeoPlayers( player, damageInfo, attacker )
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " After Wallrun_PlayerTookDamage:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ if ( player.GetShieldHealthMax() > 0 )
+ {
+ local shieldDamage = PilotShieldHealthUpdate( player, damageInfo )
+ return shieldDamage
+ }
+
+ return
+}
+
+function AdjustDamageForRodeoPlayers( entity player, var damageInfo, entity attacker )
+{
+ if ( player == attacker )
+ return
+
+ local titanSoulRodeoed = player.GetTitanSoulBeingRodeoed()
+ if ( !IsValid( titanSoulRodeoed ) )
+ return
+
+ local playerParent = titanSoulRodeoed.GetTitan()
+
+ // dont let npcs hurt rodeo player
+ if ( attacker.IsNPC() && attacker != playerParent && DamageInfo_GetDamageSourceIdentifier( damageInfo ) != eDamageSourceId.mp_titanability_smoke )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ local damage = DamageInfo_GetDamage( damageInfo )
+
+ if ( !ShouldAdjustDamageForRodeoPlayer( damageInfo ) )
+ return
+
+ local maxPer500ms
+
+ if ( attacker == playerParent )
+ {
+ // rodeo'd player can't damage quite as much
+ maxPer500ms = 56
+ }
+ else
+ if ( playerParent.GetTeam() == player.GetTeam() )
+ {
+ // riding same team titan protects you a bit from random fire on that titan
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_EXPLOSION )
+ {
+ maxPer500ms = 75
+ }
+ else if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_MELEE ) //If melee, players still die in one hit
+ {
+ maxPer500ms = player.GetMaxHealth() + 1
+ }
+ else
+ {
+ maxPer500ms = 175
+ }
+ }
+ else
+ {
+ return
+ }
+
+ //Set a cap on how much damage the playerParent can do.
+ local damageTaken = GetTotalDamageTakenInTime( player, 0.5 )
+
+ local allowedDamage = maxPer500ms - damageTaken
+ if ( damage < allowedDamage )
+ return
+
+ damage = allowedDamage
+ if ( damage <= 0 )
+ damage = 0
+
+ DamageInfo_SetDamage( damageInfo, damage )
+}
+
+
+function ShouldAdjustDamageForRodeoPlayer( damageInfo )
+{
+ int sourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ switch( sourceID )
+ {
+ case eDamageSourceId.rodeo_trap:
+ case eDamageSourceId.mp_titanweapon_vortex_shield:
+ case eDamageSourceId.mp_titanweapon_vortex_shield_ion:
+ case eDamageSourceId.mp_titanability_smoke:
+ case eDamageSourceId.mp_weapon_satchel: //added so that rodeoing players are no longer invulnerable to their satchels when detonated by Titan's smoke
+ return false
+
+ default:
+ return true
+ }
+}
+
+
+function Wallrun_OnPlayerDeath( entity player, damageInfo )
+{
+ if ( IsValidHeadShot( damageInfo, player ) )
+ {
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ local soundAlias
+ if ( damageType & DF_SHOTGUN )
+ {
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Flesh.Shotgun.BulletImpact_Headshot_3P_vs_1P" )
+ soundAlias = "Flesh.Shotgun.BulletImpact_Headshot_3P_vs_3P"
+ }
+ else if ( damageType & damageTypes.bullet || damageType & DF_BULLET )
+ {
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Flesh.Light.BulletImpact_Headshot_3P_vs_1P" )
+ soundAlias = "Flesh.Light.BulletImpact_Headshot_3P_vs_3P"
+ }
+ else if ( damageType & damageTypes.largeCaliber || damageType & DF_GIB )
+ {
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Flesh.Heavy.BulletImpact_Headshot_3P_vs_1P" )
+ soundAlias = "Flesh.Heavy.BulletImpact_Headshot_3P_vs_3P"
+ }
+
+ if ( soundAlias )
+ {
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ array<entity> pilotArray = GetPlayerArray()
+ //Iterating because we need to not play this sound on 2 pilots and the function only allows for 1. Performance difference is negligible according to Eric M between this and adding a specific code function.
+ foreach ( pilot in pilotArray )
+ {
+ if ( !IsValid( pilot ) )
+ continue
+
+ if ( pilot == player || pilot == attacker )
+ continue
+
+ EmitSoundOnEntityOnlyToPlayer( player, pilot, soundAlias )
+ }
+ }
+ }
+}
+
+
+entity function Wallrun_CreateCopyOfPilotModel( entity player )
+{
+ const string PLAYER_SETTINGS_FIELD = "bodymodel"
+
+ asset modelName
+ if ( player.IsTitan() )
+ {
+ modelName = GetPlayerSettingsAssetForClassName( player.s.storedPlayerSettings, PLAYER_SETTINGS_FIELD )
+ }
+ else
+ {
+ modelName = player.GetPlayerSettingsAsset( PLAYER_SETTINGS_FIELD )
+ }
+
+ entity model = CreatePropDynamic( modelName )
+
+ SetTeam( model, player.GetTeam() )
+
+ //model.SetSkin( 0 )
+
+ RandomizeHead( model )
+
+ return model
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/rodeo/_rodeo.gnut b/Northstar.CustomServers/scripts/vscripts/rodeo/_rodeo.gnut
new file mode 100644
index 000000000..72ff58b78
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/rodeo/_rodeo.gnut
@@ -0,0 +1,545 @@
+untyped
+
+global function Rodeo_Init
+
+global function CodeCallback_StartRodeo
+global function CodeCallback_ForceEndRodeo
+global function CodeCallback_EmbarkTitan
+global function CodeCallback_EmbarkTitanFromGrapple
+
+global function PlayerBeginsRodeo
+global function WatchForPlayerJumpingOffRodeo
+global function PlayerJumpsOffRodeoTarget
+global function PlayerClimbsIntoRodeoPosition
+global function rodeodebug
+
+//-----------------------------------------------------------------------------
+// _rodeo.nut
+//
+// The central location for rodeo, mostly a place to put code callbacks that
+// then call other things in other files based on the thing being rodeod.
+//
+//-----------------------------------------------------------------------------
+//
+// HOW TO ADD A NEW RODEO TYPE
+//
+// Create a new file for the rodeo type like _rodeo_prowler.nut and:
+// Implement "IsValid_NEWTYPE_RodeoTarget()"
+// Implement "GetRodeoPackage_RIDER_to_NEWTYPE_()"
+// Implement "_RIDER_Begins_NEWTYPE_Rodeo()"
+// Implement "_RIDER_LerpsInto_NEWTYPE_Rodeo()"
+//
+// _RIDER_ is the rodeo rider type like "Player" or "Prowler"
+// _NEWTYPE_ is the new kind of rodeo target like "SuperSpectre" or "Drone"
+//
+// In _rodeo_shared.nut:
+// IncludeFile() the NEWTYPE file
+// Add a hook for NEWTYPE in CodeCallback_OnRodeoAttach()
+// Add a hook for NEWTYPE in CodeCallback_IsValidRodeoTarget()
+// Add a hook for NEWTYPE in GetRodeoPackage() if needed
+//
+//-----------------------------------------------------------------------------
+
+function Rodeo_Init()
+{
+ RodeoShared_Init()
+ RodeoTitan_Init()
+ RegisterSignal( "RodeoPointOfNoReturn" )
+ AddCallback_OnTitanDoomed( OnTitanDoomed_Rodeo )
+}
+
+
+void function CodeCallback_EmbarkTitan( entity player, entity titan )
+{
+ if ( player.Lunge_IsActive() && (titan == player.Lunge_GetTargetEntity()) )
+ {
+ if ( PlayerCanImmediatelyEmbarkTitan( player, titan ) )
+ {
+ table embarkDirection = expect table( FindBestEmbark( player, titan ) )
+ thread PlayerEmbarksTitan( player, titan, embarkDirection )
+ }
+ }
+}
+
+bool function CodeCallback_EmbarkTitanFromGrapple( entity player, entity titan )
+{
+ Assert( player.IsHuman() )
+ Assert( titan.IsTitan() )
+
+ if ( !PlayerCanEmbarkIntoTitan( player, titan ) )
+ return false
+
+ table ornull embarkDirection = expect table ornull( FindBestEmbark( player, titan, false ) )
+ if ( !embarkDirection )
+ return false
+
+ expect table( embarkDirection )
+ thread PlayerEmbarksTitan( player, titan, embarkDirection )
+
+ return true
+}
+
+
+void function CodeCallback_StartRodeo( entity player, entity rodeoTarget )
+{
+ if ( IsMenuLevel() )
+ return
+
+ // Review: Good to remove?
+ if ( GetBugReproNum() == 7205 )
+ {
+ thread RodeoTest( player, rodeoTarget )
+ return
+ }
+
+ thread PlayerBeginsRodeo( player, player.p.rodeoPackage, rodeoTarget )
+}
+
+
+void function CodeCallback_ForceEndRodeo( entity player )
+{
+ ForceEndRodeo( player )
+}
+
+void function ForceEndRodeo( entity player )
+{
+ player.Signal( "RodeoOver" )
+}
+
+
+function RodeoTest( player, rodeoTarget )
+{
+ player.SetParent( rodeoTarget, "RODEO", false, 1 )
+ wait 5
+ player.ClearParent()
+ Rodeo_Detach( player )
+}
+
+function PlayerBeginsRodeo( entity player, RodeoPackageStruct rodeoPackage, entity rodeoTarget )
+{
+ Assert( player.GetParent() == null )
+ player.Lunge_ClearTarget()
+
+ Assert( IsValid( player ) )
+ Assert( IsValid( rodeoTarget ) )
+ Assert( !player.IsTitan() )
+
+ if ( rodeoTarget.IsTitan() )
+ PlayerBeginsTitanRodeo( player, rodeoPackage, rodeoTarget )
+ else
+ PlayerBeginsNPCRodeo( player, rodeoPackage, rodeoTarget ) //Not tested very well since non-Titan Rodeo never really became a thing. Should work thought
+}
+
+function PlayerBeginsNPCRodeo( entity player, RodeoPackageStruct rodeoPackage, entity rodeoTarget )
+{
+ bool sameTeam = player.GetTeam() == rodeoTarget.GetTeam()
+ bool playerWasEjecting = player.p.pilotEjecting // have to store this off here because the "RodeoStarted" signal below ends eject, so it will be too late to check it in actual rodeo function
+
+ player.Signal( "RodeoStarted" )
+
+ OnThreadEnd(
+ function () : ( player, rodeoTarget )
+ {
+ RodeoPackageStruct rodeoPackage = player.p.rodeoPackage
+
+ entity newRodeoTarget = rodeoTarget
+ if ( IsValid( player ) )
+ {
+ player.Signal( "RodeoOver" )
+
+ // Added via AddCallback_OnRodeoEnded
+ foreach ( callbackFunc in level.onRodeoEndedCallbacks ) //TODO: Remove this!
+ {
+ callbackFunc( player )
+ }
+
+ // show name of the pilot again
+ player.SetNameVisibleToFriendly( true )
+ player.SetNameVisibleToEnemy( true )
+
+ ClearPlayerAnimViewEntity( player )
+
+ // blend out the clear anim view entity
+ player.AnimViewEntity_SetLerpOutTime( 0.4 )
+
+ player.ClearParent()
+ player.Anim_Stop()
+ player.SetOneHandedWeaponUsageOff()
+ player.SetTitanSoulBeingRodeoed( null )
+ player.UnforceStand()
+ player.kv.PassDamageToParent = false
+ player.TouchGround() // so you can double jump off
+
+ StopSoundOnEntity( player, rodeoPackage.cockpitSound )
+ StopSoundOnEntity( player, rodeoPackage.worldSound )
+
+ if ( Rodeo_IsAttached( player ) )
+ {
+ Rodeo_Detach( player )
+ }
+
+ if ( IsAlive( player ) )
+ {
+ int attachIndex = newRodeoTarget.LookupAttachment( rodeoPackage.attachPoint )
+ vector startPos = newRodeoTarget.GetAttachmentOrigin( attachIndex )
+
+ if ( !PlayerCanTeleportHere( player, startPos, newRodeoTarget ) )
+ {
+ startPos = newRodeoTarget.GetOrigin()
+ if ( !PlayerCanTeleportHere( player, startPos, newRodeoTarget ) )
+ startPos = player.GetOrigin()
+ }
+
+ thread PlayerJumpsOffRodeoTarget( player, newRodeoTarget, startPos )
+ }
+ }
+ }
+ )
+
+ rodeoTarget.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "RodeoOver" )
+
+ string rodeoTargetType = rodeoPackage.rodeoTargetType
+
+ thread WatchForPlayerJumpingOffRodeo( player )
+
+ player.SetNameVisibleToFriendly( false ) // hide name of the pilot while he is rodeoing
+ player.SetNameVisibleToEnemy( false )
+ player.ForceStand()
+ HolsterAndDisableWeapons( player )
+ player.SetOneHandedWeaponUsageOn()
+ player.TouchGround() // so you can double jump off
+
+ waitthread PlayerClimbsIntoRodeoPosition( player, rodeoTarget, rodeoPackage, playerWasEjecting )
+
+ #if FACTION_DIALOGUE_ENABLED
+ if ( !sameTeam )
+ PlayFactionDialogueToPlayer( "kc_rodeo", player )
+ #endif
+
+ // Go straight into idle animations
+ FirstPersonSequenceStruct sequence
+ sequence.thirdPersonAnimIdle = GetAnimFromAlias( rodeoTargetType, "pt_rodeo_panel_aim_idle" )
+ sequence.firstPersonAnimIdle = GetAnimFromAlias( rodeoTargetType, "ptpov_rodeo_panel_aim_idle" )
+
+ if ( !rodeoPackage.useAttachAngles )
+ player.Anim_IgnoreParentRotation( true )
+
+ sequence.useAnimatedRefAttachment = true
+
+ thread FirstPersonSequence( sequence, player, rodeoTarget )
+
+ if ( sameTeam )
+ {
+ player.GetFirstPersonProxy().HideFirstPersonProxy()
+ OpenViewCone( player )
+ }
+ else
+ {
+ PlayerRodeoViewCone( player, rodeoTargetType ) // TODO: Add air_drone and make enum in this func()
+ }
+
+ // look! he rodeoed!
+ thread AIChatter( "aichat_rodeo_cheer", player.GetTeam(), player.GetOrigin() )
+
+ Rodeo_OnFinishClimbOnAnimation( player ) // This is to let code know the player has finished climbing on the rodeo and ready to fire
+
+ if ( sameTeam )
+ {
+ player.PlayerCone_Disable()
+ player.EnableWorldSpacePlayerEyeAngles()
+ }
+
+ DeployAndEnableWeapons( player )
+
+ WaitForever()
+}
+
+void function PlayerClimbsIntoRodeoPosition( entity player, entity rodeoTarget, RodeoPackageStruct rodeoPackage, bool playerWasEjecting = false ) //TODO: Rename this function since new style rodeo anims have climbing as part of the anim
+{
+ player.EndSignal( "OnDeath" )
+
+
+ // The only thing that should have a soul is titans now. Legacy. Can't remove without major code feature work.
+ entity soul
+ if ( rodeoTarget.IsTitan() )
+ {
+ soul = rodeoTarget.GetTitanSoul()
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "OnDestroy" )
+ }
+ else
+ {
+ rodeoTarget.EndSignal( "OnTitanDeath" )
+ rodeoTarget.EndSignal( "OnDestroy" )
+ }
+
+ FirstPersonSequenceStruct sequence
+ sequence.attachment = rodeoPackage.attachPoint
+ SetRodeoAnimsFromPackage( sequence, rodeoPackage )
+
+ switch ( rodeoPackage.method )
+ {
+ case RODEO_APPROACH_FALLING_FROM_ABOVE:
+ table animStartPos = player.Anim_GetStartForRefEntity_Old( sequence.thirdPersonAnim, rodeoTarget, rodeoPackage.attachPoint )
+ float dist = Distance( player.GetOrigin(), animStartPos.origin )
+ float speed = Length( player.GetVelocity() )
+ float fallTime = dist / speed
+ fallTime *= 0.95
+
+ sequence.blendTime = clamp( fallTime, 0.4, 1 )
+
+ break
+
+ case RODEO_APPROACH_JUMP_ON:
+ sequence.blendTime = 0.6
+ break
+
+ default:
+ Assert( 0, "Unhandled rodeo method " + rodeoPackage.method )
+ }
+
+ if ( !PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) )
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( rodeoPackage.cockpitSound, rodeoPackage.worldSound, player, rodeoTarget )
+
+ string titanType
+
+ // Titans only
+ if ( IsValid( soul ) )
+ {
+ if ( !( player in soul.rodeoRiderTracker ) )
+ {
+ soul.rodeoRiderTracker[ player ] <- true
+ if ( rodeoTarget.GetTeam() == player.GetTeam() )
+ {
+ AddPlayerScore( player, "HitchRide" )
+ AddPlayerScore( rodeoTarget, "GiveRide" )
+ }
+ else
+ {
+ AddPlayerScore( player, "RodeoEnemyTitan" )
+
+ #if HAS_STATS
+ UpdatePlayerStat( player, "misc_stats", "rodeos" )
+
+ if ( playerWasEjecting )
+ UpdatePlayerStat( player, "misc_stats", "rodeosFromEject" )
+ #endif
+
+ #if SERVER && MP
+ PIN_AddToPlayerCountStat( player, "rodeos" )
+ if ( rodeoTarget.IsPlayer() )
+ PIN_AddToPlayerCountStat( rodeoTarget, "rodeo_receives" )
+ #endif
+ }
+ }
+
+ titanType = GetSoulTitanSubClass( soul )
+ }
+
+ MessageToPlayer( player, eEventNotifications.Rodeo_HideBatteryHint )
+
+ float time = player.GetSequenceDuration( sequence.thirdPersonAnim )
+
+ if ( !rodeoPackage.useAttachAngles )
+ player.Anim_IgnoreParentRotation( true )
+
+ thread FirstPersonSequence( sequence, player, rodeoTarget )
+ wait time
+}
+
+void function WatchForPlayerJumpingOffRodeo( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "RodeoOver" )
+ player.EndSignal( "RodeoPointOfNoReturn" )
+
+ wait 0.6 // debounce so you dont accihop
+
+ AddButtonPressedPlayerInputCallback( player, IN_JUMP, ForceEndRodeo )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ //RodeoOver is signalled at the end of PlayerBeginsRodeo, so even if Rodeo ends via the Titan disconnecting etc this will run
+ RemoveButtonPressedPlayerInputCallback( player, IN_JUMP, ForceEndRodeo )
+ }
+ )
+
+ WaitForever()
+}
+
+
+void function PlayerJumpsOffRodeoTarget( entity player, entity rodeoTarget, vector startPos )
+{
+ #if DEV
+ if ( GetDebugRodeoPrint() )
+ printt( "PlayerJumpsOffRodeoTarget, playerPos: " + player.GetOrigin() + " playerAngles: " + player.GetAngles() + " rodeoTargetPos: " + rodeoTarget.GetOrigin() + " rodeoTargetAngles: " + rodeoTarget.GetAngles() + ", startPos: " + startPos )
+ #endif
+
+ // ejected, or rip off battery, etc. Those adjust velocity for the rodeo player anyway, so don't do any more adjustments for them.
+ if ( player.p.rodeoShouldAdjustJumpOffVelocity == false )
+ return
+
+ if ( !IsValid( rodeoTarget ) )
+ {
+ PutEntityInSafeSpot( player, null, null, startPos, player.GetOrigin() )
+
+ #if DEV
+ if ( GetDebugRodeoPrint() )
+ printt( "PlayerJumpsOffRodeoTarget, playerPos after PutEntityInSafeSpot, !ISValid(rodeoTarget): " + player.GetOrigin() )
+ #endif
+ return
+ }
+
+ PutEntityInSafeSpot( player, rodeoTarget, null, startPos, player.GetOrigin() )
+ #if DEV
+ if ( GetDebugRodeoPrint() )
+ printt( "PlayerJumpsOffRodeoTarget, playerPos after PutEntityInSafeSpot, rodeoTarget valid: " + player.GetOrigin() )
+ #endif
+
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( "Rodeo_Jump_Off_Interior", "Rodeo_Jump_Off", player, rodeoTarget )
+
+ vector forward = player.GetViewForward()
+ vector right = player.GetViewRight()
+
+ forward.z = 0
+ right.z = 0
+
+ // map the player's controls to his angles, and add that velocity
+ float xAxis = player.GetInputAxisRight()
+ float yAxis = player.GetInputAxisForward()
+
+ vector velocity
+ if ( fabs( xAxis ) < 0.2 && fabs( yAxis ) < 0.2 )
+ {
+ // no press = back press
+ velocity = Vector(0,0,0)
+ }
+ else
+ {
+ vector forwardVec = forward * yAxis
+ vector rightVec = right * xAxis
+ vector directionVec = ( rightVec + forwardVec )
+
+ //printt( "ForwardVec: " + forwardVec + ", rightVec: " + rightVec + ", directionVec :" + directionVec + ", directionVec scaled: " + (directionVec * 350 ) ) // for bug 123013
+
+ float speed = 350
+ velocity = directionVec * speed
+ }
+
+ // IMPORTANT: Don't give boost pilots too much vertical or they go sky high
+ if ( player.GetPlayerSettingsField( "boostEnabled" ).tointeger() > 0 )
+ velocity += Vector(0,0,200)
+ else
+ velocity += Vector(0,0,390 )
+
+ //printt( "Setting velocity to: " + velocity ) // for bug 123013
+
+ player.SetVelocity( velocity )
+ player.JumpedOffRodeo()
+}
+
+void function rodeodebug()
+{
+ // console command for forcing rodeo amongst 2 players
+ thread makerodeothread()
+}
+
+void function makerodeothread()
+{
+ array<entity> players = GetPlayerArray()
+ vector titanOrg
+ bool titanOrgSet = false
+ entity titan, pilot
+
+ for ( int i = players.len() - 1; i >= 0; i-- )
+ {
+ entity player = players[i]
+
+ if ( player.IsTitan() )
+ {
+ titan = player
+ }
+ else
+ {
+ pilot = player
+ }
+ }
+
+ if ( !titan )
+ {
+ for ( int i = players.len() - 1; i >= 0; i-- )
+ {
+ entity player = players[i]
+
+ if ( !player.IsTitan() )
+ {
+ player.SetPlayerSettings( "titan_atlas" )
+ titan = player
+ break
+ }
+ }
+ }
+ else
+ if ( !pilot )
+ {
+ for ( int i = players.len() - 1; i >= 0; i-- )
+ {
+ entity player = players[i]
+
+ if ( player.IsTitan() )
+ {
+ thread TitanEjectPlayer( player )
+ wait 1.5
+ pilot = player
+ break
+ }
+ }
+ }
+
+ for ( int i = players.len() - 1; i >= 0; i-- )
+ {
+ entity player = players[i]
+
+ if ( player.IsTitan() )
+ {
+ titanOrg = player.GetOrigin()
+ titanOrgSet = true
+ }
+ }
+
+ if ( !titanOrgSet )
+ return
+
+ for ( int i = players.len() - 1; i >= 0; i-- )
+ {
+ entity player = players[i]
+
+ if ( !player.IsTitan() )
+ {
+ vector angles = titan.GetAngles()
+ vector forward = AnglesToForward( angles )
+ titanOrg += forward * -100
+ titanOrg.z += 500
+ angles.x = 30
+ player.SetAngles( angles )
+ player.SetOrigin( titanOrg )
+ player.SetVelocity( Vector(0,0,0) )
+ break
+ }
+ }
+}
+
+void function OnTitanDoomed_Rodeo( entity titan, var damageInfo )
+{
+ if ( !IsAlive( titan ) )
+ return
+
+ entity rodeoPilot = GetRodeoPilot( titan )
+ if ( !IsValid( rodeoPilot ) )
+ return
+
+ Remote_CallFunction_NonReplay( rodeoPilot, "ServerCallback_UpdateRodeoRiderHud" )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/rodeo/_rodeo_titan.gnut b/Northstar.CustomServers/scripts/vscripts/rodeo/_rodeo_titan.gnut
new file mode 100644
index 000000000..9f05a0cd3
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/rodeo/_rodeo_titan.gnut
@@ -0,0 +1,2456 @@
+untyped //Panel.s stuff needs to be typed
+
+global function RodeoTitan_Init
+
+global function EnableTitanRodeo
+global function DisableTitanRodeo
+global function DebugRodeoTimes
+global function PlayerBeginsTitanRodeo
+global function ForceTitanRodeoToEnd
+global function PlayerRodeoViewCone
+global function OpenViewCone
+global function RodeoPanelIsOpen
+global function PlayerRemovesBatteryPack
+global function Rodeo_PilotAddsBatteryToFriendlyTitan
+global function GiveFriendlyRodeoPlayerProtection
+global function TakeAwayFriendlyRodeoPlayerProtection
+global function Rodeo_GiveBatteryToPlayer
+global function Rodeo_PilotThrowsBattery
+global function Rodeo_RemoveBatteryOffPlayer
+global function Rodeo_RemoveAllBatteriesOffPlayer
+global function Rodeo_GiveExecutingTitanABattery
+global function Rodeo_CreateBatteryPack
+global function SetSoulBatteryCount
+global function GetPlayerBatteryCount
+global function PlayerHasMaxBatteryCount
+global function Rodeo_PilotPicksUpBattery_Silent
+
+global function AddOnRodeoStartedCallback
+global function AddOnRodeoEndedCallback
+
+global function PilotBattery_SetMaxCount
+global function ThrowRiderOff
+
+global function Burnmeter_EmergencyBattery
+global function Burnmeter_AmpedBattery
+
+global function Battery_StartFX
+global function Battery_StopFX
+global function Battery_StopFXAndHideIconForPlayer
+
+global function RemovePlayerAirControl //This function should really be in a server only SP & MP utility script file. No such file exists as of right now.
+global function RestorePlayerAirControl //This function should really be in a server only SP & MP utility script file. No such file exists as of right now.
+
+#if DEV
+global function SetDebugRodeoPrint
+global function GetDebugRodeoPrint
+#endif
+
+#if MP
+global function SetApplyBatteryCallback
+#endif
+
+const float BATTERY_PICKUP_IGNORE_FRAC = 0.98
+const RODEO_EXPLOSION_DAMAGEFRAC = 0.3
+global const RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS = $"models/props/titan_battery_static/titan_battery_static.mdl" //Need a separate one for rodeo anims, instead of manually rotating the existing one
+const RODEO_BATTERY_RIP_PILOT_PUSHED_OFF_VERTICAL_HEIGHT = 80
+const RODEO_BATTERY_RIP_PILOT_PUSHED_OFF_HORIZONTAL_SPEED = 450
+const RODEO_THROW_BATTERY_BUTTON_HOLD_TIME = 0.5
+const RODEO_CLAMBER_FAILED_SOUND_DEBOUNCE_TIME = 2.0
+global const BATTERY_FX_FRIENDLY = $"P_xo_battery"
+global const BATTERY_FX_AMPED = $"P_xo_battery_amped"
+
+const HAS_BATTERY_THIEF_ICON = false
+
+const string PILOT_PICKS_UP_BATTERY_SOUND = "UI_TitanBattery_Pilot_PickUp"
+const string PILOT_APPLIES_BATTERY_TO_TITAN_HEALTH_RESTORED_SOUND = "UI_TitanBattery_Pilot_Give_TitanBattery"
+
+
+global int RODEO_BATTERY_EXPLOSION_EFFECT
+
+const string TITAN_GOT_BATTERY_RIPPED_SOUND = "UI_TitanBattery_Pilot_Take_TitanBattery"
+
+global float ANTI_RODEO_DEFAULT_START_DELAY = 0.5
+global float ANTI_RODEO_DEFAULT_DRAIN_DURATION = 1.25
+global float ANTI_RODEO_DEFAULT_WINDOW_DURATION = 0.1
+global float ANTI_RODEO_DEFAULT_WINDOW_START = 0.55
+
+struct AntiRodeoPlayerData
+{
+ bool antiRodeoPressed
+ float startTime
+ float windowStartFrac
+ float windowEndFrac
+ entity antiRodeoPlayer
+ bool wasCrouched
+}
+
+struct
+{
+ array<void functionref(entity,entity)> onRodeoEndedCallbacks
+ array<void functionref(entity,entity)> onRodeoStartedCallbacks
+
+ table<entity, AntiRodeoPlayerData> antiRodeoPlayerData
+
+ int maxPilotBatteryCount = 1
+ bool debugRodeoPrint = false
+
+ table<entity, bool> playersThatWantToUseRodeoGrenade
+
+ void functionref(entity,entity,entity) applyBatteryCallback
+} file
+
+//-----------------------------------------------------------------------------
+// _rodeo_titan.nut
+//
+// Script for a player (pilot) rodoeing a titan.
+//
+//-----------------------------------------------------------------------------
+
+void function RodeoTitan_Init()
+{
+ PrecacheParticleSystem( $"P_impact_rodeo_damage" ) //Rodeo hit spark
+ PrecacheParticleSystem( $"P_rodeo_damage_1" ) //DamageState1
+ PrecacheParticleSystem( $"P_rodeo_damage_2" ) //DamageState2
+ PrecacheParticleSystem( $"P_rodeo_damage_3" ) //DamageState3
+ PrecacheParticleSystem( BATTERY_FX_FRIENDLY )
+ PrecacheParticleSystem( BATTERY_FX_AMPED )
+
+ RegisterSignal( "CancelAirControlLoss" )
+ RegisterSignal( "FriendlyRodeoDeployWeapon" )
+ RegisterSignal( "MonitorRodeoPastPointOfNoReturn" )
+ RegisterSignal( "PostRodeoAirControl" )
+ PrecacheModel( RODEO_BATTERY_MODEL )
+ PrecacheModel( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+
+ AddSoulDeathCallback( SoulRodeoEnds )
+ AddSoulTransferFunc( RecreateRodeoPanelDamageFX )
+
+ RODEO_BATTERY_EXPLOSION_EFFECT = PrecacheParticleSystem( $"P_impact_exp_FRAG_metal" )
+
+ AddCallback_OnPlayerKilled( Rodeo_DropAllBatteriesOnDeath )
+ AddCallback_OnTouchHealthKit( "item_titan_battery", Rodeo_OnTouchBatteryPack )
+ AddCallback_OnPilotBecomesTitan( Rodeo_ApplyAllBatteriesOnEmbark )
+
+ //AddSoulInitFunc( Rodeo_HealthDecayThink )
+
+ if ( IsMultiplayer() )
+ {
+ //AddDeathCallback( "player", TitanDropsBatteryOnDeath ) //SP has its own functions. Maybe we should just copy SP's stuff? They have the green highlight FX for it too
+ //AddDeathCallback( "npc_titan", TitanDropsBatteryOnDeath ) //SP has its own functions. Maybe we should just copy SP's stuff? They have the green highlight FX for it too
+
+ AddDamageCallback( "player", ShowRequestRodeoBatteryHint_OnDamage )
+ AddCallback_OnPilotBecomesTitan( ShowRequestRodeoBatteryHint_OnPilotBecomesTitan )
+
+ AddClientCommandCallback( "OfferRodeoBattery", ClientCommand_OfferRodeoBattery )
+ AddClientCommandCallback( "RequestRodeoBattery", ClientCommand_RequestRodeoBattery )
+
+ #if MP
+ AddClientCommandCallback( "TryNukeGrenade", ClientCommand_TryNukeGrenade )
+ RegisterSignal( "TryNukeGrenade" )
+ RegisterSignal( "RodeoNukeWindowEnded" )
+ #endif
+ }
+ else
+ {
+ AddSoulInitFunc( DisableBTRodeo )
+ }
+}
+
+void function Rodeo_HealthDecayThink( entity soul ) //Remove if we don't want rodeo battery to drain health
+{
+ thread Rodeo_HealthDecayThinkInternal( soul )
+}
+
+void function Rodeo_HealthDecayThinkInternal( entity soul ) //Remove if we don't want rodeo battery to drain health
+{
+ soul.EndSignal( "OnDestroy" ) //This needs to be OnDestroy instead of OnDeath because souls don't have a death animation
+ soul.EndSignal( "OnTitanDeath" )
+
+ bool draining = false
+
+ while ( 1 )
+ {
+ entity titan = soul.GetTitan()
+
+ if ( Rodeo_ShouldDrainHealth( soul ) )
+ {
+ if ( !draining )
+ {
+ draining = true
+ EmitSoundOnEntity( titan, "titan_energyshield_down" )
+ }
+
+ int damageAmout = Rodeo_GetDrainAmount( soul )
+
+ titan.TakeDamage( damageAmout, soul.e.lastRodeoAttacker, soul.e.lastRodeoAttacker, { scriptType = damageTypes.rodeoBatteryRemoval | DF_NO_INDICATOR, damageSourceId = eDamageSourceId.rodeo_battery_removal, hitbox = 2 } )
+ }
+ else
+ {
+ if ( draining )
+ {
+ draining = false
+ StopSoundOnEntity( titan, "titan_energyshield_down" )
+ }
+ }
+ WaitFrame()
+ }
+}
+
+bool function Rodeo_ShouldDrainHealth( entity soul ) //Remove if we don't want rodeo battery to drain health
+{
+ entity titan = soul.GetTitan()
+ if ( !IsAlive( titan ) )
+ return false
+
+ if ( GetDoomedState( titan ) )
+ return false
+
+ int batt = GetSoulBatteryCount( soul )
+ int maxBattHealth = GetSegmentHealthForTitan( titan ) * batt
+ int health = titan.GetHealth()
+ return ( health > maxBattHealth )
+}
+
+int function Rodeo_GetDrainAmount( entity soul ) //Remove if we don't want rodeo battery to drain health
+{
+ entity titan = soul.GetTitan()
+
+ float damagePerSec = GetSegmentHealthForTitan( titan ) / RODEO_DRAIN_TIME
+ float damagePerFrame = ceil( GetSegmentHealthForTitan( titan ) / RODEO_DRAIN_TIME ) * 0.1
+ int damageAmout = int( damagePerFrame )
+
+ int batt = GetSoulBatteryCount( soul )
+ int maxBattHealth = GetSegmentHealthForTitan( titan ) * batt
+ int health = titan.GetHealth()
+ if ( health - maxBattHealth < damageAmout )
+ damageAmout = health - maxBattHealth
+
+ return damageAmout
+}
+
+void function GiveFriendlyRodeoPlayerProtection( entity titan )
+{
+ entity friendlyRider = GetFriendlyRodeoPilot( titan )
+ if ( IsValid( friendlyRider ) )
+ {
+ //printt( "Set friendlyRider PassDamageToParent true" )
+ friendlyRider.kv.PassDamageToParent = true //rodeo player now passes damage to titan
+ }
+}
+
+void function TakeAwayFriendlyRodeoPlayerProtection( entity titan )
+{
+ entity friendlyRider = GetFriendlyRodeoPilot( titan )
+ if ( IsValid( friendlyRider ) )
+ {
+ //printt( "Set friendlyRider PassDamageToParent false" )
+ friendlyRider.kv.PassDamageToParent = false //rodeo player now takes full damage
+ }
+}
+
+void function CreateSparksInsideTitanPanel( panel )
+{
+ entity impactSpark = CreateEntity( "info_particle_system" )
+ impactSpark.kv.start_active = 1
+ impactSpark.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ impactSpark.SetValueForEffectNameKey( $"P_impact_rodeo_damage" )
+ SetTargetName( impactSpark, UniqueString() )
+ impactSpark.SetParent( panel, "hatch", false, 0 )
+ DispatchSpawn( impactSpark )
+ impactSpark.Kill_Deprecated_UseDestroyInstead( 1.5 )
+}
+
+
+void function CreateDamageStateParticlesForPanel( var panel, asset particleSystem = $"P_impact_rodeo_damage" )
+{
+ entity impactSpark = CreateEntity( "info_particle_system" )
+ impactSpark.kv.start_active = 1
+ impactSpark.SetOwner( panel.GetParent() )
+ impactSpark.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // not visible to owner
+ impactSpark.SetValueForEffectNameKey( particleSystem )
+ SetTargetName( impactSpark, UniqueString() )
+ impactSpark.SetParent( panel, "hatch", false, 0 )
+ DispatchSpawn( impactSpark )
+ if ( IsValid( panel.s.lastDamageStateParticleSystem ) )
+ {
+ //printt("Killing particle system: " + panel.s.lastDamageStateParticleSystem)
+ panel.s.lastDamageStateParticleSystem.Kill_Deprecated_UseDestroyInstead()
+ }
+
+ panel.s.lastDamageStateParticleSystem = impactSpark
+}
+
+
+void function RecreateRodeoPanelDamageFX( entity soul, entity titan, entity oldTitan )
+{
+ thread RecreateRodeoPanelDamageFX_threaded( soul )
+}
+
+
+void function RecreateRodeoPanelDamageFX_threaded( entity soul )
+{
+ WaitEndFrame()
+ entity panel = soul.soul.batteryContainer
+
+ if (! IsValid( panel ) )
+ return
+
+ entity lastDamageStateParticleSystem = expect entity ( panel.s.lastDamageStateParticleSystem )
+
+ if ( IsValid( lastDamageStateParticleSystem ) )
+ {
+ CreateDamageStateParticlesForPanel( panel, lastDamageStateParticleSystem.GetValueForEffectNameKey() ) //This kills the last particle system too
+ }
+}
+
+void function RodeoPanelIsOpen( entity panel )
+{
+ panel.s.opened = true
+
+ entity titan = panel.GetParent()
+ Assert( titan.IsTitan() )
+
+ entity soul = titan.GetTitanSoul()
+ Assert( IsValid( soul ) )
+
+ soul.SetLastRodeoHitTime( Time() ) //Make warning always trigger now when panel is ripped
+ soul.soul.batteryContainerBeingUsed = false
+}
+
+void function RodeoBatteryRemoval( entity pilot )
+{
+ entity titan = GetTitanBeingRodeoed( pilot )
+ if ( !IsValid( titan ) )
+ return
+
+ // THROW RODEO RIDER OFF
+ entity soul = titan.GetTitanSoul()
+ string titanType = GetSoulTitanSubClass( soul )
+
+ soul.SetLastRodeoHitTime( Time() )
+
+ RodeoBatteryPackRemovalDamage( pilot, titan, soul )
+
+ bool playerHadBattery = PlayerHasBattery( pilot )
+
+ if ( !playerHadBattery )
+ {
+ AddPlayerScore( pilot, "PilotBatteryStolen" )
+ entity battery = Rodeo_CreateBatteryPack( titan )
+ Rodeo_PilotPicksUpBattery( pilot, battery )
+ thread BatteryThiefHighlight( pilot )
+
+ if ( titan.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( titan, titan, TITAN_GOT_BATTERY_RIPPED_SOUND ) //Consider playing this in world once we get sounds that aren't just notification beeps
+ }
+ }
+
+ vector direction = CalculateDirectionToThrowOffBatteryThief( pilot, titan )
+
+ ThrowRiderOff( pilot, titan, direction ) //This signals RodeoOver
+
+ #if MP
+ PIN_PlayerRodeoedEnemyTitanToCompletion( pilot, titan, playerHadBattery )
+ #endif
+}
+
+void function RodeoBatteryGrenadeShow( entity pilot )
+{
+ entity titanSoul = pilot.GetTitanSoulBeingRodeoed()
+ Assert( IsValid( titanSoul ) )
+
+ foreach( tempProp in pilot.p.rodeoAnimTempProps )
+ {
+ tempProp.Show()
+ }
+}
+
+void function RodeoBatteryRemoval_ShowBattery( entity pilot )
+{
+ foreach( tempProp in pilot.p.rodeoAnimTempProps )
+ {
+ tempProp.Show()
+ }
+
+ entity titanSoul = pilot.GetTitanSoulBeingRodeoed()
+
+ string titanType = GetSoulTitanSubClass( titanSoul )
+
+ entity batteryContainer = titanSoul.soul.batteryContainer
+ batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_down_idle" ) )
+
+}
+
+
+void function RodeoBatteryStealthMovementWarning( entity pilot )
+{
+ if ( !PlayerHasPassive( pilot, ePassives.PAS_STEALTH_MOVEMENT ) )
+ return
+
+ entity titanSoul = pilot.GetTitanSoulBeingRodeoed()
+
+ titanSoul.SetLastRodeoHitTime( Time() ) //This shows the warning icon on the Titan's hud
+}
+
+vector function CalculateDirectionToThrowOffBatteryThief( entity batteryThief, entity titan )
+{
+ vector backward
+ vector right
+
+ if ( titan.IsPlayer() )
+ {
+ backward = titan.GetViewForward() * -1.0
+ right = titan.GetViewRight()
+ }
+ else
+ {
+ backward = titan.GetForwardVector() * -1.0
+ right = titan.GetRightVector()
+ }
+
+ backward.z = 0
+ right.z = 0
+
+ backward = Normalize( backward )
+ right = Normalize( right )
+
+ // map the player's controls to his angles, and add that velocity
+ float xAxis = batteryThief.GetInputAxisRight()
+ float yAxis = batteryThief.GetInputAxisForward()
+
+ xAxis = GraphCapped( xAxis, -1.0, 1.0, -0.4, 0.4 )
+ yAxis = GraphCapped( yAxis, -1.0, 1.0, 1.0, 0.75 ) //Cap it so you don't actually let the players jump forwards
+
+ vector direction
+ if ( fabs( xAxis ) < 0.2 && fabs( yAxis ) < 0.2 )
+ {
+ // no significant controller deflection, just push forward by 0.75 as default
+ direction = backward * 0.75
+ }
+ else
+ {
+ vector forwardVec = backward * yAxis
+ vector rightVec = right * xAxis
+ direction = rightVec + forwardVec
+ }
+
+ direction *= RODEO_BATTERY_RIP_PILOT_PUSHED_OFF_HORIZONTAL_SPEED
+ direction.z = RODEO_BATTERY_RIP_PILOT_PUSHED_OFF_VERTICAL_HEIGHT
+
+ // JFS: R2DLC-310 SCRIPT ERROR: PHONE_HOME: [SERVER] vecAbsVelocity isn't valid
+ if ( LengthSqr( direction ) < 0.0 )
+ return <0, 0, RODEO_BATTERY_RIP_PILOT_PUSHED_OFF_VERTICAL_HEIGHT>
+
+ return direction
+}
+
+void function CancelAirControlLossAfterTouchGround( entity player )
+{
+ player.Signal( "CancelAirControlLoss" )
+}
+
+
+void function BatteryThiefHighlight( entity player )
+{
+ Highlight_SetEnemyHighlight( player, "battery_thief" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( !IsValid( player ) )
+ return
+
+ if ( Hightlight_HasEnemyHighlight( player, "battery_thief" ) )
+ Highlight_ClearEnemyHighlight( player )
+ }
+ )
+
+ wait RODEO_BATTERY_THIEF_ICON_DURATION
+}
+
+void function ForceTitanRodeoToEnd( entity titan ) //TODO: Not typed since it is added via anim event
+{
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return
+
+ SoulRodeoEnds( soul, null )
+}
+
+
+void function SoulRodeoEnds( entity soul, var damageInfo )
+{
+ entity titan = soul.GetTitan()
+
+ if( !IsValid( titan ) )
+ return
+
+ entity rider = GetRodeoPilot( titan )
+
+ if ( !IsValid( rider ) )
+ return
+
+ rider.Signal( "RodeoOver" )
+ rider.ClearParent()
+}
+
+
+void function EnableTitanRodeo( entity titan )
+{
+ Assert( titan.IsTitan(), "tried calling EnableTitanRodeo on non-titan" )
+
+ entity titanSoul = titan.GetTitanSoul()
+
+ Assert( IsValid( titanSoul ) )
+
+ titanSoul.SetIsValidRodeoTarget( true ) //Lets rodeo happen on them.
+}
+
+
+void function DisableTitanRodeo( entity titan )
+{
+ Assert( titan.IsTitan(), "tried calling DisableTitanRodeo( on non-titan" )
+
+ entity titanSoul = titan.GetTitanSoul()
+
+ Assert( IsValid( titanSoul ) )
+
+ titanSoul.SetIsValidRodeoTarget( false ) //Stops rodeo from happening on them.
+}
+
+void function AddOnRodeoStartedCallback( void functionref(entity,entity) callbackFunc )
+{
+ Assert (!( file.onRodeoStartedCallbacks.contains( callbackFunc ) ))
+ file.onRodeoStartedCallbacks.append( callbackFunc )
+}
+
+void function AddOnRodeoEndedCallback( void functionref(entity,entity) callbackFunc )
+{
+ Assert (!( file.onRodeoEndedCallbacks.contains( callbackFunc ) ))
+ file.onRodeoEndedCallbacks.append( callbackFunc )
+}
+
+function PlayerBeginsTitanRodeo( entity player, RodeoPackageStruct rodeoPackage, entity rodeoTitan )
+{
+ entity soul = rodeoTitan.GetTitanSoul()
+ Assert( IsValid( soul ) )
+
+ bool sameTeam = player.GetTeam() == rodeoTitan.GetTeam()
+ bool playerWasEjecting = player.p.pilotEjecting // have to store this off here because the "RodeoStarted" signal below ends eject, so it will be too late to check it in actual rodeo function. Used to check for eject -> rodeo
+
+ player.p.rodeoShouldAdjustJumpOffVelocity = true
+
+ player.Signal( "RodeoStarted" )
+
+ bool playerHadBatteryAtStartOfRodeo = PlayerHasBattery( player )
+
+ OnThreadEnd(
+ function () : ( player, soul, sameTeam, rodeoTitan, playerHadBatteryAtStartOfRodeo )
+ {
+ RodeoPackageStruct rodeoPackage = player.p.rodeoPackage
+ entity newRodeoTitan = rodeoTitan
+
+ //Clear the rodeo alert and update the newRodeoTitan to be the soul's titan
+ if ( IsValid( soul ) )
+ {
+ soul.SetLastRodeoHitTime( 0 ) //Clear rodeo warning for next time a player jumps on
+ newRodeoTitan = soul.GetTitan() //rodeoTitan might have changed because a player embarked/disembarked etc
+
+ foreach ( callbackFunc in file.onRodeoEndedCallbacks )
+ {
+ callbackFunc( player, newRodeoTitan )
+ }
+
+ for( int i = 0; i < soul.rodeoReservedSlots.len(); ++i )
+ {
+ if ( soul.rodeoReservedSlots[ i ] == player )
+ {
+ soul.rodeoReservedSlots[ i ] = null
+ break
+ }
+ }
+
+ if ( soul.soul.batteryContainerBeingUsed && playerHadBatteryAtStartOfRodeo ) //i.e. rodeo got interruped early
+ {
+ string titanType = GetSoulTitanSubClass( soul )
+ entity batteryContainer = soul.soul.batteryContainer
+ batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up" ) )
+ soul.soul.batteryContainerBeingUsed = false
+
+ }
+
+ // if the player is invalid, we still need to enable rodeo on the titan
+ // normally this would happen in Rodeo_Detach(), but that only works if the player is valid
+ if ( !IsValid( player ) )
+ EnableTitanRodeo( newRodeoTitan )
+ }
+
+ if ( IsValid( player ) )
+ {
+ player.Signal( "RodeoOver" )
+ player.SetNameVisibleToFriendly( true ) // show name of the pilot again
+ player.SetNameVisibleToEnemy( true )
+ ClearPlayerAnimViewEntity( player )
+ player.AnimViewEntity_SetLerpOutTime( 0.4 ) // blend out the clear anim view entity
+ player.ClearParent()
+ player.Anim_Stop()
+ player.SetOneHandedWeaponUsageOff()
+ player.SetTitanSoulBeingRodeoed( null )
+ player.UnforceStand()
+ player.kv.PassDamageToParent = false
+ player.TouchGround() // so you can double jump off
+ StopSoundOnEntity( player, rodeoPackage.cockpitSound )
+ StopSoundOnEntity( player, rodeoPackage.worldSound )
+ if ( Rodeo_IsAttached( player ) )
+ Rodeo_Detach( player )
+
+ if ( IsAlive( player ) )
+ {
+ int attachIndex = newRodeoTitan.LookupAttachment( rodeoPackage.attachPoint )
+ vector startPos = newRodeoTitan.GetAttachmentOrigin( attachIndex )
+
+ if ( !PlayerCanTeleportHere( player, startPos, newRodeoTitan ) )
+ {
+ startPos = newRodeoTitan.GetOrigin()
+ if ( !PlayerCanTeleportHere( player, startPos, newRodeoTitan ) )
+ startPos = player.GetOrigin()
+ }
+
+ thread PlayerJumpsOffRodeoTarget( player, newRodeoTitan, startPos )
+ }
+
+ #if MP
+ player.Signal( "RodeoNukeWindowEnded" )
+ if ( player in file.playersThatWantToUseRodeoGrenade )
+ delete file.playersThatWantToUseRodeoGrenade[ player ]
+ #endif
+ }
+ }
+ )
+
+
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "RodeoOver" )
+
+ string rodeoTitanType = rodeoPackage.rodeoTargetType
+
+ #if MP
+ thread OpenRodeoNukeWindow( player, rodeoTitan )
+ #endif
+
+ thread WatchForPlayerJumpingOffRodeo( player )
+
+ // hide name of the pilot while he is rodeoing
+ player.SetNameVisibleToFriendly( false )
+ player.SetNameVisibleToEnemy( false )
+ player.ForceStand()
+ thread ManagePlayerWeaponDeployment( player, soul ) //Spin this off in its own thread since there are multiple ways for weapon to be deployed
+ player.SetOneHandedWeaponUsageOn()
+ player.TouchGround() // so you can double jump off
+ player.SetTitanSoulBeingRodeoed( soul )
+
+ if ( soul.GetShieldHealth() > 0.0 ) // This was not evaluating properly with 0 being an int, so make it a float which works
+ GiveFriendlyRodeoPlayerProtection( rodeoTitan )
+
+ foreach ( callbackFunc in file.onRodeoStartedCallbacks )
+ callbackFunc( player, rodeoTitan )
+
+ if ( player.GetTeam() != rodeoTitan.GetTeam() && !PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) )
+ soul.SetLastRodeoHitTime( Time() ) // Alert Titan immediately if you don't have passive
+
+ soul.soul.batteryMovedDown = false
+ if ( ShouldThrowGrenadeInHatch( player ) ) //Either player is going to apply a battery, or it's going to throw a grenade. In either case, we want the battery to move
+ {
+ Rodeo_MoveBatteryDown( soul )
+ }
+
+ soul.soul.batteryContainerBeingUsed = true //All rodeo points mark batteryContainer as being true, various exit points mark it as being false when they are done cleaning it up (e.g. playing the appropriate battery going up/down anims)
+
+ if ( !sameTeam )
+ {
+ #if FACTION_DIALOGUE_ENABLED
+ thread PlayRodeoFactionDialogueAfterDelay( player, 0.5 )
+ #endif
+ TitanVO_AlertTitansTargetingThisTitanOfRodeo( player, soul )
+ }
+
+ waitthread PlayerClimbsIntoRodeoPosition( player, rodeoTitan, rodeoPackage, playerWasEjecting )
+
+ #if MP
+ player.Signal( "RodeoNukeWindowEnded" )
+ #endif
+
+ // There has been a wait, verify things are still valid.
+
+ if ( !IsValid( soul ) )
+ return
+
+ entity rodeoTitan = soul.GetTitan()
+
+ if ( !IsAlive( rodeoTitan ) )
+ return
+
+ TryBatteryStyleRodeo( player, rodeoTitan, soul, rodeoPackage )
+}
+
+#if FACTION_DIALOGUE_ENABLED
+void function PlayRodeoFactionDialogueAfterDelay( entity player, float delay = 0.5 )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "RodeoOver" )
+
+ wait delay
+ PlayFactionDialogueToPlayer( "kc_rodeo", player )
+}
+#endif
+
+void function Rodeo_MoveBatteryDown( entity soul )
+{
+ if ( soul.soul.batteryMovedDown )
+ return
+
+ string titanType = GetSoulTitanSubClass( soul )
+ entity batteryContainer = soul.soul.batteryContainer
+ batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_down" ) )
+ soul.soul.batteryMovedDown = true
+}
+
+void function ManagePlayerWeaponDeployment( entity player, entity titanSoul )
+{
+ HolsterAndDisableWeapons( player )
+
+ titanSoul.EndSignal( "OnTitanDeath" )
+ titanSoul.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "RodeoOver" )
+ player.EndSignal( "FriendlyRodeoDeployWeapon" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ DeployAndEnableWeapons( player )
+ }
+ )
+
+ WaitForever()
+}
+
+
+
+
+vector function GetAntiRodeoThrowOffDirection( entity rodeoRider, entity titan )
+{
+ vector backward
+ vector right
+
+ if ( titan.IsPlayer() )
+ {
+ backward = titan.GetViewForward() * -1.0
+ right = titan.GetViewRight()
+ }
+ else
+ {
+ backward = titan.GetForwardVector() * -1.0
+ right = titan.GetRightVector()
+ }
+
+ backward.z = 0
+ right.z = 0
+
+ // map the player's controls to his angles, and add that velocity
+ float xAxis = rodeoRider.GetInputAxisRight()
+ float yAxis = rodeoRider.GetInputAxisForward()
+
+ xAxis = GraphCapped( xAxis, -1.0, 1.0, -0.4, 0.4 )
+ yAxis = GraphCapped( yAxis, -1.0, 1.0, 1.0, 0.75 ) //Cap it so you don't actually let the players jump forwards
+
+ vector direction
+ if ( fabs( xAxis ) < 0.2 && fabs( yAxis ) < 0.2 )
+ {
+ // no significant controller deflection, just push forward by 0.75 as default
+ direction = backward * 0.75
+ }
+ else
+ {
+ vector forwardVec = backward * yAxis
+ vector rightVec = right * xAxis
+ direction = rightVec + forwardVec
+ }
+
+ direction *= 600
+ direction.z = 25
+
+ return direction
+}
+
+
+void function RodeoPilotPullsOutWeapon( entity rodeoPilot, entity rodeoTitan, string rodeoTitanType )
+{
+ PlayerRodeoViewCone( rodeoPilot, rodeoTitanType )
+
+ Rodeo_OnFinishClimbOnAnimation( rodeoPilot ) // This is to let code know the rodeoPilot has finished climbing on the rodeo and ready to fire
+ rodeoPilot.Signal( "FriendlyRodeoDeployWeapon" )
+}
+
+void function TryBatteryStyleRodeo( entity rodeoPilot, entity rodeoTitan, entity titanSoul, RodeoPackageStruct rodeoPackage )
+{
+ titanSoul.EndSignal( "OnTitanDeath" )
+
+ string rodeoTitanType = rodeoPackage.rodeoTargetType
+ if ( rodeoPilot.GetTeam() == rodeoTitan.GetTeam() )
+ {
+ if ( PilotCanApplyBattery( rodeoPilot, rodeoTitan ) )
+ waitthread PlayerAppliesBatteryPack( rodeoPilot, rodeoTitan, titanSoul, rodeoPackage )
+
+ rodeoTitan = titanSoul.GetTitan()
+ Assert( IsAlive( rodeoTitan ) )
+
+ //printt( "After applying battery" )
+
+ //This is default R1 style rodeo, with the panel ripped and ready to be shot at
+ FirstPersonSequenceStruct sequence
+ sequence.attachment = "hijack"
+ sequence.thirdPersonAnimIdle = GetAnimFromAlias( rodeoTitanType, "pt_rodeo_back_right_idle" )
+ sequence.firstPersonAnimIdle = GetAnimFromAlias( rodeoTitanType, "ptpov_rodeo_back_right_idle" )
+ sequence.useAnimatedRefAttachment = true
+ thread FirstPersonSequence( sequence, rodeoPilot, rodeoTitan )
+
+ RodeoPilotPullsOutWeapon( rodeoPilot, rodeoTitan, rodeoTitanType )
+ WaitForever()
+
+ }
+
+ #if MP
+ if ( PlayerWantsToThrowNukeGrenade( rodeoPilot ) )
+ {
+ waitthread PlayerAppliesBatteryPack( rodeoPilot, rodeoTitan, titanSoul, rodeoPackage )
+ return
+ }
+ #endif
+
+ if ( ShouldThrowGrenadeInHatch( rodeoPilot ) )
+ {
+ waitthread PlayerThrowsGrenadeInHatch( rodeoPilot, rodeoTitan, titanSoul, rodeoPackage ) //This ends rodeo at the end of the sequence
+ }
+ else
+ {
+ waitthread PlayerRemovesBatteryPack( rodeoPilot, rodeoTitan, titanSoul, rodeoPackage ) //This ends rodeo at the end of the sequence
+ }
+
+}
+
+struct RodeoRiderSequenceStruct
+{
+ bool wasCloaked = false
+ float cloakEndTime = 0.0
+ string interiorSound = ""
+ string exteriorSound = ""
+}
+
+void function DisableCloakBeforeRodeoSequence( entity rodeoPilot, RodeoRiderSequenceStruct dataStruct )
+{
+ if ( !IsCloaked( rodeoPilot ) )
+ return
+
+ dataStruct.wasCloaked = true
+ dataStruct.cloakEndTime = rodeoPilot.GetCloakEndTime()
+ DisableCloak( rodeoPilot, 0.0 )
+
+}
+
+void function RestoreCloakAfterRodeoSequence( entity rodeoPilot, RodeoRiderSequenceStruct dataStruct )
+{
+ if ( !IsAlive( rodeoPilot ) )
+ return
+
+ if ( !dataStruct.wasCloaked )
+ return
+
+ Assert( dataStruct.cloakEndTime > 0.0 )
+
+ float remainingCloakDuration = max( 0.0, dataStruct.cloakEndTime - Time() )
+ if ( remainingCloakDuration > CLOAK_FADE_IN ) //Has to be greater than 1.0 fade in duration, otherwise will cloak forever
+ EnableCloak( rodeoPilot, remainingCloakDuration, CLOAK_FADE_IN )
+}
+
+void function PlayerRemovesBatteryPack( entity rodeoPilot, entity rodeoTitan, entity titanSoul, RodeoPackageStruct rodeoPackage )
+{
+ string titanType = GetSoulTitanSubClass( titanSoul )
+
+ RodeoRiderSequenceStruct dataStruct
+ dataStruct.interiorSound = GetAudioFromAlias( titanType, "rodeo_battery_steal_1p" )
+ dataStruct.exteriorSound = GetAudioFromAlias( titanType, "rodeo_battery_steal_3p" )
+ DisableCloakBeforeRodeoSequence( rodeoPilot, dataStruct )
+
+ entity tempBattery3p
+ tempBattery3p = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ tempBattery3p.SetParent( rodeoPilot, "R_HAND", false, 0.0 )
+ tempBattery3p.RemoveFromSpatialPartition()
+ tempBattery3p.Hide()
+
+ entity pilotFirstPersonProxy = rodeoPilot.GetFirstPersonProxy()
+ entity tempBattery1p = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ tempBattery1p.SetParent( pilotFirstPersonProxy, "R_HAND", false, 0.0 )
+ tempBattery1p.RemoveFromSpatialPartition()
+ tempBattery1p.Hide()
+
+ rodeoPilot.p.rodeoAnimTempProps.append( tempBattery1p )
+ rodeoPilot.p.rodeoAnimTempProps.append( tempBattery3p )
+
+ AddAnimEvent( rodeoPilot, "rodeo_battery_show", RodeoBatteryRemoval_ShowBattery ) //Consider adding this in add player
+ AddAnimEvent( rodeoPilot, "rodeo_battery_rip", RodeoBatteryRemoval )
+ AddAnimEvent( rodeoPilot, "rodeo_battery_stealth_movement_warning", RodeoBatteryStealthMovementWarning )
+ thread MonitorRodeoPastPointOfNoReturn( rodeoPilot, titanSoul )
+
+ OnThreadEnd(
+ function() : ( rodeoPilot, titanSoul, titanType, dataStruct )
+ {
+ if ( IsValid( titanSoul ) )
+ {
+ entity rodeoPanel = titanSoul.soul.batteryContainer
+ if ( IsValid( rodeoPanel ) )
+ {
+ titanSoul.soul.batteryContainerBeingUsed = false
+
+ if ( titanSoul.soul.batteryContainerPastPointOfNoReturn )
+ {
+ rodeoPanel.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up" ) )
+
+ }
+ else
+ {
+ rodeoPanel.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up_idle" ) )
+ rodeoPanel.Anim_DisableSequenceTransition() //Snap into place instead of blending
+ }
+
+ titanSoul.soul.batteryContainerPastPointOfNoReturn = false
+ }
+ }
+
+
+ if ( !IsValid( rodeoPilot ) )
+ return
+
+ if ( HasAnimEvent( rodeoPilot, "rodeo_battery_rip" ) )
+ DeleteAnimEvent( rodeoPilot, "rodeo_battery_rip" )
+
+ if ( HasAnimEvent( rodeoPilot, "rodeo_battery_show" ) )
+ DeleteAnimEvent( rodeoPilot, "rodeo_battery_show" )
+
+ if ( HasAnimEvent( rodeoPilot, "rodeo_battery_stealth_movement_warning" ) )
+ DeleteAnimEvent( rodeoPilot, "rodeo_battery_stealth_movement_warning" )
+
+ ClearRodeoAnimTempProps( rodeoPilot )
+
+ StopSoundOnEntity( rodeoPilot, dataStruct.interiorSound )
+ StopSoundOnEntity( rodeoPilot, dataStruct.exteriorSound )
+
+ RestoreCloakAfterRodeoSequence( rodeoPilot, dataStruct )
+ }
+ )
+
+ FirstPersonSequenceStruct sequence
+ sequence.attachment = "hijack"
+ string batteryRipAnim = GetAnimFromAlias( titanType, "pt_rodeo_back_right_hijack_battery" ) // default, old style
+ //printt( "Battery Rip Anim: " + batteryRipAnim )
+ sequence.thirdPersonAnim = batteryRipAnim
+ sequence.firstPersonAnim = GetAnimFromAlias( titanType, "ptpov_rodeo_back_right_hijack_battery" )
+
+ if ( GetBugReproNum() == 112023 )
+ rodeoTitan.SnapEyeAngles( < 89, 100.02, 0 > )
+
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( dataStruct.interiorSound, dataStruct.exteriorSound, rodeoPilot, rodeoTitan ) //Play sound on rodeoPilot instead of panel
+ FirstPersonSequence( sequence, rodeoPilot, rodeoTitan )
+}
+
+void function MonitorRodeoPastPointOfNoReturn( entity rodeoPilot, entity titanSoul )
+{
+ titanSoul.Signal( "MonitorRodeoPastPointOfNoReturn" )
+ titanSoul.EndSignal( "MonitorRodeoPastPointOfNoReturn" )
+ rodeoPilot.EndSignal( "RodeoOver" )
+
+ titanSoul.soul.batteryContainerPastPointOfNoReturn = false
+
+ rodeoPilot.WaitSignal( "RodeoPointOfNoReturn" )
+ titanSoul.soul.batteryContainerPastPointOfNoReturn = true
+}
+
+void function PlayerThrowsGrenadeInHatch( entity rodeoPilot, entity rodeoTitan, entity titanSoul, RodeoPackageStruct rodeoPackage )
+{
+ AddAnimEvent( rodeoPilot, "rodeo_battery_grenade_damage", RodeoBatteryRemoval )
+ AddAnimEvent( rodeoPilot, "rodeo_battery_grenade_show", RodeoBatteryGrenadeShow )
+ AddAnimEvent( rodeoPilot, "rodeo_battery_stealth_movement_warning", RodeoBatteryStealthMovementWarning )
+
+ entity grenade3p = CreatePropDynamic( GRENADE_MODEL )
+ grenade3p.SetParent( rodeoPilot, "PROPGUN", false, 0.0 )
+ grenade3p.RemoveFromSpatialPartition()
+ grenade3p.Hide()
+
+ entity grenade1p = CreatePropDynamic( GRENADE_MODEL )
+ grenade1p.SetParent( rodeoPilot.GetFirstPersonProxy(), "PROPGUN", false, 0.0 )
+ grenade1p.RemoveFromSpatialPartition()
+ grenade1p.Hide()
+
+ rodeoPilot.p.rodeoAnimTempProps.append( grenade3p )
+ rodeoPilot.p.rodeoAnimTempProps.append( grenade1p )
+
+ string titanType = GetSoulTitanSubClass( titanSoul )
+ RodeoRiderSequenceStruct dataStruct
+ dataStruct.interiorSound = GetAudioFromAlias( titanType, "rodeo_grenade_1p" )
+ dataStruct.exteriorSound = GetAudioFromAlias( titanType, "rodeo_grenade_3p" )
+ DisableCloakBeforeRodeoSequence( rodeoPilot, dataStruct )
+
+ OnThreadEnd(
+ function() : ( rodeoPilot, titanSoul, titanType, dataStruct )
+ {
+ if ( IsValid( titanSoul ) )
+ {
+ titanSoul.soul.batteryContainerBeingUsed = false
+ titanSoul.soul.batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up" ) )
+ }
+
+ if ( !IsValid( rodeoPilot ) )
+ return
+
+ if ( HasAnimEvent( rodeoPilot, "rodeo_battery_grenade_damage" ) )
+ DeleteAnimEvent( rodeoPilot, "rodeo_battery_grenade_damage" )
+
+ if ( HasAnimEvent( rodeoPilot, "rodeo_battery_grenade_show" ) )
+ DeleteAnimEvent( rodeoPilot, "rodeo_battery_grenade_show" )
+
+ if ( HasAnimEvent( rodeoPilot, "rodeo_battery_stealth_movement_warning" ) )
+ DeleteAnimEvent( rodeoPilot, "rodeo_battery_stealth_movement_warning" )
+
+ ClearRodeoAnimTempProps( rodeoPilot )
+
+ StopSoundOnEntity( rodeoPilot, dataStruct.interiorSound )
+ StopSoundOnEntity( rodeoPilot, dataStruct.exteriorSound )
+
+ RestoreCloakAfterRodeoSequence( rodeoPilot, dataStruct )
+ }
+ )
+
+ FirstPersonSequenceStruct sequence
+ sequence.attachment = "hijack"
+ //string batteryRipAnim = GetAnimFromAlias( titanSubClass, "pt_rodeo_back_right_hijack_battery" ) // Do this once the animations aren't named the same/enabled for different titans
+ //printt( "Battery Rip Anim: " + batteryRipAnim )
+ sequence.thirdPersonAnim = GetAnimFromAlias( titanType, "pt_rodeo_grenade" )
+ sequence.firstPersonAnim = GetAnimFromAlias( titanType, "ptpov_rodeo_grenade" )
+
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( dataStruct.interiorSound, dataStruct.exteriorSound, rodeoPilot, rodeoTitan ) //Play sound on rodeoPilot instead of panel
+
+ waitthread FirstPersonSequence( sequence, rodeoPilot, rodeoTitan )
+}
+
+void function PlayerAppliesBatteryPack_DelayedClearSyncedEntity( entity rodeoPilot, entity titanSoul )
+{
+ if ( !IsValid( rodeoPilot ) )
+ {
+ return
+ }
+
+ if ( !IsValid( titanSoul ) )
+ {
+ return
+ }
+
+ if ( titanSoul.soul.batteryContainerBeingUsed )
+ {
+ return
+ }
+
+ rodeoPilot.SetSyncedEntity( null )
+}
+
+void function PlayerAppliesBatteryPack( entity rodeoPilot, entity rodeoTitan, entity titanSoul, RodeoPackageStruct rodeoPackage )
+{
+
+ entity battery
+
+ #if MP
+ bool nukeVersion = false
+ if ( PlayerWantsToThrowNukeGrenade( rodeoPilot ) )
+ {
+ nukeVersion = true
+ // battery = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ }
+ else
+ {
+
+ battery = GetBatteryOnBack( rodeoPilot )
+ battery.Hide() //Hide it because the animation has a battery model already
+ }
+ #else
+ battery = GetBatteryOnBack( rodeoPilot )
+ battery.Hide() //Hide it because the animation has a battery model already
+ #endif
+
+ entity rodeoPanel = titanSoul.soul.batteryContainer
+
+ entity tempBattery3p
+ tempBattery3p = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ tempBattery3p.SetParent( rodeoPilot, "R_HAND", false, 0.0 )
+ tempBattery3p.RemoveFromSpatialPartition()
+
+ entity tempBattery1p
+ tempBattery1p = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ tempBattery1p.SetParent( rodeoPilot.GetFirstPersonProxy(), "R_HAND", false, 0.0 )
+ tempBattery1p.RemoveFromSpatialPartition()
+
+ #if MP
+ if ( nukeVersion )
+ tempBattery1p.SetSkin( 1 )
+ #endif
+ if ( IsAmpedBattery( battery ) )
+ {
+ tempBattery1p.SetSkin( 2 )
+ tempBattery3p.SetSkin( 2 )
+ }
+
+ rodeoPilot.p.rodeoAnimTempProps.append( tempBattery3p )
+ rodeoPilot.p.rodeoAnimTempProps.append( tempBattery1p )
+
+ string soundAlias = "rodeo_battery_return"
+ string animAlias = "rodeo_back_right_apply_battery"
+
+ #if MP
+ if ( nukeVersion )
+ {
+ soundAlias = "nuke_rodeo_battery_return"
+ animAlias = "nuke_rodeo_back_right_apply_battery"
+ }
+ #endif
+
+ string titanType = GetSoulTitanSubClass( titanSoul )
+ RodeoRiderSequenceStruct dataStruct
+ dataStruct.interiorSound = GetAudioFromAlias( titanType, soundAlias + "_1p" )
+ dataStruct.exteriorSound = GetAudioFromAlias( titanType, soundAlias + "_3p" )
+ DisableCloakBeforeRodeoSequence( rodeoPilot, dataStruct )
+
+ OnThreadEnd(
+ function() : ( battery, titanSoul, titanType, rodeoPilot, dataStruct )
+ {
+ if ( IsValid( battery ) )
+ battery.Show()
+
+ entity batteryContainer = titanSoul.soul.batteryContainer
+ if ( IsValid( batteryContainer ) )
+ {
+ if ( IsValid( battery ) )
+ {
+ batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up" ) )
+ }
+ else
+ {
+ batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up_idle" ) )
+ batteryContainer.Anim_DisableSequenceTransition()
+
+ }
+
+ if ( IsValid( rodeoPilot ) && IsValid( titanSoul ) )
+ {
+ delaythread( 0.1 ) PlayerAppliesBatteryPack_DelayedClearSyncedEntity( rodeoPilot, titanSoul )
+ }
+
+ titanSoul.soul.batteryContainerBeingUsed = false
+ }
+
+
+ if ( !IsValid( rodeoPilot ) )
+ return
+
+ ClearRodeoAnimTempProps( rodeoPilot )
+
+ StopSoundOnEntity( rodeoPilot, dataStruct.interiorSound )
+ StopSoundOnEntity( rodeoPilot, dataStruct.exteriorSound )
+
+ RestoreCloakAfterRodeoSequence( rodeoPilot, dataStruct )
+ }
+ )
+
+ FirstPersonSequenceStruct sequence
+ sequence.attachment = "hijack"
+ string batteryApplicationAnim = GetAnimFromAlias( titanType, "pt_" + animAlias ) // default, old style
+ //printt( "Battery Application Anim: " + batteryApplicationAnim )
+ sequence.thirdPersonAnim = batteryApplicationAnim
+ sequence.firstPersonAnim = GetAnimFromAlias( titanType, "ptpov_" + animAlias )
+
+ EmitDifferentSoundsOnEntityForPlayerAndWorld( dataStruct.interiorSound, dataStruct.exteriorSound, rodeoPilot, rodeoTitan ) //Play sound on rodeoPilot instead of panel
+
+ entity batteryContainer = titanSoul.soul.batteryContainer
+ if ( batteryContainer )
+ {
+ rodeoPilot.SetSyncedEntity( batteryContainer )
+ }
+
+ waitthread FirstPersonSequence( sequence, rodeoPilot, rodeoTitan )
+
+ //Time passed, need to update titan reference
+ rodeoTitan = titanSoul.GetTitan()
+ Assert( IsAlive( rodeoTitan ) )
+
+
+ #if MP
+ if ( nukeVersion )
+ thread RodeoForceNuke( rodeoPilot )
+ else
+ Rodeo_PilotAddsBatteryToFriendlyTitan( rodeoPilot, rodeoTitan )
+ #else
+ Rodeo_PilotAddsBatteryToFriendlyTitan( rodeoPilot, rodeoTitan )
+ #endif
+
+}
+
+void function ClearRodeoAnimTempProps( entity player )
+{
+ foreach( tempProp in player.p.rodeoAnimTempProps )
+ {
+ if ( IsValid( tempProp ) )
+ tempProp.Destroy()
+ }
+
+ player.p.rodeoAnimTempProps.clear()
+}
+
+void function RodeoBatteryPackRemovalDamage( entity attacker, entity victim, entity victimTitanSoul )
+{
+ victimTitanSoul.e.lastRodeoAttacker = attacker
+
+ int damageAmount = GetSegmentHealthForTitan( victim )
+
+ if ( PlayerHasBattery( attacker ) ) //i.e. you are throwing a grenade
+ damageAmount /= 2
+
+ SetSoulBatteryCount( victimTitanSoul, GetSoulBatteryCount( victimTitanSoul ) - 1 )
+
+ int damageScriptType = damageTypes.rodeoBatteryRemoval
+
+ if ( GetDoomedState( victim ) )
+ {
+ damageAmount = victim.GetHealth() + 1
+ }
+ else if ( IsHardcoreGameMode() )
+ {
+ damageAmount = victim.GetHealth()
+ }
+
+ table damageTable =
+ {
+ scriptType = damageScriptType
+ forceKill = false
+ damageSourceId = eDamageSourceId.rodeo_battery_removal
+ origin = victim.GetOrigin()
+ hitbox = 2
+ }
+
+ victim.TakeDamage( damageAmount, attacker, attacker, damageTable )
+ if ( victim.IsNPC() )
+ victim.SetEnemyLKP( attacker, attacker.GetOrigin() )
+
+ entity batteryContainer = victimTitanSoul.soul.batteryContainer
+ int hatchAttachmentIndex = batteryContainer.LookupAttachment( "REF" )
+
+ TitanLoseSegementFX( victim, attacker, victim.GetAttachmentOrigin( hatchAttachmentIndex ) )
+
+ if ( IsSingleplayer() && attacker.IsPlayer() )
+ {
+ UnlockAchievement( attacker, achievements.RODEO )
+ }
+}
+
+void function Rodeo_DropAllBatteriesOnDeath( entity player, entity attacker, var damageInfo )
+{
+ Rodeo_DropAllBatteries( player )
+}
+
+void function Rodeo_ApplyAllBatteriesOnEmbark( entity player, entity titan )
+{
+ thread Rodeo_ApplyAllBatteriesOnEmbark_Thread( player, titan )
+}
+
+void function Rodeo_ApplyAllBatteriesOnEmbark_Thread( entity player, entity titan )
+{
+ player.EndSignal( "OnDeath" )
+
+ if ( !PlayerHasBattery( player ) )
+ return
+
+ entity soul = player.GetTitanSoul()
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+
+ table<string,bool> e
+ e[ "hadAmped" ] <- false
+
+ while ( GetPlayerBatteryCount( player ) > 0 )
+ {
+ thread Rodeo_ApplyBatteryDelayed( player, e )
+ }
+
+ wait 0.4
+
+ MessagePlayerGivingBatteryToTitan( player, player, eEventNotifications.Rodeo_YouEmbarkedWithABattery, -1, e[ "hadAmped" ] )
+}
+
+void function Rodeo_ApplyBatteryDelayed( entity player, table<string,bool> e )
+{
+ entity battery = Rodeo_TakeBatteryAwayFromPilot( player )
+ e[ "hadAmped" ] = e[ "hadAmped" ] || IsAmpedBattery( battery )
+ int skin = battery.GetSkin()
+ battery.Destroy()
+
+ entity dummyBattery = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ dummyBattery.SetSkin( skin )
+ dummyBattery.Hide()
+
+ entity soul = player.GetTitanSoul()
+
+ OnThreadEnd(
+ function() : ( dummyBattery ) {
+ if ( IsValid( dummyBattery ) )
+ dummyBattery.Destroy()
+ }
+ )
+
+ if ( !IsValid( soul ) )
+ return
+
+ dummyBattery.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+
+ wait 0.4 // delay so that it applies the battery when the player is inside the titan, so he can see the health bar change
+
+ if ( IsValid( soul.GetTitan() ) )
+ Rodeo_ApplyBatteryToTitan( dummyBattery, soul.GetTitan() )
+}
+
+void function Rodeo_GiveExecutingTitanABattery( entity attacker )
+{
+ Rodeo_ApplyBatteryToTitan( null, attacker )
+ MessagePlayerGivingBatteryToTitan( attacker, attacker, eEventNotifications.Rodeo_TitanPickedUpBattery, -1, false )
+}
+
+void function Rodeo_GiveBatteryToPlayer( entity player )
+{
+ if ( PlayerHasMaxBatteryCount( player ) )
+ return
+
+ entity battery = Rodeo_CreateBatteryPack()
+ Rodeo_OnTouchBatteryPack_Internal( player, battery ) //Just setting the origin to the player's origin also works, but it will parent weirdly to a pilot's back. probably because we end up doing 2 SetOrigins in the same frame
+}
+
+void function Burnmeter_AmpedBattery( entity player )
+{
+ #if MP
+ Burnmeter_EmergencyBattery( player )
+ entity battery = GetBatteryOnBack( player )
+
+ if ( battery == null ) // not ideal but at least the game won't crash
+ return
+
+ battery.SetSkin( 2 ) // yellow - CHANGE SKIN TO ORANGE someday
+ Battery_StartFX( battery )
+ #endif
+}
+
+void function Burnmeter_EmergencyBattery( entity player )
+{
+ entity battery = Rodeo_CreateBatteryPack()
+ if ( !PlayerHasMaxBatteryCount( player ) )
+ {
+ Rodeo_OnTouchBatteryPack_Internal( player, battery ) //Just setting the origin to the player's origin also works, but it will parent weirdly to a pilot's back. probably because we end up doing 2 SetOrigins in the same frame
+ return
+ }
+ else
+ {
+ //Based off ThrowBattery
+ vector ornull thrownSpot = CalculateSpotForThrownBattery( player, battery )
+
+ if ( thrownSpot == null )
+ thrownSpot = player.GetOrigin()
+
+ expect vector( thrownSpot )
+
+ vector viewVector = player.GetViewVector()
+
+ //printt( "viewVector: " + viewVector )
+ //battery.SetPhysics( MOVETYPE_FLYGRAVITY )
+
+ battery.SetParent( player ) //HACK: Clear Ground Entity of battery. Not really sure why this is needed
+ battery.ClearParent()
+
+ battery.SetAngles( < 0, 0, 0 > )
+ battery.SetOrigin( thrownSpot )
+
+ vector playerVel = player.GetVelocity()
+ vector verticalAdjustment = < 0, 0, 0 >
+ if ( playerVel.z == 0 )
+ verticalAdjustment = < 0, 0, 100 >
+
+ vector batteryVel = playerVel + viewVector * 50 + verticalAdjustment
+ battery.SetVelocity( batteryVel )
+
+ }
+
+}
+
+entity function Rodeo_CreateBatteryPack( entity titanStolenFrom = null )
+{
+ entity batteryPack = CreateEntity( "item_titan_battery" )
+ batteryPack.SetValueForModelKey( RODEO_BATTERY_MODEL )
+ batteryPack.kv.fadedist = 10000
+ DispatchSpawn( batteryPack )
+ batteryPack.SetModel( RODEO_BATTERY_MODEL )
+ batteryPack.s.touchEnabledTime <- 0
+ batteryPack.s.batteryCarriedStatusEffect <- 0
+
+ batteryPack.Minimap_SetAlignUpright( true )
+ batteryPack.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ batteryPack.Minimap_SetClampToEdge( false )
+ batteryPack.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ batteryPack.Minimap_AlwaysShow( TEAM_IMC, null )
+
+ Battery_StartFX( batteryPack )
+
+ if ( HAS_BATTERY_THIEF_ICON && titanStolenFrom != null )
+ {
+ Assert( titanStolenFrom.IsTitan() )
+ if ( titanStolenFrom.IsPlayer() )
+ {
+ batteryPack.SetBossPlayer( titanStolenFrom )
+ }
+ else
+ {
+ entity titanOwner = titanStolenFrom.GetBossPlayer()
+ if ( IsValid( titanOwner ) )
+ batteryPack.SetBossPlayer( titanOwner )
+ }
+
+ thread ClearBatteryBossPlayerAfterDelay( batteryPack, titanStolenFrom, RODEO_BATTERY_THIEF_ICON_DURATION )
+ }
+
+ batteryPack.Highlight_SetInheritHighlight( true )
+
+ if ( IsSingleplayer() )
+ {
+ thread AttachTriggerToBattery( batteryPack )
+ }
+
+ //thread MonitorBatteryVelocity( batteryPack )
+ return batteryPack
+}
+
+void function Battery_StartFX( entity battery )
+{
+ Battery_StopFX( battery ) //Clear existing fx first. Not quite ideal but easier to do this than have bug potential for FX to stack on top of each other.
+ int attachID = battery.LookupAttachment( "fx_center" )
+
+ asset fx = BATTERY_FX_FRIENDLY
+ if ( IsAmpedBattery( battery ) )
+ fx = BATTERY_FX_AMPED
+
+ battery.e.fxArray.append( StartParticleEffectOnEntity_ReturnEntity( battery, GetParticleSystemIndex( fx ), FX_PATTACH_POINT_FOLLOW, attachID ) )
+}
+
+void function Battery_StopFX( entity battery )
+{
+ foreach( fx in battery.e.fxArray )
+ {
+ EffectStop( fx )
+ }
+
+ battery.e.fxArray.clear()
+}
+
+void function Battery_StopFXAndHideIconForPlayer( entity player )
+{
+ if ( !PlayerHasBattery( player ) )
+ return
+
+ entity battery = GetBatteryOnBack( player )
+
+ Battery_StopFX( battery )
+ battery.ClearBossPlayer() //Boss player controls visibility of icon
+}
+
+void function AttachTriggerToBattery( entity batteryPack )
+{
+ entity trigger = CreateEntity( "trigger_cylinder" )
+ trigger.SetRadius( 100 )
+ trigger.SetAboveHeight( 100 )
+ trigger.SetBelowHeight( 100 ) //i.e. make the trigger a sphere as opposed to a cylinder
+ trigger.SetOrigin( batteryPack.GetOrigin() )
+ trigger.SetParent( batteryPack )
+ trigger.kv.triggerFilterNpc = "none" // none
+ trigger.kv.triggerFilterPlayer = "titan" // titan players only
+ DispatchSpawn( trigger )
+ trigger.SetEnterCallback( BatteryTrigger_ApplyBattery )
+}
+
+void function BatteryTrigger_ApplyBattery( entity trigger, entity player )
+{
+ if ( player.IsTitan() )
+ {
+ entity batteryPack = trigger.GetParent()
+
+ if ( batteryPack != null )
+ {
+ Rodeo_OnTouchBatteryPack( player, batteryPack )
+ }
+ }
+}
+
+void function Rodeo_PilotPicksUpBattery_Silent( entity pilot, entity battery )
+{
+ Assert( battery.GetParent() == null )
+
+ if ( PlayerHasBattery( pilot ) )
+ {
+ battery.Destroy()
+ battery = GetBatteryOnBack( pilot )
+ }
+
+ SetPlayerBatteryCount( pilot, GetPlayerBatteryCount( pilot ) + 1 )
+ if ( GetPlayerBatteryCount( pilot ) == 1 )
+ {
+ battery.SetParent( pilot, "BATTERY_ATTACH" )
+ battery.MarkAsNonMovingAttachment()
+ battery.RemoveFromSpatialPartition()
+ SetBatteryOnBack( pilot, battery )
+ }
+
+ if ( GAMETYPE == FREE_AGENCY && PlayerHasMaxBatteryCount( pilot ) && PlayerEarnMeter_GetOwnedFrac( pilot ) < 1.0 )
+ {
+ Rodeo_RemoveAllBatteriesOffPlayer( pilot )
+ return
+ }
+
+ if ( battery.s.batteryCarriedStatusEffect == 0 )
+ battery.s.batteryCarriedStatusEffect = StatusEffect_AddEndless( battery, eStatusEffect.battery_carried, 1.0 )
+ battery.Minimap_Hide( TEAM_MILITIA, null )
+ battery.Minimap_Hide( TEAM_IMC, null )
+}
+
+void function Rodeo_PilotPicksUpBattery( entity pilot, entity battery )
+{
+ Rodeo_PilotPicksUpBattery_Silent( pilot, battery )
+ EmitSoundOnEntityOnlyToPlayer( pilot, pilot, PILOT_PICKS_UP_BATTERY_SOUND )
+ //AddPlayerHeldButtonEventCallback( player, IN_USE, Rodeo_PilotThrowsBattery, RODEO_THROW_BATTERY_BUTTON_HOLD_TIME )
+}
+
+entity function Rodeo_TakeBatteryAwayFromPilot( entity pilot )
+{
+ //RemovePlayerHeldButtonEventCallback( player, IN_USE, Rodeo_PilotThrowsBattery, RODEO_THROW_BATTERY_BUTTON_HOLD_TIME )
+ SetPlayerBatteryCount( pilot, GetPlayerBatteryCount( pilot ) - 1 )
+
+ if ( GetPlayerBatteryCount( pilot ) == 0 )
+ {
+ entity battery = GetBatteryOnBack( pilot )
+ Assert( IsValid( battery ) )
+ Assert( battery.GetParent() == pilot )
+
+ SetBatteryOnBack( pilot, null ) //Defensive fix for 209362. Set it to null before doing any other actions on it which might cause execution to jump somewhere else. I think doing PutEntityInSafeSpot() might cause this?
+ battery.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ battery.Minimap_AlwaysShow( TEAM_IMC, null )
+
+ battery.s.touchEnabledTime = Time() + 0.3
+
+ Battery_StartFX( battery ) //Needed to properly restore effect when player is killed while cloaked and carrying a battery
+
+ battery.Show()
+
+ if ( battery.s.batteryCarriedStatusEffect > 0 )
+ {
+ StatusEffect_Stop( battery, battery.s.batteryCarriedStatusEffect )
+ battery.s.batteryCarriedStatusEffect = 0
+ }
+
+ battery.ClearParent()
+ battery.AddToSpatialPartition()
+ battery.SetAngles( <0, 0, 0 > )
+ battery.SetVelocity( < 0, 0, 1 > )
+ PutEntityInSafeSpot( battery, pilot, null, pilot.GetOrigin(), battery.GetOrigin() ) //This might cause thread of execution to jump somewhere else, see 209362
+ return battery
+ }
+ else
+ {
+ return null
+ }
+
+ unreachable
+}
+
+void function Rodeo_PilotThrowsBattery( entity pilot )
+{
+ if ( pilot.ContextAction_IsActive() ) //Maybe letting you throw the battery out of the dropship might be cool?
+ return
+
+ entity battery = GetBatteryOnBack( pilot )
+
+ vector ornull thrownSpot = CalculateSpotForThrownBattery( pilot, battery )
+
+ if ( thrownSpot == null )
+ {
+ EmitSoundOnEntityOnlyToPlayer( pilot, pilot, "CoOp_SentryGun_DeploymentDeniedBeep" )
+ return
+ }
+
+ expect vector( thrownSpot )
+
+ vector viewVector = pilot.GetViewVector()
+
+ //printt( "viewVector: " + viewVector )
+
+ entity playerBattery = Rodeo_TakeBatteryAwayFromPilot( pilot )
+ Assert( playerBattery == battery )
+
+ //battery.SetPhysics( MOVETYPE_FLYGRAVITY )
+
+ battery.SetAngles( < 0, 0, 0 > )
+ battery.SetOrigin( thrownSpot )
+ vector pilotVel = pilot.GetVelocity()
+ vector verticalAdjustment = < 0, 0, 0 >
+ if ( pilotVel.z == 0 )
+ verticalAdjustment = < 0, 0, 200 >
+
+ vector batteryVel = pilotVel + viewVector * 300 + verticalAdjustment
+ //printt( "batteryVel: " + batteryVel)
+ //battery.SetVelocity( Vector( 0, 0, 0 ) )
+ battery.SetVelocity( batteryVel )
+
+ MessageToPlayer( pilot, eEventNotifications.Rodeo_YouDroppedABattery )
+}
+
+vector ornull function CalculateSpotForThrownBattery( entity pilot, entity battery )
+{
+ vector viewVector = pilot.GetViewVector()
+ vector eyePos = pilot.EyePosition()
+ vector batteryMins = battery.GetBoundingMins()
+ vector batteryMaxs = battery.GetBoundingMaxs()
+ vector endPos = eyePos + viewVector * 100
+ TraceResults hullResult = TraceHull( eyePos, endPos, batteryMins, batteryMaxs, pilot, TRACE_MASK_SOLID | TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
+
+ //PrintTraceResults( hullResult )
+
+ if ( hullResult.startSolid )
+ return null
+
+ if ( hullResult.hitEnt == pilot )
+ return null
+
+ if ( hullResult.fraction == 1.0 )
+ return endPos
+
+ return hullResult.endPos
+}
+
+void function Rodeo_DropAllBatteries( entity player )
+{
+ if ( !PlayerHasBattery( player ) )
+ return
+
+ while ( GetPlayerBatteryCount( player ) > 1 )
+ {
+ entity newBattery = Rodeo_CreateBatteryPack()
+ newBattery.s.touchEnabledTime = Time() + 0.3
+ //look into using the players bounds for placement, instead of hardcoded numbers
+ array<vector> offsets = [<0,0,0>, <30,0,0>, <0,30,0>, <0,-30,0> ]
+ newBattery.SetOrigin( player.GetWorldSpaceCenter() + offsets[ GetPlayerBatteryCount( player ) ] ) //Temp fix, should change the origin
+ newBattery.SetAngles( <0, 0, 0 > )
+ vector baseVelocity = player.GetVelocity()
+ baseVelocity.z = 0
+ newBattery.SetVelocity( baseVelocity + AnglesToForward( <0, RandomInt( 360.0 ), 0 > ) * 100 + <0,0,1> )
+ Rodeo_TakeBatteryAwayFromPilot( player )
+ }
+
+ entity battery = Rodeo_TakeBatteryAwayFromPilot( player )
+ Assert ( IsValid( battery ) )
+}
+
+void function Rodeo_RemoveBatteryOffPlayer( entity player ) //Meant to be used in prematch etc.
+{
+ if ( !PlayerHasBattery( player ) )
+ return
+
+ entity battery = Rodeo_TakeBatteryAwayFromPilot( player )
+ if ( IsValid( battery ) )
+ {
+ battery.Destroy()
+ }
+}
+
+void function Rodeo_RemoveAllBatteriesOffPlayer( entity player ) //Meant to be used in prematch etc.
+{
+ if ( !PlayerHasBattery( player ) )
+ return
+
+ while ( GetPlayerBatteryCount( player ) > 0 )
+ {
+ Rodeo_RemoveBatteryOffPlayer( player )
+ }
+}
+
+void function Rodeo_ApplyBatteryToTitan( entity battery, entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) )
+ return
+
+ int healingAmount
+ if ( IsSingleplayer() )
+ healingAmount = 2000
+ else
+ healingAmount = GetSegmentHealthForTitan( titan )
+
+ int health = titan.GetHealth()
+ int maxHealth = titan.GetMaxHealth()
+
+ SetSoulBatteryCount( soul, GetSoulBatteryCount( soul ) + 1 )
+
+ int healthDifference = maxHealth - health
+
+ if ( IsSingleplayer() )
+ {
+ if ( soul.IsDoomed() )
+ UndoomTitan( titan, 1 )
+ else if ( healthDifference >= healingAmount )
+ titan.SetHealth( titan.GetHealth() + healingAmount )
+ else
+ titan.SetHealth( titan.GetMaxHealth() )
+
+ if ( GetHealthFrac( titan ) >= BATTERY_PICKUP_IGNORE_FRAC )
+ {
+ titan.SetHealth( titan.GetMaxHealth() )
+ }
+
+ titan.GetTitanSoul().nextRegenTime = Time()
+ if ( healthDifference < healingAmount )
+ {
+ titan.GetTitanSoul().SetShieldHealth( healingAmount - healthDifference + titan.GetTitanSoul().GetShieldHealth() )
+ }
+
+ if ( GetShieldHealthFrac( titan ) >= BATTERY_PICKUP_IGNORE_FRAC )
+ {
+ titan.GetTitanSoul().SetShieldHealth( titan.GetTitanSoul().GetShieldHealthMax() )
+ }
+
+ }
+ else if ( IsMultiplayer() )
+ {
+ if ( SoulHasPassive( soul, ePassives.PAS_VANGUARD_DOOM ) && soul.IsDoomed() )
+ {
+ UndoomTitan( titan, 1 )
+ titan.SetHealth( 1 )
+ }
+ float coreFrac = GetCurrentPlaylistVarFloat( "battery_core_frac", 0.2 )
+ float shieldFrac = GetCurrentPlaylistVarFloat( "battery_shield_frac", 1.0 )
+ float ampedHealthSegmentFrac = GetCurrentPlaylistVarFloat( "amped_battery_health_frac", 2.0 )
+ float healthSegmentFrac = GetCurrentPlaylistVarFloat( "battery_health_frac", 0.5 )
+
+ AddCreditToTitanCoreBuilder( titan, coreFrac ) //Always give core
+
+ int shieldHealth = soul.GetShieldHealth()
+ int shieldMaxHealth = soul.GetShieldHealthMax()
+
+ int shieldDifference = shieldMaxHealth - shieldHealth
+
+ bool batteryIsAmped = IsAmpedBattery( battery )
+ float frac = batteryIsAmped ? ampedHealthSegmentFrac : healthSegmentFrac
+
+ int addHealth = int( healingAmount * frac )
+
+ int totalHealth = minint( titan.GetMaxHealth(), titan.GetHealth() + addHealth )
+ if ( soul.IsDoomed() && batteryIsAmped )
+ {
+ UndoomTitan( titan, 1 )
+ soul.SetShieldHealth( soul.GetShieldHealthMax() )
+ }
+ else
+ {
+ titan.SetHealth( totalHealth )
+ soul.SetShieldHealth( soul.GetShieldHealthMax() )
+ }
+ }
+
+ if ( battery != null )
+ {
+ Assert( battery.GetParent() == null )
+ battery.Destroy()
+ }
+}
+
+bool function Rodeo_OnTouchBatteryPack( entity player, entity batteryPack )
+{
+ Rodeo_OnTouchBatteryPack_Internal( player, batteryPack )
+
+ //Basically always return false since we don't want the battery pack to go away when being touched. ApplyBatteryToTitan() etc will deal with lifetime of battery
+ return false
+}
+
+void function Rodeo_OnTouchBatteryPack_Internal( entity player, entity batteryPack )
+{
+ float currentTime = Time()
+
+ if ( currentTime < batteryPack.s.touchEnabledTime )
+ return
+
+ if ( !IsAlive( player ) )
+ return
+
+ if ( player.IsPhaseShifted() )
+ return
+
+ if ( IsValid( batteryPack.GetParent() ) )
+ return
+
+ if ( PlayerHasMaxBatteryCount( player ) )
+ {
+ if ( IsSingleplayer() )
+ {
+ MessageToPlayer( player, eEventNotifications.BATT_Full, batteryPack )
+ }
+ return
+ }
+
+ if ( player.IsTitan() )
+ {
+ //Try Titans not being able to pick up battery
+ if ( GetCurrentPlaylistVarInt( "rodeo_battery_disembark_to_pickup", 1 ) == 1 )
+ {
+ if ( currentTime - player.p.batteryLastTouchedNotificationTime > 5.0 )
+ {
+ MessageToPlayer( player, eEventNotifications.Rodeo_DisembarkToPickUpBattery )
+ player.p.batteryLastTouchedNotificationTime = currentTime
+
+ }
+ }
+ else
+ {
+ if ( IsSingleplayer() )
+ {
+ if ( player.GetHealth() >= player.GetMaxHealth() * BATTERY_PICKUP_IGNORE_FRAC && player.GetTitanSoul().GetShieldHealth() >= player.GetTitanSoul().GetShieldHealthMax() * BATTERY_PICKUP_IGNORE_FRAC )
+ {
+ MessageToPlayer( player, eEventNotifications.BATT_HealthFull, batteryPack )
+ return
+ }
+ }
+ bool amped = IsAmpedBattery( batteryPack )
+
+ Rodeo_ApplyBatteryToTitan( batteryPack, player )
+ MessagePlayerGivingBatteryToTitan( player, player, eEventNotifications.Rodeo_TitanPickedUpBattery, -1, amped )
+ }
+ return
+ }
+ else
+ {
+ if ( IsCloaked( player ) )
+ Battery_StopFX( batteryPack ) //Will be turned on again when player loses cloak
+
+ Rodeo_PilotPicksUpBattery( player, batteryPack )
+ AddPlayerScore( player, "PilotBatteryPickup" )
+// MessageToPlayer( player, eEventNotifications.Rodeo_PilotPickedUpBattery )
+ return
+ }
+}
+
+void function Rodeo_PilotAddsBatteryToFriendlyTitan( entity rider, entity titan )
+{
+ if ( !titan.IsTitan() )
+ return
+
+ if ( titan.GetTeam() != rider.GetTeam() )
+ return
+
+ if ( !PlayerHasBattery( rider ) )
+ return
+
+ entity battery = Rodeo_TakeBatteryAwayFromPilot( rider )
+ bool amped = IsAmpedBattery( battery )
+
+ if ( file.applyBatteryCallback != null )
+ file.applyBatteryCallback( rider, titan, battery )
+
+ Rodeo_ApplyBatteryToTitan( battery, titan ) //This destroys the battery
+
+ AddPlayerScore( rider, "PilotBatteryApplied" )
+
+ EmitSoundOnEntityOnlyToPlayer( rider, rider, PILOT_APPLIES_BATTERY_TO_TITAN_HEALTH_RESTORED_SOUND )
+
+ if ( titan.IsPlayer() )
+ MessagePlayerGivingBatteryToTitan( titan, rider, eEventNotifications.Rodeo_PilotAppliedBatteryToYou, eEventNotifications.Rodeo_YouAppliedBatteryToTitan, amped )
+ else
+ MessagePlayerGivingBatteryToTitan( titan, rider, eEventNotifications.Rodeo_PilotAppliedBatteryToYourPetTitan, eEventNotifications.Rodeo_YouAppliedBatteryToPetTitan, amped )
+}
+
+void function MessagePlayerGivingBatteryToTitan( entity receivingTitan, entity givingPlayer, int enumForRecevingHealth, int enumForGivingHealth, bool wasAmped )
+{
+ entity receivingPlayer = receivingTitan
+
+ if ( !receivingTitan.IsPlayer() )
+ receivingPlayer = receivingTitan.GetBossPlayer()
+
+ if ( !IsValid( receivingPlayer ) )
+ return
+
+ MessageToPlayer( receivingPlayer, enumForRecevingHealth, givingPlayer, wasAmped )
+ if ( givingPlayer != receivingPlayer )
+ MessageToPlayer( givingPlayer, enumForGivingHealth, receivingTitan, wasAmped )
+}
+
+bool function IsTitanAtFullHealth( entity receivingTitan )
+{
+ if ( !receivingTitan.IsTitan() )
+ return false
+
+ return ( receivingTitan.GetHealth() == receivingTitan.GetMaxHealth() )
+}
+
+function DebugRodeoTimes()
+{
+ array<string> settings = [ "atlas", "ogre", "stryder" ]
+
+ array< asset > models = [ $"models/Humans/imc_pilot/male_cq/imc_pilot_male_cq.mdl", $"models/humans/pilot/female_cq/pilot_female_cq.mdl" ]
+ table times = {}
+
+ array<string> rodeoAnims = [
+ "pt_rodeo_move_back_entrance",
+ "pt_rodeo_move_right_entrance",
+ "pt_rodeo_move_front_entrance",
+ "pt_rodeo_move_front_lower_entrance",
+ "pt_rodeo_move_back_mid_entrance",
+ "pt_rodeo_move_back_lower_entrance",
+ "pt_rodeo_move_left_entrance"
+ ]
+
+ foreach ( model in models )
+ {
+ times[ model ] <- []
+ entity prop = CreatePropDynamic( model, Vector(0,0,0), Vector(0,0,0) )
+ printt( "Human model: " + model )
+
+ foreach ( setting in settings )
+ {
+ foreach ( alias in rodeoAnims )
+ {
+ string animation = GetAnimFromAlias( setting, alias )
+ float time = prop.GetSequenceDuration( animation )
+ times[ model ].append( { time = time, animation = animation } )
+ }
+ }
+
+ prop.Kill_Deprecated_UseDestroyInstead()
+ }
+
+ printt( "Time comparison: " )
+ bool wrong = false
+ for ( int i = 0; i < times[ models[0] ].len(); i++ )
+ {
+ if ( times[models[0]][i].time == times[models[1]][i].time )
+ {
+ printt( " MATCH: " + ( i + 1 ) + " times: " + times[models[0]][i].time + " " + times[models[1]][i].time + " " + times[models[1]][i].animation )
+ }
+ else
+ {
+ printt( "MISMATCH: " + ( i + 1 ) + " times: " + times[models[0]][i].time + " " + times[models[1]][i].time + " " + times[models[1]][i].animation )
+ }
+ if ( ( i + 1 ) % rodeoAnims.len() == 0 )
+ printt( " " )
+ }
+ Assert( !wrong, "Times did not match between male and female, see above" )
+}
+
+void function SetBatteryOnBack( entity player, entity battery )
+{
+ player.SetPlayerNetEnt( "batteryOnBack", battery )
+}
+
+bool function ClientCommand_RequestRodeoBattery( entity player, array<string> args )
+{
+ //PrintFunc()
+ if ( !ShouldLetPlayerRequestBattery( player ) )
+ return true
+
+ player.SetPlayerNetTime( "requestRodeoBatteryLastUsedTime", Time() )
+
+ foreach( friendlyPlayer in GetPlayerArrayOfTeam( player.GetTeam() ) )
+ {
+ if ( friendlyPlayer == player )
+ continue
+
+ if ( friendlyPlayer.IsTitan() )
+ continue
+
+ //Could check to see if players actually have a battery here, but that stops players from being told that they should pick up a battery for someone in need
+ MessageToPlayer( friendlyPlayer, eEventNotifications.Rodeo_RequestBattery, player )
+ }
+
+ return true
+}
+
+bool function ClientCommand_OfferRodeoBattery( entity player, array<string> args )
+{
+ //PrintFunc()
+ if ( args.len() != 1 )
+ return true
+
+ int friendlyTitanEntIndex = args[ 0 ].tointeger()
+
+ if ( friendlyTitanEntIndex < 1 ) //Data sanitation. GetEntByIndex() will assert if passed a negative number. 0 is always world spawn, so the first valid argument is 1
+ return true
+
+ entity friendlyTitan = GetEntByIndex( friendlyTitanEntIndex )
+
+ if ( !ShouldShowOfferRodeoBatteryHint( player, friendlyTitan ) )
+ return true
+
+ entity battery = GetBatteryOnBack( player )
+
+ MessageToPlayer( friendlyTitan, eEventNotifications.Rodeo_FriendlyPickedUpBattery, player, battery.GetEncodedEHandle() )
+
+ player.SetPlayerNetTime( "offerRodeoBatteryLastUsedTime", Time() )
+
+ return true
+
+}
+
+void function PlayerRodeoViewCone( entity player, string rodeoTargetType )
+{
+ player.PlayerCone_FromAnim()
+ player.GetFirstPersonProxy().HideFirstPersonProxy()
+ OpenViewCone( player )
+ player.PlayerCone_Disable()
+ player.EnableWorldSpacePlayerEyeAngles()
+}
+
+
+void function OpenViewCone( entity player )
+{
+ player.PlayerCone_FromAnim()
+ player.PlayerCone_SetMinYaw( -179 )
+ player.PlayerCone_SetMaxYaw( 181 )
+ player.PlayerCone_SetMinPitch( -60 )
+ player.PlayerCone_SetMaxPitch( 60 )
+}
+
+bool function PilotCanApplyBattery( entity rodeoPilot, entity rodeoTitan )
+{
+ if ( !IsAlive( rodeoTitan ) )
+ return false
+
+ if ( rodeoTitan.GetTeam() != rodeoPilot.GetTeam() )
+ return false
+
+ if ( !PlayerHasBattery( rodeoPilot ) )
+ return false
+
+ entity titanSoul = rodeoTitan.GetTitanSoul()
+ Assert( IsValid( titanSoul ) )
+
+ string titanType = GetSoulTitanSubClass( titanSoul )
+
+ return true
+}
+
+void function ClearBatteryBossPlayerAfterDelay( entity battery, entity titan, float delay )
+{
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) )
+ return
+
+ soul.EndSignal( "OnTitanDeath" ) //End signal on soul to properly handle pilot getting in/out of titan
+ battery.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( battery )
+ {
+ if ( IsValid( battery ) )
+ battery.ClearBossPlayer()
+ }
+ )
+
+ wait delay
+}
+
+const float BATTERY_USES_ATTACKER_ORIGIN_THRESHOLD = 500 * 500 //500 seems like a lot, but the Titan melee execution sequences can go pretty far
+
+void function TitanDropsBatteryOnDeath( entity titan, var damageInfo ) //Todo: Might want to do something special for titan melee execution, so the attacker automatically gets a battery
+{
+ if ( !titan.IsTitan() )
+ return
+
+ entity battery = Rodeo_CreateBatteryPack()
+ vector titanOrigin = titan.GetOrigin()
+ battery.SetOrigin( titanOrigin )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ vector safeOrigin = titanOrigin
+ vector attackerOrigin
+ if ( IsValid( attacker ) )
+ {
+ vector attackerOrigin = attacker.GetOrigin()
+ float distSqr = DistanceSqr( attackerOrigin, titanOrigin )
+ //printt( "Distance sqr: " + distSqr )
+ if ( distSqr <= ( BATTERY_USES_ATTACKER_ORIGIN_THRESHOLD ) ) //
+ {
+ //printt( "Putting attackerOrigin as safeOrigin" )
+
+ safeOrigin = attackerOrigin
+ }
+ }
+
+ bool result = PutEntityInSafeSpot( battery, null, null, safeOrigin, titanOrigin )
+ if ( !result )
+ {
+ battery.Destroy() //Can't put the battery anywhere safe, so just destroy it.
+ //printt( "Destroy battery since we can't put it in a safe spot" )
+ }
+}
+
+void function ShowRequestRodeoBatteryHint_OnDamage( entity playerTitan, var damageInfo )
+{
+ ShowRequestRodeoBatteryHint( playerTitan )
+}
+
+void function ShowRequestRodeoBatteryHint_OnPilotBecomesTitan( entity player, entity titan )
+{
+ //printt( "player health: " + player.GetHealth() )
+ ShowRequestRodeoBatteryHint( player )
+}
+
+void function ShowRequestRodeoBatteryHint( entity playerTitan )
+{
+ //PrintFunc()
+ if ( !ShouldLetPlayerRequestBattery( playerTitan ) )
+ return
+
+ float currentTime = Time()
+
+ if ( playerTitan.p.rodeoRequestBatteryHintLastShownTime > 0.0 && currentTime < playerTitan.p.rodeoRequestBatteryHintLastShownTime + REQUEST_RODEO_BATTERY_HINT_COOLDOWN ) //Use a different cooldown for the hint as opposed to the ability
+ {
+ //printt( "Current time: " + currentTime + ", lastShownTime: " + playerTitan.p.rodeoRequestBatteryHintLastShownTime + ", cooldown: " + REQUEST_RODEO_BATTERY_HINT_COOLDOWN )
+ return
+ }
+
+ int stringID = GetStringID( "#RODEO_REQUEST_BATTERY_HINT" )
+ MessageToPlayer( playerTitan, eEventNotifications.Rodeo_ShowBatteryHint, null, stringID )
+
+ playerTitan.p.rodeoRequestBatteryHintLastShownTime = currentTime
+}
+
+void function SetSoulBatteryCount( entity soul, int count )
+{
+ count = maxint( 0, count )
+
+ soul.SetTitanSoulNetInt( "rodeoBatteryCount", count )
+}
+
+void function PilotBattery_SetMaxCount( int batteryCount )
+{
+ file.maxPilotBatteryCount = batteryCount
+}
+
+bool function PlayerHasMaxBatteryCount( entity player )
+{
+ if ( !PlayerHasBattery( player ) )
+ {
+ Assert( GetPlayerBatteryCount( player ) == 0 )
+ return false
+ }
+
+ return GetPlayerBatteryCount( player ) == file.maxPilotBatteryCount
+}
+
+void function ThrowRiderOff( entity rider, entity titan, vector direction, bool adjustAirControl = true )
+{
+ if ( GetBugReproNum() == 112023 ) //Track down why eye angles of rider snaps violently when titan is looking downwards
+ {
+ thread AnglesDebug( rider )
+ }
+
+ rider.p.rodeoShouldAdjustJumpOffVelocity = false
+
+ rider.Signal( "RodeoOver" )
+ rider.ClearParent()
+
+ #if DEV
+ if ( GetDebugRodeoPrint() )
+ printt( "Throw Rider off: origin before vertical adjustment: " + rider.GetOrigin() )
+ #endif
+
+ rider.SetOrigin( rider.GetOrigin() + Vector( 0, 0, 100 ) )
+
+ #if DEV
+ if ( GetDebugRodeoPrint() )
+ printt( "Throw Rider off: origin after vertical adjustment: " + rider.GetOrigin() )
+ #endif
+
+ //printt( "Rider eye angles: " + rider.EyeAngles() + ", rider Angles: " + rider.GetAngles() + " titan eye Angles:" + titan.EyeAngles() )
+
+ // Set it higher in SP so bosses less exploitable
+ #if SP
+ direction += Vector( 0, 0, SP_RODEO_BOOST )
+ #endif
+
+ rider.SetVelocity( direction )
+ rider.JumpedOffRodeo()
+
+ int attachIndex = titan.LookupAttachment( "hijack" ) //TODO: Hardcoded, no way to get rodeopackage.attachpoint easily at this point anymore!
+ vector startPos = titan.GetAttachmentOrigin( attachIndex )
+
+ //printt( "startPos of attachment: " + startPos )
+
+ if ( !PlayerCanTeleportHere( rider, startPos, titan ) )
+ {
+ startPos = titan.GetOrigin()
+ if ( !PlayerCanTeleportHere( rider, startPos, titan ) )
+ startPos = rider.GetOrigin()
+ }
+
+ PutEntityInSafeSpot( rider, titan, null, startPos, rider.GetOrigin() )
+
+ #if DEV
+ if ( GetDebugRodeoPrint() )
+ printt( "Throw Rider off: origin after PutEntityInSafeSpot: " + rider.GetOrigin() )
+ #endif
+
+ if ( adjustAirControl )
+ thread PostRodeoAirControl( rider )
+}
+
+void function PostRodeoAirControl( entity player )
+{
+ player.Signal( "PostRodeoAirControl" )
+ player.EndSignal( "PostRodeoAirControl" )
+ player.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ RestorePlayerAirControl( player )
+ }
+ )
+
+ const float POST_RODEO_AIR_CONTROL_DURATION = 0.75
+ const float POST_RODEO_AIR_CONTROL_SCALE = 0.5
+ const float POST_RODEO_AIR_CONTROL_JUMP_DELAY = 0.45
+
+ // give the player time to be thrown in the proper direction before they get back double jump
+ RemovePlayerAirControl( player )
+ player.ConsumeDoubleJump()
+ wait POST_RODEO_AIR_CONTROL_JUMP_DELAY
+ player.TouchGround()
+
+ float startTime = Time()
+ while ( Time() - startTime < POST_RODEO_AIR_CONTROL_DURATION && !player.IsOnGround() && !player.IsWallRunning() && !player.IsWallHanging() )
+ {
+ float elapsedTime = Time() - startTime
+ player.kv.airSpeed = player.GetPlayerSettingsField( "airSpeed" ) * POST_RODEO_AIR_CONTROL_SCALE * (1 - (elapsedTime / POST_RODEO_AIR_CONTROL_DURATION))
+ player.kv.airAcceleration = player.GetPlayerSettingsField( "airAcceleration" ) * POST_RODEO_AIR_CONTROL_SCALE * (1 - (elapsedTime / POST_RODEO_AIR_CONTROL_DURATION))
+ //printt( "scale", POST_RODEO_AIR_CONTROL_SCALE * (1 - (elapsedTime / POST_RODEO_AIR_CONTROL_DURATION)) )
+
+ WaitFrame()
+ }
+}
+
+void function AnglesDebug( rider )
+{
+ printt( "Begin Angles Debug, Rider eye angles: " + rider.EyeAngles() + ", rider Angles: " + rider.GetAngles() )
+ while( !rider.IsOnGround() )
+ {
+ printt( "Rider eye angles: " + rider.EyeAngles() + ", rider Angles: " + rider.GetAngles() )
+ WaitFrame()
+ }
+
+ printt( "End Angles Debug, Rider eye angles: " + rider.EyeAngles() + ", rider Angles: " + rider.GetAngles() )
+}
+
+
+
+void function SetPlayerBatteryCount( entity player, int count )
+{
+ Assert( count <= file.maxPilotBatteryCount )
+ Assert( count >= 0 )
+ player.SetPlayerNetInt( "batteryCount", count )
+}
+
+int function GetPlayerBatteryCount( entity player )
+{
+ return player.GetPlayerNetInt( "batteryCount" )
+}
+
+void function DisableBTRodeo( entity soul )
+{
+ string settings = GetSoulPlayerSettings( soul )
+ var rodeoAllow = Dev_GetPlayerSettingByKeyField_Global( settings, "rodeo_allow" )
+
+ if ( rodeoAllow == null )
+ return
+
+ if ( rodeoAllow == 0 )
+ {
+ soul.SetIsValidRodeoTarget( false )
+ }
+}
+void function RemovePlayerAirControl( entity player ) //This function should really be in a server only SP & MP utility script file. No such file exists as of right now.
+{
+ Assert( player.IsPlayer() )
+ player.kv.airSpeed = 0
+ player.kv.airAcceleration = 0
+}
+
+void function RestorePlayerAirControl( entity player ) //This function should really be in a server only SP & MP utility script file. No such file exists as of right now.
+{
+ Assert( player.IsPlayer() )
+ player.kv.airSpeed = player.GetPlayerSettingsField( "airSpeed" )
+ player.kv.airAcceleration = player.GetPlayerSettingsField( "airAcceleration" )
+}
+
+bool function ShouldThrowGrenadeInHatch( entity rodeoPilot )
+{
+ bool batteryPullingDisabled = (GetCurrentPlaylistVarInt( "rodeo_battery_disable_pulls_from_titans", 0 ) == 1)
+ if ( batteryPullingDisabled )
+ return true
+
+ #if MP
+ if ( PlayerWantsToThrowNukeGrenade( rodeoPilot ) )
+ return false
+ #endif
+
+ if ( PlayerHasBattery( rodeoPilot ) )
+ return true
+
+ return false
+}
+
+
+#if DEV
+void function SetDebugRodeoPrint( bool value )
+{
+ file.debugRodeoPrint = value
+}
+
+bool function GetDebugRodeoPrint()
+{
+ return file.debugRodeoPrint
+}
+#endif
+
+#if MP
+void function SetApplyBatteryCallback( void functionref(entity,entity,entity) func )
+{
+ file.applyBatteryCallback = func
+}
+
+bool function PlayerWantsToThrowNukeGrenade( entity player )
+{
+ return ( player in file.playersThatWantToUseRodeoGrenade )
+}
+
+bool function HasSuperRodeoGrenade( entity player )
+{
+ // HACK: because we ran out of player global net ints for "numSuperRodeoGrenades" in bounty hunt
+ if ( GameRules_GetGameMode() != FD )
+ return false
+ return player.GetPlayerNetInt( "numSuperRodeoGrenades" ) > 0
+}
+
+void function DeductSuperRodeoGrenade( entity player, int amount )
+{
+ int num = player.GetPlayerNetInt( "numSuperRodeoGrenades" )
+ player.SetPlayerNetInt( "numSuperRodeoGrenades", num-amount )
+}
+
+void function RodeoForceNuke( entity pilot )
+{
+ entity titan = GetTitanBeingRodeoed( pilot )
+ if ( !IsValid( titan ) )
+ return
+
+ if ( !titan.IsNPC() || titan.GetTitanSoul().IsEjecting() )
+ return
+
+ table damageTable =
+ {
+ scriptType = damageTypes.rodeoBatteryRemoval
+ forceKill = false
+ damageSourceId = eDamageSourceId.core_overload
+ origin = titan.GetOrigin()
+ hitbox = 2
+ }
+ titan.TakeDamage( 1, pilot, pilot, damageTable )
+
+ if ( !IsAlive( titan ) || titan.GetTitanSoul().IsEjecting() )
+ return
+
+ DeductSuperRodeoGrenade( pilot, 1 )
+
+ // THROW RODEO RIDER OFF
+ entity soul = titan.GetTitanSoul()
+ soul.soul.nukeAttacker = pilot
+ NPC_SetNuclearPayload( titan )
+
+ vector ejectAngles = titan.GetAngles()
+ ejectAngles.x = 270
+ vector riderEjectAngles = AnglesCompose( ejectAngles, < 5, 0, 0 > )
+
+ float speed = RandomFloatRange( 1900, 2100 )
+ float gravityScale = expect float ( pilot.GetPlayerSettingsField( "gravityscale" ) )
+ vector riderVelocity = AnglesToForward( riderEjectAngles ) * (speed * gravityScale) * 0.95
+ ThrowRiderOff( pilot, titan, riderVelocity )
+
+ if ( titan.ContextAction_IsBusy() )
+ titan.ContextAction_ClearBusy()
+ thread TitanEjectPlayer( titan, true )
+}
+
+void function OpenRodeoNukeWindow( entity player, entity titan )
+{
+ player.EndSignal( "RodeoNukeWindowEnded" )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "RodeoOver" )
+ titan.EndSignal( "OnDeath" )
+
+ if ( player in file.playersThatWantToUseRodeoGrenade )
+ delete file.playersThatWantToUseRodeoGrenade[ player ]
+
+ if ( player.GetTeam() == titan.GetTeam() )
+ return
+
+ if ( !HasSuperRodeoGrenade( player ) )
+ return
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_NukeGrenadeWindowOpen" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_NukeGrenadeWindowClosed" )
+ }
+ )
+
+ player.WaitSignal( "TryNukeGrenade" )
+
+ if ( !HasSuperRodeoGrenade( player ) )
+ return
+
+ file.playersThatWantToUseRodeoGrenade[ player ] <- true
+
+ MessageToPlayer( player, eEventNotifications.FD_SuperRodeoUsed )
+ Rodeo_MoveBatteryDown( titan.GetTitanSoul() )
+}
+
+bool function ClientCommand_TryNukeGrenade( entity player, array<string> args )
+{
+ if ( HasSuperRodeoGrenade( player ) )
+ player.Signal( "TryNukeGrenade" )
+
+ return true
+}
+#endif \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/sh_calling_cards.gnut b/Northstar.CustomServers/scripts/vscripts/sh_calling_cards.gnut
new file mode 100644
index 000000000..674619457
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/sh_calling_cards.gnut
@@ -0,0 +1,424 @@
+global function ShCallingCards_Init
+
+global function PlayerCallingCard_GetActive
+global function PlayerCallingCard_GetActiveIndex
+
+global function CallingCard_GetRef
+global function CallingCard_GetImage
+global function CallingCards_GetCount
+global function CallingCard_GetByIndex
+global function CallingCard_GetByRef
+
+global function PlayerCallsignIcon_GetActive
+global function PlayerCallsignIcon_GetActiveIndex
+
+global function CallsignIcon_GetRef
+global function CallsignIcon_GetImage
+global function CallingCard_GetLayout
+global function CallsignIcon_GetSmallImage
+global function CallsignIcons_GetCount
+global function CallsignIcon_GetByIndex
+global function CallsignIcon_GetByRef
+
+global function PlayerCallingCard_RefOverride
+
+#if SERVER
+ global function PlayerCallsignIcon_SetActive
+ global function PlayerCallingCard_SetActiveByRef
+ global function PlayerCallsignIcon_SetActiveByRef
+#endif
+
+global struct CallingCard
+{
+ int index = -1
+ string ref = ""
+ asset image = $""
+ int layoutType = 0
+}
+
+global struct CallsignIcon
+{
+ int index = -1
+ string ref = ""
+ asset image = $""
+ asset smallImage = $""
+ int layoutType = 0
+}
+
+struct
+{
+ table<string, CallingCard> callingCards
+ array<string> callingCardRefs
+
+ table<string, CallsignIcon> callsignIcons
+ array<string> callsignIconRefs
+
+ int nextCallingCardIndex = 0
+ int nextCallsignIconIndex = 0
+} file
+
+void function ShCallingCards_Init()
+{
+ bool initialized = ( file.callingCardRefs.len() > 0 )
+
+ if ( !initialized )
+ {
+ var dataTable = GetDataTable( $"datatable/calling_cards.rpak" )
+ for ( int row = 0; row < GetDatatableRowCount( dataTable ); row++ )
+ {
+ string cardRef = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, CALLING_CARD_REF_COLUMN_NAME ) )
+ asset image = GetDataTableAsset( dataTable, row, GetDataTableColumnByName( dataTable, CALLING_CARD_IMAGE_COLUMN_NAME ) )
+ int layoutType = GetDataTableInt( dataTable, row, GetDataTableColumnByName( dataTable, CALLING_CARD_LAYOUT_COLUMN_NAME ) )
+
+ CallingCard callingCard
+ callingCard.ref = cardRef
+ callingCard.image = image
+ callingCard.index = row
+ callingCard.layoutType = layoutType
+
+ file.callingCards[cardRef] <- callingCard
+ file.callingCardRefs.append( cardRef )
+ }
+ }
+
+ if ( !initialized )
+ {
+ var dataTable = GetDataTable( $"datatable/callsign_icons.rpak" )
+ for ( int row = 0; row < GetDatatableRowCount( dataTable ); row++ )
+ {
+ string iconRef = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, CALLSIGN_ICON_REF_COLUMN_NAME ) )
+ asset image = GetDataTableAsset( dataTable, row, GetDataTableColumnByName( dataTable, CALLSIGN_ICON_IMAGE_COLUMN_NAME ) )
+ asset smallImage = GetDataTableAsset( dataTable, row, GetDataTableColumnByName( dataTable, CALLSIGN_ICON_SMALL_IMAGE_COLUMN_NAME ) )
+
+ CallsignIcon callsignIcon
+ callsignIcon.ref = iconRef
+ callsignIcon.image = image
+ callsignIcon.smallImage = smallImage
+ callsignIcon.index = row
+
+ file.callsignIcons[iconRef] <- callsignIcon
+ file.callsignIconRefs.append( iconRef )
+ }
+ }
+
+ #if SERVER
+ AddCallback_OnClientConnecting( OnClientConnecting )
+ AddCallback_OnTitanBecomesPilot( OnClassChangeBecomePilot )
+ AddCallback_OnPilotBecomesTitan( OnClassChangeBecomeTitan )
+ #endif
+}
+
+#if SERVER
+void function OnClientConnecting( entity player )
+{
+ // hack - don't do this because pdefs aren't fully working
+
+ // initialize the persistent network vars
+ // string ref = CallingCard_GetRef( PlayerCallingCard_GetActive( player ) )
+ // PlayerCallingCard_SetActiveByRef( player, ref )
+ //
+ // CallsignIcon callsignIcon = PlayerCallsignIcon_GetActive( player )
+ //
+ // PlayerCallsignIcon_SetActive( player, callsignIcon )
+ // player.SetTargetInfoIcon( callsignIcon.smallImage )
+}
+#endif
+
+#if DEV
+CallingCard function DEV_GetNextCallingCard()
+{
+ int index = file.nextCallingCardIndex
+ printt( "using CallingCard index", index )
+ file.nextCallingCardIndex++
+ file.nextCallingCardIndex = file.nextCallingCardIndex % file.callingCardRefs.len()
+
+ string ref = file.callingCardRefs[index]
+ return file.callingCards[ref]
+}
+
+CallsignIcon function DEV_GetNextCallsignIcon()
+{
+ int index = file.nextCallsignIconIndex
+ printt( "using CallsignIcon index", index )
+ file.nextCallsignIconIndex++
+ file.nextCallsignIconIndex = file.nextCallsignIconIndex % file.callsignIconRefs.len()
+
+ string ref = file.callsignIconRefs[index]
+ return file.callsignIcons[ref]
+}
+#endif
+
+int function PlayerCallingCard_GetActiveIndex( entity player )
+{
+ #if CLIENT
+ int index
+ if ( player != GetLocalClientPlayer() )
+ index = player.GetPlayerNetInt( "activeCallingCardIndex" )
+ else
+ index = player.GetPersistentVarAsInt( "activeCallingCardIndex" )
+ #else
+ int index = player.GetPersistentVarAsInt( "activeCallingCardIndex" )
+ #endif
+ return index
+}
+
+CallingCard function PlayerCallingCard_GetActive( entity player )
+{
+ int index = PlayerCallingCard_GetActiveIndex( player )
+ string ref = file.callingCardRefs[index]
+ #if CLIENT || UI
+ ref = PlayerCallingCard_RefOverride( player, ref )
+ #endif
+ return file.callingCards[ref]
+}
+
+string function CallingCard_GetRef( CallingCard callingCard )
+{
+ return callingCard.ref
+}
+
+asset function CallingCard_GetImage( CallingCard callingCard )
+{
+ return callingCard.image
+}
+
+int function CallingCard_GetLayout( CallingCard callingCard )
+{
+ return callingCard.layoutType
+}
+
+int function CallingCards_GetCount()
+{
+ return file.callingCards.len()
+}
+
+CallingCard function CallingCard_GetByIndex( int index )
+{
+ // JFS: handle players with invalid indices
+ //Assert( index < CallingCards_GetCount() )
+ if ( index >= file.callingCards.len() )
+ return file.callingCards["callsign_16_col"]
+
+ return file.callingCards[file.callingCardRefs[index]]
+}
+
+CallingCard function CallingCard_GetByRef( string ref )
+{
+ return file.callingCards[ref]
+}
+
+
+int function PlayerCallsignIcon_GetActiveIndex( entity player )
+{
+ #if CLIENT
+ int index
+ if ( player != GetLocalClientPlayer() )
+ index = player.GetPlayerNetInt( "activeCallsignIconIndex" )
+ else
+ index = player.GetPersistentVarAsInt( "activeCallsignIconIndex" )
+ #else
+ int index = player.GetPersistentVarAsInt( "activeCallsignIconIndex" )
+ #endif
+ return index
+}
+
+CallsignIcon function PlayerCallsignIcon_GetActive( entity player )
+{
+ int index = PlayerCallsignIcon_GetActiveIndex( player )
+ string ref = file.callsignIconRefs[index]
+ return file.callsignIcons[ref]
+}
+
+string function CallsignIcon_GetRef( CallsignIcon callsignIcon )
+{
+ return callsignIcon.ref
+}
+
+asset function CallsignIcon_GetImage( CallsignIcon callsignIcon )
+{
+ return callsignIcon.image
+}
+
+asset function CallsignIcon_GetSmallImage( CallsignIcon callsignIcon )
+{
+ return callsignIcon.smallImage
+}
+
+int function CallsignIcons_GetCount()
+{
+ return file.callsignIcons.len()
+}
+
+CallsignIcon function CallsignIcon_GetByIndex( int index )
+{
+ // JFS: handle players with invalid indices
+ // Assert( index < CallsignIcons_GetCount() )
+
+ if ( index >= file.callsignIconRefs.len() )
+ index = 0
+
+ return file.callsignIcons[file.callsignIconRefs[index]]
+}
+
+CallsignIcon function CallsignIcon_GetByRef( string ref )
+{
+ return file.callsignIcons[ref]
+}
+
+
+const table< string, string > dynamicCardRefMap = {
+ callsign_fd_ion_dynamic = "ion",
+ callsign_fd_tone_dynamic = "tone",
+ callsign_fd_scorch_dynamic = "scorch",
+ callsign_fd_legion_dynamic = "legion",
+ callsign_fd_northstar_dynamic = "northstar",
+ callsign_fd_ronin_dynamic = "ronin",
+ callsign_fd_monarch_dynamic = "vanguard",
+}
+
+const table< string, array<string> > dynamicCardMap = {
+ callsign_fd_ion_dynamic =
+ [
+ "callsign_fd_ion_dynamic",
+ "callsign_fd_ion_dynamic",
+ "callsign_fd_ion_hard",
+ "callsign_fd_ion_master",
+ "callsign_fd_ion_insane",
+ ],
+
+ callsign_fd_tone_dynamic =
+ [
+ "callsign_fd_tone_dynamic",
+ "callsign_fd_tone_dynamic",
+ "callsign_fd_tone_hard",
+ "callsign_fd_tone_master",
+ "callsign_fd_tone_insane",
+ ],
+
+ callsign_fd_scorch_dynamic =
+ [
+ "callsign_fd_scorch_dynamic",
+ "callsign_fd_scorch_dynamic",
+ "callsign_fd_scorch_hard",
+ "callsign_fd_scorch_master",
+ "callsign_fd_scorch_insane",
+ ],
+
+ callsign_fd_legion_dynamic =
+ [
+ "callsign_fd_legion_dynamic",
+ "callsign_fd_legion_dynamic",
+ "callsign_fd_legion_hard",
+ "callsign_fd_legion_master",
+ "callsign_fd_legion_insane",
+ ],
+
+ callsign_fd_northstar_dynamic =
+ [
+ "callsign_fd_northstar_dynamic",
+ "callsign_fd_northstar_dynamic",
+ "callsign_fd_northstar_hard",
+ "callsign_fd_northstar_master",
+ "callsign_fd_northstar_insane",
+ ],
+
+ callsign_fd_ronin_dynamic =
+ [
+ "callsign_fd_ronin_dynamic",
+ "callsign_fd_ronin_dynamic",
+ "callsign_fd_ronin_hard",
+ "callsign_fd_ronin_master",
+ "callsign_fd_ronin_insane",
+ ],
+
+ callsign_fd_monarch_dynamic =
+ [
+ "callsign_fd_monarch_dynamic",
+ "callsign_fd_monarch_dynamic",
+ "callsign_fd_monarch_hard",
+ "callsign_fd_monarch_master",
+ "callsign_fd_monarch_insane",
+ ],
+}
+
+string function PlayerCallingCard_RefOverride( entity player, string ref )
+{
+ const string CARD_DYNAMIC = "_dynamic"
+
+ if ( ref.find( CARD_DYNAMIC ) == null )
+ return ref
+
+ if ( ref.find( CARD_DYNAMIC ) != ref.len() - CARD_DYNAMIC.len() )
+ return ref
+
+ if ( ref in dynamicCardRefMap )
+ {
+ string titanRef = dynamicCardRefMap[ref]
+ int highestDifficulty = FD_GetHighestDifficultyForTitan( player, titanRef )
+
+ return dynamicCardMap[ref][minint( highestDifficulty, dynamicCardMap[ref].len() - 1 )]
+ }
+
+ return ref
+}
+
+#if SERVER
+/*
+InitUnlockAsEntitlement( "callsign_fd_ion_dynamic", "", ET_DLC7_ION_WARPAINT )
+InitUnlockAsEntitlement( "callsign_fd_tone_dynamic", "", ET_DLC7_TONE_WARPAINT )
+InitUnlockAsEntitlement( "callsign_fd_scorch_dynamic", "", ET_DLC7_SCORCH_WARPAINT )
+InitUnlockAsEntitlement( "callsign_fd_legion_dynamic", "", ET_DLC7_LEGION_WARPAINT )
+InitUnlockAsEntitlement( "callsign_fd_northstar_dynamic", "", ET_DLC7_NORTHSTAR_WARPAINT )
+InitUnlockAsEntitlement( "callsign_fd_ronin_dynamic", "", ET_DLC7_RONIN_WARPAINT )
+InitUnlockAsEntitlement( "callsign_fd_monarch_dynamic", "", ET_DLC7_MONARCH_WARPAINT )
+*/
+
+void function PlayerCallingCard_SetActiveByIndex( entity player, int index )
+{
+// if ( player.GetPersistentVarAsInt( "activeCallingCardIndex" ) != index )
+ player.SetCallingCard( index )
+
+ player.SetPlayerNetInt( "activeCallingCardIndex", index )
+ player.SetPersistentVar( "activeCallingCardIndex", index )
+}
+
+void function PlayerCallingCard_SetActiveByRef( entity player, string ref )
+{
+ PlayerCallingCard_SetActiveByIndex( player, file.callingCards[ref].index )
+
+ if ( PlayerCallingCard_RefOverride( player, ref ) != ref )
+ player.SetCallingCard( file.callingCards[PlayerCallingCard_RefOverride( player, ref )].index )
+}
+
+void function PlayerCallsignIcon_SetActiveByIndex( entity player, int index )
+{
+// if ( player.GetPersistentVarAsInt( "activeCallsignIconIndex" ) != index )
+ player.SetCallSign( index )
+
+ player.SetPlayerNetInt( "activeCallsignIconIndex", index )
+ player.SetPersistentVar( "activeCallsignIconIndex", index )
+}
+
+void function PlayerCallsignIcon_SetActive( entity player, CallsignIcon callsignIcon )
+{
+ PlayerCallsignIcon_SetActiveByIndex( player, callsignIcon.index )
+}
+
+void function PlayerCallsignIcon_SetActiveByRef( entity player, string ref )
+{
+ PlayerCallsignIcon_SetActiveByIndex( player, file.callsignIcons[ref].index )
+}
+
+void function OnClassChangeBecomePilot( entity player, entity titan )
+{
+ CallsignIcon callsignIcon = PlayerCallsignIcon_GetActive( player )
+ player.SetTargetInfoIcon( callsignIcon.smallImage )
+}
+
+void function OnClassChangeBecomeTitan( entity player, entity titan )
+{
+ string titanRef = GetTitanCharacterNameFromSetFile( player.GetPlayerSettings() )
+ player.SetTargetInfoIcon( GetTitanCoreIcon( titanRef ) )
+}
+
+#endif \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/sh_loadouts_mp.nut b/Northstar.CustomServers/scripts/vscripts/sh_loadouts_mp.nut
new file mode 100644
index 000000000..3b1c8a8ab
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/sh_loadouts_mp.nut
@@ -0,0 +1,19 @@
+// despite the name this is NOT a shared script, it only runs on the server
+// todo: move all contents of this script to one called _loadouts_mp.nut, naming here is confusing af
+
+// current contents of this script are just random placeholder funcs i wasn't sure about the proper location of
+
+global function GetNPCDefaultWeaponForLevel
+global function GetTitanLoadoutForCurrentMap
+
+TitanLoadoutDef function GetTitanLoadoutForCurrentMap()
+{
+ TitanLoadoutDef loadout
+ return loadout
+}
+
+NPCDefaultWeapon ornull function GetNPCDefaultWeaponForLevel( entity npc )
+{
+ return null
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/sh_remote_functions_mp_custom.gnut b/Northstar.CustomServers/scripts/vscripts/sh_remote_functions_mp_custom.gnut
new file mode 100644
index 000000000..c1e49e765
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/sh_remote_functions_mp_custom.gnut
@@ -0,0 +1,20 @@
+untyped
+global function InitCustomNetworkVars
+global function AddCallback_OnRegisteringCustomNetworkVars
+
+struct {
+ array<void functionref()> onRegisteringCustomNetworkVarsCallbacks
+} file
+
+void function InitCustomNetworkVars()
+{
+ print( "InitCustomNetworkVars" )
+
+ foreach ( void functionref() callback in file.onRegisteringCustomNetworkVarsCallbacks )
+ callback()
+}
+
+void function AddCallback_OnRegisteringCustomNetworkVars( void functionref() callback )
+{
+ file.onRegisteringCustomNetworkVarsCallbacks.append( callback )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/sh_stats.gnut b/Northstar.CustomServers/scripts/vscripts/sh_stats.gnut
new file mode 100644
index 000000000..31634a9b2
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/sh_stats.gnut
@@ -0,0 +1,526 @@
+
+global function InitStatsTables
+global function IsValidStat
+global function GetPlayerStatInt
+global function GetPlayerStatFloat
+global function GetPlayerStat_AllCompetitiveModesAndMapsInt
+global function GetStatVar
+global function GetStatVarType
+global function GetStatVarLocalizedUnlock
+global function Stats_GetFixedSaveVar
+global function FD_GetHighestDifficultyForTitan
+
+/*void function AddItemsToStatsList( array<string> refs )
+{
+ foreach ( ref in refs )
+ shGlobalMP.statsItemsList.append( ref )
+}*/
+
+void function InitStatsTables()
+{
+ int persistenceItemsCount = PersistenceGetEnumCount( "loadoutWeaponsAndAbilities" )
+ for ( int i = 0; i < persistenceItemsCount; i++ )
+ {
+ string enumName = PersistenceGetEnumItemNameForIndex( "loadoutWeaponsAndAbilities", i )
+ if ( enumName != "" )
+ shGlobalMP.statsItemsList.append( enumName )
+ }
+
+ //##############################################
+ // GAMES STATS
+ //##############################################
+
+ AddPersistentStatCategory( "game_stats" )
+
+ AddPersistentStat( "game_stats", "game_joined", "", "mapStats[%mapname%].gamesJoined[%gamemode%]", "#" )
+ AddPersistentStat( "game_stats", "game_completed", "", "mapStats[%mapname%].gamesCompleted[%gamemode%]", "#" )
+ AddPersistentStat( "game_stats", "game_won", "", "mapStats[%mapname%].gamesWon[%gamemode%]", "#" )
+ AddPersistentStat( "game_stats", "game_lost", "", "mapStats[%mapname%].gamesLost[%gamemode%]", "#" )
+ AddPersistentStat( "game_stats", "mvp", "", "mapStats[%mapname%].topPlayerOnTeam[%gamemode%]", "#" )
+ AddPersistentStat( "game_stats", "top3OnTeam", "", "mapStats[%mapname%].top3OnTeam[%gamemode%]", "#" )
+ AddPersistentStat( "game_stats", "hoursPlayed", "", "mapStats[%mapname%].hoursPlayed[%gamemode%]", "#" )
+ AddPersistentStat( "game_stats", "perfectMatches", "", "mapStats[%mapname%].perfectMatchesByDifficulty[%difficulty%]", "#" )
+ AddPersistentStat( "game_stats", "games_completed_fd", "", "mapStats[%mapname%].matchesByDifficulty[%difficulty%]", "#" )
+ AddPersistentStat( "game_stats", "games_won_fd", "", "mapStats[%mapname%].winsByDifficulty[%difficulty%]", "#" )
+
+ int gameModeCount = PersistenceGetEnumCount( "gameModes" )
+ for ( int modeIndex = 0; modeIndex < gameModeCount; modeIndex++ )
+ {
+ string gameModeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeIndex )
+
+ AddPersistentStat( "game_stats", "mode_played", gameModeName, "gameStats.modesPlayed[" + gameModeName + "]", "#UNLOCK_MODE_PLAYED" )
+ AddPersistentStat( "game_stats", "mode_won", gameModeName, "gameStats.modesWon[" + gameModeName + "]", "#UNLOCK_MODE_WON" )
+
+ AddPersistentStat( "game_stats", "pvp_kills_by_mode", gameModeName, "gameStats.pvpKills[" + gameModeName + "]", "#UNLOCK_MODE_PILOT_KILLS" )
+ AddPersistentStat( "game_stats", "times_kd_2_to_1_by_mode", gameModeName, "gameStats.timesKillDeathRatio2to1[" + gameModeName + "]", "#UNLOCK_MODE_KD_2_1" )
+ AddPersistentStat( "game_stats", "times_kd_2_to_1_pvp_by_mode", gameModeName, "gameStats.timesKillDeathRatio2to1_pvp[" + gameModeName + "]", "#UNLOCK_MODE_PILOT_KD_2_1" )
+ }
+
+ AddPersistentStat( "game_stats", "mvp_total", "", "gameStats.mvp_total", "#UNLOCK_GAME_MVP" )
+ AddPersistentStat( "game_stats", "game_completed_total", "", "gameStats.gamesCompletedTotal", "#UNLOCK_GAME_COMPLETED" )
+ AddPersistentStat( "game_stats", "game_won_total", "", "gameStats.gamesWonTotal", "#UNLOCK_GAME_WON" )
+
+ //##############################################
+ // TIME STATS
+ //##############################################
+
+ AddPersistentStatCategory( "time_stats" )
+
+ AddPersistentStatFloat( "time_stats", "hours_total", "", "timeStats.total", "#UNLOCK_TIME_HOURS" )
+ AddPersistentStatFloat( "time_stats", "hours_as_pilot", "", "timeStats.asPilot", "#UNLOCK_TIME_HOURS_PILOT" )
+ AddPersistentStatFloat( "time_stats", "hours_wallrunning", "", "timeStats.wallrunning", "#UNLOCK_TIME_HOURS_WALLRUN" )
+ AddPersistentStatFloat( "time_stats", "hours_inAir", "", "timeStats.inAir", "#UNLOCK_TIME_HOURS_AIR" )
+ AddPersistentStatFloat( "time_stats", "hours_as_titan", "", "timeStats.asTitanTotal", "#UNLOCK_TIME_HOURS_TITAN" )
+
+ AddPersistentStatFloat( "time_stats", "hours_dead", "", "timeStats.dead", "#" )
+ AddPersistentStatFloat( "time_stats", "hours_wallhanging", "", "timeStats.wallhanging", "#" )
+
+ // hours_as_titan_stryder
+ // hours_as_titan_atlas
+ // hours_as_titan_ogre
+ foreach ( titan, alias in GetAsTitanTypes() )
+ {
+ AddPersistentStatFloat( "time_stats", "hours_as_titan_" + alias, "", "timeStats.asTitan[" + alias + "]", "#UNLOCK_TIME_HOURS_TITAN_SPECIFIC" )
+ }
+
+ //##############################################
+ // DISTANCE STATS
+ //##############################################
+
+ AddPersistentStatCategory( "distance_stats" )
+
+ AddPersistentStatFloat( "distance_stats", "total", "", "distanceStats.total", "#UNLOCK_DISTANCE_KM" )
+ AddPersistentStatFloat( "distance_stats", "asPilot", "", "distanceStats.asPilot", "#UNLOCK_DISTANCE_KM_PILOT" )
+ AddPersistentStatFloat( "distance_stats", "wallrunning", "", "distanceStats.wallrunning", "#UNLOCK_DISTANCE_KM_WALLRUN" )
+ AddPersistentStatFloat( "distance_stats", "inAir", "", "distanceStats.inAir", "#UNLOCK_DISTANCE_KM_AIR" )
+ AddPersistentStatFloat( "distance_stats", "asTitan", "", "distanceStats.asTitanTotal", "#UNLOCK_TIME_HOURS_TITAN" )
+
+ AddPersistentStatFloat( "distance_stats", "ziplining", "", "distanceStats.ziplining", "#" )
+ AddPersistentStatFloat( "distance_stats", "onFriendlyTitan", "", "distanceStats.onFriendlyTitan", "#" )
+ AddPersistentStatFloat( "distance_stats", "onEnemyTitan", "", "distanceStats.onEnemyTitan", "#" )
+
+ foreach ( titan, alias in GetAsTitanTypes() )
+ {
+ AddPersistentStatFloat( "distance_stats", titan, "", "distanceStats.asTitan[" + alias + "]", "#UNLOCK_DISTANCE_KM_TITAN_SPECIFIC" )
+ }
+
+ //##############################################
+ // WEAPON STATS
+ //##############################################
+
+ AddPersistentStatCategory( "weapon_stats" )
+
+ foreach ( string ref in shGlobalMP.statsItemsList )
+ {
+ AddPersistentStat( "weapon_stats", "shotsHit", ref, "weaponStats[" + ref + "].shotsHit", "#UNLOCK_WEAPON_SHOTS_HIT" )
+ AddPersistentStat( "weapon_stats", "headshots", ref, "weaponStats[" + ref + "].headshots", "#UNLOCK_WEAPON_HEADSHOTS" )
+ AddPersistentStat( "weapon_stats", "critHits", ref, "weaponStats[" + ref + "].critHits", "#UNLOCK_WEAPON_SHOTS_CRIT" )
+ AddPersistentStatFloat( "weapon_stats", "hoursUsed", ref, "weaponStats[" + ref + "].hoursUsed", "#UNLOCK_WEAPON_HOURS_USED" )
+ AddPersistentStatFloat( "weapon_stats", "hoursEquipped", ref, "weaponStats[" + ref + "].hoursEquipped", "#UNLOCK_WEAPON_HOURS_EQUIPPED" )
+
+ AddPersistentStat( "weapon_stats", "shotsFired", ref, "weaponStats[" + ref + "].shotsFired", "#" )
+ AddPersistentStat( "weapon_stats", "titanDamage", ref, "weaponStats[" + ref + "].titanDamage", "#UNLOCK_WEAPON_TITAN_DAMAGE" )
+ }
+
+ //##############################################
+ // KILLS STATS FOR WEAPON
+ //##############################################
+
+ AddPersistentStatCategory( "weapon_kill_stats" )
+
+ foreach ( string ref in shGlobalMP.statsItemsList )
+ {
+ AddPersistentStat( "weapon_kill_stats", "total", ref, "weaponKillStats[" + ref + "].total", "#UNLOCK_WEAPON_KILLS" )
+ AddPersistentStat( "weapon_kill_stats", "pilots", ref, "weaponKillStats[" + ref + "].pilots", "#UNLOCK_WEAPON_PILOT_KILLS" )
+ AddPersistentStat( "weapon_kill_stats", "ejecting_pilots", ref, "weaponKillStats[" + ref + "].ejecting_pilots", "#UNLOCK_WEAPON_GOOSER_KILLS" )
+ AddPersistentStat( "weapon_kill_stats", "titansTotal", ref, "weaponKillStats[" + ref + "].titansTotal", "#UNLOCK_WEAPON_TITAN_KILLS" )
+ AddPersistentStat( "weapon_kill_stats", "assistsTotal", ref, "weaponKillStats[" + ref + "].assistsTotal", "#UNLOCK_WEAPON_ASSISTS" )
+ AddPersistentStat( "weapon_kill_stats", "killingSprees", ref, "weaponKillStats[" + ref + "].killingSprees", "#UNLOCK_WEAPON_KILLING_SPREES" )
+
+ AddPersistentStat( "weapon_kill_stats", "spectres", ref, "weaponKillStats[" + ref + "].spectres", "#" )
+ AddPersistentStat( "weapon_kill_stats", "marvins", ref, "weaponKillStats[" + ref + "].marvins", "#" )
+ AddPersistentStat( "weapon_kill_stats", "grunts", ref, "weaponKillStats[" + ref + "].grunts", "#" )
+ AddPersistentStat( "weapon_kill_stats", "ai", ref, "weaponKillStats[" + ref + "].ai", "#" )
+
+ foreach ( titan, alias in GetPluralTitanTypes() )
+ {
+ AddPersistentStat( "weapon_kill_stats", titan, ref, "weaponKillStats[" + ref + "].titans[" + alias + "]", "#" )
+ }
+
+ // fix this so it doesn't need explicit list of titans
+ AddPersistentStat( "weapon_kill_stats", "npcTitans_ion", ref, "weaponKillStats[" + ref + "].npcTitans[ion]", "#" )
+ AddPersistentStat( "weapon_kill_stats", "npcTitans_scorch", ref, "weaponKillStats[" + ref + "].npcTitans[scorch]", "#" )
+ AddPersistentStat( "weapon_kill_stats", "npcTitans_northstar", ref, "weaponKillStats[" + ref + "].npcTitans[northstar]", "#" )
+ AddPersistentStat( "weapon_kill_stats", "npcTitans_ronin", ref, "weaponKillStats[" + ref + "].npcTitans[ronin]", "#" )
+ AddPersistentStat( "weapon_kill_stats", "npcTitans_tone", ref, "weaponKillStats[" + ref + "].npcTitans[tone]", "#" )
+ AddPersistentStat( "weapon_kill_stats", "npcTitans_legion", ref, "weaponKillStats[" + ref + "].npcTitans[legion]", "#" )
+ AddPersistentStat( "weapon_kill_stats", "npcTitans_vanguard", ref, "weaponKillStats[" + ref + "].npcTitans[vanguard]", "#" )
+ }
+
+ //##############################################
+ // GENERAL KILLS STATS
+ //##############################################
+
+ AddPersistentStatCategory( "kills_stats" )
+
+ AddPersistentStat( "kills_stats", "total", "", "killStats.total", "#UNLOCK_KILLS_TOTAL" )
+ AddPersistentStat( "kills_stats", "totalWhileUsingBurnCard", "", "killStats.totalWhileUsingBurnCard", "#" )
+ AddPersistentStat( "kills_stats", "titansWhileTitanBCActive", "", "killStats.titansWhileTitanBCActive", "#" )
+ AddPersistentStat( "kills_stats", "totalPVP", "", "killStats.totalPVP", "#" )
+ AddPersistentStat( "kills_stats", "pilots", "", "killStats.pilots", "#UNLOCK_KILLS_PILOT" )
+ AddPersistentStat( "kills_stats", "spectres", "", "killStats.spectres", "#" )
+ AddPersistentStat( "kills_stats", "marvins", "", "killStats.marvins", "#" )
+ AddPersistentStat( "kills_stats", "grunts", "", "killStats.grunts", "#" )
+ AddPersistentStat( "kills_stats", "totalTitans", "", "killStats.totalTitans", "#UNLOCK_KILLS_TITAN" )
+ AddPersistentStat( "kills_stats", "totalPilots", "", "killStats.totalPilots", "#" )
+ AddPersistentStat( "kills_stats", "totalNPC", "", "killStats.totalNPC", "#" )
+ AddPersistentStat( "kills_stats", "totalTitansWhileDoomed", "", "killStats.totalTitansWhileDoomed", "#UNLOCK_KILLS_TITAN_WHILE_DOOMED" )
+ AddPersistentStat( "kills_stats", "asPilot", "", "killStats.asPilot", "#" )
+ AddPersistentStat( "kills_stats", "totalAssists", "", "killStats.totalAssists", "#UNLOCK_KILLS_ASSISTS" )
+
+ foreach ( titan, alias in GetAsTitanTypes() )
+ {
+ AddPersistentStat( "kills_stats", titan, "", "killStats.asTitan[" + alias + "]", "#UNLOCK_KILLS_ASTITAN" )
+ }
+
+ AddPersistentStat( "kills_stats", "killingSpressAs_ion", "", "killStats.killingSprees[ion]", "#UNLOCK_KILLS_SPREES_ASTITAN" )
+ AddPersistentStat( "kills_stats", "killingSpressAs_scorch", "", "killStats.killingSprees[scorch]", "#UNLOCK_KILLS_SPREES_ASTITAN" )
+ AddPersistentStat( "kills_stats", "killingSpressAs_northstar", "", "killStats.killingSprees[northstar]", "#UNLOCK_KILLS_SPREES_ASTITAN" )
+ AddPersistentStat( "kills_stats", "killingSpressAs_ronin", "", "killStats.killingSprees[ronin]", "#UNLOCK_KILLS_SPREES_ASTITAN" )
+ AddPersistentStat( "kills_stats", "killingSpressAs_tone", "", "killStats.killingSprees[tone]", "#UNLOCK_KILLS_SPREES_ASTITAN" )
+ AddPersistentStat( "kills_stats", "killingSpressAs_legion", "", "killStats.killingSprees[legion]", "#UNLOCK_KILLS_SPREES_ASTITAN" )
+ AddPersistentStat( "kills_stats", "killingSpressAs_vanguard", "", "killStats.killingSprees[vanguard]", "#UNLOCK_KILLS_SPREES_ASTITAN" )
+
+ AddPersistentStat( "kills_stats", "firstStrikes", "", "killStats.firstStrikes", "#UNLOCK_KILLS_FIRST_STRIKE" )
+ AddPersistentStat( "kills_stats", "ejectingPilots", "", "killStats.ejectingPilots", "#UNLOCK_KILLS_GOOSER" )
+ AddPersistentStat( "kills_stats", "whileEjecting", "", "killStats.whileEjecting", "#" )
+ AddPersistentStat( "kills_stats", "cloakedPilots", "", "killStats.cloakedPilots", "#" )
+ AddPersistentStat( "kills_stats", "whileCloaked", "", "killStats.whileCloaked", "#" )
+ AddPersistentStat( "kills_stats", "wallrunningPilots", "", "killStats.wallrunningPilots", "#" )
+ AddPersistentStat( "kills_stats", "whileWallrunning", "", "killStats.whileWallrunning", "#" )
+ AddPersistentStat( "kills_stats", "wallhangingPilots", "", "killStats.wallhangingPilots", "#" )
+ AddPersistentStat( "kills_stats", "whileWallhanging", "", "killStats.whileWallhanging", "#" )
+
+ AddPersistentStat( "kills_stats", "pilotExecution", "", "killStats.pilotExecution", "#" )
+ AddPersistentStat( "kills_stats", "pilotExecutePilot", "", "killStats.pilotExecutePilot", "#UNLOCK_KILLS_PILOT_EXECUTION" )
+ AddPersistentStat( "kills_stats", "pilotExecutePilotWhileCloaked", "", "killStats.pilotExecutePilotWhileCloaked", "#UNLOCK_KILLS_PILOT_EXECUTION_WHILE_CLOAKED" )
+ AddPersistentStat( "kills_stats", "pilotKillsWithHoloPilotActive", "", "killStats.pilotKillsWithHoloPilotActive", "#UNLOCK_KILLS_PILOT_KILLS_WHILE_HOLOPILOT_ACTIVE" )
+ AddPersistentStat( "kills_stats", "pilotKillsWithAmpedWallActive", "", "killStats.pilotKillsWithAmpedWallActive", "#UNLOCK_KILLS_PILOT_KILLS_WHILE_AMPEDWALL_ACTIVE" )
+
+ int pilotExecutionCount = PersistenceGetEnumCount( "pilotExecution" )
+ for ( int i = 0; i < pilotExecutionCount; i++ )
+ {
+ string executionRef = PersistenceGetEnumItemNameForIndex( "pilotExecution", i )
+ if ( executionRef != "" )
+ AddPersistentStat( "kills_stats", "pilotExecutePilotUsing_" + executionRef, "", "killStats.pilotExecutePilotByType[" + executionRef + "]", "#UNLOCK_KILLS_PILOT_EXECUTION_USING_TELEFRAG" ) // will need to modify string if other unlock refs are used
+ }
+
+ AddPersistentStat( "kills_stats", "pilotKickMelee", "", "killStats.pilotKickMelee", "#" )
+ AddPersistentStat( "kills_stats", "pilotKickMeleePilot", "", "killStats.pilotKickMeleePilot", "#" )
+ AddPersistentStat( "kills_stats", "titanMelee", "", "killStats.titanMelee", "#" )
+ AddPersistentStat( "kills_stats", "titanMeleePilot", "", "killStats.titanMeleePilot", "#" )
+ AddPersistentStat( "kills_stats", "titanStepCrush", "", "killStats.titanStepCrush", "#" )
+ AddPersistentStat( "kills_stats", "titanStepCrushPilot", "", "killStats.titanStepCrushPilot", "#" )
+
+ foreach ( titan, alias in GetCapitalizedTitanTypes() )
+ {
+ AddPersistentStat( "kills_stats", "titanExocution" + titan, "", "killStats.titanExocution" + titan, "#UNLOCK_KILLS_TITAN_EXECUTION" )
+ }
+
+ AddPersistentStat( "kills_stats", "titanFallKill", "", "killStats.titanFallKill", "#UNLOCK_KILLS_TITANFALL" )
+ AddPersistentStat( "kills_stats", "petTitanKillsFollowMode", "", "killStats.petTitanKillsFollowMode", "#" )
+ AddPersistentStat( "kills_stats", "petTitanKillsGuardMode", "", "killStats.petTitanKillsGuardMode", "#" )
+ AddPersistentStat( "kills_stats", "rodeo_total", "", "killStats.rodeo_total", "#UNLOCK_KILLS_RODEO" )
+ AddPersistentStat( "kills_stats", "pilot_headshots_total", "", "killStats.pilot_headshots_total", "#UNLOCK_KILLS_HEADSHOT" )
+ AddPersistentStat( "kills_stats", "evacShips", "", "killStats.evacShips", "#" )
+ AddPersistentStat( "kills_stats", "flyers", "", "killStats.flyers", "#" )
+ AddPersistentStat( "kills_stats", "nuclearCore", "", "killStats.nuclearCore", "#" )
+ AddPersistentStat( "kills_stats", "evacuatingEnemies", "", "killStats.evacuatingEnemies", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_NukeTitan_Kills", "", "killStats.coopChallenge_NukeTitan_Kills", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_MortarTitan_Kills", "", "killStats.coopChallenge_MortarTitan_Kills", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_EmpTitan_Kills", "", "killStats.coopChallenge_EmpTitan_Kills", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_SuicideSpectre_Kills", "", "killStats.coopChallenge_SuicideSpectre_Kills", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_Turret_Kills", "", "killStats.coopChallenge_Turret_Kills", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_CloakDrone_Kills", "", "killStats.coopChallenge_CloakDrone_Kills", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_BubbleShieldGrunt_Kills", "", "killStats.coopChallenge_BubbleShieldGrunt_Kills", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_Dropship_Kills", "", "killStats.coopChallenge_Dropship_Kills", "#" )
+ AddPersistentStat( "kills_stats", "coopChallenge_Sniper_Kills", "", "killStats.coopChallenge_Sniper_Kills", "#" )
+ AddPersistentStat( "kills_stats", "ampedVortexKills", "", "killStats.ampedVortexKills", "#" )
+ AddPersistentStat( "kills_stats", "meleeWhileCloaked", "", "killStats.meleeWhileCloaked", "#" )
+ AddPersistentStat( "kills_stats", "pilotKillsWhileUsingActiveRadarPulse", "", "killStats.pilotKillsWhileUsingActiveRadarPulse", "#" )
+ AddPersistentStat( "kills_stats", "titanKillsAsPilot", "", "killStats.titanKillsAsPilot", "#UNLOCK_KILLS_PVT" )
+ AddPersistentStat( "kills_stats", "pilotKillsWhileStimActive", "", "killStats.pilotKillsWhileStimActive", "#" )
+ AddPersistentStat( "kills_stats", "pilotKillsAsTitan", "", "killStats.pilotKillsAsTitan", "#UNLOCK_KILLS_TVP" )
+ AddPersistentStat( "kills_stats", "pilotKillsAsPilot", "", "killStats.pilotKillsAsPilot", "#" )
+ AddPersistentStat( "kills_stats", "titanKillsAsTitan", "", "killStats.titanKillsAsTitan", "#" )
+
+ #if SERVER
+ AddPersistentStat( "kills_stats", "pilotExecutePilotUsing_execution_telefrag", "", "killStats.pilotExecutePilotUsing_execution_telefrag", "#" )
+ #endif
+
+ //##############################################
+ // GENERAL DEATHS STATS
+ //##############################################
+
+ AddPersistentStatCategory( "deaths_stats" )
+
+ AddPersistentStat( "deaths_stats", "total", "", "deathStats.total" )
+ AddPersistentStat( "deaths_stats", "totalPVP", "", "deathStats.totalPVP" )
+ AddPersistentStat( "deaths_stats", "asPilot", "", "deathStats.asPilot" )
+
+ foreach ( titan, alias in GetAsTitanTypes() )
+ {
+ AddPersistentStat( "deaths_stats", titan, "", "deathStats.asTitan[" + alias + "]" )
+ }
+
+ AddPersistentStat( "deaths_stats", "byPilots", "", "deathStats.byPilots" )
+
+ foreach ( titan, alias in GetByTitanTypes() )
+ {
+ AddPersistentStat( "deaths_stats", titan, "", "deathStats.byTitans[" + alias + "]" )
+ }
+
+ AddPersistentStat( "deaths_stats", "bySpectres", "", "deathStats.bySpectres" )
+ AddPersistentStat( "deaths_stats", "byGrunts", "", "deathStats.byGrunts" )
+
+ foreach ( titan, alias in GetAsNPCTitanTypes() )
+ {
+ AddPersistentStat( "deaths_stats", titan, "", "deathStats.byNPCTitans[" + alias + "]" )
+ }
+ AddPersistentStat( "deaths_stats", "suicides", "", "deathStats.suicides" )
+ AddPersistentStat( "deaths_stats", "whileEjecting", "", "deathStats.whileEjecting" )
+
+
+ array<string> titanChassis = ["ion", "scorch", "northstar", "ronin", "tone", "legion", "vanguard"]
+
+ AddPersistentStatCategory( "titan_stats" )
+
+ foreach ( titan, chassis in GetCapitalizedTitanTypes() )
+ {
+ AddPersistentStat( "titan_stats", "pilots", chassis, "titanStats[" + chassis + "].pilots", "#UNLOCK_TITAN_PILOT_KILLS" )
+ AddPersistentStat( "titan_stats", "titansTotal", chassis, "titanStats[" + chassis + "].titansTotal", "#UNLOCK_TITAN_TITAN_KILLS" )
+ AddPersistentStat( "titan_stats", "titanDamage", chassis, "titanStats[" + chassis + "].titanDamage", "#UNLOCK_TITAN_TITAN_DAMAGE" )
+ AddPersistentStat( "titan_stats", "coresEarned", chassis, "titanStats[" + chassis + "].coresEarned" )
+ AddPersistentStat( "titan_stats", "pilotsAsPrime", chassis, "titanStats[" + chassis + "].pilotsAsPrime", "#UNLOCK_TITAN_PRIME_PILOT_KILLS" )
+ AddPersistentStat( "titan_stats", "titansAsPrime", chassis, "titanStats[" + chassis + "].titansAsPrime", "#UNLOCK_TITAN_PRIME_TITAN_KILLS" )
+ AddPersistentStat( "titan_stats", "executionsAsPrime", chassis, "titanStats[" + chassis + "].executionsAsPrime", "#UNLOCK_TITAN_PRIME_EXECUTIONS" )
+ AddPersistentStat( "titan_stats", "matchesByDifficulty", chassis, "titanStats[" + chassis + "].matchesByDifficulty[%difficulty%]", "" )
+ AddPersistentStat( "titan_stats", "perfectMatchesByDifficulty", chassis, "titanStats[" + chassis + "].perfectMatchesByDifficulty[%difficulty%]", "" )
+ }
+
+ //##############################################
+ // MISC STATS
+ //##############################################
+
+ AddPersistentStatCategory( "misc_stats" )
+
+ AddPersistentStat( "misc_stats", "titanFalls", "", "miscStats.titanFalls", "#UNLOCK_MISC_TITANFALLS" )
+ AddPersistentStat( "misc_stats", "titanFallsFirst", "", "miscStats.titanFallsFirst", "#UNLOCK_MISC_TITANFALLS_FIRST" )
+ AddPersistentStat( "misc_stats", "titanEmbarks", "", "miscStats.titanEmbarks", "#" )
+ AddPersistentStat( "misc_stats", "rodeos", "", "miscStats.rodeos", "#UNLOCK_MISC_RODEOS" )
+ AddPersistentStat( "misc_stats", "rodeosFromEject", "", "miscStats.rodeosFromEject", "#UNLOCK_MISC_RODOES_EJECT" )
+ AddPersistentStat( "misc_stats", "timesEjected", "", "miscStats.timesEjected", "#" )
+ AddPersistentStat( "misc_stats", "timesEjectedNuclear", "", "miscStats.timesEjectedNuclear", "#" )
+ AddPersistentStat( "misc_stats", "burnCardsEarned", "", "miscStats.burnCardsEarned", "#" )
+ AddPersistentStat( "misc_stats", "burnCardsSpent", "", "miscStats.burnCardsSpent", "#" )
+ AddPersistentStat( "misc_stats", "boostsActivated", "", "miscStats.boostsActivated", "#" )
+ AddPersistentStat( "misc_stats", "spectreLeeches", "", "miscStats.spectreLeeches", "#" )
+ AddPersistentStat( "misc_stats", "spectreLeechesByMap", "", "miscStats.spectreLeechesByMap[%mapname%]", "#" )
+ AddPersistentStat( "misc_stats", "evacsAttempted", "", "miscStats.evacsAttempted", "#" )
+ AddPersistentStat( "misc_stats", "evacsSurvived", "", "miscStats.evacsSurvived", "#UNLOCK_MISC_EVACS" )
+ AddPersistentStat( "misc_stats", "flagsCaptured", "", "miscStats.flagsCaptured", "#" )
+ AddPersistentStat( "misc_stats", "flagsReturned", "", "miscStats.flagsReturned", "#" )
+ AddPersistentStat( "misc_stats", "arcCannonMultiKills", "", "miscStats.arcCannonMultiKills", "#" )
+ AddPersistentStat( "misc_stats", "gruntsConscripted", "", "miscStats.gruntsConscripted", "#" )
+ AddPersistentStat( "misc_stats", "hardpointsCaptured", "", "miscStats.hardpointsCaptured", "#" )
+ AddPersistentStat( "misc_stats", "challengeTiersCompleted", "", "miscStats.challengeTiersCompleted", "#" )
+ AddPersistentStat( "misc_stats", "challengesCompleted", "", "miscStats.challengesCompleted", "#" )
+ AddPersistentStat( "misc_stats", "dailyChallengesCompleted", "", "miscStats.dailyChallengesCompleted", "#" )
+ AddPersistentStat( "misc_stats", "timesLastTitanRemaining", "", "miscStats.timesLastTitanRemaining", "#" )
+ AddPersistentStat( "misc_stats", "killingSprees", "", "miscStats.killingSprees", "#UNLOCK_MISC_KILLING_SPREES" )
+ AddPersistentStat( "misc_stats", "coopChallengesCompleted", "", "miscStats.coopChallengesCompleted", "#" )
+
+ //##############################################
+ // FD STATS
+ //##############################################
+
+ AddPersistentStatCategory( "fd_stats" )
+
+ AddPersistentStat( "fd_stats", "arcMinesPlaced", "", "fdStats.arcMinesPlaced", "#UNLOCK_MISC_ARC_MINE_PLACE" )
+ AddPersistentStat( "fd_stats", "turretsPlaced", "", "fdStats.turretsPlaced", "#UNLOCK_MISC_TURRET_PLACE" )
+ AddPersistentStat( "fd_stats", "rodeos", "", "fdStats.rodeos", "#UNLOCK_FD_RODEOS" )
+ AddPersistentStat( "fd_stats", "rodeoNukes", "", "fdStats.rodeoNukes", "#UNLOCK_MISC_RODEO_NUKES" )
+ AddPersistentStat( "fd_stats", "arcMineZaps", "", "fdStats.arcMineZaps", "#UNLOCK_MISC_ARC_MINE_ZAPS" )
+ AddPersistentStat( "fd_stats", "turretKills", "", "fdStats.turretKills", "#UNLOCK_MISC_TURRET_KILLS" )
+ AddPersistentStat( "fd_stats", "harvesterBoosts", "", "fdStats.harvesterBoosts", "#UNLOCK_MISC_HARVESTER_BOOSTS" )
+ AddPersistentStat( "fd_stats", "wavesComplete", "", "fdStats.wavesComplete", "#UNLOCK_MISC_WAVES_COMPLETE" )
+ AddPersistentStat( "fd_stats", "easyWins", "", "fdStats.easyWins", "#UNLOCK_FD_EASY_WINS" )
+ AddPersistentStat( "fd_stats", "normalWins", "", "fdStats.normalWins", "#UNLOCK_FD_NORMAL_WINS" )
+ AddPersistentStat( "fd_stats", "hardWins", "", "fdStats.hardWins", "#UNLOCK_FD_HARD_WINS" )
+ AddPersistentStat( "fd_stats", "masterWins", "", "fdStats.masterWins", "#UNLOCK_FD_MASTER_WINS" )
+ AddPersistentStat( "fd_stats", "insaneWins", "", "fdStats.insaneWins", "#UNLOCK_FD_INSANE_WINS" )
+ AddPersistentStat( "fd_stats", "highestTitanFDLevel", "", "fdStats.highestTitanFDLevel", "#UNLOCK_FD_TITAN_LEVEL" )
+
+ //#############################################################
+ // DEV ONLY STATS (NOT TRACKED IN RETAIL FOR PLAYER DISPLAY)
+ //#############################################################
+
+ AddPersistentStatCategory( "dev_stats" )
+
+ AddPersistentStat( "dev_stats", "rank_skill", "", DEV_STAT )
+ AddPersistentStat( "dev_stats", "raw_rank_skill", "", DEV_STAT )
+}
+
+void function AddPersistentStatCategory( string category )
+{
+ shGlobalMP.playerStatVars[ category ] <- {}
+}
+
+void function AddPersistentStat( string category, string alias, string subAlias, string variable, string localizedUnlock = "" )
+{
+ if ( !( alias in shGlobalMP.playerStatVars[ category ] ) )
+ shGlobalMP.playerStatVars[ category ][ alias ] <- {}
+ Assert( !( variable in shGlobalMP.playerStatVars[ category ][ alias ] ) )
+
+ PlayerStatData playerStatData
+ playerStatData.statVar = variable
+ playerStatData.statType = ePlayerStatType.INT
+ playerStatData.localizedUnlock = localizedUnlock
+ shGlobalMP.playerStatVars[ category ][ alias ][ subAlias ] <- playerStatData
+}
+
+void function AddPersistentStatInt( string category, string alias, string subAlias, string variable, string localizedUnlock = "" )
+{
+ if ( !( alias in shGlobalMP.playerStatVars[ category ] ) )
+ shGlobalMP.playerStatVars[ category ][ alias ] <- {}
+ Assert( !( variable in shGlobalMP.playerStatVars[ category ][ alias ] ) )
+
+ PlayerStatData playerStatData
+ playerStatData.statVar = variable
+ playerStatData.statType = ePlayerStatType.INT
+ playerStatData.localizedUnlock = localizedUnlock
+ shGlobalMP.playerStatVars[ category ][ alias ][ subAlias ] <- playerStatData
+}
+
+void function AddPersistentStatFloat( string category, string alias, string subAlias, string variable, string localizedUnlock = "" )
+{
+ if ( !( alias in shGlobalMP.playerStatVars[ category ] ) )
+ shGlobalMP.playerStatVars[ category ][ alias ] <- {}
+ Assert( !( variable in shGlobalMP.playerStatVars[ category ][ alias ] ) )
+
+ PlayerStatData playerStatData
+ playerStatData.statVar = variable
+ playerStatData.statType = ePlayerStatType.FLOAT
+ playerStatData.localizedUnlock = localizedUnlock
+ shGlobalMP.playerStatVars[ category ][ alias ][ subAlias ] <- playerStatData
+}
+
+bool function IsValidStat( string category, string alias, string subAlias )
+{
+ if ( category == "" || alias == "" )
+ return false
+
+ if ( !( category in shGlobalMP.playerStatVars ) )
+ return false
+
+ if ( !( alias in shGlobalMP.playerStatVars[ category ] ) )
+ return false
+
+ return ( subAlias in shGlobalMP.playerStatVars[ category ][ alias ] )
+}
+
+string function GetStatVar( string category, string alias, string subAlias = "" )
+{
+ Assert( category in shGlobalMP.playerStatVars, "Invalid stat category " + category )
+ Assert( alias in shGlobalMP.playerStatVars[ category ], "No stat alias " + alias + " in category " + category )
+
+
+ Assert( subAlias in shGlobalMP.playerStatVars[ category ][ alias ] )
+ return shGlobalMP.playerStatVars[ category ][ alias ][ subAlias ].statVar
+}
+
+int function GetStatVarType( string category, string alias, string subAlias = "" )
+{
+ Assert( category in shGlobalMP.playerStatVars, "Invalid stat category " + category )
+ Assert( alias in shGlobalMP.playerStatVars[ category ], "No stat alias " + alias + " in category " + category )
+
+ Assert( subAlias in shGlobalMP.playerStatVars[ category ][ alias ] )
+ return shGlobalMP.playerStatVars[ category ][ alias ][ subAlias ].statType
+}
+
+string function GetStatVarLocalizedUnlock( string category, string alias, string subAlias = "" )
+{
+ Assert( category in shGlobalMP.playerStatVars, "Invalid stat category " + category )
+ Assert( alias in shGlobalMP.playerStatVars[ category ], "No stat alias " + alias + " in category " + category )
+
+ Assert( subAlias in shGlobalMP.playerStatVars[ category ][ alias ] )
+ return shGlobalMP.playerStatVars[ category ][ alias ][ subAlias ].localizedUnlock
+}
+
+int function GetPlayerStatInt( entity player, string category, string alias, string subAlias = "" )
+{
+ Assert( IsUI() || IsValid( player ) )
+
+ string statString = GetStatVar( category, alias, subAlias )
+ return player.GetPersistentVarAsInt( statString )
+}
+
+float function GetPlayerStatFloat( entity player, string category, string alias, string subAlias = "" )
+{
+ Assert( IsUI() || IsValid( player ) )
+
+ string statString = GetStatVar( category, alias, subAlias )
+ return expect float( player.GetPersistentVar( statString ) )
+}
+
+
+string function Stats_GetFixedSaveVar( string saveVar, string mapName, string modeName, string difficultyLevel )
+{
+ string fixedSaveVar = saveVar
+ fixedSaveVar = StringReplace( fixedSaveVar, "%mapname%", mapName )
+ fixedSaveVar = StringReplace( fixedSaveVar, "%gamemode%", modeName )
+ fixedSaveVar = StringReplace( fixedSaveVar, "%difficulty%", difficultyLevel )
+
+ return fixedSaveVar
+}
+
+int function GetPlayerStat_AllCompetitiveModesAndMapsInt( entity player, string category, string alias, string subAlias = "" )
+{
+ Assert( IsUI() || IsValid( player ) )
+
+ int count = 0
+
+ int numMaps = PersistenceGetEnumCount( "maps" )
+ int numModes = PersistenceGetEnumCount( "gameModes" )
+
+ string statVarName = GetStatVar( category, alias, subAlias )
+ string fixedSaveVar
+
+ for ( int mode = 0; mode < numModes; mode++ )
+ {
+ for( int map = 0; map < numMaps; map++ )
+ {
+ fixedSaveVar = Stats_GetFixedSaveVar( statVarName, string( map ), string( mode ), "0" )
+ count += expect int( player.GetPersistentVar( fixedSaveVar ) )
+ }
+ }
+
+ return count
+}
+
+
+int function FD_GetHighestDifficultyForTitan( entity player, string titanRef )
+{
+ string statVar = GetStatVar( "titan_stats", "matchesByDifficulty", titanRef )
+
+ int highestDifficulty = 0
+ for ( int difficulty = 0; difficulty < 5; difficulty++ )
+ {
+ string persistentVar = Stats_GetFixedSaveVar( statVar, "", "", string( difficulty ) )
+ if ( player.GetPersistentVarAsInt( persistentVar ) > 0 )
+ highestDifficulty = difficulty
+ }
+
+ return highestDifficulty
+}
+
diff --git a/Northstar.CustomServers/scripts/vscripts/superbar/orbitalstrike.nut b/Northstar.CustomServers/scripts/vscripts/superbar/orbitalstrike.nut
new file mode 100644
index 000000000..7e6224328
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/superbar/orbitalstrike.nut
@@ -0,0 +1,167 @@
+untyped
+
+global function Orbitalstrike_Init
+
+global function OrbitalStrike
+global function CalculateStrikeDelay
+
+const STRIKE_MODEL = $"models/containers/can_red_soda.mdl"
+const ROCKET_START_HEIGHT = 6000
+const LASER_START_HEIGHT = 1000 // TODO: Make this taller after making the trace go through sky
+const LASER_TIME_LENGTH = 7 // Must match charge length in the weapon
+const LASER_DAMAGE = 300
+const LASER_DAMAGE_RADIUS = 300
+const SPAWN_DELAY = 0.2
+
+table file =
+{
+ impactEffectTable = null
+}
+
+
+function Orbitalstrike_Init()
+{
+ PrecacheParticleSystem( $"ar_rocket_strike_small_friend" )
+ PrecacheParticleSystem( $"ar_rocket_strike_small_foe" )
+ PrecacheParticleSystem( $"ar_rocket_strike_large_friend" )
+ PrecacheParticleSystem( $"ar_rocket_strike_large_foe" )
+ PrecacheParticleSystem( $"wpn_orbital_beam" )
+
+ //if ( IsServer() )
+ // file.impactEffectTable <- PrecacheImpactEffectTable( GetWeaponInfoFileKeyField_Global( "mp_projectile_orbital_strike", "impact_effect_table" ) )
+ #if SERVER
+ file.impactEffectTable = PrecacheImpactEffectTable( "orbital_strike" )
+ #endif
+
+
+
+ RegisterSignal( "TargetDesignated" )
+ RegisterSignal( "BeginLaser" )
+ RegisterSignal( "MoveLaser" )
+ RegisterSignal( "FreezeLaser" )
+ RegisterSignal( "EndLaser" )
+}
+
+
+function CalculateStrikeDelay( index, stepCount, duration )
+{
+ local lastStepDelay = 0
+ if ( index )
+ {
+ local stepFrac = (index - 1) / stepCount.tofloat()
+ stepFrac = 1 - (1 - stepFrac) * (1 - stepFrac)
+ lastStepDelay = stepFrac * (duration)
+ }
+
+ local stepFrac = index / stepCount.tofloat()
+ stepFrac = 1 - (1 - stepFrac) * (1 - stepFrac)
+ return (stepFrac * (duration)) - lastStepDelay
+}
+
+
+function OrbitalStrike( entity player, vector targetPos, numRockets = 12, float radius = 256.0, float totalTime = 3.0, extraStartDelay = null )
+{
+ int team = player.GetTeam()
+ CreateNoSpawnArea( TEAM_INVALID, TEAM_INVALID, targetPos, totalTime, radius )
+
+ if ( extraStartDelay != null )
+ wait extraStartDelay
+
+ // Trace down from max z height until we hit something so we know where rockets should land
+ // This makes calling in orbital strike indoors land on the roof like it should, not indoors
+ //local downStartPos = Vector( targetPos.x, targetPos.y, 16384 )
+ //TraceResults downResult = TraceLine( downStartPos, targetPos, null, (TRACE_MASK_NPCSOLID_BRUSHONLY|TRACE_MASK_WATER), TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( downStartPos+ Vector(0,10,0), targetPos + Vector(0,10,0), 255, 255, 255, true, 60 )
+
+ /*
+ while ( true ) // retrace because we hit a sky brush from outside the level, not the ground
+ {
+ if ( !downResult.hitSky )
+ break
+ printt( "Hit sky" )
+ downStartPos = downResult.endPos
+ downStartPos.z -= 5
+ downResult = TraceLine( downStartPos, targetPos, null, (TRACE_MASK_NPCSOLID_BRUSHONLY|TRACE_MASK_WATER), TRACE_COLLISION_GROUP_NONE )
+ DebugDrawLine( downStartPos, downResult.endPos, 0, 255, 0, true, 60.0 )
+ DebugDrawLine( downStartPos + Vector(10,0,0), targetPos + Vector(10,0,0), 255, 255, 0, true, 60.0 )
+ }
+ */
+
+ /*
+ local upEndPos = targetPos + Vector( 0, 0, ROCKET_START_HEIGHT )
+ TraceResults upResult = TraceLine( downResult.endPos, upEndPos, null, (TRACE_MASK_NPCSOLID_BRUSHONLY|TRACE_MASK_WATER), TRACE_COLLISION_GROUP_NONE )
+ local spawnPos = upResult.endPos
+
+ local rocketPos
+ local min = radius * -1
+ local max = radius
+ local rocket
+ */
+
+ vector rocketOrigin = GetRocketSpawnOrigin( targetPos )
+
+ entity rocket = SpawnRocket( rocketOrigin, Vector( 90, 0, 0 ), player, team ) // First rocket hits center target
+ EmitSoundOnEntity( rocket, "weapon_titanmortar_fire" )
+ EmitSoundOnEntity( rocket, "weapon_titanmortar_projectile" )
+
+ for ( int i = 1; i < numRockets; i++ )
+ {
+ wait CalculateStrikeDelay( i, numRockets, totalTime )
+
+ vector offset = Normalize( Vector( RandomFloatRange( -1.0, 1.0 ), RandomFloatRange( -1.0, 1.0 ), 0 ) )
+ vector rocketPos = rocketOrigin + ( offset * RandomFloat( radius ) )
+
+ entity rocket = SpawnRocket( rocketPos, Vector( 90, 0, 0 ), player, team )
+ EmitSoundOnEntity( rocket, "weapon_titanmortar_fire" )
+ EmitSoundOnEntity( rocket, "weapon_titanmortar_projectile" )
+ }
+}
+
+vector function GetRocketSpawnOrigin( vector point )
+{
+ vector skyPos = GetSkyOriginAbovePoint( point )
+ TraceResults traceResult = TraceLine( skyPos, point, null, (TRACE_MASK_SHOT), TRACE_COLLISION_GROUP_NONE )
+ vector rocketOrigin = traceResult.endPos
+ rocketOrigin.z += 6000
+ if ( rocketOrigin.z > skyPos.z - 1 )
+ rocketOrigin.z = skyPos.z - 1
+ return rocketOrigin
+}
+
+vector function GetSkyOriginAbovePoint( vector point )
+{
+ vector skyOrigin = Vector( point.x, point.y, MAX_WORLD_COORD )
+ vector traceFromPos = Vector( point.x, point.y, point.z )
+
+ while ( true )
+ {
+ TraceResults traceResult = TraceLine( traceFromPos, skyOrigin, null, (TRACE_MASK_SHOT), TRACE_COLLISION_GROUP_NONE )
+
+ if ( traceResult.hitSky )
+ {
+ skyOrigin = traceResult.endPos
+ break
+ }
+
+ traceFromPos = traceResult.endPos
+ traceFromPos.z += 1
+ }
+
+ return skyOrigin
+}
+
+entity function SpawnRocket( vector spawnPos, vector spawnAng, entity owner, int team )
+{
+ entity rocket = CreateEntity( "rpg_missile" )
+ rocket.SetOrigin( spawnPos )
+ rocket.SetAngles( spawnAng )
+ rocket.SetOwner( owner )
+ SetTeam( rocket, team )
+ rocket.SetModel( $"models/weapons/bullets/projectile_rocket.mdl" )
+ rocket.SetImpactEffectTable( file.impactEffectTable )
+ rocket.SetWeaponClassName( "mp_titanweapon_orbital_strike" )
+ rocket.kv.damageSourceId = eDamageSourceId.mp_titanweapon_orbital_strike
+ DispatchSpawn( rocket )
+
+ return rocket
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/superbar/smokescreen.nut b/Northstar.CustomServers/scripts/vscripts/superbar/smokescreen.nut
new file mode 100644
index 000000000..6bbb3e899
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/superbar/smokescreen.nut
@@ -0,0 +1,417 @@
+
+global function Smokescreen_Init
+global function Smokescreen
+global function IsOriginTouchingSmokescreen
+global function IsRayTouchingSmokescreen
+
+#if DEV
+const bool SMOKESCREEN_DEBUG = false
+#endif
+
+global struct SmokescreenStruct
+{
+ vector origin
+ vector angles
+ bool fxUseWeaponOrProjectileAngles = false
+
+ float lifetime = 5.0
+ int ownerTeam = TEAM_ANY
+
+ asset smokescreenFX = FX_ELECTRIC_SMOKESCREEN
+ float fxXYRadius = 230.0 // single fx xy radius used to create nospawn area and block traces
+ float fxZRadius = 170.0 // single fx z radius used to create nospawn area and block traces
+ string deploySound1p = SFX_SMOKE_DEPLOY_1P
+ string deploySound3p = SFX_SMOKE_DEPLOY_3P
+ string stopSound1p = ""
+ string stopSound3p = ""
+ int damageSource = eDamageSourceId.mp_titanability_smoke
+
+ bool blockLOS = true
+ bool shouldHibernate = true
+
+ bool isElectric = true
+ entity attacker
+ entity inflictor
+ entity weaponOrProjectile
+ float damageDelay = 2.0
+ float damageInnerRadius = 320.0
+ float damageOuterRadius = 350.0
+ float dangerousAreaRadius = -1.0
+ int dpsPilot = 30
+ int dpsTitan = 2200
+
+ array<vector> fxOffsets
+}
+
+struct SmokescreenFXStruct
+{
+ vector center // center of all fx positions
+ vector mins // approx mins of all fx relative to center
+ vector maxs // approx maxs of all fx relative to center
+ float radius // approx radius of all fx relative to center
+ array<vector> fxWorldPositions
+ int ownerTeam = TEAM_ANY
+}
+
+struct
+{
+ array<SmokescreenFXStruct> allSmokescreenFX
+ table<entity, float> nextSmokeSoundTime
+} file
+
+void function Smokescreen_Init()
+{
+ PrecacheParticleSystem( FX_ELECTRIC_SMOKESCREEN )
+ PrecacheParticleSystem( FX_ELECTRIC_SMOKESCREEN_BURN )
+ #if MP
+ PrecacheParticleSystem( FX_ELECTRIC_SMOKESCREEN_HEAL )
+ #endif
+ PrecacheParticleSystem( FX_GRENADE_SMOKESCREEN )
+
+ PrecacheSprite( $"sprites/physbeam.vmt" )
+ PrecacheSprite( $"sprites/glow01.vmt" )
+
+#if SERVER
+ AddDamageCallbackSourceID( eDamageSourceId.mp_titanability_smoke, TitanElectricSmoke_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_grenade_electric_smoke, GrenadeElectricSmoke_DamagedPlayerOrNPC )
+#endif
+}
+
+void function Smokescreen( SmokescreenStruct smokescreen )
+{
+ SmokescreenFXStruct fxInfo = Smokescreen_CalculateFXStruct( smokescreen )
+ file.allSmokescreenFX.append( fxInfo )
+
+ array<entity> thermiteBurns = GetActiveThermiteBurnsWithinRadius( fxInfo.center, fxInfo.radius )
+ foreach ( thermiteBurn in thermiteBurns )
+ {
+ entity owner = thermiteBurn.GetOwner()
+
+ if ( IsValid( owner ) && owner.GetTeam() != smokescreen.ownerTeam )
+ thermiteBurn.Destroy()
+ }
+
+ entity traceBlocker
+
+ if ( smokescreen.blockLOS )
+ traceBlocker = Smokescreen_CreateTraceBlockerVol( smokescreen, fxInfo )
+
+#if DEV
+ if ( SMOKESCREEN_DEBUG )
+ DebugDrawCircle( fxInfo.center, <0,0,0>, fxInfo.radius + 240.0, 255, 255, 0, true, smokescreen.lifetime )
+#endif
+ CreateNoSpawnArea( TEAM_ANY, TEAM_ANY, fxInfo.center, smokescreen.lifetime, fxInfo.radius + 240.0 )
+
+ if ( IsValid( smokescreen.attacker ) && smokescreen.attacker.IsPlayer() )
+ {
+ EmitSoundAtPositionExceptToPlayer( TEAM_ANY, fxInfo.center, smokescreen.attacker, smokescreen.deploySound3p )
+ EmitSoundAtPositionOnlyToPlayer( TEAM_ANY, fxInfo.center, smokescreen.attacker, smokescreen.deploySound1p)
+ }
+ else
+ {
+ EmitSoundAtPosition( TEAM_ANY, fxInfo.center, smokescreen.deploySound3p )
+ }
+
+ array<entity> fxEntities = SmokescreenFX( smokescreen, fxInfo )
+ if ( smokescreen.isElectric )
+ thread SmokescreenAffectsEntitiesInArea( smokescreen, fxInfo )
+ //thread CreateSmokeSightTrigger( fxInfo.center, smokescreen.ownerTeam, smokescreen.lifetime ) // disabling for now, this should use the calculated radius if reenabled
+
+ thread DestroySmokescreen( smokescreen, smokescreen.lifetime, fxInfo, traceBlocker, fxEntities )
+}
+
+SmokescreenFXStruct function Smokescreen_CalculateFXStruct( SmokescreenStruct smokescreen )
+{
+ SmokescreenFXStruct fxInfo
+
+ foreach ( i, position in smokescreen.fxOffsets )
+ {
+ //mins
+ if ( i == 0 || position.x < fxInfo.mins.x )
+ fxInfo.mins = <position.x, fxInfo.mins.y, fxInfo.mins.z>
+
+ if ( i == 0 || position.y < fxInfo.mins.y )
+ fxInfo.mins = <fxInfo.mins.x, position.y, fxInfo.mins.z>
+
+ if ( i == 0 || position.z < fxInfo.mins.z )
+ fxInfo.mins = <fxInfo.mins.x, fxInfo.mins.y, position.z>
+
+ // maxs
+ if ( i == 0 || position.x > fxInfo.maxs.x )
+ fxInfo.maxs = <position.x, fxInfo.maxs.y, fxInfo.maxs.z>
+
+ if ( i == 0 || position.y > fxInfo.maxs.y )
+ fxInfo.maxs = <fxInfo.maxs.x, position.y, fxInfo.maxs.z>
+
+ if ( i == 0 || position.z > fxInfo.maxs.z )
+ fxInfo.maxs = <fxInfo.maxs.x, fxInfo.maxs.y, position.z>
+ }
+
+ vector offsetCenter = fxInfo.mins + ( fxInfo.maxs - fxInfo.mins ) * 0.5
+
+ float xyRadius = smokescreen.fxXYRadius * 0.7071
+ float zRadius = smokescreen.fxZRadius * 0.7071
+
+ fxInfo.mins = <fxInfo.mins.x - xyRadius, fxInfo.mins.y - xyRadius, fxInfo.mins.z - zRadius> - offsetCenter
+ fxInfo.maxs = <fxInfo.maxs.x + xyRadius, fxInfo.maxs.y + xyRadius, fxInfo.maxs.z + zRadius> - offsetCenter
+
+ float radiusSqr
+ float singleFXRadius = max( smokescreen.fxXYRadius, smokescreen.fxZRadius )
+
+ vector forward = AnglesToForward( smokescreen.angles )
+ vector right = AnglesToRight( smokescreen.angles )
+ vector up = AnglesToUp( smokescreen.angles )
+
+ foreach ( i, position in smokescreen.fxOffsets )
+ {
+ float distanceSqr = DistanceSqr( position, offsetCenter )
+
+ if ( radiusSqr < distanceSqr )
+ radiusSqr = distanceSqr
+
+ fxInfo.fxWorldPositions.append( smokescreen.origin + ( position.x * forward ) + ( position.y * right ) + ( position.z * up ) )
+ }
+
+ fxInfo.center = smokescreen.origin + ( offsetCenter.x * forward ) + ( offsetCenter.y * right ) + ( offsetCenter.z * up )
+ fxInfo.radius = sqrt( radiusSqr ) + singleFXRadius
+ fxInfo.ownerTeam = smokescreen.ownerTeam
+
+ return fxInfo
+}
+
+void function SmokescreenAffectsEntitiesInArea( SmokescreenStruct smokescreen, SmokescreenFXStruct fxInfo )
+{
+ float startTime = Time()
+ float tickRate = 0.1
+
+ float dpsPilot = smokescreen.dpsPilot * tickRate
+ float dpsTitan = smokescreen.dpsTitan * tickRate
+ Assert( dpsPilot || dpsTitan > 0, "Electric smokescreen with 0 damage created" )
+
+ entity aiDangerTarget = CreateEntity( "info_target" )
+ DispatchSpawn( aiDangerTarget )
+ aiDangerTarget.SetOrigin( fxInfo.center )
+ SetTeam( aiDangerTarget, smokescreen.ownerTeam )
+
+ float dangerousAreaRadius = smokescreen.damageOuterRadius
+ if ( smokescreen.dangerousAreaRadius != -1.0 )
+ dangerousAreaRadius = smokescreen.dangerousAreaRadius
+
+ AI_CreateDangerousArea_Static( aiDangerTarget, smokescreen.weaponOrProjectile, dangerousAreaRadius, TEAM_INVALID, true, true, fxInfo.center )
+
+ OnThreadEnd(
+ function () : ( aiDangerTarget )
+ {
+ aiDangerTarget.Destroy()
+ }
+ )
+
+ wait smokescreen.damageDelay
+
+ while ( Time() - startTime <= smokescreen.lifetime )
+ {
+#if DEV
+ if ( SMOKESCREEN_DEBUG )
+ {
+ DebugDrawCircle( fxInfo.center, <0,0,0>, smokescreen.damageInnerRadius, 255, 0, 0, true, tickRate )
+ DebugDrawCircle( fxInfo.center, <0,0,0>, smokescreen.damageOuterRadius, 255, 0, 0, true, tickRate )
+ }
+#endif
+
+ RadiusDamage(
+ fxInfo.center, // center
+ smokescreen.attacker, // attacker
+ smokescreen.inflictor, // inflictor
+ dpsPilot, // damage
+ dpsTitan, // damageHeavyArmor
+ smokescreen.damageInnerRadius, // innerRadius
+ smokescreen.damageOuterRadius, // outerRadius
+ SF_ENVEXPLOSION_MASK_BRUSHONLY, // flags
+ 0.0, // distanceFromAttacker
+ 0.0, // explosionForce
+ DF_ELECTRICAL | DF_NO_HITBEEP, // scriptDamageFlags
+ smokescreen.damageSource ) // scriptDamageSourceIdentifier
+
+ wait tickRate
+ }
+}
+
+entity function Smokescreen_CreateTraceBlockerVol( SmokescreenStruct smokescreen, SmokescreenFXStruct fxInfo )
+{
+ entity traceBlockerVol = CreateEntity( "trace_volume" )
+ traceBlockerVol.kv.targetname = UniqueString( "smokescreen_traceblocker_vol" )
+ traceBlockerVol.kv.origin = fxInfo.center
+ traceBlockerVol.kv.angles = smokescreen.angles
+ DispatchSpawn( traceBlockerVol )
+ traceBlockerVol.SetBox( fxInfo.mins * 0.9, fxInfo.maxs * 0.9 )
+
+#if DEV
+ if ( SMOKESCREEN_DEBUG )
+ DrawAngledBox( fxInfo.center, smokescreen.angles, fxInfo.mins, fxInfo.maxs, 255, 0, 0, true, smokescreen.lifetime - 0.6 )
+#endif
+
+ return traceBlockerVol
+}
+
+array<entity> function SmokescreenFX( SmokescreenStruct smokescreen, SmokescreenFXStruct fxInfo )
+{
+ array<entity> fxEntities
+
+ foreach ( position in fxInfo.fxWorldPositions )
+ {
+#if DEV
+ if ( SMOKESCREEN_DEBUG )
+ DebugDrawCircle( position, <0.0, 0.0, 0.0>, smokescreen.fxXYRadius, 0, 0, 255, true, smokescreen.lifetime )
+#endif
+ int fxID = GetParticleSystemIndex( smokescreen.smokescreenFX )
+ vector angles = smokescreen.fxUseWeaponOrProjectileAngles ? smokescreen.weaponOrProjectile.GetAngles() : <0.0, 0.0, 0.0>
+ entity fxEnt = StartParticleEffectInWorld_ReturnEntity( fxID, position, angles )
+ float fxLife = smokescreen.lifetime
+
+ EffectSetControlPointVector( fxEnt, 1, <fxLife, 0.0, 0.0> )
+
+ if ( !smokescreen.shouldHibernate )
+ fxEnt.DisableHibernation()
+
+ fxEntities.append( fxEnt )
+ }
+
+ return fxEntities
+}
+
+void function DestroySmokescreen( SmokescreenStruct smokescreen, float lifetime, SmokescreenFXStruct fxInfo, entity traceBlocker, array<entity> fxEntities )
+{
+ float timeToWait = 0.0
+
+ timeToWait = max( lifetime - 0.5, 0.0 )
+
+ wait( timeToWait )
+ if ( IsValid( traceBlocker ) )
+ traceBlocker.Destroy()
+ file.allSmokescreenFX.fastremovebyvalue( fxInfo )
+
+ StopSoundAtPosition( fxInfo.center, smokescreen.deploySound1p )
+ StopSoundAtPosition( fxInfo.center, smokescreen.deploySound3p )
+
+ if ( IsValid( smokescreen.attacker ) && smokescreen.attacker.IsPlayer() )
+ {
+ if ( smokescreen.stopSound3p != "" )
+ EmitSoundAtPositionExceptToPlayer( TEAM_ANY, fxInfo.center, smokescreen.attacker, smokescreen.stopSound3p )
+
+ if ( smokescreen.stopSound1p != "" )
+ EmitSoundAtPositionOnlyToPlayer( TEAM_ANY, fxInfo.center, smokescreen.attacker, smokescreen.stopSound1p)
+ }
+ else
+ {
+ if ( smokescreen.stopSound3p != "" )
+ EmitSoundAtPosition( TEAM_ANY, fxInfo.center, smokescreen.stopSound3p )
+ }
+
+ timeToWait = max( ( lifetime + 0.1 ) - timeToWait, 0.0 )
+ wait( timeToWait )
+
+ foreach ( fxEnt in fxEntities )
+ {
+ if ( IsValid( fxEnt ) )
+ fxEnt.Destroy()
+ }
+}
+
+bool function IsOriginTouchingSmokescreen( vector origin, int teamToIgnore = TEAM_UNASSIGNED )
+{
+ foreach ( fxInfo in file.allSmokescreenFX )
+ {
+ if ( teamToIgnore == fxInfo.ownerTeam )
+ continue
+
+ if ( DistanceSqr( origin, fxInfo.center ) < fxInfo.radius * fxInfo.radius )
+ return true
+ }
+
+ return false
+}
+
+bool function IsRayTouchingSmokescreen( vector rayStart, vector rayEnd, int teamToIgnore = TEAM_UNASSIGNED )
+{
+ foreach ( fxInfo in file.allSmokescreenFX )
+ {
+ if ( teamToIgnore == fxInfo.ownerTeam )
+ continue
+
+ if ( IntersectRayWithSphere( rayStart, rayEnd, fxInfo.center, fxInfo.radius ).result )
+ return true
+ }
+
+ return false
+}
+
+#if SERVER
+void function TitanElectricSmoke_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ if ( !IsAlive( ent ) )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( ent.GetTeam() == attacker.GetTeam() )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ PlayDamageSounds( ent, attacker, ELECTRIC_SMOKESCREEN_SFX_DAMAGE_TITAN_1P, ELECTRIC_SMOKESCREEN_SFX_DAMAGE_TITAN_3P, ELECTRIC_SMOKESCREEN_SFX_DAMAGE_PILOT_1P, ELECTRIC_SMOKESCREEN_SFX_DAMAGE_PILOT_3P )
+}
+
+void function GrenadeElectricSmoke_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ if ( !IsAlive( ent ) )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ PlayDamageSounds( ent, attacker, ELECTRIC_SMOKE_GRENADE_SFX_DAMAGE_TITAN_1P, ELECTRIC_SMOKE_GRENADE_SFX_DAMAGE_TITAN_3P, ELECTRIC_SMOKE_GRENADE_SFX_DAMAGE_PILOT_1P, ELECTRIC_SMOKE_GRENADE_SFX_DAMAGE_PILOT_3P )
+}
+
+void function PlayDamageSounds( entity ent, entity attacker, string titan1P_SFX, string titan3P_SFX, string pilot1P_SFX, string pilot3P_SFX )
+{
+ float currentTime = Time()
+
+ if ( !( ent in file.nextSmokeSoundTime ) )
+ {
+ if ( ent.IsPlayer() )
+ file.nextSmokeSoundTime[ ent ] <- currentTime
+ else
+ file.nextSmokeSoundTime[ ent ] <- currentTime + RandomFloat( 0.5 )
+ }
+
+ if ( file.nextSmokeSoundTime[ ent ] <= currentTime )
+ {
+ if ( ent.IsPlayer() )
+ {
+ if ( ent.IsTitan() )
+ {
+ EmitSoundOnEntityExceptToPlayer( ent, ent, titan3P_SFX )
+ EmitSoundOnEntityOnlyToPlayer( ent, ent, titan1P_SFX )
+ file.nextSmokeSoundTime[ ent ] = currentTime + RandomFloatRange( 0.75, 1.25 )
+ }
+ else
+ {
+ EmitSoundOnEntityExceptToPlayer( ent, ent, pilot3P_SFX )
+ EmitSoundOnEntityOnlyToPlayer( ent, ent, pilot1P_SFX )
+ }
+
+ if ( IsValid( attacker ) && attacker.IsPlayer() )
+ EmitSoundOnEntityOnlyToPlayer( attacker, attacker, "Player.Hitbeep" )
+ }
+ else
+ {
+ if ( ent.IsTitan() )
+ EmitSoundOnEntity( ent, titan3P_SFX )
+ else if ( IsHumanSized( ent ) )
+ EmitSoundOnEntity( ent, pilot3P_SFX )
+ }
+
+ file.nextSmokeSoundTime[ ent ] = currentTime + RandomFloatRange( 0.75, 1.25 )
+ }
+}
+#endif \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/sv_globals.gnut b/Northstar.CustomServers/scripts/vscripts/sv_globals.gnut
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/sv_globals.gnut
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_battery_generator.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_battery_generator.gnut
new file mode 100644
index 000000000..567ad6e74
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/_battery_generator.gnut
@@ -0,0 +1,128 @@
+global function InitDestroyableGenerator
+global function ClearGenerators
+
+const GENERATOR_HEALTH = 200
+
+const MODEL_DESTROYED_GENERATOR = $"models/beacon/charge_generator_01_destroyed.mdl"
+const FX_GENERATOR_EXP = $"P_generator_exp"
+
+struct
+{
+ array<entity> generators
+} file
+
+void function InitDestroyableGenerator()
+{
+ AddSpawnCallbackEditorClass( "script_ref", "script_battery_generator", SpawnPropGenerator )
+ AddSpawnCallback_ScriptName( "prop_battery_generator", PropBatteryGeneratorThink )
+
+ PrecacheModel( MODEL_GENERATOR )
+ PrecacheModel( MODEL_DESTROYED_GENERATOR )
+ PrecacheParticleSystem( FX_GENERATOR_EXP )
+}
+
+void function SpawnPropGenerator( entity generatorRef )
+{
+ entity generator = CreatePropScript( MODEL_GENERATOR, generatorRef.GetOrigin(), generatorRef.GetAngles(), 6 )
+ thread PropBatteryGeneratorThink( generator )
+ generatorRef.Destroy()
+}
+
+void function PropBatteryGeneratorThink( entity generator )
+{
+ SetObjectCanBeMeleed( generator, true )
+ SetVisibleEntitiesInConeQueriableEnabled( generator, true )
+ generator.SetTakeDamageType( DAMAGE_EVENTS_ONLY )
+ generator.SetDamageNotifications( true )
+ generator.SetMaxHealth( GENERATOR_HEALTH )
+ generator.SetHealth( GENERATOR_HEALTH )
+ generator.DisableHibernation()
+ AddEntityCallback_OnDamaged( generator, GeneratorOnDamage )
+
+ entity trigger = CreateEntity( "trigger_cylinder" )
+ trigger.SetRadius( 150 )
+ trigger.SetAboveHeight( 150 )
+ trigger.SetBelowHeight( 150 ) //i.e. make the trigger a sphere as opposed to a cylinder
+ trigger.SetOrigin( generator.GetOrigin() )
+ trigger.SetParent( generator )
+ DispatchSpawn( trigger )
+ trigger.SetEnterCallback( GeneratorTriggerThink )
+
+
+ file.generators.append( generator )
+}
+
+void function GeneratorTriggerThink( entity trigger, entity ent )
+{
+ if ( ent.IsTitan() || IsSuperSpectre( ent ) )
+ {
+ entity generator = trigger.GetParent()
+
+ if ( generator != null )
+ {
+ GeneratorDestroy( generator )
+ }
+ }
+}
+
+void function GeneratorOnDamage( entity generator, var damageInfo )
+{
+ if ( !IsValid( generator ) )
+ {
+ // sometimes OnDamage gets called twice in the same frame, ( scorch's fire )
+ // and first call destroys generator in GeneratorDestroy()
+ return
+ }
+
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ int health = generator.GetHealth()
+ health -= int( damage )
+
+ if ( health <= 0 )
+ GeneratorDestroy( generator )
+ else
+ generator.SetHealth( health )
+}
+
+void function GeneratorDestroy( entity generator )
+{
+ int solidType = 6 //phys collision
+ entity destroyedProp = CreatePropDynamic( MODEL_DESTROYED_GENERATOR, generator.GetOrigin(), generator.GetAngles(), solidType )
+ if ( generator.GetParent() )
+ destroyedProp.SetToSameParentAs( generator )
+
+ destroyedProp.AllowMantle()
+ destroyedProp.DisableHibernation()
+ int fxID = GetParticleSystemIndex( FX_GENERATOR_EXP )
+ vector origin = generator.GetOrigin()
+ vector up = generator.GetUpVector()
+
+ EmitSoundOnEntity( destroyedProp, "BatteryCrate_Explosion" )
+ StartParticleEffectOnEntity( destroyedProp, fxID, FX_PATTACH_ABSORIGIN_FOLLOW, -1 )
+
+ entity battery = CreateTitanBattery( origin + ( up * 40 ) )
+ battery.DisableHibernation()
+
+ //throw out the battery
+ vector right = generator.GetRightVector() * RandomFloatRange( -0.5, 0.5 )
+ vector forward = generator.GetForwardVector() * RandomFloatRange( -0.5, 0.5 )
+ vector velocity = Normalize( up + right + forward ) * 10
+
+ //for moving geo
+ vector parentVelocity = generator.GetVelocity()
+
+ battery.SetVelocity( velocity + parentVelocity )
+
+ file.generators.fastremovebyvalue( generator )
+ generator.Destroy()
+}
+
+void function ClearGenerators()
+{
+ foreach ( g in file.generators )
+ {
+ g.Destroy()
+ }
+ file.generators = []
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_replacement_titans.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_replacement_titans.gnut
new file mode 100644
index 000000000..c9d986bcc
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/_replacement_titans.gnut
@@ -0,0 +1,1183 @@
+untyped
+
+global function ReplacementTitans_Init
+
+global function EmptyTitanPlaysAnim
+global function TryReplacementTitanReadyAnnouncement
+
+global function IsReplacementTitanAvailable
+
+global function SetTitanRespawnTimer
+global function GetTitanRespawnTimer
+global function DecrementBuildTimer
+global function ReplacementTitanTimerFinished
+global function GetAttachmentAtTimeFromModel
+global function TryETATitanReadyAnnouncement
+global function TryUpdateTitanRespawnTimerForNewTitanSelection
+global function IsReplacementDropInProgress
+
+global function req
+global function ReplacementTitan
+global function TryAnnounceTitanfallWarningToEnemyTeam
+global function GetTitanForPlayer
+
+
+global function ShouldSetTitanRespawnTimer
+
+global function PauseTitanTimers
+global function PauseTitansThink
+
+global function IsReplacementTitanAvailableForGameState
+
+global function SetReplacementTitanGamemodeRules
+global function SetRequestTitanGamemodeRules
+
+global function CreateTitanForPlayerAndHotdrop
+
+struct {
+ array<int> ETATimeThresholds = [ 120, 60, 30, 15 ]
+ float ETA2MinUpperBound = 123
+ float ETA2MinLowerBound = 115
+ float ETA60sUpperBound = 63
+ float ETA60sLowerBound = 55
+ float ETA30sUpperBound = 33
+ float ETA30sLowerBound = 25
+ float ETA15sUpperBound = 18
+ float ETA15sLowerBound = 12
+ float ETAAnnouncementAllowanceTime = 6.0
+
+ bool buildTimerDisabled = false
+
+ table warpFallDebounce = {}
+
+ bool functionref( entity ) ReplacementTitanGamemodeRules
+ bool functionref( entity, vector ) RequestTitanGamemodeRules
+
+} file
+
+const nagInterval = 40
+
+global const float WARPFALL_SOUND_DELAY = 1.1
+global const float WARPFALL_FX_DELAY = 0.9
+
+function ReplacementTitans_Init()
+{
+ ReplacementTitansDrop_Init()
+
+ RegisterSignal( "titan_impact" )
+
+ RegisterSignal( "SetTitanRespawnTimer" )
+ RegisterSignal( "CalledInReplacementTitan" )
+
+ PrecacheEffect( TURBO_WARP_FX )
+ PrecacheEffect( TURBO_WARP_COMPANY )
+
+
+ AddCallback_OnClientConnecting( ReplacementTitan_InitPlayer )
+ AddClientCommandCallback( "ClientCommand_RequestTitan", ClientCommand_RequestTitan )
+ AddSoulDeathCallback( ResetTitanReplacementAnnouncements )
+
+ level.maxTitansPerTeam <- 2
+
+ if ( file.ReplacementTitanGamemodeRules == null )
+ file.ReplacementTitanGamemodeRules = ReplacementTitanGamemodeRules_Default
+ if ( file.RequestTitanGamemodeRules == null )
+ file.RequestTitanGamemodeRules = RequestTitanGamemodeRules_Default
+
+ FlagInit( "LevelHasRoof" )
+}
+
+
+void function ReplacementTitan_InitPlayer( entity player )
+{
+ player.p.replacementTitanETATimer = GetTimeLimit_ForGameMode() * 60.0
+}
+
+
+bool function IsReplacementTitanAvailable( player, timeBuffer = 0 )
+{
+ expect entity( player )
+
+ if ( !IsReplacementTitanAvailableForGameState() )
+ return false
+
+ if ( player.IsTitan() )
+ return false
+
+ if ( IsAlive( player.GetPetTitan() ) )
+ return false
+
+ if ( player.isSpawning )
+ return false
+
+ if ( !file.ReplacementTitanGamemodeRules( player ) )
+ return false
+
+ switch ( Riff_TitanAvailability() )
+ {
+ case eTitanAvailability.Default:
+ if ( player.titansBuilt == 0 )
+ return true
+ else
+ break
+
+ default:
+ return Riff_IsTitanAvailable( player )
+ }
+
+ if ( player.IsBot() )
+ return true
+
+ return ReplacementTitanTimerFinished( player, timeBuffer )
+}
+
+function IsReplacementTitanAvailableForGameState()
+{
+ #if HAS_GAMEMODES
+ local currentGameState = GetGameState()
+
+ switch ( currentGameState ) //need to add a new entry in here for every new game state we make
+ {
+ case eGameState.WaitingForCustomStart:
+ case eGameState.WaitingForPlayers:
+ case eGameState.PickLoadout:
+ case eGameState.Prematch:
+ case eGameState.SwitchingSides:
+ case eGameState.Postmatch:
+ return false
+
+ case eGameState.Playing:
+ case eGameState.SuddenDeath:
+ return true
+
+ case eGameState.WinnerDetermined:
+ case eGameState.Epilogue:
+ {
+ if ( IsRoundBased() )
+ {
+ if ( !IsRoundBasedGameOver() )
+ return false
+
+ if ( !ShouldRunEvac() )
+ return false
+ }
+
+ return true
+ }
+
+ default:
+ Assert( false, "Unknown Game State: " + currentGameState )
+ return false
+ }
+ #endif
+
+ return true
+}
+
+void function SetReplacementTitanGamemodeRules( bool functionref( entity ) rules )
+{
+ file.ReplacementTitanGamemodeRules = rules
+}
+
+void function SetRequestTitanGamemodeRules( bool functionref( entity, vector ) rules )
+{
+ file.RequestTitanGamemodeRules = rules
+}
+
+bool function ReplacementTitanGamemodeRules_Default( entity player )
+{
+ return true
+}
+
+bool function RequestTitanGamemodeRules_Default( entity player, vector origin )
+{
+ return true
+}
+
+float function GetTitanRespawnTimer( entity player )
+{
+ return player.GetNextTitanRespawnAvailable() - Time()
+}
+
+
+#if SP
+void function DecrementBuildTimer( entity player, float amount )
+{
+ if ( !player.IsTitan() )
+ return
+ // core ability in use
+ if ( TitanCoreInUse( player ) )
+ return
+
+ if ( !IsAlive( player ) )
+ return
+
+ SetTitanCoreTimer( player, GetTitanCoreTimer( player ) - amount )
+}
+#endif
+
+#if MP
+void function DecrementBuildTimer( entity player, float amount )
+{
+ Assert( !TitanDamageRewardsTitanCoreTime() || !player.IsTitan() )
+
+ amount = ModifyBuildTimeForPlayerBonuses( player, amount )
+
+ bool shouldDecrementBuildTimer = true
+
+ if ( player.IsTitan() )
+ {
+ // core ability in use
+ if ( TitanCoreInUse( player ) )
+ return
+
+ if ( !IsAlive( player ) )
+ return
+ }
+ else
+ {
+ //Don't decrement build time for Titan if already have Titan in map
+ if ( player.GetPetTitan() )
+ return
+ }
+
+ if ( player.IsTitan() )
+ {
+ SetTitanCoreTimer( player, GetTitanCoreTimer( player ) - amount )
+ }
+ else if ( shouldDecrementBuildTimer )
+ {
+ float remainingTime = GetTitanRespawnTimer( player )
+ SetTitanRespawnTimer( player, remainingTime - amount )
+ }
+}
+#endif
+
+float function ModifyBuildTimeForPlayerBonuses( entity player, float amount )
+{
+ if ( PlayerHasServerFlag( player, SFLAG_FAST_BUILD2 ) )
+ amount *= 2.0
+ else if ( PlayerHasServerFlag( player, SFLAG_FAST_BUILD1 ) )
+ amount *= 1.5
+
+ return amount
+}
+
+
+void function TryUpdateTitanRespawnTimerForNewTitanSelection( entity player )
+{
+ if ( GetCurrentPlaylistVarInt( "titan_build_time_use_set_file", 0 ) == 1 )
+ {
+ if ( ShouldSetTitanRespawnTimer( player ) )
+ {
+ if ( player.GetTitanBuildTime() != GetTitanBuildTime( player ) )
+ {
+ float timeElapsed = player.GetTitanBuildTime() - ( player.GetNextTitanRespawnAvailable() - Time() )
+ ResetTitanBuildTime( player ) // update titan build time here
+ float newTime = Time() + ( player.GetTitanBuildTime() - timeElapsed )
+ player.SetNextTitanRespawnAvailable( max( 0, newTime ) )
+ }
+ }
+ }
+}
+
+void function SetTitanRespawnTimer( entity player, float timeDiff )
+{
+ //printt( "SetTitanRespawnTimer with timeDiff: " + timeDiff )
+ if ( ShouldSetTitanRespawnTimer( player ) == false )
+ return
+
+ float newTime = Time() + timeDiff
+ player.SetNextTitanRespawnAvailable( max( Time() - 1, newTime ) )
+
+ thread WaitToAnnounceTitanETA( player, timeDiff )
+}
+
+bool function ShouldSetTitanRespawnTimer( player )
+{
+ if ( Riff_TitanAvailability() == eTitanAvailability.Custom )
+ return false
+
+ if ( Riff_TitanAvailability() == eTitanAvailability.Default )
+ return true
+
+ if ( player.IsTitan() )
+ return true
+
+ if ( IsValid( player.GetPetTitan() ) )
+ return true
+
+ if ( player.GetNextTitanRespawnAvailable() < 0 )
+ return false
+
+ return true
+}
+
+
+
+function WaitToAnnounceTitanETA( entity player, timeDiff )
+{
+ player.EndSignal( "OnDestroy" )
+ player.Signal( "SetTitanRespawnTimer" )
+ player.EndSignal( "SetTitanRespawnTimer" )
+ player.EndSignal( "CalledInReplacementTitan" )
+ player.EndSignal( "ChoseToSpawnAsTitan" )
+
+ if ( timeDiff > 0 )
+ wait GetTimeTillNextETAAnnouncement( player )
+
+ TryETATitanReadyAnnouncement( player )
+}
+
+float function GetTimeTillNextETAAnnouncement( entity player )
+{
+// if ( !IsValid( player ) )
+// return 0
+
+ float timeTillNextTitan = player.GetNextTitanRespawnAvailable() - Time()
+ if ( timeTillNextTitan <= 0 )
+ {
+ //printt( "Waiting 0, Titan Ready" )
+ return 0
+ }
+
+// if ( !( "replacementTitanETATimer" in player.s ) )
+// return 0
+
+ if ( timeTillNextTitan >= file.ETA2MinUpperBound && player.p.replacementTitanETATimer > 120 ) //Give some leadup time to conversation starting
+ {
+ //printt( "Waiting " + ( timeTillNextTitan - file.ETA2MinUpperBound ) + " till 2 min announcement" )
+ return timeTillNextTitan - file.ETA2MinUpperBound
+ }
+
+ if ( timeTillNextTitan >= file.ETA2MinLowerBound && player.p.replacementTitanETATimer > 120 )
+ {
+ //printt( "Waiting 0 till 2 min announcement" )
+ return 0 //Play 2 min ETA announcement immediately
+ }
+
+ if ( timeTillNextTitan >= file.ETA60sUpperBound && player.p.replacementTitanETATimer > 60 )
+ {
+ //printt( "Waiting " + ( timeTillNextTitan - file.ETA60sUpperBound ) + " till 60s announcement" )
+ return timeTillNextTitan - file.ETA60sUpperBound
+ }
+
+ if ( timeTillNextTitan >= file.ETA60sLowerBound && player.p.replacementTitanETATimer > 60 )
+ {
+ //printt( "Waiting 0 till 60s announcement" )
+ return 0
+ }
+
+ if ( timeTillNextTitan >= file.ETA30sUpperBound && player.p.replacementTitanETATimer > 30 )
+ {
+ //printt( "Waiting " + ( timeTillNextTitan - file.ETA30sUpperBound ) + " till 30s announcement" )
+ return timeTillNextTitan - file.ETA30sUpperBound
+ }
+
+ if ( timeTillNextTitan >= file.ETA30sLowerBound && player.p.replacementTitanETATimer > 30 )
+ {
+ //printt( "Waiting 0 till 30 announcement" )
+ return 0
+ }
+
+ if ( timeTillNextTitan >= file.ETA15sUpperBound && player.p.replacementTitanETATimer > 15 )
+ {
+ //printt( "Waiting " + ( timeTillNextTitan - file.ETA15sUpperBound ) + " till 15s announcement" )
+ return timeTillNextTitan - file.ETA15sUpperBound
+ }
+
+ if ( timeTillNextTitan >= file.ETA15sLowerBound && player.p.replacementTitanETATimer > 15 )
+ {
+ //printt( "Waiting 0 till 15s announcement" )
+ return 0
+ }
+
+ //printt( "Waiting " + timeTillNextTitan + " till next Titan" )
+ return timeTillNextTitan
+
+
+}
+
+function TryETATitanReadyAnnouncement( entity player )
+{
+ //printt( "TryETATitanReadyAnnouncement" )
+ if ( !IsAlive( player ) )
+ return
+
+ if ( GetPlayerTitanInMap( player ) )
+ return
+
+ if ( player.GetNextTitanRespawnAvailable() < 0 )
+ return
+
+ if ( GetGameState() > eGameState.SuddenDeath )
+ return
+
+ if ( GameTime_PlayingTime() < 5.0 )
+ return
+
+ local timeTillNextTitan = player.GetNextTitanRespawnAvailable() - Time()
+ //printt( "TryETATitanReadyAnnouncement timetillNextTitan: " + timeTillNextTitan )
+ if ( floor(timeTillNextTitan) <= 0 )
+ {
+ //Titan is ready, let TryReplacementTitanReadyAnnouncement take care of it
+ TryReplacementTitanReadyAnnouncement( player )
+ return
+ }
+
+ //This entire loop is probably too complicated now for what it's doing. Simplify next game!
+ //Loop might be pretty hard to read, a particular iteration of the loop is written in comments below
+ for ( int i = 0; i < file.ETATimeThresholds.len(); ++i )
+ {
+ if ( fabs( timeTillNextTitan - file.ETATimeThresholds[ i ] ) < file.ETAAnnouncementAllowanceTime )
+ {
+ if ( player.p.replacementTitanETATimer > file.ETATimeThresholds[ i ] )
+ {
+ if ( player.titansBuilt )
+ PlayConversationToPlayer( "TitanReplacementETA" + file.ETATimeThresholds[ i ] + "s" , player )
+ else
+ PlayConversationToPlayer( "FirstTitanETA" + file.ETATimeThresholds[ i ] + "s", player )
+
+ player.p.replacementTitanETATimer = float ( file.ETATimeThresholds[ i ] )
+ wait timeTillNextTitan - file.ETATimeThresholds[ i ]
+ if ( IsAlive( player ) )
+ SetTitanRespawnTimer( player, player.GetNextTitanRespawnAvailable() - Time() )
+ return
+ }
+ }
+ }
+
+ /*if ( fabs( timeTillNextTitan - 120 ) < ETAAnnouncementAllowanceTime && player.p.replacementTitanETATimer > 120 )
+ {
+ if ( player.titansBuilt )
+ PlayConversationToPlayer( "TitanReplacementETA120s", player )
+ else
+ PlayConversationToPlayer( "FirstTitanETA120s", player )
+ player.p.replacementTitanETATimer = 120
+ wait timeTillNextTitan - 120
+ SetTitanRespawnTimer( player, player.GetNextTitanRespawnAvailable() - Time() )
+ return
+ }
+ */
+
+}
+
+function TryReplacementTitanReadyAnnouncement( entity player )
+{
+ while( true )
+ {
+ //printt( "TryReplacementTitanReadyAnnouncementLoop" )
+ if ( !IsAlive( player ) )
+ return
+
+ if ( GetGameState() > eGameState.SuddenDeath )
+ return
+
+ if ( GetPlayerTitanInMap( player ) )
+ return
+
+ if ( level.nv.titanDropEnabledForTeam != TEAM_BOTH && level.nv.titanDropEnabledForTeam != player.GetTeam() )
+ return
+
+ if ( player.p.replacementTitanReady_lastNagTime == 0 || Time() - player.p.replacementTitanReady_lastNagTime >= nagInterval )
+ {
+ //Don't play Titan Replacement Announcements if you don't have it ready
+ switch ( Riff_TitanAvailability() )
+ {
+ case eTitanAvailability.Default:
+ break
+
+ default:
+ if ( !Riff_IsTitanAvailable( player ) )
+ return
+ }
+
+ if ( player.titansBuilt )
+ {
+ PlayConversationToPlayer( "TitanReplacementReady", player )
+ }
+ else
+ {
+ PlayConversationToPlayer( "FirstTitanReady", player )
+ }
+ player.p.replacementTitanReady_lastNagTime = Time()
+ }
+
+ wait 5.0 // Once every 5 seconds should be fine
+ }
+}
+
+void function ResetTitanReplacementAnnouncements( entity soul, var damageInfo )
+{
+ entity player = soul.GetBossPlayer()
+
+ if ( !IsValid( player ) )
+ return
+
+ player.p.replacementTitanETATimer = expect float( level.nv.gameEndTime )
+}
+
+function req()
+{
+ ReplacementTitan( GetPlayerArray()[0] )
+}
+
+bool function ClientCommand_RequestTitan( entity player, array<string> args )
+{
+ ReplacementTitan( player ) //Separate function because other functions will call ReplacementTitan
+ return true
+}
+
+// This a baseline titan request function; the only things that prevent this from happening are
+// common cases; wrong gamestate, already has a titan, is currently dead, etc...
+bool function RequestTitan( entity player )
+{
+ if ( !IsReplacementTitanAvailableForGameState() )
+ return false
+
+ if ( player.IsTitan() )
+ return false
+
+ if ( IsAlive( player.GetPetTitan() ) )
+ return false
+
+ if ( player.isSpawning )
+ return false
+
+ if ( !IsAlive( player ) )
+ return false
+
+ Point spawnPoint = GetTitanReplacementPoint( player, false )
+ local origin = spawnPoint.origin
+ Assert( origin )
+
+ //Check titanfall request against any custom gamemode rules
+ if ( !file.RequestTitanGamemodeRules( player, spawnPoint.origin ) )
+ return false
+
+ //if ( ShouldDoTitanfall() )
+ thread CreateTitanForPlayerAndHotdrop( player, spawnPoint )
+ //else
+ // thread ForcePilotToBecomeTitan( player )
+
+ return true
+}
+
+bool function ReplacementTitan( entity player )
+{
+ if ( !IsAlive( player ) )
+ {
+ printt( "ReplacementTitan", player, player.entindex(), "failed", "IsAlive( player ) was false" )
+ return false
+ }
+
+ if ( !IsReplacementTitanAvailable( player, 0 ) )
+ {
+ printt( "ReplacementTitan", player, player.entindex(), "failed", "IsReplacementTitanAvailable was false" )
+ return false
+ }
+
+ entity titan = GetPlayerTitanInMap( player )
+ if ( IsAlive( titan ) )
+ {
+ printt( "ReplacementTitan", player, player.entindex(), "failed", "GetPlayerTitanInMap was true" )
+ return false
+ }
+
+ if ( player in file.warpFallDebounce )
+ {
+ if ( Time() - file.warpFallDebounce[ player ] < 3.0 )
+ {
+ printt( "ReplacementTitan", player, player.entindex(), "failed", "player in file.warpFallDebounce was true" )
+ return false
+ }
+ }
+
+ Point spawnPoint = GetTitanReplacementPoint( player, false )
+ local origin = spawnPoint.origin
+ Assert( origin )
+
+ #if MP
+ PIN_PlayerAbility( player, "titanfall", "titanfall", {pos = origin} )
+ #endif
+
+ //Check titanfall request against any custom gamemode rules
+ if ( !file.RequestTitanGamemodeRules( player, spawnPoint.origin ) )
+ return false
+
+ #if SP
+ thread CreateTitanForPlayerAndHotdrop( player, spawnPoint )
+ #endif
+
+ #if MP
+ if ( ShouldDoTitanfall() )
+ thread CreateTitanForPlayerAndHotdrop( player, spawnPoint )
+ else
+ thread ForcePilotToBecomeTitan( player )
+ #endif
+
+ return true
+}
+
+#if MP
+
+void function ForcePilotToBecomeTitan( entity player )
+{
+ float fadeTime = 0.5
+ float holdTime = 2.0
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+
+ if ( GAMETYPE != SST )
+ {
+ #if FACTION_DIALOGUE_ENABLED
+ PlayFactionDialogueToPlayer( "mp_titanInbound" , player )
+ #else
+ if ( player.titansBuilt )
+ PlayConversationToPlayer( "TitanReplacement", player )
+ else
+ PlayConversationToPlayer( "FirstTitanInbound", player )
+ #endif
+ }
+
+ player.Signal( "RodeoOver" )
+ player.Signal( "ScriptAnimStop" )
+
+ table<string,bool> e = {}
+ e.settingsRestored <- false
+
+ OnThreadEnd(
+ function() : ( player, e )
+ {
+ if ( IsValid( player ) && !e.settingsRestored )
+ {
+ Rodeo_Allow( player )
+ player.Show()
+ player.MakeVisible()
+ }
+ }
+ )
+ Rodeo_Disallow( player )
+
+ ScreenFadeToBlack( player, fadeTime, holdTime )
+ player.DissolveNonLethal( ENTITY_DISSOLVE_CORE, Vector( 0, 0, 0 ), 500 )
+
+ wait fadeTime
+ player.SetInvulnerable()
+ player.Hide()
+
+ wait holdTime
+ ScreenFadeFromBlack( player, 1.0, 0.5 )
+ waitthread TitanPlayerHotDropsIntoLevel( player )
+ e.settingsRestored = true
+ Rodeo_Allow( player )
+ player.Show()
+ player.MakeVisible()
+ player.ClearInvulnerable()
+}
+#endif
+
+bool function IsReplacementDropInProgress( entity player )
+{
+ return expect bool( player.s.replacementDropInProgress )
+}
+
+void function CreateTitanForPlayerAndHotdrop( entity player, Point spawnPoint, TitanLoadoutDef ornull overrideLoadout = null )
+{
+ Assert( IsValid( player ) )
+
+ if ( player.isSpawning )
+ {
+ printt( "CreateTitanForPlayerAndHotdrop", player, player.entindex(), "failed", "player.isSpawning was true" )
+ return
+ }
+
+ if ( player.s.replacementDropInProgress )
+ {
+ printt( "CreateTitanForPlayerAndHotdrop", player, player.entindex(), "failed", "player.s.replacementDropInProgress was true" )
+ return
+ }
+
+ player.s.replacementDropInProgress = true
+
+ entity titanFallDisablingEntity = CreateInfoTarget()
+
+ OnThreadEnd(
+ function() : ( player, titanFallDisablingEntity )
+ {
+ if ( IsValid( titanFallDisablingEntity ) ) //As a fail safe. Should have been cleaned up in OnThreadEnd of CleanupTitanFallDisablingEntity
+ titanFallDisablingEntity.Destroy()
+
+ if ( !IsValid( player ) )
+ return
+
+ player.s.replacementDropInProgress = false
+ player.ClearHotDropImpactTime()
+ }
+ )
+
+ player.EndSignal( "OnDestroy" )
+
+ if ( GAMETYPE != SST )
+ {
+ #if FACTION_DIALOGUE_ENABLED
+ PlayFactionDialogueToPlayer( "mp_titanInbound" , player )
+ #else
+ if ( player.titansBuilt )
+ PlayConversationToPlayer( "TitanReplacement", player )
+ else
+ PlayConversationToPlayer( "FirstTitanInbound", player )
+ #endif
+ }
+
+ vector origin = spawnPoint.origin
+ vector angles
+ if ( spawnPoint.angles != < 0.0, 0.0, 0.0 > )
+ angles = spawnPoint.angles
+ else
+ angles = VectorToAngles( FlattenVector( player.GetViewVector() ) * -1 ) // face the player
+
+ printt( "Dropping replacement titan at " + origin + " with angles " + angles )
+
+ #if HAS_STATS
+ UpdatePlayerStat( player, "misc_stats", "titanFalls" )
+ #endif
+ #if SERVER && MP
+ PIN_AddToPlayerCountStat( player, "titanfalls" )
+ #endif
+
+ if ( !level.firstTitanfall )
+ {
+ AddPlayerScore( player, "FirstTitanfall", player )
+
+ #if HAS_STATS
+ UpdatePlayerStat( player, "misc_stats", "titanFallsFirst" )
+ #endif
+
+ level.firstTitanfall = true
+ }
+ else
+ {
+ AddPlayerScore( player, "Titanfall", player )
+ }
+
+
+ player.Signal( "CalledInReplacementTitan" )
+
+ int playerTeam = player.GetTeam()
+
+ TryAnnounceTitanfallWarningToEnemyTeam( playerTeam, origin )
+
+ titanFallDisablingEntity.SetOrigin( origin )
+ DisableTitanfallForLifetimeOfEntityNearOrigin( titanFallDisablingEntity, origin, TITANHOTDROP_DISABLE_ENEMY_TITANFALL_RADIUS )
+
+ entity titan
+ string animation
+
+ string regularTitanfallAnim = "at_hotdrop_drop_2knee_turbo"
+
+ TitanLoadoutDef loadout
+ if ( overrideLoadout == null )
+ {
+ loadout = GetTitanLoadoutForPlayer( player )
+ }
+ else
+ {
+ loadout = expect TitanLoadoutDef( overrideLoadout )
+ }
+ bool hasWarpfall = loadout.passive3 == "pas_warpfall"
+ if ( hasWarpfall || Flag( "LevelHasRoof" ) )
+ {
+ ClearTitanAvailable( player ) //Normally this is done when the Titan is spawned, but for warpfall the Titan isn't spawned instaneously after requesting it.
+
+ file.warpFallDebounce[ player ] <- Time()
+ animation = "at_hotdrop_drop_2knee_turbo_upgraded"
+ string settings = loadout.setFile
+ asset model = GetPlayerSettingsAssetForClassName( settings, "bodymodel" )
+ Attachment warpAttach = GetAttachmentAtTimeFromModel( model, animation, "offset", origin, angles, 0 )
+
+ entity fakeTitan = CreatePropDynamic( model )
+ float impactTime = GetHotDropImpactTime( fakeTitan, animation )
+
+ float diff = 0.0
+
+ if ( !hasWarpfall ) // this means the level requested the warpfall
+ {
+ float regularImpactTime = GetHotDropImpactTime( fakeTitan, regularTitanfallAnim ) - (WARPFALL_SOUND_DELAY + WARPFALL_FX_DELAY)
+ diff = ( regularImpactTime - impactTime )
+ impactTime = regularImpactTime
+ }
+
+ fakeTitan.Kill_Deprecated_UseDestroyInstead()
+
+ local impactStartTime = Time()
+ impactTime += (WARPFALL_SOUND_DELAY + WARPFALL_FX_DELAY)
+ player.SetHotDropImpactDelay( impactTime )
+ Remote_CallFunction_Replay( player, "ServerCallback_ReplacementTitanSpawnpoint", origin.x, origin.y, origin.z, Time() + impactTime )
+
+ EmitDifferentSoundsAtPositionForPlayerAndWorld( "Titan_1P_Warpfall_CallIn", "Titan_3P_Warpfall_CallIn", origin, player, playerTeam )
+
+ wait diff
+
+ wait WARPFALL_SOUND_DELAY
+
+ // "Titan_1P_Warpfall_Start" - for first person warp calls, starting right on the button press
+ // "Titan_3P_Warpfall_Start" - for any 3P other player or NPC when they call in a warp, starting right on their button press
+ EmitSoundAtPositionOnlyToPlayer( playerTeam, origin, player, "Titan_1P_Warpfall_Start" )
+ EmitSoundAtPositionExceptToPlayer( playerTeam, origin, player, "Titan_3P_Warpfall_Start" )
+
+ PlayFX( TURBO_WARP_FX, warpAttach.position + Vector(0,0,-104), warpAttach.angle )
+
+ wait WARPFALL_FX_DELAY
+
+ titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, origin, angles )
+ DispatchSpawn( titan )
+ thread PlayFXOnEntity( TURBO_WARP_COMPANY, titan, "offset" )
+ }
+ else
+ {
+ animation = regularTitanfallAnim
+
+ titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, origin, angles )
+ DispatchSpawn( titan )
+
+ float impactTime = GetHotDropImpactTime( titan, animation )
+ player.SetHotDropImpactDelay( impactTime )
+ Remote_CallFunction_Replay( player, "ServerCallback_ReplacementTitanSpawnpoint", origin.x, origin.y, origin.z, Time() + impactTime )
+ }
+
+ SetActiveTitanLoadoutIndex( player, GetPersistentSpawnLoadoutIndex( player, "titan" ) )
+ #if MP
+ SetActiveTitanLoadout( player )
+ #endif
+ if ( player in file.warpFallDebounce )
+ delete file.warpFallDebounce[ player ]
+
+ titan.EndSignal( "OnDeath" )
+ Assert( IsAlive( titan ) )
+
+ // dont let AI titan get enemies while dropping. Don't do trigger checks
+ titan.SetEfficientMode( true )
+ titan.SetTouchTriggers( false )
+ titan.SetNoTarget( true )
+ titan.SetAimAssistAllowed( false )
+
+#if R1_VGUI_MINIMAP
+ thread PingMinimapDuringHotdrop( player, titan, origin )
+#endif
+
+ thread CleanupTitanFallDisablingEntity( titanFallDisablingEntity, titan ) //needs to be here after titan is created
+ waitthread PlayersTitanHotdrops( titan, origin, angles, player, animation ) //Note that this function returns after the titan has played the landing anim, not when the titan hits the ground
+
+ titan.SetEfficientMode( false )
+ titan.SetTouchTriggers( true )
+ titan.SetAimAssistAllowed( true )
+
+ player.Signal( "titan_impact" )
+
+ thread TitanNPC_WaitForBubbleShield_StartAutoTitanBehavior( titan )
+}
+
+void function CleanupTitanFallDisablingEntity( entity titanFallDisablingEntity, entity titan )
+{
+ titanFallDisablingEntity.EndSignal( "OnDestroy" ) //titanFallDisablingEntity can be destroyed multiple ways
+ titan.EndSignal( "ClearDisableTitanfall" ) //This is awkward, CreateBubbleShield() and OnHotDropImpact() signals this to deestroy CleanupTitanFallDisablingEntity
+ titan.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( titanFallDisablingEntity )
+ {
+ if( IsValid( titanFallDisablingEntity ) )
+ titanFallDisablingEntity.Destroy()
+
+ }
+ )
+
+ WaitForever()
+}
+
+void function DrawReplacementTitanLocation( entity player, vector origin, float delay )
+{
+ // have to keep resending this info because a dead player won't see it
+ player.EndSignal( "OnDestroy" )
+ float endTime = Time() + delay
+
+ for ( ;; )
+ {
+ if ( !IsAlive( player ) )
+ {
+ player.WaitSignal( "OnRespawned" )
+ continue
+ }
+
+ float remainingTime = endTime - Time()
+ if ( remainingTime <= 0 )
+ return
+
+ player.SetHotDropImpactDelay( remainingTime )
+ Remote_CallFunction_Replay( player, "ServerCallback_ReplacementTitanSpawnpoint", origin.x, origin.y, origin.z, Time() + remainingTime )
+ player.WaitSignal( "OnDeath" )
+ }
+}
+
+void function TryAnnounceTitanfallWarningToEnemyTeam( int team, vector origin )
+{
+ float innerDistance = TITANFALL_OUTER_RADIUS * TITANFALL_OUTER_RADIUS
+ float outerDistance = innerDistance * 4.0
+
+ array<entity> enemies = GetPlayerArrayOfEnemies( team )
+ foreach ( entity enemyPlayer in enemies )
+ {
+ float distSqr = DistanceSqr( origin, enemyPlayer.GetOrigin() )
+ if ( distSqr > outerDistance )
+ continue
+
+ if ( distSqr < innerDistance )
+ Remote_CallFunction_NonReplay( enemyPlayer, "ServerCallback_TitanFallWarning", true )
+ else
+ Remote_CallFunction_NonReplay( enemyPlayer, "ServerCallback_TitanFallWarning", false )
+ }
+}
+
+TitanSettings function GetTitanForPlayer( entity player )
+{
+ string ornull currentTitanSettings
+ array<string> currentTitanMods
+
+ if ( player.IsBot() )
+ {
+ string botTitanSettings = GetConVarString( "bot_titan_settings" )
+ array<string> legalLoadouts = GetAllowedTitanSetFiles()
+ if ( legalLoadouts.contains( botTitanSettings ) )
+ currentTitanSettings = botTitanSettings
+ else
+ currentTitanSettings = legalLoadouts.getrandom()
+ }
+
+ if ( currentTitanSettings == null )
+ {
+ TitanLoadoutDef loadout = GetTitanLoadoutForPlayer( player )
+ currentTitanSettings = loadout.setFile
+ foreach ( mod in loadout.setFileMods )
+ {
+ currentTitanMods.append( mod )
+ }
+ }
+
+ if ( DebugNewTitanModels() )
+ {
+ switch ( currentTitanSettings )
+ {
+ case "titan_atlas":
+ currentTitanSettings = "titan_medium_ajax"
+ break
+ case "titan_stryder":
+ currentTitanSettings = "titan_light_locust"
+ break
+ case "titan_ogre":
+ currentTitanSettings = "titan_heavy_ogre"
+ break
+ }
+ }
+
+ TitanSettings titanSettings
+ titanSettings.titanSetFile = expect string( currentTitanSettings )
+ titanSettings.titanSetFileMods = currentTitanMods
+ return titanSettings
+}
+
+Attachment function GetAttachmentAtTimeFromModel( asset model, string animation, string attachment, vector origin, vector angles, float time )
+{
+ entity dummy = CreatePropDynamic( model, origin, angles )
+ Attachment start = dummy.Anim_GetAttachmentAtTime( animation, attachment, time )
+ dummy.Destroy()
+ return start
+}
+
+#if R1_VGUI_MINIMAP
+function PingMinimapDuringHotdrop( player, titan, impactOrigin )
+{
+ expect entity( player )
+ expect entity( titan )
+
+ player.EndSignal( "titan_impact" )
+ player.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDeath" )
+
+ titan.Minimap_Hide( TEAM_IMC, null )
+ titan.Minimap_Hide( TEAM_MILITIA, null )
+
+ OnThreadEnd(
+ function() : ( player, titan )
+ {
+ if ( !IsAlive( titan ) )
+ return
+
+ titan.Minimap_DisplayDefault( TEAM_IMC, null )
+ titan.Minimap_DisplayDefault( TEAM_MILITIA, null )
+ }
+ )
+
+ while ( true )
+ {
+ Minimap_CreatePingForPlayer( player, impactOrigin, $"vgui/HUD/threathud_titan_friendlyself", 0.5 )
+ wait 0.4
+ }
+}
+#endif
+
+function EmptyTitanPlaysAnim( titan )
+{
+ local idleAnimAlias = "at_atlas_getin_idle"
+ if ( titan.HasKey( "idleAnim" ) )
+ idleAnimAlias = titan.GetValueForKey( "idleAnim" )
+
+ thread PlayAnim( titan, idleAnimAlias )
+}
+
+function FreeSpawnpointOnEnterTitan( spawnpoint, titan )
+{
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "TitanEntered" )
+
+ OnThreadEnd(
+ function() : ( spawnpoint, titan )
+ {
+ Assert( IsValid( titan ) )
+ spawnpoint.e.spawnPointInUse = false
+ }
+ )
+
+ titan.WaitSignal( "TitanBeingEntered" )
+}
+
+
+function DebugText( origin, text, time )
+{
+ local endTime = Time() + time
+
+ while( Time() < endTime )
+ {
+ DebugDrawText( origin, text, true, 1.0 )
+ wait 1
+ }
+}
+
+
+
+bool function ReplacementTitanTimerFinished( player, timeBuffer = 0 )
+{
+ local nextTitanTime = player.GetNextTitanRespawnAvailable()
+ if ( nextTitanTime < 0 )
+ return false
+
+ return nextTitanTime - Time() <= timeBuffer
+}
+
+
+struct
+{
+ float titanTimerPauseTime = 0
+ table<entity, float> playerPauseStartTimes
+
+} protoFile
+
+
+void function PauseTitansThink()
+{
+ bool titan
+ while ( true )
+ {
+ array<entity> players = GetPlayerArray()
+
+ bool foundTitan = false
+ foreach ( player in players )
+ {
+ if ( player.IsTitan() || IsValid( player.GetPetTitan() ) )
+ {
+ foundTitan = true
+ break
+ }
+ }
+
+ if ( foundTitan && protoFile.titanTimerPauseTime == 0 )
+ thread PauseTitanTimers()
+ else if ( !foundTitan && protoFile.titanTimerPauseTime != 0 )
+ thread PauseTitanTimers()
+
+ WaitFrame()
+ }
+}
+
+
+void function PauseTitanTimers()
+{
+ RegisterSignal( "PauseTitanTimers" )
+ svGlobal.levelEnt.Signal( "PauseTitanTimers" )
+ svGlobal.levelEnt.EndSignal( "PauseTitanTimers" )
+
+ if ( protoFile.titanTimerPauseTime != 0 )
+ {
+ protoFile.playerPauseStartTimes = {}
+ protoFile.titanTimerPauseTime = 0
+ return
+ }
+
+ protoFile.titanTimerPauseTime = Time()
+ float lastTime = Time()
+
+ while ( true )
+ {
+ array<entity> players = GetPlayerArray()
+
+ float addTime = Time() - protoFile.titanTimerPauseTime
+
+ foreach ( player in players )
+ {
+ if ( player.IsTitan() )
+ {
+ if ( player in protoFile.playerPauseStartTimes )
+ delete protoFile.playerPauseStartTimes[player]
+
+ continue
+ }
+
+ if ( IsValid( player.GetPetTitan() ) )
+ {
+ if ( player in protoFile.playerPauseStartTimes )
+ delete protoFile.playerPauseStartTimes[player]
+
+ continue
+ }
+
+ if ( Time() > player.GetNextTitanRespawnAvailable() )
+ {
+ if ( player in protoFile.playerPauseStartTimes )
+ delete protoFile.playerPauseStartTimes[player]
+
+ continue
+ }
+
+ if ( !(player in protoFile.playerPauseStartTimes) )
+ {
+ protoFile.playerPauseStartTimes[player] <- player.GetNextTitanRespawnAvailable()
+ }
+
+ protoFile.playerPauseStartTimes[player] += Time() - lastTime
+
+ player.SetNextTitanRespawnAvailable( protoFile.playerPauseStartTimes[player] )
+ }
+
+ lastTime = Time()
+ wait 0.1
+ }
+}
+
+bool function ShouldDoTitanfall()
+{
+ if ( svGlobal.forceDisableTitanfalls )
+ return false
+
+ return ( GetCurrentPlaylistVarInt( "enable_titanfalls", 1 ) == 1 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_replacement_titans_drop.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_replacement_titans_drop.gnut
new file mode 100644
index 000000000..5970f7eab
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/_replacement_titans_drop.gnut
@@ -0,0 +1,443 @@
+global function ReplacementTitansDrop_Init
+global function GetTitanReplacementPoint
+global function HullTraceDropPoint
+global function DebugTitanSpawn
+global function TitanFindDropNodes
+global function TitanHulldropSpawnpoint
+
+global const TITANDROP_LOS_DIST = 2000 // 2D distance at which we do the line of sight check to see where the player wants to call in the titan
+global const TITANDROP_MIN_FOV = 10
+global const TITANDROP_MAX_FOV = 80
+global const TITANDROP_FOV_PENALTY = 8
+global const TITANDROP_PATHNODESEARCH_EXACTDIST = 500 // within this distance, we use the position the player is looking for the pathnode search
+global const TITANDROP_PATHNODESEARCH_DISTFRAC = 0.8 // beyond that distance, we use this fraction of how far the player is looking.
+global const TITANDROP_GROUNDSEARCH_ZDIR = -1.0 // if the player's not looking at anything, we search downward for ground at this slope
+global const TITANDROP_GROUNDSEARCH_FORWARDDIST = 350 // if the player's not looking at anything, we search for ground starting this many units in front of the player
+global const TITANDROP_GROUNDSEARCH_DIST = 1000 // if the player's not looking at anything, we search for ground this many units forward (max)
+global const TITANDROP_FALLBACK_DIST = 150 // if the ground search hits, we go this many units forward from it
+
+struct
+{
+ int replacementSpawnpointsID
+} file
+
+void function ReplacementTitansDrop_Init()
+{
+ AddSpawnCallback( "info_spawnpoint_titan", AddDroppoint )
+ AddSpawnCallback( "info_spawnpoint_titan_start", AddDroppoint )
+ AddSpawnCallback( "info_replacement_titan_spawn", AddDroppoint )
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+ file.replacementSpawnpointsID = CreateScriptManagedEntArray()
+}
+
+void function EntitiesDidLoad()
+{
+}
+
+
+void function AddDroppoint( entity ent )
+{
+ AddToScriptManagedEntArray( file.replacementSpawnpointsID, ent )
+}
+
+void function DebugTitanSpawn()
+{
+ thread DebugTitanSpawnThread()
+}
+
+void function DebugTitanSpawnThread()
+{
+ entity player = GetPlayerArray()[0]
+
+ float interval = 0.1
+
+ FlightPath flightPath = GetAnalysisForModel( GetFlightPathModel( "fp_titan_model" ), HOTDROP_TURBO_ANIM )
+ int dataIndex = GetAnalysisDataIndex( flightPath )
+
+ for ( ;; )
+ {
+ if ( !IsValid( player ) )
+ {
+ wait interval
+ continue
+ }
+
+ vector playerOrg = player.GetOrigin()
+ vector playerEyeForward = player.GetViewVector()
+ vector playerEyePos = player.EyePosition()
+ vector playerEyeAngles = player.EyeAngles()
+ float yaw = playerEyeAngles.y
+ vector ornull desiredPos = GetReplacementTrace( playerEyePos, playerEyeForward )
+ vector pathNodeSearchPos
+ if ( desiredPos == null )
+ {
+ pathNodeSearchPos = GetPathNodeSearchPos( playerOrg, playerEyePos, playerEyeForward, true )
+ }
+ else
+ {
+ expect vector( desiredPos )
+ DebugDrawCircle( desiredPos, Vector(0,0,0), 10, 128, 255, 128, true, interval )
+ DebugDrawText( desiredPos + Vector(0,0,60), "Looking here", false, interval )
+ pathNodeSearchPos = GetPathNodeSearchPosWithLookPos( playerOrg, playerEyePos, playerEyeForward, desiredPos, true )
+ }
+
+ DebugDrawCircle( pathNodeSearchPos, Vector(0,0,0), 10, 128, 128, 255, true, interval )
+ DebugDrawText( pathNodeSearchPos + Vector(0,0,40), "Searching from here", false, interval )
+
+ DebugDrawLine( playerOrg, playerOrg + AnglesToForward( Vector( 0, yaw - TITANDROP_MIN_FOV, 0 ) ) * 500, 200, 200, 200, true, interval )
+ DebugDrawLine( playerOrg, playerOrg + AnglesToForward( Vector( 0, yaw + TITANDROP_MIN_FOV, 0 ) ) * 500, 200, 200, 200, true, interval )
+ DebugDrawLine( playerOrg, playerOrg + AnglesToForward( Vector( 0, yaw - TITANDROP_MAX_FOV, 0 ) ) * 500, 128, 128, 128, true, interval )
+ DebugDrawLine( playerOrg, playerOrg + AnglesToForward( Vector( 0, yaw + TITANDROP_MAX_FOV, 0 ) ) * 500, 128, 128, 128, true, interval )
+
+ int node = GetBestNodeForPosInWedge( pathNodeSearchPos, playerEyePos, yaw, TITANDROP_MIN_FOV, TITANDROP_MAX_FOV, TITANDROP_FOV_PENALTY, dataIndex, /*ANALYSIS_STEPS*/ 8 )
+
+ if ( node >= 0 )
+ {
+ Assert( NodeHasFlightPath( dataIndex, node ) )
+
+ vector pos = GetNodePos( node )
+ DebugDrawCircle( pos, Vector(0,0,0), 25, 255, 255, 128, true, interval )
+ DebugDrawText( pos + Vector(0,0,20), "Best node", false, interval )
+ }
+
+ Point actualResult = GetTitanReplacementPoint( player, true )
+ vector actualPos = actualResult.origin
+ DebugDrawCircle( actualPos, Vector(0,0,0), 32, 255, 255, 255, true, interval )
+ DebugDrawLine( actualPos, actualPos + AnglesToForward( actualResult.angles ) * 40, 255, 255, 255, true, interval )
+ DebugDrawText( actualPos, "Final location", false, interval )
+
+ wait interval
+ }
+}
+
+Point function GetTitanReplacementPoint( entity player, bool forDebugging = false )
+{
+ vector playerEyePos = player.EyePosition()
+ vector playerEyeAngles = player.EyeAngles()
+ vector playerOrg = player.GetOrigin()
+
+ return CalculateTitanReplacementPoint( playerOrg, playerEyePos, playerEyeAngles, forDebugging )
+}
+
+Point function CalculateTitanReplacementPoint( vector playerOrg, vector playerEyePos, vector playerEyeAngles, bool forDebugging = false )
+{
+ //local playerEyePos = Vector(-281.036224, 34.857925, 860.031250)
+ //local playerEyeAngles = Vector(60.055622, 80.775780, 0.000000)
+ //local playerOrg = Vector(-281.036224, 34.857925, 800.031250)
+
+ if ( !forDebugging )
+ printt( "Requested replacement Titan from eye pos " + playerEyePos + " view angles " + playerEyeAngles + " player origin " + playerOrg + " map " + GetMapName() )
+
+ vector playerEyeForward = AnglesToForward( playerEyeAngles )
+
+ // use the flightPath to find a position
+ FlightPath flightPath = GetAnalysisForModel( GetFlightPathModel( "fp_titan_model" ), HOTDROP_TURBO_ANIM )
+ int dataIndex = GetAnalysisDataIndex( flightPath )
+
+ var dropPoint
+ vector ornull traceOrigin = GetReplacementTrace( playerEyePos, playerEyeForward )
+ bool traceOriginIsNull = traceOrigin == null
+
+ if ( !traceOriginIsNull )
+ {
+ expect vector( traceOrigin )
+
+ dropPoint = TitanHulldropSpawnpoint( flightPath, traceOrigin, 0 )
+ if ( dropPoint != null && !NearTitanfallBlocker( dropPoint ) )
+ {
+ expect vector( dropPoint )
+ if ( EdgeTraceDropPoint( dropPoint ) )
+ {
+ if ( SafeForTitanFall( dropPoint ) && TitanTestDropPoint( dropPoint, flightPath ) )
+ {
+ vector yawVec = playerEyePos - dropPoint
+ vector yawAngles = VectorToAngles( yawVec )
+ yawAngles.x = 0
+ yawAngles.z = 0
+ // add some randomness
+ yawAngles.y += RandomFloatRange( -60, 60 )
+ if ( yawAngles.y < 0 )
+ yawAngles.y += 360
+ else if ( yawAngles.y > 360 )
+ yawAngles.y -= 360
+
+ Point point
+ point.origin = dropPoint
+ point.angles = yawAngles
+ return point
+ }
+ }
+ }
+ }
+
+ vector pathNodeSearchPos
+ if ( !traceOriginIsNull )
+ {
+ expect vector( traceOrigin )
+ pathNodeSearchPos = GetPathNodeSearchPosWithLookPos( playerOrg, playerEyePos, playerEyeForward, traceOrigin, false )
+ }
+ else
+ {
+ pathNodeSearchPos = GetPathNodeSearchPos( playerOrg, playerEyePos, playerEyeForward, false )
+ }
+
+ int node = GetBestNodeForPosInWedge( pathNodeSearchPos, playerEyePos, playerEyeAngles.y, TITANDROP_MIN_FOV, TITANDROP_MAX_FOV, TITANDROP_FOV_PENALTY, dataIndex, /*ANALYSIS_STEPS*/ 8 )
+
+ if ( node < 0 )
+ {
+ // This won't ever happen on a map with any reasonably placed path nodes.
+ entity spawner = FindSpawnpoint_ForReplacementTitan( playerOrg )
+ Assert( spawner )
+ Point point
+ point.origin = spawner.GetOrigin()
+ return point
+ }
+
+ Assert( NodeHasFlightPath( dataIndex, node ) )
+
+ vector nodeOrigin = GetNodePos( node )
+ vector dir = nodeOrigin - playerEyePos
+ vector angles = VectorToAngles( dir )
+ float yaw = angles.y + 180
+
+ if ( yaw < 0 )
+ yaw += 360
+ else if ( yaw > 360 )
+ yaw -= 360
+
+ var yawResult = GetSpawnPoint_ClosestYaw( node, dataIndex, yaw, 360.0 )
+ Assert( yawResult != null )
+ yaw = expect float( yawResult )
+ Assert( yaw >= 0 )
+ Assert( yaw <= 360 )
+
+ Point point
+ point.origin = nodeOrigin
+ point.angles = Vector( 0, yaw, 0 )
+ return point
+}
+
+vector function GetPathNodeSearchPosWithLookPos( vector playerOrg, vector playerEyePos, vector playerEyeForward, vector playerLookPos, bool debug )
+{
+ float dist2DSqr = Distance2DSqr( playerOrg, playerLookPos )
+ if ( dist2DSqr > (TITANDROP_PATHNODESEARCH_EXACTDIST / TITANDROP_PATHNODESEARCH_DISTFRAC) * (TITANDROP_PATHNODESEARCH_EXACTDIST / TITANDROP_PATHNODESEARCH_DISTFRAC) )
+ {
+ return playerOrg + (playerLookPos - playerOrg) * TITANDROP_PATHNODESEARCH_DISTFRAC
+ }
+ else if ( dist2DSqr > TITANDROP_PATHNODESEARCH_EXACTDIST * TITANDROP_PATHNODESEARCH_EXACTDIST )
+ {
+ vector dir = Normalize( playerLookPos - playerOrg )
+ return playerOrg + dir * TITANDROP_PATHNODESEARCH_EXACTDIST
+ }
+ else
+ {
+ return playerLookPos
+ }
+
+ unreachable
+}
+
+vector function GetPathNodeSearchPos( vector playerOrg, vector playerEyePos, vector playerEyeForward, bool debug )
+{
+ vector diagonallyDown = Normalize( <playerEyeForward.x, playerEyeForward.y, 0> )
+ diagonallyDown.z = TITANDROP_GROUNDSEARCH_ZDIR
+
+ vector startPos = playerEyePos + playerEyeForward * TITANDROP_GROUNDSEARCH_FORWARDDIST
+ vector endPos = startPos + diagonallyDown * TITANDROP_GROUNDSEARCH_DIST
+
+ TraceResults result = TraceLine( startPos, endPos, null, TRACE_MASK_SOLID_BRUSHONLY, TRACE_COLLISION_GROUP_NONE )
+
+ if ( debug )
+ {
+ DebugDrawLine( playerEyePos, startPos, 128,128,200, true, 0.1 )
+ DebugDrawLine( startPos, result.endPos, 128,128,200, true, 0.1 )
+ if ( result.fraction < 1 )
+ DebugDrawLine( result.endPos, result.endPos + playerEyeForward * TITANDROP_FALLBACK_DIST, 128,128,200, true, 0.1 )
+ }
+
+ if ( result.fraction < 1 )
+ return result.endPos + playerEyeForward * TITANDROP_FALLBACK_DIST
+
+ return playerEyePos + playerEyeForward * TITANDROP_FALLBACK_DIST
+}
+
+// Returns a position vector or null
+vector ornull function GetReplacementTrace( vector startPos, vector viewVector )
+{
+ float viewDirLen2D = Length2D( viewVector )
+ if ( viewDirLen2D < 0.1 )
+ viewDirLen2D = 0.1
+
+ vector endPos = startPos + ( viewVector * ( TITANDROP_LOS_DIST / viewDirLen2D ) )
+ int mask = TRACE_MASK_SOLID & (~CONTENTS_WINDOW)
+ TraceResults result = TraceLine( startPos, endPos, null, mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( result.endPos, endPos, 255, 0, 0, true, 20.0 )
+ //DebugDrawLine( startPos, result.endPos, 0, 255, 0, true, 20.0 )
+
+ if ( result.fraction == 1 )
+ return null
+
+ entity hitEnt = result.hitEnt
+ if ( IsValid( hitEnt ) && ( hitEnt.IsTitan() || hitEnt.IsPlayer() || hitEnt.IsNPC() ) )
+ {
+ endPos = OriginToGround( hitEnt.GetOrigin() )
+ }
+ else
+ {
+ endPos = result.endPos
+
+ if ( result.surfaceNormal.Dot( <0.0, 0.0, 1.0> ) < 0.7 )
+ {
+ //DebugDrawLine( endPos, Vector(0,0,0), 0, 200, 0, true, 5.0 )
+ // pull it back towards player
+ float titanRadius = GetBoundsMax( HULL_TITAN ).x * 1.2
+ endPos -= viewVector * titanRadius
+ endPos += result.surfaceNormal * titanRadius
+
+ endPos = OriginToGround( endPos )
+ }
+ }
+
+ vector ornull clampedEndPos = NavMesh_ClampPointForHullWithExtents( endPos, HULL_TITAN, <160.0, 160.0, 80.0> )
+
+ if ( !clampedEndPos )
+ return null
+
+ expect vector( clampedEndPos )
+
+ vector dir = clampedEndPos - startPos
+ if ( DotProduct2D( dir, viewVector ) < 0 )
+ return null
+
+ return clampedEndPos
+}
+
+var function HullTraceDropPoint( FlightPath flightPath, vector baseOrigin, float heightCapMax = 190 )
+{
+ float heightCapMin = -512
+ vector startOrigin = baseOrigin + Vector( 0,0,1000 )
+ vector endOrigin = baseOrigin + Vector( 0,0, heightCapMin )
+
+ int mask = flightPath.traceMask
+
+ TraceResults result = TraceHull( startOrigin, endOrigin, flightPath.mins, flightPath.maxs, null, mask, TRACE_COLLISION_GROUP_NONE )
+ //DebugDrawLine( startOrigin, result.endPos, 0, 255, 0, true, 5.0 )
+ //DebugDrawLine( result.endPos, endOrigin, 255, 0, 0, true, 5.0 )
+
+// DebugDrawLine( startOrigin, baseOrigin, 0, 255, 0, true, 5.0 )
+// DebugDrawLine( baseOrigin, endOrigin, 255, 0, 0, true, 5.0 )
+// local offset = Vector(0.15, 0.15, 0.0 )
+// DebugDrawLine( startOrigin + offset, result.endPos + offset, 0, 255, 0, true, 5.0 )
+// DebugDrawLine( result.endPos + offset, endOrigin + offset, 255, 0, 0, true, 5.0 )
+// DrawArrow( baseOrigin, Vector(0,0,0), 5.0, 50 )
+// DebugDrawLine( result.endPos, baseOrigin, 255, 255, 255, true, 4.5 )
+
+/*
+ printt( " " )
+ printt( "Hull drop " )
+ printt( "start " + startOrigin )
+ printt( "end " + endOrigin )
+ printt( "hit " + result.endPos )
+ printt( "mins " + flightPath.mins + " maxs " + flightPath.maxs )
+ printt( "mask " + mask )
+*/
+ if ( result.allSolid || result.startSolid || result.hitSky )
+ return null
+
+ if ( result.fraction == 0 || result.fraction == 1 )
+ return null
+
+ if ( fabs( result.endPos.z - baseOrigin.z ) > heightCapMax )
+ return null
+
+ return result.endPos
+}
+
+
+entity function FindSpawnpoint_ForReplacementTitan( vector origin )
+{
+ Assert( GetScriptManagedEntArrayLen( file.replacementSpawnpointsID ) > 0 )
+
+ array<entity> spawnpoints = GetScriptManagedEntArray( file.replacementSpawnpointsID )
+ entity selectedSpawnpoint = spawnpoints[0]
+
+ float closestDist = -1
+ foreach ( spawnpoint in spawnpoints )
+ {
+ if ( spawnpoint.e.spawnPointInUse )
+ continue
+ if ( spawnpoint.IsOccupied() )
+ continue
+
+ float dist = DistanceSqr( spawnpoint.GetOrigin(), origin )
+ if ( closestDist == -1 || dist < closestDist )
+ {
+ closestDist = dist
+ selectedSpawnpoint = spawnpoint
+ }
+
+ }
+
+ Assert( selectedSpawnpoint )
+ return selectedSpawnpoint
+}
+
+bool function TitanFindDropNodes( FlightPath flightPath, vector baseOrigin, float yaw )
+{
+// return TitanFindDropNodesReloadable( flightPath, baseOrigin, yaw )
+//}
+//function TitanFindDropNodesReloadable( flightPath, baseOrigin, yaw )
+//{
+ if ( NearTitanfallBlocker( baseOrigin ) )
+ return false
+
+ asset model = flightPath.model
+ string animation = flightPath.anim
+ //local flightPath = GetAnalysisForModel( model, animation )
+
+ vector origin = baseOrigin
+ vector angles = Vector(0,yaw,0)
+ //entity titan = CreatePropDynamic( model, origin, Vector(0,0,0) )
+ //entity titan = CreateNPCTitanFromSettings( "titan_atlas", TEAM_IMC, origin, angles )
+
+ entity titan = expect entity( level.ainTestTitan )
+
+ titan.SetModel( model )
+ titan.SetAngles( angles )
+ titan.SetOrigin( origin )
+
+ float impactTime = GetHotDropImpactTime( titan, animation )
+ Attachment result = titan.Anim_GetAttachmentAtTime( animation, "OFFSET", impactTime )
+ vector maxs = titan.GetBoundingMaxs()
+ vector mins = titan.GetBoundingMins()
+ int mask = titan.GetPhysicsSolidMask()
+ origin = ModifyOriginForDrop( origin, mins, maxs, result.position, mask )
+ titan.SetOrigin( origin )
+
+ // Don't use nodes on top of the roof in kodai
+ if ( GetMapName() == "mp_forwardbase_kodai" && origin.z > 1200 )
+ return false
+
+ if ( !TitanTestDropPoint( origin, flightPath ) )
+ return false
+
+ if ( !TitanCanStand( titan ) )
+ return false
+
+ if ( TitanHulldropSpawnpoint( flightPath, origin, 0 ) == null )
+ return false
+
+ if ( !EdgeTraceDropPoint( origin ) )
+ return false
+
+ return true
+}
+
+
+var function TitanHulldropSpawnpoint( FlightPath flightPath, vector origin, float _ )
+{
+ return HullTraceDropPoint( flightPath, origin, 20 )
+}
+
+
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_titan_commands.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_titan_commands.gnut
new file mode 100644
index 000000000..06232c08b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/_titan_commands.gnut
@@ -0,0 +1,49 @@
+untyped
+
+global function TitanCommands_Init
+
+
+function TitanCommands_Init()
+{
+ if ( GetCurrentPlaylistVarInt( "titan_move_command_enabled", 0 ) == 0 )
+ return
+
+ AddClientCommandCallback( "PrototypeOrderTitanMove", Prototype_OrderTitanMove )
+ RegisterSignal( "Prototype_TitanMove" )
+}
+
+bool function Prototype_OrderTitanMove( entity player, array<string> args )
+{
+ Assert( args.len() == 3 )
+ vector pos = Vector( args[0].tofloat(), args[1].tofloat(), args[2].tofloat() )
+
+ DebugDrawLine( pos, pos + Vector(0,0,500), 255, 0, 0, true, 5.0 )
+ entity titan = player.GetPetTitan()
+ if ( !IsAlive( titan ) )
+ return true
+
+ thread Prototype_TitanMove( player, titan, pos )
+
+ return true
+}
+
+void function Prototype_TitanMove( entity player, entity titan, vector origin )
+{
+ titan.Signal( "Prototype_TitanMove" )
+ titan.EndSignal( "Prototype_TitanMove" )
+ titan.EndSignal( "ChangedTitanMode" )
+ titan.EndSignal( "OnDeath" )
+ local mode = player.GetPetTitanMode()
+ if ( mode != eNPCTitanMode.STAY ) // assuming there are only 2 modes
+ {
+ player.SetPetTitanMode( eNPCTitanMode.STAY )
+ titan.DisableBehavior( "Follow" )
+ #if R1_VGUI_MINIMAP
+ titan.Minimap_SetBossPlayerMaterial( $"vgui/HUD/threathud_titan_friendlyself_guard" )
+ #endif
+
+ titan.AssaultSetOrigin( origin )
+ }
+
+ AssaultOrigin( titan, origin, 100 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_titan_health.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_titan_health.gnut
new file mode 100644
index 000000000..d600cb03b
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/_titan_health.gnut
@@ -0,0 +1,1072 @@
+global function TitanHealth_Init
+
+global function Titan_PlayerTookDamage
+global function Titan_NPCTookDamage
+
+global function GetShieldRegenTime
+global function GetShieldRegenDelay
+global function PlayerHasAutoEject
+global function SetTitanCoreTimer
+global function GetTitanCoreTimer
+
+global function AddCreditToTitanCoreBuilderForTitanDamageInflicted
+global function AddCreditToTitanCoreBuilderForTitanDamageReceived
+global function AddCreditToTitanCoreBuilderForDoomInflicted
+global function AddCreditToTitanCoreBuilderForDoomEntered
+global function AddCreditToTitanCoreBuilder
+
+global function TitanShieldRegenThink
+
+global function IsRodeoDamageFromBatteryPack
+global function IsKillshot
+
+global function DoomedHealthThink
+global function UndoomTitan
+global function RestoreTitan
+
+global const SIGNAL_TITAN_HEALTH_REGEN = "BeginTitanHealthRegen"
+global const SIGNAL_TITAN_SHIELD_REGEN = "BeginTitanShieldRegen"
+
+global const TITAN_HEALTH_REGEN_DELAY_MAX = 0.7 // 2.2
+
+#if MP
+// PROTO : Was 99, 49 is for test
+global const TITAN_REGEN_MIN_DAMAGE = 49
+global const TITAN_REGEN_MIN_DAMAGE_DELAY = 0.5
+#elseif SP
+global const TITAN_REGEN_MIN_DAMAGE = 70
+global const TITAN_REGEN_MIN_DAMAGE_DELAY = 0.5
+#endif
+
+// titan health system
+const TITAN_HEALTH_HISTORY_FALLOFF_START = 0 // how many seconds until shield begins to regen
+
+const float TITAN_HEALTH_HISTORY_FALLOFF_END = 4.0
+
+struct
+{
+ float earn_meter_titan_multiplier
+} file
+
+void function TitanHealth_Init()
+{
+ RegisterSignal( SIGNAL_TITAN_HEALTH_REGEN )
+ RegisterSignal( SIGNAL_TITAN_SHIELD_REGEN )
+ RegisterSignal( "Doomed" )
+ RegisterSignal( "TitanUnDoomed" )
+ RegisterSignal( "StopShieldRegen" )
+ RegisterSignal( "WeakTitanHealthInitialized" )
+
+ file.earn_meter_titan_multiplier = GetCurrentPlaylistVarFloat( "earn_meter_titan_multiplier", 1.0 )
+
+ if ( IsMenuLevel() )
+ return
+
+ HealthRegenInit()
+ AddSoulInitFunc( TitanShieldRegenThink ) //This runs even if playlist var titan_shield_regen is set to 0 because it also does stuff like give friendly Pilots protection with shield, etc
+ AddSoulDeathCallback( Titan_MonarchCleanup )
+}
+
+void function UndoomTitan( entity titan, int numSegments )
+{
+ entity soul = titan.GetTitanSoul()
+ string settings = GetSoulPlayerSettings( soul )
+
+ soul.DisableDoomed()
+ int maxHealth
+ int segmentHealth = GetSegmentHealthForTitan( titan )
+ if ( titan.IsNPC() )
+ {
+ maxHealth = int( GetPlayerSettingsFieldForClassName_Health( settings ) )
+ if ( titan.ai.titanSpawnLoadout.setFileMods.contains( "fd_health_upgrade" ) )
+ maxHealth += segmentHealth
+ if ( soul.soul.titanLoadout.setFileMods.contains( "core_health_upgrade" ) )
+ maxHealth += segmentHealth
+ }
+ else
+ {
+ maxHealth = int( titan.GetPlayerModHealth() )
+ }
+ titan.SetMaxHealth( maxHealth )
+ titan.SetHealth( segmentHealth * numSegments )
+ SetSoulBatteryCount( soul, numSegments )
+
+ titan.Signal( "TitanUnDoomed" )
+ UndoomTitan_Body( titan )
+ thread TitanShieldRegenThink( soul )
+}
+
+void function RestoreTitan( entity titan, float percent = 0.625 )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( soul.IsDoomed() )
+ UndoomTitan( titan, 1 )
+
+ soul.nextRegenTime = 0.0
+ soul.SetShieldHealth( soul.GetShieldHealthMax() )
+ int minHealth = int( titan.GetMaxHealth() * percent )
+ if ( titan.GetHealth() < minHealth )
+ {
+ titan.SetHealth( minHealth )
+ int segmentHealth = GetSegmentHealthForTitan( titan )
+ int segments = int( minHealth / float( segmentHealth ) )
+ SetSoulBatteryCount( soul, segments )
+ }
+}
+
+bool function IsRodeoDamage( entity soul, var damageInfo )
+{
+ entity titan = soul.GetTitan()
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !attacker.IsPlayer() )
+ {
+ entity rider = GetRodeoPilot( titan )
+ if ( rider == attacker )
+ return true
+ else
+ return false
+ }
+
+ if ( attacker.GetTitanSoulBeingRodeoed() != soul )
+ return false
+
+ return true
+}
+
+bool function IsCoopRodeoDamage( entity soul, var damageInfo )
+{
+ entity titan = soul.GetTitan()
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity rider = GetRodeoPilot( titan )
+ if ( rider == attacker )
+ return true
+ else
+ return false
+
+ unreachable
+}
+
+
+void function CheckRodeoRiderHitsTitan( entity soul, var damageInfo )
+{
+ if ( IsRodeoDamage( soul, damageInfo ) )
+ {
+ //Set Last Attack Time so warning is triggered
+ soul.SetLastRodeoHitTime( Time() )
+
+ DamageInfo_AddCustomDamageType( damageInfo, DF_RODEO )
+ }
+}
+
+bool function ShouldMultiplyRodeoDamage( var damageInfo )
+{
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ case eDamageSourceId.mp_weapon_smr:
+ case eDamageSourceId.mp_titanability_smoke:
+ return false
+
+ case eDamageSourceId.mp_weapon_defender :
+ return true
+ }
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_EXPLOSION )
+ return false
+
+ return true
+}
+
+bool function IsRodeoDamageFromBatteryPack( entity soul, var damageInfo )
+{
+ if ( !IsRodeoDamage( soul, damageInfo ) )
+ return false
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) != damageTypes.rodeoBatteryRemoval )
+ return false
+
+ return true
+}
+
+
+int function ShieldHealthUpdate( entity titan, var damageInfo, bool critHit )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( DamageInfo_GetForceKill( damageInfo ) )
+ {
+ soul.SetShieldHealth( 0 )
+ return 0
+ }
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_BYPASS_SHIELD )
+ return 0
+
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+
+ Assert( soul == titan.GetTitanSoul() )
+ int shieldHealth = soul.GetShieldHealth()
+
+ if ( soul.e.forcedRegenTime <= Time() )
+ soul.nextRegenTime = CalculateNextRegenTime( damage, damageType, critHit, expect float( soul.nextRegenTime ), GetShieldRegenDelay( soul ) )
+
+ int result = 0
+ if ( shieldHealth )
+ {
+ DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )
+ result = int( ShieldModifyDamage( titan, damageInfo ) )
+ }
+ else
+ {
+ TakeAwayFriendlyRodeoPlayerProtection( titan )
+ }
+
+ return result
+}
+
+
+void function PlayerOrNPCTitanTookDamage( entity titan, var damageInfo, bool critHit, TitanDamage titanDamage )
+{
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) ) //Defensive fix for transient times in frame where Titan can have no soul but be damaged, e.g. immediately after embark
+ return
+
+ // zero out small forces
+ if ( LengthSqr( DamageInfo_GetDamageForce( damageInfo ) ) < 30000 * 30000 )
+ DamageInfo_SetDamageForce( damageInfo, < 0, 0, 0 > )
+
+ titanDamage.shieldDamage = CheckSpecialCaseShieldDamage( soul, titan, damageInfo )
+ if ( titanDamage.shieldDamage < 0 )
+ {
+ CheckRodeoRiderHitsTitan( soul, damageInfo )
+ titanDamage.shieldDamage = ShieldHealthUpdate( titan, damageInfo, critHit )
+ }
+
+ HandleKillshot( titan, damageInfo, titanDamage )
+
+ // health regen based on how much damage dealt to titan
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ bool rodeoDamage = ( ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_RODEO ) > 0 )
+
+ if ( soul.e.forcedRegenTime <= Time() )
+ soul.nextHealthRegenTime = CalculateNextRegenTime( damage, damageType, critHit || rodeoDamage, expect float( soul.nextHealthRegenTime ), GetHealthRegenDelay( soul ) )
+}
+
+int function CheckSpecialCaseShieldDamage( entity soul, entity titan, var damageInfo )
+{
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == damagedef_suicide )
+ return 0
+
+ // no protection from doomed health loss
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return 0
+
+ if ( IsTitanWithinBubbleShield( titan ) || TitanHasBubbleShieldWeapon( titan ) )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return 0
+ }
+
+ return -1
+}
+
+void function Titan_NPCTookDamage( entity titan, var damageInfo, TitanDamage titanDamage )
+{
+ Assert( titan.IsTitan() )
+ Assert( DamageInfo_GetDamage( damageInfo ) > 0 )
+
+ // dead entities can take damage
+ if ( !IsAlive( titan ) )
+ return
+
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) ) //Defensive fix for transient times in frame where Titan can have no soul but be damaged, e.g. immediately after embark
+ return
+
+ bool critHit = false
+ if ( CritWeaponInDamageInfo( damageInfo ) )
+ critHit = IsCriticalHit( DamageInfo_GetAttacker( damageInfo ), titan, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) )
+
+ if ( critHit )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( HeavyArmorCriticalHitRequired( damageInfo ) && CritWeaponInDamageInfo( damageInfo ) && !critHit && IsValid( attacker ) && !attacker.IsTitan())
+ {
+ float shieldHealth = float( titan.GetTitanSoul().GetShieldHealth() )
+ float damage = DamageInfo_GetDamage( damageInfo )
+ if ( shieldHealth - damage <= 0 )
+ {
+ if ( shieldHealth > 0 )
+ DamageInfo_SetDamage( damageInfo, shieldHealth )
+ else
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+ }
+
+ PlayerOrNPCTitanTookDamage( titan, damageInfo, critHit, titanDamage )
+
+ RecordDamageToNPCTitanSoul( soul, damageInfo )
+
+ entity owner = GetPetTitanOwner( titan )
+ if ( IsValid( owner ) )
+ AutoTitan_TryMultipleTitanCallout( titan, damageInfo )
+
+ if ( GetDoomedState( titan ) )
+ titanDamage.shieldDamage = 0
+}
+
+void function Titan_PlayerTookDamage( entity player, var damageInfo, entity attacker, bool critHit, TitanDamage titanDamage )
+{
+ Assert( player.IsTitan() )
+
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ if ( !IsAlive( player ) )
+ return
+
+ entity soul = player.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Defensive fix for transient times in frame where Titan can have no soul but be damaged, e.g. immediately after embark
+ return
+
+ if ( damage > 0 )
+ AdjustVelocityFromHit( player, damageInfo, attacker, damage, critHit )
+
+ if ( IsDemigod( player ) )
+ EntityDemigod_TryAdjustDamageInfo( player, damageInfo )
+
+ bool critHit = false
+ if ( CritWeaponInDamageInfo( damageInfo ) )
+ critHit = IsCriticalHit( attacker, player, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) )
+
+ if ( critHit )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+
+ #if MP
+ if ( HeavyArmorCriticalHitRequired( damageInfo ) && CritWeaponInDamageInfo( damageInfo ) && !critHit && IsValid( attacker ) && !attacker.IsTitan())
+ {
+ float shieldHealth = float( player.GetTitanSoul().GetShieldHealth() )
+ if ( shieldHealth - damage <= 0 )
+ {
+ if ( shieldHealth > 0 )
+ DamageInfo_SetDamage( damageInfo, shieldHealth )
+ else
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+ }
+ #endif
+
+ PlayerOrNPCTitanTookDamage( player, damageInfo, critHit, titanDamage )
+}
+
+bool function IsKillshot( entity ent, var damageInfo, entity titanSoul )
+{
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int health = ent.GetHealth()
+
+ if ( health - damage > DOOMED_MIN_HEALTH )
+ return false
+
+ return true
+}
+
+bool function ShouldDoomTitan( entity ent, var damageInfo )
+{
+ if ( DoomStateDisabled() )
+ return false
+
+ if ( GetDoomedState( ent ) )
+ return false
+
+ if ( DamageInfo_GetForceKill( damageInfo ) )
+ return false
+
+ float doomedHealth = GetTitanSoulDoomedHealth( ent.GetTitanSoul() )
+ if ( doomedHealth <= 0 )
+ return false
+
+ entity soul = ent.GetTitanSoul()
+ if ( soul.soul.skipDoomState )
+ return false
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_SKIP_DAMAGE_PROT )
+ return doomedHealth > ( DamageInfo_GetDamage( damageInfo ) - ent.GetHealth() )
+
+ bool skipDoom = ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_SKIPS_DOOMED_STATE ) > 0
+ return !skipDoom
+}
+
+bool function HandleKillshot( entity ent, var damageInfo, TitanDamage titanDamage )
+{
+ #if NPC_TITAN_PILOT_PROTOTYPE
+ if ( TitanHasNpcPilot( ent ) ) //an npc titan that was dropped by an npc human
+ {
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int health = ent.GetHealth()
+
+ if ( health - damage <= 0 )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ thread TitanEjectPlayer( ent )
+ }
+
+ return
+ }
+ #endif
+
+ if ( ent.IsPlayer() && ent.IsBuddhaMode() )
+ return false
+
+ entity titanSoul = ent.GetTitanSoul()
+
+ if ( IsKillshot( ent, damageInfo, titanSoul ) )
+ {
+ entity boss = titanSoul.GetBossPlayer()
+ Soul_SetLastAttackInfo( titanSoul, damageInfo )
+
+ if ( ShouldDoomTitan( ent, damageInfo ) )
+ {
+ // Added via AddCallback_OnTitanDoomed
+ foreach ( callbackFunc in svGlobal.onTitanDoomedCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+
+ if ( IsMultiplayer() )
+ {
+ entity attacker = expect entity( expect table( titanSoul.lastAttackInfo ).attacker )
+ if ( IsValid( attacker ) )
+ {
+ entity bossPlayer = attacker.GetBossPlayer()
+ if ( attacker.IsNPC() && IsValid( bossPlayer ) )
+ attacker = bossPlayer
+
+ if ( attacker.IsPlayer() )
+ ScoreEvent_TitanDoomed( ent, attacker, damageInfo )
+ }
+ }
+
+ thread DoomedHealthThink( titanSoul, damageInfo )
+
+ titanDamage.doomedNow = true
+ titanDamage.doomedDamage = int( DamageInfo_GetDamage( damageInfo ) )
+
+ int health = ent.GetHealth()
+ DamageInfo_SetDamage( damageInfo, health - 1 )
+ return true
+ }
+ else
+ {
+ // handle auto eject here
+ if ( ent.IsPlayer() && PlayerHasAutoEject( ent ) )
+ {
+ int health = ent.GetHealth()
+ DamageInfo_SetDamage( damageInfo, health - 1 )
+ thread HandleAutoEject( ent, titanSoul )
+ return false
+ }
+ }
+ }
+
+ // Handle doom state damage
+ if ( GetDoomedState( ent ) )
+ {
+ // as long as we're dying but not yet ejecting, the last player to damage us gets credit
+ if ( titanSoul.IsEjecting() )
+ {
+ Soul_SetLastAttackInfo( titanSoul, damageInfo )
+ }
+ else if ( ent.IsPlayer() && PlayerHasAutoEject( ent ) ) //Handle auto eject for when the frame in which Titan became doomed was not valid for ejecting, e.g. melee
+ {
+ int health = ent.GetHealth()
+ DamageInfo_SetDamage( damageInfo, health - 1 )
+ thread HandleAutoEject( ent, titanSoul )
+ return false
+ }
+
+ // protect players who eject early
+ // if ( ent.IsPlayer() && IsEjectProtected( ent, damageInfo ) )
+ // DamageInfo_SetDamage( damageInfo, 0 )
+
+ // slight protection to prevent multiple rapid damage events from eating through doomed state health
+ if ( Time() - titanSoul.soul.doomedStartTime < TITAN_DOOMED_INVUL_TIME && !DamageInfo_GetForceKill( damageInfo ) )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+ else
+ {
+ Soul_SetLastAttackInfo( titanSoul, damageInfo )
+ }
+
+ return false
+}
+
+bool function PlayerHasAutoEject( entity player )
+{
+ if ( player.IsBot() )
+ return false
+
+ if ( !PlayerHasPassive( player, ePassives.PAS_AUTO_EJECT ) )
+ return false
+
+ return true
+}
+
+
+void function AdjustVelocityFromHit( entity player, var damageInfo, entity attacker, float damage, bool critHit )
+{
+/*
+ if ( DamageInfo_GetDamageCriticalHitScale( damageInfo ) > 1.0 )
+ {
+ // if you can crit, you have to crit!
+ if ( !critHit )
+ return
+ }
+*/
+
+ //printt( " " )
+ //printt( "damage: " + damage )
+
+ vector damageForward = DamageInfo_GetDamageForce( damageInfo )
+ damageForward.z = 0
+ //printt( "damageForward " + damageForward )
+
+ damageForward.Norm()
+
+ //vector org = DamageInfo_GetDamagePosition( damageInfo )
+ //DebugDrawLine( org, org + damageForward * 250, 255, 0, 0, true, 5.0 )
+
+ vector velocity = player.GetVelocity()
+ vector velForward = player.GetVelocity()
+ velForward.z = 0
+ velForward.Norm()
+
+ //DebugDrawLine( org, org + velForward * 250, 0, 255, 0, true, 5.0 )
+
+ float dot = DotProduct( velForward, damageForward )
+
+ // only stop from the ~front cone
+ if ( dot >= -0.5 )
+ return
+
+ float speedPercent
+
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ //case eDamageSourceId.mp_titanweapon_40mm:
+ // speedPercent = GraphCapped( damage, 0, 750, 1, 0 )
+ // break
+
+ case eDamageSourceId.mp_titanweapon_xo16:
+ speedPercent = 0.075
+ break
+
+ default:
+ speedPercent = GraphCapped( damage, 0, 2500, 0, 1.0 )
+ }
+
+ //float dif = GraphCapped( dot, -1, -0.5, 1, 0 )
+ //speedPercent = speedPercent * dif + ( 1.0 - dif )
+
+ speedPercent *= GraphCapped( dot, -1.0, -0.5, 1, 0 )
+
+ //printt( " " )
+ //printt( "Damage: " + damage )
+ //printt( "dot: " + dot )
+ //printt( "speedPercent: " + speedPercent )
+ speedPercent = 1.0 - speedPercent
+ // make the dot into a tighter range
+ //dot += 0.5
+ //dot *= -2.0
+
+ //printt( "modifier: " + ( speedPercent ) )
+ velocity *= ( speedPercent )
+ player.SetVelocity( velocity )
+}
+
+
+
+void function DoomedHealthThink( entity titanSoul, var damageInfo )
+{
+ Assert( expect table( titanSoul.lastAttackInfo ).attacker, "Player entered reserve health with no attacker" )
+
+ entity soulOwner = titanSoul.GetTitan()
+ Assert( IsValid( soulOwner ), "Invalid owner " + soulOwner )
+
+ titanSoul.soul.doomedStartTime = Time()
+
+ // kill any existing health regen thread
+ titanSoul.Signal( SIGNAL_TITAN_HEALTH_REGEN )
+ titanSoul.Signal( SIGNAL_TITAN_SHIELD_REGEN )
+
+ titanSoul.EndSignal( "OnDestroy" )
+ titanSoul.EndSignal( "OnTitanDeath" )
+
+ float tickRate = 0.15
+ float maxDoomedHealth = GetTitanSoulDoomedHealth( titanSoul )
+ float doomedHealth = maxDoomedHealth
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_SKIP_DAMAGE_PROT )
+ doomedHealth = min( doomedHealth + soulOwner.GetHealth() - DamageInfo_GetDamage( damageInfo ), doomedHealth )
+
+ float DPS = (doomedHealth / TITAN_DOOMED_MAX_DURATION )
+
+ titanSoul.EnableDoomed()
+ titanSoul.doomedTime = Time()
+ soulOwner.SetDoomed()
+ DoomTitan( soulOwner )
+ soulOwner.Signal( "Doomed" )
+ titanSoul.Signal( "Doomed" )
+
+ // allow the damage to go through before resetting the health, so that we get proper damage indicators, etc...
+ // this process should also be in code
+ WaitEndFrame()
+
+ // grab the soul owner again since there was a wait
+ soulOwner = titanSoul.GetTitan()
+ if ( !IsValid( soulOwner ) )
+ return
+
+ if ( PROTO_AlternateDoomedState() )
+ {
+ //printt( soulOwner.GetHealth() )
+ soulOwner.SetHealth( doomedHealth )
+ soulOwner.SetMaxHealth( maxDoomedHealth )
+ //soulOwner.SetHealthPerSegment( 0 )
+
+ soulOwner.ClearDoomed()
+
+ if ( soulOwner.IsPlayer() && PlayerHasAutoEject( soulOwner ) )
+ {
+ HandleAutoEject( soulOwner, titanSoul )
+ }
+ else
+ {
+ //If it's an auto-titan with auto-eject, this just instantly kills it.
+ var attacker = ( "attacker" in titanSoul.lastAttackInfo ) ? expect table( titanSoul.lastAttackInfo ).attacker : null
+ expect entity( attacker )
+ var inflictor = ( "inflictor" in titanSoul.lastAttackInfo ) ? expect table( titanSoul.lastAttackInfo ).inflictor : null
+ expect entity( inflictor )
+ var damageSource = ( "damageSourceId" in titanSoul.lastAttackInfo ) ? expect table( titanSoul.lastAttackInfo ).damageSourceId : -1
+ int damageFlags = expect int( expect table( titanSoul.lastAttackInfo ).scriptType )
+ if ( SoulHasPassive( titanSoul, ePassives.PAS_AUTO_EJECT ) )
+ {
+ int scriptDamageType = damageTypes.titanEjectExplosion | damageFlags
+ soulOwner.Die( attacker, inflictor, { scriptType = scriptDamageType, damageSourceId = damageSource } )
+ }
+ }
+ return
+ }
+ soulOwner.SetHealth( doomedHealth )
+ soulOwner.SetMaxHealth( maxDoomedHealth )
+ //soulOwner.SetHealthPerSegment( 0 )
+
+ string settings = GetSoulPlayerSettings( titanSoul )
+ float damageMod = 1.0
+ while ( true )
+ {
+ table lastAttackInfo = expect table( titanSoul.lastAttackInfo )
+
+ table extraDeathInfo = {}
+ extraDeathInfo.scriptType <- (DF_NO_INDICATOR | DF_DOOMED_HEALTH_LOSS)
+ if ( expect int( lastAttackInfo.scriptType ) & DF_BURN_CARD_WEAPON )
+ extraDeathInfo.scriptType = expect int( extraDeathInfo.scriptType ) | DF_BURN_CARD_WEAPON
+ if ( expect int( lastAttackInfo.scriptType ) & DF_VORTEX_REFIRE )
+ extraDeathInfo.scriptType = expect int( extraDeathInfo.scriptType ) | DF_VORTEX_REFIRE
+
+ extraDeathInfo.damageSourceId <- lastAttackInfo.damageSourceId
+
+ entity soulOwner = titanSoul.GetTitan()
+ if ( !IsValid( soulOwner ) )
+ return
+ if ( soulOwner.IsPlayer() )
+ {
+ //if ( PlayerHasPassive( soulOwner, ePassives.PAS_DOOMED_TIME ) )
+ // damageMod = 0.4
+ //else
+ // damageMod = 1.0
+
+ if ( PlayerHasAutoEject( soulOwner ) )
+ {
+ //printt( "About to Auto Eject" )
+ // do it in the loop cause player could somehow get in a titan in doomed state
+ HandleAutoEject( soulOwner, titanSoul )
+ }
+ }
+
+ float dmgAmount = DPS * tickRate * damageMod
+
+ soulOwner.TakeDamage( dmgAmount, expect entity( lastAttackInfo.attacker ), expect entity( lastAttackInfo.inflictor ), extraDeathInfo )
+
+ wait tickRate
+ }
+}
+
+void function HandleAutoEject( entity rider, entity soul )
+{
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+
+ thread TitanEjectPlayer( rider )
+ if ( soul.IsEjecting() )
+ {
+ // so we don't cloak the titan during the ejection animation
+ if ( GetNuclearPayload( rider ) > 0 )
+ wait 2.0
+ else
+ wait 1.0
+
+ EnableCloak( rider, 7.0 )
+ return
+ }
+}
+
+void function TitanShieldRegenThink( entity soul )
+{
+ thread TitanShieldRegenThink_Internal( soul )
+}
+
+// HACK: this technically doesn't work properly because server framerate and all that jazz. Should really be in code.
+void function TitanShieldRegenThink_Internal( entity soul )
+{
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "Doomed" )
+ soul.EndSignal( "StopShieldRegen" )
+
+ //Shield starts at 0 health for now
+ string settings = GetSoulPlayerSettings( soul )
+ bool hasShield = Dev_GetPlayerSettingByKeyField_Global( settings, "start_with_shields" ) == 1
+
+ if ( !hasShield )
+ soul.SetShieldHealth( 0 )
+
+ int lastShieldHealth = soul.GetShieldHealth()
+ bool shieldHealthSound = false
+ int maxShield = soul.GetShieldHealthMax()
+ float lastTime = Time()
+
+ while ( true )
+ {
+ entity titan = soul.GetTitan()
+ if ( !IsValid( titan ) )
+ return
+
+ int shieldHealth = soul.GetShieldHealth()
+ Assert( titan )
+
+ if ( lastShieldHealth <= 0 && shieldHealth && titan.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( titan, titan, "titan_energyshield_up_1P" )
+ shieldHealthSound = true
+ if ( titan.IsTitan() )
+ {
+ GiveFriendlyRodeoPlayerProtection( titan )
+ }
+ else
+ {
+ if ( titan.IsPlayer() )
+ {
+ printt( "Player was " + titan.GetPlayerSettings() )
+ }
+
+ printt( "ERROR! Expected Titan, but got " + titan )
+ }
+ }
+ else if ( shieldHealthSound && shieldHealth == soul.GetShieldHealthMax() )
+ {
+ shieldHealthSound = false
+ }
+ else if ( lastShieldHealth > shieldHealth && shieldHealthSound )
+ {
+ StopSoundOnEntity( titan, "titan_energyshield_up_1P" )
+ shieldHealthSound = false
+ }
+
+ if ( Time() >= soul.nextRegenTime && TitanHasRegenningShield( soul ) )
+ {
+ float shieldRegenRate = maxShield / ( GetShieldRegenTime( soul ) / SHIELD_REGEN_TICK_TIME )
+
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_BOOST ) )
+ shieldRegenRate = SHIELD_BEACON_REGEN_RATE
+
+ float frameTime = max( 0.0, Time() - lastTime )
+ shieldRegenRate = shieldRegenRate * frameTime / SHIELD_REGEN_TICK_TIME
+ // Faster shield recharge if we have Fusion Core active ability ( Stryder Signature )
+ //if ( titan.IsPlayer() && PlayerHasPassive( titan, ePassives.PAS_FUSION_CORE ) )
+ // shieldRegenRate *= 1.25
+
+ soul.SetShieldHealth( minint( soul.GetShieldHealthMax(), int( shieldHealth + shieldRegenRate ) ) )
+ }
+
+ lastShieldHealth = shieldHealth
+ lastTime = Time()
+ WaitFrame()
+ }
+}
+
+float function GetShieldRegenTime( entity soul )
+{
+ float time
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_REGEN ) )
+ time = TITAN_SHIELD_REGEN_TIME * 0.5
+ else
+ time = TITAN_SHIELD_REGEN_TIME
+
+ return time
+}
+
+float function GetHealthRegenDelay( entity soul )
+{
+ if ( GetDoomedState( soul.GetTitan() ) )
+ return TITAN_DOOMED_REGEN_DELAY
+
+ return GetShieldRegenDelay( soul )
+}
+
+float function GetShieldRegenDelay( entity soul )
+{
+ float regenDelay = TITAN_SHIELD_REGEN_DELAY
+
+ string settings = GetSoulPlayerSettings( soul )
+ regenDelay = expect float( Dev_GetPlayerSettingByKeyField_Global( settings, "titan_regen_delay" ) )
+
+ float delay
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_REGEN ) )
+ delay = regenDelay - 1.0
+ else
+ delay = regenDelay
+
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_BOOST ) )
+ delay = 2.0
+
+ return delay
+}
+
+void function RecordDamageToNPCTitanSoul( entity soul, var damageInfo )
+{
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ vector inflictOrigin = <0.0,0.0,0.0>
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ if ( IsValid( inflictor ) )
+ inflictOrigin = inflictor.GetOrigin()
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ entity weapon = DamageInfo_GetWeapon( damageInfo )
+ array<string> weaponMods
+ if ( IsValid( weapon ) )
+ weaponMods = weapon.GetMods()
+
+ StoreDamageHistoryAndUpdate( soul, TITAN_HEALTH_HISTORY_FALLOFF_END, damage, inflictOrigin, DamageInfo_GetCustomDamageType( damageInfo ), DamageInfo_GetDamageSourceIdentifier( damageInfo ), attacker, weaponMods )
+}
+
+void function AutoTitan_TryMultipleTitanCallout( entity titan, var damageInfo )
+{
+ array<entity> titans = GetTitansHitMeInTime( titan.GetTitanSoul(), 5 )
+ entity enemy = titan.GetEnemy()
+ if ( IsAlive( enemy ) && enemy.IsTitan() && !titans.contains( enemy ) )
+ titans.append( enemy )
+
+ int totalEngagedTitans = titans.len()
+
+ if ( totalEngagedTitans == 1 )
+ PlayAutoTitanConversation( titan, "autoEngageTitan" )
+ else if ( totalEngagedTitans > 1 )
+ PlayAutoTitanConversation( titan, "autoEngageTitans" )
+}
+
+float function CalculateNextRegenTime( float damage, int damageType, bool critHit, float oldNextRegenTime, float maxRegenDelay )
+{
+ if ( damage >= TITAN_REGEN_MIN_DAMAGE || critHit || damageType & DF_STOPS_TITAN_REGEN )
+ {
+ if ( PROTO_VariableRegenDelay() )
+ {
+ // regen delay based on damage dealt
+ float minRegenDelay = 1.0
+ float regenDelay = GraphCapped( damage, 100, 1000, minRegenDelay, maxRegenDelay )
+
+ float nextRegenTime = oldNextRegenTime
+ float delayBasedOnCurrentTime = Time() + regenDelay
+ float delayBasedOnPreviousDelay = nextRegenTime + regenDelay
+ maxRegenDelay = Time() + maxRegenDelay
+
+ delayBasedOnCurrentTime = min( delayBasedOnCurrentTime, maxRegenDelay )
+ delayBasedOnPreviousDelay = min( delayBasedOnPreviousDelay, maxRegenDelay )
+ nextRegenTime = max( delayBasedOnCurrentTime, delayBasedOnPreviousDelay )
+
+ return nextRegenTime
+ }
+ else
+ {
+ // old style
+ return Time() + maxRegenDelay
+ }
+ }
+ else
+ {
+ float addTime = TITAN_REGEN_MIN_DAMAGE_DELAY
+
+ if ( oldNextRegenTime <= Time() + addTime )
+ return Time() + addTime
+ }
+
+ return oldNextRegenTime
+}
+
+void function AddCreditToTitanCoreBuilderForTitanDamageInflicted( entity titanAttacker, float damage )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ float rateRaw = CORE_BUILD_PERCENT_FROM_TITAN_DAMAGE_INFLICTED
+ float rate = (rateRaw * 0.01)
+ float credit = (rate * damage)
+ if ( credit > 0.0 )
+ AddCreditToTitanCoreBuilder( titanAttacker, credit )
+}
+
+void function AddCreditToTitanCoreBuilderForTitanDamageReceived( entity titanVictim, float damage )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ float rateRaw = CORE_BUILD_PERCENT_FROM_TITAN_DAMAGE_RECEIVED
+ float rate = (rateRaw * 0.01)
+ float credit = (rate * damage)
+ if ( credit > 0.0 )
+ AddCreditToTitanCoreBuilder( titanVictim, credit )
+}
+
+void function AddCreditToTitanCoreBuilderForDoomInflicted( entity titanAttacker )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ float valueRaw = CORE_BUILD_PERCENT_FROM_DOOM_INFLICTED
+ float credit = (valueRaw * 0.01)
+ if ( credit > 0.0 )
+ AddCreditToTitanCoreBuilder( titanAttacker, credit )
+}
+
+void function AddCreditToTitanCoreBuilderForDoomEntered( entity titanVictim )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ float valueRaw = CORE_BUILD_PERCENT_FROM_DOOM_ENTERED
+ float credit = (valueRaw * 0.01)
+ if ( credit > 0.0 )
+ AddCreditToTitanCoreBuilder( titanVictim, credit )
+}
+
+void function AddCreditToTitanCoreBuilder( entity titan, float credit )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return
+
+ entity bossPlayer = soul.GetBossPlayer()
+
+ if ( titan.IsPlayer() )
+ {
+ if ( !IsValid( bossPlayer ) )
+ return
+
+ if ( bossPlayer.IsTitan() && TitanCoreInUse( bossPlayer ) )
+ return
+ }
+ else
+ {
+ Assert( titan.IsNPC() )
+ if ( TitanCoreInUse( titan ) )
+ return
+ }
+
+ if ( !IsAlive( titan ) )
+ return
+
+ if ( SoulHasPassive( soul, ePassives.PAS_VANGUARD_COREMETER ) )
+ credit *= 1.10
+
+ credit *= file.earn_meter_titan_multiplier
+ #if MP
+ if ( titan.IsPlayer() )
+ {
+ float coreModifier = titan.GetPlayerNetFloat( "coreMeterModifier" )
+ if ( coreModifier >= 0.5 )
+ credit *= FD_HOT_STREAK_MULTIPLIER
+ }
+ #endif
+
+ bool coreWasAvailable = false
+
+ if ( IsValid( bossPlayer ) )
+ coreWasAvailable = IsCoreChargeAvailable( bossPlayer, soul )
+
+ float oldTotalCredit = SoulTitanCore_GetNextAvailableTime( soul )
+ float newTotalCredit = (credit + oldTotalCredit)
+ if ( newTotalCredit >= 0.998 ) //JFS - the rui has a +0.001 for showing the meter as full. This fixes the case where the core meter displays 100 but can't be fired.
+ newTotalCredit = 1.0
+ SoulTitanCore_SetNextAvailableTime( soul, newTotalCredit )
+
+ if ( IsValid( bossPlayer ) && !coreWasAvailable && IsCoreChargeAvailable( bossPlayer, soul ) )
+ {
+ AddPlayerScore( bossPlayer, "TitanCoreEarned" )
+ #if MP
+ UpdateTitanCoreEarnedStat( bossPlayer, titan )
+ PIN_PlayerAbilityReady( bossPlayer, "core" )
+ #endif
+ }
+
+ #if MP
+ if ( IsValid( bossPlayer ) )
+ JFS_PlayerEarnMeter_CoreRewardUpdate( titan, oldTotalCredit, newTotalCredit )
+ #endif
+
+ #if HAS_TITAN_TELEMETRY
+ if ( titan.IsPlayer() )
+ {
+ if ( IsCoreChargeAvailable( titan, soul ) )
+ {
+ TitanHints_TryShowHint( titan, [OFFHAND_EQUIPMENT] )
+ }
+ }
+ #endif
+}
+
+float function GetTitanCoreTimer( entity titan )
+{
+ Assert( titan.IsTitan() )
+ entity soul = titan.GetTitanSoul()
+ Assert( soul )
+
+ return SoulTitanCore_GetNextAvailableTime( soul ) - Time()
+}
+
+
+
+void function SetTitanCoreTimer( entity titan, float timeDiff )
+{
+ Assert( !TitanDamageRewardsTitanCoreTime() )
+
+ Assert( titan.IsTitan() )
+ entity soul = titan.GetTitanSoul()
+ Assert( soul )
+
+ float newTime = Time() + timeDiff
+ SoulTitanCore_SetNextAvailableTime( soul, max( Time() - 1, newTime ) )
+}
+
+
+void function Titan_MonarchCleanup( entity soul, var damageInfo )
+{
+ entity titan = soul.GetTitan()
+
+ if ( !IsValid( titan ) )
+ return
+
+ int statesIndex = titan.FindBodyGroup( "states" )
+ if ( statesIndex <= -1 )
+ return
+
+ titan.SetBodygroup( statesIndex, 2 )
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_titan_hints.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_titan_hints.gnut
new file mode 100644
index 000000000..0e8b4b5b4
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/_titan_hints.gnut
@@ -0,0 +1,267 @@
+global function TitanHints_Init
+global function TitanHints_NotifyUsedOffhand
+global function TitanHints_ResetThresholds
+global function TitanHints_TryShowHint
+global function TitanHints_ShowHint
+
+const float FIGHT_START_THRESHOLD = 10.0
+const float FIGHT_HINT_THRESHOLD = 8.0
+const float TITAN_HINT_COOLDOWN = 15.0
+
+struct
+{
+ float titanFightStartTime = -99
+ float lastDidDamageTime = -99
+ float lastTookDamageTime = -99
+ float lastShowHintTime = -99
+ float lastDodgeTime = -99
+ table<int,float> titanHintThresholds
+ table<int,float> titanHintThresholdAdd
+ table<int,float> lastShowHintTimes
+} file
+
+void function TitanHints_Init()
+{
+ AddDamageCallback( "player", TitanHint_Player_OnDamaged )
+ AddDamageCallback( "npc_titan", TitanHint_NPC_OnDamaged )
+ AddDamageCallback( "npc_super_spectre", TitanHint_NPC_OnDamaged )
+
+ file.titanHintThresholds[ TITAN_HINT_DASH ] <- 5.0
+ file.titanHintThresholds[ OFFHAND_ORDNANCE ] <- 5.0
+ file.titanHintThresholds[ OFFHAND_SPECIAL ] <- 5.0
+ file.titanHintThresholds[ OFFHAND_ANTIRODEO ] <- 10.0
+ file.titanHintThresholds[ OFFHAND_EQUIPMENT ] <- 1.0
+
+ file.lastShowHintTimes[ TITAN_HINT_DASH ] <- -99.0
+ file.lastShowHintTimes[ OFFHAND_ORDNANCE ] <- -99.0
+ file.lastShowHintTimes[ OFFHAND_SPECIAL ] <- -99.0
+ file.lastShowHintTimes[ OFFHAND_ANTIRODEO ] <- -99.0
+ file.lastShowHintTimes[ OFFHAND_EQUIPMENT ] <- -99.0
+
+ file.titanHintThresholdAdd[ TITAN_HINT_DASH ] <- 0
+ file.titanHintThresholdAdd[ OFFHAND_ORDNANCE ] <- 0
+ file.titanHintThresholdAdd[ OFFHAND_SPECIAL ] <- 0
+ file.titanHintThresholdAdd[ OFFHAND_ANTIRODEO ] <- 0
+ file.titanHintThresholdAdd[ OFFHAND_EQUIPMENT ] <- 0
+
+ AddCallback_OnPlayerInventoryChanged( TitanHints_ResetThresholds )
+ AddSpawnCallback( "player", PlayerDidLoad )
+}
+
+void function PlayerDidLoad( entity player )
+{
+ AddPlayerMovementEventCallback( player, ePlayerMovementEvents.DODGE, OnPlayerDodge )
+}
+
+void function TitanHint_Player_OnDamaged( entity player, var damageInfo )
+{
+ if ( !player.IsTitan() )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !attacker.IsTitan() && !IsSuperSpectre(attacker) )
+ return
+
+ if ( attacker.GetTeam() == player.GetTeam() )
+ return
+
+ TrySetFightTime()
+
+ file.lastTookDamageTime = Time()
+
+ array<int> hintsToShow = [ TITAN_HINT_DASH, OFFHAND_EQUIPMENT, OFFHAND_SPECIAL, OFFHAND_ORDNANCE, OFFHAND_ANTIRODEO ]
+
+ if ( GetDoomedState( player ) || GetTitanCurrentRegenTab( player ) < 2 )
+ hintsToShow = [ TITAN_HINT_DASH, OFFHAND_SPECIAL ]
+
+ TitanHints_TryShowHint( player, hintsToShow, attacker )
+}
+
+void function TitanHint_NPC_OnDamaged( entity victim, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !attacker.IsPlayer() )
+ return
+
+ if ( !attacker.IsTitan() )
+ return
+
+ TrySetFightTime()
+
+ file.lastDidDamageTime = Time()
+
+ TitanHints_TryShowHint( attacker, [ OFFHAND_EQUIPMENT, OFFHAND_ORDNANCE, OFFHAND_ANTIRODEO ], victim )
+}
+
+// reset thresholds
+void function TitanHints_ResetThresholds( entity player )
+{
+ if ( !player.IsTitan() )
+ return
+
+ foreach ( index, value in file.titanHintThresholdAdd )
+ {
+ if ( index != TITAN_HINT_DASH ) // don't reset dash
+ file.titanHintThresholdAdd[ index ] = 0.0
+ }
+}
+
+// increase threshold for hints every time the player uses it
+void function TitanHints_NotifyUsedOffhand( int index )
+{
+ // never increment for core
+ if ( index == OFFHAND_EQUIPMENT )
+ return
+
+ if ( index in file.titanHintThresholds )
+ {
+ file.titanHintThresholdAdd[ index ] += TITAN_HINT_COOLDOWN
+ }
+}
+
+bool function TrySetFightTime()
+{
+ if (
+ Time() - file.lastTookDamageTime > FIGHT_START_THRESHOLD &&
+ Time() - file.lastDidDamageTime > FIGHT_START_THRESHOLD
+ )
+ {
+ file.titanFightStartTime = Time()
+ return true
+ }
+
+ return false
+}
+
+void function TitanHints_TryShowHint( entity player, array<int> indexes, entity enemy = null )
+{
+ if ( GetConVarInt( "hud_setting_showTips" ) == 0 )
+ return
+
+ float fightDuration = Time() - file.titanFightStartTime
+ if ( fightDuration < FIGHT_HINT_THRESHOLD )
+ return
+
+ if ( TitanCoreInUse( player ) )
+ return
+
+ foreach ( idx in indexes )
+ {
+ float threshold = file.titanHintThresholds[idx] + file.titanHintThresholdAdd[idx]
+
+ // have we been fighting for a while?
+ if ( fightDuration < max( threshold, TITAN_HINT_COOLDOWN ) )
+ continue
+
+ // have we already shown this hint?
+ if ( Time() - file.lastShowHintTimes[idx] < max( threshold, TITAN_HINT_COOLDOWN ) )
+ continue
+
+ // have we already shown a hint?
+ if ( Time() - file.lastShowHintTime < TITAN_HINT_COOLDOWN )
+ continue
+
+ if ( idx != TITAN_HINT_DASH )
+ {
+ // when did you last use this ability?
+ if ( Time() - player.p.lastTitanOffhandUseTime[idx] < threshold )
+ continue
+
+ entity weapon = player.GetOffhandWeapon( idx )
+
+ if ( weapon == null )
+ continue
+
+ // has this ability been available for a while?
+ if ( weapon.GetNextAttackAllowedTime() + threshold > Time() )
+ continue
+
+ var requiresLocks = weapon.GetWeaponInfoFileKeyField( "requires_lock" )
+
+ if ( requiresLocks != null )
+ {
+ expect int( requiresLocks )
+ if ( requiresLocks == 1 )
+ {
+ if ( weapon.SmartAmmo_IsEnabled() && !SmartAmmo_CanWeaponBeFired( weapon ) )
+ continue
+ }
+ }
+
+
+ int curEnergyCost = weapon.GetWeaponCurrentEnergyCost()
+ if ( !player.CanUseSharedEnergy( curEnergyCost ) )
+ continue
+
+ if ( weapon.IsChargeWeapon() )
+ {
+ if ( weapon.GetWeaponChargeFraction() > 0.0 )
+ continue
+ }
+
+ if ( weapon.GetWeaponPrimaryClipCount() < weapon.GetWeaponSettingInt( eWeaponVar.ammo_min_to_fire ) )
+ continue
+
+ // special core check
+ if ( idx == OFFHAND_EQUIPMENT )
+ {
+ if( !CheckCoreAvailable( weapon ) )
+ continue
+ if ( IsConversationPlaying() )
+ continue
+ }
+
+ var hintType = weapon.GetWeaponInfoFileKeyField( "hint_type" )
+ if ( hintType != null )
+ {
+ if ( hintType == "range_toggle" )
+ {
+ if ( enemy != null )
+ {
+ float dist = Distance2D( enemy.GetOrigin(), player.GetOrigin() )
+
+ if ( weapon.HasMod( "ammo_swap_ranged_mode" ) )
+ { // has long range mode, will tell to swap to short range
+ if ( dist > 2500 )
+ {
+ continue
+ }
+ }
+ else
+ { // has short range mode, will tell to swap to long range
+ if ( dist < 1500 )
+ {
+ continue
+ }
+ }
+ }
+ }
+ }
+
+ }
+ else
+ {
+ if ( Time() - file.lastDodgeTime < threshold )
+ continue
+
+ // should check if dodge is available here, but we can't seem to do that
+ }
+
+ // show hint
+ TitanHints_ShowHint( player, idx )
+ break
+ }
+}
+
+void function TitanHints_ShowHint( entity player, int idx )
+{
+ Remote_CallFunction_Replay( player, "ServerCallback_ShowOffhandWeaponHint", idx )
+ file.lastShowHintTimes[idx] = Time()
+ file.lastShowHintTime = Time()
+}
+
+void function OnPlayerDodge( entity player )
+{
+ file.lastDodgeTime = Time()
+ file.titanHintThresholdAdd[ TITAN_HINT_DASH ] += TITAN_HINT_COOLDOWN
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_titan_hotdrop.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_titan_hotdrop.gnut
new file mode 100644
index 000000000..e3410de8a
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/_titan_hotdrop.gnut
@@ -0,0 +1,778 @@
+untyped
+
+global function TitanHotdrop_Init
+
+global function TitanHotDrop
+global function PlayersTitanHotdrops
+global function NPCTitanHotdrops
+global function NPCPrespawnWarpfallSequence
+global function WaitTillHotDropComplete
+global function OnTitanHotdropImpact
+global function PlayHotdropImpactFX
+global function TitanTestDropPoint
+global function EdgeTraceDropPoint
+
+
+global function GetHotDropImpactTime
+
+global function ModifyOriginForDrop
+
+global function NearTitanfallBlocker
+
+global function DevCheckInTitanfallBlocker
+
+global function DrawTitanfallBlockers
+
+global function DropPodFindDropNodes
+
+global function PlayDeathFromTitanFallSounds
+
+global const HOTDROP_FP_WARP = $"P_warpjump_FP"
+global const HOTDROP_TRAIL_FX = $"hotdrop_hld_warp"
+global int BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX
+
+function TitanHotdrop_Init()
+{
+
+ RegisterSignal( "titan_impact" )
+ RegisterSignal( "TitanHotDropComplete" )
+ RegisterSignal( "BubbleShieldStatusUpdate" )
+
+ PrecacheEffect( HOTDROP_TRAIL_FX )
+ PrecacheEffect( HOTDROP_FP_WARP )
+
+ AddDamageCallbackSourceID( damagedef_titan_fall, TitanFall_DamagedPlayerOrNPC )
+
+ PrecacheImpactEffectTable( HOTDROP_IMPACT_FX_TABLE )
+
+ PrecacheModel( $"models/fx/xo_shield.mdl" )
+ PrecacheModel( $"models/fx/xo_shield_wall.mdl" )
+ BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX = PrecacheParticleSystem( $"P_shield_hld_01_CP" )
+
+ BubbleShield_Init()
+}
+
+void function TitanHotDrop( entity titan, string animation, vector origin, vector angles, entity player, entity camera )
+{
+ Assert( titan.IsTitan(), titan + " is not a titan" )
+
+ titan.EndSignal( "OnDeath" )
+
+ HideName( titan )
+
+ array<entity> cleanup = [] // ents that will be deleted upon completion
+
+ OnThreadEnd(
+ function() : ( cleanup, titan, player, camera )
+ {
+ printt( "Post impact,anim is done" )
+ if ( IsValid( titan ) )
+ {
+ delete titan.s.hotDropPlayer
+ titan.e.isHotDropping = false
+ titan.Signal( "TitanHotDropComplete" )
+ if ( !IsFFAGame() )
+ titan.Minimap_DisplayDefault( titan.GetTeam(), null )
+ }
+
+ if ( IsValid( camera ) )
+ camera.ClearParent()
+
+ foreach ( entity ent in cleanup )
+ {
+ if ( IsValid_ThisFrame( ent ) )
+ {
+ // Delay enough seconds to allow titan hot drop smokeTrail FX to play fully
+ ent.Kill_Deprecated_UseDestroyInstead()
+ }
+ }
+
+ if ( IsValid( player ) )
+ ScreenFadeFromBlack( player, 0.2, 0.2 )
+ }
+ )
+
+ titan.s.hotDropPlayer <- player
+ titan.e.isHotDropping = true
+
+ origin += Vector(0,0,8 ) // work around for currently busted animation
+
+ entity ref = CreateScriptRef()
+ ref.SetOrigin( origin )
+ ref.SetAngles( angles )
+ ref.Show()
+ cleanup.append( ref )
+
+ // add smoke fx
+
+ TitanHotDrop_Smoke( cleanup, titan, titan.GetBossPlayer() )
+
+// "Titan_1P_Warpfall_Hotdrop" - for first person drops while inside the titan dropping into the level
+// "Titan_1P_Warpfall_Start" - for first person warp calls, starting right on the button press
+// "Titan_1P_Warpfall_WarpToLanding" - for first person from the visual of the titan appearing and falling
+// "Titan_3P_Warpfall_Start" - for any 3P other player or NPC when they call in a warp, starting right on their button press
+// "Titan_3P_Warpfall_WarpToLanding" - for any 3P other player or NPC from the visual of the titan appearing and falling
+ int teamNum = TEAM_UNASSIGNED
+ if ( IsValid( player ) )
+ teamNum = player.GetTeam();
+
+ EmitSoundAtPositionOnlyToPlayer( teamNum, origin, player, "Titan_1P_Warpfall_Hotdrop" )
+ EmitSoundAtPositionOnlyToPlayer( teamNum, origin, player, "Titan_1P_Warpfall_Start" )
+ EmitSoundAtPositionExceptToPlayer( teamNum, origin, player, "Titan_3P_Warpfall_Start" )
+ EmitSoundAtPositionExceptToPlayer( teamNum, origin, player, "Titan_3P_Warpfall_WarpToLanding" )
+
+ float duration = titan.GetSequenceDuration( animation )
+
+ Minimap_PingForTeam( titan.GetTeam(), origin, 64.0, duration, TEAM_COLOR_FRIENDLY / 255.0, 4, false )
+ if ( !IsFFAGame() )
+ titan.Minimap_Hide( titan.GetTeam(), null )
+
+ titan.NotSolid();
+ thread PlayAnimTeleport( titan, animation, ref )
+ titan.EndSignal( "OnAnimationDone" )
+
+ if ( player )
+ {
+ player.PlayerCone_SetMinYaw( -70 )
+ player.PlayerCone_SetMaxYaw( 70 )
+ player.PlayerCone_SetMinPitch( -90 )
+ player.PlayerCone_SetMaxPitch( 90 )
+ }
+
+ titan.WaitSignal( "titan_impact" )
+ player.ClearHotDropImpactTime()
+// wait duration - 1.25
+
+ titan.Solid();
+
+ ShowName( titan )
+
+ vector sourcePosition = origin
+ sourcePosition.z = sourcePosition.z + 5.0
+
+ Explosion_DamageDefSimple(
+ damagedef_titan_hotdrop,
+ origin,
+ titan, // attacker
+ titan, // inflictor
+ origin )
+
+ float zoomTime = 2.0
+ float rotateTime = 0.5
+
+ //printt( "Post impact, before anim is done" )
+
+ if ( IsValid( camera ) )
+ {
+ camera.ClearParent()
+
+ entity mover = CreateExpensiveScriptMover()
+ mover.SetOrigin( camera.GetOrigin() )
+ mover.SetAngles( camera.GetAngles() )
+ camera.SetParent( mover )
+
+ mover.NonPhysicsMoveTo( titan.GetWorldSpaceCenter(), zoomTime, zoomTime * 0.4, zoomTime * 0.4 )
+ cleanup.append( mover )
+
+ wait 0.5
+
+ ScreenFadeToBlackForever( player, 0.8 )
+
+ wait 0.6
+
+ mover.RotateTo( angles, rotateTime, rotateTime*0.2, rotateTime*0.2 )
+ }
+
+ WaittillAnimDone( titan )
+}
+
+entity function TitanHotDrop_Smoke( array<entity> cleanup, entity titan, entity player )
+{
+ entity smokeTrail = CreateEntity( "info_particle_system" )
+ if ( IsValid( player ) )
+ {
+ smokeTrail.SetOwner( player )
+ smokeTrail.kv.VisibilityFlags = ENTITY_VISIBLE_TO_OWNER
+ }
+
+ smokeTrail.SetValueForEffectNameKey( HOTDROP_TRAIL_FX ) // HOTDROP_FP_WARP
+ smokeTrail.kv.start_active = 1
+ DispatchSpawn( smokeTrail )
+ smokeTrail.SetParent( titan, "HATCH_HEAD" )
+ cleanup.append( smokeTrail )
+
+
+ smokeTrail = CreateEntity( "info_particle_system" )
+ if ( IsValid( player ) )
+ {
+ smokeTrail.SetOwner( player )
+ smokeTrail.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) //owner cant see
+ }
+
+ smokeTrail.SetValueForEffectNameKey( HOTDROP_TRAIL_FX ) // HOTDROP_FP_WARP
+ smokeTrail.kv.start_active = 1
+ DispatchSpawn( smokeTrail )
+ smokeTrail.SetParent( titan, "HATCH_HEAD" )
+ cleanup.append( smokeTrail )
+
+ return smokeTrail
+}
+
+void function PlayersTitanHotdrops( entity titan, vector origin, vector angles, entity player, string animation )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.s.disableAutoTitanConversation <- true // refactor: Should be created on spawn, and always exist -mackey
+
+ OnThreadEnd(
+ function() : ( titan, player )
+ {
+ if ( !IsValid( titan ) )
+ return
+
+ // removed so that model highlight always works for you autotitan
+// titan.DisableRenderAlways()
+
+ delete titan.s.hotDropPlayer
+ titan.e.isHotDropping = false
+ titan.Signal( "TitanHotDropComplete" )
+ DeleteAnimEvent( titan, "titan_impact" )
+ DeleteAnimEvent( titan, "second_stage" )
+ DeleteAnimEvent( titan, "set_usable" )
+ }
+ )
+
+ HideName( titan )
+ titan.s.hotDropPlayer <- player
+ titan.e.isHotDropping = true
+ titan.UnsetUsable() //Stop titan embark before it lands
+ AddAnimEvent( titan, "titan_impact", OnTitanHotdropImpact )
+ AddAnimEvent( titan, "second_stage", OnReplacementTitanSecondStage, origin )
+ AddAnimEvent( titan, "set_usable", SetTitanUsableByOwner )
+
+ string sfxFirstPerson
+ string sfxThirdPerson
+
+ switch ( animation )
+ {
+ case "at_hotdrop_drop_2knee_turbo_upgraded":
+ sfxFirstPerson = "Titan_1P_Warpfall_WarpToLanding_fast"
+ sfxThirdPerson = "Titan_3P_Warpfall_WarpToLanding_fast"
+ break
+
+ case "bt_hotdrop_skyway":
+ sfxFirstPerson = "titan_hot_drop_turbo_begin"
+ sfxThirdPerson = "titan_hot_drop_turbo_begin_3P"
+ break
+
+ case "at_hotdrop_drop_2knee_turbo":
+ sfxFirstPerson = "titan_hot_drop_turbo_begin"
+ sfxThirdPerson = "titan_hot_drop_turbo_begin_3P"
+ break
+
+ default:
+ Assert( 0, "Unknown anim " + animation )
+ }
+
+ float impactTime = GetHotDropImpactTime( titan, animation )
+ Attachment result = titan.Anim_GetAttachmentAtTime( animation, "OFFSET", impactTime )
+ vector maxs = titan.GetBoundingMaxs()
+ vector mins = titan.GetBoundingMins()
+ int mask = titan.GetPhysicsSolidMask()
+ origin = ModifyOriginForDrop( origin, mins, maxs, result.position, mask )
+
+ titan.SetInvulnerable() //Make Titan invulnerable until bubble shield is up. Cleared in OnTitanHotdropImpact
+
+ if ( SoulHasPassive( titan.GetTitanSoul(), ePassives.PAS_BUBBLESHIELD ) )
+ {
+ delaythread( impactTime ) CreateBubbleShield( titan, origin, angles )
+ }
+ else if ( SoulHasPassive( titan.GetTitanSoul(), ePassives.PAS_WARPFALL ) )
+ {
+ angles = AnglesCompose( angles, Vector( 0.0, 180.0, 0.0) )
+ }
+
+ //DrawArrow( origin, angles, 10, 150 )
+ // HACK: not really a hack, but this could be optimized to only render always for a given client
+ titan.EnableRenderAlways()
+
+ int teamNum = TEAM_UNASSIGNED
+ if ( IsValid( player ) )
+ teamNum = player.GetTeam()
+
+ EmitDifferentSoundsAtPositionForPlayerAndWorld( sfxFirstPerson, sfxThirdPerson, origin, player, teamNum )
+
+ SetStanceKneel( titan.GetTitanSoul() )
+
+ waitthread PlayAnimTeleport( titan, animation, origin, angles )
+
+ TitanCanStand( titan )
+ if ( !titan.GetCanStand() )
+ {
+ titan.SetOrigin( origin )
+ titan.SetAngles( angles )
+ }
+
+ titan.ClearInvulnerable() //Make Titan vulnerable again once he's landed
+
+ if ( !Flag( "DisableTitanKneelingEmbark" ) )
+ {
+ if ( IsValid( GetEmbarkPlayer( titan ) ) )
+ {
+ titan.SetTouchTriggers( true ) //Hack, potential fix for triggers bug. See bug 212751
+ //A player is trying to get in before the hotdrop animation has finished
+ //Wait until the embark animation has finished
+ WaittillAnimDone( titan )
+ return
+ }
+
+ titan.s.standQueued = false // SetStanceKneel should set this
+ SetStanceKneel( titan.GetTitanSoul() )
+ thread PlayAnim( titan, "at_MP_embark_idle_blended" )
+ }
+}
+
+float function GetHotDropImpactTime( entity titan, string animation )
+{
+ float impactTime = titan.GetScriptedAnimEventCycleFrac( animation, "titan_impact" )
+ if ( impactTime < 0.0 )
+ impactTime = titan.GetScriptedAnimEventCycleFrac( animation, "signal:titan_impact" )
+
+ Assert( impactTime > -1.0, "No event titan_impact in " + animation )
+
+ float duration = titan.GetSequenceDuration( animation )
+
+ impactTime *= duration
+
+ return impactTime
+}
+
+function NPCTitanHotdrops( entity titan, bool standImmediately, string titanfallAnim = "at_hotdrop_drop_2knee_turbo" )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ titan.e.isHotDropping = true
+ titan.s.bubbleShieldStatus <- 0
+
+ titan.SetEfficientMode( true )
+ titan.SetTouchTriggers( false )
+ titan.SetAimAssistAllowed( false )
+
+ float impactTime = GetHotDropImpactTime( titan, titanfallAnim )
+ vector origin = titan.GetOrigin()
+ vector angles = titan.GetAngles()
+
+ #if GRUNTCHATTER_ENABLED
+ GruntChatter_TryIncomingSpawn( titan, origin )
+ #endif
+
+ #if MP
+ TryAnnounceTitanfallWarningToEnemyTeam( titan.GetTeam(), origin )
+ #endif
+
+ if ( NPCShouldDoBubbleShieldAfterHotdrop( titan ) )
+ {
+ titan.SetNoTarget( true )
+ thread CreateGenericBubbleShield_Delayed( titan, origin, angles, impactTime - 0.1 )
+ }
+
+ waitthread PlayersTitanHotdrops( titan, origin, angles, null, titanfallAnim )
+
+ if ( standImmediately )
+ {
+ SetStanceStand( titan.GetTitanSoul() )
+ waitthread PlayAnimGravity( titan, "at_hotdrop_quickstand" )
+ }
+
+ titan.SetEfficientMode( false )
+ titan.SetTouchTriggers( true )
+ titan.SetAimAssistAllowed( true )
+
+ titan.e.isHotDropping = false
+ titan.Signal( "TitanHotDropComplete" )
+
+ titan.SetNoTarget( false )
+
+ while( titan.s.bubbleShieldStatus == 1 )
+ titan.WaitSignal( "BubbleShieldStatusUpdate" )
+}
+
+void function NPCPrespawnWarpfallSequence( string aiSettings, vector spawnOrigin, vector spawnAngle )
+{
+ string animation = "at_hotdrop_drop_2knee_turbo_upgraded"
+// string settings = GetTitanForPlayer( player ).titanSetFile
+ string playerSettings = expect string( Dev_GetAISettingByKeyField_Global( aiSettings, "npc_titan_player_settings" ) )
+ asset model = GetPlayerSettingsAssetForClassName( playerSettings, "bodymodel" )
+ Attachment warpAttach = GetAttachmentAtTimeFromModel( model, animation, "offset", spawnOrigin, spawnAngle, 0 )
+
+ entity fakeTitan = CreatePropDynamic( model )
+ float impactTime = GetHotDropImpactTime( fakeTitan, animation )
+
+ #if SP //MP AI already call DisableTitanfallForLifetimeOfEntityNearOrigin() in SpawnNeutralAI()/SpawnTeamAI() functions. Pretty sure can just remove this for SP too
+ thread TemporarilyDisableTitanfallAroundRadius( spawnOrigin, 72, WARPFALL_SOUND_DELAY + WARPFALL_FX_DELAY ) //TODO: Look into getting rid of this. Doesn't play well with DisableTitanfallForLifetimeOfEntityNearOrigin. Only used in Beacon
+ #endif
+
+ fakeTitan.Kill_Deprecated_UseDestroyInstead()
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, spawnOrigin, "Titan_3P_Warpfall_CallIn" )
+
+ wait WARPFALL_SOUND_DELAY
+
+ // "Titan_1P_Warpfall_Start" - for first person warp calls, starting right on the button press
+ // "Titan_3P_Warpfall_Start" - for any 3P other player or NPC when they call in a warp, starting right on their button press
+ EmitSoundAtPosition( TEAM_UNASSIGNED, spawnOrigin, "Titan_3P_Warpfall_Start" )
+
+ PlayFX( TURBO_WARP_FX, warpAttach.position + Vector(0,0,-104), warpAttach.angle )
+
+ wait WARPFALL_FX_DELAY
+}
+
+void function WaitTillHotDropComplete( entity titan )
+{
+ titan.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDestroy" )
+
+ // waits for him to drop in from the sky AND stand up
+ if ( titan.e.isHotDropping )
+ WaitSignal( titan, "TitanHotDropComplete" )
+}
+
+function CreateGenericBubbleShield_Delayed( entity titan, vector origin, vector angles, float delay = 0.0 )
+{
+ titan.EndSignal( "OnDestroy" )
+
+ if ( delay > 0 )
+ wait delay
+
+ titan.s.bubbleShieldStatus = 1
+ CreateGenericBubbleShield( titan, origin, angles )
+ titan.s.bubbleShieldStatus = 0
+ titan.Signal( "BubbleShieldStatusUpdate" )
+}
+
+
+vector function ModifyOriginForDrop( vector origin, vector mins, vector maxs, vector resultPos, int mask )
+{
+ TraceResults trace = TraceHull( resultPos + Vector(0,0,20), resultPos + Vector(0,0,-20), mins, maxs, null, mask, TRACE_COLLISION_GROUP_NONE )
+ float zDif = trace.endPos.z - resultPos.z
+ origin.z += zDif
+ origin.z += 3.0
+
+ return origin
+}
+
+void function OnReplacementTitanSecondStage( entity titan )
+{
+ vector origin = expect vector( GetOptionalAnimEventVar( titan, "second_stage" ) )
+
+ string sfxFirstPerson = "titan_drop_pod_turbo_landing"
+ string sfxThirdPerson = "titan_drop_pod_turbo_landing_3P"
+ entity player = titan.GetBossPlayer()
+ EmitDifferentSoundsAtPositionForPlayerAndWorld( sfxFirstPerson, sfxThirdPerson, origin, player, titan.GetTeam() )
+}
+
+void function OnTitanHotdropImpact( entity titan )
+{
+ ShowName( titan )
+ PlayHotdropImpactFX( titan )
+ titan.Signal( "ClearDisableTitanfall" )
+}
+
+function SetTitanUsable( titan )
+{
+ titan.SetUsableByGroup( "friendlies pilot" )
+}
+
+void function SetTitanUsableByOwner( entity titan )
+{
+ titan.SetUsableByGroup( "owner pilot" )
+}
+
+function PlayHotdropImpactFX( titan )
+{
+ expect entity( titan )
+ if ( !IsAlive( titan ) || !titan.IsTitan() )
+ return
+
+ local origin = titan.GetOrigin()
+
+ Explosion_DamageDefSimple(
+ damagedef_titan_fall,
+ origin,
+ titan, // attacker
+ titan, // inflictor
+ origin )
+
+
+ CreateShake( titan.GetOrigin(), 16, 150, 2, 1500 )
+ // No Damage - Only Force
+ // Push players
+ // Push radially - not as a sphere
+ // Test LOS before pushing
+ int flags = 15
+ vector impactOrigin = titan.GetOrigin() + Vector( 0,0,10 )
+ float impactRadius = 512
+ CreatePhysExplosion( impactOrigin, impactRadius, PHYS_EXPLOSION_LARGE, flags )
+}
+
+function NearTitanfallBlocker( baseOrigin )
+{
+ foreach ( hardpoint in level.testHardPoints )
+ {
+ local hpOrigin = hardpoint.GetOrigin()
+ hpOrigin.z -= 100 // why are hardpoints not really at the origin?
+ if ( Distance( hpOrigin, baseOrigin ) < SAFE_TITANFALL_DISTANCE )
+ return true
+ }
+
+ foreach ( flagSpawnPoint in level.testFlagSpawnPoints )
+ {
+ local fspOrigin = flagSpawnPoint.GetOrigin()
+ if ( Distance( fspOrigin, baseOrigin ) < SAFE_TITANFALL_DISTANCE_CTF )
+ return true
+ }
+
+ foreach ( blocker in level.titanfallBlockers )
+ {
+ if ( Distance2D( baseOrigin, blocker.origin ) > blocker.radius )
+ continue
+
+ if ( baseOrigin.z < blocker.origin.z )
+ continue
+
+ if ( baseOrigin.z > blocker.maxHeight )
+ continue
+
+ return true
+ }
+
+ return false
+}
+
+function DevCheckInTitanfallBlocker()
+{
+ if ( "toggleBlocker" in svGlobal.levelEnt.s )
+ {
+ svGlobal.levelEnt.s.toggleBlocker.Kill_Deprecated_UseDestroyInstead()
+ delete svGlobal.levelEnt.s.toggleBlocker
+ return
+ }
+
+ svGlobal.levelEnt.s.toggleBlocker <- CreateScriptRef()
+ svGlobal.levelEnt.s.toggleBlocker.EndSignal( "OnDestroy" )
+
+ entity player = GetPlayerArray()[0]
+ for ( ;; )
+ {
+ printt( "Inside Titanfall blocker: " + NearTitanfallBlocker( player.GetOrigin() ) )
+ DrawTitanfallBlockers()
+ wait 0.5
+ }
+}
+
+function DrawTitanfallBlockers()
+{
+ foreach ( hardpoint in level.testHardPoints )
+ {
+ vector hpOrigin = expect entity( hardpoint ).GetOrigin()
+ DebugDrawCircle( hpOrigin, Vector(0,0,0), SAFE_TITANFALL_DISTANCE, 255, 255, 0, true, 1.0 )
+ }
+
+ foreach ( flagSpawnPoint in level.testFlagSpawnPoints )
+ {
+ vector fspOrigin = expect entity( flagSpawnPoint ).GetOrigin()
+ DebugDrawCircle( fspOrigin, Vector(0,0,0), SAFE_TITANFALL_DISTANCE_CTF, 255, 255, 0, true, 1.0 )
+ }
+
+ foreach ( blocker in level.titanfallBlockers )
+ {
+ DebugDrawCircle( expect vector( blocker.origin ), Vector(0,0,0), expect float( blocker.radius ), 255, 255, 0, true, 1.0 )
+ vector org = Vector( blocker.origin.x, blocker.origin.y, blocker.maxHeight )
+ DebugDrawCircle( org, Vector(0,0,0), expect float( blocker.radius ), 255, 255, 0, true, 1.0 )
+ }
+}
+
+
+
+bool function EdgeTraceDropPoint( vector dropPoint )
+{
+ local offsetArray = [
+ Vector( 64,64,0 ),
+ Vector( -64,64,0 ),
+ Vector( 64,-64,0 ),
+ Vector( -64,-64,0 ),
+ ]
+ local maxDif = 48
+ local mask = TRACE_MASK_TITANSOLID | TRACE_MASK_PLAYERSOLID | TRACE_MASK_SOLID | TRACE_MASK_NPCSOLID
+ local totalDif = 0
+
+ foreach ( offset in offsetArray )
+ {
+ local startPos = dropPoint + Vector( 0, 0, 64 ) + offset
+ local endPos = dropPoint + Vector( 0, 0, -64 ) + offset
+ TraceResults result = TraceLine( startPos, endPos, null, mask, TRACE_COLLISION_GROUP_NONE )
+ local dif = fabs( result.endPos.z - dropPoint.z )
+ totalDif += dif
+
+ if ( dif > maxDif )
+ {
+ //DebugDrawLine( startPos, result.endPos, 200, 50, 50, true, 3 )
+ return false
+ }
+ //DebugDrawLine( startPos, result.endPos, 50, 50, 200, true, 3 )
+ }
+
+ if ( totalDif > ( maxDif * 2 ) )
+ {
+ // this should catch cases where a small item like a box or barrel stops the hull collision trace above the ground.
+ return false
+ }
+
+ return true
+}
+
+
+bool function DropPodFindDropNodes( FlightPath flightPath, vector origin, float yaw )
+{
+ if ( NearTitanfallBlocker( origin ) )
+ return false
+
+ //level.drawAnalysisPreview = true
+ if ( !TitanTestDropPoint( origin, flightPath ) )
+ return false
+
+ return EdgeTraceDropPoint( origin )
+}
+
+bool function TitanTestDropPoint( vector start, FlightPath flightPath )
+{
+ local draw = level.drawAnalysisPreview
+ local end = start + Vector(0,0,8000)
+
+ TraceResults result = TraceHull( start, end, flightPath.mins, flightPath.maxs, null, flightPath.traceMask, TRACE_COLLISION_GROUP_NONE )
+ if ( result.startSolid )
+ {
+ if ( draw )
+ {
+ DrawArrow( start, Vector(0,0,0), 5.0, 80 )
+ DebugDrawLine( start, result.endPos, 0, 255, 0, true, 5.0 )
+ DebugDrawLine( result.endPos, end, 255, 0, 0, true, 5.0 )
+ //local newstart = start + Vector(0,0,150)
+ //local reresult = TraceHull( newstart, start, flightPath.mins, flightPath.maxs, null, flightPath.traceMask, TRACE_COLLISION_GROUP_NONE )
+ //printt( "surface " + reresult.surfaceName )
+ //DebugDrawLine( newstart, reresult.endPos, 155, 0, 0, true, ANALYSIS_PREVIEW_TIME )
+ //DrawArrow( reresult.endPos, Vector(0,0,0), ANALYSIS_PREVIEW_TIME, 15 )
+ //
+// //DrawArrow( start, Vector(0,0,0), ANALYSIS_PREVIEW_TIME, 15 )
+ //DebugDrawLine( start, result.endPos, 255, 0, 0, true, ANALYSIS_PREVIEW_TIME )
+ //printt( "length " + Length( start - result.endPos ) )
+ }
+ return false
+ }
+
+ if ( result.fraction < 1 )
+ {
+ if ( result.hitSky )
+ {
+ if ( draw )
+ {
+ DebugDrawLine( start, end, 0, 0, 255, true, ANALYSIS_PREVIEW_TIME )
+ //DrawArrow( start, Vector(0,0,0), 1.0, 100 )
+ }
+ return true
+ }
+
+// if ( draw )
+// DebugDrawLine( orgs[i-1] + Vector(10,10,10), orgs[i]+ Vector(10,10,10), 255, 255, 0, true, ANALYSIS_PREVIEW_TIME )
+
+ // some fudge factor
+ if ( Distance( result.endPos, end ) > 16 )
+ {
+ if ( draw )
+ {
+ local offset = Vector(-0.1, -0.1, 0 )
+ DebugDrawLine( start + offset, result.endPos + offset, 0, 255, 0, true, ANALYSIS_PREVIEW_TIME )
+ DebugDrawLine( result.endPos + offset, end + offset, 255, 0, 0, true, ANALYSIS_PREVIEW_TIME )
+ //DebugDrawLine( start, end, 255, 0, 0, true, ANALYSIS_PREVIEW_TIME )
+ }
+ return false
+ }
+ }
+
+// DebugDrawLine( orgs[i-1], orgs[i], 0, 255, 0, true, ANALYSIS_PREVIEW_TIME )
+
+ if ( draw )
+ DebugDrawLine( start, end, 0, 255, 0, true, 0.2 )
+ return true
+}
+
+
+
+
+
+void function TitanFall_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ if ( !ent.IsPlayer() )
+ return
+
+ if ( !ent.IsTitan() )
+ return
+
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+ vector entityOrigin = ent.GetOrigin()
+ local distance = Distance( entityOrigin, damageOrigin )
+
+ // on top of them, let the titans fall where they may
+ if ( distance < TITANFALL_INNER_RADIUS )
+ return
+
+ if ( IsTitanWithinBubbleShield( ent ) )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ vector pushVector = Normalize( entityOrigin - damageOrigin )
+
+ vector traceEndOrigin = damageOrigin + (pushVector * TITANFALL_OUTER_RADIUS)
+ TraceResults traceResult = TraceHull( damageOrigin, traceEndOrigin, ent.GetBoundingMins(), ent.GetBoundingMins(), ent, TRACE_MASK_NPCSOLID_BRUSHONLY, TRACE_COLLISION_GROUP_NONE )
+
+ // no room to push them
+ if ( traceResult.fraction < 0.85 )
+ return
+
+ DamageInfo_ScaleDamage( damageInfo, 0.15 )
+
+ ent.SetVelocity( pushVector * 400 )
+ ent.SetStaggering()
+}
+
+function PlayDeathFromTitanFallSounds( player )
+{
+ if ( player.IsTitan() )
+ {
+ //printt( "Playing titanfall_on_titan at: "+ player.GetOrigin() )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, player.GetOrigin(), "titanfall_on_titan" )
+ }
+ else
+ {
+ //printt( "Playing titanfall_on_human at " + player.GetOrigin() )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, player.GetOrigin(), "titanfall_on_human" )
+ }
+}
+
+bool function NPCShouldDoBubbleShieldAfterHotdrop( entity titan )
+{
+ if ( titan.HasKey( "script_hotdrop" ) )
+ {
+ switch ( titan.kv.script_hotdrop )
+ {
+ case "4":
+ case "3":
+ printt( "DROP WITH NO BUBBLE" )
+ return false
+ }
+ }
+
+ return true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_titan_triple_health.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_titan_triple_health.gnut
new file mode 100644
index 000000000..7515b868f
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/_titan_triple_health.gnut
@@ -0,0 +1,524 @@
+untyped
+
+global function HealthRegenInit
+
+global function TitanLoseSegementFX //JFS: Only being used for Rodeo now, rename later if needed
+global function GibBodyPart
+
+const SEGMENT_DOWN_SOUNDS_3P = [
+ "titan_healthbar_tier3_down_3P_vs_3P", // 0 left (doom)
+ "titan_healthbar_tier2_down_3P_vs_3P", // 1 left
+ "titan_healthbar_tier1_down_3P_vs_3P", // 2 left
+ "titan_healthbar_tier1_down_3P_vs_3P" // shield gone
+]
+
+const SEGMENT_DOWN_SOUNDS_3P_ATTACKER = [
+ "titan_healthbar_tier3_down_1P_vs_3P", // 0 left (doom)
+ "titan_healthbar_tier2_down_1P_vs_3P", // 1 left
+ "titan_healthbar_tier1_down_1P_vs_3P", // 2 left
+ "titan_healthbar_tier1_down_1P_vs_3P" // shield gone
+]
+
+const SEGMENT_DOWN_SOUNDS_1P = [
+ "titan_healthbar_tier3_down_1P", // 0 left (doom)
+ "titan_healthbar_tier2_down_1P", // 1 left
+ "titan_healthbar_tier1_down_1P", // 2 left
+ "titan_healthbar_tier1_down_1P" // shield gone
+]
+
+const DAMAGE_FORGIVENESS_CEILING = 1200.0
+const DAMAGE_FORGIVENESS_FLOOR = 500.0
+const LOW_HEALTH_WARNING_SOUND = "Weapon_Vortex_Gun.ExplosiveWarningBeep"
+
+const TITAN_DAMAGE_MITIGATION_DAMAGESCALE = 0.5
+
+struct {
+ int shieldDecayRate = 2
+
+ table< entity, table< entity, float > > soulToSoulDamageMemory
+} file;
+
+function HealthRegenInit()
+{
+ if ( GetCurrentPlaylistVarInt( "titan_health_decay_amount", 0 ) > 0 )
+ {
+ AddSoulInitFunc( TitanHealthDecayThink )
+ }
+
+ AddSoulInitFunc( TitanHealthRegenThink )
+
+ if ( TitanShieldDecayEnabled() )
+ {
+ AddSoulInitFunc( TitanShieldDecayThink )
+ }
+
+ AddDamageCallback( "player", TitanSegmentedHealth_OnDamage )
+ AddDamageCallback( "npc_titan", TitanSegmentedHealth_OnDamage )
+ AddCallback_OnTitanDoomed( OnTitanDoomed )
+
+ RegisterSignal( "HealthSegmentLost" )
+}
+
+
+void function TitanHealthDecayThink( entity soul )
+{
+ thread TitanHealthDecayThinkInternal( soul )
+}
+
+void function TitanHealthDecayThinkInternal( entity soul )
+{
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+
+ soul.SetShieldHealth( 0 )
+
+ while ( 1 )
+ {
+ entity titan = soul.GetTitan()
+ int damageAmout = GetCurrentPlaylistVarInt( "titan_health_decay_amount", 0 )
+ titan.TakeDamage( damageAmout, null, null, { scriptType = DF_DOOMED_HEALTH_LOSS, damageSourceId = damagedef_suicide } )
+ WaitFrame()
+ }
+}
+
+void function TitanHealthRegenThink( entity soul )
+{
+ thread TitanHealthRegenThink_Internal( soul )
+}
+
+void function TitanHealthRegenThink_Internal( entity soul )
+{
+ soul.EndSignal( SIGNAL_TITAN_HEALTH_REGEN )
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "OnDestroy" )
+
+ if ( !soul.soul.regensHealth )
+ return
+
+ entity titan = soul.GetTitan()
+
+ if ( !IsValid( titan ) )
+ return
+
+ int healthPerTab = GetSegmentHealthForTitan( titan )
+
+ // set this if AI titans need to be aware of segment health. Not used currently
+ //titan.SetHealthPerSegment( healthPerTab )
+
+ int lastTitanHealth = titan.GetHealth()
+ bool regenSound = false
+ int maxHealth = titan.GetMaxHealth()
+ float lastTime = Time()
+
+ while ( 1 )
+ {
+ titan = soul.GetTitan()
+ if ( !IsAlive( titan ) )
+ return
+ int titanHealth = titan.GetHealth()
+ Assert( titan )
+
+ if ( !titan.IsTitan() )
+ return
+
+ if ( !soul.soul.regensHealth )
+ return
+
+ int currentRegenTab = GetTitanCurrentRegenTab( titan )
+
+ if ( currentRegenTab != GetSoulBatteryCount( soul ) )
+ SetSoulBatteryCount( soul, GetTitanCurrentRegenTab( titan ) )
+
+ int maxHealthForCurrentTab = currentRegenTab * healthPerTab
+
+ if ( titanHealth == maxHealthForCurrentTab )
+ {
+ if ( regenSound )
+ {
+ StopSoundOnEntity( titan, "titan_energyshield_up" )
+ regenSound = false
+ }
+ }
+
+ lastTitanHealth = titanHealth
+ lastTime = Time()
+ WaitFrame()
+ }
+}
+
+void function TitanSegmentedHealth_OnDamage( entity titan, var damageInfo )
+{
+ if ( !titan.IsTitan() )
+ return
+
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) )
+ return
+
+ if ( ShouldReduceDamageForSegmentedHealth( soul, damageInfo ) )
+ DamageInfo_ScaleDamage( damageInfo, 0.3 )
+
+ thread TitanSegmentedHealth_OnDamage_Thread( soul, damageInfo )
+}
+
+bool function ShouldReduceDamageForSegmentedHealth( entity soul, damageInfo )
+{
+ if ( !soul.soul.rebooting )
+ return false
+
+ if ( IsRodeoDamageFromBatteryPack( soul, damageInfo ) )
+ return false
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return false
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOM_FATALITY )
+ return false
+
+ return true
+}
+
+function TitanSegmentedHealth_OnDamage_Thread( entity soul, damageInfo )
+{
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "Doomed" )
+
+ entity titan = soul.GetTitan()
+
+ vector damageOrigin = GetDamageOrigin( damageInfo, titan )
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ int hitBox = DamageInfo_GetHitBox( damageInfo )
+
+ int healthFloor = CalculateHealthFloorForDamage( soul, titan, damageInfo )
+
+ bool skipDoom = ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_SKIPS_DOOMED_STATE ) > 0
+
+
+ WaitEndFrame()
+
+ titan = soul.GetTitan()
+ Assert( IsValid( titan ) )
+
+ if ( soul.soul.lastSegmentLossTime >= Time() )
+ return
+
+ if ( GetDoomedState( titan ) )
+ return
+
+ if ( titan.GetHealth() > healthFloor )
+ return
+
+ if ( !IsAlive( titan ) )
+ return
+
+ string settings = GetSoulPlayerSettings( soul )
+ if ( Dev_GetPlayerSettingByKeyField_Global( settings, "use_damage_states" ) == 1 )
+ UpdateDamageStateForTab( titan, GetTitanCurrentRegenTab( titan ), hitBox )
+
+ thread TitanLoseSegement( soul, titan, damageOrigin, damageAmount, attacker )
+}
+
+int function CalculateHealthFloorForDamage( entity soul, entity titan, damageInfo )
+{
+ //Lets you bypass the health segment limitation and remove an entire health segment.
+ /*if ( IsRodeoDamageFromBatteryPack( soul, damageInfo ) )
+ return minint( 0, int ( titan.GetHealth() - DamageInfo_GetDamage( damageInfo ) ) )
+ */
+ int oldTab = GetTitanCurrentRegenTab( titan )
+ return ( oldTab - 1 ) * GetSegmentHealthForTitan( titan )
+}
+
+void function TitanLoseSegement( entity soul, entity titan, vector damageOrigin, float damageAmount, entity attacker )
+{
+ if ( !IsValid( soul ) )
+ return
+
+ if ( !IsValid( titan ) )
+ return
+
+ if ( soul.soul.lastSegmentLossTime >= Time() )
+ return
+
+ soul.soul.lastSegmentLossTime = Time()
+
+ entity player
+ if ( titan.IsPlayer() )
+ player = titan
+
+ foreach ( callbackFunc in svGlobal.onTitanHealthSegmentLostCallbacks )
+ {
+ callbackFunc( titan, attacker )
+ }
+
+ // Added via AddTitanCallback_OnHealthSegmentLost
+ foreach ( callbackFunc in titan.e.entSegmentLostCallbacks )
+ {
+ callbackFunc( titan, attacker )
+ }
+
+ GiveDefenderAmmo( titan )
+
+ titan.Signal( "HealthSegmentLost" )
+
+ soul.EndSignal( "OnTitanDeath" )
+ soul.EndSignal( "OnDestroy" )
+
+ if ( GetCurrentPlaylistVarInt( "titan_health_chicklet_fx", 0 ) == 1 )
+ TitanLoseSegementFX( titan, attacker, damageOrigin )
+
+ SetSoulBatteryCount( soul, GetTitanCurrentRegenTab( titan ) )
+}
+
+void function TitanLoseSegementFX( entity titan, entity attacker, vector damageOrigin )
+{
+ int handle = titan.GetEncodedEHandle()
+ int handleAttacker = -1
+
+ if ( IsValid( attacker ) )
+ handleAttacker = attacker.GetEncodedEHandle()
+
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanLostHealthSegment", handle, handleAttacker, damageOrigin.x, damageOrigin.y, damageOrigin.z )
+ }
+
+ if ( !IsAlive( titan ) )
+ return
+
+ int currentRegenTab = minint( GetTitanCurrentRegenTab( titan ), SEGMENT_DOWN_SOUNDS_3P_ATTACKER.len()-1 )
+ if ( currentRegenTab < SEGMENT_DOWN_SOUNDS_3P_ATTACKER.len() )
+ {
+ if ( titan.IsPlayer() && IsAlive( attacker ) && attacker.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( titan, attacker, SEGMENT_DOWN_SOUNDS_3P_ATTACKER[ currentRegenTab ] )
+ EmitSoundOnEntityOnlyToPlayer( titan, titan, SEGMENT_DOWN_SOUNDS_1P[ currentRegenTab ] )
+
+ // need a command here to play for not victim and not attacker
+ EmitSoundOnEntityExceptToPlayer( titan, titan, SEGMENT_DOWN_SOUNDS_3P[ currentRegenTab ] )
+ }
+ else if ( IsAlive( attacker ) && attacker.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( titan, attacker, SEGMENT_DOWN_SOUNDS_3P_ATTACKER[ currentRegenTab ] )
+ EmitSoundOnEntityExceptToPlayer( titan, attacker, SEGMENT_DOWN_SOUNDS_3P[ currentRegenTab ] )
+ }
+ else if ( titan.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( titan, titan, SEGMENT_DOWN_SOUNDS_1P[ currentRegenTab ] )
+ EmitSoundOnEntityExceptToPlayer( titan, titan, SEGMENT_DOWN_SOUNDS_3P[ currentRegenTab ] )
+ }
+ else
+ {
+ EmitSoundOnEntity( titan, SEGMENT_DOWN_SOUNDS_3P[ currentRegenTab ] )
+ }
+ }
+}
+
+
+void function UpdateDamageStateForTab( entity titan, int tab, int hitBox )
+{
+ if ( hitBox == -1 ) // not every hitbox has data defined
+ return
+
+ var bodyGroup = titan.GetBodyGroupNameFromHitboxId( hitBox ) // can be null
+
+ if ( bodyGroup == null )
+ {
+ printt( "bodyGroup was null" )
+ return
+ }
+
+#if MP
+ // these are flipped on purpose to prevent both legs or arms from being blown up
+ switch ( bodyGroup )
+ {
+ case "left_leg":
+ if ( IsBroken( titan, "right_leg" ) )
+ return
+ break
+
+ case "right_leg":
+ if ( IsBroken( titan, "left_leg" ) )
+ return
+ break
+
+ case "left_arm":
+ if ( IsBroken( titan, "right_arm" ) )
+ return
+ break
+
+ case "right_arm":
+ if ( IsBroken( titan, "left_arm" ) )
+ return
+ break
+
+ default:
+ return
+ }
+
+ GibBodyPart( titan, bodyGroup )
+#else
+ int maxTab = 3
+ int count = maxTab - tab
+
+ RecursiveGibBodyPart( titan, bodyGroup, count )
+#endif
+}
+
+void function RecursiveGibBodyPart( entity titan, var bodyGroup, int count )
+{
+ GibBodyPart( titan, bodyGroup )
+
+ count -= 1
+ if ( count <= 0 )
+ return
+
+ foreach ( siblingName in titan.s.skeletonData[bodyGroup].siblings )
+ {
+ // printt( count + " recurse: " + siblingName )
+ RecursiveGibBodyPart( titan, siblingName, count )
+ }
+}
+
+bool function IsBroken( entity titan, var bodyGroup )
+{
+ local bodyGroupIndex = titan.FindBodyGroup( bodyGroup )
+ local stateCount = GetStateCountForBodyGroup( titan, bodyGroup )
+ local bodyGroupState = titan.GetBodyGroupState( bodyGroupIndex )
+
+ //return ( bodyGroupState >= (stateCount - 1) )
+ return ( bodyGroupState > 0 )
+}
+
+void function GibBodyPart( entity titan, var bodyGroup )
+{
+ // if ( IsBodyGroupBroken( titan, bodyGroup ) )
+ // return
+
+ // titan.s.damageStateInfo[bodyGroup] = 1
+
+ local bodyGroupIndex = titan.FindBodyGroup( bodyGroup )
+ local stateCount = GetStateCountForBodyGroup( titan, bodyGroup )
+ local bodyGroupState = titan.GetBodyGroupState( bodyGroupIndex )
+
+ if ( bodyGroupState >= (stateCount - 1) )
+ return
+
+ titan.SetBodygroup( bodyGroupIndex, bodyGroupState + 1 )
+ // printt( "break: " + bodyGroup )
+}
+
+function GiveAttackerAmmo( entity titan )
+{
+}
+
+void function TemporaryInvul( entity titan )
+{
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDeath" )
+ if ( titan.IsPlayer() )
+ {
+ titan.EndSignal( "DisembarkingTitan" )
+ titan.EndSignal( "TitanEjectionStarted" )
+ }
+
+ OnThreadEnd(
+ function() : ( titan )
+ {
+ if ( IsValid( titan ) )
+ titan.ClearInvulnerable()
+ }
+ )
+
+ titan.SetInvulnerable()
+ wait 0.25
+}
+
+void function GiveDefenderAmmo( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+
+ if ( IsSingleplayer() )
+ {
+ if ( titan.IsNPC() )
+ {
+ soul.SetNextCoreChargeAvailable( soul.GetNextCoreChargeAvailable() + 0.5 ) // shave time off core timer
+ }
+ }
+}
+
+void function OnTitanDoomed( entity titan, var damageInfo )
+{
+
+ if ( !IsAlive( titan ) )
+ return
+
+ entity soul = titan.GetTitanSoul()
+
+ if ( titan.IsPlayer() )
+ {
+ if ( SoulHasPassive( soul, ePassives.PAS_RONIN_AUTOSHIFT ) )
+ PhaseShift( titan, 0, 3.0 )
+
+ if ( SoulHasPassive( soul, ePassives.PAS_AUTO_EJECT ) )
+ return
+ }
+
+ soul.nextHealthRegenTime = Time()
+
+ vector damageOrigin = GetDamageOrigin( damageInfo, titan )
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( TitanDamageRewardsTitanCoreTime() && (titan != attacker) )
+ {
+ AddCreditToTitanCoreBuilderForDoomEntered( titan )
+ if ( attacker.IsTitan() )
+ AddCreditToTitanCoreBuilderForDoomInflicted( attacker )
+ }
+
+ thread TitanLoseSegement( soul, titan, damageOrigin, damageAmount, attacker )
+
+ if ( SoulHasPassive( soul, ePassives.PAS_DOOMED_TIME ) )
+ return
+
+ if ( NoWeaponDoomState() )
+ TakeAllWeapons( titan )
+}
+
+void function OnTitanDeath( entity titan, var damageInfo )
+{
+ if ( !titan.IsTitan() )
+ return
+
+ if ( !PROTO_AlternateDoomedState() )
+ return
+
+ entity soul = titan.GetTitanSoul()
+ vector damageOrigin = GetDamageOrigin( damageInfo, titan )
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ thread TitanLoseSegement( soul, titan, damageOrigin, damageAmount, attacker )
+}
+
+void function TitanShieldDecayThink( entity soul )
+{
+ thread TitanShieldDecayThinkInternal( soul )
+}
+
+void function TitanShieldDecayThinkInternal( entity soul )
+{
+ soul.EndSignal( "OnDestroy" ) //This needs to be OnDestroy instead of OnDeath because souls don't have a death animation
+ soul.EndSignal( "OnTitanDeath" )
+
+ while ( 1 )
+ {
+ if ( Time() >= soul.e.nextShieldDecayTime && !TitanHasRegenningShield( soul ) )
+ soul.SetShieldHealth( maxint( soul.GetShieldHealth() - file.shieldDecayRate, 0 ) )
+ WaitFrame()
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/titan/class_titan.gnut b/Northstar.CustomServers/scripts/vscripts/titan/class_titan.gnut
new file mode 100644
index 000000000..5f72385ea
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan/class_titan.gnut
@@ -0,0 +1,77 @@
+untyped
+
+global function ClassTitan_Init
+
+global function Titan_AddPlayer
+global function Titan_OnPlayerDeath
+global function ClientCommand_TitanEject
+global function ApplyTitanLoadoutModifiers
+
+
+const TITAN_HATCHCOMMANDANIMTIME = 1.5 // cooldown time between toggling the cockpit state. Will be needed when we have animations to play
+
+const COCKPIT_JOLT_DAMAGE_MIN = 1
+const COCKPIT_JOLT_DAMAGE_MAX = 200
+const TITAN_STUMBLE_HEALTH_PERCENTAGE = 0.5
+
+string thisClassName = "titan"
+
+function ClassTitan_Init()
+{
+
+ AddClientCommandCallback( "TitanEject", ClientCommand_TitanEject ) //
+}
+
+function Titan_AddPlayer( player )
+{
+ player.playerClassData[thisClassName] <- {}
+ player.s.lastStaggerTime <- 0
+}
+
+
+// TODO: There should be an equivalent function for pilots
+TitanLoadoutDef function ApplyTitanLoadoutModifiers( entity player, TitanLoadoutDef loadout )
+{
+ return loadout
+}
+
+void function Titan_OnPlayerDeath( entity player, var damageInfo )
+{
+ player.p.storedWeapons.clear()
+}
+
+bool function PlayerCanEject( entity player )
+{
+ if ( !IsAlive( player ) )
+ return false
+
+ if ( !player.IsTitan() )
+ return false
+
+ if ( Riff_TitanExitEnabled() == eTitanExitEnabled.Never )
+ return false
+
+ //if ( !CanDisembark( player ) )
+ // return false
+
+ if ( IsPlayerDisembarking( player ) )
+ return false
+
+ if ( TitanEjectIsDisabled() )
+ return false
+
+ return true
+}
+
+bool function ClientCommand_TitanEject( entity player, array<string> args )
+{
+ if ( !PlayerCanEject( player ) )
+ return true
+
+ int ejectPressCount = args[ 0 ].tointeger()
+ if ( ejectPressCount < 3 )
+ return true
+
+ thread TitanEjectPlayer( player )
+ return true
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/titan_xp.gnut b/Northstar.CustomServers/scripts/vscripts/titan_xp.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/titan_xp.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/vehicle/_vehicle_behavior.gnut b/Northstar.CustomServers/scripts/vscripts/vehicle/_vehicle_behavior.gnut
new file mode 100644
index 000000000..2d0dd920e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/vehicle/_vehicle_behavior.gnut
@@ -0,0 +1,6 @@
+global function CodeCallback_OnVehiclePass
+
+// params: vehicle, nodePassed, nextNode, nextNextNode
+void function CodeCallback_OnVehiclePass( table params )
+{
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/vehicle/_vehicle_dropship_new.nut b/Northstar.CustomServers/scripts/vscripts/vehicle/_vehicle_dropship_new.nut
new file mode 100644
index 000000000..87010ca74
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/vehicle/_vehicle_dropship_new.nut
@@ -0,0 +1,528 @@
+untyped
+
+global function VehicleDropshipNew_Init
+
+global function InitLeanDropship
+global function CreateDropship
+global function GetDropshipSquadSize
+global function WarpinEffect
+global function WarpoutEffect
+global function WarpoutEffectFPS
+global function WaitForNPCsDeployed
+global function CreateNPCSForDropship
+global function GuyDeploysOffShip
+global function WaittillPlayDeployAnims
+global function GetDropshipRopeAttachments
+global function DelayDropshipDelete
+global function PlayDropshipRampDoorOpenSound
+global function PlayWarpFxOnPlayers
+global function DefensiveFreePlayers
+global function CreateDropshipAnimTable
+
+global function InitDropShipFlightPaths
+
+// _dropship
+//
+// dropship/passenger setup:
+// 1. Create an npc_dropship and target it with a point_template. Give the point_template a name.
+// 2. npc_dropship targets starting path_track (see below for path setup info) AND passenger spawning point_template
+// 3. passenger spawning point_template points to six passengers that will spawn and rappel at the unload spot
+//
+// path/unload spot setup:
+// 1. create your path out of path_track entities
+// 2. to create rappel targets, add six info_targets on the ground around your unload node, name them all the same
+// 3. on the node where the dropship will unload, create a key/value pair called "unload" and point it at the name you gave to the rappel target info_targets
+//
+// spawning in script:
+// 1. use GetEnt to get the point_template that targets the dropship
+// 2. pass that point_template into DropshipSpawn(), this returns with the dropship ent
+// 3. pass the dropship ent into DropshipDropshipFlyPathAndUnload() (thread it off if you want to do stuff right after)
+//
+
+
+const FX_DROPSHIP_THRUSTERS = $"xo_atlas_jet_large"
+const FX_GUNSHIP_DAMAGE = $"veh_gunship_damage_FULL"
+const FX_DROPSHIP_DEATH = $"P_veh_exp_crow"
+
+const DROPSHIP_ROPE_ENDPOINT_FX = $"runway_light_blue"
+const DROPSHIP_ACL_LIGHT_GREEN_FX = $"acl_light_green"
+const DROPSHIP_ACL_LIGHT_RED_FX = $"acl_light_red"
+const DROPSHIP_ACL_LIGHT_WHITE_FX = $"acl_light_white"
+
+const ENGAGEMENT_DIST = 1024
+const ENGAGEMENT_DIST_SQD = ENGAGEMENT_DIST * ENGAGEMENT_DIST
+
+const DEFAULT_READYANIM_BLENDTIME = 1.0
+
+table file = {
+ dropshipAttachments = null
+}
+
+function VehicleDropshipNew_Init()
+{
+
+ RegisterSignal( "sRampOpen" )
+ RegisterSignal( "sRampClose" )
+
+ #if SERVER
+ PrecacheParticleSystem( FX_GUNSHIP_CRASH_EXPLOSION_ENTRANCE )
+ PrecacheParticleSystem( FX_GUNSHIP_CRASH_EXPLOSION_EXIT )
+ PrecacheParticleSystem( FX_DROPSHIP_DEATH )
+ AddDeathCallback( "npc_dropship", OnNPCDropshipDeath )
+ AddDamageCallback( "npc_dropship", OnDropshipDamaged )
+ #endif
+
+ PrecacheParticleSystem( FX_HORNET_DEATH )
+ PrecacheParticleSystem( FX_GUNSHIP_DAMAGE )
+ PrecacheParticleSystem( FX_DROPSHIP_THRUSTERS )
+ PrecacheParticleSystem( DROPSHIP_ROPE_ENDPOINT_FX )
+ PrecacheParticleSystem( DROPSHIP_ACL_LIGHT_GREEN_FX )
+ PrecacheParticleSystem( DROPSHIP_ACL_LIGHT_RED_FX )
+ PrecacheParticleSystem( DROPSHIP_ACL_LIGHT_WHITE_FX )
+
+ level.DROPSHIP_DEFAULT_AIRSPEED <- 750
+
+ PrecacheEntity( "keyframe_rope" )
+ PrecacheModel( DROPSHIP_MODEL )
+
+ PrecacheSprite( $"sprites/laserbeam.vmt" )
+ PrecacheSprite( $"sprites/glow_05.vmt" )
+
+
+ //Array of all attachments in the dropship model. Used in DropshipDamageEffects
+ local names = []
+ names.append( "FRONT_TURRET" )
+ names.append( "BOMB_L" )
+ names.append( "BOMB_R" )
+ names.append( "Spotlight" )
+ names.append( "Light_Red0" )
+ names.append( "Light_Red1" )
+ names.append( "Light_Red2" )
+ names.append( "Light_Red3" )
+ names.append( "HeadlightLeft" )
+ names.append( "RopeAttachLeftA" )
+ names.append( "RopeAttachLeftB" )
+ names.append( "RopeAttachLeftC" )
+ names.append( "L_exhaust_rear_1" )
+ names.append( "L_exhaust_rear_2" )
+ names.append( "L_exhaust_front_1" )
+ names.append( "Light_Green0" )
+ names.append( "Light_Green1" )
+ names.append( "Light_Green2" )
+ names.append( "Light_Green3" )
+ names.append( "HeadlightRight" )
+ names.append( "RopeAttachRightA" )
+ names.append( "RopeAttachRightB" )
+ names.append( "RopeAttachRightC" )
+ names.append( "R_exhaust_rear_1" )
+ names.append( "R_exhaust_rear_2" )
+ names.append( "R_exhaust_front_1" )
+
+ file.dropshipAttachments = names
+
+ level.DSAIziplineAnims <- {}
+ level.DSAIziplineAnims[ "left" ] <- []
+ level.DSAIziplineAnims[ "left" ].append( { idle = "pt_dropship_rider_L_A_idle", attach = "RopeAttachLeftA" } )
+ level.DSAIziplineAnims[ "left" ].append( { idle = "pt_dropship_rider_L_C_idle", attach = "RopeAttachLeftC" } )
+ level.DSAIziplineAnims[ "left" ].append( { idle = "pt_dropship_rider_L_B_idle", attach = "RopeAttachLeftB" } )
+
+ level.DSAIziplineAnims[ "right" ] <- []
+ level.DSAIziplineAnims[ "right" ].append( { idle = "pt_dropship_rider_R_A_idle", attach = "RopeAttachRightA" } )
+ level.DSAIziplineAnims[ "right" ].append( { idle = "pt_dropship_rider_R_C_idle", attach = "RopeAttachRightC" } )
+ level.DSAIziplineAnims[ "right" ].append( { idle = "pt_dropship_rider_R_B_idle", attach = "RopeAttachRightB" } )
+
+ level.DSAIziplineAnims[ "both" ] <- []
+ level.DSAIziplineAnims[ "both" ].append( { idle = "pt_dropship_rider_L_A_idle", attach = "RopeAttachLeftA" } )
+ level.DSAIziplineAnims[ "both" ].append( { idle = "pt_dropship_rider_R_A_idle", attach = "RopeAttachRightA" } )
+ level.DSAIziplineAnims[ "both" ].append( { idle = "pt_dropship_rider_L_C_idle", attach = "RopeAttachLeftC" } )
+ level.DSAIziplineAnims[ "both" ].append( { idle = "pt_dropship_rider_R_B_idle", attach = "RopeAttachRightC" } )
+ level.DSAIziplineAnims[ "both" ].append( { idle = "pt_dropship_rider_L_B_idle", attach = "RopeAttachLeftB" } )
+ level.DSAIziplineAnims[ "both" ].append( { idle = "pt_dropship_rider_R_B_idle", attach = "RopeAttachRightB" } )
+}
+
+function InitLeanDropship( dropship )
+{
+ if ( dropship.kv.desiredSpeed.tofloat() <= 0 )
+ {
+ dropship.kv.desiredSpeed = level.DROPSHIP_DEFAULT_AIRSPEED
+ }
+
+ //dropship.s.dropFunc <- ShipDropsGuys
+}
+
+array<entity> function CreateNPCSForDropship( entity ship, array<entity functionref( int, vector, vector )> spawnFuncs, string side = "both" )
+{
+ int count = minint( spawnFuncs.len(), level.DSAIziplineAnims[ side ].len() )
+
+ int team = ship.GetTeam()
+ string squadName = expect string( ship.kv.squadname )
+ vector origin = ship.GetOrigin()
+ vector angles = ship.GetAngles()
+
+ array<entity> guys = []
+
+ if ( Flag( "disable_npcs" ) )
+ return guys //i.e. empty aray
+
+ local guy
+
+ // this is to maintain sketchy support for just passing an array of 1 spawn function
+ entity functionref( int, vector, vector ) spawnFunc = spawnFuncs[0]
+
+ for ( int i = 0; i < count; i++ )
+ {
+ if ( i < spawnFuncs.len() )
+ spawnFunc = spawnFuncs[i]
+
+ entity guy = spawnFunc( team, origin, angles )
+ guy.kv.squadname = squadName
+ DispatchSpawn( guy )
+
+ if ( !IsAlive( guy ) )
+ continue
+
+ guys.append( guy )
+
+ local seat = i
+ table Table = CreateDropshipAnimTable( ship, side, seat )
+
+ thread GuyDeploysOffShip( guy, Table )
+ }
+
+ return guys
+}
+
+
+
+table function CreateDropshipAnimTable( ship, side, seat )
+{
+ table Table
+
+ Table.idleAnim <- level.DSAIziplineAnims[ side ][ seat ].idle
+ Table.deployAnim <- "zipline"
+ Table.shipAttach <- level.DSAIziplineAnims[ side ][ seat ].attach
+ Table.attachIndex <- null
+ Table.ship <- ship
+ Table.side <- side
+ Table.blendTime <- DEFAULT_SCRIPTED_ANIMATION_BLEND_TIME
+
+ return Table
+}
+
+function WaitForNPCsDeployed( npcArray )
+{
+ local ent = CreateScriptRef()
+ ent.s.count <- 0
+
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ if ( IsValid( ent ) )
+ ent.Kill_Deprecated_UseDestroyInstead()
+ }
+ )
+
+ local func =
+ function( ent, guy )
+ {
+ ent.s.count++
+
+ WaitSignal( guy, "npc_deployed", "OnDeath", "OnDestroy" )
+ ent.s.count--
+
+ if ( !ent.s.count )
+ ent.Kill_Deprecated_UseDestroyInstead()
+ }
+
+ foreach ( entity guy in npcArray )
+ {
+ if ( IsAlive( guy ) )
+ thread func( ent, guy )
+ }
+
+ ent.WaitSignal( "OnDestroy" )
+}
+
+function InitDropShipFlightPaths( spawnPoints )
+{
+ entity tempDropShip = CreateEntity( "prop_dynamic" )
+ tempDropShip.kv.spawnflags = 0
+ tempDropShip.SetModel( DROPSHIP_MODEL )
+
+ DispatchSpawn( tempDropShip )
+
+ foreach ( spawnPoint in spawnPoints )
+ {
+ tempDropShip.SetOrigin( spawnPoint.GetOrigin() )
+
+ spawnPoint.s.dropShipPathAnims <- {}
+ spawnPoint.s.dropShipPathAnims[ "gd_goblin_zipline_strafe" ] <- GetDropShipAnimOffset( tempDropShip, "gd_goblin_zipline_strafe", spawnPoint )
+ spawnPoint.s.dropShipPathAnims[ "gd_goblin_zipline_dive" ] <- GetDropShipAnimOffset( tempDropShip, "gd_goblin_zipline_dive", spawnPoint )
+ }
+
+ tempDropShip.Destroy()
+}
+
+entity function CreateDropship( int team, vector origin, vector angles )
+{
+ entity dropship = CreateEntity( "npc_dropship" )
+ dropship.kv.teamnumber = team
+ dropship.SetOrigin( origin )
+ dropship.SetAngles( angles )
+ return dropship
+}
+
+function GetDropShipAnimOffset( dropShip, animName, refEnt )
+{
+ local animStart = dropShip.Anim_GetStartForRefPoint_Old( animName, refEnt.GetOrigin(), refEnt.GetAngles() )
+ return animStart.origin - refEnt.GetOrigin()
+}
+
+
+
+function GetDropshipSquadSize( squadname )
+{
+ local squadsize = 0
+ array<entity> dropships = GetNPCArrayByClass( "npc_dropship" )
+
+ //printl( dropships.len()+ " dropships, checking squadname: " + squadname )
+ foreach ( ship in dropships )
+ if ( ship.kv.squadname == squadname )
+ squadsize++
+
+ //printl( dropships.len()+ " dropships, squadsize: " + squadsize )
+ return squadsize
+}
+
+function DelayDropshipDelete( dropship )
+{
+ dropship.EndSignal( "OnDeath" )
+
+ //very defensive check
+ DefensiveFreePlayers( dropship )
+
+ WaitFrame() // so the dropship wont pop out before it warps out
+
+ dropship.Kill_Deprecated_UseDestroyInstead()
+}
+
+function DefensiveFreePlayers( dropship )
+{
+ array<entity> players = GetPlayerArrayOfTeam( dropship.GetTeam() )
+ foreach ( player in players )
+ {
+ if ( !IsValid( player ) )
+ continue
+
+ if ( player.GetParent() != dropship )
+ continue
+
+ player.ClearParent() //Clear parent before dropship gets deleted with players still attached to it. Defensive fix for bug 178543
+
+ KillPlayer( player, eDamageSourceId.fall )
+ }
+}
+
+void function OnNPCDropshipDeath( entity dropship, var damageInfo )
+{
+ if ( !IsValid( dropship ) )
+ return
+
+ asset modelName = dropship.GetModelName()
+
+ vector dropshipOrigin = dropship.GetOrigin()
+
+ PlayFX( $"P_veh_exp_crow", dropshipOrigin )
+
+ EmitSoundAtPosition( TEAM_UNASSIGNED, dropshipOrigin, "Goblin_Dropship_Explode" )
+}
+
+void function OnDropshipDamaged( entity dropship, var damageInfo )
+{
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ //Tried to give visual shield indicator, but it doesn't seem to work?
+ //DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )
+
+ // store the damage so all hits can be tallied
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ Assert( IsValid( inflictor ) )//Done so we can still get the error in dev
+ if ( !IsValid( inflictor ) ) //JFS Defensive fix
+ return
+
+ StoreDamageHistoryAndUpdate( dropship, 120.0, DamageInfo_GetDamage( damageInfo ), inflictor.GetOrigin(), DamageInfo_GetCustomDamageType( damageInfo ), DamageInfo_GetDamageSourceIdentifier( damageInfo ), DamageInfo_GetAttacker( damageInfo ) )
+
+ if ( DamageInfo_GetDamage( damageInfo ) < 450 )
+ return
+
+ vector pos = DamageInfo_GetDamagePosition( damageInfo )
+ PlayFX( FX_GUNSHIP_DAMAGE, pos )
+}
+
+void function GuyDeploysOffShip( entity guy, table Table )
+{
+ guy.EndSignal( "OnDeath" )
+ entity ship = expect entity( Table.ship )
+ local shipAttach = Table.shipAttach
+
+ OnThreadEnd(
+ function() : ( guy, ship )
+ {
+ if ( !IsValid( guy ) )
+ return
+
+ if ( ship != null )
+ {
+ if ( !IsAlive( ship ) && IsAlive( guy ) )
+ {
+ // try to transfer the last attacker from the ship to the attached guys.
+ entity attacker = null
+ entity lastAttacker = GetLastAttacker( ship )
+ if ( IsValid( lastAttacker ) )
+ attacker = lastAttacker
+
+ guy.TakeDamage( 500, attacker, attacker, null )
+ }
+ }
+
+ if ( !IsAlive( guy ) )
+ guy.BecomeRagdoll( Vector(0,0,0), false )
+ }
+ )
+
+ guy.SetEfficientMode( true )
+ HideName( guy )
+
+ Assert( shipAttach, "Ship but no shipAttach" )
+ ship.EndSignal( "OnDeath" )
+ GuyAnimatesRelativeToShipAttachment( guy, Table )
+
+ WaittillPlayDeployAnims( ship )
+
+ GuyAnimatesOut( guy, Table )
+}
+
+function WaittillPlayDeployAnims( ref )
+{
+ waitthread WaittillPlayDeployAnimsThread( ref )
+}
+
+function WaittillPlayDeployAnimsThread( ref )
+{
+ ref.EndSignal( "OnDeath" )
+
+ ref.WaitSignal( "deploy" )
+}
+
+void function GuyAnimatesOut( entity guy, table Table )
+{
+ switch ( Table.side )
+ {
+ case "left":
+ case "right":
+ case "both":
+ case "zipline":
+ waitthread GuyZiplinesToGround( guy, Table )
+ break
+
+ default:
+ thread PlayAnim( guy, Table.deployAnim, Table.ship, Table.shipAttach )
+ break
+ }
+
+
+ guy.SetEfficientMode( false )
+ guy.SetNameVisibleToOwner( true )
+
+ WaittillAnimDone( guy )
+ guy.ClearParent()
+
+ UpdateEnemyMemoryFromTeammates( guy )
+
+ guy.Signal( "npc_deployed" )
+}
+
+function GuyAnimatesRelativeToShipAttachment( guy, Table )
+{
+ expect entity( guy )
+ Table.attachIndex <- Table.ship.LookupAttachment( Table.shipAttach )
+ guy.SetOrigin( Table.ship.GetOrigin() )
+
+ guy.SetParent( Table.ship, Table.shipAttach, false, 0 )
+
+ guy.Anim_ScriptedPlay( Table.idleAnim )
+}
+
+table<string, array<string> > function GetDropshipRopeAttachments( string side = "both" )
+{
+ table<string, array<string> > attachments
+
+ if ( side == "both" )
+ {
+ attachments[ "left" ] <- []
+ attachments[ "right" ] <- []
+
+ foreach ( seat, Table in level.DSAIziplineAnims[ "left"] )
+ {
+ attachments[ "left" ].append( expect string( Table.attach ) )
+ }
+
+ foreach ( seat, Table in level.DSAIziplineAnims[ "right"] )
+ {
+ attachments[ "right" ].append( expect string( Table.attach ) )
+ }
+ }
+ else
+ {
+ attachments[ side ] <- []
+
+ foreach ( seat, Table in level.DSAIziplineAnims[ side ] )
+ {
+ attachments[ side ].append( expect string( Table.attach ) )
+ }
+ }
+
+ return attachments
+}
+
+function PlayDropshipRampDoorOpenSound( entity dropship )
+{
+ entity snd = CreateOwnedScriptMover( dropship )
+ snd.SetParent( dropship, "RAMPDOORLIP" )
+
+ EmitSoundOnEntity( snd, "fracture_scr_intro_dropship_dooropen" )
+}
+
+void function WarpoutEffect( entity dropship )
+{
+ if ( !IsValid( dropship ) )
+ return
+
+ __WarpOutEffectShared( dropship )
+
+ thread DelayDropshipDelete( dropship )
+}
+
+void function WarpoutEffectFPS( entity dropship )
+{
+ __WarpOutEffectShared( dropship )
+}
+
+void function WarpinEffect( asset model, string animation, vector origin, vector angles, string sfx = "" )
+{
+ //we need a temp dropship to get the anim offsets
+ Point start = GetWarpinPosition( model, animation, origin, angles )
+
+ __WarpInEffectShared( start.origin, start.angles, sfx )
+}
+
+function PlayWarpFxOnPlayers( guys )
+{
+ foreach ( entity guy in guys )
+ {
+ if ( !IsAlive( guy ) )
+ continue
+
+ Remote_CallFunction_Replay( guy, "ServerCallback_PlayScreenFXWarpJump" )
+ }
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapon_xp.gnut b/Northstar.CustomServers/scripts/vscripts/weapon_xp.gnut
new file mode 100644
index 000000000..37b891699
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapon_xp.gnut
@@ -0,0 +1 @@
+//fuck \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_arc_cannon.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_arc_cannon.nut
new file mode 100644
index 000000000..1601330c8
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_arc_cannon.nut
@@ -0,0 +1,1032 @@
+untyped
+
+global function ArcCannon_Init
+
+global function ArcCannon_PrecacheFX
+global function ArcCannon_Start
+global function ArcCannon_Stop
+global function ArcCannon_ChargeBegin
+global function ArcCannon_ChargeEnd
+global function FireArcCannon
+global function ArcCannon_HideIdleEffect
+#if SERVER
+ global function AddToArcCannonTargets
+ global function RemoveArcCannonTarget
+ global function ConvertTitanShieldIntoBonusCharge
+#endif
+global function GetArcCannonChargeFraction
+
+global function IsEntANeutralMegaTurret
+global function CreateArcCannonBeam
+
+
+// Aiming & Range
+global const DEFAULT_ARC_CANNON_FOVDOT = 0.98 // First target must be within this dot to be zapped and start a chain
+global const DEFAULT_ARC_CANNON_FOVDOT_MISSILE = 0.95 // First target must be within this dot to be zapped and start a chain ( if it's a missile, we allow more leaniency )
+global const ARC_CANNON_RANGE_CHAIN = 400 // Max distance we can arc from one target to another
+global const ARC_CANNON_TITAN_RANGE_CHAIN = 900 // Max distance we can arc from one target to another
+global const ARC_CANNON_CHAIN_COUNT_MIN = 5 // Max number of chains at no charge
+global const ARC_CANNON_CHAIN_COUNT_MAX = 5 // Max number of chains at full charge
+global const ARC_CANNON_CHAIN_COUNT_NPC = 2 // Number of chains when an NPC fires the weapon
+global const ARC_CANNON_FORK_COUNT_MAX = 1 // Number of forks that can come out of one target to other targets
+global const ARC_CANNON_FORK_DELAY = 0.1
+
+global const ARC_CANNON_RANGE_CHAIN_BURN = 400
+global const ARC_CANNON_TITAN_RANGE_CHAIN_BURN = 900
+global const ARC_CANNON_CHAIN_COUNT_MIN_BURN = 100 // Max number of chains at no charge
+global const ARC_CANNON_CHAIN_COUNT_MAX_BURN = 100 // Max number of chains at full charge
+global const ARC_CANNON_CHAIN_COUNT_NPC_BURN = 10 // Number of chains when an NPC fires the weapon
+global const ARC_CANNON_FORK_COUNT_MAX_BURN = 10 // Number of forks that can come out of one target to other targets
+global const ARC_CANNON_BEAM_LIFETIME_BURN = 1
+
+// Visual settings
+global const ARC_CANNON_BOLT_RADIUS_MIN = 32 // Bolt radius at no charge ( not actually sure what this does to the beam lol )
+global const ARC_CANNON_BOLT_RADIUS_MAX = 640 // Bold radius at full charge ( not actually sure what this does to the beam lol )
+global const ARC_CANNON_BOLT_WIDTH_MIN = 1 // Bolt width at no charge
+global const ARC_CANNON_BOLT_WIDTH_MAX = 26 // Bolt width at full charge
+global const ARC_CANNON_BOLT_WIDTH_NPC = 8 // Bolt width when used by NPC
+global const ARC_CANNON_BEAM_COLOR = "150 190 255"
+global const ARC_CANNON_BEAM_LIFETIME = 0.75
+
+// Player Effects
+global const ARC_CANNON_TITAN_SCREEN_SFX = "Null_Remove_SoundHook"
+global const ARC_CANNON_PILOT_SCREEN_SFX = "Null_Remove_SoundHook"
+global const ARC_CANNON_EMP_DURATION_MIN = 0.1
+global const ARC_CANNON_EMP_DURATION_MAX = 1.8
+global const ARC_CANNON_EMP_FADEOUT_DURATION = 0.4
+global const ARC_CANNON_SCREEN_EFFECTS_MIN = 0.01
+global const ARC_CANNON_SCREEN_EFFECTS_MAX = 0.02
+global const ARC_CANNON_SCREEN_THRESHOLD = 0.3385
+global const ARC_CANNON_3RD_PERSON_EFFECT_MIN_DURATION = 0.2
+
+// Damage
+global const ARC_CANNON_DAMAGE_FALLOFF_SCALER = 0.75 // Amount of damage carried on to the next target in the chain lightning. If 0.75, then a target that would normally take 100 damage will take 75 damage if they are one chain deep, or 56 damage if 2 levels deep
+global const ARC_CANNON_DAMAGE_CHARGE_RATIO = 0.85 // What amount of charge is required for full damage.
+global const ARC_CANNON_DAMAGE_CHARGE_RATIO_BURN = 0.676 // What amount of charge is required for full damage.
+global const ARC_CANNON_CAPACITOR_CHARGE_RATIO = 1.0
+
+// Options
+global const ARC_CANNON_TARGETS_MISSILES = 1 // 1 = arc cannon zaps missiles that are active, 0 = missiles are ignored by arc cannon
+
+//Mods
+global const OVERCHARGE_MAX_SHIELD_DECAY = 0.2
+global const OVERCHARGE_SHIELD_DECAY_MULTIPLIER = 0.04
+global const OVERCHARGE_BONUS_CHARGE_FRACTION = 0.05
+
+global const SPLITTER_DAMAGE_FALLOFF_SCALER = 0.6
+global const SPLITTER_FORK_COUNT_MAX = 10
+
+global const ARC_CANNON_SIGNAL_DEACTIVATED = "ArcCannonDeactivated"
+global const ARC_CANNON_SIGNAL_CHARGEEND = "ArcCannonChargeEnd"
+
+global const ARC_CANNON_BEAM_EFFECT = $"wpn_arc_cannon_beam"
+global const ARC_CANNON_BEAM_EFFECT_MOD = $"wpn_arc_cannon_beam_mod"
+
+global const ARC_CANNON_FX_TABLE = "exp_arc_cannon"
+
+global const ArcCannonTargetClassnames = {
+ [ "npc_drone" ] = true,
+ [ "npc_dropship" ] = true,
+ [ "npc_marvin" ] = true,
+ [ "npc_prowler" ] = true,
+ [ "npc_soldier" ] = true,
+ [ "npc_soldier_heavy" ] = true,
+ [ "npc_soldier_shield" ] = true,
+ [ "npc_spectre" ] = true,
+ [ "npc_stalker" ] = true,
+ [ "npc_super_spectre" ] = true,
+ [ "npc_titan" ] = true,
+ [ "npc_turret_floor" ] = true,
+ [ "npc_turret_mega" ] = true,
+ [ "npc_turret_sentry" ] = true,
+ [ "npc_frag_drone" ] = true,
+ [ "player" ] = true,
+ [ "prop_dynamic" ] = true,
+ [ "prop_script" ] = true,
+ [ "grenade_frag" ] = true,
+ [ "rpg_missile" ] = true,
+ [ "script_mover" ] = true,
+ [ "turret" ] = true,
+}
+
+struct {
+ array<string> missileCheckTargetnames = [
+ // "Arc Pylon",
+ "Arc Ball"
+ ]
+} file;
+
+function ArcCannon_Init()
+{
+ RegisterSignal( ARC_CANNON_SIGNAL_DEACTIVATED )
+ RegisterSignal( ARC_CANNON_SIGNAL_CHARGEEND )
+ PrecacheParticleSystem( ARC_CANNON_BEAM_EFFECT )
+ PrecacheParticleSystem( ARC_CANNON_BEAM_EFFECT_MOD )
+ PrecacheImpactEffectTable( ARC_CANNON_FX_TABLE )
+
+ #if CLIENT
+ AddDestroyCallback( "mp_titanweapon_arc_cannon", ClientDestroyCallback_ArcCannon_Stop )
+ #else
+ level._arcCannonTargetsArrayID <- CreateScriptManagedEntArray()
+ #endif
+
+ PrecacheParticleSystem( $"impact_arc_cannon_titan" )
+}
+
+function ArcCannon_PrecacheFX()
+{
+ PrecacheParticleSystem( $"wpn_arc_cannon_electricity_fp" )
+ PrecacheParticleSystem( $"wpn_arc_cannon_electricity" )
+
+ PrecacheParticleSystem( $"wpn_muzzleflash_arc_cannon_fp" )
+ PrecacheParticleSystem( $"wpn_muzzleflash_arc_cannon" )
+}
+
+function ArcCannon_Start( weapon )
+{
+ expect entity( weapon )
+ if ( !IsPilot( weapon.GetWeaponOwner() ) )
+ {
+ weapon.PlayWeaponEffectNoCull( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity", "muzzle_flash" )
+ weapon.EmitWeaponSound( "arc_cannon_charged_loop" )
+ }
+ else
+ {
+ weapon.EmitWeaponSound_1p3p( "Arc_Rifle_charged_Loop_1P", "Arc_Rifle_charged_Loop_3P" )
+ }
+}
+
+function ArcCannon_Stop( weapon, player = null )
+{
+ expect entity( weapon )
+ weapon.Signal( ARC_CANNON_SIGNAL_DEACTIVATED )
+
+ weapon.StopWeaponEffect( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity" )
+ weapon.StopWeaponSound( "arc_cannon_charged_loop" )
+}
+
+function ArcCannon_ChargeBegin( entity weapon )
+{
+ #if SERVER
+ if ( weapon.HasMod( "overcharge" ) )
+ {
+ entity weaponOwner = weapon.GetWeaponOwner()
+ if ( weaponOwner.IsTitan() )
+ {
+ entity soul = weaponOwner.GetTitanSoul()
+ thread ConvertTitanShieldIntoBonusCharge( soul, weapon )
+ }
+ }
+ #endif
+
+ #if CLIENT
+ if ( !weapon.ShouldPredictProjectiles() )
+ return
+
+ entity weaponOwner = weapon.GetWeaponOwner()
+ Assert( weaponOwner.IsPlayer() )
+ weaponOwner.StartArcCannon();
+ #endif
+}
+
+function ArcCannon_ChargeEnd( entity weapon, entity player = null )
+{
+ #if SERVER
+ if ( IsValid( weapon ) )
+ weapon.Signal( ARC_CANNON_SIGNAL_CHARGEEND )
+ #endif
+
+ #if CLIENT
+ if ( weapon.GetWeaponOwner() == GetLocalViewPlayer() )
+ {
+ entity weaponOwner
+ if ( player != null )
+ weaponOwner = player
+ else
+ weaponOwner = weapon.GetWeaponOwner()
+
+ if ( IsValid( weaponOwner ) && weaponOwner.IsPlayer() )
+ weaponOwner.StopArcCannon()
+ }
+ #endif
+}
+
+#if SERVER
+function ConvertTitanShieldIntoBonusCharge( entity soul, entity weapon )
+{
+ weapon.EndSignal( ARC_CANNON_SIGNAL_CHARGEEND )
+ weapon.EndSignal( "OnDestroy" )
+
+ local maxShieldDecay = OVERCHARGE_MAX_SHIELD_DECAY
+ local bonusChargeFraction = OVERCHARGE_BONUS_CHARGE_FRACTION
+ local shieldDecayMultiplier = OVERCHARGE_SHIELD_DECAY_MULTIPLIER
+ int shieldHealthMax = soul.GetShieldHealthMax()
+ local chargeRatio = GetArcCannonChargeFraction( weapon )
+
+ while( 1 )
+ {
+ if ( !IsValid( soul ) || !IsValid( weapon ) )
+ break
+
+ local baseCharge = GetWeaponChargeFrac( weapon ) // + GetOverchargeBonusChargeFraction()
+ local charge = clamp ( baseCharge * ( 1 / chargeRatio ), 0.0, 1.0 )
+ if ( charge < 1.0 || maxShieldDecay > 0)
+ {
+ int shieldHealth = soul.GetShieldHealth()
+
+ //Slight inconsistency in server updates, this ensures it never takes too much.
+ if ( shieldDecayMultiplier > maxShieldDecay )
+ shieldDecayMultiplier = maxShieldDecay
+ maxShieldDecay -= shieldDecayMultiplier
+
+ local shieldDecayAmount = shieldHealthMax * shieldDecayMultiplier
+ local newShieldAmount = shieldHealth - shieldDecayAmount
+ soul.SetShieldHealth( max( newShieldAmount, 0 ) )
+ soul.nextRegenTime = Time() + GetShieldRegenTime( soul )
+
+ if ( shieldDecayAmount > shieldHealth )
+ bonusChargeFraction = bonusChargeFraction * ( shieldHealth / shieldDecayAmount )
+ weapon.SetWeaponChargeFraction( baseCharge + bonusChargeFraction )
+ }
+ wait 0.1
+ }
+}
+#endif
+
+function FireArcCannon( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ local weaponScriptScope = weapon.GetScriptScope()
+ local baseCharge = GetWeaponChargeFrac( weapon ) // + GetOverchargeBonusChargeFraction()
+ local charge = clamp( baseCharge * ( 1 / GetArcCannonChargeFraction( weapon ) ), 0.0, 1.0 )
+ float newVolume = GraphCapped( charge, 0.25, 1.0, 0.0, 1.0 )
+
+ weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 )
+
+ weapon.PlayWeaponEffect( $"wpn_muzzleflash_arc_cannon_fp", $"wpn_muzzleflash_arc_cannon", "muzzle_flash" )
+
+ local attachmentName = "muzzle_flash"
+ local attachmentIndex = weapon.LookupAttachment( attachmentName )
+ Assert( attachmentIndex >= 0 )
+ local muzzleOrigin = weapon.GetAttachmentOrigin( attachmentIndex )
+
+ //printt( "-------- FIRING ARC CANNON --------" )
+
+ table firstTargetInfo = GetFirstArcCannonTarget( weapon, attackParams )
+ if ( !IsValid( firstTargetInfo.target ) )
+ FireArcNoTargets( weapon, attackParams, muzzleOrigin )
+ else
+ FireArcWithTargets( weapon, firstTargetInfo, attackParams, muzzleOrigin )
+
+ return 1
+}
+
+table function GetFirstArcCannonTarget( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ entity owner = weapon.GetWeaponOwner()
+ local coneHeight = weapon.GetMaxDamageFarDist()
+
+ local angleToAxis = 2 // set this too high and auto-titans using it will error on FindVisibleEntitiesInCone
+ array<entity> ignoredEntities = [ owner, weapon ]
+ int traceMask = TRACE_MASK_SHOT
+ int flags = VIS_CONE_ENTS_TEST_HITBOXES
+ local antilagPlayer = null
+ if ( owner.IsPlayer() )
+ {
+ angleToAxis = owner.GetAttackSpreadAngle() * 0.11
+ antilagPlayer = owner
+ }
+
+ int ownerTeam = owner.GetTeam()
+
+ // Get a missile target and a non-missile target in the cone that the player can zap
+ // We do this in a separate check so we can use a wider cone to be more forgiving for targeting missiles
+ table firstTargetInfo = {}
+ firstTargetInfo.target <- null
+ firstTargetInfo.hitLocation <- null
+
+ for ( int i = 0; i < 2; i++ )
+ {
+ local missileCheck = i == 0
+ local coneAngle = angleToAxis
+ if ( missileCheck && owner.IsPlayer() ) // missile check only if owner is player
+ coneAngle *= 8.0
+
+ coneAngle = clamp( coneAngle, 0.1, 89.9 )
+
+ array<VisibleEntityInCone> results = FindVisibleEntitiesInCone( attackParams.pos, attackParams.dir, coneHeight, coneAngle, ignoredEntities, traceMask, flags, antilagPlayer )
+ foreach ( result in results )
+ {
+ entity visibleEnt = result.ent
+
+ if ( !IsValid( visibleEnt ) )
+ continue
+
+ if ( visibleEnt.IsPhaseShifted() )
+ continue
+
+ local classname = IsServer() ? visibleEnt.GetClassName() : visibleEnt.GetSignifierName()
+
+ if ( !( classname in ArcCannonTargetClassnames ) )
+ continue
+
+ if ( "GetTeam" in visibleEnt )
+ {
+ int visibleEntTeam = visibleEnt.GetTeam()
+ if ( visibleEntTeam == ownerTeam )
+ continue
+ if ( IsEntANeutralMegaTurret( visibleEnt, ownerTeam ) )
+ continue
+ }
+
+ expect string( classname )
+ string targetname = visibleEnt.GetTargetName()
+
+ if ( missileCheck && ( classname != "rpg_missile" && !file.missileCheckTargetnames.contains( targetname ) ) )
+ continue
+
+ if ( !missileCheck && ( classname == "rpg_missile" || file.missileCheckTargetnames.contains( targetname ) ) )
+ continue
+
+ firstTargetInfo.target = visibleEnt
+ firstTargetInfo.hitLocation = result.visiblePosition
+ break
+ }
+ }
+ //Creating a whiz-by sound.
+ weapon.FireWeaponBullet_Special( attackParams.pos, attackParams.dir, 1, 0, true, true, true, true, true, false, false )
+
+ return firstTargetInfo
+}
+
+function FireArcNoTargets( entity weapon, WeaponPrimaryAttackParams attackParams, muzzleOrigin )
+{
+ Assert( IsValid( weapon ) )
+ entity player = weapon.GetWeaponOwner()
+ local chargeFrac = GetWeaponChargeFrac( weapon )
+ local beamVec = attackParams.dir * weapon.GetMaxDamageFarDist()
+ local playerEyePos = player.EyePosition()
+ TraceResults traceResults = TraceLineHighDetail( playerEyePos, (playerEyePos + beamVec), weapon, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE )
+ local beamEnd = traceResults.endPos
+
+ VortexBulletHit ornull vortexHit = VortexBulletHitCheck( player, playerEyePos, beamEnd )
+ if ( vortexHit )
+ {
+ expect VortexBulletHit( vortexHit )
+ #if SERVER
+ entity vortexWeapon = vortexHit.vortex.GetOwnerWeapon()
+ string className = vortexWeapon.GetWeaponClassName()
+ if ( vortexWeapon && ( className == "mp_titanweapon_vortex_shield" || className == "mp_titanweapon_vortex_shield_ion" ) )
+ {
+ // drain the vortex shield
+ VortexDrainedByImpact( vortexWeapon, weapon, null, null )
+ }
+ else if ( IsVortexSphere( vortexHit.vortex ) )
+ {
+ // do damage to vortex_sphere entities that isn't the titan "vortex shield"
+ local damageNear = weapon.GetWeaponInfoFileKeyField( "damage_near_value" )
+ local damage = damageNear * GraphCapped( chargeFrac, 0, 0.5, 0.0, 1.0 ) * 10 // do more damage the more charged the weapon is.
+ VortexSphereDrainHealthForDamage( vortexHit.vortex, damage )
+ }
+ #endif
+ beamEnd = vortexHit.hitPos
+ }
+
+ float radius = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_RADIUS_MIN, ARC_CANNON_BOLT_RADIUS_MAX )
+ local boltWidth = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_WIDTH_MIN, ARC_CANNON_BOLT_WIDTH_MAX )
+ if ( player.IsNPC() )
+ boltWidth = ARC_CANNON_BOLT_WIDTH_NPC
+ thread CreateArcCannonBeam( weapon, null, muzzleOrigin, beamEnd, player, ARC_CANNON_BEAM_LIFETIME, radius, boltWidth, 2, false, true )
+
+ #if SERVER
+ PlayImpactFXTable( expect vector( beamEnd ), player, ARC_CANNON_FX_TABLE, SF_ENVEXPLOSION_INCLUDE_ENTITIES )
+ #endif
+}
+
+function FireArcWithTargets( entity weapon, table firstTargetInfo, WeaponPrimaryAttackParams attackParams, muzzleOrigin )
+{
+ local beamStart = muzzleOrigin
+ local beamEnd
+ entity player = weapon.GetWeaponOwner()
+ local chargeFrac = GetWeaponChargeFrac( weapon )
+ float radius = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_RADIUS_MIN, ARC_CANNON_BOLT_RADIUS_MAX )
+ float boltWidth = Graph( chargeFrac, 0, 1, ARC_CANNON_BOLT_WIDTH_MIN, ARC_CANNON_BOLT_WIDTH_MAX )
+ local maxChains
+ local minChains
+
+ if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) )
+ {
+ if ( player.IsNPC() )
+ maxChains = ARC_CANNON_CHAIN_COUNT_NPC_BURN
+ else
+ maxChains = ARC_CANNON_CHAIN_COUNT_MAX_BURN
+
+ minChains = ARC_CANNON_CHAIN_COUNT_MIN_BURN
+ }
+ else
+ {
+ if ( player.IsNPC() )
+ maxChains = ARC_CANNON_CHAIN_COUNT_NPC
+ else
+ maxChains = ARC_CANNON_CHAIN_COUNT_MAX
+
+ minChains = ARC_CANNON_CHAIN_COUNT_MIN
+ }
+
+ if ( !player.IsNPC() )
+ maxChains = Graph( chargeFrac, 0, 1, minChains, maxChains )
+
+ table zapInfo = {}
+ zapInfo.weapon <- weapon
+ zapInfo.player <- player
+ zapInfo.muzzleOrigin <- muzzleOrigin
+ zapInfo.radius <- radius
+ zapInfo.boltWidth <- boltWidth
+ zapInfo.maxChains <- maxChains
+ zapInfo.chargeFrac <- chargeFrac
+ zapInfo.zappedTargets <- {}
+ zapInfo.zappedTargets[ firstTargetInfo.target ] <- true
+ zapInfo.dmgSourceID <- weapon.GetDamageSourceID()
+ local chainNum = 1
+ thread ZapTargetRecursive( expect entity( firstTargetInfo.target), zapInfo, zapInfo.muzzleOrigin, expect vector( firstTargetInfo.hitLocation ), chainNum )
+}
+
+function ZapTargetRecursive( entity target, table zapInfo, beamStartPos, vector ornull firstTargetBeamEndPos = null, chainNum = 1 )
+{
+ if ( !IsValid( target ) )
+ return
+
+ if ( !IsValid( zapInfo.weapon ) )
+ return
+
+ Assert( target in zapInfo.zappedTargets )
+ if ( chainNum > zapInfo.maxChains )
+ return
+ vector beamEndPos
+ if ( firstTargetBeamEndPos == null )
+ beamEndPos = target.GetWorldSpaceCenter()
+ else
+ beamEndPos = expect vector( firstTargetBeamEndPos )
+
+ waitthread ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum )
+
+ // Get other nearby targets we can chain to
+ #if SERVER
+ if ( !IsValid( zapInfo.weapon ) )
+ return
+
+ var noArcing = expect entity( zapInfo.weapon ).GetWeaponInfoFileKeyField( "disable_arc" )
+
+ if ( noArcing != null && noArcing == 1 )
+ return // no chaining on new arc cannon
+
+ // NOTE: 'target' could be invalid at this point (no corpse)
+ array<entity> chainTargets = GetArcCannonChainTargets( beamEndPos, target, zapInfo )
+ foreach( entity chainTarget in chainTargets )
+ {
+ local newChainNum = chainNum
+ if ( chainTarget.GetClassName() != "rpg_missile" )
+ newChainNum++
+ zapInfo.zappedTargets[ chainTarget ] <- true
+ thread ZapTargetRecursive( chainTarget, zapInfo, beamEndPos, null, newChainNum )
+ }
+
+ if ( IsValid( zapInfo.player ) && zapInfo.player.IsPlayer() && zapInfo.zappedTargets.len() >= 5 )
+ {
+ #if HAS_STATS
+ if ( chainNum == 5 )
+ UpdatePlayerStat( expect entity( zapInfo.player ), "misc_stats", "arcCannonMultiKills", 1 )
+ #endif
+ }
+ #endif
+}
+
+function ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum = 1 )
+{
+ expect entity( target )
+ expect vector( beamStartPos )
+ expect vector( beamEndPos )
+
+ //DebugDrawLine( beamStartPos, beamEndPos, 255, 0, 0, true, 5.0 )
+ local boltWidth = zapInfo.boltWidth
+ if ( zapInfo.player.IsNPC() )
+ boltWidth = ARC_CANNON_BOLT_WIDTH_NPC
+ local firstBeam = ( chainNum == 1 )
+ #if SERVER
+ if ( firstBeam )
+ {
+ PlayImpactFXTable( beamEndPos, expect entity( zapInfo.player ), ARC_CANNON_FX_TABLE, SF_ENVEXPLOSION_INCLUDE_ENTITIES )
+ }
+ #endif
+
+ thread CreateArcCannonBeam( zapInfo.weapon, target, beamStartPos, beamEndPos, zapInfo.player, ARC_CANNON_BEAM_LIFETIME, zapInfo.radius, boltWidth, 5, true, firstBeam )
+
+ #if SERVER
+ local isMissile = ( target.GetClassName() == "rpg_missile" )
+ if ( !isMissile )
+ wait ARC_CANNON_FORK_DELAY
+ else
+ wait 0.05
+
+ local deathPackage = damageTypes.arcCannon
+
+ float damageAmount
+ int damageMin
+ int damageMax
+
+ int damageFarValue = eWeaponVar.damage_far_value
+ int damageNearValue = eWeaponVar.damage_near_value
+ int damageFarValueTitanArmor = eWeaponVar.damage_far_value_titanarmor
+ int damageNearValueTitanArmor = eWeaponVar.damage_near_value_titanarmor
+ if ( zapInfo.player.IsNPC() )
+ {
+ damageFarValue = eWeaponVar.npc_damage_far_value
+ damageNearValue = eWeaponVar.npc_damage_near_value
+ damageFarValueTitanArmor = eWeaponVar.npc_damage_far_value_titanarmor
+ damageNearValueTitanArmor = eWeaponVar.npc_damage_near_value_titanarmor
+ }
+
+ if ( IsValid( target ) && IsValid( zapInfo.player ) )
+ {
+ bool hasFastPacitor = false
+ bool noArcing = false
+
+ if ( IsValid( zapInfo.weapon ) )
+ {
+ entity weap = expect entity( zapInfo.weapon )
+ hasFastPacitor = weap.GetWeaponInfoFileKeyField( "push_apart" ) != null && weap.GetWeaponInfoFileKeyField( "push_apart" ) == 1
+ noArcing = weap.GetWeaponInfoFileKeyField( "no_arcing" ) != null && weap.GetWeaponInfoFileKeyField( "no_arcing" ) == 1
+ }
+
+ if ( target.GetArmorType() == ARMOR_TYPE_HEAVY )
+ {
+ if ( IsValid( zapInfo.weapon ) )
+ {
+ entity weapon = expect entity( zapInfo.weapon )
+ damageMin = weapon.GetWeaponSettingInt( damageFarValueTitanArmor )
+ damageMax = weapon.GetWeaponSettingInt( damageNearValueTitanArmor )
+ }
+ else
+ {
+ damageMin = 100
+ damageMax = zapInfo.player.IsNPC() ? 1200 : 800
+ }
+ }
+ else
+ {
+ if ( IsValid( zapInfo.weapon ) )
+ {
+ entity weapon = expect entity( zapInfo.weapon )
+ damageMin = weapon.GetWeaponSettingInt( damageFarValue )
+ damageMax = weapon.GetWeaponSettingInt( damageNearValue )
+ }
+ else
+ {
+ damageMin = 120
+ damageMax = zapInfo.player.IsNPC() ? 140 : 275
+ }
+
+ if ( target.IsNPC() )
+ {
+ damageMin *= 3 // more powerful against NPC humans so they die easy
+ damageMax *= 3
+ }
+ }
+
+
+ local chargeRatio = GetArcCannonChargeFraction( zapInfo.weapon )
+ if ( IsValid( zapInfo.weapon ) && !zapInfo.weapon.GetWeaponSettingBool( eWeaponVar.charge_require_input ) )
+ {
+ // use distance for damage if the weapon auto-fires
+ entity weapon = expect entity( zapInfo.weapon )
+ float nearDist = weapon.GetWeaponSettingFloat( eWeaponVar.damage_near_distance )
+ float farDist = weapon.GetWeaponSettingFloat( eWeaponVar.damage_far_distance )
+
+ float dist = Distance( weapon.GetOrigin(), target.GetOrigin() )
+ damageAmount = GraphCapped( dist, farDist, nearDist, damageMin, damageMax )
+ }
+ else
+ {
+ // Scale damage amount based on how many chains deep we are
+ damageAmount = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, damageMin, damageMax )
+ }
+ local damageFalloff = ARC_CANNON_DAMAGE_FALLOFF_SCALER
+ if ( IsValid( zapInfo.weapon ) && zapInfo.weapon.HasMod( "splitter" ) )
+ damageFalloff = SPLITTER_DAMAGE_FALLOFF_SCALER
+ damageAmount *= pow( damageFalloff, chainNum - 1 )
+
+ local dmgSourceID = zapInfo.dmgSourceID
+
+ // Update Later - This shouldn't be done here, this is not where we determine if damage actually happened to the target
+ // move to Damaged callback instead
+ if ( damageAmount > 0 )
+ {
+ float empDuration = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, ARC_CANNON_EMP_DURATION_MIN, ARC_CANNON_EMP_DURATION_MAX )
+
+ if ( target.IsPlayer() && target.IsTitan() && !hasFastPacitor && !noArcing )
+ {
+ float empViewStrength = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, ARC_CANNON_SCREEN_EFFECTS_MIN, ARC_CANNON_SCREEN_EFFECTS_MAX )
+
+ if ( target.IsTitan() && zapInfo.chargeFrac >= ARC_CANNON_SCREEN_THRESHOLD )
+ {
+ Remote_CallFunction_Replay( target, "ServerCallback_TitanEMP", empViewStrength, empDuration, ARC_CANNON_EMP_FADEOUT_DURATION )
+ EmitSoundOnEntityOnlyToPlayer( target, target, ARC_CANNON_TITAN_SCREEN_SFX )
+ }
+ else if ( zapInfo.chargeFrac >= ARC_CANNON_SCREEN_THRESHOLD )
+ {
+ StatusEffect_AddTimed( target, eStatusEffect.emp, empViewStrength, empDuration, ARC_CANNON_EMP_FADEOUT_DURATION )
+ EmitSoundOnEntityOnlyToPlayer( target, target, ARC_CANNON_PILOT_SCREEN_SFX )
+ }
+ }
+
+ // Do 3rd person effect on the body
+ asset effect
+ string tag
+ target.TakeDamage( damageAmount, zapInfo.player, zapInfo.player, { origin = beamEndPos, force = Vector(0,0,0), scriptType = deathPackage, weapon = zapInfo.weapon, damageSourceId = dmgSourceID } )
+ //vector dir = Normalize( beamEndPos - beamStartPos )
+ //vector velocity = dir * 600
+ //PushPlayerAway( target, velocity )
+ //PushPlayerAway( expect entity( zapInfo.player ), -velocity )
+
+ if ( IsValid( zapInfo.weapon ) && hasFastPacitor )
+ {
+ if ( IsAlive( target ) && IsAlive( expect entity( zapInfo.player ) ) && target.IsTitan() )
+ {
+ float pushPercent = GraphCapped( damageAmount, damageMin, damageMax, 0.0, 1.0 )
+
+ if ( pushPercent > 0.6 )
+ PushPlayersApart( target, expect entity( zapInfo.player ), pushPercent * 400.0 )
+ }
+ }
+
+ if ( zapInfo.chargeFrac < ARC_CANNON_SCREEN_THRESHOLD )
+ empDuration = ARC_CANNON_3RD_PERSON_EFFECT_MIN_DURATION
+ else
+ empDuration += ARC_CANNON_EMP_FADEOUT_DURATION
+
+ if ( target.GetArmorType() == ARMOR_TYPE_HEAVY )
+ {
+ effect = $"impact_arc_cannon_titan"
+ tag = "exp_torso_front"
+ }
+ else
+ {
+ effect = $"P_emp_body_human"
+ tag = "CHESTFOCUS"
+ }
+
+ if ( target.IsPlayer() )
+ {
+ if ( target.LookupAttachment( tag ) != 0 )
+ ClientStylePlayFXOnEntity( effect, target, tag, empDuration )
+ }
+
+ if ( target.IsPlayer() )
+ EmitSoundOnEntityExceptToPlayer( target, target, "Titan_Blue_Electricity_Cloud" )
+ else
+ EmitSoundOnEntity( target, "Titan_Blue_Electricity_Cloud" )
+
+ thread FadeOutSoundOnEntityAfterDelay( target, "Titan_Blue_Electricity_Cloud", empDuration * 0.6666, empDuration * 0.3333 )
+ }
+ else
+ {
+ //Don't bounce if the beam is set to do 0 damage.
+ chainNum = zapInfo.maxChains
+ }
+
+ if ( isMissile )
+ {
+ if ( IsValid( zapInfo.player ) )
+ target.SetOwner( zapInfo.player )
+ target.MissileExplode()
+ }
+ }
+ #endif // SERVER
+}
+
+
+#if SERVER
+
+void function PushEntForTime( entity ent, vector velocity, float time )
+{
+ ent.EndSignal( "OnDeath" )
+ float endTime = Time() + time
+ float startTime = Time()
+ for ( ;; )
+ {
+ if ( Time() >= endTime )
+ break
+ float multiplier = Graph( Time(), startTime, endTime, 1.0, 0.0 )
+ vector currentVel = ent.GetVelocity()
+ currentVel += velocity * multiplier
+ ent.SetVelocity( currentVel )
+ WaitFrame()
+ }
+}
+
+array<entity> function GetArcCannonChainTargets( vector fromOrigin, entity fromTarget, table zapInfo )
+{
+ // NOTE: fromTarget could be null/invalid if it was a drone
+ array<entity> results = []
+ if ( !IsValid( zapInfo.player ) )
+ return results
+
+ int playerTeam = expect entity( zapInfo.player ).GetTeam()
+ array<entity> allTargets = GetArcCannonTargetsInRange( fromOrigin, playerTeam, expect entity( zapInfo.weapon ) )
+ allTargets = ArrayClosest( allTargets, fromOrigin )
+
+ local viewVector
+ if ( zapInfo.player.IsPlayer() )
+ viewVector = zapInfo.player.GetViewVector()
+ else
+ viewVector = AnglesToForward( zapInfo.player.EyeAngles() )
+
+ local eyePosition = zapInfo.player.EyePosition()
+
+ foreach ( ent in allTargets )
+ {
+ local forkCount = ARC_CANNON_FORK_COUNT_MAX
+ if ( zapInfo.weapon.HasMod( "splitter" ) )
+ forkCount = SPLITTER_FORK_COUNT_MAX
+ else if ( zapInfo.weapon.HasMod( "burn_mod_titan_arc_cannon" ) )
+ forkCount = ARC_CANNON_FORK_COUNT_MAX_BURN
+
+ if ( results.len() >= forkCount )
+ break
+
+ if ( ent.IsPhaseShifted() )
+ continue
+
+ if ( ent.IsPlayer() )
+ {
+ // Ignore players that are passing damage to their parent. This is to address zapping a friendly rodeo player
+ local entParent = ent.GetParent()
+ if ( IsValid( entParent ) && ent.kv.PassDamageToParent.tointeger() )
+ continue
+
+ // only chains to other titan players for now
+ if ( !ent.IsTitan() )
+ continue
+ }
+
+ if ( ent.GetClassName() == "script_mover" )
+ continue
+
+ if ( IsEntANeutralMegaTurret( ent, playerTeam ) )
+ continue
+
+ if ( !IsAlive( ent ) )
+ continue
+
+ // Don't consider targets that already got zapped
+ if ( ent in zapInfo.zappedTargets )
+ continue
+
+ //Preventing the arc-cannon from firing behind.
+ local vecToEnt = ( ent.GetWorldSpaceCenter() - eyePosition )
+ vecToEnt.Norm()
+ local dotVal = DotProduct( vecToEnt, viewVector )
+ if ( dotVal < 0 )
+ continue
+
+ // Check if we can see them, they aren't behind a wall or something
+ local ignoreEnts = []
+ ignoreEnts.append( zapInfo.player )
+ ignoreEnts.append( ent )
+
+ foreach( zappedTarget, val in zapInfo.zappedTargets )
+ {
+ if ( IsValid( zappedTarget ) )
+ ignoreEnts.append( zappedTarget )
+ }
+
+ TraceResults traceResult = TraceLineHighDetail( fromOrigin, ent.GetWorldSpaceCenter(), ignoreEnts, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE )
+
+ // Trace failed, lets try an eye to eye trace
+ if ( traceResult.fraction < 1 )
+ {
+ // 'fromTarget' may be invalid
+ if ( IsValid( fromTarget ) )
+ traceResult = TraceLineHighDetail( fromTarget.EyePosition(), ent.EyePosition(), ignoreEnts, (TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS), TRACE_COLLISION_GROUP_NONE )
+ }
+
+ if ( traceResult.fraction < 1 )
+ continue
+
+ // Enemy is in visible, and within range.
+ if ( !results.contains( ent ) )
+ results.append( ent )
+ }
+
+ //printt( "NEARBY TARGETS VALID AND VISIBLE:", results.len() )
+
+ return results
+}
+#endif // SERVER
+
+bool function IsEntANeutralMegaTurret( ent, int playerTeam )
+{
+ expect entity( ent )
+
+ if ( ent.GetClassName() != "npc_turret_mega" )
+ return false
+ int entTeam = ent.GetTeam()
+ if ( entTeam == playerTeam )
+ return false
+ if ( !IsEnemyTeam( playerTeam, entTeam ) )
+ return true
+
+ return false
+}
+
+function ArcCannon_HideIdleEffect( entity weapon, delay )
+{
+ bool weaponOwnerIsPilot = IsPilot( weapon.GetWeaponOwner() )
+ weapon.EndSignal( ARC_CANNON_SIGNAL_DEACTIVATED )
+ if ( weaponOwnerIsPilot == false )
+ {
+ weapon.StopWeaponEffect( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity" )
+ weapon.StopWeaponSound( "arc_cannon_charged_loop" )
+ }
+ wait delay
+
+ if ( !IsValid( weapon ) )
+ return
+
+ entity weaponOwner = weapon.GetWeaponOwner()
+ //The weapon can be valid, but the player isn't a Titan during melee execute.
+ // JFS: threads with waits should just end on "OnDestroy"
+ if ( !IsValid( weaponOwner ) )
+ return
+
+ if ( weapon != weaponOwner.GetActiveWeapon() )
+ return
+
+ if ( weaponOwnerIsPilot == false )
+ {
+ weapon.PlayWeaponEffectNoCull( $"wpn_arc_cannon_electricity_fp", $"wpn_arc_cannon_electricity", "muzzle_flash" )
+ weapon.EmitWeaponSound( "arc_cannon_charged_loop" )
+ }
+ else
+ {
+ weapon.EmitWeaponSound_1p3p( "Arc_Rifle_charged_Loop_1P", "Arc_Rifle_charged_Loop_3P" )
+ }
+}
+
+#if SERVER
+void function AddToArcCannonTargets( entity ent )
+{
+ AddToScriptManagedEntArray( level._arcCannonTargetsArrayID, ent );
+}
+
+function RemoveArcCannonTarget( ent )
+{
+ RemoveFromScriptManagedEntArray( level._arcCannonTargetsArrayID, ent )
+}
+
+array<entity> function GetArcCannonTargets( vector origin, int team )
+{
+ array<entity> targets = GetScriptManagedEntArrayWithinCenter( level._arcCannonTargetsArrayID, team, origin, ARC_CANNON_TITAN_RANGE_CHAIN )
+
+ if ( ARC_CANNON_TARGETS_MISSILES )
+ targets.extend( GetProjectileArrayEx( "rpg_missile", TEAM_ANY, team, origin, ARC_CANNON_TITAN_RANGE_CHAIN ) )
+
+ return targets
+}
+
+array<entity> function GetArcCannonTargetsInRange( vector origin, int team, entity weapon )
+{
+ array<entity> allTargets = GetArcCannonTargets( origin, team )
+ array<entity> targetsInRange
+
+ float titanDistSq
+ float distSq
+ if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) )
+ {
+ titanDistSq = ARC_CANNON_TITAN_RANGE_CHAIN_BURN * ARC_CANNON_TITAN_RANGE_CHAIN_BURN
+ distSq = ARC_CANNON_RANGE_CHAIN_BURN * ARC_CANNON_RANGE_CHAIN_BURN
+ }
+ else
+ {
+ titanDistSq = ARC_CANNON_TITAN_RANGE_CHAIN * ARC_CANNON_TITAN_RANGE_CHAIN
+ distSq = ARC_CANNON_RANGE_CHAIN * ARC_CANNON_RANGE_CHAIN
+ }
+
+ foreach( target in allTargets )
+ {
+ float d = DistanceSqr( target.GetOrigin(), origin )
+ float validDist = target.IsTitan() ? titanDistSq : distSq
+ if ( d <= validDist )
+ targetsInRange.append( target )
+ }
+
+ return targetsInRange
+}
+#endif // SERVER
+
+function CreateArcCannonBeam( weapon, target, startPos, endPos, player, lifeDuration = ARC_CANNON_BEAM_LIFETIME, radius = 256, boltWidth = 4, noiseAmplitude = 5, hasTarget = true, firstBeam = false )
+{
+ Assert( startPos )
+ Assert( endPos )
+
+ //**************************
+ // LIGHTNING BEAM EFFECT
+ //**************************
+ if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) )
+ lifeDuration = ARC_CANNON_BEAM_LIFETIME_BURN
+ // If it's the first beam and on client we do a special beam so it's lined up with the muzzle origin
+ #if CLIENT
+ if ( firstBeam )
+ thread CreateClientArcBeam( weapon, endPos, lifeDuration, target )
+ #endif
+
+ #if SERVER
+ // Control point sets the end position of the effect
+ entity cpEnd = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpEnd, UniqueString( "arc_cannon_beam_cpEnd" ) )
+ cpEnd.SetOrigin( endPos )
+ DispatchSpawn( cpEnd )
+
+ entity zapBeam = CreateEntity( "info_particle_system" )
+ zapBeam.kv.cpoint1 = cpEnd.GetTargetName()
+
+ zapBeam.SetValueForEffectNameKey( GetBeamEffect( weapon ) )
+
+ zapBeam.kv.start_active = 0
+ zapBeam.SetOwner( player )
+ zapBeam.SetOrigin( startPos )
+ if ( firstBeam )
+ {
+ zapBeam.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // everyone but owner
+ zapBeam.SetParent( player.GetActiveWeapon(), "muzzle_flash", false, 0.0 )
+ }
+ DispatchSpawn( zapBeam )
+
+ zapBeam.Fire( "Start" )
+ zapBeam.Fire( "StopPlayEndCap", "", lifeDuration )
+ zapBeam.Kill_Deprecated_UseDestroyInstead( lifeDuration )
+ cpEnd.Kill_Deprecated_UseDestroyInstead( lifeDuration )
+ #endif
+}
+
+function GetBeamEffect( weapon )
+{
+ if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) )
+ return ARC_CANNON_BEAM_EFFECT_MOD
+
+ return ARC_CANNON_BEAM_EFFECT
+}
+
+#if CLIENT
+function CreateClientArcBeam( weapon, endPos, lifeDuration, target )
+{
+ Assert( IsClient() )
+
+ local beamEffect = GetBeamEffect( weapon )
+
+ // HACK HACK HACK HACK
+ string tag = "muzzle_flash"
+ if ( weapon.GetWeaponInfoFileKeyField( "client_tag_override" ) != null )
+ tag = expect string( weapon.GetWeaponInfoFileKeyField( "client_tag_override" ) )
+
+ local handle = weapon.PlayWeaponEffectReturnViewEffectHandle( beamEffect, $"", tag )
+ if ( !EffectDoesExist( handle ) )
+ return
+
+ EffectSetControlPointVector( handle, 1, endPos )
+
+ if ( weapon.HasMod( "burn_mod_titan_arc_cannon" ) )
+ lifeDuration = ARC_CANNON_BEAM_LIFETIME_BURN
+
+ wait( lifeDuration )
+
+ if ( IsValid( weapon ) )
+ weapon.StopWeaponEffect( beamEffect, $"" )
+}
+
+void function ClientDestroyCallback_ArcCannon_Stop( entity ent )
+{
+ ArcCannon_Stop( ent )
+}
+#endif // CLIENT
+
+function GetArcCannonChargeFraction( weapon )
+{
+ if ( IsValid( weapon ) )
+ {
+ local chargeRatio = ARC_CANNON_DAMAGE_CHARGE_RATIO
+ if ( weapon.HasMod( "capacitor" ) )
+ chargeRatio = ARC_CANNON_CAPACITOR_CHARGE_RATIO
+ if ( weapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) )
+ chargeRatio = ARC_CANNON_DAMAGE_CHARGE_RATIO_BURN
+ return chargeRatio
+ }
+
+ return 0
+}
+
+function GetWeaponChargeFrac( weapon )
+{
+ if ( weapon.IsChargeWeapon() )
+ return weapon.GetWeaponChargeFraction()
+ return 1.0
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_at_turrets.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_at_turrets.gnut
new file mode 100644
index 000000000..b061c1824
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_at_turrets.gnut
@@ -0,0 +1,284 @@
+untyped
+
+global function ATTurrets_Init
+global function CreateATTurret
+global function ATTurretSettings
+//global function SetDriverOnTurret
+global function PROTO_ATTurretsEnabled
+global function PROTO_Simulate_Turret_Use
+
+const USE_DEBOUNCE_TIME = 0.3
+const FX_ANTI_TITAN_SHIELD_WALL = $"P_anti_titan_shield_3P"
+const vector AT_TURRET_SHIELD_COLOR = Vector( 115, 247, 255 )
+
+void function ATTurrets_Init()
+{
+ AddSpawnCallbackEditorClass( "turret", "turret_pilot_at", ATTurretSettings )
+ RegisterSignal( "ClearDriver" )
+ RegisterSignal( "DismebarkATTurret" )
+}
+
+void function CreateATTurret( vector origin, vector angles )
+{
+ entity turret = CreateEntity( "turret" )
+ turret.kv.editorclass = "turret_pilot_at"
+ turret.kv.settings = "PROTO_at_turret"
+ turret.kv.teamnumber = 0
+ turret.SetValueForModelKey( $"models/weapons/sentry_turret/sentry_turret.mdl" )
+ turret.kv.origin = origin
+ turret.kv.angles = angles
+ DispatchSpawn( turret )
+ ATTurretSettings( turret )
+}
+
+void function ATTurretSettings( entity turret )
+{
+ if ( PROTO_ATTurretsEnabled() )
+ {
+ turret.SetUsable()
+ turret.SetUsableByGroup( "pilot" )
+ turret.SetUsePrompts( "Hold %use% to use AT-Turret", "Press %use% to use AT-Turret" )
+ //AddCallback_OnUseEntity( turret, SetDriverOnTurret )
+ AddCallback_OnUseEntity( turret, PROTO_Simulate_Turret_Use )
+
+ local attachmentID = turret.LookupAttachment( "muzzle_flash" )
+ local origin = turret.GetAttachmentOrigin( attachmentID )
+ local angles = turret.GetAttachmentAngles( attachmentID )
+
+ entity cpoint = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpoint, UniqueString( "shield_wall_controlpoint" ) )
+ DispatchSpawn( cpoint )
+
+ turret.e.shieldWallFX = CreateEntity( "info_particle_system" )
+ entity shieldWallFX = turret.e.shieldWallFX
+ shieldWallFX.SetValueForEffectNameKey( FX_ANTI_TITAN_SHIELD_WALL )
+ shieldWallFX.kv.start_active = 1
+ SetShieldWallCPoint( shieldWallFX, cpoint )
+ shieldWallFX.SetOwner( turret )
+ shieldWallFX.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // not owner only
+ shieldWallFX.kv.cpoint1 = cpoint.GetTargetName()
+ shieldWallFX.SetStopType( "destroyImmediately" )
+ shieldWallFX.SetOrigin( origin )
+ shieldWallFX.SetAngles( angles - Vector(0,0,90) )
+ shieldWallFX.SetParent( turret, "muzzle_flash", true, 0.0 )
+ DispatchSpawn( shieldWallFX )
+ SetShieldWallCPointOrigin( shieldWallFX, AT_TURRET_SHIELD_COLOR )
+ }
+ else
+ {
+ turret.DisableDraw()
+ turret.NotSolid()
+ }
+}
+
+bool function PROTO_ATTurretsEnabled()
+{
+ return ( GetCurrentPlaylistVarInt( "at_turrets_enabled", 0 ) == 1 )
+}
+
+/*/////////////////////////////////////////////////////////////////
+ WEAPON PROTOTYPE
+///////////////////////////////////////////////////////////////////*/
+
+function PROTO_Simulate_Turret_Use( turret, player )
+{
+ expect entity( turret )
+ expect entity( player )
+
+ if ( Time() < player.p.PROTO_UseDebounceEndTime )
+ return
+
+ PROTO_ActivateTurret( turret, player )
+}
+
+const array<int> TURRET_CANCEL_BUTTONS =
+[
+ IN_USE,
+ IN_DUCK,
+ IN_DUCKTOGGLE,
+ IN_WEAPON_CYCLE,
+ IN_MELEE,
+ IN_OFFHAND0,
+ IN_OFFHAND1,
+ IN_OFFHAND2,
+ IN_OFFHAND3,
+ IN_OFFHAND4,
+ IN_JUMP
+]
+
+void function PROTO_ActivateTurret( entity turret, entity player )
+{
+ if ( turret.GetOwner() == player )
+ {
+ player.Signal( "DismebarkATTurret" )
+ }
+ else
+ {
+ if ( turret.GetOwner() == null )
+ {
+ turret.DisableDraw()
+ turret.NotSolid()
+ SetShieldWallCPointOrigin( turret.e.shieldWallFX, < 0, 0, 0 > )
+ turret.SetOwner( player )
+ player.p.PROTO_UseDebounceEndTime = Time() + USE_DEBOUNCE_TIME
+ foreach( int button in TURRET_CANCEL_BUTTONS )
+ AddButtonPressedPlayerInputCallback( player, button, PROTO_DisembarkATTurret )
+ AddEntityCallback_OnDamaged( player, PlayerDamagedWhileOnTurret )
+ thread MonitorPilot( turret, player )
+ }
+ else
+ {
+ SendHudMessage( player, "Turret in use.", -1, 0.4, 255, 255, 0, 255, 0.0, 0.5, 0.0 )
+ }
+ }
+}
+
+void function PlayerDamagedWhileOnTurret( entity player, var damageInfo )
+{
+ if ( Time() < player.p.PROTO_UseDebounceEndTime )
+ return
+
+ player.Signal( "DismebarkATTurret" )
+}
+
+function MonitorPilot( entity turret, entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "DismebarkATTurret")
+ turret.EndSignal( "OnDestroy" )
+
+ player.ForceStand()
+ entity playerMover = CreateOwnedScriptMover( player )
+ player.SetParent( playerMover, "ref", true )
+ vector forward = turret.GetForwardVector()
+ vector basePos = turret.GetOrigin() + forward * -25
+ vector startOrigin = player.GetOrigin()
+ float moveTime = 0.1
+ playerMover.NonPhysicsMoveTo( basePos, moveTime, 0.0, 0.0 )
+ playerMover.NonPhysicsRotateTo( turret.GetAngles(), moveTime, 0, 0 )
+ player.FreezeControlsOnServer()
+
+ StorePilotWeapons( player )
+
+ OnThreadEnd(
+ function() : ( turret, player, playerMover, startOrigin )
+ {
+ if ( IsValid( player ) )
+ {
+ player.ClearParent()
+ player.UnforceStand()
+ ClearPlayerAnimViewEntity( player )
+ player.UnfreezeControlsOnServer()
+ RetrievePilotWeapons( player )
+ ViewConeZeroInstant( player )
+ foreach( int button in TURRET_CANCEL_BUTTONS )
+ RemoveButtonPressedPlayerInputCallback( player, button, PROTO_DisembarkATTurret )
+ RemoveEntityCallback_OnDamaged( player, PlayerDamagedWhileOnTurret )
+ player.p.PROTO_UseDebounceEndTime = Time() + USE_DEBOUNCE_TIME
+ PutEntityInSafeSpot( player, turret, null, startOrigin, player.GetOrigin() )
+ }
+
+ if ( IsValid( turret ) )
+ {
+ turret.EnableDraw()
+ turret.Solid()
+ SetShieldWallCPointOrigin( turret.e.shieldWallFX, AT_TURRET_SHIELD_COLOR )
+ turret.SetOwner( null )
+ }
+
+ playerMover.Destroy()
+ }
+ )
+
+ wait moveTime
+
+ player.PlayerCone_SetSpecific( forward )
+ ViewConeZeroInstant( player )
+
+ // PROTO: Supporting ability to pick different turret weapons for turrets in LevelEd and the legacy Defender prototype turret
+ // We need a predator cannon style turret in SP.
+ if ( IsMultiplayer() )
+ {
+ //player.GiveWeapon( "mp_weapon_smr", [ "PROTO_at_turret" ] )
+ //player.SetActiveWeaponByName( "mp_weapon_smr" )
+
+ // modded code: smr's at turret mod does exist in release tf2
+ player.GiveWeapon( "mp_weapon_defender", [ "PROTO_at_turret" ] )
+ player.SetActiveWeaponByName( "mp_weapon_defender" )
+ }
+ else if ( turret.HasKey( "weaponsettings" ) )
+ {
+ // See if we have any special turret mods on this weapon
+ array<string> turretMods = []
+ array<string> mods = GetWeaponMods_Global( turret.kv.weaponsettings )
+ foreach ( mod in mods )
+ {
+ if ( mod.find( "PROTO_at_turret" ) == 0 )
+ turretMods.append( "PROTO_at_turret" )
+ }
+
+ player.GiveWeapon( turret.kv.weaponsettings, turretMods )
+ player.SetActiveWeaponByName( turret.kv.weaponsettings )
+ }
+
+ wait 0.1
+
+ player.UnfreezeControlsOnServer()
+
+ ViewConeLockedForward( player )
+
+ player.WaitSignal( "OnDeath" )
+}
+
+void function PROTO_DisembarkATTurret( entity player )
+{
+ if ( Time() < player.p.PROTO_UseDebounceEndTime )
+ return
+
+ player.Signal( "DismebarkATTurret" )
+}
+
+/*/////////////////////////////////////////////////////////////////
+ TURRET ENTITY PROTOTYPE
+///////////////////////////////////////////////////////////////////*/
+//
+//function SetDriverOnTurret( turret, player )
+//{
+// if ( turret.GetOwner() == player )
+// {
+// turret.SetOwner( null )
+// turret.ClearDriver()
+// player.Signal( "ClearDriver" )
+// }
+// else
+// {
+// entity oldOwner = expect entity( turret.GetOwner() )
+// if ( oldOwner != null )
+// {
+// oldOwner.Signal( "ClearDriver" )
+// turret.ClearDriver()
+// }
+// turret.SetOwner( player )
+// turret.SetDriver( player )
+// thread ClearDriverOnDeath( turret, player )
+// }
+// //turret.SetUsePrompts( "DEACTIVATE", "DEACTIVATE" )
+// //turret.SetOwner( player )
+// //turret.SetBossPlayer( player )
+// //turret.SetUsableByGroup( "owner pilot" )
+//}
+//
+//function ClearDriverOnDeath( turret, player )
+//{
+// player.EndSignal( "ClearDriver" )
+// player.EndSignal( "OnDestroy" )
+// turret.EndSignal( "OnDestroy" )
+//
+// player.WaitSignal( "OnDeath" )
+//
+// if ( IsValid( turret ) )
+// turret.ClearDriver()
+//}
+////TODO: Handle death and handle deactivate.
+//
+// \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_ball_lightning.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_ball_lightning.gnut
new file mode 100644
index 000000000..9aae59e54
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_ball_lightning.gnut
@@ -0,0 +1,363 @@
+untyped
+
+global function BallLightning_Init
+global function AttachBallLightning
+global function AttachBallLightningToProp
+global function CreateBallLightning
+global function DestroyBallLightningOnEnt
+global function GetBallLightningFromEnt
+
+global function RegisterBallLightningDamage
+
+global function BallLightningZapFX
+global function BallLightningZapTargets
+global function BallLightningZapConnectionFX
+
+struct {
+ table< string, float > uniqueStrings
+} file
+
+function BallLightning_Init()
+{
+ PrecacheParticleSystem( BALL_LIGHTNING_ZAP_FX )
+
+ if ( BALL_LIGHTNING_FX_TABLE != "" )
+ PrecacheImpactEffectTable( BALL_LIGHTNING_FX_TABLE )
+
+ RegisterBallLightningDamage( eDamageSourceId.mp_weapon_arc_launcher )
+ RegisterBallLightningDamage( eDamageSourceId.mp_titanweapon_arc_ball )
+ RegisterBallLightningDamage( eDamageSourceId.mp_weapon_arc_trap )
+}
+
+function AttachBallLightning( entity weapon, entity projectile )
+{
+ Assert( !( "ballLightning" in projectile.s ) )
+
+ int damageSourceId
+ entity owner
+
+ if ( weapon.IsProjectile() )
+ {
+ owner = weapon.GetOwner()
+ damageSourceId = weapon.ProjectileGetDamageSourceID()
+ }
+ else
+ {
+ owner = weapon.GetWeaponOwner()
+ damageSourceId = weapon.GetDamageSourceID()
+ }
+
+
+ entity ball = CreateBallLightning( owner, damageSourceId, projectile.GetOrigin(), projectile.GetAngles() )
+ ball.SetParent( projectile )
+ projectile.s.ballLightning <- ball
+}
+
+void function DestroyBallLightningOnEnt( entity prop )
+{
+ if ( "ballLightning" in prop.s )
+ {
+ prop.s.ballLightning.Destroy()
+ delete prop.s.ballLightning
+ }
+}
+
+
+entity function AttachBallLightningToProp( entity prop, entity owner, int damageSourceId )
+{
+ entity ball = CreateBallLightning( owner, damageSourceId, prop.GetOrigin(), prop.GetAngles() )
+ ball.SetParent( prop )
+ prop.s.ballLightning <- ball
+ return ball
+}
+
+entity function CreateBallLightning( entity owner, int damageSourceId, vector origin, vector angles )
+{
+ entity ballLightning = CreateScriptMover( origin, angles )
+ ballLightning.SetOwner( owner )
+ SetTeam( ballLightning, owner.GetTeam() )
+
+ thread BallLightningThink( ballLightning, damageSourceId )
+ return ballLightning
+}
+
+void function RegisterBallLightningDamage( int damageSourceId )
+{
+ AddDamageCallbackSourceID( damageSourceId, OnBallLightningDamage )
+}
+
+void function OnBallLightningDamage( entity victim, var damageInfo )
+{
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ if ( damage <= 0 )
+ return
+
+ if ( victim.IsWorld() )
+ return
+
+ if ( victim.IsProjectile() )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & (DF_EXPLOSION | DF_IMPACT) )
+ return
+
+ // if ( IsHumanSized( victim ) )
+ // {
+ // DamageInfo_SetDamage( damageInfo, 0 )
+ // return
+ // }
+
+ entity ballLightning = DamageInfo_GetInflictor( damageInfo )
+
+ if ( victim == ballLightning )
+ return
+
+ if ( victim.GetParent() == ballLightning )
+ return
+
+ if ( !IsTargetEntValid( ballLightning, victim, ballLightning.e.ballLightningData ) )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ vector origin = DamageInfo_GetDamagePosition( damageInfo )
+ int hitBox = DamageInfo_GetHitBox( damageInfo )
+
+ string tag = GetEntityCenterTag( victim )
+ thread BallLightningZapConnectionFX( ballLightning, victim, tag, ballLightning.e.ballLightningData )
+ BallLightningZapFX( ballLightning, victim, tag, ballLightning.e.ballLightningData )
+}
+
+void function BallLightningThink( entity ballLightning, int damageSourceId )
+{
+ ballLightning.EndSignal( "OnDestroy" )
+
+ EmitSoundOnEntity( ballLightning, "Weapon_Arc_Ball_Loop" )
+
+ local data = {}
+
+ OnThreadEnd(
+ function() : ( ballLightning, data )
+ {
+ if ( IsValid( ballLightning ) )
+ StopSoundOnEntity( ballLightning, "Weapon_Arc_Ball_Loop" )
+ }
+ )
+
+ int inflictorTeam = ballLightning.GetTeam()
+ ballLightning.e.ballLightningTargetsIdx = CreateScriptManagedEntArray()
+
+ WaitEndFrame()
+
+ while( 1 )
+ {
+ for( int i=0; i<BALL_LIGHTNING_BURST_NUM; i++ )
+ {
+ if ( BALL_LIGHTNING_BURST_NUM > 1 )
+ wait BALL_LIGHTNING_BURST_PAUSE
+
+ vector origin = ballLightning.GetOrigin()
+ BallLightningZapTargets( ballLightning, origin, inflictorTeam, damageSourceId, ballLightning.e.ballLightningData, false )
+ }
+ wait BALL_LIGHTNING_BURST_DELAY
+ }
+}
+
+void function BallLightningZapTargets( entity ballLightning, vector origin, int inflictorTeam, int damageSourceId, BallLightningData fxData, bool single )
+{
+ RadiusDamage(
+ origin, // origin
+ ballLightning.GetOwner(), // owner
+ ballLightning, // inflictor
+ fxData.damageToPilots, // normal damage
+ fxData.damage, // heavy armor damage
+ fxData.radius, // inner radius
+ fxData.radius, // outer radius
+ SF_ENVEXPLOSION_NO_DAMAGEOWNER, // explosion flags
+ 0, // distanceFromAttacker
+ 0, // explosionForce
+ fxData.deathPackage, // damage flags
+ damageSourceId // damage source id
+ )
+}
+
+string function GetEntityCenterTag( entity target )
+{
+ string tag = "center"
+
+ if ( IsHumanSized( target ) )
+ tag = "CHESTFOCUS"
+ else if ( target.IsTitan() )
+ tag = "HIJACK"
+ else if ( IsSuperSpectre( target ) || IsAirDrone( target ) )
+ tag = "CHESTFOCUS"
+ else if ( IsDropship( target ) )
+ tag = "ORIGIN"
+ else if ( target.GetClassName() == "npc_turret_mega" )
+ tag = "ATTACH"
+
+ return tag
+}
+
+bool function IsTargetEntValid( entity ballLightning, entity target, BallLightningData fxData )
+{
+ if ( !IsValid( target ) )
+ return false
+
+ vector origin = ballLightning.GetOrigin()
+
+ if ( target == ballLightning )
+ return false
+
+ if ( target == ballLightning.GetParent() )
+ return false
+
+ if ( target.GetParent() == ballLightning.GetParent() )
+ return false
+
+ // if ( target.IsPlayer() && !target.IsTitan() )
+ // return false
+
+ if ( fabs( origin.z - target.GetOrigin().z ) > fxData.height )
+ return false
+
+ if ( GetBugReproNum() != 131703 )
+ {
+ if ( target.GetModelName() == $"" )
+ return false
+ }
+
+ if ( !( target.GetClassName() in ArcCannonTargetClassnames ) )
+ return false
+
+ vector entityCenter = target.GetWorldSpaceCenter()
+
+ if ( target.GetModelName() != $"" )
+ {
+ string tag = GetEntityCenterTag( target )
+ int index = target.LookupAttachment( tag )
+
+ if ( index == 0 )
+ return false
+
+ entityCenter = target.GetAttachmentOrigin( index )
+ }
+
+ vector fwd = AnglesToForward( ballLightning.GetAngles() )
+ vector fwdToEnemy = Normalize( entityCenter - ballLightning.GetOrigin() )
+
+ float dot = DotProduct( fwd, fwdToEnemy )
+
+ if ( dot < fxData.minDot )
+ return false
+
+
+ if ( IsHumanSized( target ) )
+ {
+ float maxDist = fxData.humanRadius
+ if ( Distance( entityCenter, ballLightning.GetOrigin() ) > maxDist )
+ return false
+ }
+
+ // array<entity> ignoreEnts = [ target, ballLightning ]
+ // if ( ballLightning.GetParent() != null )
+ // ignoreEnts.append( ballLightning.GetParent() )
+
+ // TraceResults trace = TraceLine( ballLightning.GetOrigin(), entityCenter, ignoreEnts, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS )
+
+ // if ( trace.fraction < 1 )
+ // return false
+
+ VortexBulletHit ornull vortexHit = VortexBulletHitCheck( ballLightning.GetOwner(), ballLightning.GetOrigin(), entityCenter )
+
+ if ( vortexHit )
+ return false
+
+ return true
+}
+
+void function BallLightningZapConnectionFX( entity ballLightning, entity target, string tag, BallLightningData fxData )
+{
+ if ( fxData.zapFx != $"" )
+ {
+ // Control point sets the end position of the effect
+ entity cpEnd = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpEnd, GetUniqueCpString() )
+ cpEnd.SetParent( target, tag, false, 0.0 )
+ DispatchSpawn( cpEnd )
+
+ entity zapBeam = CreateEntity( "info_particle_system" )
+ zapBeam.kv.cpoint1 = cpEnd.GetTargetName()
+
+ zapBeam.SetValueForEffectNameKey( fxData.zapFx )
+ zapBeam.kv.start_active = 0
+ zapBeam.SetOwner( ballLightning )
+ zapBeam.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ zapBeam.SetParent( ballLightning, "", false, 0.0 )
+ DispatchSpawn( zapBeam )
+
+ zapBeam.Fire( "Start" )
+
+ OnThreadEnd(
+ function() : ( zapBeam, cpEnd )
+ {
+ if ( IsValid( zapBeam ) )
+ zapBeam.Destroy()
+ if ( IsValid( cpEnd ) )
+ cpEnd.Destroy()
+ }
+ )
+
+ ballLightning.EndSignal( "OnDestroy" )
+ target.EndSignal( "OnDestroy" )
+ target.EndSignal( "OnDeath" )
+
+ if ( fxData.zapLifetime > 0 )
+ {
+ wait fxData.zapLifetime
+ }
+ }
+}
+
+void function BallLightningZapFX( entity ballLightning, entity target, string tag, BallLightningData fxData )
+{
+ int index = target.LookupAttachment( tag )
+
+ vector entityCenter = target.GetAttachmentOrigin( index )
+
+ if ( fxData.zapImpactTable != "" )
+ PlayImpactFXTable( entityCenter, ballLightning.GetOwner(), fxData.zapImpactTable, SF_ENVEXPLOSION_INCLUDE_ENTITIES )
+
+ EmitSoundOnEntity( ballLightning, fxData.zapSound )
+ thread FadeOutSoundOnEntityAfterDelay( ballLightning, fxData.zapSound, 0.2, 0.2 )
+}
+
+// This is to minimize creation of new Unique Strings
+string function GetUniqueCpString()
+{
+ foreach ( string uString, float useTime in file.uniqueStrings )
+ {
+ if ( useTime + BALL_LIGHTNING_ZAP_LIFETIME*2 > Time() )
+ continue
+
+ file.uniqueStrings[ uString ] = Time()
+ return uString
+ }
+
+ string newString = UniqueString( "ball_lightning_cpEnd" )
+
+ // printt( "Generated new string " + newString )
+
+ file.uniqueStrings[ newString ] <- Time()
+ return newString
+}
+
+entity function GetBallLightningFromEnt( entity ent )
+{
+ if ( "ballLightning" in ent.s )
+ return expect entity( ent.s.ballLightning )
+
+ return null
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_cloaker.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_cloaker.gnut
new file mode 100644
index 000000000..6ec0bc0ac
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_cloaker.gnut
@@ -0,0 +1,121 @@
+untyped
+
+global function CloakerThink
+global function CloakerShouldCloakGuy
+
+global function CloakerCloaksGuy
+global function CloakerDeCloaksGuy
+
+function CloakerThink( entity cloaker, float radius, array<string> ents = [ "any" ], vector offset = Vector(0,0,0), var shouldCloakGuyFunc = null, float waitTime = 0.25 )
+{
+ OnThreadEnd(
+ function() : ( cloaker )
+ {
+ local cloakList = clone cloaker.s.cloakList
+ foreach ( entity guy, value in cloakList )
+ {
+ if ( !IsAlive( guy ) )
+ continue
+
+ CloakerDeCloaksGuy( guy )
+ }
+ }
+ )
+
+ cloaker.s.cloakList <- {}
+ cloaker.s.decloakList <- {}
+
+ while( 1 )
+ {
+ vector origin = cloaker.GetOrigin() + offset
+ array<entity> guys
+
+ foreach ( entType in ents )
+ {
+ switch ( entType )
+ {
+ case "player":
+ case "players":
+ guys.extend( GetPlayerArrayEx( "any", cloaker.GetTeam(), TEAM_ANY, origin, radius ) )
+ break;
+ default:
+ guys.extend( GetNPCArrayEx( entType, cloaker.GetTeam(), TEAM_ANY, origin, radius ) )
+ break
+ }
+ }
+ int index = 0
+
+ float startTime = Time()
+
+ table cloakList = expect table( cloaker.s.cloakList )
+ cloaker.s.decloakList = clone cloakList
+
+ foreach ( guy in guys )
+ {
+ //only do 5 distanceSqr / cansee checks per frame
+ if ( index++ > 5 )
+ {
+ wait 0.1
+ index = 0
+ origin = cloaker.GetOrigin() + offset
+ }
+
+ bool shouldCloakGuy = CloakerShouldCloakGuy( cloaker, guy )
+
+ if ( shouldCloakGuy )
+ shouldCloakGuy = expect bool( shouldCloakGuyFunc( cloaker, guy ) )
+
+ if ( shouldCloakGuy )
+ {
+ if ( guy in cloaker.s.decloakList )
+ delete cloaker.s.decloakList[ guy ]
+
+ if ( IsCloaked( guy ) )
+ continue
+
+ cloakList[ guy ] <- true
+ CloakerCloaksGuy( guy )
+ }
+ }
+
+ foreach ( entity guy, value in cloaker.s.decloakList )
+ {
+ // any guys still in the decloakList shouldn't be decloaked ... if alive.
+ Assert( guy in cloakList )
+ delete cloakList[ guy ]
+
+ if ( IsAlive( guy ) )
+ CloakerDeCloaksGuy( guy )
+ }
+
+ float endTime = Time()
+ float elapsedTime = endTime - startTime
+ if ( elapsedTime < waitTime )
+ wait waitTime - elapsedTime
+ }
+}
+
+void function CloakerCloaksGuy( guy )
+{
+ guy.SetCloakDuration( 2.0, -1, 0 )
+ EmitSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_START_SFX )
+ EmitSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_LOOP_SFX )
+ guy.Minimap_Hide( TEAM_IMC, null )
+ guy.Minimap_Hide( TEAM_MILITIA, null )
+}
+
+void function CloakerDeCloaksGuy( guy )
+{
+ guy.SetCloakDuration( 0, 0, 1.5 )
+ StopSoundOnEntity( guy, CLOAKED_DRONE_CLOAK_LOOP_SFX )
+ guy.Minimap_AlwaysShow( TEAM_IMC, null )
+ guy.Minimap_AlwaysShow( TEAM_MILITIA, null )
+}
+
+bool function CloakerShouldCloakGuy( entity cloaker, entity guy )
+{
+ if ( !IsAlive( guy ) )
+ return false
+
+ return true
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut
new file mode 100644
index 000000000..c2036e85d
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_grenade.nut
@@ -0,0 +1,604 @@
+untyped
+
+global function Grenade_FileInit
+global function GetGrenadeThrowSound_1p
+global function GetGrenadeDeploySound_1p
+global function GetGrenadeThrowSound_3p
+global function GetGrenadeDeploySound_3p
+global function GetGrenadeProjectileSound
+
+const DEFAULT_FUSE_TIME = 2.25
+const DEFAULT_WARNING_TIME = 1.0
+global const float DEFAULT_MAX_COOK_TIME = 99999.9 //Longer than an entire day. Really just an arbitrarily large number
+
+global function Grenade_OnWeaponTossReleaseAnimEvent
+global function Grenade_OnWeaponTossCancelDrop
+global function Grenade_OnWeaponDeactivate
+global function Grenade_OnWeaponTossPrep
+global function Grenade_OnProjectileIgnite
+
+#if SERVER
+ global function Grenade_OnPlayerNPCTossGrenade_Common
+ global function ProxMine_Triggered
+ global function EnableTrapWarningSound
+ global function AddToProximityTargets
+ global function ProximityMineThink
+#endif
+global function Grenade_Init
+
+const GRENADE_EXPLOSIVE_WARNING_SFX_LOOP = "Weapon_Vortex_Gun.ExplosiveWarningBeep"
+const EMP_MAGNETIC_FORCE = 1600
+const MAG_FLIGHT_SFX_LOOP = "Explo_MGL_MagneticAttract"
+
+//Proximity Mine Settings
+global const PROXIMITY_MINE_EXPLOSION_DELAY = 1.2
+global const PROXIMITY_MINE_ARMING_DELAY = 1.0
+const TRIGGERED_ALARM_SFX = "Weapon_ProximityMine_CloseWarning"
+global const THERMITE_GRENADE_FX = $"P_grenade_thermite"
+global const CLUSTER_BASE_FX = $"P_wpn_meteor_exp"
+
+global const ProximityTargetClassnames = {
+ [ "npc_soldier_shield" ] = true,
+ [ "npc_soldier_heavy" ] = true,
+ [ "npc_soldier" ] = true,
+ [ "npc_spectre" ] = true,
+ [ "npc_drone" ] = true,
+ [ "npc_titan" ] = true,
+ [ "npc_marvin" ] = true,
+ [ "player" ] = true,
+ [ "npc_turret_mega" ] = true,
+ [ "npc_turret_sentry" ] = true,
+ [ "npc_dropship" ] = true,
+}
+
+const SOLDIER_ARC_STUN_ANIMS = [
+ "pt_react_ARC_fall",
+ "pt_react_ARC_kneefall",
+ "pt_react_ARC_sidefall",
+ "pt_react_ARC_slowfall",
+ "pt_react_ARC_scream",
+ "pt_react_ARC_stumble_F",
+ "pt_react_ARC_stumble_R" ]
+
+function Grenade_FileInit()
+{
+ PrecacheParticleSystem( CLUSTER_BASE_FX )
+
+ RegisterSignal( "ThrowGrenade" )
+ RegisterSignal( "WeaponDeactivateEvent" )
+ RegisterSignal( "OnEMPPilotHit" )
+ RegisterSignal( "StopGrenadeClientEffects" )
+ RegisterSignal( "DisableTrapWarningSound" )
+
+ //Globalize( MagneticFlight )
+
+ #if CLIENT
+ AddDestroyCallback( "grenade_frag", ClientDestroyCallback_GrenadeDestroyed )
+ #endif
+
+ #if SERVER
+ level._empForcedCallbacks <- {}
+ level._proximityTargetArrayID <- CreateScriptManagedEntArray()
+
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_proximity_mine, ProxMine_Triggered )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_thermite_grenade, Thermite_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_frag_grenade, Frag_DamagedPlayerOrNPC )
+
+ level._empForcedCallbacks[eDamageSourceId.mp_weapon_grenade_emp] <- true
+ level._empForcedCallbacks[eDamageSourceId.mp_weapon_proximity_mine] <- true
+
+ PrecacheParticleSystem( THERMITE_GRENADE_FX )
+ #endif
+}
+
+void function Grenade_OnWeaponTossPrep( entity weapon, WeaponTossPrepParams prepParams )
+{
+ weapon.w.startChargeTime = Time()
+
+ entity weaponOwner = weapon.GetWeaponOwner()
+ weapon.EmitWeaponSound_1p3p( GetGrenadeDeploySound_1p( weapon ), GetGrenadeDeploySound_3p( weapon ) )
+
+ #if SERVER
+ thread HACK_CookGrenade( weapon, weaponOwner )
+ thread HACK_DropGrenadeOnDeath( weapon, weaponOwner )
+ #elseif CLIENT
+ if ( weaponOwner.IsPlayer() )
+ {
+ weaponOwner.p.grenadePulloutTime = Time()
+ }
+ #endif
+}
+
+void function Grenade_OnWeaponDeactivate( entity weapon )
+{
+ StopSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP )
+ weapon.Signal( "WeaponDeactivateEvent" )
+}
+
+void function Grenade_OnProjectileIgnite( entity weapon )
+{
+ printt( "Grenade_OnProjectileIgnite() callback." )
+}
+
+function Grenade_Init( entity grenade, entity weapon )
+{
+ entity weaponOwner = weapon.GetOwner()
+ if ( IsValid( weaponOwner ) )
+ SetTeam( grenade, weaponOwner.GetTeam() )
+
+ // JFS: this is because I don't know if the above line should be
+ // weapon.GetOwner() or it's a typo and should really be weapon.GetWeaponOwner()
+ // and it's too close to ship and who knows what effect that will have
+ entity owner = weapon.GetWeaponOwner()
+ if ( IsMultiplayer() && IsValid( owner ) )
+ {
+ if ( owner.IsNPC() )
+ {
+ SetTeam( grenade, owner.GetTeam() )
+ }
+ }
+
+ #if SERVER
+ bool smartPistolVisible = weapon.GetWeaponSettingBool( eWeaponVar.projectile_visible_to_smart_ammo )
+ if ( smartPistolVisible )
+ {
+ grenade.SetDamageNotifications( true )
+ grenade.SetTakeDamageType( DAMAGE_EVENTS_ONLY )
+ grenade.proj.onlyAllowSmartPistolDamage = true
+
+ if ( !grenade.GetProjectileWeaponSettingBool( eWeaponVar.projectile_damages_owner ) && !grenade.GetProjectileWeaponSettingBool( eWeaponVar.explosion_damages_owner ) )
+ SetCustomSmartAmmoTarget( grenade, true ) // prevent friendly target lockon
+ }
+ else
+ {
+ grenade.SetTakeDamageType( DAMAGE_NO )
+ }
+ #endif
+ if ( IsValid( weaponOwner ) )
+ grenade.s.originalOwner <- weaponOwner // for later in damage callbacks, to skip damage vs friendlies but not for og owner or his enemies
+}
+
+
+int function Grenade_OnWeaponToss_( entity weapon, WeaponPrimaryAttackParams attackParams, float directionScale )
+{
+ weapon.EmitWeaponSound_1p3p( GetGrenadeThrowSound_1p( weapon ), GetGrenadeThrowSound_3p( weapon ) )
+ bool projectilePredicted = PROJECTILE_PREDICTED
+ bool projectileLagCompensated = PROJECTILE_LAG_COMPENSATED
+#if SERVER
+ if ( weapon.IsForceReleaseFromServer() )
+ {
+ projectilePredicted = false
+ projectileLagCompensated = false
+ }
+#endif
+ entity grenade = Grenade_Launch( weapon, attackParams.pos, (attackParams.dir * directionScale), projectilePredicted, projectileLagCompensated )
+ entity weaponOwner = weapon.GetWeaponOwner()
+ weaponOwner.Signal( "ThrowGrenade" )
+
+ PlayerUsedOffhand( weaponOwner, weapon ) // intentionally here and in Hack_DropGrenadeOnDeath - accurate for when cooldown actually begins
+
+#if SERVER
+
+ #if BATTLECHATTER_ENABLED
+ TryPlayWeaponBattleChatterLine( weaponOwner, weapon )
+ #endif
+
+#endif
+
+ return weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+}
+
+var function Grenade_OnWeaponTossReleaseAnimEvent( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ var result = Grenade_OnWeaponToss_( weapon, attackParams, 1.0 )
+ return result
+}
+
+var function Grenade_OnWeaponTossCancelDrop( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ var result = Grenade_OnWeaponToss_( weapon, attackParams, 0.2 )
+ return result
+}
+
+// Can return entity or nothing
+entity function Grenade_Launch( entity weapon, vector attackPos, vector throwVelocity, bool isPredicted, bool isLagCompensated )
+{
+ #if CLIENT
+ if ( !weapon.ShouldPredictProjectiles() )
+ return null
+ #endif
+
+ //TEMP FIX while Deploy anim is added to sprint
+ float currentTime = Time()
+ if ( weapon.w.startChargeTime == 0.0 )
+ weapon.w.startChargeTime = currentTime
+
+ entity weaponOwner = weapon.GetWeaponOwner()
+
+ var discThrow = weapon.GetWeaponInfoFileKeyField( "grenade_disc_throw" )
+
+ vector angularVelocity = Vector( 3600, RandomFloatRange( -1200, 1200 ), 0 )
+
+ if ( discThrow == 1 )
+ angularVelocity = Vector( 100, 100, RandomFloatRange( 1200, 2200 ) )
+
+
+ float fuseTime
+
+ float baseFuseTime = weapon.GetGrenadeFuseTime() //Note that fuse time of 0 means the grenade won't explode on its own, instead it depends on OnProjectileCollision() functions to be defined and explode there. Arguably in this case grenade_fuse_time shouldn't be 0, but an arbitrarily large number instead.
+ if ( baseFuseTime > 0.0 )
+ {
+ fuseTime = baseFuseTime - ( currentTime - weapon.w.startChargeTime )
+ if ( fuseTime <= 0 )
+ fuseTime = 0.001
+ }
+ else
+ {
+ fuseTime = baseFuseTime
+ }
+
+ int damageFlags = weapon.GetWeaponDamageFlags()
+ entity frag = weapon.FireWeaponGrenade( attackPos, throwVelocity, angularVelocity, fuseTime, damageFlags, damageFlags, isPredicted, isLagCompensated, true )
+ if ( frag == null )
+ return null
+
+ if ( discThrow == 1 ) // add wobble by pitching it slightly
+ {
+ Assert( !frag.IsMarkedForDeletion(), "Frag before .SetAngles() is marked for deletion." )
+ frag.SetAngles( frag.GetAngles() + < RandomFloatRange( 7,11 ),0,0 > )
+ //Assert( !frag.IsMarkedForDeletion(), "Frag after .SetAngles() is marked for deletion." )
+ if ( frag.IsMarkedForDeletion() )
+ {
+ CodeWarning( "Frag after .SetAngles() was marked for deletion." )
+ return null
+ }
+ }
+
+ Grenade_OnPlayerNPCTossGrenade_Common( weapon, frag )
+
+ return frag
+}
+
+void function Grenade_OnPlayerNPCTossGrenade_Common( entity weapon, entity frag )
+{
+ Grenade_Init( frag, weapon )
+ #if SERVER
+ thread TrapExplodeOnDamage( frag, 20, 0.0, 0.0 )
+
+ string projectileSound = GetGrenadeProjectileSound( weapon )
+ if ( projectileSound != "" )
+ EmitSoundOnEntity( frag, projectileSound )
+ #endif
+
+ if( weapon.HasMod( "burn_mod_emp_grenade" ) )
+ frag.InitMagnetic( EMP_MAGNETIC_FORCE, MAG_FLIGHT_SFX_LOOP )
+}
+
+struct CookGrenadeStruct //Really just a convenience struct so we can read the changed value of a bool in an OnThreadEnd
+{
+ bool shouldOverrideFuseTime = false
+}
+
+void function HACK_CookGrenade( entity weapon, entity weaponOwner )
+{
+ float maxCookTime = GetMaxCookTime( weapon )
+ if ( maxCookTime >= DEFAULT_MAX_COOK_TIME )
+ return
+
+ weaponOwner.EndSignal( "OnDeath" )
+ weaponOwner.EndSignal( "ThrowGrenade" )
+ weapon.EndSignal( "WeaponDeactivateEvent" )
+ weapon.EndSignal( "OnDestroy" )
+
+ /*CookGrenadeStruct grenadeStruct
+
+ OnThreadEnd(
+ function() : ( weapon, grenadeStruct )
+ {
+ if ( grenadeStruct.shouldOverrideFuseTime )
+ {
+ var minFuseTime = weapon.GetWeaponInfoFileKeyField( "min_fuse_time" )
+ printt( "minFuseTime: " + minFuseTime )
+ if ( minFuseTime != null )
+ {
+ expect float( minFuseTime )
+ printt( "Setting overrideFuseTime to : " + weapon.GetWeaponInfoFileKeyField( "min_fuse_time" ) )
+ weapon.w.overrideFuseTime = minFuseTime
+ }
+ }
+ }
+ )
+*/
+ if ( maxCookTime - DEFAULT_WARNING_TIME <= 0 )
+ {
+ EmitSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP )
+ wait maxCookTime
+ }
+ else
+ {
+ wait( maxCookTime - DEFAULT_WARNING_TIME )
+
+ EmitSoundOnEntity( weapon, GRENADE_EXPLOSIVE_WARNING_SFX_LOOP )
+
+ wait( DEFAULT_WARNING_TIME )
+ }
+
+ if ( !IsValid( weapon.GetWeaponOwner() ) )
+ return
+
+ weapon.ForceReleaseFromServer() // Will eventually result in Grenade_OnWeaponToss_() or equivalent function
+
+ // JFS: prevent grenade cook exploit in coliseum
+ if ( GameRules_GetGameMode() == COLISEUM )
+ {
+ #if SERVER
+ int damageSource = weapon.GetDamageSourceID()
+
+ if ( damageSource == eDamageSourceId.mp_weapon_frag_grenade )
+ {
+ var impact_effect_table = weapon.GetWeaponInfoFileKeyField( "impact_effect_table" )
+ if ( impact_effect_table != null )
+ {
+ string fx = expect string( impact_effect_table )
+ PlayImpactFXTable( weaponOwner.EyePosition(), weaponOwner, fx )
+ }
+ weaponOwner.Die( weaponOwner, weapon, { damageSourceId = damageSource } )
+ }
+ #endif
+ }
+
+ weaponOwner.Signal( "ThrowGrenade" ) // Only necessary to end HACK_DropGrenadeOnDeath
+}
+
+
+void function HACK_WaitForGrenadeDropEvent( weapon, entity weaponOwner )
+{
+ weapon.EndSignal( "WeaponDeactivateEvent" )
+
+ weaponOwner.WaitSignal( "OnDeath" )
+}
+
+
+void function HACK_DropGrenadeOnDeath( entity weapon, entity weaponOwner )
+{
+ if ( weapon.HasMod( "burn_card_weapon_mod" ) ) //JFS: Primarily to stop boost grenade weapons (e.g. frag_drone ) not doing TryUsingBurnCardWeapon() when dropped through this function.
+ return
+
+ weaponOwner.EndSignal( "ThrowGrenade" )
+ weaponOwner.EndSignal( "OnDestroy" )
+
+ waitthread HACK_WaitForGrenadeDropEvent( weapon, weaponOwner )
+
+ if( !IsValid( weaponOwner ) || !IsValid( weapon ) || IsAlive( weaponOwner ) )
+ return
+
+ float elapsedTime = Time() - weapon.w.startChargeTime
+ float baseFuseTime = weapon.GetGrenadeFuseTime()
+ float fuseDelta = (baseFuseTime - elapsedTime)
+
+ if ( (baseFuseTime == 0.0) || (fuseDelta > -0.1) )
+ {
+ float forwardScale = weapon.GetWeaponSettingFloat( eWeaponVar.grenade_death_drop_velocity_scale )
+ vector velocity = weaponOwner.GetForwardVector() * forwardScale
+ velocity.z += weapon.GetWeaponSettingFloat( eWeaponVar.grenade_death_drop_velocity_extraUp )
+ vector angularVelocity = Vector( 0, 0, 0 )
+ float fuseTime = baseFuseTime ? baseFuseTime - elapsedTime : baseFuseTime
+
+ int primaryClipCount = weapon.GetWeaponPrimaryClipCount()
+ int ammoPerShot = weapon.GetWeaponSettingInt( eWeaponVar.ammo_per_shot )
+ weapon.SetWeaponPrimaryClipCountAbsolute( maxint( 0, primaryClipCount - ammoPerShot ) )
+
+ PlayerUsedOffhand( weaponOwner, weapon ) // intentionally here and in ReleaseAnimEvent - for cases where grenade is dropped on death
+
+ entity grenade = Grenade_Launch( weapon, weaponOwner.GetOrigin(), velocity, PROJECTILE_NOT_PREDICTED, PROJECTILE_NOT_LAG_COMPENSATED )
+ }
+}
+
+
+#if SERVER
+void function ProxMine_Triggered( entity ent, var damageInfo )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( !IsValid( attacker ) )
+ return
+
+ if ( attacker == ent )
+ return
+
+ if ( ent.IsPlayer() || ent.IsNPC() )
+ thread ShowProxMineTriggeredIcon( ent )
+
+ //If this feature is good, we should add this to NPCs as well. Currently script errors if applied to an NPC.
+ //if ( ent.IsPlayer() )
+ // thread ProxMine_ShowOnMinimapTimed( ent, GetOtherTeam( ent.GetTeam() ), PROX_MINE_MARKER_TIME )
+}
+
+/*
+function ProxMine_ShowOnMinimapTimed( ent, teamToDisplayEntTo, duration )
+{
+ ent.Minimap_AlwaysShow( teamToDisplayEntTo, null )
+ Minimap_CreatePingForTeam( teamToDisplayEntTo, ent.GetOrigin(), $"vgui/HUD/titanFiringPing", 1.0 )
+
+ wait duration
+
+ if ( IsValid( ent ) && ent.IsPlayer() )
+ ent.Minimap_DisplayDefault( teamToDisplayEntTo, ent )
+}
+*/
+
+void function Thermite_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ Thermite_DamagePlayerOrNPCSounds( ent )
+}
+
+void function Frag_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ #if MP
+ if ( !IsValid( ent ) || ent.IsPlayer() || ent.IsTitan() )
+ return
+
+ if ( ent.IsMechanical() )
+ DamageInfo_ScaleDamage( damageInfo, 0.5 )
+ #endif
+}
+
+#endif // SERVER
+
+
+#if CLIENT
+void function ClientDestroyCallback_GrenadeDestroyed( entity grenade )
+{
+}
+#endif // CLIENT
+
+#if SERVER
+function EnableTrapWarningSound( entity trap, delay = 0, warningSound = DEFAULT_WARNING_SFX )
+{
+ trap.EndSignal( "OnDestroy" )
+ trap.EndSignal( "DisableTrapWarningSound" )
+
+ if ( delay > 0 )
+ wait delay
+
+ while ( IsValid( trap ) )
+ {
+ EmitSoundOnEntity( trap, warningSound )
+ wait 1.0
+ }
+}
+
+void function AddToProximityTargets( entity ent )
+{
+ AddToScriptManagedEntArray( level._proximityTargetArrayID, ent );
+}
+
+function ProximityMineThink( entity proximityMine, entity owner )
+{
+ proximityMine.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( proximityMine )
+ {
+ if ( IsValid( proximityMine ) )
+ proximityMine.Destroy()
+ }
+ )
+ thread TrapExplodeOnDamage( proximityMine, 50 )
+
+ wait PROXIMITY_MINE_ARMING_DELAY
+
+ int teamNum = proximityMine.GetTeam()
+ float explodeRadius = proximityMine.GetDamageRadius()
+ float triggerRadius = ( ( explodeRadius * 0.75 ) + 0.5 )
+ local lastTimeNPCsChecked = 0
+ local NPCTickRate = 0.5
+ local PlayerTickRate = 0.2
+
+ // Wait for someone to enter proximity
+ while( IsValid( proximityMine ) && IsValid( owner ) )
+ {
+ if ( lastTimeNPCsChecked + NPCTickRate <= Time() )
+ {
+ array<entity> nearbyNPCs = GetNPCArrayEx( "any", TEAM_ANY, teamNum, proximityMine.GetOrigin(), triggerRadius )
+ foreach( ent in nearbyNPCs )
+ {
+ if ( ShouldSetOffProximityMine( proximityMine, ent ) )
+ {
+ ProximityMine_Explode( proximityMine )
+ return
+ }
+ }
+ lastTimeNPCsChecked = Time()
+ }
+
+ array<entity> nearbyPlayers = GetPlayerArrayEx( "any", TEAM_ANY, teamNum, proximityMine.GetOrigin(), triggerRadius )
+ foreach( ent in nearbyPlayers )
+ {
+ if ( ShouldSetOffProximityMine( proximityMine, ent ) )
+ {
+ ProximityMine_Explode( proximityMine )
+ return
+ }
+ }
+
+ wait PlayerTickRate
+ }
+}
+
+function ProximityMine_Explode( proximityMine )
+{
+ local explodeTime = Time() + PROXIMITY_MINE_EXPLOSION_DELAY
+ EmitSoundOnEntity( proximityMine, TRIGGERED_ALARM_SFX )
+
+ wait PROXIMITY_MINE_EXPLOSION_DELAY
+
+ if ( IsValid( proximityMine ) )
+ proximityMine.GrenadeExplode( proximityMine.GetForwardVector() )
+}
+
+bool function ShouldSetOffProximityMine( entity proximityMine, entity ent )
+{
+ if ( !IsAlive( ent ) )
+ return false
+
+ if ( ent.IsPhaseShifted() )
+ return false
+
+ TraceResults results = TraceLine( proximityMine.GetOrigin(), ent.EyePosition(), proximityMine, (TRACE_MASK_SHOT | CONTENTS_BLOCKLOS), TRACE_COLLISION_GROUP_NONE )
+ if ( results.fraction >= 1 || results.hitEnt == ent )
+ return true
+
+ return false
+}
+
+#endif // SERVER
+
+
+
+float function GetMaxCookTime( entity weapon )
+{
+ var cookTime = weapon.GetWeaponInfoFileKeyField( "max_cook_time" )
+ if (cookTime == null )
+ return DEFAULT_MAX_COOK_TIME
+
+ expect float ( cookTime )
+ return cookTime
+}
+
+function GetGrenadeThrowSound_1p( weapon )
+{
+ return weapon.GetWeaponInfoFileKeyField( "sound_throw_1p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_throw_1p" ) : ""
+}
+
+
+function GetGrenadeDeploySound_1p( weapon )
+{
+ return weapon.GetWeaponInfoFileKeyField( "sound_deploy_1p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_deploy_1p" ) : ""
+}
+
+
+function GetGrenadeThrowSound_3p( weapon )
+{
+ return weapon.GetWeaponInfoFileKeyField( "sound_throw_3p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_throw_3p" ) : ""
+}
+
+
+function GetGrenadeDeploySound_3p( weapon )
+{
+ return weapon.GetWeaponInfoFileKeyField( "sound_deploy_3p" ) ? weapon.GetWeaponInfoFileKeyField( "sound_deploy_3p" ) : ""
+}
+
+string function GetGrenadeProjectileSound( weapon )
+{
+ return expect string( weapon.GetWeaponInfoFileKeyField( "sound_grenade_projectile" ) ? weapon.GetWeaponInfoFileKeyField( "sound_grenade_projectile" ) : "" )
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_particle_wall.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_particle_wall.gnut
new file mode 100644
index 000000000..a46bfff82
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_particle_wall.gnut
@@ -0,0 +1,460 @@
+untyped
+
+global function ParticleWall_Init
+
+global function CreateTurretParticleWall
+global function CreateParticleWallFromOwner
+global function CreateShieldWithSettings
+global function DrainHealthOverTime
+
+global function CreateAmpedWallFromOwner
+
+global function CreateParticleWallForOwnerFromDirection
+
+global const SHIELD_WALL_COL_MODEL = $"models/fx/xo_shield_wall.mdl"
+global const SHIELD_WALL_FX = $"P_xo_shield_wall"
+
+global const TURRET_SHIELD_WALL_COL_MODEL = $"models/fx/turret_shield_wall.mdl"
+global const TURRET_SHIELD_WALL_FX = $"P_turret_shield_wall"
+
+global const AMPED_WALL_FX = $"P_xo_amped_wall"
+#if MP
+global const SHIELD_WALL_HEALTH = 2000
+global const TURRET_SHIELD_WALL_HEALTH = 3500//1750
+#else
+global const SHIELD_WALL_HEALTH = 1750
+global const TURRET_SHIELD_WALL_HEALTH = 1750
+#endif
+global const PAS_TONE_WALL_HEALTH = 3000
+global const PAS_TONE_WALL_DURATION_MULTIPLIER = 1.5
+global const SHIELD_WALL_DURATION = 8.0
+global const SHIELD_WALL_RADIUS = 180
+global const SHIELD_WALL_FOV = 120
+global const SHIELD_WALL_WIDTH = 156.0 // SHIELD_WALL_RADIUS * cos( SHIELD_WALL_FOV/2 )
+
+global function UpdateShieldWallColorForFrac
+global function PlayEffectOnVortexSphere
+global function SetVortexSphereShieldWallCPoint
+global function SetShieldWallCPoint
+global function StopShieldWallFX
+global function StopShieldWallFXOverTime
+global function SetShieldWallCPointOrigin
+
+function ParticleWall_Init()
+{
+ PrecacheParticleSystem( SHIELD_WALL_FX )
+ PrecacheModel( SHIELD_WALL_COL_MODEL )
+
+ PrecacheParticleSystem( TURRET_SHIELD_WALL_FX )
+ PrecacheModel( TURRET_SHIELD_WALL_COL_MODEL )
+
+ PrecacheParticleSystem( AMPED_WALL_FX )
+}
+
+void function CreateParticleWallFromOwner( entity weaponOwner, float duration, WeaponPrimaryAttackParams attackParams )
+{
+ vector dir = GetParticleWallAttackAnglesFromOwner( weaponOwner, attackParams )
+ CreateParticleWallForOwnerFromDirection( weaponOwner, duration, dir )
+}
+
+vector function GetParticleWallAttackAnglesFromOwner( entity weaponOwner, WeaponPrimaryAttackParams attackParams )
+{
+ if ( weaponOwner.IsNPC() )
+ return attackParams.dir
+
+ vector angles = weaponOwner.CameraAngles()
+ angles.x = 0
+ return AnglesToForward( angles )
+}
+
+void function CreateParticleWallForOwnerFromDirection( entity weaponOwner, float duration, vector dir )
+{
+ Assert( IsServer() )
+
+ entity titanSoul = weaponOwner.GetTitanSoul()
+
+ // JFS the weapon owner should always have a soul, at least on the server
+ if ( !IsValid( titanSoul ) )
+ return
+
+ vector origin = weaponOwner.GetOrigin()
+ vector safeSpot = origin
+ vector angles = VectorToAngles( dir )
+
+ if ( weaponOwner.IsNPC() )
+ {
+ // spawn in front of npc a bit
+ origin += dir * 100
+ }
+
+ float endTime = Time() + duration
+ titanSoul.SetDefensivePlacement( endTime, SHIELD_WALL_WIDTH, 0, true, safeSpot, dir )
+
+ Assert( weaponOwner.IsTitan() )
+ Assert( titanSoul )
+
+ int health
+ if ( SoulHasPassive( titanSoul, ePassives.PAS_TONE_WALL ) )
+ {
+ health = PAS_TONE_WALL_HEALTH
+ duration *= PAS_TONE_WALL_DURATION_MULTIPLIER
+ }
+ else
+ {
+ health = SHIELD_WALL_HEALTH
+ }
+ entity vortexSphere = CreateShieldWithSettings( origin + < 0, 0, -64 >, angles, SHIELD_WALL_RADIUS, SHIELD_WALL_RADIUS * 2, SHIELD_WALL_FOV, duration, health, SHIELD_WALL_FX )
+ thread DrainHealthOverTime( vortexSphere, vortexSphere.e.shieldWallFX, duration )
+
+ entity groundEntity = weaponOwner.GetGroundEntity()
+ if ( groundEntity != null && groundEntity.HasPusherRootParent() )
+ vortexSphere.SetParent( groundEntity, "", true, 0 )
+}
+
+entity function CreateTurretParticleWall( vector origin, vector angles, float duration )
+{
+ Assert( IsServer() )
+
+ entity vortexSphere = CreateTurretShieldWithSettings( origin + < 0, 0, -64 >, angles, SHIELD_WALL_RADIUS, int( SHIELD_WALL_RADIUS * 1.65 ), 270, duration, TURRET_SHIELD_WALL_HEALTH, TURRET_SHIELD_WALL_FX )
+ thread DrainHealthOverTime( vortexSphere, vortexSphere.e.shieldWallFX, duration )
+
+ return vortexSphere
+}
+
+entity function CreateShieldWithSettings( vector origin, vector angles, int radius, int height, int fov, float duration, int health, asset effectName )
+{
+ entity vortexSphere = CreateEntity( "vortex_sphere" )
+
+ vortexSphere.kv.spawnflags = SF_ABSORB_BULLETS | SF_BLOCK_OWNER_WEAPON | SF_BLOCK_NPC_WEAPON_LOF | SF_ABSORB_CYLINDER
+ vortexSphere.kv.enabled = 0
+ vortexSphere.kv.radius = radius
+ vortexSphere.kv.height = height
+ vortexSphere.kv.bullet_fov = fov
+ vortexSphere.kv.physics_pull_strength = 25
+ vortexSphere.kv.physics_side_dampening = 6
+ vortexSphere.kv.physics_fov = 360
+ vortexSphere.kv.physics_max_mass = 2
+ vortexSphere.kv.physics_max_size = 6
+
+ vortexSphere.SetAngles( angles ) // viewvec?
+ vortexSphere.SetOrigin( origin )
+ vortexSphere.SetMaxHealth( health )
+ vortexSphere.SetHealth( health )
+ vortexSphere.SetTakeDamageType( DAMAGE_YES )
+
+ DispatchSpawn( vortexSphere )
+
+ vortexSphere.Fire( "Enable" )
+ vortexSphere.Fire( "Kill", "", duration )
+
+ // Shield wall fx control point
+ entity cpoint = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpoint, UniqueString( "shield_wall_controlpoint" ) )
+ DispatchSpawn( cpoint )
+
+ // Shield wall fx
+ entity shieldWallFX = PlayFXWithControlPoint( effectName, origin, cpoint, -1, null, angles, C_PLAYFX_LOOP )
+ vortexSphere.e.shieldWallFX = shieldWallFX
+ shieldWallFX.SetParent( vortexSphere )
+ SetVortexSphereShieldWallCPoint( vortexSphere, cpoint )
+ StopShieldWallFXOverTime( vortexSphere, duration )
+
+
+ thread StopFXOnDestroy( vortexSphere, shieldWallFX, duration )
+ return vortexSphere
+}
+
+//Turret Shields do not block npc line of fire.
+entity function CreateTurretShieldWithSettings( vector origin, vector angles, int radius, int height, int fov, float duration, int health, asset effectName )
+{
+ entity vortexSphere = CreateEntity( "vortex_sphere" )
+
+ vortexSphere.kv.spawnflags = SF_ABSORB_BULLETS | SF_BLOCK_OWNER_WEAPON | SF_ABSORB_CYLINDER
+ vortexSphere.kv.enabled = 0
+ vortexSphere.kv.radius = radius
+ vortexSphere.kv.height = height
+ vortexSphere.kv.bullet_fov = fov
+ vortexSphere.kv.physics_pull_strength = 25
+ vortexSphere.kv.physics_side_dampening = 6
+ vortexSphere.kv.physics_fov = 360
+ vortexSphere.kv.physics_max_mass = 2
+ vortexSphere.kv.physics_max_size = 6
+
+ vortexSphere.SetAngles( angles ) // viewvec?
+ vortexSphere.SetOrigin( origin )
+ vortexSphere.SetMaxHealth( health )
+ vortexSphere.SetHealth( health )
+ vortexSphere.SetTakeDamageType( DAMAGE_YES )
+
+ DispatchSpawn( vortexSphere )
+
+ vortexSphere.Fire( "Enable" )
+ vortexSphere.Fire( "Kill", "", duration )
+
+ // Shield wall fx control point
+ entity cpoint = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpoint, UniqueString( "shield_wall_controlpoint" ) )
+ DispatchSpawn( cpoint )
+
+ // Shield wall fx
+ entity shieldWallFX = PlayFXWithControlPoint( effectName, origin, cpoint, -1, null, angles, C_PLAYFX_LOOP )
+ vortexSphere.e.shieldWallFX = shieldWallFX
+ shieldWallFX.SetParent( vortexSphere )
+ SetVortexSphereShieldWallCPoint( vortexSphere, cpoint )
+ StopShieldWallFXOverTime( vortexSphere, duration )
+
+
+ thread StopFXOnDestroy( vortexSphere, shieldWallFX, duration )
+ return vortexSphere
+}
+
+function StopFXOnDestroy( entity vortexSphere, entity shieldWallFX, float duration )
+{
+ vortexSphere.EndSignal( "OnDestroy" )
+ shieldWallFX.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( vortexSphere )
+ {
+ StopShieldWallFX( vortexSphere )
+ }
+ )
+
+ wait duration * 1.5
+}
+
+void function CreateAmpedWallFromOwner( entity weaponOwner, float duration, WeaponPrimaryAttackParams attackParams )
+{
+ Assert( IsNewThread(), "Must be threaded off" )
+ Assert( IsServer() )
+ entity titanSoul = weaponOwner.GetTitanSoul()
+
+ // JFS the weapon owner should always have a soul, at least on the server
+ if ( !IsValid( titanSoul ) )
+ return
+ Assert( weaponOwner.IsTitan() )
+
+ vector dir = GetParticleWallAttackAnglesFromOwner( weaponOwner, attackParams )
+ vector origin = weaponOwner.GetOrigin()
+ vector safeSpot = origin
+ vector angles = VectorToAngles( dir )
+ vector forward = AnglesToForward( angles )
+ angles = AnglesCompose( angles, <0,180,0> )
+
+ if ( weaponOwner.IsNPC() )
+ {
+ // spawn in front of npc a bit
+ origin += dir * 100
+ }
+
+ origin += dir * 500
+ origin += Vector(0,0,-64)
+
+ float endTime = Time() + duration
+ titanSoul.SetDefensivePlacement( endTime, SHIELD_WALL_WIDTH, 0, true, safeSpot, dir )
+
+ entity vortexSphere = CreateShieldWithSettings( origin, angles, SHIELD_WALL_RADIUS, SHIELD_WALL_RADIUS * 2, SHIELD_WALL_FOV, duration, SHIELD_WALL_HEALTH, AMPED_WALL_FX )
+ vortexSphere.EndSignal( "OnDestroy" )
+ entity shieldWallFX = vortexSphere.e.shieldWallFX
+ shieldWallFX.EndSignal( "OnDestroy" )
+ SetTargetName( vortexSphere, PROTO_AMPED_WALL ) // so projectiles pass through
+
+ SetShieldWallCPointOrigin( shieldWallFX, < BURN_CARD_WEAPON_HUD_COLOR[0], BURN_CARD_WEAPON_HUD_COLOR[1], BURN_CARD_WEAPON_HUD_COLOR[2] > )
+
+ float tickRate = 0.1
+ float dps = vortexSphere.GetMaxHealth() / duration
+ float dmgAmount = dps * tickRate
+
+ EmitSoundOnEntity( vortexSphere, "ShieldWall_Loop" )
+
+ float endSoundTime = endTime - 3.0 // so magic
+ thread PlayDelayedVortexEndSound( endSoundTime, vortexSphere )
+ bool playedEndSound = false
+ vector vortexOrigin = vortexSphere.GetOrigin()
+ entity mover = CreateScriptMover()
+
+ int weaponOwnerTeam = weaponOwner.GetTeam();
+
+ OnThreadEnd(
+ function() : ( vortexSphere, vortexOrigin, endTime, mover, weaponOwnerTeam )
+ {
+ if ( IsValid( vortexSphere ) )
+ {
+ StopSoundOnEntity( vortexSphere, "ShieldWall_Loop" )
+ StopSoundOnEntity( vortexSphere, "ShieldWall_End" )
+ }
+
+ if ( IsValid( mover ) )
+ mover.Destroy()
+
+ if ( endTime - Time() >= 1.0 )
+ EmitSoundAtPosition( weaponOwnerTeam, vortexOrigin, "ShieldWall_Destroyed" )
+ }
+ )
+
+ int rampOuts = 3
+ float rampOutTime = 0.75
+ float rampOutFinalFade = 1.0
+ float finalFadeExtraBuffer = 0.45
+
+ wait duration - ( rampOutTime * rampOuts + rampOutFinalFade + finalFadeExtraBuffer )
+ EmitSoundOnEntity( vortexSphere, "ShieldWall_End" )
+
+ entity cpoint = GetShieldWallFXCPoint( shieldWallFX )
+
+ vector cpointOrigin = cpoint.GetOrigin()
+ mover.SetOrigin( cpointOrigin )
+ cpoint.SetParent( mover )
+ float rampTime1 = rampOutTime * 0.75
+ float rampTime2 = rampOutTime - rampTime1
+ for ( int i = 0; i < rampOuts; i++ )
+ {
+ mover.NonPhysicsMoveTo( <100,0,0>, rampTime1, rampTime1, 0.0 )
+ wait rampTime1
+ mover.NonPhysicsMoveTo( cpointOrigin, rampTime2, 0.0, rampTime2 )
+ wait rampTime2
+ }
+
+ mover.NonPhysicsMoveTo( <0,0,0>, rampOutFinalFade, 0.0, 0.0 )
+ wait rampOutFinalFade + finalFadeExtraBuffer
+}
+
+void function PlayDelayedVortexEndSound( float delay, entity vortexSphere )
+{
+ vortexSphere.EndSignal( "OnDestroy" )
+ wait delay
+ EmitSoundOnEntity( vortexSphere, "ShieldWall_End" )
+}
+
+
+function DrainHealthOverTime( entity vortexSphere, entity shieldWallFX, float duration )
+{
+ vortexSphere.EndSignal( "OnDestroy" )
+ shieldWallFX.EndSignal( "OnDestroy" )
+
+ float startTime = Time()
+ float endTime = startTime + duration
+
+ float tickRate = 0.1
+ float dps = vortexSphere.GetMaxHealth() / duration
+ float dmgAmount = dps * tickRate
+
+ EmitSoundOnEntity( vortexSphere, "ShieldWall_Loop" )
+
+ float endSoundTime = endTime - 3.0
+ bool playedEndSound = false
+ vector vortexOrigin = vortexSphere.GetOrigin()
+
+ OnThreadEnd(
+ function() : ( vortexSphere, vortexOrigin, endTime )
+ {
+ if ( endTime - Time() < 1.0 )
+ return
+
+ int teamNum = TEAM_UNASSIGNED
+
+ if ( IsValid( vortexSphere ) )
+ {
+ StopSoundOnEntity( vortexSphere, "ShieldWall_Loop" )
+ StopSoundOnEntity( vortexSphere, "ShieldWall_End" )
+
+ teamNum = vortexSphere.GetTeam()
+ }
+
+ EmitSoundAtPosition( teamNum, vortexOrigin, "ShieldWall_Destroyed" )
+ }
+ )
+
+ while ( Time() < endTime )
+ {
+ if ( Time() > endSoundTime && !playedEndSound )
+ {
+ EmitSoundOnEntity( vortexSphere, "ShieldWall_End" )
+ playedEndSound = true
+ }
+
+ //vortexSphere.SetHealth( vortexSphere.GetHealth() - dmgAmount )
+ UpdateShieldWallColorForFrac( shieldWallFX, GetHealthFrac( vortexSphere ) )
+ wait tickRate
+ }
+
+ StopSoundOnEntity( vortexSphere, "ShieldWall_Loop" )
+}
+
+function UpdateShieldWallColorForFrac( entity shieldWallFX, float colorFrac )
+{
+ vector color = GetShieldTriLerpColor( 1 - colorFrac )
+
+ if ( IsValid( shieldWallFX ) )
+ SetShieldWallCPointOrigin( shieldWallFX, color )
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// All functions that care about to-be-deprecated cpoint are below here:
+//
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+void function PlayEffectOnVortexSphere( int fx, vector origin, vector angles, entity vortexSphere )
+{
+ if ( !IsValid( vortexSphere ) )
+ return
+ if ( !IsValid( vortexSphere.e.shieldWallFX ) )
+ return
+ entity cpoint = vortexSphere.e.shieldWallFX.e.cpoint
+ if ( !IsValid( cpoint ) )
+ return
+ StartParticleEffectInWorldWithControlPoint( fx, origin, angles, cpoint.GetOrigin() )
+}
+
+void function SetVortexSphereShieldWallCPoint( entity vortexSphere, entity cpoint )
+{
+ Assert( IsValid( vortexSphere ) )
+ Assert( IsValid( vortexSphere.e.shieldWallFX ) )
+ SetShieldWallCPoint( vortexSphere.e.shieldWallFX, cpoint )
+}
+
+void function SetShieldWallCPoint( entity shieldWallFX, entity cpoint )
+{
+ Assert( IsValid( shieldWallFX ) )
+ Assert( IsValid( cpoint ) )
+ shieldWallFX.e.cpoint = cpoint
+}
+
+void function StopShieldWallFX( entity vortexSphere )
+{
+ entity shieldWallFX = vortexSphere.e.shieldWallFX
+ vortexSphere.e.shieldWallFX = null
+
+ if ( !IsValid( shieldWallFX ) )
+ return
+
+ shieldWallFX.Fire( "StopPlayEndCap" )
+ shieldWallFX.Fire( "Kill", "", 1.0 )
+
+ if ( IsValid( shieldWallFX.e.cpoint ) )
+ shieldWallFX.e.cpoint.Fire( "Kill", "", 1.0 )
+ EffectStop( shieldWallFX )
+}
+
+void function StopShieldWallFXOverTime( entity vortexSphere, float duration )
+{
+ entity shieldWallFX = vortexSphere.e.shieldWallFX
+ shieldWallFX.Fire( "StopPlayEndCap", "", duration )
+ shieldWallFX.Fire( "Kill", "", duration )
+ shieldWallFX.e.cpoint.Fire( "Kill", "", duration )
+}
+
+void function SetShieldWallCPointOrigin( entity shieldWallFX, vector AT_TURRET_SHIELD_COLOR )
+{
+ Assert( IsValid( shieldWallFX ) )
+ if ( !IsValid( shieldWallFX.e.cpoint ) )
+ return
+ shieldWallFX.e.cpoint.SetOrigin( AT_TURRET_SHIELD_COLOR )
+}
+
+entity function GetShieldWallFXCPoint( entity shieldWallFX )
+{
+ Assert( IsValid( shieldWallFX.e.cpoint ) )
+ return shieldWallFX.e.cpoint
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_team_emp.gnut b/Northstar.CustomServers/scripts/vscripts/weapons/_team_emp.gnut
new file mode 100644
index 000000000..41d428484
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_team_emp.gnut
@@ -0,0 +1,38 @@
+global function TeamEMP_Init
+global function EMPEffects
+
+void function TeamEMP_Init()
+{
+ RegisterSignal( "PlayerEMPed" )
+}
+
+void function EMPEffects( entity player, float time )
+{
+ player.nv.empEndTime = Time() + time
+
+ player.Signal( "PlayerEMPed" )
+
+ // remember this is a stack so you need to enable as many times as you disable
+ DisableOffhandWeapons( player )
+
+ thread RecoverFromEMP( player, time )
+}
+
+void function RecoverFromEMP( entity player, float time )
+{
+ svGlobal.levelEnt.EndSignal( "BurnMeter_PreMatchEnter" )
+ player.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ {
+ // remember this is a stack so you need to enable as many times as you disable
+ EnableOffhandWeapons( player )
+ }
+ }
+ )
+
+ wait time + 0.1
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_vortex.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_vortex.nut
new file mode 100644
index 000000000..f1e46a531
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_vortex.nut
@@ -0,0 +1,1983 @@
+untyped
+
+global function Vortex_Init
+
+global function CreateVortexSphere
+global function DestroyVortexSphereFromVortexWeapon
+global function EnableVortexSphere
+#if SERVER
+global function ValidateVortexImpact
+global function TryVortexAbsorb
+global function SetVortexSphereBulletHitRules
+global function SetVortexSphereProjectileHitRules
+#endif
+global function VortexDrainedByImpact
+global function VortexPrimaryAttack
+global function GetVortexSphereCurrentColor
+global function GetShieldTriLerpColor
+global function IsVortexing
+#if SERVER
+global function Vortex_HandleElectricDamage
+global function VortexSphereDrainHealthForDamage
+global function Vortex_CreateImpactEventData
+global function Vortex_SpawnHeatShieldPingFX
+#endif
+
+global function Vortex_SetTagName
+global function Vortex_SetBulletCollectionOffset
+
+global function CodeCallback_OnVortexHitBullet
+global function CodeCallback_OnVortexHitProjectile
+
+const AMPED_WALL_IMPACT_FX = $"P_impact_xo_shield_cp"
+
+global const PROTO_AMPED_WALL = "proto_amped_wall"
+global const GUN_SHIELD_WALL = "gun_shield_wall"
+const PROX_MINE_MODEL = $"models/weapons/caber_shot/caber_shot_thrown.mdl"
+
+const VORTEX_SPHERE_COLOR_CHARGE_FULL = <115, 247, 255> // blue
+const VORTEX_SPHERE_COLOR_CHARGE_MED = <200, 128, 80> // orange
+const VORTEX_SPHERE_COLOR_CHARGE_EMPTY = <200, 80, 80> // red
+const VORTEX_SPHERE_COLOR_PAS_ION_VORTEX = <115, 174, 255> // blue
+const AMPED_DAMAGE_SCALAR = 1.5
+
+const VORTEX_SPHERE_COLOR_CROSSOVERFRAC_FULL2MED = 0.75 // from zero to this fraction, fade between full and medium charge colors
+const VORTEX_SPHERE_COLOR_CROSSOVERFRAC_MED2EMPTY = 0.95 // from "full2med" to this fraction, fade between medium and empty charge colors
+
+const VORTEX_BULLET_ABSORB_COUNT_MAX = 32
+const VORTEX_PROJECTILE_ABSORB_COUNT_MAX = 32
+
+const VORTEX_TIMED_EXPLOSIVE_FUSETIME = 2.75 // fuse time for absorbed projectiles
+const VORTEX_TIMED_EXPLOSIVE_FUSETIME_WARNINGFRAC = 0.75 // wait this fraction of the fuse time before warning the player it's about to explode
+
+const VORTEX_EXP_ROUNDS_RETURN_SPREAD_XY = 0.15
+const VORTEX_EXP_ROUNDS_RETURN_SPREAD_Z = 0.075
+
+const VORTEX_ELECTRIC_DAMAGE_CHARGE_DRAIN_MIN = 0.1 // fraction of charge time
+const VORTEX_ELECTRIC_DAMAGE_CHARGE_DRAIN_MAX = 0.3
+
+//The shotgun spams a lot of pellets that deal too much damage if they return full damage.
+const VORTEX_SHOTGUN_DAMAGE_RATIO = 0.25
+
+
+const SHIELD_WALL_BULLET_FX = $"P_impact_xo_shield_cp"
+const SHIELD_WALL_EXPMED_FX = $"P_impact_exp_med_xo_shield_CP"
+
+const SIGNAL_ID_BULLET_HIT_THINK = "signal_id_bullet_hit_think"
+
+const VORTEX_EXPLOSIVE_WARNING_SFX_LOOP = "Weapon_Vortex_Gun.ExplosiveWarningBeep"
+
+const VORTEX_PILOT_WEAPON_WEAKNESS_DAMAGESCALE = 6.0
+
+// These match the strings in the WeaponEd dropdown box for vortex_refire_behavior
+global const VORTEX_REFIRE_NONE = ""
+global const VORTEX_REFIRE_ABSORB = "absorb"
+global const VORTEX_REFIRE_BULLET = "bullet"
+global const VORTEX_REFIRE_EXPLOSIVE_ROUND = "explosive_round"
+global const VORTEX_REFIRE_ROCKET = "rocket"
+global const VORTEX_REFIRE_GRENADE = "grenade"
+global const VORTEX_REFIRE_GRENADE_LONG_FUSE = "grenade_long_fuse"
+
+const VortexIgnoreClassnames = {
+ ["mp_titancore_flame_wave"] = true,
+ ["mp_ability_grapple"] = true,
+ ["mp_ability_shifter"] = true,
+}
+
+table vortexImpactWeaponInfo
+
+const DEG_COS_60 = cos( 60 * DEG_TO_RAD )
+
+function Vortex_Init()
+{
+ PrecacheParticleSystem( SHIELD_WALL_BULLET_FX )
+ GetParticleSystemIndex( SHIELD_WALL_BULLET_FX )
+ PrecacheParticleSystem( SHIELD_WALL_EXPMED_FX )
+ GetParticleSystemIndex( SHIELD_WALL_EXPMED_FX )
+ PrecacheParticleSystem( AMPED_WALL_IMPACT_FX )
+ GetParticleSystemIndex( AMPED_WALL_IMPACT_FX )
+
+ RegisterSignal( SIGNAL_ID_BULLET_HIT_THINK )
+ RegisterSignal( "VortexStopping" )
+
+ RegisterSignal( "VortexAbsorbed" )
+ RegisterSignal( "VortexFired" )
+ RegisterSignal( "Script_OnDamaged" )
+}
+
+#if SERVER
+var function VortexBulletHitRules_Default( entity vortexSphere, var damageInfo )
+{
+ return damageInfo
+}
+
+bool function VortexProjectileHitRules_Default( entity vortexSphere, entity attacker, bool takesDamageByDefault )
+{
+ return takesDamageByDefault
+}
+
+void function SetVortexSphereBulletHitRules( entity vortexSphere, var functionref( entity, var ) customRules )
+{
+ vortexSphere.e.BulletHitRules = customRules
+}
+
+void function SetVortexSphereProjectileHitRules( entity vortexSphere, bool functionref( entity, entity, bool ) customRules )
+{
+ vortexSphere.e.ProjectileHitRules = customRules
+}
+#endif
+function CreateVortexSphere( entity vortexWeapon, bool useCylinderCheck, bool blockOwnerWeapon, int sphereRadius = 40, int bulletFOV = 180 )
+{
+ entity owner = vortexWeapon.GetWeaponOwner()
+ Assert( owner )
+
+ #if SERVER
+ //printt( "util ent:", vortexWeapon.GetWeaponUtilityEntity() )
+ Assert ( !vortexWeapon.GetWeaponUtilityEntity(), "Tried to create more than one vortex sphere on a vortex weapon!" )
+
+ entity vortexSphere = CreateEntity( "vortex_sphere" )
+ Assert( vortexSphere )
+
+ int spawnFlags = SF_ABSORB_BULLETS | SF_BLOCK_NPC_WEAPON_LOF
+
+ if ( useCylinderCheck )
+ {
+ spawnFlags = spawnFlags | SF_ABSORB_CYLINDER
+ vortexSphere.kv.height = sphereRadius * 2
+ }
+
+ if ( blockOwnerWeapon )
+ spawnFlags = spawnFlags | SF_BLOCK_OWNER_WEAPON
+
+ vortexSphere.kv.spawnflags = spawnFlags
+
+ vortexSphere.kv.enabled = 0
+ vortexSphere.kv.radius = sphereRadius
+ vortexSphere.kv.bullet_fov = bulletFOV
+ vortexSphere.kv.physics_pull_strength = 25
+ vortexSphere.kv.physics_side_dampening = 6
+ vortexSphere.kv.physics_fov = 360
+ vortexSphere.kv.physics_max_mass = 2
+ vortexSphere.kv.physics_max_size = 6
+ Assert( owner.IsNPC() || owner.IsPlayer(), "Vortex script expects the weapon owner to be a player or NPC." )
+
+ SetVortexSphereBulletHitRules( vortexSphere, VortexBulletHitRules_Default )
+ SetVortexSphereProjectileHitRules( vortexSphere, VortexProjectileHitRules_Default )
+
+ DispatchSpawn( vortexSphere )
+
+ vortexSphere.SetOwner( owner )
+
+ if ( owner.IsNPC() )
+ {
+ vortexSphere.SetParent( owner, "PROPGUN" )
+ vortexSphere.SetLocalOrigin( Vector( 0, 35, 0 ) )
+ }
+ else
+ {
+ vortexSphere.SetParent( owner )
+ vortexSphere.SetLocalOrigin( Vector( 0, 10, -30 ) )
+ }
+ vortexSphere.SetAbsAngles( Vector( 0, 0, 0 ) ) //Setting local angles on a parented object is not supported
+
+ vortexSphere.SetOwnerWeapon( vortexWeapon )
+ vortexWeapon.SetWeaponUtilityEntity( vortexSphere )
+ #endif
+
+ SetVortexAmmo( vortexWeapon, 0 )
+}
+
+
+function EnableVortexSphere( entity vortexWeapon )
+{
+ string tagname = GetVortexTagName( vortexWeapon )
+ entity weaponOwner = vortexWeapon.GetWeaponOwner()
+ local hasBurnMod = vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod )
+
+ #if SERVER
+ entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity()
+ Assert( vortexSphere )
+ vortexSphere.FireNow( "Enable" )
+
+ thread SetPlayerUsingVortex( weaponOwner, vortexWeapon )
+
+ Vortex_CreateAbsorbFX_ControlPoints( vortexWeapon )
+
+ // world (3P) version of the vortex sphere FX
+ vortexSphere.s.worldFX <- CreateEntity( "info_particle_system" )
+
+ if ( hasBurnMod )
+ {
+ if ( "fxChargingControlPointBurn" in vortexWeapon.s )
+ vortexSphere.s.worldFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxChargingControlPointBurn ) )
+ }
+ else
+ {
+ if ( "fxChargingControlPoint" in vortexWeapon.s )
+ vortexSphere.s.worldFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxChargingControlPoint ) )
+ }
+
+ vortexSphere.s.worldFX.kv.start_active = 1
+ vortexSphere.s.worldFX.SetOwner( weaponOwner )
+ vortexSphere.s.worldFX.SetParent( vortexWeapon, tagname )
+ vortexSphere.s.worldFX.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // not owner only
+ vortexSphere.s.worldFX.kv.cpoint1 = vortexWeapon.s.vortexSphereColorCP.GetTargetName()
+ vortexSphere.s.worldFX.SetStopType( "destroyImmediately" )
+
+ DispatchSpawn( vortexSphere.s.worldFX )
+ #endif
+
+ SetVortexAmmo( vortexWeapon, 0 )
+
+ #if CLIENT
+ if ( IsLocalViewPlayer( weaponOwner ) )
+ {
+ local fxAlias = null
+
+ if ( hasBurnMod )
+ {
+ if ( "fxChargingFPControlPointBurn" in vortexWeapon.s )
+ fxAlias = vortexWeapon.s.fxChargingFPControlPointBurn
+ }
+ else
+ {
+ if ( "fxChargingFPControlPoint" in vortexWeapon.s )
+ fxAlias = vortexWeapon.s.fxChargingFPControlPoint
+ }
+
+ if ( fxAlias )
+ {
+ int sphereClientFXHandle = vortexWeapon.PlayWeaponEffectReturnViewEffectHandle( fxAlias, $"", tagname )
+ thread VortexSphereColorUpdate( vortexWeapon, sphereClientFXHandle )
+ }
+ }
+ #elseif SERVER
+ asset fxAlias = $""
+
+ if ( hasBurnMod )
+ {
+ if ( "fxChargingFPControlPointReplayBurn" in vortexWeapon.s )
+ fxAlias = expect asset( vortexWeapon.s.fxChargingFPControlPointReplayBurn )
+ }
+ else
+ {
+ if ( "fxChargingFPControlPointReplay" in vortexWeapon.s )
+ fxAlias = expect asset( vortexWeapon.s.fxChargingFPControlPointReplay )
+ }
+
+ if ( fxAlias != $"" )
+ vortexWeapon.PlayWeaponEffect( fxAlias, $"", tagname )
+
+ thread VortexSphereColorUpdate( vortexWeapon )
+ #endif
+}
+
+
+function DestroyVortexSphereFromVortexWeapon( entity vortexWeapon )
+{
+ DisableVortexSphereFromVortexWeapon( vortexWeapon )
+
+ #if SERVER
+ DestroyVortexSphere( vortexWeapon.GetWeaponUtilityEntity() )
+ vortexWeapon.SetWeaponUtilityEntity( null )
+ #endif
+}
+
+void function DestroyVortexSphere( entity vortexSphere )
+{
+ if ( IsValid( vortexSphere ) )
+ {
+ vortexSphere.s.worldFX.Destroy()
+ vortexSphere.Destroy()
+ }
+}
+
+
+function DisableVortexSphereFromVortexWeapon( entity vortexWeapon )
+{
+ vortexWeapon.Signal( "VortexStopping" )
+
+ // server cleanup
+ #if SERVER
+ DisableVortexSphere( vortexWeapon.GetWeaponUtilityEntity() )
+ Vortex_CleanupAllEffects( vortexWeapon )
+ Vortex_ClearImpactEventData( vortexWeapon )
+ #endif
+
+ // client & server cleanup
+
+ if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) )
+ {
+ if ( "fxChargingFPControlPointBurn" in vortexWeapon.s )
+ vortexWeapon.StopWeaponEffect( expect asset( vortexWeapon.s.fxChargingFPControlPointBurn ), $"" )
+ if ( "fxChargingFPControlPointReplayBurn" in vortexWeapon.s )
+ vortexWeapon.StopWeaponEffect( expect asset( vortexWeapon.s.fxChargingFPControlPointReplayBurn ), $"" )
+ }
+ else
+ {
+ if ( "fxChargingFPControlPoint" in vortexWeapon.s )
+ vortexWeapon.StopWeaponEffect( expect asset( vortexWeapon.s.fxChargingFPControlPoint ), $"" )
+ if ( "fxChargingFPControlPointReplay" in vortexWeapon.s )
+ vortexWeapon.StopWeaponEffect( expect asset( vortexWeapon.s.fxChargingFPControlPointReplay ), $"" )
+ }
+}
+
+void function DisableVortexSphere( entity vortexSphere )
+{
+ if ( IsValid( vortexSphere ) )
+ {
+ vortexSphere.FireNow( "Disable" )
+ vortexSphere.Signal( SIGNAL_ID_BULLET_HIT_THINK )
+ }
+
+}
+
+
+#if SERVER
+function Vortex_CreateAbsorbFX_ControlPoints( entity vortexWeapon )
+{
+ entity player = vortexWeapon.GetWeaponOwner()
+ Assert( player )
+
+ // vortex swirling incoming rounds FX location control point
+ if ( !( "vortexBulletEffectCP" in vortexWeapon.s ) )
+ vortexWeapon.s.vortexBulletEffectCP <- null
+ vortexWeapon.s.vortexBulletEffectCP = CreateEntity( "info_placement_helper" )
+ SetTargetName( expect entity( vortexWeapon.s.vortexBulletEffectCP ), UniqueString( "vortexBulletEffectCP" ) )
+ vortexWeapon.s.vortexBulletEffectCP.kv.start_active = 1
+
+ DispatchSpawn( vortexWeapon.s.vortexBulletEffectCP )
+
+ vector offset = GetBulletCollectionOffset( vortexWeapon )
+ vector origin = player.OffsetPositionFromView( player.EyePosition(), offset )
+
+ vortexWeapon.s.vortexBulletEffectCP.SetOrigin( origin )
+ vortexWeapon.s.vortexBulletEffectCP.SetParent( player )
+
+ // vortex sphere color control point
+ if ( !( "vortexSphereColorCP" in vortexWeapon.s ) )
+ vortexWeapon.s.vortexSphereColorCP <- null
+ vortexWeapon.s.vortexSphereColorCP = CreateEntity( "info_placement_helper" )
+ SetTargetName( expect entity( vortexWeapon.s.vortexSphereColorCP ), UniqueString( "vortexSphereColorCP" ) )
+ vortexWeapon.s.vortexSphereColorCP.kv.start_active = 1
+
+ DispatchSpawn( vortexWeapon.s.vortexSphereColorCP )
+}
+
+
+function Vortex_CleanupAllEffects( entity vortexWeapon )
+{
+ Assert( IsServer() )
+
+ Vortex_CleanupImpactAbsorbFX( vortexWeapon )
+
+ if ( ( "vortexBulletEffectCP" in vortexWeapon.s ) && IsValid_ThisFrame( expect entity( vortexWeapon.s.vortexBulletEffectCP ) ) )
+ vortexWeapon.s.vortexBulletEffectCP.Destroy()
+
+ if ( ( "vortexSphereColorCP" in vortexWeapon.s ) && IsValid_ThisFrame( expect entity( vortexWeapon.s.vortexSphereColorCP ) ) )
+ vortexWeapon.s.vortexSphereColorCP.Destroy()
+}
+#endif // SERVER
+
+
+function SetPlayerUsingVortex( entity weaponOwner, entity vortexWeapon )
+{
+ weaponOwner.EndSignal( "OnDeath" )
+
+ weaponOwner.s.isVortexing <- true
+
+ vortexWeapon.WaitSignal( "VortexStopping" )
+
+ OnThreadEnd
+ (
+ function() : ( weaponOwner )
+ {
+ if ( IsValid_ThisFrame( weaponOwner ) && "isVortexing" in weaponOwner.s )
+ {
+ delete weaponOwner.s.isVortexing
+ }
+ }
+ )
+}
+
+
+function IsVortexing( entity ent )
+{
+ Assert( IsServer() )
+
+ if ( "isVortexing" in ent.s )
+ return true
+}
+
+
+#if SERVER
+function Vortex_HandleElectricDamage( entity ent, entity attacker, damage, entity weapon )
+{
+ if ( !IsValid( ent ) )
+ return damage
+
+ if ( !ent.IsTitan() )
+ return damage
+
+ if ( !ent.IsPlayer() && !ent.IsNPC() )
+ return damage
+
+ if ( !IsVortexing( ent ) )
+ return damage
+
+ entity vortexWeapon = ent.GetActiveWeapon()
+ if ( !IsValid( vortexWeapon ) )
+ return damage
+
+ entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity()
+ if ( !IsValid( vortexSphere ) )
+ return damage
+
+ if ( !IsValid( vortexWeapon ) || !IsValid( vortexSphere ) )
+ return damage
+
+ // vortex FOV check
+ //printt( "sphere FOV:", vortexSphere.kv.bullet_fov )
+ local sphereFOV = vortexSphere.kv.bullet_fov.tointeger()
+ entity attackerWeapon = attacker.GetActiveWeapon()
+ int attachIdx = attackerWeapon.LookupAttachment( "muzzle_flash" )
+ vector beamOrg = attackerWeapon.GetAttachmentOrigin( attachIdx )
+ vector firingDir = beamOrg - vortexSphere.GetOrigin()
+ firingDir = Normalize( firingDir )
+ vector vortexDir = AnglesToForward( vortexSphere.GetAngles() )
+
+ float dot = DotProduct( vortexDir, firingDir )
+
+ float degCos = DEG_COS_60
+ if ( sphereFOV != 120 )
+ deg_cos( sphereFOV * 0.5 )
+
+ // not in the vortex cone
+ if ( dot < degCos )
+ return damage
+
+ if ( "fxElectricalExplosion" in vortexWeapon.s )
+ {
+ entity fxRef = CreateEntity( "info_particle_system" )
+ fxRef.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxElectricalExplosion ) )
+ fxRef.kv.start_active = 1
+ fxRef.SetStopType( "destroyImmediately" )
+ //fxRef.kv.VisibilityFlags = ENTITY_VISIBLE_TO_OWNER // HACK this turns on owner only visibility. Uncomment when we hook up dedicated 3P effects
+ fxRef.SetOwner( ent )
+ fxRef.SetOrigin( vortexSphere.GetOrigin() )
+ fxRef.SetParent( ent )
+
+ DispatchSpawn( fxRef )
+ fxRef.Kill_Deprecated_UseDestroyInstead( 1 )
+ }
+
+ return 0
+}
+
+// this function handles all incoming vortex impact events
+bool function TryVortexAbsorb( entity vortexSphere, entity attacker, vector origin, int damageSourceID, entity weapon, string weaponName, string impactType, entity projectile = null, damageType = null, reflect = false )
+{
+ if ( weaponName in VortexIgnoreClassnames )
+ return false
+
+ entity vortexWeapon = vortexSphere.GetOwnerWeapon()
+ entity owner = vortexWeapon.GetWeaponOwner()
+
+ // keep cycling the oldest hitscan bullets out
+ if( !reflect )
+ {
+ if ( impactType == "hitscan" )
+ Vortex_ClampAbsorbedBulletCount( vortexWeapon )
+ else
+ Vortex_ClampAbsorbedProjectileCount( vortexWeapon )
+ }
+
+ // vortex spheres tag refired projectiles with info about the original projectile for accurate duplication when re-absorbed
+ if ( projectile )
+ {
+
+ // specifically for tether, since it gets moved to the vortex area and can get absorbed in the process, then destroyed
+ if ( !IsValid( projectile ) )
+ return false
+
+ entity projOwner = projectile.GetOwner()
+ if ( IsValid( projOwner ) && projOwner.GetTeam() == owner.GetTeam() )
+ return false
+
+ if ( projectile.proj.hasBouncedOffVortex )
+ return false
+
+ if ( projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_ignores_vortex" ) == "fall_vortex" )
+ {
+ vector velocity = projectile.GetVelocity()
+ vector multiplier = < -0.25, -0.25, -0.25 >
+ velocity = < velocity.x * multiplier.x, velocity.y * multiplier.y, velocity.z * multiplier.z >
+ projectile.SetVelocity( velocity )
+ projectile.proj.hasBouncedOffVortex = true
+ return false
+ }
+
+ // if ( projectile.GetParent() == owner )
+ // return false
+
+ if ( "originalDamageSource" in projectile.s )
+ {
+ damageSourceID = expect int( projectile.s.originalDamageSource )
+
+ // Vortex Volley Achievement
+ if ( IsValid( owner ) && owner.IsPlayer() )
+ {
+ //if ( PlayerProgressionAllowed( owner ) )
+ // SetAchievement( owner, "ach_vortexVolley", true )
+ }
+ }
+
+ // Max projectile stat tracking
+ int projectilesInVortex = 1
+ projectilesInVortex += vortexWeapon.w.vortexImpactData.len()
+
+ if ( IsValid( owner ) && owner.IsPlayer() )
+ {
+ if ( PlayerProgressionAllowed( owner ) )
+ {
+ int record = owner.GetPersistentVarAsInt( "mostProjectilesCollectedInVortex" )
+ if ( projectilesInVortex > record )
+ owner.SetPersistentVar( "mostProjectilesCollectedInVortex", projectilesInVortex )
+ }
+
+ var impact_sound_1p = projectile.ProjectileGetWeaponInfoFileKeyField( "vortex_impact_sound_1p" )
+ if ( impact_sound_1p != null )
+ EmitSoundOnEntityOnlyToPlayer( vortexSphere, owner, impact_sound_1p )
+ }
+
+ var impact_sound_3p = projectile.ProjectileGetWeaponInfoFileKeyField( "vortex_impact_sound_3p" )
+ if ( impact_sound_3p != null )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, impact_sound_3p )
+ }
+ else
+ {
+ if ( IsValid( owner ) && owner.IsPlayer() )
+ {
+ var impact_sound_1p = GetWeaponInfoFileKeyField_Global( weaponName, "vortex_impact_sound_1p" )
+ if ( impact_sound_1p != null )
+ EmitSoundOnEntityOnlyToPlayer( vortexSphere, owner, impact_sound_1p )
+ }
+
+ var impact_sound_3p = GetWeaponInfoFileKeyField_Global( weaponName, "vortex_impact_sound_3p" )
+ if ( impact_sound_3p != null )
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, impact_sound_3p )
+ }
+
+ local impactData = Vortex_CreateImpactEventData( vortexWeapon, attacker, origin, damageSourceID, weaponName, impactType )
+
+ VortexDrainedByImpact( vortexWeapon, weapon, projectile, damageType )
+ Vortex_NotifyAttackerDidDamage( expect entity( impactData.attacker ), owner, impactData.origin )
+
+ if ( impactData.refireBehavior == VORTEX_REFIRE_ABSORB )
+ return true
+
+ if ( vortexWeapon.GetWeaponClassName() == "mp_titanweapon_heat_shield" )
+ return true
+
+ if ( !Vortex_ScriptCanHandleImpactEvent( impactData ) )
+ return false
+
+ Vortex_StoreImpactEvent( vortexWeapon, impactData )
+
+ VortexImpact_PlayAbsorbedFX( vortexWeapon, impactData )
+
+ if ( impactType == "hitscan" )
+ vortexSphere.AddBulletToSphere();
+ else
+ vortexSphere.AddProjectileToSphere();
+
+ local maxShotgunPelletsToIgnore = VORTEX_BULLET_ABSORB_COUNT_MAX * ( 1 - VORTEX_SHOTGUN_DAMAGE_RATIO )
+ if ( IsPilotShotgunWeapon( weaponName ) && ( vortexWeapon.s.shotgunPelletsToIgnore + 1 ) < maxShotgunPelletsToIgnore )
+ vortexWeapon.s.shotgunPelletsToIgnore += ( 1 - VORTEX_SHOTGUN_DAMAGE_RATIO )
+
+ if ( reflect )
+ {
+ local attackParams = {}
+ attackParams.pos <- owner.EyePosition()
+ attackParams.dir <- owner.GetPlayerOrNPCViewVector()
+
+ int bulletsFired = VortexReflectAttack( vortexWeapon, attackParams, expect vector( impactData.origin ) )
+
+ Vortex_CleanupImpactAbsorbFX( vortexWeapon )
+ Vortex_ClearImpactEventData( vortexWeapon )
+
+ while ( vortexSphere.GetBulletAbsorbedCount() > 0 )
+ vortexSphere.RemoveBulletFromSphere();
+
+ while ( vortexSphere.GetProjectileAbsorbedCount() > 0 )
+ vortexSphere.RemoveProjectileFromSphere();
+ }
+
+ return true
+}
+#endif // SERVER
+
+function VortexDrainedByImpact( entity vortexWeapon, entity weapon, entity projectile, damageType )
+{
+ if ( vortexWeapon.HasMod( "unlimited_charge_time" ) )
+ return
+ if ( vortexWeapon.HasMod( "vortex_extended_effect_and_no_use_penalty" ) )
+ return
+
+ float amount
+ if ( projectile )
+ amount = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.vortex_drain )
+ else
+ amount = weapon.GetWeaponSettingFloat( eWeaponVar.vortex_drain )
+
+ if ( amount <= 0.0 )
+ return
+
+ if ( vortexWeapon.GetWeaponClassName() == "mp_titanweapon_vortex_shield_ion" )
+ {
+ entity owner = vortexWeapon.GetWeaponOwner()
+ int totalEnergy = owner.GetSharedEnergyTotal()
+ owner.TakeSharedEnergy( int( float( totalEnergy ) * amount ) )
+ }
+ else
+ {
+ float frac = min ( vortexWeapon.GetWeaponChargeFraction() + amount, 1.0 )
+ vortexWeapon.SetWeaponChargeFraction( frac )
+ }
+}
+
+
+function VortexSlowOwnerFromAttacker( entity player, entity attacker, vector velocity, float multiplier )
+{
+ vector damageForward = player.GetOrigin() - attacker.GetOrigin()
+ damageForward.z = 0
+ damageForward.Norm()
+
+ vector velForward = player.GetVelocity()
+ velForward.z = 0
+ velForward.Norm()
+
+ float dot = DotProduct( velForward, damageForward )
+ if ( dot >= -0.5 )
+ return
+
+ dot += 0.5
+ dot *= -2.0
+
+ vector negateVelocity = velocity * -multiplier
+ negateVelocity *= dot
+
+ velocity += negateVelocity
+ player.SetVelocity( velocity )
+}
+
+
+#if SERVER
+function Vortex_ClampAbsorbedBulletCount( entity vortexWeapon )
+{
+ if ( GetBulletsAbsorbedCount( vortexWeapon ) >= ( VORTEX_BULLET_ABSORB_COUNT_MAX - 1 ) )
+ Vortex_RemoveOldestAbsorbedBullet( vortexWeapon )
+}
+
+function Vortex_ClampAbsorbedProjectileCount( entity vortexWeapon )
+{
+ if ( GetProjectilesAbsorbedCount( vortexWeapon ) >= ( VORTEX_PROJECTILE_ABSORB_COUNT_MAX - 1 ) )
+ Vortex_RemoveOldestAbsorbedProjectile( vortexWeapon )
+}
+
+function Vortex_RemoveOldestAbsorbedBullet( entity vortexWeapon )
+{
+ entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity()
+
+ local bulletImpacts = Vortex_GetHitscanBulletImpacts( vortexWeapon )
+ local impactDataToRemove = bulletImpacts[ 0 ] // since it's an array, the first one will be the oldest
+
+ Vortex_RemoveImpactEvent( vortexWeapon, impactDataToRemove )
+
+ vortexSphere.RemoveBulletFromSphere()
+}
+
+function Vortex_RemoveOldestAbsorbedProjectile( entity vortexWeapon )
+{
+ entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity()
+
+ local projImpacts = Vortex_GetProjectileImpacts( vortexWeapon )
+ local impactDataToRemove = projImpacts[ 0 ] // since it's an array, the first one will be the oldest
+
+ Vortex_RemoveImpactEvent( vortexWeapon, impactDataToRemove )
+
+ vortexSphere.RemoveProjectileFromSphere()
+}
+
+function Vortex_CreateImpactEventData( entity vortexWeapon, entity attacker, vector origin, int damageSourceID, string weaponName, string impactType )
+{
+ entity player = vortexWeapon.GetWeaponOwner()
+ local impactData = {}
+
+ impactData.attacker <- attacker
+ impactData.origin <- origin
+ impactData.damageSourceID <- damageSourceID
+ impactData.weaponName <- weaponName
+ impactData.impactType <- impactType
+
+ impactData.refireBehavior <- VORTEX_REFIRE_NONE
+ impactData.absorbSFX <- "Vortex_Shield_AbsorbBulletSmall"
+ impactData.absorbSFX_1p_vs_3p <- null
+
+ impactData.team <- null
+ // sets a team even if the attacker disconnected
+ if ( IsValid_ThisFrame( attacker ) )
+ {
+ impactData.team = attacker.GetTeam()
+ }
+ else
+ {
+ // default to opposite team
+ if ( player.GetTeam() == TEAM_IMC )
+ impactData.team = TEAM_MILITIA
+ else
+ impactData.team = TEAM_IMC
+ }
+
+ impactData.absorbFX <- null
+ impactData.absorbFX_3p <- null
+ impactData.fxEnt_absorb <- null
+
+ impactData.explosionradius <- null
+ impactData.explosion_damage <- null
+ impactData.impact_effect_table <- -1
+ // -- everything from here down relies on being able to read a megaweapon file
+ if ( !( impactData.weaponName in vortexImpactWeaponInfo ) )
+ {
+ vortexImpactWeaponInfo[ impactData.weaponName ] <- {}
+ vortexImpactWeaponInfo[ impactData.weaponName ].absorbFX <- GetWeaponInfoFileKeyFieldAsset_Global( impactData.weaponName, "vortex_absorb_effect" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].absorbFX_3p <- GetWeaponInfoFileKeyFieldAsset_Global( impactData.weaponName, "vortex_absorb_effect_third_person" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].refireBehavior <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "vortex_refire_behavior" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].absorbSound <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "vortex_absorb_sound" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].absorbSound_1p_vs_3p <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "vortex_absorb_sound_1p_vs_3p" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].explosionradius <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "explosionradius" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].explosion_damage_heavy_armor <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "explosion_damage_heavy_armor" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].explosion_damage <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "explosion_damage" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].impact_effect_table <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "impact_effect_table" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].grenade_ignition_time <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "grenade_ignition_time" )
+ vortexImpactWeaponInfo[ impactData.weaponName ].grenade_fuse_time <- GetWeaponInfoFileKeyField_Global( impactData.weaponName, "grenade_fuse_time" )
+ }
+
+ impactData.absorbFX = vortexImpactWeaponInfo[ impactData.weaponName ].absorbFX
+ impactData.absorbFX_3p = vortexImpactWeaponInfo[ impactData.weaponName ].absorbFX_3p
+ if ( impactData.absorbFX )
+ Assert( impactData.absorbFX_3p, "Missing 3rd person absorb effect for " + impactData.weaponName )
+ impactData.refireBehavior = vortexImpactWeaponInfo[ impactData.weaponName ].refireBehavior
+
+ local absorbSound = vortexImpactWeaponInfo[ impactData.weaponName ].absorbSound
+ if ( absorbSound )
+ impactData.absorbSFX = absorbSound
+
+ local absorbSound_1p_vs_3p = vortexImpactWeaponInfo[ impactData.weaponName ].absorbSound_1p_vs_3p
+ if ( absorbSound_1p_vs_3p )
+ impactData.absorbSFX_1p_vs_3p = absorbSound_1p_vs_3p
+
+ // info we need for refiring (some types of) impacts
+ impactData.explosionradius = vortexImpactWeaponInfo[ impactData.weaponName ].explosionradius
+ impactData.explosion_damage = vortexImpactWeaponInfo[ impactData.weaponName ].explosion_damage_heavy_armor
+ if ( impactData.explosion_damage == null )
+ impactData.explosion_damage = vortexImpactWeaponInfo[ impactData.weaponName ].explosion_damage
+ impactData.impact_effect_table = vortexImpactWeaponInfo[ impactData.weaponName ].impact_effect_table
+
+ return impactData
+}
+
+function Vortex_ScriptCanHandleImpactEvent( impactData )
+{
+ if ( impactData.refireBehavior == VORTEX_REFIRE_NONE )
+ return false
+
+ if ( !impactData.absorbFX )
+ return false
+
+ if ( impactData.impactType == "projectile" && !impactData.impact_effect_table )
+ return false
+
+ return true
+}
+
+function Vortex_StoreImpactEvent( entity vortexWeapon, impactData )
+{
+ vortexWeapon.w.vortexImpactData.append( impactData )
+}
+
+// safely removes data for a single impact event
+function Vortex_RemoveImpactEvent( entity vortexWeapon, impactData )
+{
+ Vortex_ImpactData_KillAbsorbFX( impactData )
+
+ vortexWeapon.w.vortexImpactData.fastremovebyvalue( impactData )
+}
+
+function Vortex_GetAllImpactEvents( entity vortexWeapon )
+{
+ return vortexWeapon.w.vortexImpactData
+}
+
+function Vortex_ClearImpactEventData( entity vortexWeapon )
+{
+ vortexWeapon.w.vortexImpactData = []
+}
+
+function VortexImpact_PlayAbsorbedFX( entity vortexWeapon, impactData )
+{
+ // generic shield ping FX
+ Vortex_SpawnShieldPingFX( vortexWeapon, impactData )
+
+ // specific absorb FX
+ impactData.fxEnt_absorb = Vortex_SpawnImpactAbsorbFX( vortexWeapon, impactData )
+}
+
+// FX played when something first enters the vortex sphere
+function Vortex_SpawnShieldPingFX( entity vortexWeapon, impactData )
+{
+ entity player = vortexWeapon.GetWeaponOwner()
+ Assert( player )
+
+ local absorbSFX = impactData.absorbSFX
+ //printt( "SFX absorb sound:", absorbSFX )
+ if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) )
+ EmitSoundOnEntity( vortexWeapon, "Vortex_Shield_Deflect_Amped" )
+ else
+ {
+ EmitSoundOnEntity( vortexWeapon, absorbSFX )
+ if ( impactData.absorbSFX_1p_vs_3p != null )
+ {
+ if ( IsValid( impactData.attacker ) && impactData.attacker.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( vortexWeapon, impactData.attacker, impactData.absorbSFX_1p_vs_3p )
+ }
+ }
+ }
+
+ entity pingFX = CreateEntity( "info_particle_system" )
+
+ if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) )
+ {
+ if ( "fxBulletHitBurn" in vortexWeapon.s )
+ pingFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxBulletHitBurn ) )
+ }
+ else
+ {
+ if ( "fxBulletHit" in vortexWeapon.s )
+ pingFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxBulletHit ) )
+ }
+
+ pingFX.kv.start_active = 1
+
+ DispatchSpawn( pingFX )
+
+ pingFX.SetOrigin( impactData.origin )
+ pingFX.SetParent( player )
+ pingFX.Kill_Deprecated_UseDestroyInstead( 0.25 )
+}
+
+function Vortex_SpawnHeatShieldPingFX( entity vortexWeapon, impactData, bool impactTypeIsBullet )
+{
+ entity player = vortexWeapon.GetWeaponOwner()
+ Assert( player )
+
+ if ( impactTypeIsBullet )
+ EmitSoundOnEntity( vortexWeapon, "heat_shield_stop_bullet" )
+ else
+ EmitSoundOnEntity( vortexWeapon, "heat_shield_stop_projectile" )
+
+ entity pingFX = CreateEntity( "info_particle_system" )
+
+ if ( "fxBulletHit" in vortexWeapon.s )
+ pingFX.SetValueForEffectNameKey( expect asset( vortexWeapon.s.fxBulletHit ) )
+
+ pingFX.kv.start_active = 1
+
+ DispatchSpawn( pingFX )
+
+ pingFX.SetOrigin( impactData.origin )
+ pingFX.SetParent( player )
+ pingFX.Kill_Deprecated_UseDestroyInstead( 0.25 )
+}
+
+function Vortex_SpawnImpactAbsorbFX( entity vortexWeapon, impactData )
+{
+ // in case we're in the middle of cleaning the weapon up
+ if ( !IsValid( vortexWeapon.s.vortexBulletEffectCP ) )
+ return
+
+ entity owner = vortexWeapon.GetWeaponOwner()
+ Assert( owner )
+
+ local fxRefs = []
+
+ // owner
+ {
+ entity fxRef = CreateEntity( "info_particle_system" )
+
+ fxRef.SetValueForEffectNameKey( expect asset( impactData.absorbFX ) )
+ fxRef.kv.start_active = 1
+ fxRef.SetStopType( "destroyImmediately" )
+ fxRef.kv.VisibilityFlags = ENTITY_VISIBLE_TO_OWNER
+ fxRef.kv.cpoint1 = vortexWeapon.s.vortexBulletEffectCP.GetTargetName()
+
+ DispatchSpawn( fxRef )
+
+ fxRef.SetOwner( owner )
+ fxRef.SetOrigin( impactData.origin )
+ fxRef.SetParent( owner )
+
+ fxRefs.append( fxRef )
+ }
+
+ // everyone else
+ {
+ entity fxRef = CreateEntity( "info_particle_system" )
+
+ fxRef.SetValueForEffectNameKey( expect asset( impactData.absorbFX_3p ) )
+ fxRef.kv.start_active = 1
+ fxRef.SetStopType( "destroyImmediately" )
+ fxRef.kv.VisibilityFlags = (ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY) // other only visibility
+ fxRef.kv.cpoint1 = vortexWeapon.s.vortexBulletEffectCP.GetTargetName()
+
+ DispatchSpawn( fxRef )
+
+ fxRef.SetOwner( owner )
+ fxRef.SetOrigin( impactData.origin )
+ fxRef.SetParent( owner )
+
+ fxRefs.append( fxRef )
+ }
+
+ return fxRefs
+}
+
+function Vortex_CleanupImpactAbsorbFX( entity vortexWeapon )
+{
+ foreach ( impactData in Vortex_GetAllImpactEvents( vortexWeapon ) )
+ {
+ Vortex_ImpactData_KillAbsorbFX( impactData )
+ }
+}
+
+function Vortex_ImpactData_KillAbsorbFX( impactData )
+{
+ foreach ( fxRef in impactData.fxEnt_absorb )
+ {
+ if ( !IsValid( fxRef ) )
+ continue
+
+ fxRef.Fire( "DestroyImmediately" )
+ fxRef.Kill_Deprecated_UseDestroyInstead()
+ }
+}
+
+bool function PlayerDiedOrDisconnected( entity player )
+{
+ if ( !IsValid( player ) )
+ return true
+
+ if ( !IsAlive( player ) )
+ return true
+
+ if ( IsDisconnected( player ) )
+ return true
+
+ return false
+}
+
+#endif // SERVER
+
+int function VortexPrimaryAttack( entity vortexWeapon, WeaponPrimaryAttackParams attackParams )
+{
+ entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity()
+ if ( !vortexSphere )
+ return 0
+
+ #if SERVER
+ Assert( vortexSphere )
+ #endif
+
+ int totalfired = 0
+ int totalAttempts = 0
+
+ bool forceReleased = false
+ // in this case, it's also considered "force released" if the charge time runs out
+ if ( vortexWeapon.IsForceRelease() || vortexWeapon.GetWeaponChargeFraction() == 1 )
+ forceReleased = true
+
+ // PREDICTED REFIRES
+ // bullet impact events don't individually fire back per event because we aggregate and then shotgun blast them
+ int bulletsFired = Vortex_FireBackBullets( vortexWeapon, attackParams )
+ totalfired += bulletsFired
+
+ // UNPREDICTED REFIRES
+ #if SERVER
+ //printt( "server: force released?", forceReleased )
+
+ local unpredictedRefires = Vortex_GetProjectileImpacts( vortexWeapon )
+
+ // HACK we don't actually want to refire them with a spiral but
+ // this is to temporarily ensure compatibility with the Titan rocket launcher
+ if ( !( "spiralMissileIdx" in vortexWeapon.s ) )
+ vortexWeapon.s.spiralMissileIdx <- null
+ vortexWeapon.s.spiralMissileIdx = 0
+
+ foreach ( impactData in unpredictedRefires )
+ {
+ table fakeAttackParams = {pos = attackParams.pos, dir = attackParams.dir, firstTimePredicted = attackParams.firstTimePredicted, burstIndex = attackParams.burstIndex}
+ bool didFire = DoVortexAttackForImpactData( vortexWeapon, fakeAttackParams, impactData, totalAttempts )
+ if ( didFire )
+ totalfired++
+ totalAttempts++
+ }
+ //printt( "totalfired", totalfired )
+ #else
+ totalfired += GetProjectilesAbsorbedCount( vortexWeapon )
+ #endif
+
+ SetVortexAmmo( vortexWeapon, 0 )
+
+ vortexWeapon.Signal( "VortexFired" )
+
+ if ( forceReleased )
+ DestroyVortexSphereFromVortexWeapon( vortexWeapon )
+ else
+ DisableVortexSphereFromVortexWeapon( vortexWeapon )
+
+ return totalfired
+}
+
+int function Vortex_FireBackBullets( entity vortexWeapon, WeaponPrimaryAttackParams attackParams )
+{
+ int bulletCount = GetBulletsAbsorbedCount( vortexWeapon )
+ //Defensive Check - Couldn't repro error.
+ if ( "shotgunPelletsToIgnore" in vortexWeapon.s )
+ bulletCount = int( ceil( bulletCount - vortexWeapon.s.shotgunPelletsToIgnore ) )
+
+ if ( bulletCount )
+ {
+ bulletCount = minint( bulletCount, MAX_BULLET_PER_SHOT )
+
+ //if ( IsClient() && GetLocalViewPlayer() == vortexWeapon.GetWeaponOwner() )
+ // printt( "vortex firing", bulletCount, "bullets" )
+
+ float radius = LOUD_WEAPON_AI_SOUND_RADIUS_MP;
+ vortexWeapon.EmitWeaponNpcSound( radius, 0.2 )
+ int damageType = damageTypes.shotgun | DF_VORTEX_REFIRE
+ if ( bulletCount == 1 )
+ vortexWeapon.FireWeaponBullet( attackParams.pos, attackParams.dir, bulletCount, damageType )
+ else
+ ShotgunBlast( vortexWeapon, attackParams.pos, attackParams.dir, bulletCount, damageType )
+ }
+
+ return bulletCount
+}
+
+#if SERVER
+bool function Vortex_FireBackExplosiveRound( vortexWeapon, attackParams, impactData, sequenceID )
+{
+ expect entity( vortexWeapon )
+
+ // common projectile data
+ float projSpeed = 8000.0
+ int damageType = damageTypes.explosive | DF_VORTEX_REFIRE
+
+ vortexWeapon.EmitWeaponSound( "Weapon.Explosion_Med" )
+
+ vector attackPos
+ //Requires code feature to properly fire tracers from offset positions.
+ //if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) )
+ // attackPos = impactData.origin
+ //else
+ attackPos = Vortex_GenerateRandomRefireOrigin( vortexWeapon )
+
+ vector fireVec = Vortex_GenerateRandomRefireVector( vortexWeapon, VORTEX_EXP_ROUNDS_RETURN_SPREAD_XY, VORTEX_EXP_ROUNDS_RETURN_SPREAD_Z )
+
+ // fire off the bolt
+ entity bolt = vortexWeapon.FireWeaponBolt( attackPos, fireVec, projSpeed, damageType, damageType, PROJECTILE_NOT_PREDICTED, sequenceID )
+ if ( bolt )
+ {
+ bolt.kv.gravity = 0.3
+
+ Vortex_ProjectileCommonSetup( bolt, impactData )
+ }
+
+ return true
+}
+
+bool function Vortex_FireBackProjectileBullet( vortexWeapon, attackParams, impactData, sequenceID )
+{
+ expect entity( vortexWeapon )
+
+ // common projectile data
+ float projSpeed = 12000.0
+ int damageType = damageTypes.bullet | DF_VORTEX_REFIRE
+
+ vortexWeapon.EmitWeaponSound( "Weapon.Explosion_Med" )
+
+ vector attackPos
+ //Requires code feature to properly fire tracers from offset positions.
+ //if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) )
+ // attackPos = impactData.origin
+ //else
+ attackPos = Vortex_GenerateRandomRefireOrigin( vortexWeapon )
+
+ vector fireVec = Vortex_GenerateRandomRefireVector( vortexWeapon, 0.15, 0.1 )
+ //printt( Time(), fireVec ) // print for bug with random
+
+ // fire off the bolt
+ entity bolt = vortexWeapon.FireWeaponBolt( attackPos, fireVec, projSpeed, damageType, damageType, PROJECTILE_NOT_PREDICTED, sequenceID )
+ if ( bolt )
+ {
+ bolt.kv.gravity = 0.0
+
+ Vortex_ProjectileCommonSetup( bolt, impactData )
+ }
+
+ return true
+}
+
+vector function Vortex_GenerateRandomRefireOrigin( entity vortexWeapon, float distFromCenter = 3.0 )
+{
+ float distFromCenter_neg = distFromCenter * -1
+
+ vector attackPos = expect vector( vortexWeapon.s.vortexBulletEffectCP.GetOrigin() )
+
+ float x = RandomFloatRange( distFromCenter_neg, distFromCenter )
+ float y = RandomFloatRange( distFromCenter_neg, distFromCenter )
+ float z = RandomFloatRange( distFromCenter_neg, distFromCenter )
+
+ attackPos = attackPos + Vector( x, y, z )
+
+ return attackPos
+}
+
+vector function Vortex_GenerateRandomRefireVector( entity vortexWeapon, float vecSpread, float vecSpreadZ )
+{
+ float x = RandomFloatRange( vecSpread * -1, vecSpread )
+ float y = RandomFloatRange( vecSpread * -1, vecSpread )
+ float z = RandomFloatRange( vecSpreadZ * -1, vecSpreadZ )
+
+ vector fireVec = vortexWeapon.GetWeaponOwner().GetPlayerOrNPCViewVector() + Vector( x, y, z )
+ return fireVec
+}
+
+bool function Vortex_FireBackRocket( vortexWeapon, attackParams, impactData, sequenceID )
+{
+ expect entity( vortexWeapon )
+
+ // TODO prediction for clients
+ Assert( IsServer() )
+
+ entity rocket = vortexWeapon.FireWeaponMissile( attackParams.pos, attackParams.dir, 1800.0, damageTypes.largeCaliberExp | DF_VORTEX_REFIRE, damageTypes.largeCaliberExp | DF_VORTEX_REFIRE, false, PROJECTILE_NOT_PREDICTED )
+
+ if ( rocket )
+ {
+ rocket.kv.lifetime = RandomFloatRange( 2.6, 3.5 )
+
+ InitMissileForRandomDriftForVortexLow( rocket, expect vector( attackParams.pos ), expect vector( attackParams.dir ) )
+
+ Vortex_ProjectileCommonSetup( rocket, impactData )
+ }
+
+ return true
+}
+
+bool function Vortex_FireBackGrenade( entity vortexWeapon, attackParams, impactData, int attackSeedCount, float baseFuseTime )
+{
+ float x = RandomFloatRange( -0.2, 0.2 )
+ float y = RandomFloatRange( -0.2, 0.2 )
+ float z = RandomFloatRange( -0.2, 0.2 )
+
+ vector velocity = ( expect vector( attackParams.dir ) + Vector( x, y, z ) ) * 1500
+ vector angularVelocity = Vector( RandomFloatRange( -1200, 1200 ), 100, 0 )
+
+ bool hasIgnitionTime = vortexImpactWeaponInfo[ impactData.weaponName ].grenade_ignition_time > 0
+ float fuseTime = hasIgnitionTime ? 0.0 : baseFuseTime
+ const int HARDCODED_DAMAGE_TYPE = (damageTypes.explosive | DF_VORTEX_REFIRE)
+
+ entity grenade = vortexWeapon.FireWeaponGrenade( attackParams.pos, velocity, angularVelocity, fuseTime, HARDCODED_DAMAGE_TYPE, HARDCODED_DAMAGE_TYPE, PROJECTILE_NOT_PREDICTED, true, true )
+ if ( grenade )
+ {
+ Grenade_Init( grenade, vortexWeapon )
+ Vortex_ProjectileCommonSetup( grenade, impactData )
+ if ( hasIgnitionTime )
+ grenade.SetGrenadeIgnitionDuration( vortexImpactWeaponInfo[ impactData.weaponName ].grenade_ignition_time )
+ }
+
+ return (grenade ? true : false)
+}
+
+bool function DoVortexAttackForImpactData( entity vortexWeapon, attackParams, impactData, int attackSeedCount )
+{
+ bool didFire = false
+ switch ( impactData.refireBehavior )
+ {
+ case VORTEX_REFIRE_EXPLOSIVE_ROUND:
+ didFire = Vortex_FireBackExplosiveRound( vortexWeapon, attackParams, impactData, attackSeedCount )
+ break
+
+ case VORTEX_REFIRE_ROCKET:
+ didFire = Vortex_FireBackRocket( vortexWeapon, attackParams, impactData, attackSeedCount )
+ break
+
+ case VORTEX_REFIRE_GRENADE:
+ didFire = Vortex_FireBackGrenade( vortexWeapon, attackParams, impactData, attackSeedCount, 1.25 )
+ break
+
+ case VORTEX_REFIRE_GRENADE_LONG_FUSE:
+ didFire = Vortex_FireBackGrenade( vortexWeapon, attackParams, impactData, attackSeedCount, 10.0 )
+ break
+
+ case VORTEX_REFIRE_BULLET:
+ didFire = Vortex_FireBackProjectileBullet( vortexWeapon, attackParams, impactData, attackSeedCount )
+ break
+
+ case VORTEX_REFIRE_NONE:
+ break
+ }
+
+ return didFire
+}
+
+function Vortex_ProjectileCommonSetup( entity projectile, impactData )
+{
+ // custom tag it so it shows up correctly if it hits another vortex sphere
+ projectile.s.originalDamageSource <- impactData.damageSourceID
+
+ Vortex_SetImpactEffectTable_OnProjectile( projectile, impactData ) // set the correct impact effect table
+
+ projectile.SetVortexRefired( true ) // This tells code the projectile was refired from the vortex so that it uses "projectile_vortex_vscript"
+ projectile.SetModel( GetWeaponInfoFileKeyFieldAsset_Global( impactData.weaponName, "projectilemodel" ) )
+ projectile.SetWeaponClassName( impactData.weaponName ) // causes the projectile to use its normal trail FX
+
+ projectile.ProjectileSetDamageSourceID( impactData.damageSourceID ) // obit will show the owner weapon
+}
+
+// gives a refired projectile the correct impact effect table
+function Vortex_SetImpactEffectTable_OnProjectile( projectile, impactData )
+{
+ //Getting more info for bug 207595, don't check into Staging.
+ #if DEV
+ printt( "impactData.impact_effect_table ", impactData.impact_effect_table )
+ if ( impactData.impact_effect_table == "" )
+ PrintTable( impactData )
+ #endif
+
+ local fxTableHandle = GetImpactEffectTable( impactData.impact_effect_table )
+
+ projectile.SetImpactEffectTable( fxTableHandle )
+}
+#endif // SERVER
+
+// absorbed bullets are tracked with a special networked kv variable because clients need to know how many bullets to fire as well, when they are doing the client version of FireWeaponBullet
+int function GetBulletsAbsorbedCount( entity vortexWeapon )
+{
+ if ( !vortexWeapon )
+ return 0
+
+ entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity()
+ if ( !vortexSphere )
+ return 0
+
+ return vortexSphere.GetBulletAbsorbedCount()
+}
+
+int function GetProjectilesAbsorbedCount( entity vortexWeapon )
+{
+ if ( !vortexWeapon )
+ return 0
+
+ entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity()
+ if ( !vortexSphere )
+ return 0
+
+ return vortexSphere.GetProjectileAbsorbedCount()
+}
+
+#if SERVER
+function Vortex_GetProjectileImpacts( entity vortexWeapon )
+{
+ local impacts = []
+ foreach ( impactData in Vortex_GetAllImpactEvents( vortexWeapon ) )
+ {
+ if ( impactData.impactType == "projectile" )
+ impacts.append( impactData )
+ }
+
+ return impacts
+}
+
+function Vortex_GetHitscanBulletImpacts( entity vortexWeapon )
+{
+ local impacts = []
+ foreach ( impactData in Vortex_GetAllImpactEvents( vortexWeapon ) )
+ {
+ if ( impactData.impactType == "hitscan" )
+ impacts.append( impactData )
+ }
+
+ return impacts
+}
+
+int function GetHitscanBulletImpactCount( entity vortexWeapon )
+{
+ int count = 0
+ foreach ( impactData in Vortex_GetAllImpactEvents( vortexWeapon ) )
+ {
+ if ( impactData.impactType == "hitscan" )
+ count++
+ }
+
+ return count
+}
+#endif // SERVER
+
+// // lets the damage callback communicate to the attacker that he hit a vortex shield
+function Vortex_NotifyAttackerDidDamage( entity attacker, entity vortexOwner, hitPos )
+{
+ if ( !IsValid( attacker ) || !attacker.IsPlayer() )
+ return
+
+ if ( !IsValid( vortexOwner ) )
+ return
+
+ Assert( hitPos )
+
+ attacker.NotifyDidDamage( vortexOwner, 0, hitPos, 0, 0, DAMAGEFLAG_VICTIM_HAS_VORTEX, 0, null, 0 )
+}
+
+function SetVortexAmmo( entity vortexWeapon, count )
+{
+ entity owner = vortexWeapon.GetWeaponOwner()
+ if ( !IsValid_ThisFrame( owner ) )
+ return
+ #if CLIENT
+ if ( !IsLocalViewPlayer( owner ) )
+ return
+ #endif
+
+ vortexWeapon.SetWeaponPrimaryAmmoCount( count )
+}
+
+
+// sets the RGB color value for the vortex sphere FX based on current charge fraction
+function VortexSphereColorUpdate( entity weapon, sphereClientFXHandle = null )
+{
+ weapon.EndSignal( "VortexStopping" )
+
+ #if CLIENT
+ Assert( sphereClientFXHandle != null )
+ #endif
+ bool isIonVortex = weapon.GetWeaponClassName() == "mp_titanweapon_vortex_shield_ion"
+ entity weaponOwner = weapon.GetWeaponOwner()
+ float energyTotal = float ( weaponOwner.GetSharedEnergyTotal() )
+ while( IsValid( weapon ) && IsValid( weaponOwner ) )
+ {
+ vector colorVec
+ if ( isIonVortex )
+ {
+ float energyFrac = 1.0 - float( weaponOwner.GetSharedEnergyCount() ) / energyTotal
+ if ( weapon.HasMod( "pas_ion_vortex" ) )
+ colorVec = GetVortexSphereCurrentColor( energyFrac, VORTEX_SPHERE_COLOR_PAS_ION_VORTEX )
+ else
+ colorVec = GetVortexSphereCurrentColor( energyFrac )
+ }
+ else
+ {
+ colorVec = GetVortexSphereCurrentColor( weapon.GetWeaponChargeFraction() )
+ }
+
+
+ // update the world entity that is linked to the world FX playing on the server
+ #if SERVER
+ weapon.s.vortexSphereColorCP.SetOrigin( colorVec )
+ #else
+ // handles the server killing the vortex sphere without the client knowing right away,
+ // for example if an explosive goes off and we short circuit the charge timer
+ if ( !EffectDoesExist( sphereClientFXHandle ) )
+ break
+
+ EffectSetControlPointVector( sphereClientFXHandle, 1, colorVec )
+ #endif
+
+ WaitFrame()
+ }
+}
+
+vector function GetVortexSphereCurrentColor( float chargeFrac, vector fullHealthColor = VORTEX_SPHERE_COLOR_CHARGE_FULL )
+{
+ return GetTriLerpColor( chargeFrac, fullHealthColor, VORTEX_SPHERE_COLOR_CHARGE_MED, VORTEX_SPHERE_COLOR_CHARGE_EMPTY )
+}
+
+vector function GetShieldTriLerpColor( float frac )
+{
+ return GetTriLerpColor( frac, VORTEX_SPHERE_COLOR_CHARGE_FULL, VORTEX_SPHERE_COLOR_CHARGE_MED, VORTEX_SPHERE_COLOR_CHARGE_EMPTY )
+}
+
+vector function GetTriLerpColor( float fraction, vector color1, vector color2, vector color3 )
+{
+ float crossover1 = VORTEX_SPHERE_COLOR_CROSSOVERFRAC_FULL2MED // from zero to this fraction, fade between color1 and color2
+ float crossover2 = VORTEX_SPHERE_COLOR_CROSSOVERFRAC_MED2EMPTY // from crossover1 to this fraction, fade between color2 and color3
+
+ float r, g, b
+
+ // 0 = full charge, 1 = no charge remaining
+ if ( fraction < crossover1 )
+ {
+ r = Graph( fraction, 0, crossover1, color1.x, color2.x )
+ g = Graph( fraction, 0, crossover1, color1.y, color2.y )
+ b = Graph( fraction, 0, crossover1, color1.z, color2.z )
+ return <r, g, b>
+ }
+ else if ( fraction < crossover2 )
+ {
+ r = Graph( fraction, crossover1, crossover2, color2.x, color3.x )
+ g = Graph( fraction, crossover1, crossover2, color2.y, color3.y )
+ b = Graph( fraction, crossover1, crossover2, color2.z, color3.z )
+ return <r, g, b>
+ }
+ else
+ {
+ // for the last bit of overload timer, keep it max danger color
+ r = color3.x
+ g = color3.y
+ b = color3.z
+ return <r, g, b>
+ }
+
+ unreachable
+}
+
+// generic impact validation
+#if SERVER
+bool function ValidateVortexImpact( entity vortexSphere, entity projectile = null )
+{
+ Assert( IsServer() )
+
+ if ( !IsValid( vortexSphere ) )
+ return false
+
+ if ( !vortexSphere.GetOwnerWeapon() )
+ return false
+
+ entity vortexWeapon = vortexSphere.GetOwnerWeapon()
+ if ( !IsValid( vortexWeapon ) )
+ return false
+
+ if ( projectile )
+ {
+ if ( !IsValid_ThisFrame( projectile ) )
+ return false
+
+ if ( projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_ignores_vortex" ) == 1 )
+ return false
+
+ if ( projectile.ProjectileGetWeaponClassName() == "" )
+ return false
+
+ // TEMP HACK
+ if ( projectile.ProjectileGetWeaponClassName() == "mp_weapon_tether" )
+ return false
+ }
+
+ return true
+}
+#endif
+
+/********************************/
+/* Setting override functions */
+/********************************/
+
+function Vortex_SetTagName( entity weapon, string tagName )
+{
+ Vortex_SetWeaponSettingOverride( weapon, "vortexTagName", tagName )
+}
+
+function Vortex_SetBulletCollectionOffset( entity weapon, vector offset )
+{
+ Vortex_SetWeaponSettingOverride( weapon, "bulletCollectionOffset", offset )
+}
+
+function Vortex_SetWeaponSettingOverride( entity weapon, string setting, value )
+{
+ if ( !( setting in weapon.s ) )
+ weapon.s[ setting ] <- null
+ weapon.s[ setting ] = value
+}
+
+string function GetVortexTagName( entity weapon )
+{
+ if ( "vortexTagName" in weapon.s )
+ return expect string( weapon.s.vortexTagName )
+
+ return "vortex_center"
+}
+
+vector function GetBulletCollectionOffset( entity weapon )
+{
+ if ( "bulletCollectionOffset" in weapon.s )
+ return expect vector( weapon.s.bulletCollectionOffset )
+
+ entity owner = weapon.GetWeaponOwner()
+ if ( owner.IsTitan() )
+ return Vector( 300.0, -90.0, -70.0 )
+ else
+ return Vector( 80.0, 17.0, -11.0 )
+
+ unreachable
+}
+
+
+#if SERVER
+function VortexSphereDrainHealthForDamage( entity vortexSphere, damage )
+{
+ // don't drain the health of vortex_spheres that are set to be invulnerable. This is the case for the Particle Wall
+ if ( vortexSphere.IsInvulnerable() )
+ return
+
+ local result = {}
+ result.damage <- damage
+ vortexSphere.Signal( "Script_OnDamaged", result )
+
+ int currentHealth = vortexSphere.GetHealth()
+ Assert( damage >= 0 )
+ // JFS to fix phone home bug; we never hit the assert above locally...
+ damage = max( damage, 0 )
+ vortexSphere.SetHealth( currentHealth - damage )
+
+ entity vortexWeapon = vortexSphere.GetOwnerWeapon()
+ if ( IsValid( vortexWeapon ) && vortexWeapon.HasMod( "fd_gun_shield_redirect" ) )
+ {
+ entity owner = vortexWeapon.GetWeaponOwner()
+ if ( IsValid( owner ) && owner.IsTitan() )
+ {
+ entity soul = owner.GetTitanSoul()
+ if ( IsValid( soul ) )
+ {
+ int shieldRestoreAmount = int( damage ) //Might need tuning
+ soul.SetShieldHealth( min( soul.GetShieldHealth() + shieldRestoreAmount, soul.GetShieldHealthMax() ) )
+ }
+ }
+ }
+
+ UpdateShieldWallColorForFrac( vortexSphere.e.shieldWallFX, GetHealthFrac( vortexSphere ) )
+}
+#endif
+
+
+bool function CodeCallback_OnVortexHitBullet( entity weapon, entity vortexSphere, var damageInfo )
+{
+ bool isAmpedWall = vortexSphere.GetTargetName() == PROTO_AMPED_WALL
+ bool takesDamage = !isAmpedWall
+ bool adjustImpactAngles = !(vortexSphere.GetTargetName() == GUN_SHIELD_WALL)
+
+ #if SERVER
+ if ( vortexSphere.e.BulletHitRules != null )
+ {
+ vortexSphere.e.BulletHitRules( vortexSphere, damageInfo )
+ takesDamage = takesDamage && (DamageInfo_GetDamage( damageInfo ) > 0)
+ }
+ #endif
+
+ vector damageAngles = vortexSphere.GetAngles()
+
+ if ( adjustImpactAngles )
+ damageAngles = AnglesCompose( damageAngles, Vector( 90, 0, 0 ) )
+
+ int teamNum = vortexSphere.GetTeam()
+
+ #if CLIENT
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+ if ( !isAmpedWall )
+ {
+ // TODO: slightly change angles to match radius rotation of vortex cylinder
+ int effectHandle = StartParticleEffectInWorldWithHandle( GetParticleSystemIndex( SHIELD_WALL_BULLET_FX ), damageOrigin, damageAngles )
+ //local color = GetShieldTriLerpColor( 1 - GetHealthFrac( vortexSphere ) )
+ vector color = GetShieldTriLerpColor( 0.0 )
+ EffectSetControlPointVector( effectHandle, 1, color )
+ }
+
+ if ( takesDamage )
+ {
+ float damage = ceil( DamageInfo_GetDamage( damageInfo ) )
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ DamageFlyout( damage, damageOrigin, vortexSphere, false, false )
+ }
+
+ if ( DamageInfo_GetAttacker( damageInfo ) && DamageInfo_GetAttacker( damageInfo ).IsTitan() )
+ EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Heavy.BulletImpact_1P_vs_3P" )
+ else
+ EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Light.BulletImpact_1P_vs_3P" )
+ #else
+ if ( !isAmpedWall )
+ {
+ int fxId = GetParticleSystemIndex( SHIELD_WALL_BULLET_FX )
+ PlayEffectOnVortexSphere( fxId, DamageInfo_GetDamagePosition( damageInfo ), damageAngles, vortexSphere )
+ }
+
+ entity weapon = DamageInfo_GetWeapon( damageInfo )
+ float damage = ceil( DamageInfo_GetDamage( damageInfo ) )
+
+ Assert( damage >= 0, "Bug 159851 - Damage should be greater than or equal to 0.")
+ damage = max( 0.0, damage )
+
+ if ( IsValid( weapon ) )
+ damage = HandleWeakToPilotWeapons( vortexSphere, weapon.GetWeaponClassName(), damage )
+
+ if ( takesDamage )
+ {
+ //JFS - Arc Round bug fix for Monarch. Projectiles vortex callback doesn't even have damageInfo, so the shield modifier here doesn't exist in VortexSphereDrainHealthForDamage like it should.
+ ShieldDamageModifier damageModifier = GetShieldDamageModifier( damageInfo )
+ damage *= damageModifier.damageScale
+ VortexSphereDrainHealthForDamage( vortexSphere, damage )
+ }
+
+ if ( DamageInfo_GetAttacker( damageInfo ) && DamageInfo_GetAttacker( damageInfo ).IsTitan() )
+ EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Heavy.BulletImpact_3P_vs_3P" )
+ else
+ EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Light.BulletImpact_3P_vs_3P" )
+ #endif
+
+ if ( isAmpedWall )
+ {
+ #if SERVER
+ DamageInfo_ScaleDamage( damageInfo, AMPED_DAMAGE_SCALAR )
+ #endif
+ return false
+ }
+
+ return true
+}
+
+bool function OnVortexHitBullet_BubbleShieldNPC( entity vortexSphere, var damageInfo )
+{
+ vector vortexOrigin = vortexSphere.GetOrigin()
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+
+ float distSq = DistanceSqr( vortexOrigin, damageOrigin )
+ if ( distSq < MINION_BUBBLE_SHIELD_RADIUS_SQR )
+ return false//the damage is coming from INSIDE the sphere
+
+ vector damageVec = damageOrigin - vortexOrigin
+ vector damageAngles = VectorToAngles( damageVec )
+ damageAngles = AnglesCompose( damageAngles, Vector( 90, 0, 0 ) )
+
+ int teamNum = vortexSphere.GetTeam()
+
+ #if CLIENT
+ int effectHandle = StartParticleEffectInWorldWithHandle( GetParticleSystemIndex( SHIELD_WALL_BULLET_FX ), damageOrigin, damageAngles )
+
+ vector color = GetShieldTriLerpColor( 0.9 )
+ EffectSetControlPointVector( effectHandle, 1, color )
+
+ if ( DamageInfo_GetAttacker( damageInfo ) && DamageInfo_GetAttacker( damageInfo ).IsTitan() )
+ EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Heavy.BulletImpact_1P_vs_3P" )
+ else
+ EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Light.BulletImpact_1P_vs_3P" )
+ #else
+ int fxId = GetParticleSystemIndex( SHIELD_WALL_BULLET_FX )
+ PlayEffectOnVortexSphere( fxId, DamageInfo_GetDamagePosition( damageInfo ), damageAngles, vortexSphere )
+ //VortexSphereDrainHealthForDamage( vortexSphere, DamageInfo_GetWeapon( damageInfo ), null )
+
+ if ( DamageInfo_GetAttacker( damageInfo ) && DamageInfo_GetAttacker( damageInfo ).IsTitan() )
+ EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Heavy.BulletImpact_3P_vs_3P" )
+ else
+ EmitSoundAtPosition( teamNum, DamageInfo_GetDamagePosition( damageInfo ), "TitanShieldWall.Light.BulletImpact_3P_vs_3P" )
+ #endif
+ return true
+}
+
+bool function CodeCallback_OnVortexHitProjectile( entity weapon, entity vortexSphere, entity attacker, entity projectile, vector contactPos )
+{
+ // code shouldn't call this on an invalid vortexsphere!
+ if ( !IsValid( vortexSphere ) )
+ return false
+
+ var ignoreVortex = projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_ignores_vortex" )
+ if ( ignoreVortex != null )
+ {
+ #if SERVER
+ if ( projectile.proj.hasBouncedOffVortex )
+ return false
+
+ vector velocity = projectile.GetVelocity()
+ vector multiplier
+
+ switch ( ignoreVortex )
+ {
+ case "drop":
+ multiplier = < -0.25, -0.25, 0.0 >
+ break
+
+ case "fall_vortex":
+ case "fall":
+ multiplier = < -0.25, -0.25, -0.25 >
+ break
+
+ case "mirror":
+ // bounce back, assume along xy axis
+ multiplier = < -1.0, -1.0, 1.0 >
+ break
+
+ default:
+ CodeWarning( "Unknown projectile_ignores_vortex " + ignoreVortex )
+ break
+ }
+
+ velocity = < velocity.x * multiplier.x, velocity.y * multiplier.y, velocity.z * multiplier.z >
+ projectile.proj.hasBouncedOffVortex = true
+ projectile.SetVelocity( velocity )
+ #endif
+ return false
+ }
+
+ bool adjustImpactAngles = !(vortexSphere.GetTargetName() == GUN_SHIELD_WALL)
+
+ vector damageAngles = vortexSphere.GetAngles()
+
+ if ( adjustImpactAngles )
+ damageAngles = AnglesCompose( damageAngles, Vector( 90, 0, 0 ) )
+
+ asset projectileSettingFX = projectile.GetProjectileWeaponSettingAsset( eWeaponVar.vortex_impact_effect )
+ asset impactFX = (projectileSettingFX != $"") ? projectileSettingFX : SHIELD_WALL_EXPMED_FX
+
+ bool isAmpedWall = vortexSphere.GetTargetName() == PROTO_AMPED_WALL
+ bool takesDamage = !isAmpedWall
+
+ #if SERVER
+ if ( vortexSphere.e.ProjectileHitRules != null )
+ takesDamage = vortexSphere.e.ProjectileHitRules( vortexSphere, attacker, takesDamage )
+ #endif
+ // hack to let client know about amped wall, and to amp the shot
+ if ( isAmpedWall )
+ impactFX = AMPED_WALL_IMPACT_FX
+
+ int teamNum = vortexSphere.GetTeam()
+
+ #if CLIENT
+ if ( !isAmpedWall )
+ {
+ int effectHandle = StartParticleEffectInWorldWithHandle( GetParticleSystemIndex( impactFX ), contactPos, damageAngles )
+ //local color = GetShieldTriLerpColor( 1 - GetHealthFrac( vortexSphere ) )
+ vector color = GetShieldTriLerpColor( 0.0 )
+ EffectSetControlPointVector( effectHandle, 1, color )
+ }
+
+ var impact_sound_1p = projectile.ProjectileGetWeaponInfoFileKeyField( "vortex_impact_sound_1p" )
+ if ( impact_sound_1p == null )
+ impact_sound_1p = "TitanShieldWall.Explosive.BulletImpact_1P_vs_3P"
+
+ EmitSoundAtPosition( teamNum, contactPos, impact_sound_1p )
+ #else
+ if ( !isAmpedWall )
+ {
+ int fxId = GetParticleSystemIndex( impactFX )
+ PlayEffectOnVortexSphere( fxId, contactPos, damageAngles, vortexSphere )
+ }
+
+ float damage = float( projectile.GetProjectileWeaponSettingInt( eWeaponVar.damage_near_value ) )
+ // once damageInfo is passed correctly we'll use that instead of looking up the values from the weapon .txt file.
+ // local damage = ceil( DamageInfo_GetDamage( damageInfo ) )
+
+ damage = HandleWeakToPilotWeapons( vortexSphere, projectile.ProjectileGetWeaponClassName(), damage )
+ damage = damage + CalculateTitanSniperExtraDamage( projectile, vortexSphere )
+
+ if ( takesDamage )
+ {
+ VortexSphereDrainHealthForDamage( vortexSphere, damage )
+ if ( IsValid( attacker ) && attacker.IsPlayer() )
+ attacker.NotifyDidDamage( vortexSphere, 0, contactPos, 0, damage, DF_NO_HITBEEP, 0, null, 0 )
+ }
+
+ var impact_sound_3p = projectile.ProjectileGetWeaponInfoFileKeyField( "vortex_impact_sound_3p" )
+
+ if ( impact_sound_3p == null )
+ impact_sound_3p = "TitanShieldWall.Explosive.BulletImpact_3P_vs_3P"
+
+ EmitSoundAtPosition( teamNum, contactPos, impact_sound_3p )
+
+ int damageSourceID = projectile.ProjectileGetDamageSourceID()
+ switch ( damageSourceID )
+ {
+ case eDamageSourceId.mp_titanweapon_dumbfire_rockets:
+ vector normal = projectile.GetVelocity() * -1
+ normal = Normalize( normal )
+ ClusterRocket_Detonate( projectile, normal )
+ CreateNoSpawnArea( TEAM_INVALID, TEAM_INVALID, contactPos, ( CLUSTER_ROCKET_BURST_COUNT / 5.0 ) * 0.5 + 1.0, CLUSTER_ROCKET_BURST_RANGE + 100 )
+ break
+
+ case eDamageSourceId.mp_weapon_grenade_electric_smoke:
+ ElectricGrenadeSmokescreen( projectile, FX_ELECTRIC_SMOKESCREEN_PILOT_AIR )
+ break
+
+ case eDamageSourceId.mp_weapon_grenade_emp:
+
+ if ( StatusEffect_Get( vortexSphere, eStatusEffect.destroyed_by_emp ) )
+ VortexSphereDrainHealthForDamage( vortexSphere, vortexSphere.GetHealth() )
+ break
+
+ case eDamageSourceId.mp_titanability_sonar_pulse:
+ if ( IsValid( attacker ) && attacker.IsTitan() )
+ {
+ int team = attacker.GetTeam()
+ PulseLocation( attacker, team, contactPos, false, false )
+ array<string> mods = projectile.ProjectileGetMods()
+ if ( mods.contains( "pas_tone_sonar" ) )
+ thread DelayedPulseLocation( attacker, team, contactPos, false, false )
+ }
+ break
+
+ }
+ #endif
+
+ // hack to let client know about amped wall, and to amp the shot
+ if ( isAmpedWall )
+ {
+ #if SERVER
+ projectile.proj.damageScale = AMPED_DAMAGE_SCALAR
+ #endif
+
+ return false
+ }
+
+ return true
+}
+
+bool function OnVortexHitProjectile_BubbleShieldNPC( entity vortexSphere, entity attacker, entity projectile, vector contactPos )
+{
+ vector vortexOrigin = vortexSphere.GetOrigin()
+
+ float dist = DistanceSqr( vortexOrigin, contactPos )
+ if ( dist < MINION_BUBBLE_SHIELD_RADIUS_SQR )
+ return false // the damage is coming from INSIDE THE SPHERE
+
+ vector damageVec = Normalize( contactPos - vortexOrigin )
+ vector damageAngles = VectorToAngles( damageVec )
+ damageAngles = AnglesCompose( damageAngles, Vector( 90, 0, 0 ) )
+
+ asset projectileSettingFX = projectile.GetProjectileWeaponSettingAsset( eWeaponVar.vortex_impact_effect )
+ asset impactFX = (projectileSettingFX != $"") ? projectileSettingFX : SHIELD_WALL_EXPMED_FX
+
+ int teamNum = vortexSphere.GetTeam()
+
+ #if CLIENT
+ int effectHandle = StartParticleEffectInWorldWithHandle( GetParticleSystemIndex( impactFX ), contactPos, damageAngles )
+
+ vector color = GetShieldTriLerpColor( 0.9 )
+ EffectSetControlPointVector( effectHandle, 1, color )
+
+ EmitSoundAtPosition( teamNum, contactPos, "TitanShieldWall.Explosive.BulletImpact_1P_vs_3P" )
+ #else
+ int fxId = GetParticleSystemIndex( impactFX )
+ PlayEffectOnVortexSphere( fxId, contactPos, damageAngles, vortexSphere )
+// VortexSphereDrainHealthForDamage( vortexSphere, null, projectile )
+
+ EmitSoundAtPosition( teamNum, contactPos, "TitanShieldWall.Explosive.BulletImpact_3P_vs_3P" )
+
+ if ( projectile.ProjectileGetDamageSourceID() == eDamageSourceId.mp_titanweapon_dumbfire_rockets )
+ {
+ vector normal = projectile.GetVelocity() * -1
+ normal = Normalize( normal )
+ ClusterRocket_Detonate( projectile, normal )
+ CreateNoSpawnArea( TEAM_INVALID, TEAM_INVALID, contactPos, ( CLUSTER_ROCKET_BURST_COUNT / 5.0 ) * 0.5 + 1.0, CLUSTER_ROCKET_BURST_RANGE + 100 )
+ }
+ #endif
+ return true
+}
+
+#if SERVER
+float function HandleWeakToPilotWeapons( entity vortexSphere, string weaponName, float damage )
+{
+ if ( vortexSphere.e.proto_weakToPilotWeapons ) //needs code for real, but this is fine for prototyping
+ {
+ // is weapon a pilot weapon?
+ local refType = GetWeaponInfoFileKeyField_Global( weaponName, "weaponClass" )
+ if ( refType == "human" )
+ {
+ damage *= VORTEX_PILOT_WEAPON_WEAKNESS_DAMAGESCALE
+ }
+ }
+
+ return damage
+}
+#endif
+
+// ???: reflectOrigin not used
+int function VortexReflectAttack( entity vortexWeapon, attackParams, vector reflectOrigin )
+{
+ entity vortexSphere = vortexWeapon.GetWeaponUtilityEntity()
+ if ( !vortexSphere )
+ return 0
+
+ #if SERVER
+ Assert( vortexSphere )
+ #endif
+
+ int totalfired = 0
+ int totalAttempts = 0
+
+ bool forceReleased = false
+ // in this case, it's also considered "force released" if the charge time runs out
+ if ( vortexWeapon.IsForceRelease() || vortexWeapon.GetWeaponChargeFraction() == 1 )
+ forceReleased = true
+
+ //Requires code feature to properly fire tracers from offset positions.
+ //if ( vortexWeapon.GetWeaponSettingBool( eWeaponVar.is_burn_mod ) )
+ // attackParams.pos = reflectOrigin
+
+ // PREDICTED REFIRES
+ // bullet impact events don't individually fire back per event because we aggregate and then shotgun blast them
+
+ //Remove the below script after FireWeaponBulletBroadcast
+ //local bulletsFired = Vortex_FireBackBullets( vortexWeapon, attackParams )
+ //totalfired += bulletsFired
+ int bulletCount = GetBulletsAbsorbedCount( vortexWeapon )
+ if ( bulletCount > 0 )
+ {
+ if ( "ampedBulletCount" in vortexWeapon.s )
+ vortexWeapon.s.ampedBulletCount++
+ else
+ vortexWeapon.s.ampedBulletCount <- 1
+ vortexWeapon.Signal( "FireAmpedVortexBullet" )
+ totalfired += 1
+ }
+
+ // UNPREDICTED REFIRES
+ #if SERVER
+ //printt( "server: force released?", forceReleased )
+
+ local unpredictedRefires = Vortex_GetProjectileImpacts( vortexWeapon )
+
+ // HACK we don't actually want to refire them with a spiral but
+ // this is to temporarily ensure compatibility with the Titan rocket launcher
+ if ( !( "spiralMissileIdx" in vortexWeapon.s ) )
+ vortexWeapon.s.spiralMissileIdx <- null
+ vortexWeapon.s.spiralMissileIdx = 0
+ foreach ( impactData in unpredictedRefires )
+ {
+ bool didFire = DoVortexAttackForImpactData( vortexWeapon, attackParams, impactData, totalAttempts )
+ if ( didFire )
+ totalfired++
+ totalAttempts++
+ }
+ #endif
+
+ SetVortexAmmo( vortexWeapon, 0 )
+ vortexWeapon.Signal( "VortexFired" )
+
+#if SERVER
+ vortexSphere.ClearAllBulletsFromSphere()
+#endif
+
+ /*
+ if ( forceReleased )
+ DestroyVortexSphereFromVortexWeapon( vortexWeapon )
+ else
+ DisableVortexSphereFromVortexWeapon( vortexWeapon )
+ */
+
+ return totalfired
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_dialogue.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_dialogue.nut
new file mode 100644
index 000000000..04fd24d3e
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_dialogue.nut
@@ -0,0 +1,44 @@
+untyped
+
+globalize_all_functions
+
+function Weapon_Dialogue_Init()
+{
+ RegisterConversation( "CoopTD_TurretAvailable", VO_PRIORITY_PLAYERSTATE ) // player turret becomes available
+ RegisterConversation( "CoopTD_TurretAvailableNag", VO_PRIORITY_PLAYERSTATE ) // player turret available nag
+ RegisterConversation( "CoopTD_TurretDestroyed", VO_PRIORITY_PLAYERSTATE ) // player turret destroyed
+ RegisterConversation( "CoopTD_TurretDeadAndReady", VO_PRIORITY_PLAYERSTATE ) // player turret destroyed, and one ready
+ RegisterConversation( "CoopTD_TurretKillstreak", VO_PRIORITY_PLAYERSTATE ) // player turret has lots of kills
+ RegisterConversation( "CoopTD_TurretKilledTitan", VO_PRIORITY_PLAYERSTATE ) // player turret killed a titan
+ RegisterConversation( "CoopTD_TurretKilledTitan_Multi", VO_PRIORITY_PLAYERSTATE ) // player turret killed multiple titans
+
+ #if CLIENT
+
+ AddVDULineForSarah( "CoopTD_TurretAvailable", "diag_gm_coop_playerTurretEarned_mcor_Sarah" )
+ AddVDULineForSpyglass( "CoopTD_TurretAvailable", "diag_gm_coop_playerTurretEarned_mcor_Sarah" )
+
+ // player turret available nag
+ AddVDULineForSarah( "CoopTD_TurretAvailableNag", "diag_gm_coop_playerTurretNag_mcor_Sarah" )
+ AddVDULineForSpyglass( "CoopTD_TurretAvailableNag", "diag_gm_coop_playerTurretNag_mcor_Sarah" )
+
+ // player turret destroyed
+ AddVDULineForSarah( "CoopTD_TurretDestroyed", "diag_gm_coop_playerTurretDestro_mcor_Sarah" )
+ AddVDULineForSpyglass( "CoopTD_TurretDestroyed", "diag_gm_coop_playerTurretDestro_mcor_Sarah" )
+
+ // player turret destroyed and another one ready
+ AddVDULineForSarah( "CoopTD_TurretDeadAndReady", "diag_gm_coop_playerTurretDeadAndReady_mcor_Sarah" )
+ AddVDULineForSpyglass( "CoopTD_TurretDeadAndReady", "diag_gm_coop_playerTurretDeadAndReady_mcor_Sarah" )
+
+ // player turret has lots of kills
+ AddVDULineForSarah( "CoopTD_TurretKillstreak", "diag_gm_coop_playerTurretHighKills_mcor_Sarah" )
+ AddVDULineForSpyglass( "CoopTD_TurretKillstreak", "diag_gm_coop_playerTurretHighKills_mcor_Sarah" )
+
+ // player turret killed a titan
+ AddVDULineForSarah( "CoopTD_TurretKilledTitan", "diag_gm_coop_playerTurretKilledTitan_mcor_Sarah" )
+ AddVDULineForSpyglass( "CoopTD_TurretKilledTitan", "diag_gm_coop_playerTurretKilledTitan_mcor_Sarah" )
+
+ // player turret killed multiple titans
+ AddVDULineForSarah( "CoopTD_TurretKilledTitan_Multi", "diag_gm_coop_playerTurretKilledTitanAgain_mcor_Sarah" )
+ AddVDULineForSpyglass( "CoopTD_TurretKilledTitan_Multi", "diag_gm_coop_playerTurretKilledTitanAgain_mcor_Sarah" )
+ #endif
+}
diff --git a/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_utility.nut b/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_utility.nut
new file mode 100644
index 000000000..b3e5f5a39
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/weapons/_weapon_utility.nut
@@ -0,0 +1,3966 @@
+untyped
+
+//TODO: Should split this up into server, client and shared versions and just globalize_all_functions
+global function WeaponUtility_Init
+
+global function ApplyVectorSpread
+global function DebugDrawMissilePath
+global function DegreesToTarget
+global function DetonateAllPlantedExplosives
+global function EntityCanHaveStickyEnts
+global function EntityShouldStick
+global function FireExpandContractMissiles
+global function FireExpandContractMissiles_S2S
+global function GetVectorFromPositionToCrosshair
+global function GetVelocityForDestOverTime
+global function GetPlayerVelocityForDestOverTime
+global function GetWeaponBurnMods
+global function InitMissileForRandomDriftForVortexLow
+global function IsPilotShotgunWeapon
+global function PlantStickyEntity
+global function PlantStickyEntityThatBouncesOffWalls
+global function PlantStickyEntityOnWorldThatBouncesOffWalls
+global function PlantStickyGrenade
+global function PlantSuperStickyGrenade
+global function Player_DetonateSatchels
+global function PROTO_CanPlayerDeployWeapon
+global function ProximityCharge_PostFired_Init
+global function RegenerateOffhandAmmoOverTime
+global function ShotgunBlast
+global function FireGenericBoltWithDrop
+global function OnWeaponPrimaryAttack_GenericBoltWithDrop_Player
+global function OnWeaponPrimaryAttack_GenericMissile_Player
+global function OnWeaponActivate_updateViewmodelAmmo
+global function TEMP_GetDamageFlagsFromProjectile
+global function WeaponCanCrit
+global function GiveEMPStunStatusEffects
+global function GetPrimaryWeapons
+global function GetSidearmWeapons
+global function GetATWeapons
+global function GetPlayerFromTitanWeapon
+global function ChargeBall_Precache
+global function ChargeBall_FireProjectile
+global function ChargeBall_ChargeBegin
+global function ChargeBall_ChargeEnd
+global function ChargeBall_StopChargeEffects
+global function ChargeBall_GetChargeTime
+
+global function PlayerUsedOffhand
+#if SERVER
+global function SetPlayerCooldowns
+global function ResetPlayerCooldowns
+global function StoreOffhandData
+#endif
+
+global function GetRadiusDamageDataFromProjectile
+
+#if DEV
+global function DevPrintAllStatusEffectsOnEnt
+#endif // #if DEV
+
+#if SERVER
+ global function ClusterRocket_Detonate
+ global function PassThroughDamage
+ global function PROTO_CleanupTrackedProjectiles
+ global function PROTO_InitTrackedProjectile
+ global function PROTO_PlayTrapLightEffect
+ global function Satchel_PostFired_Init
+ global function StartClusterExplosions
+ global function TrapDestroyOnRoundEnd
+ global function TrapExplodeOnDamage
+ global function PROTO_DelayCooldown
+ global function PROTO_FlakCannonMissiles
+ global function GetBulletPassThroughTargets
+ global function IsValidPassThroughTarget
+ global function GivePlayerAmpedWeapon
+ global function GivePlayerAmpedWeaponAndSetAsActive
+ global function ReplacePlayerOffhand
+ global function ReplacePlayerOrdnance
+ global function DisableWeapons
+ global function EnableWeapons
+ global function WeaponAttackWave
+ global function AddActiveThermiteBurn
+ global function GetActiveThermiteBurnsWithinRadius
+ global function OnWeaponPrimaryAttack_GenericBoltWithDrop_NPC
+ global function OnWeaponPrimaryAttack_GenericMissile_NPC
+ global function EMP_DamagedPlayerOrNPC
+ global function EMP_FX
+ global function GetWeaponDPS
+ global function GetTTK
+ global function GetWeaponModsFromDamageInfo
+ global function Thermite_DamagePlayerOrNPCSounds
+ global function AddThreatScopeColorStatusEffect
+ global function RemoveThreatScopeColorStatusEffect
+#endif //SERVER
+#if CLIENT
+ global function GlobalClientEventHandler
+ global function UpdateViewmodelAmmo
+ global function ServerCallback_AirburstIconUpdate
+ global function ServerCallback_GuidedMissileDestroyed
+ global function IsOwnerViewPlayerFullyADSed
+#endif //CLIENT
+
+global const PROJECTILE_PREDICTED = true
+global const PROJECTILE_NOT_PREDICTED = false
+
+global const PROJECTILE_LAG_COMPENSATED = true
+global const PROJECTILE_NOT_LAG_COMPENSATED = false
+
+const float EMP_SEVERITY_SLOWTURN = 0.35
+const float EMP_SEVERITY_SLOWMOVE = 0.50
+const float LASER_STUN_SEVERITY_SLOWTURN = 0.20
+const float LASER_STUN_SEVERITY_SLOWMOVE = 0.30
+
+const asset FX_EMP_BODY_HUMAN = $"P_emp_body_human"
+const asset FX_EMP_BODY_TITAN = $"P_emp_body_titan"
+const asset FX_VANGUARD_ENERGY_BODY_HUMAN = $"P_monarchBeam_body_human"
+const asset FX_VANGUARD_ENERGY_BODY_TITAN = $"P_monarchBeam_body_titan"
+const SOUND_EMP_REBOOT_SPARKS = "marvin_weld"
+const FX_EMP_REBOOT_SPARKS = $"weld_spark_01_sparksfly"
+const EMP_GRENADE_BEAM_EFFECT = $"wpn_arc_cannon_beam"
+const DRONE_REBOOT_TIME = 5.0
+const GUNSHIP_REBOOT_TIME = 5.0
+
+global struct RadiusDamageData
+{
+ int explosionDamage
+ int explosionDamageHeavyArmor
+ float explosionRadius
+ float explosionInnerRadius
+}
+
+#if SERVER
+
+global struct PopcornInfo
+{
+ string weaponName
+ array weaponMods // could be array< string >
+ int damageSourceId
+ int count
+ float delay
+ float offset
+ float range
+ vector normal
+ float duration
+ int groupSize
+ bool hasBase
+}
+
+struct ColorSwapStruct
+{
+ int statusEffectId
+ entity weaponOwner
+}
+
+struct
+{
+ float titanRocketLauncherTitanDamageRadius
+ float titanRocketLauncherOtherDamageRadius
+
+ int activeThermiteBurnsManagedEnts
+ array<ColorSwapStruct> colorSwapStatusEffects
+} file
+
+global int HOLO_PILOT_TRAIL_FX
+
+global struct HoverSounds
+{
+ string liftoff_1p
+ string liftoff_3p
+ string hover_1p
+ string hover_3p
+ string descent_1p
+ string descent_3p
+ string landing_1p
+ string landing_3p
+}
+
+#endif
+
+function WeaponUtility_Init()
+{
+ level.weaponsPrecached <- {}
+
+ // what classes can sticky thrown entities stick to?
+ level.stickyClasses <- {}
+ level.stickyClasses[ "worldspawn" ] <- true
+ level.stickyClasses[ "player" ] <- true
+ level.stickyClasses[ "prop_dynamic" ] <- true
+ level.stickyClasses[ "prop_script" ] <- true
+ level.stickyClasses[ "func_brush" ] <- true
+ level.stickyClasses[ "func_brush_lightweight" ] <- true
+ level.stickyClasses[ "phys_bone_follower" ] <- true
+
+ level.trapChainReactClasses <- {}
+ level.trapChainReactClasses[ "mp_weapon_frag_grenade" ] <- true
+ level.trapChainReactClasses[ "mp_weapon_satchel" ] <- true
+ level.trapChainReactClasses[ "mp_weapon_proximity_mine" ] <- true
+ level.trapChainReactClasses[ "mp_weapon_laser_mine" ] <- true
+
+ RegisterSignal( "Planted" )
+ RegisterSignal( "EMP_FX" )
+ RegisterSignal( "ArcStunned" )
+
+ PrecacheParticleSystem( EMP_GRENADE_BEAM_EFFECT )
+ PrecacheParticleSystem( FX_EMP_BODY_TITAN )
+ PrecacheParticleSystem( FX_EMP_BODY_HUMAN )
+ PrecacheParticleSystem( FX_VANGUARD_ENERGY_BODY_HUMAN )
+ PrecacheParticleSystem( FX_VANGUARD_ENERGY_BODY_TITAN )
+ PrecacheParticleSystem( FX_EMP_REBOOT_SPARKS )
+
+ PrecacheImpactEffectTable( CLUSTER_ROCKET_FX_TABLE )
+
+ #if SERVER
+ AddDamageCallbackSourceID( eDamageSourceId.mp_titanweapon_triple_threat, TripleThreatGrenade_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_defender, Defender_DamagedPlayerOrNPC )
+ //AddDamageCallbackSourceID( eDamageSourceId.mp_titanweapon_rocketeer_rocketstream, TitanRocketLauncher_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_smr, SMR_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_flak_rifle, PROTO_Flak_Rifle_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_titanweapon_stun_laser, VanguardEnergySiphon_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_grenade_emp, EMP_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId.mp_weapon_proximity_mine, EMP_DamagedPlayerOrNPC )
+ AddDamageCallbackSourceID( eDamageSourceId[ CHARGE_TOOL ], EMP_DamagedPlayerOrNPC )
+ if ( IsMultiplayer() )
+ AddCallback_OnPlayerRespawned( PROTO_TrackedProjectile_OnPlayerRespawned )
+ AddCallback_OnPlayerKilled( PAS_CooldownReduction_OnKill )
+ AddCallback_OnPlayerGetsNewPilotLoadout( OnPlayerGetsNewPilotLoadout )
+ AddCallback_OnPlayerKilled( OnPlayerKilled )
+
+ file.activeThermiteBurnsManagedEnts = CreateScriptManagedEntArray()
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad )
+
+ HOLO_PILOT_TRAIL_FX = PrecacheParticleSystem( $"P_ar_holopilot_trail" )
+
+ PrecacheParticleSystem( $"wpn_laser_blink" )
+ PrecacheParticleSystem( $"wpn_laser_blink_fast" )
+ PrecacheParticleSystem( $"P_ordinance_icon_owner" )
+ #endif
+}
+
+#if SERVER
+void function EntitiesDidLoad()
+{
+#if SP
+ // if we are going to do this, it should happen in the weapon, not globally
+ //float titanRocketLauncherInnerRadius = expect float( GetWeaponInfoFileKeyField_Global( "mp_titanweapon_rocketeer_rocketstream", "explosion_inner_radius" ) )
+ //float titanRocketLauncherOuterRadius = expect float( GetWeaponInfoFileKeyField_Global( "mp_titanweapon_rocketeer_rocketstream", "explosionradius" ) )
+ //file.titanRocketLauncherTitanDamageRadius = titanRocketLauncherInnerRadius + ( ( titanRocketLauncherOuterRadius - titanRocketLauncherInnerRadius ) * 0.4 )
+ //file.titanRocketLauncherOtherDamageRadius = titanRocketLauncherInnerRadius + ( ( titanRocketLauncherOuterRadius - titanRocketLauncherInnerRadius ) * 0.1 )
+#endif
+}
+#endif
+
+////////////////////////////////////////////////////////////////////
+
+#if CLIENT
+void function GlobalClientEventHandler( entity weapon, string name )
+{
+ if ( name == "ammo_update" )
+ UpdateViewmodelAmmo( false, weapon )
+
+ if ( name == "ammo_full" )
+ UpdateViewmodelAmmo( true, weapon )
+}
+
+function UpdateViewmodelAmmo( bool forceFull, entity weapon )
+{
+ Assert( weapon != null ) // used to be: if ( weapon == null ) weapon = this.self
+
+ if ( !IsValid( weapon ) )
+ return
+ if ( !IsLocalViewPlayer( weapon.GetWeaponOwner() ) )
+ return
+
+ int bodyGroupCount = weapon.GetWeaponSettingInt( eWeaponVar.bodygroup_ammo_index_count )
+ if ( bodyGroupCount <= 0 )
+ return
+
+ int rounds = weapon.GetWeaponPrimaryClipCount()
+ int maxRoundsForClipSize = weapon.GetWeaponPrimaryClipCountMax()
+ int maxRoundsForBodyGroup = (bodyGroupCount - 1)
+ int maxRounds = minint( maxRoundsForClipSize, maxRoundsForBodyGroup )
+
+ if ( forceFull || (rounds > maxRounds) )
+ rounds = maxRounds
+
+ //printt( "ROUNDS:", rounds, "/", maxRounds )
+ weapon.SetViewmodelAmmoModelIndex( rounds )
+}
+#endif // #if CLIENT
+
+void function OnWeaponActivate_updateViewmodelAmmo( entity weapon )
+{
+#if CLIENT
+ UpdateViewmodelAmmo( false, weapon )
+#endif // #if CLIENT
+}
+
+#if SERVER
+//////////////////WEAPON DAMAGE CALLBACKS/////////////////////////////////////////
+void function TripleThreatGrenade_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ if ( ent.GetClassName() == "grenade_frag" )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ vector damagePosition = DamageInfo_GetDamagePosition( damageInfo )
+
+ vector entOrigin = ent.GetOrigin()
+ vector entCenter = ent.GetWorldSpaceCenter()
+ float distanceToOrigin = Distance( entOrigin, damagePosition )
+ float distanceToCenter = Distance( entCenter, damagePosition )
+
+ vector normal = Vector( 0, 0, 1 )
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ if ( IsValid( inflictor.s ) )
+ {
+ if ( "collisionNormal" in inflictor.s )
+ normal = expect vector( inflictor.s.collisionNormal )
+ }
+
+ local zDifferenceOrigin = deg_cos( DegreesToTarget( entOrigin, normal, damagePosition ) ) * distanceToOrigin
+ local zDifferenceTop = deg_cos( DegreesToTarget( entCenter, normal, damagePosition ) ) * distanceToCenter - (entCenter.z - entOrigin.z)
+
+ float zDamageDiff
+ //Full damage if explosion is between Origin or Center.
+ if ( zDifferenceOrigin > 0 && zDifferenceTop < 0 )
+ zDamageDiff = 1.0
+ else if ( zDifferenceTop > 0 )
+ zDamageDiff = GraphCapped( zDifferenceTop, 0.0, 32.0, 1.0, 0.0 )
+ else
+ zDamageDiff = GraphCapped( zDifferenceOrigin, 0.0, -32.0, 1.0, 0.0 )
+
+ DamageInfo_ScaleDamage( damageInfo, zDamageDiff )
+}
+
+void function Defender_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ local damage = Vortex_HandleElectricDamage( ent, DamageInfo_GetAttacker( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetWeapon( damageInfo ) )
+ DamageInfo_SetDamage( damageInfo, damage )
+}
+
+/*
+void function TitanRocketLauncher_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ Assert( IsSingleplayer() )
+
+ if ( !IsValid( ent ) )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ vector damagePosition = DamageInfo_GetDamagePosition( damageInfo )
+
+ if ( ent == DamageInfo_GetAttacker( damageInfo ) )
+ return
+
+ if ( ent.IsTitan() )
+ {
+ vector entOrigin = ent.GetOrigin()
+ if ( Distance( damagePosition, entOrigin ) > file.titanRocketLauncherTitanDamageRadius )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+ else if ( IsHumanSized( ent ) )
+ {
+ if ( Distance( damagePosition, ent.GetOrigin() ) > file.titanRocketLauncherOtherDamageRadius )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+}
+*/
+
+void function SMR_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ //Hack - JFS ( The explosion radius is too small on the SMR to deal splash damage to pilots on a Titan. )
+ if ( !IsValid( ent ) )
+ return
+
+ if ( !ent.IsTitan() )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( IsValid( attacker ) && attacker.IsPlayer() && attacker.GetTitanSoulBeingRodeoed() == ent.GetTitanSoul() )
+ attacker.TakeDamage( 30, attacker, attacker, { scriptType = DF_GIB | DF_EXPLOSION, damageSourceId = eDamageSourceId.mp_weapon_smr, weapon = DamageInfo_GetWeapon( damageInfo ) } )
+}
+
+
+void function PROTO_Flak_Rifle_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( !IsValid( ent ) || !IsValid( attacker ) )
+ return
+
+ if ( attacker == ent )
+ DamageInfo_ScaleDamage( damageInfo, 0.5 )
+}
+
+function EngineerRocket_DamagedPlayerOrNPC( ent, damageInfo )
+{
+ expect entity( ent )
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( !IsValid( ent ) || !IsValid( attacker ) )
+ return
+
+ if ( attacker == ent )
+ DamageInfo_SetDamage( damageInfo, 10 )
+}
+///////////////////////////////////////////////////////////////////////
+
+#endif // SERVER
+
+vector function ApplyVectorSpread( vector vecShotDirection, float spreadDegrees, float bias = 1.0 )
+{
+ vector angles = VectorToAngles( vecShotDirection )
+ vector vecUp = AnglesToUp( angles )
+ vector vecRight = AnglesToRight( angles )
+
+ float sinDeg = deg_sin( spreadDegrees / 2.0 )
+
+ // get circular gaussian spread
+ float x
+ float y
+ float z
+
+ if ( bias > 1.0 )
+ bias = 1.0
+ else if ( bias < 0.0 )
+ bias = 0.0
+
+ // code gets these values from cvars ai_shot_bias_min & ai_shot_bias_max
+ float shotBiasMin = -1.0
+ float shotBiasMax = 1.0
+
+ // 1.0 gaussian, 0.0 is flat, -1.0 is inverse gaussian
+ float shotBias = ( ( shotBiasMax - shotBiasMin ) * bias ) + shotBiasMin
+ float flatness = ( fabs(shotBias) * 0.5 )
+
+ while ( true )
+ {
+ x = RandomFloatRange( -1.0, 1.0 ) * flatness + RandomFloatRange( -1.0, 1.0 ) * (1 - flatness)
+ y = RandomFloatRange( -1.0, 1.0 ) * flatness + RandomFloatRange( -1.0, 1.0 ) * (1 - flatness)
+ if ( shotBias < 0 )
+ {
+ x = ( x >= 0 ) ? 1.0 - x : -1.0 - x
+ y = ( y >= 0 ) ? 1.0 - y : -1.0 - y
+ }
+ z = x * x + y * y
+
+ if ( z <= 1 )
+ break
+ }
+
+ vector addX = vecRight * ( x * sinDeg )
+ vector addY = vecUp * ( y * sinDeg )
+ vector m_vecResult = vecShotDirection + addX + addY
+
+ return m_vecResult
+}
+
+
+float function DegreesToTarget( vector origin, vector forward, vector targetPos )
+{
+ vector dirToTarget = targetPos - origin
+ dirToTarget = Normalize( dirToTarget )
+ float dot = DotProduct( forward, dirToTarget )
+ float degToTarget = (acos( dot ) * 180 / PI)
+
+ return degToTarget
+}
+
+function ShotgunBlast( entity weapon, vector pos, vector dir, int numBlasts, int damageType, float damageScaler = 1.0, float ornull maxAngle = null, float ornull maxDistance = null )
+{
+ Assert( numBlasts > 0 )
+ int numBlastsOriginal = numBlasts
+ entity owner = weapon.GetWeaponOwner()
+
+ /*
+ Debug ConVars:
+ visible_ent_cone_debug_duration_client - Set to non-zero to see debug output
+ visible_ent_cone_debug_duration_server - Set to non-zero to see debug output
+ visible_ent_cone_debug_draw_radius - Size of trace endpoint debug draw
+ */
+
+ if ( maxDistance == null )
+ maxDistance = weapon.GetMaxDamageFarDist()
+ expect float( maxDistance )
+
+ if ( maxAngle == null )
+ maxAngle = owner.GetAttackSpreadAngle() * 0.5
+ expect float( maxAngle )
+
+ array<entity> ignoredEntities = [ owner ]
+ int traceMask = TRACE_MASK_SHOT
+ int visConeFlags = VIS_CONE_ENTS_TEST_HITBOXES | VIS_CONE_ENTS_CHECK_SOLID_BODY_HIT | VIS_CONE_ENTS_APPOX_CLOSEST_HITBOX | VIS_CONE_RETURN_HIT_VORTEX
+
+ entity antilagPlayer
+ if ( owner.IsPlayer() )
+ {
+ if ( owner.IsPhaseShifted() )
+ return;
+
+ antilagPlayer = owner
+ }
+
+ //JFS - Bug 198500
+ Assert( maxAngle > 0.0, "JFS returning out at this instance. We need to investigate when a valid mp_titanweapon_laser_lite weapon returns 0 spread")
+ if ( maxAngle == 0.0 )
+ return
+
+ array<VisibleEntityInCone> results = FindVisibleEntitiesInCone( pos, dir, maxDistance, (maxAngle * 1.1), ignoredEntities, traceMask, visConeFlags, antilagPlayer, weapon )
+ foreach ( result in results )
+ {
+ float angleToHitbox = 0.0
+ if ( !result.solidBodyHit )
+ angleToHitbox = DegreesToTarget( pos, dir, result.approxClosestHitboxPos )
+
+ numBlasts -= ShotgunBlastDamageEntity( weapon, pos, dir, result, angleToHitbox, maxAngle, numBlasts, damageType, damageScaler )
+ if ( numBlasts <= 0 )
+ break
+ }
+
+ //Something in the TakeDamage above is triggering the weapon owner to become invalid.
+ owner = weapon.GetWeaponOwner()
+ if ( !IsValid( owner ) )
+ return
+
+ // maxTracer limit set in /r1dev/src/game/client/c_player.h
+ const int MAX_TRACERS = 16
+ bool didHitAnything = ((numBlastsOriginal - numBlasts) != 0)
+ bool doTraceBrushOnly = (!didHitAnything)
+ if ( numBlasts > 0 )
+ weapon.FireWeaponBullet_Special( pos, dir, minint( numBlasts, MAX_TRACERS ), damageType, false, false, true, false, false, false, doTraceBrushOnly )
+}
+
+
+const SHOTGUN_ANGLE_MIN_FRACTION = 0.1;
+const SHOTGUN_ANGLE_MAX_FRACTION = 1.0;
+const SHOTGUN_DAMAGE_SCALE_AT_MIN_ANGLE = 0.8;
+const SHOTGUN_DAMAGE_SCALE_AT_MAX_ANGLE = 0.1;
+
+int function ShotgunBlastDamageEntity( entity weapon, vector barrelPos, vector barrelVec, VisibleEntityInCone result, float angle, float maxAngle, int numPellets, int damageType, float damageScaler )
+{
+ entity target = result.ent
+
+ //The damage scaler is currently only > 1 for the Titan Shotgun alt fire.
+ if ( !target.IsTitan() && damageScaler > 1 )
+ damageScaler = max( damageScaler * 0.4, 1.5 )
+
+ entity owner = weapon.GetWeaponOwner()
+ // Ent in cone not valid
+ if ( !IsValid( target ) || !IsValid( owner ) )
+ return 0
+
+ // Fire fake bullet towards entity for visual purposes only
+ vector hitLocation = result.visiblePosition
+ vector vecToEnt = ( hitLocation - barrelPos )
+ vecToEnt.Norm()
+ if ( Length( vecToEnt ) == 0 )
+ vecToEnt = barrelVec
+
+ // This fires a fake bullet that doesn't do any damage. Currently it triggeres a damage callback with 0 damage which is bad.
+ weapon.FireWeaponBullet_Special( barrelPos, vecToEnt, 1, damageType, true, true, true, false, false, false, false ) // fires perfect bullet with no antilag and no spread
+
+#if SERVER
+ // Determine how much damage to do based on distance
+ float distanceToTarget = Distance( barrelPos, hitLocation )
+
+ if ( !result.solidBodyHit ) // non solid hits take 1 blast more
+ distanceToTarget += 130
+
+ int extraMods = result.extraMods
+ float damageAmount = CalcWeaponDamage( owner, target, weapon, distanceToTarget, extraMods )
+
+ // vortex needs to scale damage based on number of rounds absorbed
+ string className = weapon.GetWeaponClassName()
+ if ( (className == "mp_titanweapon_vortex_shield") || (className == "mp_titanweapon_vortex_shield_ion") || (className == "mp_titanweapon_heat_shield") )
+ {
+ damageAmount *= numPellets
+ //printt( "scaling vortex hitscan output damage by", numPellets, "pellets for", weaponNearDamageTitan, "damage vs titans" )
+ }
+
+ float coneScaler = 1.0
+ //if ( angle > 0 )
+ // coneScaler = GraphCapped( angle, (maxAngle * SHOTGUN_ANGLE_MIN_FRACTION), (maxAngle * SHOTGUN_ANGLE_MAX_FRACTION), SHOTGUN_DAMAGE_SCALE_AT_MIN_ANGLE, SHOTGUN_DAMAGE_SCALE_AT_MAX_ANGLE )
+
+ // Calculate the final damage abount to inflict on the target. Also scale it by damageScaler which may have been passed in by script ( used by alt fire mode on titan shotgun to fire multiple shells )
+ float finalDamageAmount = damageAmount * coneScaler * damageScaler
+ //printt( "angle:", angle, "- coneScaler:", coneScaler, "- damageAmount:", damageAmount, "- damageScaler:", damageScaler, " = finalDamageAmount:", finalDamageAmount )
+
+ // Calculate impulse force to apply based on damage
+ int maxImpulseForce = expect int( weapon.GetWeaponInfoFileKeyField( "impulse_force" ) )
+ float impulseForce = float( maxImpulseForce ) * coneScaler * damageScaler
+ vector impulseVec = barrelVec * impulseForce
+
+ int damageSourceID = weapon.GetDamageSourceID()
+
+ //
+ float critScale = weapon.GetWeaponSettingFloat( eWeaponVar.critical_hit_damage_scale )
+ target.TakeDamage( finalDamageAmount, owner, weapon, { origin = hitLocation, force = impulseVec, scriptType = damageType, damageSourceId = damageSourceID, weapon = weapon, hitbox = result.visibleHitbox, criticalHitScale = critScale } )
+
+ //printt( "-----------" )
+ //printt( " distanceToTarget:", distanceToTarget )
+ //printt( " damageAmount:", damageAmount )
+ //printt( " coneScaler:", coneScaler )
+ //printt( " impulseForce:", impulseForce )
+ //printt( " impulseVec:", impulseVec.x + ", " + impulseVec.y + ", " + impulseVec.z )
+ //printt( " finalDamageAmount:", finalDamageAmount )
+ //PrintTable( result )
+#endif // #if SERVER
+
+ return 1
+}
+
+int function FireGenericBoltWithDrop( entity weapon, WeaponPrimaryAttackParams attackParams, bool isPlayerFired )
+{
+#if CLIENT
+ if ( !weapon.ShouldPredictProjectiles() )
+ return 1
+#endif // #if CLIENT
+
+ weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 )
+
+ const float PROJ_SPEED_SCALE = 1
+ const float PROJ_GRAVITY = 1
+ int damageFlags = weapon.GetWeaponDamageFlags()
+ entity bolt = weapon.FireWeaponBolt( attackParams.pos, attackParams.dir, PROJ_SPEED_SCALE, damageFlags, damageFlags, isPlayerFired, 0 )
+ if ( bolt != null )
+ {
+ bolt.kv.gravity = PROJ_GRAVITY
+ bolt.kv.rendercolor = "0 0 0"
+ bolt.kv.renderamt = 0
+ bolt.kv.fadedist = 1
+ }
+
+ return 1
+}
+var function OnWeaponPrimaryAttack_GenericBoltWithDrop_Player( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ return FireGenericBoltWithDrop( weapon, attackParams, true )
+}
+
+var function OnWeaponPrimaryAttack_EPG( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ entity missile = weapon.FireWeaponMissile( attackParams.pos, attackParams.dir, 1, damageTypes.largeCaliberExp, damageTypes.largeCaliberExp, false, PROJECTILE_NOT_PREDICTED )
+ if ( missile )
+ {
+ EmitSoundOnEntity( missile, "Weapon_Sidwinder_Projectile" )
+ missile.InitMissileForRandomDriftFromWeaponSettings( attackParams.pos, attackParams.dir )
+ }
+
+ return missile
+}
+
+#if SERVER
+var function OnWeaponPrimaryAttack_GenericBoltWithDrop_NPC( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ return FireGenericBoltWithDrop( weapon, attackParams, false )
+}
+#endif // #if SERVER
+
+
+var function OnWeaponPrimaryAttack_GenericMissile_Player( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 )
+
+ entity weaponOwner = weapon.GetWeaponOwner()
+ vector bulletVec = ApplyVectorSpread( attackParams.dir, weaponOwner.GetAttackSpreadAngle() - 1.0 )
+ attackParams.dir = bulletVec
+
+ if ( IsServer() || weapon.ShouldPredictProjectiles() )
+ {
+ entity missile = weapon.FireWeaponMissile( attackParams.pos, attackParams.dir, 1.0, weapon.GetWeaponDamageFlags(), weapon.GetWeaponDamageFlags(), false, PROJECTILE_PREDICTED )
+ if ( missile )
+ {
+ missile.InitMissileForRandomDriftFromWeaponSettings( attackParams.pos, attackParams.dir )
+ }
+ }
+}
+
+#if SERVER
+var function OnWeaponPrimaryAttack_GenericMissile_NPC( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 )
+
+ entity missile = weapon.FireWeaponMissile( attackParams.pos, attackParams.dir, 1.0, weapon.GetWeaponDamageFlags(), weapon.GetWeaponDamageFlags(), true, PROJECTILE_NOT_PREDICTED )
+ if ( missile )
+ {
+ missile.InitMissileForRandomDriftFromWeaponSettings( attackParams.pos, attackParams.dir )
+ }
+}
+#endif // #if SERVER
+
+bool function PlantStickyEntityOnWorldThatBouncesOffWalls( entity ent, table collisionParams, float bounceDot )
+{
+ entity hitEnt = expect entity( collisionParams.hitEnt )
+ if ( hitEnt && ( hitEnt.IsWorld() || hitEnt.HasPusherRootParent() ) )
+ {
+ float dot = expect vector( collisionParams.normal ).Dot( Vector( 0, 0, 1 ) )
+
+ if ( dot < bounceDot )
+ return false
+
+ return PlantStickyEntity( ent, collisionParams )
+ }
+
+ return false
+}
+
+bool function PlantStickyEntityThatBouncesOffWalls( entity ent, table collisionParams, float bounceDot )
+{
+ if ( expect entity( collisionParams.hitEnt ) == GetEntByIndex( 0 ) )
+ {
+ // Satchel hit the world
+ float dot = expect vector( collisionParams.normal ).Dot( Vector( 0, 0, 1 ) )
+
+ if ( dot < bounceDot )
+ return false
+ }
+
+ return PlantStickyEntity( ent, collisionParams )
+}
+
+
+bool function PlantStickyEntity( entity ent, table collisionParams, vector angleOffset = <0.0, 0.0, 0.0> )
+{
+ if ( !EntityShouldStick( ent, expect entity( collisionParams.hitEnt ) ) )
+ return false
+
+ // Don't allow parenting to another "sticky" entity to prevent them parenting onto each other
+ if ( collisionParams.hitEnt.IsProjectile() )
+ return false
+
+ // Update normal from last bouce so when it explodes it can orient the effect properly
+
+ vector plantAngles = AnglesCompose( VectorToAngles( collisionParams.normal ), angleOffset )
+ vector plantPosition = expect vector( collisionParams.pos )
+
+ if ( !LegalOrigin( plantPosition ) )
+ return false
+
+ #if SERVER
+ ent.SetAbsOrigin( plantPosition )
+ ent.SetAbsAngles( plantAngles )
+ ent.proj.isPlanted = true
+ #else
+ ent.SetOrigin( plantPosition )
+ ent.SetAngles( plantAngles )
+ #endif
+ ent.SetVelocity( Vector( 0, 0, 0 ) )
+
+ //printt( " - Hitbox is:", collisionParams.hitbox, " IsWorld:", collisionParams.hitEnt )
+ if ( !collisionParams.hitEnt.IsWorld() )
+ {
+ if ( !ent.IsMarkedForDeletion() && !collisionParams.hitEnt.IsMarkedForDeletion() )
+ {
+ if ( collisionParams.hitbox > 0 )
+ ent.SetParentWithHitbox( collisionParams.hitEnt, collisionParams.hitbox, true )
+
+ // Hit a func_brush
+ else
+ ent.SetParent( collisionParams.hitEnt )
+
+ if ( collisionParams.hitEnt.IsPlayer() )
+ {
+ thread HandleDisappearingParent( ent, expect entity( collisionParams.hitEnt ) )
+ }
+ }
+ }
+ else
+ {
+ ent.SetVelocity( Vector( 0, 0, 0 ) )
+ ent.StopPhysics()
+ }
+ #if CLIENT
+ if ( ent instanceof C_BaseGrenade )
+ #else
+ if ( ent instanceof CBaseGrenade )
+ #endif
+ ent.MarkAsAttached()
+
+ ent.Signal( "Planted" )
+
+ return true
+}
+
+bool function PlantStickyGrenade( entity ent, vector pos, vector normal, entity hitEnt, int hitbox, float depth = 0.0, bool allowBounce = true, bool allowEntityStick = true )
+{
+ if ( ent.GetTeam() == hitEnt.GetTeam() )
+ return false
+
+ if ( ent.IsMarkedForDeletion() || hitEnt.IsMarkedForDeletion() )
+ return false
+
+ vector plantAngles = VectorToAngles( normal )
+ vector plantPosition = pos + normal * -depth
+
+ if ( !allowBounce )
+ ent.SetVelocity( Vector( 0, 0, 0 ) )
+
+ if ( !LegalOrigin( plantPosition ) )
+ return false
+
+ #if SERVER
+ ent.SetAbsOrigin( plantPosition )
+ ent.SetAbsAngles( plantAngles )
+ ent.proj.isPlanted = true
+ #else
+ ent.SetOrigin( plantPosition )
+ ent.SetAngles( plantAngles )
+ #endif
+
+ if ( !hitEnt.IsWorld() && (!hitEnt.IsTitan() || !allowEntityStick) )
+ return false
+
+ // SetOrigin might be causing the ent to get markedForDeletion.
+ if ( ent.IsMarkedForDeletion() )
+ return false
+
+ ent.SetVelocity( Vector( 0, 0, 0 ) )
+
+ if ( hitEnt.IsWorld() )
+ {
+ ent.SetParent( hitEnt, "", true )
+ ent.StopPhysics()
+ }
+ else
+ {
+ if ( hitbox > 0 )
+ ent.SetParentWithHitbox( hitEnt, hitbox, true )
+ else // Hit a func_brush
+ ent.SetParent( hitEnt )
+
+ if ( hitEnt.IsPlayer() )
+ {
+ thread HandleDisappearingParent( ent, hitEnt )
+ }
+ }
+
+ #if CLIENT
+ if ( ent instanceof C_BaseGrenade )
+ ent.MarkAsAttached()
+ #else
+ if ( ent instanceof CBaseGrenade )
+ ent.MarkAsAttached()
+ #endif
+
+ return true
+}
+
+
+bool function PlantSuperStickyGrenade( entity ent, vector pos, vector normal, entity hitEnt, int hitbox )
+{
+ if ( ent.GetTeam() == hitEnt.GetTeam() )
+ return false
+
+ vector plantAngles = VectorToAngles( normal )
+ vector plantPosition = pos
+
+ if ( !LegalOrigin( plantPosition ) )
+ return false
+
+ #if SERVER
+ ent.SetAbsOrigin( plantPosition )
+ ent.SetAbsAngles( plantAngles )
+ ent.proj.isPlanted = true
+ #else
+ ent.SetOrigin( plantPosition )
+ ent.SetAngles( plantAngles )
+ #endif
+
+ if ( !hitEnt.IsWorld() && !hitEnt.IsPlayer() && !hitEnt.IsNPC() )
+ return false
+
+ ent.SetVelocity( Vector( 0, 0, 0 ) )
+
+ if ( hitEnt.IsWorld() )
+ {
+ ent.StopPhysics()
+ }
+ else
+ {
+ if ( !ent.IsMarkedForDeletion() && !hitEnt.IsMarkedForDeletion() )
+ {
+ if ( hitbox > 0 )
+ ent.SetParentWithHitbox( hitEnt, hitbox, true )
+ else // Hit a func_brush
+ ent.SetParent( hitEnt )
+
+ if ( hitEnt.IsPlayer() )
+ {
+ thread HandleDisappearingParent( ent, hitEnt )
+ }
+ }
+ }
+
+ #if CLIENT
+ if ( ent instanceof C_BaseGrenade )
+ ent.MarkAsAttached()
+ #else
+ if ( ent instanceof CBaseGrenade )
+ ent.MarkAsAttached()
+ #endif
+
+ return true
+}
+
+#if SERVER
+void function HandleDisappearingParent( entity ent, entity parentEnt )
+{
+ parentEnt.EndSignal( "OnDeath" )
+ ent.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ ent.ClearParent()
+ }
+ )
+
+ parentEnt.WaitSignal( "StartPhaseShift" )
+}
+#else
+void function HandleDisappearingParent( entity ent, entity parentEnt )
+{
+ parentEnt.EndSignal( "OnDeath" )
+ ent.EndSignal( "OnDestroy" )
+
+ parentEnt.WaitSignal( "StartPhaseShift" )
+
+ ent.ClearParent()
+}
+#endif
+
+bool function EntityShouldStick( entity stickyEnt, entity hitent )
+{
+ if ( !EntityCanHaveStickyEnts( stickyEnt, hitent ) )
+ return false
+
+ if ( hitent == stickyEnt )
+ return false
+
+ return true
+}
+
+bool function EntityCanHaveStickyEnts( entity stickyEnt, entity ent )
+{
+ if ( !IsValid( ent ) )
+ return false
+
+ if ( ent.GetModelName() == $"" ) // valid case, other projectiles bullets, etc.. sometimes have no model
+ return false;
+
+ local entClassname
+ if ( IsServer() )
+ entClassname = ent.GetClassName()
+ else
+ entClassname = ent.GetSignifierName() // Can return null
+
+ if ( !( entClassname in level.stickyClasses ) && !ent.IsNPC() )
+ return false
+
+ #if CLIENT
+ if ( stickyEnt instanceof C_Projectile )
+ #else
+ if ( stickyEnt instanceof CProjectile )
+ #endif
+ {
+ string weaponClassName = stickyEnt.ProjectileGetWeaponClassName()
+ local stickPlayer = GetWeaponInfoFileKeyField_Global( weaponClassName, "stick_pilot" )
+ local stickTitan = GetWeaponInfoFileKeyField_Global( weaponClassName, "stick_titan" )
+ local stickNPC = GetWeaponInfoFileKeyField_Global( weaponClassName, "stick_npc" )
+
+ if ( ent.IsTitan() && stickTitan )
+ return true
+ else if ( ent.IsPlayer() && stickPlayer )
+ return true
+ else if ( ent.IsNPC() && stickNPC )
+ return true
+
+ // not pilots
+ if ( ent.IsPlayer() && !ent.IsTitan() )
+ return false
+ }
+
+ return true
+}
+
+#if SERVER
+// shared with the vortex script which also needs to create satchels
+function Satchel_PostFired_Init( entity satchel, entity player )
+{
+ satchel.proj.onlyAllowSmartPistolDamage = false
+ thread SatchelThink( satchel, player )
+}
+
+function SatchelThink( entity satchel, entity player )
+{
+ player.EndSignal("OnDestroy")
+ satchel.EndSignal("OnDestroy")
+
+ int satchelHealth = 15
+ thread TrapExplodeOnDamage( satchel, satchelHealth )
+
+ #if DEV
+ // temp HACK for FX to use to figure out the size of the particle to play
+ if ( Flag( "ShowExplosionRadius" ) )
+ thread ShowExplosionRadiusOnExplode( satchel )
+ #endif
+
+ player.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( satchel )
+ {
+ if ( IsValid( satchel ) )
+ {
+ satchel.Destroy()
+ }
+ }
+ )
+
+ WaitForever()
+}
+
+#endif // SERVER
+
+function ProximityCharge_PostFired_Init( entity proximityMine, entity player )
+{
+ #if SERVER
+ proximityMine.proj.onlyAllowSmartPistolDamage = false
+ #endif
+}
+
+function DetonateAllPlantedExplosives( entity player )
+{
+ // ToDo: Could use Player_DetonateSatchels but it only tracks satchels, not laser mines.
+
+ // Detonate all explosives - satchels and laser mines are also frag grenades in disguise
+ array<entity> grenades = GetProjectileArrayEx( "grenade_frag", TEAM_ANY, TEAM_ANY, Vector( 0, 0, 0 ), -1 )
+ foreach( grenade in grenades )
+ {
+ if ( grenade.GetOwner() != player )
+ continue
+
+ if ( grenade.ProjectileGetDamageSourceID() != eDamageSourceId.mp_weapon_satchel && grenade.ProjectileGetDamageSourceID() != eDamageSourceId.mp_weapon_proximity_mine )
+ continue
+
+ thread ExplodePlantedGrenadeAfterDelay( grenade, RandomFloatRange( 0.75, 0.95 ) )
+ }
+}
+
+function ExplodePlantedGrenadeAfterDelay( entity grenade, float delay )
+{
+ grenade.EndSignal( "OnDeath" )
+ grenade.EndSignal( "OnDestroy" )
+
+ float endTime = Time() + delay
+
+ while ( Time() < endTime )
+ {
+ EmitSoundOnEntity( grenade, DEFAULT_WARNING_SFX )
+ wait 0.1
+ }
+
+ grenade.GrenadeExplode( grenade.GetForwardVector() )
+}
+
+function Player_DetonateSatchels( entity player )
+{
+ #if SERVER
+ Assert( IsServer() )
+
+ array<entity> traps = GetScriptManagedEntArray( player.s.activeTrapArrayId )
+ traps.sort( CompareCreationReverse )
+ foreach ( index, satchel in traps )
+ {
+ if ( IsValidSatchel( satchel ) )
+ {
+
+ thread PROTO_ExplodeAfterDelay( satchel, index * 0.25 )
+ }
+ }
+ #endif
+}
+
+function IsValidSatchel( entity satchel )
+{
+ #if SERVER
+ if ( satchel.ProjectileGetWeaponClassName() != "mp_weapon_satchel" )
+ return false
+
+ if ( satchel.e.isDisabled == true )
+ return false
+
+ return true
+ #endif
+}
+
+#if SERVER
+function PROTO_ExplodeAfterDelay( entity satchel, float delay )
+{
+ satchel.EndSignal( "OnDestroy" )
+
+ #if MP
+ while ( !satchel.proj.isPlanted )
+ {
+ WaitFrame()
+ }
+ #endif
+
+ wait delay
+
+ satchel.GrenadeExplode( satchel.GetForwardVector() )
+}
+#endif
+
+
+#if DEV
+function ShowExplosionRadiusOnExplode( entity ent )
+{
+ ent.WaitSignal( "OnDestroy" )
+
+ float innerRadius = expect float( ent.GetWeaponInfoFileKeyField( "explosion_inner_radius" ) )
+ float outerRadius = expect float( ent.GetWeaponInfoFileKeyField( "explosionradius" ) )
+
+ vector org = ent.GetOrigin()
+ vector angles = Vector( 0, 0, 0 )
+ thread DebugDrawCircle( org, angles, innerRadius, 255, 255, 51, true, 3.0 )
+ thread DebugDrawCircle( org, angles, outerRadius, 255, 255, 255, true, 3.0 )
+}
+#endif // DEV
+
+#if SERVER
+// shared between nades, satchels and laser mines
+void function TrapExplodeOnDamage( entity trapEnt, int trapEntHealth = 50, float waitMin = 0.0, float waitMax = 0.0 )
+{
+ Assert( IsValid( trapEnt ), "Given trapEnt entity is not valid, fired from: " + trapEnt.ProjectileGetWeaponClassName() )
+ EndSignal( trapEnt, "OnDestroy" )
+
+ trapEnt.SetDamageNotifications( true )
+ var results //Really should be a struct
+ entity attacker
+ entity inflictor
+
+ while ( true )
+ {
+ if ( !IsValid( trapEnt ) )
+ return
+
+ results = WaitSignal( trapEnt, "OnDamaged" )
+ attacker = expect entity( results.activator )
+ inflictor = expect entity( results.inflictor )
+
+ if ( IsValid( inflictor ) && inflictor == trapEnt )
+ continue
+
+ bool shouldDamageTrap = false
+ if ( IsValid( attacker ) )
+ {
+ if ( trapEnt.proj.onlyAllowSmartPistolDamage )
+ {
+ if ( attacker.IsNPC() || attacker.IsPlayer() )
+ {
+ entity attackerWeapon = attacker.GetActiveWeapon()
+ if ( IsValid( attackerWeapon ) && WeaponIsSmartPistolVariant( attackerWeapon ) )
+ shouldDamageTrap = true
+ }
+ }
+ else
+ {
+ if ( trapEnt.GetTeam() == attacker.GetTeam() )
+ {
+ if ( trapEnt.GetOwner() != attacker )
+ shouldDamageTrap = false
+ else
+ shouldDamageTrap = !ProjectileIgnoresOwnerDamage( trapEnt )
+ }
+ else
+ {
+ shouldDamageTrap = true
+ }
+ }
+ }
+
+ if ( shouldDamageTrap )
+ trapEntHealth -= int ( results.value ) //TODO: This returns float even though it feels like it should return int
+
+ if ( trapEntHealth <= 0 )
+ break
+ }
+
+ if ( !IsValid( trapEnt ) )
+ return
+
+ inflictor = expect entity( results.inflictor ) // waiting on code feature to pass inflictor with OnDamaged signal results table
+
+ if ( waitMin >= 0 && waitMax > 0 )
+ {
+ float waitTime = RandomFloatRange( waitMin, waitMax )
+
+ if ( waitTime > 0 )
+ wait waitTime
+ }
+ else if ( IsValid( inflictor ) && (inflictor.IsProjectile() || (inflictor instanceof CWeaponX)) )
+ {
+ int dmgSourceID
+ if ( inflictor.IsProjectile() )
+ dmgSourceID = inflictor.ProjectileGetDamageSourceID()
+ else
+ dmgSourceID = inflictor.GetDamageSourceID()
+
+ string inflictorClass = GetObitFromDamageSourceID( dmgSourceID )
+
+ if ( inflictorClass in level.trapChainReactClasses )
+ {
+ // chain reaction delay
+ Wait( RandomFloatRange( 0.2, 0.275 ) )
+ }
+ }
+
+ if ( !IsValid( trapEnt ) )
+ return
+
+ if ( IsValid( attacker ) )
+ {
+ if ( attacker.IsPlayer() )
+ {
+ AddPlayerScoreForTrapDestruction( attacker, trapEnt )
+ trapEnt.SetOwner( attacker )
+ }
+ else
+ {
+ entity lastAttacker = GetLastAttacker( attacker )
+ if ( IsValid( lastAttacker ) )
+ {
+ // for chain explosions, figure out the attacking player that started the chain
+ trapEnt.SetOwner( lastAttacker )
+ }
+ }
+ }
+
+ trapEnt.GrenadeExplode( trapEnt.GetForwardVector() )
+}
+
+bool function ProjectileIgnoresOwnerDamage( entity projectile )
+{
+ var ignoreOwnerDamage = projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_ignore_owner_damage" )
+
+ if ( ignoreOwnerDamage == null )
+ return false
+
+ return ignoreOwnerDamage == 1
+}
+
+bool function WeaponIsSmartPistolVariant( entity weapon )
+{
+ var isSP = weapon.GetWeaponInfoFileKeyField( "is_smart_pistol" )
+
+ //printt( isSP )
+
+ if ( isSP == null )
+ return false
+
+ return ( isSP == 1 )
+}
+
+// NOTE: we should stop using this
+function TrapDestroyOnRoundEnd( entity player, entity trapEnt )
+{
+ trapEnt.EndSignal( "OnDestroy" )
+
+ svGlobal.levelEnt.WaitSignal( "ClearedPlayers" )
+
+ if ( IsValid( trapEnt ) )
+ trapEnt.Destroy()
+}
+
+function AddPlayerScoreForTrapDestruction( entity player, entity trapEnt )
+{
+ // don't get score for killing your own trap
+ if ( "originalOwner" in trapEnt.s && trapEnt.s.originalOwner == player )
+ return
+
+ string trapClass = trapEnt.ProjectileGetWeaponClassName()
+ if ( trapClass == "" )
+ return
+
+ string scoreEvent
+ if ( trapClass == "mp_weapon_satchel" )
+ scoreEvent = "Destroyed_Satchel"
+ else if ( trapClass == "mp_weapon_proximity_mine" )
+ scoreEvent = "Destored_Proximity_Mine"
+
+ if ( scoreEvent == "" )
+ return
+
+ AddPlayerScore( player, scoreEvent, trapEnt )
+}
+
+table function GetBulletPassThroughTargets( entity attacker, WeaponBulletHitParams hitParams )
+{
+ //HACK requires code later
+ table passThroughInfo = {
+ endPos = null
+ targetArray = []
+ }
+
+ TraceResults result
+ array<entity> ignoreEnts = [ attacker, hitParams.hitEnt ]
+
+ while ( true )
+ {
+ vector vec = ( hitParams.hitPos - hitParams.startPos ) * 1000
+ ArrayRemoveInvalid( ignoreEnts )
+ result = TraceLine( hitParams.startPos, vec, ignoreEnts, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
+
+ if ( result.hitEnt == svGlobal.worldspawn )
+ break
+
+ ignoreEnts.append( result.hitEnt )
+
+ if ( IsValidPassThroughTarget( result.hitEnt, attacker ) )
+ passThroughInfo.targetArray.append( result.hitEnt )
+ }
+ passThroughInfo.endPos = result.endPos
+
+ return passThroughInfo
+}
+#endif // SERVER
+
+bool function WeaponCanCrit( entity weapon )
+{
+ // player sometimes has no weapon during titan exit, mantle, etc...
+ if ( !weapon )
+ return false
+
+ return weapon.GetWeaponSettingBool( eWeaponVar.critical_hit )
+}
+
+
+#if SERVER
+bool function IsValidPassThroughTarget( entity target, entity attacker )
+{
+ //Tied to PassThroughHack function remove when supported by code.
+ if ( target == svGlobal.worldspawn )
+ return false
+
+ if ( !IsValid( target ) )
+ return false
+
+ if ( target.GetTeam() == attacker.GetTeam() )
+ return false
+
+ if ( target.GetTeam() != TEAM_IMC && target.GetTeam() != TEAM_MILITIA )
+ return false
+
+ return true
+}
+
+function PassThroughDamage( entity weapon, targetArray )
+{
+ //Tied to PassThroughHack function remove when supported by code.
+
+ int damageSourceID = weapon.GetDamageSourceID()
+ entity owner = weapon.GetWeaponOwner()
+
+ foreach ( ent in targetArray )
+ {
+ expect entity( ent )
+
+ float distanceToTarget = Distance( weapon.GetOrigin(), ent.GetOrigin() )
+ float damageToDeal = CalcWeaponDamage( owner, ent, weapon, distanceToTarget, 0 )
+
+ ent.TakeDamage( damageToDeal, owner, weapon.GetWeaponOwner(), { damageSourceId = damageSourceID } )
+ }
+}
+#endif // SERVER
+
+vector function GetVectorFromPositionToCrosshair( entity player, vector startPos )
+{
+ Assert( IsValid( player ) )
+
+ // See where we're looking
+ vector traceStart = player.EyePosition()
+ vector traceEnd = traceStart + ( player.GetViewVector() * 20000 )
+ local ignoreEnts = [ player ]
+ TraceResults traceResult = TraceLine( traceStart, traceEnd, ignoreEnts, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
+
+ // Return vec from startPos to where we are looking
+ vector vec = traceResult.endPos - startPos
+ vec = Normalize( vec )
+ return vec
+}
+
+/*
+function InitMissileForRandomDriftBasic( missile, startPos, startDir )
+{
+ missile.s.RandomFloatRange <- RandomFloat( 1.0 )
+ missile.s.startPos <- startPos
+ missile.s.startDir <- startDir
+}
+*/
+
+function InitMissileForRandomDriftForVortexHigh( entity missile, vector startPos, vector startDir )
+{
+ missile.InitMissileForRandomDrift( startPos, startDir, 8, 2.5, 0, 0, 100, 100 )
+}
+
+function InitMissileForRandomDriftForVortexLow( entity missile, vector startPos, vector startDir )
+{
+ missile.InitMissileForRandomDrift( startPos, startDir, 0.3, 0.085, 0, 0, 0.5, 0.5 )
+}
+
+/*
+function InitMissileForRandomDrift( missile, startPos, startDir )
+{
+ InitMissileForRandomDriftBasic( missile, startPos, startDir )
+
+ missile.s.drift_windiness <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_drift_windiness" )
+ missile.s.drift_intensity <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_drift_intensity" )
+
+ missile.s.straight_time_min <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_straight_time_min" )
+ missile.s.straight_time_max <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_straight_time_max" )
+
+ missile.s.straight_radius_min <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_straight_radius_min" )
+ if ( missile.s.straight_radius_min < 1 )
+ missile.s.straight_radius_min = 1
+ missile.s.straight_radius_max <- missile.ProjectileGetWeaponInfoFileKeyField( "projectile_straight_radius_max" )
+ if ( missile.s.straight_radius_max < 1 )
+ missile.s.straight_radius_max = 1
+}
+
+function SmoothRandom( x )
+{
+ return 0.25 * (sin(x) + sin(x * 0.762) + sin(x * 0.363) + sin(x * 0.084))
+}
+
+function MissileRandomDrift( timeElapsed, timeStep, windiness, intensity )
+{
+ // This function makes the missile go in a random direction.
+ // Windiness is how frequently the missile changes direction.
+ // Intensity is how strongly the missile steers in the direction it has chosen.
+
+ local sampleTime = timeElapsed - timeStep * 0.5
+
+ intensity *= timeStep
+
+ local offset = self.s.RandomFloatRange * 1000
+
+ local offsetx = intensity * SmoothRandom( offset + sampleTime * windiness )
+ local offsety = intensity * SmoothRandom( offset * 2 + 100 + sampleTime * windiness )
+
+ local right = self.GetRightVector()
+ local up = self.GetUpVector()
+
+ //DebugDrawLine( self.GetOrigin(), self.GetOrigin() + right * 100, 255,255,255, true, 0 )
+ //DebugDrawLine( self.GetOrigin(), self.GetOrigin() + up * 100, 255,128,255, true, 0 )
+
+ local dir = self.GetVelocity()
+ local speed = Length( dir )
+ dir = Normalize( dir )
+ dir += right * offsetx
+ dir += up * offsety
+ dir = Normalize( dir )
+ dir *= speed
+
+ return dir
+}
+
+// designed to be called every frame (GetProjectileVelocity callback) on projectiles that are flying through the air
+function ApplyMissileControlledDrift( missile, timeElapsed, timeStep )
+{
+ // If we have a target, don't do anything fancy; just let code do the homing behavior
+ if ( missile.GetMissileTarget() )
+ return missile.GetVelocity()
+
+ local s = missile.s
+ return MissileControlledDrift( timeElapsed, timeStep, s.drift_windiness, s.drift_intensity, s.straight_time_min, s.straight_time_max, s.straight_radius_min, s.straight_radius_max )
+}
+
+function MissileControlledDrift( timeElapsed, timeStep, windiness, intensity, pathTimeMin, pathTimeMax, pathRadiusMin, pathRadiusMax )
+{
+ // Start with random drift.
+ local vel = MissileRandomDrift( timeElapsed, timeStep, windiness, intensity )
+
+ // Straighten our velocity back along our original path if we're below pathTimeMax.
+ // Path time is how long it tries to stay on a straight path.
+ // Path radius is how far it can get from its straight path.
+ if ( timeElapsed < pathTimeMax )
+ {
+ local org = self.GetOrigin()
+ local alongPathLen = self.s.startDir.Dot( org - self.s.startPos )
+ local alongPathPos = self.s.startPos + self.s.startDir * alongPathLen
+ local offPathOffset = org - alongPathPos
+ local pathDist = Length( offPathOffset )
+
+ local speed = Length( vel )
+
+ local lerp = 1
+ if ( timeElapsed > pathTimeMin )
+ lerp = 1.0 - (timeElapsed - pathTimeMin) / (pathTimeMax - pathTimeMin)
+
+ local pathRadius = pathRadiusMax + (pathRadiusMin - pathRadiusMax) * lerp
+
+ // This circle shows the radius the missile is allowed to be in.
+ //if ( IsServer() )
+ // DebugDrawCircle( alongPathPos, VectorToAngles( AnglesToUp( VectorToAngles( self.s.startDir ) ) ), pathRadius, 255,255,255, true, 0.0 )
+
+ local backToPathVel = offPathOffset * -1
+ // Cap backToPathVel at speed
+ if ( pathDist > pathRadius )
+ backToPathVel *= speed / pathDist
+ else
+ backToPathVel *= speed / pathRadius
+
+ if ( pathDist < pathRadius )
+ {
+ backToPathVel += self.s.startDir * (speed * (1.0 - pathDist / pathRadius))
+ }
+
+ //DebugDrawLine( org, org + vel * 0.1, 255,255,255, true, 0 )
+ //DebugDrawLine( org, org + backToPathVel * intensity * lerp * 0.1, 128,255,128, true, 0 )
+
+ vel += backToPathVel * (intensity * timeStep)
+ vel = Normalize( vel )
+ vel *= speed
+ }
+
+ return vel
+}
+*/
+
+#if SERVER
+function ClusterRocket_Detonate( entity rocket, vector normal )
+{
+ entity owner = rocket.GetOwner()
+ if ( !IsValid( owner ) )
+ return
+
+ int count
+ float duration
+ float range
+
+ array mods = rocket.ProjectileGetMods()
+ if ( mods.contains( "pas_northstar_cluster" ) )
+ {
+ count = CLUSTER_ROCKET_BURST_COUNT_BURN
+ duration = PAS_NORTHSTAR_CLUSTER_ROCKET_DURATION
+ range = CLUSTER_ROCKET_BURST_RANGE * 1.5
+ }
+ else
+ {
+ count = CLUSTER_ROCKET_BURST_COUNT
+ duration = CLUSTER_ROCKET_DURATION
+ range = CLUSTER_ROCKET_BURST_RANGE
+ }
+
+ if ( mods.contains( "fd_twin_cluster" ) )
+ {
+ count = int( count * 0.7 )
+ duration *= 0.7
+ }
+ PopcornInfo popcornInfo
+
+ popcornInfo.weaponName = "mp_titanweapon_dumbfire_rockets"
+ popcornInfo.weaponMods = mods
+ popcornInfo.damageSourceId = eDamageSourceId.mp_titanweapon_dumbfire_rockets
+ popcornInfo.count = count
+ popcornInfo.delay = CLUSTER_ROCKET_BURST_DELAY
+ popcornInfo.offset = CLUSTER_ROCKET_BURST_OFFSET
+ popcornInfo.range = range
+ popcornInfo.normal = normal
+ popcornInfo.duration = duration
+ popcornInfo.groupSize = CLUSTER_ROCKET_BURST_GROUP_SIZE
+ popcornInfo.hasBase = true
+
+ thread StartClusterExplosions( rocket, owner, popcornInfo, CLUSTER_ROCKET_FX_TABLE )
+}
+
+
+function StartClusterExplosions( entity projectile, entity owner, PopcornInfo popcornInfo, customFxTable = null )
+{
+ Assert( IsValid( owner ) )
+ owner.EndSignal( "OnDestroy" )
+
+ string weaponName = popcornInfo.weaponName
+ float innerRadius
+ float outerRadius
+ int explosionDamage
+ int explosionDamageHeavyArmor
+
+ innerRadius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosion_inner_radius )
+ outerRadius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosionradius )
+ if ( owner.IsPlayer() )
+ {
+ explosionDamage = projectile.GetProjectileWeaponSettingInt( eWeaponVar.explosion_damage )
+ explosionDamageHeavyArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.explosion_damage_heavy_armor )
+ }
+ else
+ {
+ explosionDamage = projectile.GetProjectileWeaponSettingInt( eWeaponVar.npc_explosion_damage )
+ explosionDamageHeavyArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.npc_explosion_damage_heavy_armor )
+ }
+
+ local explosionDelay = projectile.ProjectileGetWeaponInfoFileKeyField( "projectile_explosion_delay" )
+
+ if ( owner.IsPlayer() )
+ owner.EndSignal( "OnDestroy" )
+
+ vector origin = projectile.GetOrigin()
+
+ vector rotateFX = Vector( 90,0,0 )
+ entity placementHelper = CreateScriptMover()
+ placementHelper.SetOrigin( origin )
+ placementHelper.SetAngles( VectorToAngles( popcornInfo.normal ) )
+ SetTeam( placementHelper, owner.GetTeam() )
+
+ array<entity> players = GetPlayerArray()
+ foreach ( player in players )
+ {
+ Remote_CallFunction_NonReplay( player, "SCB_AddGrenadeIndicatorForEntity", owner.GetTeam(), owner.GetEncodedEHandle(), placementHelper.GetEncodedEHandle(), outerRadius )
+ }
+
+ int particleSystemIndex = GetParticleSystemIndex( CLUSTER_BASE_FX )
+ int attachId = placementHelper.LookupAttachment( "REF" )
+ entity fx
+
+ if ( popcornInfo.hasBase )
+ {
+ fx = StartParticleEffectOnEntity_ReturnEntity( placementHelper, particleSystemIndex, FX_PATTACH_POINT_FOLLOW, attachId )
+ EmitSoundOnEntity( placementHelper, "Explo_ThermiteGrenade_Impact_3P" ) // TODO: wants a custom sound
+ }
+
+ OnThreadEnd(
+ function() : ( fx, placementHelper )
+ {
+ if ( IsValid( fx ) )
+ EffectStop( fx )
+ placementHelper.Destroy()
+ }
+ )
+
+ if ( explosionDelay )
+ wait explosionDelay
+
+ waitthread ClusterRocketBursts( origin, explosionDamage, explosionDamageHeavyArmor, innerRadius, outerRadius, owner, popcornInfo, customFxTable )
+
+ if ( IsValid( projectile ) )
+ projectile.Destroy()
+}
+
+
+//------------------------------------------------------------
+// ClusterRocketBurst() - does a "popcorn airburst" explosion effect over time around the origin. Total distance is based on popRangeBase
+// - returns the entity in case you want to parent it
+//------------------------------------------------------------
+function ClusterRocketBursts( vector origin, int damage, int damageHeavyArmor, float innerRadius, float outerRadius, entity owner, PopcornInfo popcornInfo, customFxTable = null )
+{
+ owner.EndSignal( "OnDestroy" )
+
+ // this ent remembers the weapon mods
+ entity clusterExplosionEnt = CreateEntity( "info_target" )
+ DispatchSpawn( clusterExplosionEnt )
+
+ if ( popcornInfo.weaponMods.len() > 0 )
+ clusterExplosionEnt.s.weaponMods <- popcornInfo.weaponMods
+
+ clusterExplosionEnt.SetOwner( owner )
+ clusterExplosionEnt.SetOrigin( origin )
+
+ AI_CreateDangerousArea_Static( clusterExplosionEnt, null, outerRadius, TEAM_INVALID, true, true, origin )
+
+ OnThreadEnd(
+ function() : ( clusterExplosionEnt )
+ {
+ clusterExplosionEnt.Destroy()
+ }
+ )
+
+ // No Damage - Only Force
+ // Push players
+ // Test LOS before pushing
+ int flags = 11
+ // create a blast that knocks pilots out of the way
+ CreatePhysExplosion( origin, outerRadius, PHYS_EXPLOSION_LARGE, flags )
+
+ int count = popcornInfo.groupSize
+ for ( int index = 0; index < count; index++ )
+ {
+ thread ClusterRocketBurst( clusterExplosionEnt, origin, damage, damageHeavyArmor, innerRadius, outerRadius, owner, popcornInfo, customFxTable )
+ WaitFrame()
+ }
+
+ wait CLUSTER_ROCKET_DURATION
+}
+
+function ClusterRocketBurst( entity clusterExplosionEnt, vector origin, damage, damageHeavyArmor, innerRadius, outerRadius, entity owner, PopcornInfo popcornInfo, customFxTable = null )
+{
+ clusterExplosionEnt.EndSignal( "OnDestroy" )
+ Assert( IsValid( owner ), "ClusterRocketBurst had invalid owner" )
+
+ // first explosion always happens where you fired
+ //int eDamageSource = popcornInfo.damageSourceId
+ int numBursts = popcornInfo.count
+ float popRangeBase = popcornInfo.range
+ float popDelayBase = popcornInfo.delay
+ float popDelayRandRange = popcornInfo.offset
+ float duration = popcornInfo.duration
+ int groupSize = popcornInfo.groupSize
+
+ int counter = 0
+ vector randVec
+ float randRangeMod
+ float popRange
+ vector popVec
+ vector popOri = origin
+ float popDelay
+ float colTrace
+
+ float burstDelay = duration / ( numBursts / groupSize )
+
+ vector clusterBurstOrigin = origin + (popcornInfo.normal * 8.0)
+ entity clusterBurstEnt = CreateClusterBurst( clusterBurstOrigin )
+
+ OnThreadEnd(
+ function() : ( clusterBurstEnt )
+ {
+ if ( IsValid( clusterBurstEnt ) )
+ {
+ foreach ( fx in clusterBurstEnt.e.fxArray )
+ {
+ if ( IsValid( fx ) )
+ fx.Destroy()
+ }
+ clusterBurstEnt.Destroy()
+ }
+ }
+ )
+
+ while ( IsValid( clusterBurstEnt ) && counter <= numBursts / popcornInfo.groupSize )
+ {
+ randVec = RandomVecInDome( popcornInfo.normal )
+ randRangeMod = RandomFloat( 1.0 )
+ popRange = popRangeBase * randRangeMod
+ popVec = randVec * popRange
+ popOri = origin + popVec
+ popDelay = popDelayBase + RandomFloatRange( -popDelayRandRange, popDelayRandRange )
+
+ colTrace = TraceLineSimple( origin, popOri, null )
+ if ( colTrace < 1 )
+ {
+ popVec = popVec * colTrace
+ popOri = origin + popVec
+ }
+
+ clusterBurstEnt.SetOrigin( clusterBurstOrigin )
+
+ vector velocity = GetVelocityForDestOverTime( clusterBurstEnt.GetOrigin(), popOri, burstDelay - popDelay )
+ clusterBurstEnt.SetVelocity( velocity )
+
+ clusterBurstOrigin = popOri
+
+ counter++
+
+ wait burstDelay - popDelay
+
+ Explosion(
+ clusterBurstOrigin,
+ owner,
+ clusterExplosionEnt,
+ damage,
+ damageHeavyArmor,
+ innerRadius,
+ outerRadius,
+ SF_ENVEXPLOSION_NOSOUND_FOR_ALLIES,
+ clusterBurstOrigin,
+ damage,
+ damageTypes.explosive,
+ popcornInfo.damageSourceId,
+ customFxTable )
+ }
+}
+
+
+entity function CreateClusterBurst( vector origin )
+{
+ entity prop_physics = CreateEntity( "prop_physics" )
+ prop_physics.SetValueForModelKey( $"models/weapons/bullets/projectile_rocket.mdl" )
+ prop_physics.kv.spawnflags = 4 // 4 = SF_PHYSPROP_DEBRIS
+ prop_physics.kv.fadedist = 2000
+ prop_physics.kv.renderamt = 255
+ prop_physics.kv.rendercolor = "255 255 255"
+ prop_physics.kv.CollisionGroup = TRACE_COLLISION_GROUP_DEBRIS
+
+ prop_physics.kv.minhealthdmg = 9999
+ prop_physics.kv.nodamageforces = 1
+ prop_physics.kv.inertiaScale = 1.0
+
+ prop_physics.SetOrigin( origin )
+ DispatchSpawn( prop_physics )
+ prop_physics.SetModel( $"models/weapons/grenades/m20_f_grenade.mdl" )
+
+ entity fx = PlayFXOnEntity( $"P_wpn_dumbfire_burst_trail", prop_physics )
+ prop_physics.e.fxArray.append( fx )
+
+ return prop_physics
+}
+#endif // SERVER
+
+vector function GetVelocityForDestOverTime( vector startPoint, vector endPoint, float duration )
+{
+ const GRAVITY = 750
+
+ float Vox = (endPoint.x - startPoint.x) / duration
+ float Voy = (endPoint.y - startPoint.y) / duration
+ float Voz = (endPoint.z + 0.5 * GRAVITY * duration * duration - startPoint.z) / duration
+
+ return Vector( Vox, Voy, Voz )
+}
+
+vector function GetPlayerVelocityForDestOverTime( vector startPoint, vector endPoint, float duration )
+{
+ // Same as above but accounts for player gravity setting not being 1.0
+
+ float gravityScale = expect float( GetPlayerSettingsFieldForClassName( DEFAULT_PILOT_SETTINGS, "gravityscale" ) )
+ float GRAVITY = 750 * gravityScale // adjusted for new gravity scale
+
+ float Vox = (endPoint.x - startPoint.x) / duration
+ float Voy = (endPoint.y - startPoint.y) / duration
+ float Voz = (endPoint.z + 0.5 * GRAVITY * duration * duration - startPoint.z) / duration
+
+ return Vector( Vox, Voy, Voz )
+}
+
+bool function HasLockedTarget( weapon )
+{
+ if ( weapon.SmartAmmo_IsEnabled() )
+ {
+ local targets = weapon.SmartAmmo_GetTargets()
+ if ( targets.len() > 0 )
+ {
+ foreach ( target in targets )
+ {
+ if ( target.fraction == 1 )
+ return true
+ }
+ }
+ }
+ return false
+}
+
+function CanWeaponShootWhileRunning( entity weapon )
+{
+ if ( "primary_fire_does_not_block_sprint" in weapon.s )
+ return weapon.s.primary_fire_does_not_block_sprint
+
+ if ( weapon.GetWeaponInfoFileKeyField( "primary_fire_does_not_block_sprint" ) == 1 )
+ {
+ weapon.s.primary_fire_does_not_block_sprint <- true
+ return true
+ }
+
+ weapon.s.primary_fire_does_not_block_sprint <- false
+ return false
+}
+
+#if CLIENT
+function ServerCallback_GuidedMissileDestroyed()
+{
+ entity player = GetLocalViewPlayer()
+
+ // guided missiles has not been updated to work with replays. added this if statement defensively just in case. - Roger
+ if ( !( "missileInFlight" in player.s ) )
+ return
+
+ player.s.missileInFlight = false
+}
+
+function ServerCallback_AirburstIconUpdate( toggle )
+{
+ entity player = GetLocalViewPlayer()
+ entity cockpit = player.GetCockpit()
+ if ( cockpit )
+ {
+ entity mainVGUI = cockpit.e.mainVGUI
+ if ( mainVGUI )
+ {
+ if ( toggle )
+ cockpit.s.offhandHud[OFFHAND_RIGHT].icon.SetImage( $"vgui/HUD/dpad_airburst_activate" )
+ else
+ cockpit.s.offhandHud[OFFHAND_RIGHT].icon.SetImage( $"vgui/HUD/dpad_airburst" )
+ }
+ }
+}
+
+bool function IsOwnerViewPlayerFullyADSed( entity weapon )
+{
+ entity owner = weapon.GetOwner()
+ if ( !IsValid( owner ) )
+ return false
+
+ if( !owner.IsPlayer() )
+ return false
+
+ if ( owner != GetLocalViewPlayer() )
+ return false
+
+ float zoomFrac = owner.GetZoomFrac()
+ if ( zoomFrac < 1.0 )
+ return false
+
+ return true
+
+}
+#endif // CLIENT
+
+array<entity> function FireExpandContractMissiles( entity weapon, WeaponPrimaryAttackParams attackParams, vector attackPos, vector attackDir, int damageType, int explosionDamageType, shouldPredict, int rocketsPerShot, missileSpeed, launchOutAng, launchOutTime, launchInAng, launchInTime, launchInLerpTime, launchStraightLerpTime, applyRandSpread, int burstFireCountOverride = -1, debugDrawPath = false )
+{
+ local missileVecs = GetExpandContractRocketTrajectories( weapon, attackParams.burstIndex, attackPos, attackDir, rocketsPerShot, launchOutAng, launchInAng, burstFireCountOverride )
+ entity owner = weapon.GetWeaponOwner()
+ array<entity> firedMissiles
+
+ vector missileEndPos = owner.EyePosition() + ( attackDir * 5000 )
+
+ for ( int i = 0; i < rocketsPerShot; i++ )
+ {
+ entity missile = weapon.FireWeaponMissile( attackPos, attackDir, missileSpeed, damageType, explosionDamageType, false, shouldPredict )
+
+ if ( missile )
+ {
+ /*
+ missile.s.flightData <- {
+ launchOutVec = missileVecs[i].outward,
+ launchOutTime = launchOutTime,
+ launchInLerpTime = launchInLerpTime,
+ launchInVec = missileVecs[i].inward,
+ launchInTime = launchInTime,
+ launchStraightLerpTime = launchStraightLerpTime,
+ endPos = missileEndPos,
+ applyRandSpread = applyRandSpread
+ }
+ */
+
+ missile.InitMissileExpandContract( missileVecs[i].outward, missileVecs[i].inward, launchOutTime, launchInLerpTime, launchInTime, launchStraightLerpTime, missileEndPos, applyRandSpread )
+
+ if ( IsServer() && debugDrawPath )
+ thread DebugDrawMissilePath( missile )
+
+ //InitMissileForRandomDrift( missile, attackPos, attackDir )
+ missile.InitMissileForRandomDriftFromWeaponSettings( attackPos, attackDir )
+
+ firedMissiles.append( missile )
+ }
+ }
+
+ return firedMissiles
+}
+
+array<entity> function FireExpandContractMissiles_S2S( entity weapon, WeaponPrimaryAttackParams attackParams, vector attackPos, vector attackDir, shouldPredict, int rocketsPerShot, missileSpeed, launchOutAng, launchOutTime, launchInAng, launchInTime, launchInLerpTime, launchStraightLerpTime, applyRandSpread, int burstFireCountOverride = -1, debugDrawPath = false )
+{
+ local missileVecs = GetExpandContractRocketTrajectories( weapon, attackParams.burstIndex, attackPos, attackDir, rocketsPerShot, launchOutAng, launchInAng, burstFireCountOverride )
+ entity owner = weapon.GetWeaponOwner()
+ array<entity> firedMissiles
+
+ vector missileEndPos = attackPos + ( attackDir * 5000 )
+
+ for ( int i = 0; i < rocketsPerShot; i++ )
+ {
+ entity missile = weapon.FireWeaponMissile( attackPos, attackDir, missileSpeed, DF_GIB | DF_IMPACT, damageTypes.explosive, false, shouldPredict )
+ missile.SetOrigin( attackPos )//HACK why do I have to do this?
+ if ( missile )
+ {
+ /*
+ missile.s.flightData <- {
+ launchOutVec = missileVecs[i].outward,
+ launchOutTime = launchOutTime,
+ launchInLerpTime = launchInLerpTime,
+ launchInVec = missileVecs[i].inward,
+ launchInTime = launchInTime,
+ launchStraightLerpTime = launchStraightLerpTime,
+ endPos = missileEndPos,
+ applyRandSpread = applyRandSpread
+ }
+ */
+
+ missile.InitMissileExpandContract( missileVecs[i].outward, missileVecs[i].inward, launchOutTime, launchInLerpTime, launchInTime, launchStraightLerpTime, missileEndPos, applyRandSpread )
+
+ if ( IsServer() && debugDrawPath )
+ thread DebugDrawMissilePath( missile )
+
+ //InitMissileForRandomDrift( missile, attackPos, attackDir )
+ missile.InitMissileForRandomDriftFromWeaponSettings( attackPos, attackDir )
+
+ firedMissiles.append( missile )
+ }
+ }
+
+ return firedMissiles
+}
+
+function GetExpandContractRocketTrajectories( entity weapon, int burstIndex, vector attackPos, vector attackDir, int rocketsPerShot, launchOutAng, launchInAng, int burstFireCount = -1 )
+{
+ bool DEBUG_DRAW_MATH = false
+
+ if ( burstFireCount == -1 )
+ burstFireCount = weapon.GetWeaponBurstFireCount()
+
+ local additionalRotation = ( ( 360.0 / rocketsPerShot ) / burstFireCount ) * burstIndex
+ //printt( "burstIndex:", burstIndex )
+ //printt( "rocketsPerShot:", rocketsPerShot )
+ //printt( "burstFireCount:", burstFireCount )
+
+ vector ang = VectorToAngles( attackDir )
+ vector forward = AnglesToForward( ang )
+ vector right = AnglesToRight( ang )
+ vector up = AnglesToUp( ang )
+
+ if ( DEBUG_DRAW_MATH )
+ DebugDrawLine( attackPos, attackPos + ( forward * 1000 ), 255, 0, 0, true, 30.0 )
+
+ // Create points on circle
+ float offsetAng = 360.0 / rocketsPerShot
+ for ( int i = 0; i < rocketsPerShot; i++ )
+ {
+ local a = offsetAng * i + additionalRotation
+ vector vec = Vector( 0, 0, 0 )
+ vec += up * deg_sin( a )
+ vec += right * deg_cos( a )
+
+ if ( DEBUG_DRAW_MATH )
+ DebugDrawLine( attackPos, attackPos + ( vec * 50 ), 10, 10, 10, true, 30.0 )
+ }
+
+ // Create missile points
+ vector x = right * deg_sin( launchOutAng )
+ vector y = up * deg_sin( launchOutAng )
+ vector z = forward * deg_cos( launchOutAng )
+ vector rx = right * deg_sin( launchInAng )
+ vector ry = up * deg_sin( launchInAng )
+ vector rz = forward * deg_cos( launchInAng )
+ local missilePoints = []
+ for ( int i = 0; i < rocketsPerShot; i++ )
+ {
+ local points = {}
+
+ // Outward vec
+ local a = offsetAng * i + additionalRotation
+ float s = deg_sin( a )
+ float c = deg_cos( a )
+ vector vecOut = z + x * c + y * s
+ vecOut = Normalize( vecOut )
+ points.outward <- vecOut
+
+ // Inward vec
+ vector vecIn = rz + rx * c + ry * s
+ points.inward <- vecIn
+
+ // Add to array
+ missilePoints.append( points )
+
+ if ( DEBUG_DRAW_MATH )
+ {
+ DebugDrawLine( attackPos, attackPos + ( vecOut * 50 ), 255, 255, 0, true, 30.0 )
+ DebugDrawLine( attackPos + vecOut * 50, attackPos + vecOut * 50 + ( vecIn * 50 ), 255, 0, 255, true, 30.0 )
+ }
+ }
+
+ return missilePoints
+}
+
+function DebugDrawMissilePath( entity missile )
+{
+ EndSignal( missile, "OnDestroy" )
+ vector lastPos = missile.GetOrigin()
+ while ( true )
+ {
+ WaitFrame()
+ if ( !IsValid( missile ) )
+ return
+ DebugDrawLine( lastPos, missile.GetOrigin(), 0, 255, 0, true, 20.0 )
+ lastPos = missile.GetOrigin()
+ }
+}
+
+
+function RegenerateOffhandAmmoOverTime( entity weapon, float rechargeTime, int maxAmmo, int offhandIndex )
+{
+ weapon.Signal( "RegenAmmo" )
+ weapon.EndSignal( "RegenAmmo" )
+ weapon.EndSignal( "OnDestroy" )
+
+ #if CLIENT
+ entity weaponOwner = weapon.GetWeaponOwner()
+ if ( IsValid( weaponOwner ) && weaponOwner.IsPlayer() )
+ {
+ entity cockpit = weaponOwner.GetCockpit()
+ if ( IsValid( cockpit ) )
+ {
+ cockpit.s.offhandHud[offhandIndex].bar.SetBarProgressSource( ProgressSource.PROGRESS_SOURCE_SCRIPTED )
+ cockpit.s.offhandHud[offhandIndex].bar.SetBarProgressRemap( 0.0, 1.0, 0.0, 1.0 )
+ cockpit.s.offhandHud[offhandIndex].bar.SetBarProgressAndRate( 1.0 / maxAmmo , 1 / ( rechargeTime * maxAmmo ) )
+ }
+ }
+ #endif
+
+ if ( !( "totalChargeTime" in weapon.s ) )
+ weapon.s.totalChargeTime <- rechargeTime
+
+ if ( !( "nextChargeTime" in weapon.s ) )
+ weapon.s.nextChargeTime <- null
+
+ for ( ;; )
+ {
+ weapon.s.nextChargeTime = rechargeTime + Time()
+
+ wait rechargeTime
+
+ if ( IsServer() )
+ {
+ int max = maxAmmo
+ int weaponMax = weapon.GetWeaponPrimaryClipCountMax()
+ if ( weaponMax < max )
+ max = weaponMax
+
+ int ammo = weapon.GetWeaponPrimaryClipCount()
+ if ( ammo < max )
+ weapon.SetWeaponPrimaryClipCount( ammo + 1 )
+ }
+ }
+}
+
+bool function IsPilotShotgunWeapon( string weaponName )
+{
+ return GetWeaponInfoFileKeyField_Global( weaponName, "weaponSubClass" ) == "shotgun"
+}
+
+array<string> function GetWeaponBurnMods( string weaponClassName )
+{
+ array<string> burnMods = []
+ array<string> mods = GetWeaponMods_Global( weaponClassName )
+ string prefix = "burn_mod"
+ foreach ( mod in mods )
+ {
+ if ( mod.find( prefix ) == 0 )
+ burnMods.append( mod )
+ }
+
+ return burnMods
+}
+
+int function TEMP_GetDamageFlagsFromProjectile( entity projectile )
+{
+ var damageFlagsString = projectile.ProjectileGetWeaponInfoFileKeyField( "damage_flags" )
+ if ( damageFlagsString == null )
+ return 0
+ expect string( damageFlagsString )
+
+ return TEMP_GetDamageFlagsFromString( damageFlagsString )
+}
+
+int function TEMP_GetDamageFlagsFromString( string damageFlagsString )
+{
+ int damageFlags = 0
+
+ array<string> damageFlagTokens = split( damageFlagsString, "|" )
+ foreach ( token in damageFlagTokens )
+ {
+ damageFlags = damageFlags | getconsttable()[strip(token)]
+ }
+
+ return damageFlags
+}
+
+#if SERVER
+function PROTO_InitTrackedProjectile( entity projectile )
+{
+ // HACK: accessing ProjectileGetWeaponInfoFileKeyField or ProjectileGetWeaponClassName during CodeCallback_OnSpawned causes a code assert
+ projectile.EndSignal( "OnDestroy" )
+ WaitFrame()
+
+ entity owner = projectile.GetOwner()
+
+ if ( !IsValid( owner ) || !owner.IsPlayer() )
+ return
+
+ int maxDeployed = projectile.GetProjectileWeaponSettingInt( eWeaponVar.projectile_max_deployed )
+ if ( maxDeployed != 0 )
+ {
+ AddToScriptManagedEntArray( owner.s.activeTrapArrayId, projectile )
+
+ array<entity> traps = GetScriptManagedEntArray( owner.s.activeTrapArrayId )
+ array<entity> sameTypeTrapEnts
+ foreach ( ent in traps )
+ {
+ if ( ent.ProjectileGetWeaponClassName() != projectile.ProjectileGetWeaponClassName() )
+ continue
+
+ sameTypeTrapEnts.append( ent )
+ }
+
+ int numToDestroy = sameTypeTrapEnts.len() - maxDeployed
+ if ( numToDestroy > 0 )
+ {
+ sameTypeTrapEnts.sort( CompareCreation )
+ foreach ( ent in sameTypeTrapEnts )
+ {
+ ent.Destroy()
+ numToDestroy--
+
+ if ( !numToDestroy )
+ break
+ }
+ }
+ }
+}
+
+
+function PROTO_CleanupTrackedProjectiles( entity player )
+{
+ array<entity> traps = GetScriptManagedEntArray( player.s.activeTrapArrayId )
+ foreach ( ent in traps )
+ {
+ ent.Destroy()
+ }
+}
+
+int function CompareCreation( entity a, entity b )
+{
+ if ( a.GetProjectileCreationTime() > b.GetProjectileCreationTime() )
+ return 1
+
+ return -1
+}
+
+int function CompareCreationReverse( entity a, entity b )
+{
+ if ( a.GetProjectileCreationTime() > b.GetProjectileCreationTime() )
+ return 1
+
+ return -1
+}
+
+void function PROTO_TrackedProjectile_OnPlayerRespawned( entity player )
+{
+ thread PROTO_TrackedProjectile_OnPlayerRespawned_Internal( player )
+}
+
+void function PROTO_TrackedProjectile_OnPlayerRespawned_Internal( entity player )
+{
+ player.EndSignal( "OnDeath" )
+
+ if ( player.s.inGracePeriod )
+ player.WaitSignal( "GracePeriodDone" )
+
+ entity ordnance = player.GetOffhandWeapon( OFFHAND_ORDNANCE )
+
+ array<entity> traps = GetScriptManagedEntArray( player.s.activeTrapArrayId )
+ foreach ( ent in traps )
+ {
+ if ( ordnance && ent.ProjectileGetWeaponClassName() == ordnance.GetWeaponClassName() )
+ continue
+
+ ent.Destroy()
+ }
+}
+
+function PROTO_PlayTrapLightEffect( entity ent, string tag, int team )
+{
+ asset ownerFx = ent.ProjectileGetWeaponInfoFileKeyFieldAsset( "trap_warning_owner_fx" )
+ if ( ownerFx != $"" )
+ {
+ entity ownerFxEnt = CreateServerEffect_Owner( ownerFx, ent.GetOwner() )
+ SetServerEffectControlPoint( ownerFxEnt, 0, FRIENDLY_COLOR )
+ StartServerEffectOnEntity( ownerFxEnt, ent, tag )
+ }
+
+ asset friendlyFx = ent.ProjectileGetWeaponInfoFileKeyFieldAsset( "trap_warning_friendly_fx" )
+ if ( friendlyFx != $"" )
+ {
+ entity friendlyFxEnt = CreateServerEffect_Friendly( friendlyFx, team )
+ SetServerEffectControlPoint( friendlyFxEnt, 0, FRIENDLY_COLOR_FX )
+ StartServerEffectOnEntity( friendlyFxEnt, ent, tag )
+ }
+
+ asset enemyFx = ent.ProjectileGetWeaponInfoFileKeyFieldAsset( "trap_warning_enemy_fx" )
+ if ( enemyFx != $"" )
+ {
+ entity enemyFxEnt = CreateServerEffect_Enemy( enemyFx, team )
+ SetServerEffectControlPoint( enemyFxEnt, 0, ENEMY_COLOR_FX )
+ StartServerEffectOnEntity( enemyFxEnt, ent, tag )
+ }
+}
+
+string ornull function GetCooldownBeepPrefix( weapon )
+{
+ var reloadBeepPrefix = weapon.GetWeaponInfoFileKeyField( "cooldown_sound_prefix" )
+ if ( reloadBeepPrefix == null )
+ return null
+
+ expect string( reloadBeepPrefix )
+
+ return reloadBeepPrefix
+}
+
+void function PROTO_DelayCooldown( entity weapon )
+{
+ weapon.s.nextCooldownTime = Time() + weapon.s.cooldownDelay
+}
+
+string function GetBeepSuffixForAmmo( int currentAmmo, int maxAmmo )
+{
+ float frac = float( currentAmmo ) / float( maxAmmo )
+
+ if ( frac >= 1.0 )
+ return "_full"
+
+ if ( frac >= 0.25 )
+ return ""
+
+ return "_low"
+}
+
+#endif //SERVER
+
+bool function PROTO_CanPlayerDeployWeapon( entity player )
+{
+ if ( player.IsPhaseShifted() )
+ return false
+
+ if ( player.ContextAction_IsActive() == true )
+ {
+ if ( player.IsZiplining() )
+ return true
+ else
+ return false
+ }
+
+ return true
+}
+
+#if SERVER
+void function PROTO_FlakCannonMissiles( entity projectile, float speed )
+{
+ projectile.EndSignal( "OnDestroy" )
+
+ float radius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosionradius )
+ vector velocity = projectile.GetVelocity()
+ vector currentPos = projectile.GetOrigin()
+ int team = projectile.GetTeam()
+
+ float waitTime = 0.1
+ float distanceInterval = speed * waitTime
+ int forwardDistanceChecks = int( ceil( distanceInterval / radius ) )
+ bool forceExplosion = false
+ while ( forceExplosion == false )
+ {
+ currentPos = projectile.GetOrigin()
+ for ( int i = 0; i < forwardDistanceChecks; i++ )
+ {
+ float frac = float( i ) / float (forwardDistanceChecks )
+ if ( PROTO_FlakCannon_HasNearbyEnemies( currentPos + velocity * waitTime * frac , team, radius ) )
+ {
+ if ( i == 0 )
+ {
+ forceExplosion = true
+ break
+ }
+ else
+ {
+ projectile.SetVelocity( velocity * ( frac - 0.05 ) )
+ break
+ }
+ }
+ }
+
+ if ( forceExplosion == false )
+ wait waitTime
+ }
+
+ projectile.MissileExplode()
+}
+
+bool function PROTO_FlakCannon_HasNearbyEnemies( vector origin, int team, float radius )
+{
+ float worldSpaceCenterBuffer = 200
+
+ array<entity> guys = GetPlayerArrayEx( "any", TEAM_ANY, team, origin, radius + worldSpaceCenterBuffer )
+ foreach ( guy in guys )
+ {
+ if ( IsAlive( guy ) && Distance( origin, guy.GetWorldSpaceCenter() ) < radius )
+ return true
+ }
+
+ array<entity> ai = GetNPCArrayEx( "any", TEAM_ANY, team, origin, radius + worldSpaceCenterBuffer )
+ foreach ( guy in ai )
+ {
+ if ( IsAlive( guy ) && Distance( origin, guy.GetWorldSpaceCenter() ) < radius )
+ return true
+ }
+
+ return false
+}
+#endif // #if SERVER
+
+void function GiveEMPStunStatusEffects( entity ent, float duration, float fadeoutDuration = 0.5, float slowTurn = EMP_SEVERITY_SLOWTURN, float slowMove = EMP_SEVERITY_SLOWMOVE)
+{
+ entity target = ent.IsTitan() ? ent.GetTitanSoul() : ent
+ int slowEffect = StatusEffect_AddTimed( target, eStatusEffect.turn_slow, slowTurn, duration, fadeoutDuration )
+ int turnEffect = StatusEffect_AddTimed( target, eStatusEffect.move_slow, slowMove, duration, fadeoutDuration )
+
+ #if SERVER
+ if ( ent.IsPlayer() )
+ {
+ ent.p.empStatusEffectsToClearForPhaseShift.append( slowEffect )
+ ent.p.empStatusEffectsToClearForPhaseShift.append( turnEffect )
+ }
+ #endif
+}
+
+#if DEV
+string ornull function FindEnumNameForValue( table searchTable, int searchVal )
+{
+ foreach( string keyname, int value in searchTable )
+ {
+ if ( value == searchVal )
+ return keyname;
+ }
+ return null
+}
+
+void function DevPrintAllStatusEffectsOnEnt( entity ent )
+{
+ printt( "Effects:", ent )
+ array<float> effects = StatusEffect_GetAll( ent )
+ int length = effects.len()
+ int found = 0;
+ for ( int idx = 0; idx < length; idx++ )
+ {
+ float severity = effects[idx];
+ if ( severity <= 0.0 )
+ continue
+ string ornull name = FindEnumNameForValue( eStatusEffect, idx )
+ Assert( name )
+ expect string( name )
+ printt( " eStatusEffect." + name + ": " + severity )
+ found++;
+ }
+ printt( found + " effects active.\n" );
+}
+#endif // #if DEV
+
+array<entity> function GetPrimaryWeapons( entity player )
+{
+ array<entity> primaryWeapons
+ array<entity> weapons = player.GetMainWeapons()
+ foreach ( weaponEnt in weapons )
+ {
+ int weaponType = weaponEnt.GetWeaponType()
+ if ( weaponType == WT_SIDEARM || weaponType == WT_ANTITITAN )
+ continue;
+
+ primaryWeapons.append( weaponEnt )
+ }
+ return primaryWeapons
+}
+
+array<entity> function GetSidearmWeapons( entity player )
+{
+ array<entity> sidearmWeapons
+ array<entity> weapons = player.GetMainWeapons()
+ foreach ( weaponEnt in weapons )
+ {
+ if ( weaponEnt.GetWeaponType() != WT_SIDEARM )
+ continue
+
+ sidearmWeapons.append( weaponEnt )
+ }
+ return sidearmWeapons
+}
+
+array<entity> function GetATWeapons( entity player )
+{
+ array<entity> atWeapons
+ array<entity> weapons = player.GetMainWeapons()
+ foreach ( weaponEnt in weapons )
+ {
+ if ( weaponEnt.GetWeaponType() != WT_ANTITITAN )
+ continue
+
+ atWeapons.append( weaponEnt )
+ }
+ return atWeapons
+}
+
+entity function GetPlayerFromTitanWeapon( entity weapon )
+{
+ entity titan = weapon.GetWeaponOwner()
+ entity player
+
+ if ( titan == null )
+ return null
+
+ if ( !titan.IsPlayer() )
+ player = titan.GetBossPlayer()
+ else
+ player = titan
+
+ return player
+}
+
+
+const asset CHARGE_SHOT_PROJECTILE = $"models/weapons/bullets/temp_triple_threat_projectile_large.mdl"
+
+const asset CHARGE_EFFECT_1P = $"P_ordnance_charge_st_FP" // $"P_wpn_defender_charge_FP"
+const asset CHARGE_EFFECT_3P = $"P_ordnance_charge_st" // $"P_wpn_defender_charge"
+const asset CHARGE_EFFECT_DLIGHT = $"defender_charge_CH_dlight"
+
+const string CHARGE_SOUND_WINDUP_1P = "Weapon_ChargeRifle_WindUp_1P"
+const string CHARGE_SOUND_WINDUP_3P = "Weapon_ChargeRifle_WindUp_3P"
+const string CHARGE_SOUND_WINDDOWN_1P = "Weapon_ChargeRifle_WindDown_1P"
+const string CHARGE_SOUND_WINDDOWN_3P = "Weapon_ChargeRifle_WindDown_3P"
+
+void function ChargeBall_Precache()
+{
+#if SERVER
+ PrecacheModel( CHARGE_SHOT_PROJECTILE )
+ PrecacheEffect( CHARGE_EFFECT_1P )
+ PrecacheEffect( CHARGE_EFFECT_3P )
+#endif // #if SERVER
+}
+
+void function ChargeBall_FireProjectile( entity weapon, vector position, vector direction, bool shouldPredict )
+{
+ weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 )
+
+ entity owner = weapon.GetWeaponOwner()
+ const float MISSILE_SPEED = 1200.0
+ const int CONTACT_DAMAGE_TYPES = (damageTypes.projectileImpact | DF_DOOM_FATALITY)
+ const int EXPLOSION_DAMAGE_TYPES = damageTypes.explosive
+ const bool DO_POPUP = false
+
+ if ( shouldPredict )
+ {
+ entity missile = weapon.FireWeaponMissile( position, direction, MISSILE_SPEED, CONTACT_DAMAGE_TYPES, EXPLOSION_DAMAGE_TYPES, DO_POPUP, shouldPredict )
+ if ( missile )
+ {
+ EmitSoundOnEntity( owner, "ShoulderRocket_Cluster_Fire_3P" )
+ missile.SetModel( CHARGE_SHOT_PROJECTILE )
+#if CLIENT
+ const ROCKETEER_MISSILE_EXPLOSION = $"xo_exp_death"
+ const ROCKETEER_MISSILE_SHOULDER_FX = $"wpn_mflash_xo_rocket_shoulder_FP"
+ entity owner = weapon.GetWeaponOwner()
+ vector origin = owner.OffsetPositionFromView( Vector(0, 0, 0), Vector(25, -25, 15) )
+ vector angles = owner.CameraAngles()
+ StartParticleEffectOnEntityWithPos( owner, GetParticleSystemIndex( ROCKETEER_MISSILE_SHOULDER_FX ), FX_PATTACH_EYES_FOLLOW, -1, origin, angles )
+#else // #if CLIENT
+ missile.SetProjectileImpactDamageOverride( 1440 )
+ missile.kv.damageSourceId = eDamageSourceId.charge_ball
+#endif // #else // #if CLIENT
+ }
+ }
+}
+
+bool function ChargeBall_ChargeBegin( entity weapon, string tagName )
+{
+#if CLIENT
+ if ( InPrediction() && !IsFirstTimePredicted() )
+ return true
+#endif // #if CLIENT
+
+ weapon.w.statusEffects.append( StatusEffect_AddEndless( weapon.GetWeaponOwner(), eStatusEffect.move_slow, 0.6 ) )
+ weapon.w.statusEffects.append( StatusEffect_AddEndless( weapon.GetWeaponOwner(), eStatusEffect.turn_slow, 0.35 ) )
+
+ weapon.PlayWeaponEffect( CHARGE_EFFECT_1P, CHARGE_EFFECT_3P, tagName )
+ weapon.PlayWeaponEffect( $"", CHARGE_EFFECT_DLIGHT, tagName )
+
+#if SERVER
+ StopSoundOnEntity( weapon, CHARGE_SOUND_WINDDOWN_3P )
+ entity weaponOwner = weapon.GetWeaponOwner()
+ if ( IsValid( weaponOwner ) )
+ {
+ if ( weaponOwner.IsPlayer() )
+ EmitSoundOnEntityExceptToPlayer( weapon, weaponOwner, CHARGE_SOUND_WINDUP_3P )
+ else
+ EmitSoundOnEntity( weapon, CHARGE_SOUND_WINDUP_3P )
+ }
+#else
+ StopSoundOnEntity( weapon, CHARGE_SOUND_WINDDOWN_1P )
+ EmitSoundOnEntity( weapon, CHARGE_SOUND_WINDUP_1P )
+#endif
+
+ return true
+}
+
+void function ChargeBall_ChargeEnd( entity weapon )
+{
+#if CLIENT
+ if ( InPrediction() && !IsFirstTimePredicted() )
+ return
+#endif
+
+ if ( IsValid( weapon.GetWeaponOwner() ) )
+ {
+ #if CLIENT
+ if ( InPrediction() && IsFirstTimePredicted() )
+ {
+ #endif
+
+ foreach ( effect in weapon.w.statusEffects )
+ {
+ StatusEffect_Stop( weapon.GetWeaponOwner(), effect )
+ }
+
+ #if CLIENT
+ }
+ #endif
+ }
+
+#if SERVER
+ StopSoundOnEntity( weapon, CHARGE_SOUND_WINDUP_3P )
+ entity weaponOwner = weapon.GetWeaponOwner()
+ if ( IsValid( weaponOwner ) )
+ {
+ if ( weaponOwner.IsPlayer() )
+ EmitSoundOnEntityExceptToPlayer( weapon, weaponOwner, CHARGE_SOUND_WINDDOWN_3P )
+ else
+ EmitSoundOnEntity( weapon, CHARGE_SOUND_WINDDOWN_3P )
+ }
+#else
+ StopSoundOnEntity( weapon, CHARGE_SOUND_WINDUP_1P )
+ EmitSoundOnEntity( weapon, CHARGE_SOUND_WINDDOWN_1P )
+#endif
+
+ ChargeBall_StopChargeEffects( weapon )
+}
+
+void function ChargeBall_StopChargeEffects( entity weapon )
+{
+ Assert( IsValid( weapon ) )
+ // weapon.StopWeaponEffect( CHARGE_EFFECT_1P, CHARGE_EFFECT_3P )
+ // weapon.StopWeaponEffect( CHARGE_EFFECT_3P, CHARGE_EFFECT_1P )
+ // weapon.StopWeaponEffect( CHARGE_EFFECT_DLIGHT, CHARGE_EFFECT_DLIGHT )
+ thread HACK_Deplayed_ChargeBall_StopChargeEffects( weapon )
+}
+
+void function HACK_Deplayed_ChargeBall_StopChargeEffects( entity weapon )
+{
+ weapon.EndSignal( "OnDestroy" )
+ wait 0.2
+ weapon.StopWeaponEffect( CHARGE_EFFECT_1P, CHARGE_EFFECT_3P )
+ weapon.StopWeaponEffect( CHARGE_EFFECT_3P, CHARGE_EFFECT_1P )
+ weapon.StopWeaponEffect( CHARGE_EFFECT_DLIGHT, CHARGE_EFFECT_DLIGHT )
+}
+
+float function ChargeBall_GetChargeTime()
+{
+ return 1.05
+}
+
+#if SERVER
+void function GivePlayerAmpedWeapon( entity player, string weaponName )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ int numWeapons = weapons.len()
+ if ( numWeapons == 0 )
+ return
+
+ //Figure out what weapon to take away.
+ //This is more complicated than it should be because of rules of what weapons can be in what slots, e.g. your anti-titan weapon can't be replaced by non anti-titan weapons
+ if ( HasWeapon( player, weaponName ) )
+ {
+ //Simplest case:
+ //Take away the currently existing version of the weapon you already have.
+ player.TakeWeaponNow( weaponName )
+ }
+ else
+ {
+ bool ampedWeaponIsAntiTitan = GetWeaponInfoFileKeyField_Global( weaponName, "weaponType" ) == "anti_titan"
+ if ( ampedWeaponIsAntiTitan )
+ {
+ foreach( weapon in weapons )
+ {
+ string currentWeaponClassName = weapon.GetWeaponClassName()
+ if ( GetWeaponInfoFileKeyField_Global( currentWeaponClassName, "weaponType" ) == "anti_titan" )
+ {
+ player.TakeWeaponNow( currentWeaponClassName )
+ break
+ }
+ }
+
+ unreachable //We had no anti-titan weapon? Shouldn't ever be possible
+
+ }
+ else
+ {
+ string currentActiveWeaponClassName = player.GetActiveWeapon().GetWeaponClassName()
+ if ( ShouldReplaceWeaponInFirstSlot( player, currentActiveWeaponClassName ) )
+ {
+ //Current weapon is anti_titan, but amped weapon we are trying to give is not. Just replace the weapon that is in the first slot.
+ //Assumes that weapon in first slot is not an anti-titan weapon
+ //We could get even fancier and look to see if the amped weapon is a primary weapon or a sidearm and replace the slot accordingly, but
+ //that makes it more complicated, plus there are cases where you can have no primary weapons/no side arms etc
+ string firstWeaponClassName = weapons[ 0 ].GetWeaponClassName()
+ Assert( GetWeaponInfoFileKeyField_Global( firstWeaponClassName, "weaponType" ) != "anti_titan" )
+ player.TakeWeaponNow( firstWeaponClassName )
+ }
+ else
+ {
+ player.TakeWeaponNow( currentActiveWeaponClassName )
+ }
+ }
+ }
+
+ array<string> burnMods = GetWeaponBurnMods( weaponName )
+ entity ampedWeapon = player.GiveWeapon( weaponName, burnMods )
+ ampedWeapon.SetWeaponPrimaryClipCount( ampedWeapon.GetWeaponPrimaryClipCountMax() ) //Needed for weapons that give a mod with extra clip size
+}
+
+bool function ShouldReplaceWeaponInFirstSlot( entity player, string currentActiveWeaponClassName )
+{
+ if ( GetWeaponInfoFileKeyField_Global( currentActiveWeaponClassName, "weaponType" ) == "anti_titan" ) //Active weapon is anti-titan weapon. Can't replace anti-titan weapon slot with non-anti-titan weapon
+ return true
+
+ if ( currentActiveWeaponClassName == player.GetOffhandWeapon( OFFHAND_ORDNANCE ).GetWeaponClassName() )
+ return true
+
+ return false
+
+}
+
+void function GivePlayerAmpedWeaponAndSetAsActive( entity player, string weaponName )
+{
+ GivePlayerAmpedWeapon( player, weaponName )
+ player.SetActiveWeaponByName( weaponName )
+}
+
+void function ReplacePlayerOffhand( entity player, string offhandName, array<string> mods = [] )
+{
+ player.TakeOffhandWeapon( OFFHAND_SPECIAL )
+ player.GiveOffhandWeapon( offhandName, OFFHAND_SPECIAL, mods )
+}
+
+void function ReplacePlayerOrdnance( entity player, string ordnanceName, array<string> mods = [] )
+{
+ player.TakeOffhandWeapon( OFFHAND_ORDNANCE )
+ player.GiveOffhandWeapon( ordnanceName, OFFHAND_ORDNANCE, mods )
+}
+
+void function PAS_CooldownReduction_OnKill( entity victim, entity attacker, var damageInfo )
+{
+ if ( !IsAlive( attacker ) || !IsPilot( attacker ) )
+ return
+
+ array<string> weaponMods = GetWeaponModsFromDamageInfo( damageInfo )
+
+ if ( GetCurrentPlaylistVarInt( "featured_mode_tactikill", 0 ) > 0 )
+ {
+ entity weapon = attacker.GetOffhandWeapon( OFFHAND_LEFT )
+
+ switch ( GetWeaponInfoFileKeyField_Global( weapon.GetWeaponClassName(), "cooldown_type" ) )
+ {
+ case "grapple":
+ attacker.SetSuitGrapplePower( attacker.GetSuitGrapplePower() + 100 )
+ break
+
+ case "ammo":
+ case "ammo_instant":
+ case "ammo_deployed":
+ case "ammo_timed":
+ int maxAmmo = weapon.GetWeaponPrimaryClipCountMax()
+ weapon.SetWeaponPrimaryClipCountNoRegenReset( maxAmmo )
+ break
+
+ case "chargeFrac":
+ weapon.SetWeaponChargeFraction( 0 )
+ break
+
+ // case "mp_ability_ground_slam":
+ // break
+
+ default:
+ Assert( false, weapon.GetWeaponClassName() + " needs to be updated to support cooldown_type setting" )
+ break
+ }
+ }
+ else
+ {
+ if ( !PlayerHasPassive( attacker, ePassives.PAS_CDR_ON_KILL ) && !weaponMods.contains( "tactical_cdr_on_kill" ) )
+ return
+
+ entity weapon = attacker.GetOffhandWeapon( OFFHAND_LEFT )
+
+ switch ( GetWeaponInfoFileKeyField_Global( weapon.GetWeaponClassName(), "cooldown_type" ) )
+ {
+ case "grapple":
+ attacker.SetSuitGrapplePower( attacker.GetSuitGrapplePower() + 25 )
+ break
+
+ case "ammo":
+ case "ammo_instant":
+ case "ammo_deployed":
+ case "ammo_timed":
+ int maxAmmo = weapon.GetWeaponPrimaryClipCountMax()
+ weapon.SetWeaponPrimaryClipCountNoRegenReset( min( maxAmmo, weapon.GetWeaponPrimaryClipCount() + ( maxAmmo / 4 ) ) )
+ break
+
+ case "chargeFrac":
+ weapon.SetWeaponChargeFraction( max( 0, weapon.GetWeaponChargeFraction() - 0.25 ) )
+ break
+
+ // case "mp_ability_ground_slam":
+ // break
+
+ default:
+ Assert( false, weapon.GetWeaponClassName() + " needs to be updated to support cooldown_type setting" )
+ break
+ }
+ }
+}
+
+void function DisableWeapons( entity player, array<string> excludeNames )
+{
+ array<entity> weapons = GetPlayerWeapons( player, excludeNames )
+ foreach ( weapon in weapons )
+ weapon.AllowUse( false )
+}
+
+void function EnableWeapons( entity player, array<string> excludeNames )
+{
+ array<entity> weapons = GetPlayerWeapons( player, excludeNames )
+ foreach ( weapon in weapons )
+ weapon.AllowUse( true )
+}
+
+array<entity> function GetPlayerWeapons( entity player, array<string> excludeNames )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ weapons.extend( player.GetOffhandWeapons() )
+
+ for ( int idx = weapons.len() - 1; idx > 0; idx-- )
+ {
+ foreach ( excludeName in excludeNames )
+ {
+ if ( weapons[idx].GetWeaponClassName() == excludeName )
+ weapons.remove( idx )
+ }
+ }
+
+ return weapons
+}
+
+void function WeaponAttackWave( entity ent, int projectileCount, entity inflictor, vector pos, vector dir, bool functionref( entity, int, entity, entity, vector, vector, int ) waveFunc )
+{
+ ent.EndSignal( "OnDestroy" )
+
+ entity weapon
+ entity projectile
+ int maxCount
+ float step
+ entity owner
+ int damageNearValueTitanArmor
+ int count = 0
+ array<vector> positions = []
+ vector lastDownPos
+ bool firstTrace = true
+
+ dir = <dir.x, dir.y, 0.0>
+ dir = Normalize( dir )
+ vector angles = VectorToAngles( dir )
+
+ if ( ent.IsProjectile() )
+ {
+ projectile = ent
+ string chargedPrefix = ""
+ if ( ent.proj.isChargedShot )
+ chargedPrefix = "charge_"
+
+ maxCount = expect int( ent.ProjectileGetWeaponInfoFileKeyField( chargedPrefix + "wave_max_count" ) )
+ step = expect float( ent.ProjectileGetWeaponInfoFileKeyField( chargedPrefix + "wave_step_dist" ) )
+ owner = ent.GetOwner()
+ damageNearValueTitanArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.damage_near_value_titanarmor )
+ }
+ else
+ {
+ weapon = ent
+ maxCount = expect int( ent.GetWeaponInfoFileKeyField( "wave_max_count" ) )
+ step = expect float( ent.GetWeaponInfoFileKeyField( "wave_step_dist" ) )
+ owner = ent.GetWeaponOwner()
+ damageNearValueTitanArmor = weapon.GetWeaponSettingInt( eWeaponVar.damage_near_value_titanarmor )
+ }
+
+ owner.EndSignal( "OnDestroy" )
+
+ for ( int i = 0; i < maxCount; i++ )
+ {
+ vector newPos = pos + dir * step
+
+ vector traceStart = pos
+ vector traceEndUnder = newPos
+ vector traceEndOver = newPos
+
+ if ( !firstTrace )
+ {
+ traceStart = lastDownPos + <0.0, 0.0, 80.0 >
+ traceEndUnder = <newPos.x, newPos.y, traceStart.z - 40.0 >
+ traceEndOver = <newPos.x, newPos.y, traceStart.z + step * 0.57735056839> // The over height is to cover the case of a sheer surface that then continues gradually upwards (like mp_box)
+ }
+ firstTrace = false
+
+ VortexBulletHit ornull vortexHit = VortexBulletHitCheck( owner, traceStart, traceEndOver )
+ if ( vortexHit )
+ {
+ expect VortexBulletHit( vortexHit )
+ entity vortexWeapon = vortexHit.vortex.GetOwnerWeapon()
+
+ if ( vortexWeapon && vortexWeapon.GetWeaponClassName() == "mp_titanweapon_vortex_shield" )
+ VortexDrainedByImpact( vortexWeapon, weapon, projectile, null ) // drain the vortex shield
+ else if ( IsVortexSphere( vortexHit.vortex ) )
+ VortexSphereDrainHealthForDamage( vortexHit.vortex, damageNearValueTitanArmor )
+
+ WaitFrame()
+ continue
+ }
+
+ //DebugDrawLine( traceStart, traceEndUnder, 0, 255, 0, true, 25.0 )
+ array ignoreArray = []
+ if ( IsValid( inflictor ) && inflictor.GetOwner() != null )
+ ignoreArray.append( inflictor.GetOwner() )
+
+ TraceResults forwardTrace = TraceLine( traceStart, traceEndUnder, ignoreArray, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS )
+ if ( forwardTrace.fraction == 1.0 )
+ {
+ //DebugDrawLine( forwardTrace.endPos, forwardTrace.endPos + <0.0, 0.0, -1000.0>, 255, 0, 0, true, 25.0 )
+ TraceResults downTrace = TraceLine( forwardTrace.endPos, forwardTrace.endPos + <0.0, 0.0, -1000.0>, ignoreArray, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS )
+ if ( downTrace.fraction == 1.0 )
+ break
+
+ entity movingGeo = null
+ if ( downTrace.hitEnt && downTrace.hitEnt.HasPusherRootParent() && !downTrace.hitEnt.IsMarkedForDeletion() )
+ movingGeo = downTrace.hitEnt
+
+ if ( !waveFunc( ent, projectileCount, inflictor, movingGeo, downTrace.endPos, angles, i ) )
+ return
+
+ lastDownPos = downTrace.endPos
+ pos = forwardTrace.endPos
+
+ WaitFrame()
+ continue
+ }
+ else
+ {
+ if ( IsValid( forwardTrace.hitEnt ) && (StatusEffect_Get( forwardTrace.hitEnt, eStatusEffect.pass_through_amps_weapon ) > 0) && !CheckPassThroughDir( forwardTrace.hitEnt, forwardTrace.surfaceNormal, forwardTrace.endPos ) )
+ break;
+ }
+
+ TraceResults upwardTrace = TraceLine( traceStart, traceEndOver, ignoreArray, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS )
+ //DebugDrawLine( traceStart, traceEndOver, 0, 0, 255, true, 25.0 )
+ if ( upwardTrace.fraction < 1.0 )
+ {
+ if ( IsValid( upwardTrace.hitEnt ) )
+ {
+ if ( upwardTrace.hitEnt.IsWorld() || upwardTrace.hitEnt.IsPlayer() || upwardTrace.hitEnt.IsNPC() )
+ break
+ }
+ }
+ else
+ {
+ TraceResults downTrace = TraceLine( upwardTrace.endPos, upwardTrace.endPos + <0.0, 0.0, -1000.0>, ignoreArray, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_BLOCK_WEAPONS )
+ if ( downTrace.fraction == 1.0 )
+ break
+
+ entity movingGeo = null
+ if ( downTrace.hitEnt && downTrace.hitEnt.HasPusherRootParent() && !downTrace.hitEnt.IsMarkedForDeletion() )
+ movingGeo = downTrace.hitEnt
+
+ if ( !waveFunc( ent, projectileCount, inflictor, movingGeo, downTrace.endPos, angles, i ) )
+ return
+
+ lastDownPos = downTrace.endPos
+ pos = forwardTrace.endPos
+ }
+
+ WaitFrame()
+ }
+}
+
+void function AddActiveThermiteBurn( entity ent )
+{
+ AddToScriptManagedEntArray( file.activeThermiteBurnsManagedEnts, ent )
+}
+
+array<entity> function GetActiveThermiteBurnsWithinRadius( vector origin, float dist, team = TEAM_ANY )
+{
+ return GetScriptManagedEntArrayWithinCenter( file.activeThermiteBurnsManagedEnts, team, origin, dist )
+}
+
+void function EMP_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ Elecriticy_DamagedPlayerOrNPC( ent, damageInfo, FX_EMP_BODY_HUMAN, FX_EMP_BODY_TITAN, EMP_SEVERITY_SLOWTURN, EMP_SEVERITY_SLOWMOVE )
+}
+
+void function VanguardEnergySiphon_DamagedPlayerOrNPC( entity ent, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( IsValid( attacker ) && attacker.GetTeam() == ent.GetTeam() )
+ return
+
+ Elecriticy_DamagedPlayerOrNPC( ent, damageInfo, FX_VANGUARD_ENERGY_BODY_HUMAN, FX_VANGUARD_ENERGY_BODY_TITAN, LASER_STUN_SEVERITY_SLOWTURN, LASER_STUN_SEVERITY_SLOWMOVE )
+}
+
+void function Elecriticy_DamagedPlayerOrNPC( entity ent, var damageInfo, asset humanFx, asset titanFx, float slowTurn, float slowMove )
+{
+ if ( !IsValid( ent ) )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ local inflictor = DamageInfo_GetInflictor( damageInfo )
+ if( !IsValid( inflictor ) )
+ return
+
+ // Do electrical effect on this ent that everyone can see if they are a titan
+ string tag = ""
+ asset effect
+
+ if ( ent.IsTitan() )
+ {
+ tag = "exp_torso_front"
+ effect = titanFx
+ }
+ else if ( IsStalker( ent ) || IsSpectre( ent ) )
+ {
+ tag = "CHESTFOCUS"
+ effect = humanFx
+ if ( !ent.ContextAction_IsActive() && IsAlive( ent ) && ent.IsInterruptable() )
+ {
+ ent.Anim_ScriptedPlayActivityByName( "ACT_STUNNED", true, 0.1 )
+ }
+ }
+ else if ( IsSuperSpectre( ent ) )
+ {
+ tag = "CHESTFOCUS"
+ effect = humanFx
+
+ if ( ent.GetParent() == null && !ent.ContextAction_IsActive() && IsAlive( ent ) && ent.IsInterruptable() )
+ {
+ ent.Anim_ScriptedPlayActivityByName( "ACT_STUNNED", true, 0.1 )
+ }
+ }
+ else if ( IsGrunt( ent ) )
+ {
+ tag = "CHESTFOCUS"
+ effect = humanFx
+ if ( !ent.ContextAction_IsActive() && IsAlive( ent ) && ent.IsInterruptable() )
+ {
+ ent.Anim_ScriptedPlayActivityByName( "ACT_STUNNED", true, 0.1 )
+ ent.EnableNPCFlag( NPC_PAIN_IN_SCRIPTED_ANIM )
+ }
+ }
+ else if ( IsPilot( ent ) )
+ {
+ tag = "CHESTFOCUS"
+ effect = humanFx
+ }
+ else if ( IsAirDrone( ent ) )
+ {
+ if ( GetDroneType( ent ) == "drone_type_marvin" )
+ return
+ tag = "HEADSHOT"
+ effect = humanFx
+ thread NpcEmpRebootPrototype( ent, damageInfo, humanFx, titanFx )
+ }
+ else if ( IsGunship( ent ) )
+ {
+ tag = "ORIGIN"
+ effect = titanFx
+ thread NpcEmpRebootPrototype( ent, damageInfo, humanFx, titanFx )
+ }
+
+ ent.Signal( "ArcStunned" )
+
+ if ( tag != "" )
+ {
+ local inflictor = DamageInfo_GetInflictor( damageInfo )
+ Assert( !(inflictor instanceof CEnvExplosion) )
+ if ( IsValid( inflictor ) )
+ {
+ float duration = EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MAX
+ if ( inflictor instanceof CBaseGrenade )
+ {
+ local entCenter = ent.GetWorldSpaceCenter()
+ local dist = Distance( DamageInfo_GetDamagePosition( damageInfo ), entCenter )
+ local damageRadius = inflictor.GetDamageRadius()
+ duration = GraphCapped( dist, damageRadius * 0.5, damageRadius, EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MIN, EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MAX )
+ }
+ thread EMP_FX( effect, ent, tag, duration )
+ }
+ }
+
+ if ( StatusEffect_Get( ent, eStatusEffect.destroyed_by_emp ) )
+ DamageInfo_SetDamage( damageInfo, ent.GetHealth() )
+
+ // Don't do arc beams to entities that are on the same team... except the owner
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( IsValid( attacker ) && attacker.GetTeam() == ent.GetTeam() && attacker != ent )
+ return
+
+ if ( ent.IsPlayer() )
+ {
+ thread EMPGrenade_EffectsPlayer( ent, damageInfo )
+ }
+ else if ( ent.IsTitan() )
+ {
+ EMPGrenade_AffectsShield( ent, damageInfo )
+ #if MP
+ GiveEMPStunStatusEffects( ent, 2.5, 1.0, slowTurn, slowMove )
+ #endif
+ thread EMPGrenade_AffectsAccuracy( ent )
+ }
+ else if ( ent.IsMechanical() )
+ {
+ #if MP
+ GiveEMPStunStatusEffects( ent, 2.5, 1.0, slowTurn, slowMove )
+ DamageInfo_ScaleDamage( damageInfo, 2.05 )
+ #endif
+ }
+ else if ( ent.IsHuman() )
+ {
+ #if MP
+ DamageInfo_ScaleDamage( damageInfo, 0.99 )
+ #endif
+ }
+
+ if ( inflictor instanceof CBaseGrenade )
+ {
+ if ( !ent.IsPlayer() || ent.IsTitan() ) //Beam should hit cloaked targets, when cloak is updated make IsCloaked() function.
+ EMPGrenade_ArcBeam( DamageInfo_GetDamagePosition( damageInfo ), ent )
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// HACK: might make sense to move this to code
+void function NpcEmpRebootPrototype( entity npc, var damageInfo, asset humanFx, asset titanFx )
+{
+ if ( !IsValid( npc ) )
+ return
+
+ npc.EndSignal( "OnDeath" )
+ npc.EndSignal( "OnDestroy" )
+
+ if ( !( "rebooting" in npc.s ) )
+ npc.s.rebooting <- null
+
+ if ( npc.s.rebooting ) // npc already knocked down and in rebooting process
+ return
+
+ float rebootTime
+ vector groundPos
+ local nearestNode
+ local neighborNodes
+ local groundNodePos
+ local origin = npc.GetOrigin()
+ local startOrigin = origin
+ local classname = npc.GetClassName()
+ local soundPowerDown
+ local soundPowerUp
+
+ //------------------------------------------------------
+ // Custom stuff depending on AI type
+ //------------------------------------------------------
+ switch ( classname )
+ {
+ case "npc_drone":
+ soundPowerDown = "Drone_Power_Down"
+ soundPowerUp = "Drone_Power_On"
+ rebootTime = DRONE_REBOOT_TIME
+ break
+ case "npc_gunship":
+ soundPowerDown = "Gunship_Power_Down"
+ soundPowerUp = "Gunship_Power_On"
+ rebootTime = GUNSHIP_REBOOT_TIME
+ break
+ default:
+ Assert( 0, "Unhandled npc type: " + classname )
+
+ }
+
+ //------------------------------------------------------
+ // NPC stunned and is rebooting
+ //------------------------------------------------------
+ npc.Signal( "OnStunned" )
+ npc.s.rebooting = true
+
+
+ //TODO: make drone/gunship slowly drift to the ground while rebooting
+ /*
+ groundPos = OriginToGround( origin )
+ groundPos += Vector( 0, 0, 32 )
+
+
+ //DebugDrawLine(origin, groundPos, 255, 0, 0, true, 15 )
+
+ //thread AssaultOrigin( drone, groundPos, 16 )
+ //thread PlayAnim( drone, "idle" )
+ */
+
+
+ thread EmpRebootFxPrototype( npc, humanFx, titanFx )
+ npc.EnableNPCFlag( NPC_IGNORE_ALL )
+ npc.SetNoTarget( true )
+ npc.EnableNPCFlag( NPC_DISABLE_SENSING ) // don't do traces to look for enemies or players
+
+ if ( IsAttackDrone( npc ) )
+ npc.SetAttackMode( false )
+
+ EmitSoundOnEntity( npc, soundPowerDown )
+
+ wait rebootTime
+
+ EmitSoundOnEntity( npc, soundPowerUp )
+ npc.DisableNPCFlag( NPC_IGNORE_ALL )
+ npc.SetNoTarget( false )
+ npc.DisableNPCFlag( NPC_DISABLE_SENSING ) // don't do traces to look for enemies or players
+
+ if ( IsAttackDrone( npc ) )
+ npc.SetAttackMode( true )
+
+ npc.s.rebooting = false
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// HACK: might make sense to move this to code
+function EmpRebootFxPrototype( npc, asset humanFx, asset titanFx )
+{
+ expect entity( npc )
+
+ if ( !IsValid( npc ) )
+ return
+
+ npc.EndSignal( "OnDeath" )
+ npc.EndSignal( "OnDestroy" )
+
+ string classname = npc.GetClassName()
+ vector origin
+ float delayDuration
+ entity fxHandle
+ asset fxEMPdamage
+ string fxTag
+ float rebootTime
+ string soundEMPdamage
+
+ //------------------------------------------------------
+ // Custom stuff depending on AI type
+ //------------------------------------------------------
+ switch ( classname )
+ {
+ case "npc_drone":
+ if ( GetDroneType( npc ) == "drone_type_marvin" )
+ return
+ fxEMPdamage = humanFx
+ fxTag = "HEADSHOT"
+ rebootTime = DRONE_REBOOT_TIME
+ soundEMPdamage = "Titan_Blue_Electricity_Cloud"
+ break
+ case "npc_gunship":
+ fxEMPdamage = titanFx
+ fxTag = "ORIGIN"
+ rebootTime = GUNSHIP_REBOOT_TIME
+ soundEMPdamage = "Titan_Blue_Electricity_Cloud"
+ break
+ default:
+ Assert( 0, "Unhandled npc type: " + classname )
+
+ }
+
+ //------------------------------------------------------
+ // Play Fx/Sound till reboot finishes
+ //------------------------------------------------------
+ fxHandle = ClientStylePlayFXOnEntity( fxEMPdamage, npc, fxTag, rebootTime )
+ EmitSoundOnEntity( npc, soundEMPdamage )
+
+ while ( npc.s.rebooting == true )
+ {
+ delayDuration = RandomFloatRange( 0.4, 1.2 )
+ origin = npc.GetOrigin()
+
+
+ EmitSoundAtPosition( npc.GetTeam(), origin, SOUND_EMP_REBOOT_SPARKS )
+ PlayFX( FX_EMP_REBOOT_SPARKS, origin )
+ PlayFX( FX_EMP_REBOOT_SPARKS, origin )
+
+ OnThreadEnd(
+ function() : ( fxHandle, npc, soundEMPdamage )
+ {
+ if ( IsValid( fxHandle ) )
+ fxHandle.Fire( "StopPlayEndCap" )
+ if ( IsValid( npc ) )
+ StopSoundOnEntity( npc, soundEMPdamage )
+ }
+ )
+
+ wait ( delayDuration )
+ }
+}
+
+function EMP_FX( asset effect, entity ent, string tag, float duration )
+{
+ if ( !IsAlive( ent ) )
+ return
+
+ ent.Signal( "EMP_FX" )
+ ent.EndSignal( "OnDestroy" )
+ ent.EndSignal( "OnDeath" )
+ ent.EndSignal( "StartPhaseShift" )
+ ent.EndSignal( "EMP_FX" )
+
+ bool isPlayer = ent.IsPlayer()
+
+ int fxId = GetParticleSystemIndex( effect )
+ int attachId = ent.LookupAttachment( tag )
+
+ entity fxHandle = StartParticleEffectOnEntity_ReturnEntity( ent, fxId, FX_PATTACH_POINT_FOLLOW, attachId )
+ fxHandle.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY | ENTITY_VISIBLE_TO_ENEMY
+ fxHandle.SetOwner( ent )
+
+ OnThreadEnd(
+ function() : ( fxHandle, ent )
+ {
+ if ( IsValid( fxHandle ) )
+ {
+ EffectStop( fxHandle )
+ }
+
+ if ( IsValid( ent ) )
+ StopSoundOnEntity( ent, "Titan_Blue_Electricity_Cloud" )
+ }
+ )
+
+ if ( !isPlayer )
+ {
+ EmitSoundOnEntity( ent, "Titan_Blue_Electricity_Cloud" )
+ wait duration
+ }
+ else
+ {
+ EmitSoundOnEntityExceptToPlayer( ent, ent, "Titan_Blue_Electricity_Cloud" )
+
+ var endTime = Time() + duration
+ bool effectsActive = true
+ while( endTime > Time() )
+ {
+ if ( ent.IsPhaseShifted() )
+ {
+ if ( effectsActive )
+ {
+ effectsActive = false
+ if ( IsValid( fxHandle ) )
+ EffectSleep( fxHandle )
+
+ if ( IsValid( ent ) )
+ StopSoundOnEntity( ent, "Titan_Blue_Electricity_Cloud" )
+ }
+ }
+ else if ( effectsActive == false )
+ {
+ EffectWake( fxHandle )
+ EmitSoundOnEntityExceptToPlayer( ent, ent, "Titan_Blue_Electricity_Cloud" )
+ effectsActive = true
+ }
+
+ WaitFrame()
+ }
+ }
+}
+
+function EMPGrenade_AffectsShield( entity titan, damageInfo )
+{
+ int shieldHealth = titan.GetTitanSoul().GetShieldHealth()
+ int shieldDamage = int( titan.GetTitanSoul().GetShieldHealthMax() * 0.5 )
+
+ titan.GetTitanSoul().SetShieldHealth( maxint( 0, shieldHealth - shieldDamage ) )
+
+ // attacker took down titan shields
+ if ( shieldHealth && !titan.GetTitanSoul().GetShieldHealth() )
+ {
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( attacker && attacker.IsPlayer() )
+ EmitSoundOnEntityOnlyToPlayer( attacker, attacker, "titan_energyshield_down" )
+ }
+}
+
+function EMPGrenade_AffectsAccuracy( npcTitan )
+{
+ npcTitan.EndSignal( "OnDestroy" )
+
+ npcTitan.kv.AccuracyMultiplier = 0.5
+ wait EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MAX
+ npcTitan.kv.AccuracyMultiplier = 1.0
+}
+
+
+function EMPGrenade_EffectsPlayer( entity player, damageInfo )
+{
+ player.Signal( "OnEMPPilotHit" )
+ player.EndSignal( "OnEMPPilotHit" )
+
+ if ( player.IsPhaseShifted() )
+ return
+
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ local dist = Distance( DamageInfo_GetDamagePosition( damageInfo ), player.GetWorldSpaceCenter() )
+ local damageRadius = 128
+ if ( inflictor instanceof CBaseGrenade )
+ damageRadius = inflictor.GetDamageRadius()
+ float frac = GraphCapped( dist, damageRadius * 0.5, damageRadius, 1.0, 0.0 )
+ local strength = EMP_GRENADE_PILOT_SCREEN_EFFECTS_MIN + ( ( EMP_GRENADE_PILOT_SCREEN_EFFECTS_MAX - EMP_GRENADE_PILOT_SCREEN_EFFECTS_MIN ) * frac )
+ float fadeoutDuration = EMP_GRENADE_PILOT_SCREEN_EFFECTS_FADE * frac
+ float duration = EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MIN + ( ( EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MAX - EMP_GRENADE_PILOT_SCREEN_EFFECTS_DURATION_MIN ) * frac ) - fadeoutDuration
+ local origin = inflictor.GetOrigin()
+
+ int dmgSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( dmgSource == eDamageSourceId.mp_weapon_proximity_mine || dmgSource == eDamageSourceId.mp_titanweapon_stun_laser )
+ {
+ strength *= 0.1
+ }
+
+ if ( player.IsTitan() )
+ {
+ // Hit player should do EMP screen effects locally
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanCockpitEMP", duration )
+
+ EMPGrenade_AffectsShield( player, damageInfo )
+
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanEMP", strength, duration, fadeoutDuration )
+ }
+ else
+ {
+ if ( IsCloaked( player ) )
+ player.SetCloakFlicker( 0.5, duration )
+
+ // duration = 0
+ // fadeoutDuration = 0
+
+ StatusEffect_AddTimed( player, eStatusEffect.emp, strength, duration, fadeoutDuration )
+ //DamageInfo_SetDamage( damageInfo, 0 )
+ }
+
+ GiveEMPStunStatusEffects( player, (duration + fadeoutDuration), fadeoutDuration)
+}
+
+function EMPGrenade_ArcBeam( grenadePos, ent )
+{
+ if ( !ent.IsPlayer() && !ent.IsNPC() )
+ return
+
+ Assert( IsValid( ent ) )
+ local lifeDuration = 0.5
+
+ // Control point sets the end position of the effect
+ entity cpEnd = CreateEntity( "info_placement_helper" )
+ SetTargetName( cpEnd, UniqueString( "emp_grenade_beam_cpEnd" ) )
+ cpEnd.SetOrigin( grenadePos )
+ DispatchSpawn( cpEnd )
+
+ entity zapBeam = CreateEntity( "info_particle_system" )
+ zapBeam.kv.cpoint1 = cpEnd.GetTargetName()
+ zapBeam.SetValueForEffectNameKey( EMP_GRENADE_BEAM_EFFECT )
+ zapBeam.kv.start_active = 0
+ zapBeam.SetOrigin( ent.GetWorldSpaceCenter() )
+ if ( !ent.IsMarkedForDeletion() ) // TODO: This is a hack for shipping. Should not be parenting to deleted entities
+ {
+ zapBeam.SetParent( ent, "", true, 0.0 )
+ }
+
+ DispatchSpawn( zapBeam )
+
+ zapBeam.Fire( "Start" )
+ zapBeam.Fire( "StopPlayEndCap", "", lifeDuration )
+ zapBeam.Kill_Deprecated_UseDestroyInstead( lifeDuration )
+ cpEnd.Kill_Deprecated_UseDestroyInstead( lifeDuration )
+}
+
+void function GetWeaponDPS( bool vsTitan = false )
+{
+ entity player = GetPlayerArray()[0]
+ entity weapon = player.GetActiveWeapon()
+
+ local fire_rate = weapon.GetWeaponInfoFileKeyField( "fire_rate" )
+ local burst_fire_count = weapon.GetWeaponInfoFileKeyField( "burst_fire_count" )
+ local burst_fire_delay = weapon.GetWeaponInfoFileKeyField( "burst_fire_delay" )
+
+ local damage_near_value = weapon.GetWeaponInfoFileKeyField( "damage_near_value" )
+ local damage_far_value = weapon.GetWeaponInfoFileKeyField( "damage_far_value" )
+
+ if ( vsTitan )
+ {
+ damage_near_value = weapon.GetWeaponInfoFileKeyField( "damage_near_value_titanarmor" )
+ damage_far_value = weapon.GetWeaponInfoFileKeyField( "damage_far_value_titanarmor" )
+ }
+
+ if ( burst_fire_count )
+ {
+ local timePerShot = 1 / fire_rate
+ local timePerBurst = (timePerShot * burst_fire_count) + burst_fire_delay
+ local burstPerSecond = 1 / timePerBurst
+
+ printt( timePerBurst )
+
+ printt( "DPS Near", (burstPerSecond * burst_fire_count) * damage_near_value )
+ printt( "DPS Far ", (burstPerSecond * burst_fire_count) * damage_far_value )
+ }
+ else
+ {
+ printt( "DPS Near", fire_rate * damage_near_value )
+ printt( "DPS Far ", fire_rate * damage_far_value )
+ }
+}
+
+
+void function GetTTK( string weaponRef, float health = 100.0 )
+{
+ local fire_rate = GetWeaponInfoFileKeyField_Global( weaponRef, "fire_rate" ).tofloat()
+ local burst_fire_count = GetWeaponInfoFileKeyField_Global( weaponRef, "burst_fire_count" )
+ if ( burst_fire_count != null )
+ burst_fire_count = burst_fire_count.tofloat()
+
+ local burst_fire_delay = GetWeaponInfoFileKeyField_Global( weaponRef, "burst_fire_delay" )
+ if ( burst_fire_delay != null )
+ burst_fire_delay = burst_fire_delay.tofloat()
+
+ local damage_near_value = GetWeaponInfoFileKeyField_Global( weaponRef, "damage_near_value" ).tointeger()
+ local damage_far_value = GetWeaponInfoFileKeyField_Global( weaponRef, "damage_far_value" ).tointeger()
+
+ local nearBodyShots = ceil( health / damage_near_value ) - 1
+ local farBodyShots = ceil( health / damage_far_value ) - 1
+
+ local delayAdd = 0
+ if ( burst_fire_count && burst_fire_count < nearBodyShots )
+ delayAdd += burst_fire_delay
+
+ printt( "TTK Near", (nearBodyShots * (1 / fire_rate)) + delayAdd, " (" + (nearBodyShots + 1) + ")" )
+
+
+ delayAdd = 0
+ if ( burst_fire_count && burst_fire_count < farBodyShots )
+ delayAdd += burst_fire_delay
+
+ printt( "TTK Far ", (farBodyShots * (1 / fire_rate)) + delayAdd, " (" + (farBodyShots + 1) + ")" )
+}
+
+array<string> function GetWeaponModsFromDamageInfo( var damageInfo )
+{
+ entity weapon = DamageInfo_GetWeapon( damageInfo )
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+
+ if ( IsValid( weapon ) )
+ {
+ return weapon.GetMods()
+ }
+ else if ( IsValid( inflictor ) )
+ {
+ if ( "weaponMods" in inflictor.s && inflictor.s.weaponMods )
+ {
+ array<string> temp
+ foreach ( string mod in inflictor.s.weaponMods )
+ {
+ temp.append( mod )
+ }
+
+ return temp
+ }
+ else if( inflictor.IsProjectile() )
+ return inflictor.ProjectileGetMods()
+ else if ( damageType & DF_EXPLOSION && inflictor.IsPlayer() && IsValid( inflictor.GetActiveWeapon() ) )
+ return inflictor.GetActiveWeapon().GetMods()
+ //Hack - Splash damage doesn't pass mod weapon through. This only works under the assumption that offhand weapons don't have mods.
+ }
+ return []
+}
+
+void function OnPlayerGetsNewPilotLoadout( entity player, PilotLoadoutDef loadout )
+{
+ if ( GetCurrentPlaylistVarInt( "featured_mode_amped_tacticals", 0 ) >= 1 )
+ {
+ player.GiveExtraWeaponMod( "amped_tacticals" )
+ }
+
+ if ( GetCurrentPlaylistVarInt( "featured_mode_all_grapple", 0 ) >= 1 )
+ {
+ player.GiveExtraWeaponMod( "all_grapple" )
+ }
+
+ if ( GetCurrentPlaylistVarInt( "featured_mode_all_phase", 0 ) >= 1 )
+ {
+ player.GiveExtraWeaponMod( "all_phase" )
+ }
+
+ SetPlayerCooldowns( player )
+}
+
+void function SetPlayerCooldowns( entity player )
+{
+ if ( player.IsTitan() )
+ return
+
+ array<int> offhandIndices = [ OFFHAND_LEFT, OFFHAND_RIGHT ]
+
+ foreach ( index in offhandIndices )
+ {
+ float lastUseTime = player.p.lastPilotOffhandUseTime[ index ]
+ float lastChargeFrac = player.p.lastPilotOffhandChargeFrac[ index ]
+ float lastClipFrac = player.p.lastPilotClipFrac[ index ]
+
+ if ( lastUseTime >= 0.0 )
+ {
+ entity weapon = player.GetOffhandWeapon( index )
+ if ( !IsValid( weapon ) )
+ continue
+
+ string weaponClassName = weapon.GetWeaponClassName()
+
+ switch ( GetWeaponInfoFileKeyField_Global( weaponClassName, "cooldown_type" ) )
+ {
+ case "grapple":
+ // GetPlayerSettingsField isn't working for moddable fields? - Bug 129567
+ float powerRequired = 100.0 // GetPlayerSettingsField( "grapple_power_required" )
+ float regenRefillDelay = 3.0 // GetPlayerSettingsField( "grapple_power_regen_delay" )
+ float regenRefillRate = 5.0 // GetPlayerSettingsField( "grapple_power_regen_rate" )
+ float suitPowerToRestore = powerRequired - player.p.lastSuitPower
+ float regenRefillTime = suitPowerToRestore / regenRefillRate
+
+ float regenStartTime = lastUseTime + regenRefillDelay
+
+ float newSuitPower = GraphCapped( Time() - regenStartTime, 0.0, regenRefillTime, player.p.lastSuitPower, powerRequired )
+
+ player.SetSuitGrapplePower( newSuitPower )
+ break
+
+ case "ammo":
+ case "ammo_instant":
+ case "ammo_deployed":
+ case "ammo_timed":
+ int maxAmmo = weapon.GetWeaponPrimaryClipCountMax()
+ float fireDuration = weapon.GetWeaponSettingFloat( eWeaponVar.fire_duration )
+ float regenRefillDelay = weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_start_delay )
+ float regenRefillRate = weapon.GetWeaponSettingFloat( eWeaponVar.regen_ammo_refill_rate )
+ int startingClipCount = int( lastClipFrac * maxAmmo )
+ int ammoToRestore = maxAmmo - startingClipCount
+ float regenRefillTime = ammoToRestore / regenRefillRate
+
+ float regenStartTime = lastUseTime + fireDuration + regenRefillDelay
+
+ int newAmmo = int( GraphCapped( Time() - regenStartTime, 0.0, regenRefillTime, startingClipCount, maxAmmo ) )
+
+ weapon.SetWeaponPrimaryClipCountAbsolute( newAmmo )
+ break
+
+ case "chargeFrac":
+ float chargeCooldownDelay = weapon.GetWeaponSettingFloat( eWeaponVar.charge_cooldown_delay )
+ float chargeCooldownTime = weapon.GetWeaponSettingFloat( eWeaponVar.charge_cooldown_time )
+ float regenRefillTime = lastChargeFrac * chargeCooldownTime
+ float regenStartTime = lastUseTime + chargeCooldownDelay
+
+ float newCharge = GraphCapped( Time() - regenStartTime, 0.0, regenRefillTime, lastChargeFrac, 0.0 )
+
+ weapon.SetWeaponChargeFraction( newCharge )
+ break
+
+ default:
+ printt( weaponClassName + " needs to be updated to support cooldown_type setting" )
+ break
+ }
+ }
+ }
+}
+
+void function ResetPlayerCooldowns( entity player )
+{
+ if ( player.IsTitan() )
+ return
+
+ array<int> offhandIndices = [ OFFHAND_LEFT, OFFHAND_RIGHT ]
+
+ foreach ( index in offhandIndices )
+ {
+ float lastUseTime = -99.0//player.p.lastPilotOffhandUseTime[ index ]
+ float lastChargeFrac = -1.0//player.p.lastPilotOffhandChargeFrac[ index ]
+ float lastClipFrac = 1.0//player.p.lastPilotClipFrac[ index ]
+
+ entity weapon = player.GetOffhandWeapon( index )
+ if ( !IsValid( weapon ) )
+ continue
+
+ string weaponClassName = weapon.GetWeaponClassName()
+
+ switch ( GetWeaponInfoFileKeyField_Global( weaponClassName, "cooldown_type" ) )
+ {
+ case "grapple":
+ // GetPlayerSettingsField isn't working for moddable fields? - Bug 129567
+ float powerRequired = 100.0 // GetPlayerSettingsField( "grapple_power_required" )
+ player.SetSuitGrapplePower( powerRequired )
+ break
+
+ case "ammo":
+ case "ammo_instant":
+ case "ammo_deployed":
+ case "ammo_timed":
+ int maxAmmo = weapon.GetWeaponPrimaryClipCountMax()
+ weapon.SetWeaponPrimaryClipCountAbsolute( maxAmmo )
+ break
+
+ case "chargeFrac":
+ weapon.SetWeaponChargeFraction( 1.0 )
+ break
+
+ default:
+ printt( weaponClassName + " needs to be updated to support cooldown_type setting" )
+ break
+ }
+ }
+}
+
+void function OnPlayerKilled( entity player, entity attacker, var damageInfo )
+{
+ StoreOffhandData( player )
+}
+
+void function StoreOffhandData( entity player, bool waitEndFrame = true )
+{
+ thread StoreOffhandDataThread( player, waitEndFrame )
+}
+
+void function StoreOffhandDataThread( entity player, bool waitEndFrame )
+{
+ if ( !IsValid( player ) )
+ return
+
+ player.EndSignal( "OnDestroy" )
+
+ if ( waitEndFrame )
+ WaitEndFrame() // Need to WaitEndFrame so clip counts can be updated if player is dying the same frame
+
+ array<int> offhandIndices = [ OFFHAND_LEFT, OFFHAND_RIGHT ]
+
+ // Reset all values for full cooldown
+ player.p.lastSuitPower = 0.0
+
+ foreach ( index in offhandIndices )
+ {
+ player.p.lastPilotOffhandChargeFrac[ index ] = 1.0
+ player.p.lastPilotClipFrac[ index ] = 1.0
+
+ player.p.lastTitanOffhandChargeFrac[ index ] = 1.0
+ player.p.lastTitanClipFrac[ index ] = 1.0
+ }
+
+ if ( player.IsTitan() )
+ return
+
+ foreach ( index in offhandIndices )
+ {
+ entity weapon = player.GetOffhandWeapon( index )
+ if ( !IsValid( weapon ) )
+ continue
+
+ string weaponClassName = weapon.GetWeaponClassName()
+
+ switch ( GetWeaponInfoFileKeyField_Global( weaponClassName, "cooldown_type" ) )
+ {
+ case "grapple":
+ player.p.lastSuitPower = player.GetSuitGrapplePower()
+ break
+
+ case "ammo":
+ case "ammo_instant":
+ case "ammo_deployed":
+ case "ammo_timed":
+
+ if ( player.IsTitan() )
+ {
+ if ( !weapon.IsWeaponRegenDraining() )
+ player.p.lastTitanClipFrac[ index ] = min( 1.0, weapon.GetWeaponPrimaryClipCount() / float( weapon.GetWeaponPrimaryClipCountMax() ) ) //Was returning greater than one with extraweaponmod timing.
+ else
+ player.p.lastTitanClipFrac[ index ] = 0.0
+ }
+ else
+ {
+ if ( !weapon.IsWeaponRegenDraining() )
+ player.p.lastPilotClipFrac[ index ] = min( 1.0, weapon.GetWeaponPrimaryClipCount() / float( weapon.GetWeaponPrimaryClipCountMax() ) ) //Was returning greater than one with extraweaponmod timing.
+ else
+ player.p.lastPilotClipFrac[ index ] = 0.0
+ }
+ break
+
+ case "chargeFrac":
+ if ( player.IsTitan() )
+ player.p.lastTitanOffhandChargeFrac[ index ] = weapon.GetWeaponChargeFraction()
+ else
+ player.p.lastPilotOffhandChargeFrac[ index ] = weapon.GetWeaponChargeFraction()
+ break
+
+ default:
+ printt( weaponClassName + " needs to be updated to support cooldown_type setting" )
+ break
+ }
+ }
+}
+#endif // #if SERVER
+
+void function PlayerUsedOffhand( entity player, entity offhandWeapon )
+{
+ array<int> offhandIndices = [ OFFHAND_LEFT, OFFHAND_RIGHT, OFFHAND_ANTIRODEO, OFFHAND_EQUIPMENT ]
+
+ foreach ( index in offhandIndices )
+ {
+ entity weapon = player.GetOffhandWeapon( index )
+ if ( !IsValid( weapon ) )
+ continue
+
+ if ( weapon != offhandWeapon )
+ continue
+
+ #if SERVER
+ if ( player.IsTitan() )
+ player.p.lastTitanOffhandUseTime[ index ] = Time()
+ else
+ player.p.lastPilotOffhandUseTime[ index ] = Time()
+
+ #if MP
+ string weaponName = offhandWeapon.GetWeaponClassName()
+ if ( weaponName != "mp_ability_grapple" ) // handled in CodeCallback_OnGrapple // nope, it's not (?)
+ {
+ string category
+ float duration
+ if ( index == OFFHAND_EQUIPMENT && player.IsTitan() )
+ {
+ category = "core"
+ duration = -1
+ }
+ else
+ {
+ category = ""
+ duration = Time() - offhandWeapon.GetNextAttackAllowedTimeRaw()
+ }
+ PIN_PlayerAbility( player, category, weaponName, {}, duration )
+ }
+ #endif
+ #endif // SERVER
+
+ #if HAS_TITAN_TELEMETRY && CLIENT
+ ClTitanHints_ClearOffhandHint( index )
+ #endif
+
+ #if HAS_TITAN_TELEMETRY && SERVER
+ TitanHints_NotifyUsedOffhand( index )
+ #endif
+
+ return
+ }
+}
+
+RadiusDamageData function GetRadiusDamageDataFromProjectile( entity projectile, entity owner )
+{
+ RadiusDamageData radiusDamageData
+
+ radiusDamageData.explosionDamage = -1
+ radiusDamageData.explosionDamageHeavyArmor = -1
+
+ if ( owner.IsNPC() )
+ {
+ radiusDamageData.explosionDamage = projectile.GetProjectileWeaponSettingInt( eWeaponVar.npc_explosion_damage )
+ radiusDamageData.explosionDamageHeavyArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.npc_explosion_damage_heavy_armor )
+ }
+
+ if ( radiusDamageData.explosionDamage == -1 )
+ radiusDamageData.explosionDamage = projectile.GetProjectileWeaponSettingInt( eWeaponVar.explosion_damage )
+
+ if ( radiusDamageData.explosionDamageHeavyArmor == -1 )
+ radiusDamageData.explosionDamageHeavyArmor = projectile.GetProjectileWeaponSettingInt( eWeaponVar.explosion_damage_heavy_armor )
+
+ radiusDamageData.explosionRadius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosionradius )
+ radiusDamageData.explosionInnerRadius = projectile.GetProjectileWeaponSettingFloat( eWeaponVar.explosion_inner_radius )
+
+ Assert( radiusDamageData.explosionRadius > 0, "Created RadiusDamageData with 0 radius" )
+ Assert( radiusDamageData.explosionDamage > 0 || radiusDamageData.explosionDamageHeavyArmor > 0, "Created RadiusDamageData with 0 damage" )
+ return radiusDamageData
+}
+
+#if SERVER
+void function Thermite_DamagePlayerOrNPCSounds( entity ent )
+{
+ if ( ent.IsTitan() )
+ {
+ if ( ent.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( ent, ent, "titan_thermiteburn_3p_vs_1p" )
+ EmitSoundOnEntityExceptToPlayer( ent, ent, "titan_thermiteburn_1p_vs_3p" )
+ }
+ else
+ {
+ EmitSoundOnEntity( ent, "titan_thermiteburn_1p_vs_3p" )
+ }
+ }
+ else
+ {
+ if ( ent.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( ent, ent, "flesh_thermiteburn_3p_vs_1p" )
+ EmitSoundOnEntityExceptToPlayer( ent, ent, "flesh_thermiteburn_1p_vs_3p" )
+ }
+ else
+ {
+ EmitSoundOnEntity( ent, "flesh_thermiteburn_1p_vs_3p" )
+ }
+ }
+}
+#endif
+
+#if SERVER
+void function RemoveThreatScopeColorStatusEffect( entity player )
+{
+ for ( int i = file.colorSwapStatusEffects.len() - 1; i >= 0; i-- )
+ {
+ entity owner = file.colorSwapStatusEffects[i].weaponOwner
+ if ( !IsValid( owner ) )
+ {
+ file.colorSwapStatusEffects.remove( i )
+ continue
+ }
+ if ( owner == player )
+ {
+ StatusEffect_Stop( player, file.colorSwapStatusEffects[i].statusEffectId )
+ file.colorSwapStatusEffects.remove( i )
+ }
+ }
+}
+
+void function AddThreatScopeColorStatusEffect( entity player )
+{
+ ColorSwapStruct info
+ info.weaponOwner = player
+ info.statusEffectId = StatusEffect_AddTimed( player, eStatusEffect.cockpitColor, COCKPIT_COLOR_THREAT, 100000, 0 )
+ file.colorSwapStatusEffects.append( info )
+}
+#endif
diff --git a/bobthebob.testing/mod.json b/bobthebob.testing/mod.json
new file mode 100644
index 000000000..f79cb091c
--- /dev/null
+++ b/bobthebob.testing/mod.json
@@ -0,0 +1,40 @@
+{
+ "ApiId" : "bobthebob.testing",
+ "Name" : "bobthebob.testing",
+ "Description" : "a place to put custom testing/debug functions and shit, shouldn't mess with game core behaviour at all",
+ "Authors" : [
+ "BobTheBob"
+ ],
+ "Contacts" : [
+ "BobTheBob#1150"
+ ],
+ "Version" : "0.1",
+ "CustomScripts": [
+ {
+ "Path": "sh_bobtestingfunctions_mp.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP"
+ },
+ {
+ "Path": "_bobtestingfunctions_sp.gnut",
+ "RunOn": "SERVER && SP"
+ },
+ {
+ "Path": "_bobtestingfunctions_mp.gnut",
+ "RunOn": "SERVER && MP",
+ "ServerPreCallback": "SvTestingMPInit"
+ },
+
+ {
+ "Path": "sh_bleedout_test.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ServerPreCallback": "BleedoutTest_Init",
+ "ClientPreCallback": "BleedoutTest_Init",
+ },
+
+ {
+ "Path": "sh_northstar_utils.gnut",
+ "RunOn": "CLIENT || SERVER || UI",
+ }
+
+ ]
+} \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/_bobtestingfunctions_mp.gnut b/bobthebob.testing/scripts/vscripts/_bobtestingfunctions_mp.gnut
new file mode 100644
index 000000000..b782243a4
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/_bobtestingfunctions_mp.gnut
@@ -0,0 +1,239 @@
+untyped
+globalize_all_functions
+
+void function SvTestingMPInit()
+{
+ Bleedout_Init()
+ AddCallback_EntitiesDidLoad( CreateSpawns )
+}
+
+void function CreateSpawns()
+{
+ //thread CreateSpawns_Threaded()
+}
+
+void function CreateSpawns_Threaded()
+{
+ WaitEndFrame() // wait for spawns to get cleared, game ctds if we don't do this
+ entity validSpawn = GetEntArrayByClass_Expensive( "info_spawnpoint_human" )[ 0 ]
+
+ for ( int i = 0; i < 50; i++ )
+ {
+ entity newSpawn = CreateEntity( "info_spawnpoint_human" )
+ newSpawn.SetOrigin( validSpawn.GetOrigin() )
+ DispatchSpawn( newSpawn )
+ }
+}
+
+void function TestSpawnpoints( bool titan = false )
+{
+ entity player = GetPlayerArray()[0]
+ array<entity> spawnpoints
+ if ( !titan )
+ spawnpoints = SpawnPoints_GetPilotStart( player.GetTeam() )
+ else
+ spawnpoints = SpawnPoints_GetTitanStart( player.GetTeam() )
+
+ SpawnPoints_InitRatings( player, player.GetTeam() )
+
+ foreach ( entity spawnpoint in spawnpoints )
+ spawnpoint.CalculateRating( titan ? TD_TITAN : TD_PILOT, player.GetTeam(), RandomFloat( 7.0 ) - 3.5, RandomFloat( 7.0 ) - 3.5 )
+
+ if ( !titan )
+ {
+ SpawnPoints_SortPilotStart()
+ spawnpoints = SpawnPoints_GetPilotStart( player.GetTeam() )
+ }
+ else
+ {
+ SpawnPoints_SortTitanStart()
+ spawnpoints = SpawnPoints_GetTitanStart( player.GetTeam() )
+ }
+
+ entity chosenPoint
+ foreach ( entity spawnpoint in spawnpoints )
+ if ( IsSpawnpointValid( spawnpoint, player.GetTeam() ) )
+ {
+ chosenPoint = spawnpoint
+ break
+ }
+
+ player.SetOrigin( chosenPoint.GetOrigin() )
+ player.SetAngles( chosenPoint.GetAngles() )
+
+ //SpawnPoints_DiscardRatings() // somehow the game seems to call this automatically so unneeded
+}
+
+bool function IsSpawnpointValid( entity spawnpoint, int team )
+{
+ if ( GameModeRemove( spawnpoint ) )
+ return false
+
+ // ultra temp
+ print( spawnpoint.GetTeam() )
+ if ( spawnpoint.GetTeam() == 0 )
+ return false
+
+ if ( spawnpoint.GetTeam() > 0 && spawnpoint.GetTeam() != team )
+ return false
+
+
+
+ return true
+}
+
+void function TestScoreEvent( entity player, int id )
+{
+ ScoreEvent event = ScoreEvent_FromId( id )
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_ScoreEvent", id, event.pointValue, event.displayType, player.GetEncodedEHandle(), event.earnMeterOwnValue, event.earnMeterEarnValue )
+}
+
+void function TestDrop( entity player )
+{
+ thread CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) )
+}
+
+void function WarpThroughSpawnpoints( string modeOverride = "" )
+{
+ if ( !modeOverride.len() )
+ modeOverride = GAMETYPE
+
+ entity player = GetPlayerArray()[0]
+
+ array<entity> spawnpoints = GetEntArrayByClass_Expensive( "info_hardpoint" ) //SpawnPoints_GetPilot()
+ foreach ( entity spawnpoint in spawnpoints )
+ {
+ //string gamemodeKey = "gamemode_" + modeOverride
+ //if ( spawnpoint.HasKey( gamemodeKey ) && ( spawnpoint.kv[ gamemodeKey ] == "0" || spawnpoint.kv[ gamemodeKey ] == "" ) )
+ // continue
+ //
+ //if ( !spawnpoint.HasKey( gamemodeKey ) )
+ // continue
+
+ if ( spawnpoint.HasKey( "hardpointGroup" ) )
+ print( spawnpoint.kv.hardpointGroup )
+
+ player.SetOrigin( spawnpoint.GetOrigin() )
+ player.SetAngles( spawnpoint.GetAngles() )
+
+ wait 0.5
+ }
+}
+
+void function AttemptSpawnBuddyTitan()
+{
+ PrecacheModel( $"models/titans/buddy/titan_buddy.mdl" )
+ PrecacheModel( $"models/weapons/arms/buddypov.mdl" )
+
+ entity player = GetPlayerArray()[0]
+ entity titan = CreateNPCTitan( "titan_buddy", player.GetTeam(), player.GetOrigin(), <0, 0, 0> )
+ SetSpawnOption_AISettings( titan, "npc_titan_buddy" )
+ SetSpawnOption_Weapon( titan, "mp_titanweapon_xo16_vanguard", [ ] )
+
+ DispatchSpawn( titan )
+
+ player.SetPetTitan( titan )
+ titan.SetOwner( player )
+ titan.SetUsable()
+}
+
+
+void function SetPlayerCameraToHead()
+{
+ entity viewControl = CreateEntity( "point_viewcontrol" )
+ viewControl.kv.spawnflags = 56
+ DispatchSpawn( viewControl )
+
+ viewControl.SetParent( GetPlayerArray()[0], "headshot" )
+ viewControl.SetOrigin( < 4, 0, 1 > )
+ viewControl.SetAngles( < 0, 0, 0 > )
+ GetPlayerArray()[0].SetViewEntity( viewControl, true )
+}
+
+void function CreateTestControlPanel()
+{
+ entity player = GetPlayerArray()[0]
+
+ entity panel = CreateEntity( "prop_control_panel" )
+ panel.SetValueForModelKey( $"models/communication/terminal_usable_imc_01.mdl" )
+ panel.SetOrigin( player.GetOrigin() )
+ panel.SetAngles( player.GetAngles() )
+ panel.kv.solid = SOLID_VPHYSICS
+ SetTargetName( panel, "cpanel" )
+ DispatchSpawn( panel )
+
+ panel.SetModel( $"models/communication/terminal_usable_imc_01.mdl" )
+ panel.s.onPlayerFinishesUsing_func = TestOnPanelHacked
+
+}
+
+function TestOnPanelHacked( panel, player, success )
+{
+ expect entity( panel )
+ expect entity( player )
+ expect bool( success )
+
+ if ( !success )
+ return
+
+ print( panel + " was hacked by " + player )
+ PanelFlipsToPlayerTeamAndUsableByEnemies( panel, player )
+}
+
+void function TestFastball()
+{
+ PrecacheModel( $"models/titans/buddy/titan_buddy.mdl" )
+ RegisterSignal( "fastball_start_throw" )
+ RegisterSignal( "fastball_release" )
+ PrecacheParticleSystem( $"P_BT_eye_SM" )
+
+ entity player = GetPlayerArray()[0]
+
+ entity prop = CreatePropDynamic( $"models/titans/buddy/titan_buddy.mdl", player.GetOrigin(), player.GetAngles() )
+ thread PlayAnim( prop, "bt_beacon_fastball_throw_end" )
+
+ player.ContextAction_SetFastball()
+ FirstPersonSequenceStruct throwSequence
+ throwSequence.attachment = "REF"
+ throwSequence.useAnimatedRefAttachment = true
+ throwSequence.hideProxy = true
+ throwSequence.firstPersonAnim = "ptpov_beacon_fastball_throw_end"
+ //throwSequence.thirdPersonAnim = "pt_beacon_fastball_throw_end"
+ throwSequence.firstPersonBlendOutTime = 0.0
+
+ thread FirstPersonSequence( throwSequence, player, prop )
+ player.HolsterWeapon()
+
+ EmitSoundOnEntityOnlyToPlayer( player, player, "Music_Beacon_14_BTThrowThruFirstCrane" )
+
+ prop.WaitSignal( "fastball_start_throw" )
+ float duration = EmitSoundOnEntity( prop, "diag_sp_spoke1_BE117_04_01_mcor_bt" ) // trust me
+ vector eyeAngles = player.EyeAngles()
+
+ // particle effect
+ StartParticleEffectOnEntity( prop, GetParticleSystemIndex( $"P_BT_eye_SM" ), FX_PATTACH_POINT_FOLLOW, prop.LookupAttachment( "EYEGLOW" ) )
+ wait duration
+
+ prop.WaitSignal( "fastball_release" )
+ player.ContextAction_ClearFastball()
+ player.ClearParent()
+ ClearPlayerAnimViewEntity( player )
+ player.SetVelocity( AnglesToForward( eyeAngles ) * 1250 )
+ player.DeployWeapon()
+
+ wait 0.5
+ prop.Destroy()
+}
+
+void function TestPlanetExplosion() // won't look good until we can load textures from rpak for these models
+{
+ PrecacheModel( $"models/vistas/planet_ex_static.mdl" )
+ PrecacheModel( $"models/vistas/planet_explosion_animated.mdl" )
+
+ entity cam = GetEnt( "skybox_cam_level" )
+ cam.SetOrigin( < 7000, 7000, 7000 > ) // arbitrary point
+ CreatePropDynamic( $"models/vistas/planet_ex_static.mdl", cam.GetOrigin(), cam.GetAngles() )
+ entity explosion = CreatePropDynamic( $"models/vistas/planet_explosion_animated.mdl", cam.GetOrigin(), cam.GetAngles() )
+ thread PlayAnim( explosion, "planet_ex_ending" )
+} \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/_bobtestingfunctions_sp.gnut b/bobthebob.testing/scripts/vscripts/_bobtestingfunctions_sp.gnut
new file mode 100644
index 000000000..2ad814fe2
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/_bobtestingfunctions_sp.gnut
@@ -0,0 +1,13 @@
+globalize_all_functions
+
+void function TestPlanetExplosion()
+{
+ PrecacheModel( $"models/vistas/planet_ex_static.mdl" )
+ PrecacheModel( $"models/vistas/planet_explosion_animated.mdl" )
+
+ entity cam = GetEnt( "skybox_cam_level" )
+ cam.SetOrigin( < 7000, 7000, 7000 > ) // arbitrary point
+ CreatePropDynamic( $"models/vistas/planet_ex_static.mdl", cam.GetOrigin(), cam.GetAngles() )
+ entity explosion = CreatePropDynamic( $"models/vistas/planet_explosion_animated.mdl", cam.GetOrigin(), cam.GetAngles() )
+ thread PlayAnim( explosion, "planet_ex_ending" )
+} \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/lobby/sh_lobby.gnut b/bobthebob.testing/scripts/vscripts/lobby/sh_lobby.gnut
new file mode 100644
index 000000000..24436017f
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/lobby/sh_lobby.gnut
@@ -0,0 +1,356 @@
+globalize_all_functions
+
+const string PRIVATE_MATCH_PLAYLIST = "private_match"
+
+struct {
+ array<string> modes = [ // default modes in vanilla
+ "aitdm",
+ "tdm",
+ "cp",
+ "at",
+ "ctf",
+ "lts",
+ "ps",
+ "speedball",
+ "mfd",
+ "ttdm",
+ "fd_easy",
+ "fd_normal",
+ "fd_hard",
+ "fd_master",
+ "fd_insane"
+ ]
+
+ array<string> maps = [ // default maps in vanilla
+ "mp_forwardbase_kodai",
+ "mp_grave",
+ "mp_homestead",
+ "mp_thaw",
+ "mp_black_water_canal",
+ "mp_eden",
+ "mp_drydock",
+ "mp_crashsite3",
+ "mp_complex3",
+ "mp_angel_city",
+ "mp_colony02",
+ "mp_glitch",
+ "mp_relic02",
+ "mp_wargames",
+ "mp_rise",
+ "mp_lf_stacks",
+ "mp_lf_deck",
+ "mp_lf_meadow",
+ "mp_lf_traffic",
+ "mp_lf_township",
+ "mp_lf_uma"
+ ]
+} file
+
+void function AddPrivateMatchMode( string mode )
+{
+ if ( !file.modes.contains( mode ) )
+ file.modes.append( mode )
+
+ #if CLIENT
+ // call this on ui too so the client and ui states are the same
+ RunUIScript( "AddPrivateMatchMode", mode )
+ #endif
+}
+
+void function AddPrivateMatchMap( string map )
+{
+ if ( !file.maps.contains( map ) )
+ file.maps.append( map )
+
+ #if CLIENT
+ // call this on ui too so the client and ui states are the same
+ RunUIScript( "AddPrivateMatchMap", map )
+ #endif
+}
+
+array<string> function GetPrivateMatchModes()
+{
+ //array<string> modesArray
+ //
+ //int numModes = GetPlaylistGamemodesCount( PRIVATE_MATCH_PLAYLIST )
+ //for ( int modeIndex = 0; modeIndex < numModes; modeIndex++ )
+ //{
+ // modesArray.append( GetPlaylistGamemodeByIndex( PRIVATE_MATCH_PLAYLIST, modeIndex ) )
+ //}
+
+ //return modesArray
+
+ return file.modes
+}
+
+int function GetPrivateMatchModeIndex( string modeName )
+{
+ //int indexForName = 0
+ //
+ //int numModes = GetPlaylistGamemodesCount( PRIVATE_MATCH_PLAYLIST )
+ //for ( int modeIndex = 0; modeIndex < numModes; modeIndex++ )
+ //{
+ // if ( GetPlaylistGamemodeByIndex( PRIVATE_MATCH_PLAYLIST, modeIndex ) != modeName )
+ // continue
+ //
+ // indexForName = modeIndex;
+ // break
+ //}
+ //
+ //return indexForName
+
+ return file.modes.find( modeName )
+}
+
+
+array<string> function GetPrivateMatchMapsForMode( string modeName )
+{
+ //array<string> mapsArray
+ //
+ //int modeIndex = GetPrivateMatchModeIndex( modeName )
+ //int numMaps = GetPlaylistGamemodeByIndexMapsCount( PRIVATE_MATCH_PLAYLIST, modeIndex )
+ //for ( int mapIndex = 0; mapIndex < numMaps; mapIndex++ )
+ //{
+ // mapsArray.append( GetPlaylistGamemodeByIndexMapByIndex( PRIVATE_MATCH_PLAYLIST, modeIndex, mapIndex ) )
+ //}
+ //
+ //return mapsArray
+
+ array<string> maps
+
+ // use the private match playlist for this if the gamemode is in it already
+ int privatePlaylistModeIndex = GetPrivateMatchModeIndex( modeName )
+ if ( privatePlaylistModeIndex < GetPlaylistGamemodesCount( PRIVATE_MATCH_PLAYLIST ) )
+ {
+ for ( int i = 0; i < GetPlaylistGamemodeByIndexMapsCount( PRIVATE_MATCH_PLAYLIST, privatePlaylistModeIndex ); i++ )
+ maps.append( GetPlaylistGamemodeByIndexMapByIndex( PRIVATE_MATCH_PLAYLIST, privatePlaylistModeIndex, i ) )
+ }
+ else
+ {
+ int numMaps = GetPlaylistGamemodeByIndexMapsCount( modeName, 0 )
+ for ( int i = 0; i < numMaps; i++ )
+ maps.append( GetPlaylistGamemodeByIndexMapByIndex( modeName, 0, i ) )
+ }
+
+ return maps
+}
+
+// never called
+/*array<string> function GetPrivateMatchModesForMap( string mapName )
+{
+ array<string> modesArray
+
+ int numModes = GetPlaylistGamemodesCount( PRIVATE_MATCH_PLAYLIST )
+ for ( int modeIndex = 0; modeIndex < numModes; modeIndex++ )
+ {
+ int numMaps = GetPlaylistGamemodeByIndexMapsCount( PRIVATE_MATCH_PLAYLIST, modeIndex )
+ for ( int mapIndex = 0; mapIndex < numMaps; mapIndex++ )
+ {
+ if ( GetPlaylistGamemodeByIndexMapByIndex( PRIVATE_MATCH_PLAYLIST, modeIndex, mapIndex ) != mapName )
+ continue
+
+ modesArray.append( GetPlaylistGamemodeByIndex( PRIVATE_MATCH_PLAYLIST, modeIndex ) )
+ }
+ }
+
+ return modesArray
+}*/
+
+
+string function GetPrivateMatchMapForIndex( int index )
+{
+ array<string> mapsArray = GetPrivateMatchMaps()
+
+ if ( index >= mapsArray.len() )
+ return ""
+
+ return mapsArray[index]
+}
+
+string function GetPrivateMatchModeForIndex( int index )
+{
+ array<string> modesArray = GetPrivateMatchModes()
+
+ if ( index >= modesArray.len() )
+ return ""
+
+ return modesArray[index]
+}
+
+int function GetPrivateMatchMapIndex( string mapName )
+{
+ array<string> mapsArray = GetPrivateMatchMaps()
+ for ( int index = 0; index < mapsArray.len(); index++ )
+ {
+ if ( mapsArray[index] == mapName )
+ return index
+ }
+
+ return 0
+}
+/*
+int function GetPrivateMatchModeIndex( string modeName )
+{
+ array<string> modesArray = GetPrivateMatchModes()
+ for ( int index = 0; index < modesArray.len(); index++ )
+ {
+ if ( modesArray[index] == modeName )
+ return index
+ }
+
+ return 0
+}
+*/
+
+array<string> function GetPrivateMatchMaps()
+{
+ //array<string> mapsArray
+ //
+ //int numModes = GetPlaylistGamemodesCount( PRIVATE_MATCH_PLAYLIST )
+ //for ( int modeIndex = 0; modeIndex < numModes; modeIndex++ )
+ //{
+ // int numMaps = GetPlaylistGamemodeByIndexMapsCount( PRIVATE_MATCH_PLAYLIST, modeIndex )
+ // for ( int mapIndex = 0; mapIndex < numMaps; mapIndex++ )
+ // {
+ // string mapName = GetPlaylistGamemodeByIndexMapByIndex( PRIVATE_MATCH_PLAYLIST, modeIndex, mapIndex )
+ // if ( mapsArray.contains( mapName ) )
+ // continue
+ //
+ // mapsArray.append( mapName )
+ // }
+ //}
+ //
+ //return mapsArray
+
+ return file.maps
+}
+
+
+
+array<string> function GetPlaylistMaps( string playlistName )
+{
+ array<string> mapsArray
+
+ int numModes = GetPlaylistGamemodesCount( playlistName )
+ for ( int modeIndex = 0; modeIndex < numModes; modeIndex++ )
+ {
+ int numMaps = GetPlaylistGamemodeByIndexMapsCount( playlistName, modeIndex )
+ for ( int mapIndex = 0; mapIndex < numMaps; mapIndex++ )
+ {
+ string mapName = GetPlaylistGamemodeByIndexMapByIndex( playlistName, modeIndex, mapIndex )
+ if ( mapsArray.contains( mapName ) )
+ continue
+
+ mapsArray.append( mapName )
+ }
+ }
+
+ return mapsArray
+}
+
+
+bool function MapSettings_SupportsTitans( string mapName )
+{
+ if ( mapName.find( "mp_lf_") != null )
+ return false
+
+ if ( mapName.find( "coliseum" ) != null )
+ return false;
+
+ return true
+}
+
+bool function MapSettings_SupportsAI( string mapName )
+{
+ if ( mapName.find( "mp_lf_") != null )
+ return false
+
+ if ( mapName.find( "coliseum" ) != null )
+ return false;
+
+ return true
+}
+
+
+bool function ModeSettings_RequiresTitans( string modeName )
+{
+ switch ( modeName )
+ {
+ case "lts":
+ return true
+ }
+
+ return false
+}
+
+bool function ModeSettings_RequiresAI( string modeName )
+{
+ switch ( modeName )
+ {
+ case "aitdm":
+ case "at":
+ return true
+ }
+
+ return false
+}
+
+#if !CLIENT
+string function PrivateMatch_GetSelectedMap()
+{
+ var mapIndex = level.ui.privatematch_map
+ string mapName = GetPrivateMatchMapForIndex( expect int(mapIndex) )
+
+ return mapName
+}
+
+
+string function PrivateMatch_GetSelectedMode()
+{
+ var modeIndex = level.ui.privatematch_mode
+ string modeName = GetPrivateMatchModeForIndex( expect int(modeIndex) )
+
+ return modeName
+}
+#endif
+
+bool function PrivateMatch_IsValidMapModeCombo( string mapName, string modeName )
+{
+ array<string> mapsForMode = GetPrivateMatchMapsForMode( modeName )
+
+ return mapsForMode.contains( mapName )
+}
+
+// end private match stuff
+
+int function Player_GetMaxMatchmakingDelay( entity player )
+{
+ // return GetCurrentPlaylistVarInt( "matchmaking_delay", 0 )
+ return 300
+}
+
+int function Player_GetRemainingMatchmakingDelay( entity player )
+{
+ int lastLeaveTime = player.GetPersistentVarAsInt( PERSISTENCE_LAST_LEAVE_TIME )
+
+ return Player_GetMaxMatchmakingDelay( player ) - (GetCurrentTimeForPersistence() - lastLeaveTime)
+}
+
+int function Player_NextAvailableMatchmakingTime( entity player )
+{
+ #if MP
+ int lastLeaveTime = player.GetPersistentVarAsInt( PERSISTENCE_LAST_LEAVE_TIME )
+ if ( GetCurrentTimeForPersistence() - lastLeaveTime < Player_GetMaxMatchmakingDelay( player ) )
+ {
+ return Player_GetRemainingMatchmakingDelay( player )
+ }
+ #endif
+
+ return 0
+}
+
+int function GetCurrentTimeForPersistence()
+{
+ // Returns the unix timestap offset to the timezone we want to use
+ return GetUnixTimestamp() + DAILY_RESET_TIME_ZONE_OFFSET * SECONDS_PER_HOUR
+}
diff --git a/bobthebob.testing/scripts/vscripts/mp/levels/mp_box.nut b/bobthebob.testing/scripts/vscripts/mp/levels/mp_box.nut
new file mode 100644
index 000000000..aa94be52f
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/mp/levels/mp_box.nut
@@ -0,0 +1,24 @@
+/*global function CodeCallback_MapInit
+global function SpawnGamemodeObjects
+
+void function CodeCallback_MapInit()
+{
+ AddCallback_EntitiesDidLoad( SpawnGamemodeObjects )
+}
+
+void function SpawnGamemodeObjects()
+{
+ thread SpawnGamemodeObjects_Threaded()
+
+}
+
+void function SpawnGamemodeObjects_Threaded()
+{
+ WaitEndFrame()
+ entity liveFireFlagSpawn = CreateEntity( "script_ref" )
+ liveFireFlagSpawn.kv.editorclass = "info_speedball_flag"
+ liveFireFlagSpawn.kv.origin = < 0.0, -382.0, 60.0 >
+ liveFireFlagSpawn.kv.angles = < 0, 0, 0 >
+ DispatchSpawn( liveFireFlagSpawn )
+ liveFireFlagSpawn.kv.editorclass = "info_speedball_flag"
+}*/ \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/mp/sh_revive.gnut b/bobthebob.testing/scripts/vscripts/mp/sh_revive.gnut
new file mode 100644
index 000000000..8caa2a821
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/mp/sh_revive.gnut
@@ -0,0 +1,19 @@
+#if SERVER
+global const REVIVE_ENABLED = true // currently not used anywhere
+global function ReviveEnabled
+
+bool function ReviveEnabled()
+{
+ return GetCurrentPlaylistVarInt( "player_revive_enabled", 0 ) == 1
+}
+#elseif true
+global const REVIVE_ENABLED = false // currently not used anywhere
+global function ReviveEnabled
+
+bool function ReviveEnabled()
+{
+ // client version of this doesn't work lol
+ return false
+ //return GetCurrentPlaylistVarInt( "player_revive_enabled", 0 ) == 1
+}
+#endif \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/sh_bleedout_test.gnut b/bobthebob.testing/scripts/vscripts/sh_bleedout_test.gnut
new file mode 100644
index 000000000..42727adc4
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/sh_bleedout_test.gnut
@@ -0,0 +1,34 @@
+global function BleedoutTest_Init
+
+void function BleedoutTest_Init()
+{
+ return // not in use rn
+
+ AddCallback_OnRegisteringCustomNetworkVars( RegisterBleedoutVars )
+
+ BleedoutShared_Init( 30.0, 3.0, -1.0, 1.0, 0.0, false, false )
+
+ #if SERVER
+ Bleedout_Init()
+ if ( ReviveEnabled() )
+ AddCallback_OnPlayerKilled( StartRevive )
+ #elseif CLIENT
+ BleedoutClient_Init()
+ #endif
+}
+
+#if SERVER
+void function StartRevive( entity victim, entity attacker, var damageInfo )
+{
+ DeathPackage_PlayerRevive( victim )
+ thread PlayerRevivesOrBleedsOut( victim )
+}
+#endif
+
+void function RegisterBleedoutVars()
+{
+ Remote_RegisterFunction( "ServerCallback_BLEEDOUT_StartFirstAidProgressBar" )
+ Remote_RegisterFunction( "ServerCallback_BLEEDOUT_StopFirstAidProgressBar" )
+ Remote_RegisterFunction( "ServerCallback_BLEEDOUT_ShowWoundedMarker" )
+ Remote_RegisterFunction( "ServerCallback_BLEEDOUT_HideWoundedMarker" )
+} \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/sh_bobtestingfunctions_mp.gnut b/bobthebob.testing/scripts/vscripts/sh_bobtestingfunctions_mp.gnut
new file mode 100644
index 000000000..124c7a541
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/sh_bobtestingfunctions_mp.gnut
@@ -0,0 +1,128 @@
+globalize_all_functions
+
+void function DumpPdefEnum( string pdefEnum )
+{
+ for ( int i = 0; i < PersistenceGetEnumCount( pdefEnum ); i++ )
+ print( PersistenceGetEnumItemNameForIndex( pdefEnum, i ) )
+}
+
+void function ListPlayers()
+{
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( player == null )
+ continue
+
+ print( "player " + player.GetPlayerName() + ": G" + player.GetGen() + "." + player.GetLevel() )
+ }
+}
+
+void function DumpPdefTable()
+{
+ foreach ( key0, value0 in shGlobalMP.playerStatVars )
+ {
+ foreach ( key1, value1 in value0 )
+ {
+ foreach ( key2, statData in value1 )
+ {
+ print ( statData.statVar )
+ }
+ }
+ }
+}
+
+void function DumpModels()
+{
+ for ( int i = 0; i < 2048; i++ )
+ if ( GetEntByIndex( i ) != null && GetEntByIndex( i ).GetModelName() != $"?" )
+ print( i + ": " + GetEntByIndex( i ).GetModelName() )
+}
+
+void function DumpScoreEvents()
+{
+ int i = 0;
+ while ( true )
+ {
+ ScoreEvent event
+ try event = ScoreEvent_FromId( i ) catch ( exception ) break
+
+ print( "event " + i + ":" )
+ print( "name: " + event.name )
+ print( "splashText: " + event.splashText )
+
+ // get string representation of displaytype
+ // if squirrel has a better way to do this i don't know it
+ string displayType
+
+ if ( event.displayType & eEventDisplayType.HIDDEN )
+ displayType += "HIDDEN, "
+ if ( event.displayType & eEventDisplayType.CENTER )
+ displayType += "CENTER, "
+ if ( event.displayType & eEventDisplayType.MEDAL )
+ displayType += "MEDAL, "
+ if ( event.displayType & eEventDisplayType.CALLINGCARD )
+ displayType += "CALLINGCARD, "
+ if ( event.displayType & eEventDisplayType.ATTRITION )
+ displayType += "ATTRITION, "
+ if ( event.displayType & eEventDisplayType.BIG )
+ displayType += "BIG, "
+ if ( event.displayType & eEventDisplayType.GAMEMODE )
+ displayType += "GAMEMODE, "
+ if ( event.displayType & eEventDisplayType.CHALLENGE )
+ displayType += "CHALLENGE, "
+ if ( event.displayType & eEventDisplayType.MEDAL_FORCED )
+ displayType += "MEDAL_FORCED, "
+ if ( event.displayType & eEventDisplayType.SHOW_SCORE )
+ displayType += "SHOW_SCORE, "
+
+ print( "displayType: " + displayType )
+ print( " " ) // newline
+
+ i++
+ }
+
+ print( "got " + i + " events!" )
+}
+
+void function DumpConversations()
+{
+ foreach ( string k, v in GetConversationToIndexTable() )
+ print( k )
+}
+
+#if CLIENT
+void function TestMarkRUI()
+{
+ var rui = CreateCockpitRui( $"ui/speedball_flag_marker.rpak", 200 )
+ RuiSetBool( rui, "isVisible", true )
+ RuiSetFloat3( rui, "pos", GetLocalViewPlayer().GetOrigin() )
+ RuiSetBool( rui, "playerIsCarrying", false )
+ RuiSetInt( rui, "teamRelation", TEAM_IMC )
+ RuiSetBool( rui, "isCarried", false )
+}
+
+void function TestObjectiveRUIHunted()
+{
+ var rui = CreateCockpitRui( $"ui/hunted_objective.rpak", 200 )
+ RuiSetGameTime( rui, "startTime", Time() )
+ RuiSetGameTime( rui, "endTime", Time() + 20.0 )
+ RuiSetString( rui, "objectiveTitleText", "#HUNTED_OBJECTIVE_TITLE" )
+ RuiSetFloat( rui, "blingDuration", 5.0 )
+ RuiSetBool( rui, "showAll", true )
+}
+
+void function TestObjectiveRUIFW()
+{
+ var rui = CreateCockpitRui( $"ui/fw_objective_text.rpak" )
+ RuiSetString( rui, "objective", "deez nuts" )
+
+ float time = Time() + 10.0
+ while ( time > Time() )
+ {
+ RuiSetString( rui, "objective", "deez nuts " + ( time - Time() ) )
+ WaitFrame()
+ }
+
+ RuiDestroy( rui )
+}
+#endif \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/sh_northstar_utils.gnut b/bobthebob.testing/scripts/vscripts/sh_northstar_utils.gnut
new file mode 100644
index 000000000..15eed9b21
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/sh_northstar_utils.gnut
@@ -0,0 +1,49 @@
+globalize_all_functions
+
+enum eNorthstarLobbyType
+{
+ PrivateMatchLobby, // normal vanilla private lobby
+ IntermissionLobby, // similar to tf1's intermission lobby, chooses next map automatically
+ CompetitiveLobby // similar to vanilla privates, but with ready up system
+}
+
+// whether the server is a modded, northstar server
+bool function IsNorthstarServer()
+{
+ bool isModded = true // TEMP for testing
+ try
+ {
+ // need this in a trycatch because the var might not exist atm
+ isModded = GetConVarInt( "northstar_is_modded_server" ) == 1
+ } catch ( ex ) {}
+
+ return isModded
+}
+
+// whether the game should return to the lobby on GameRules_EndMatch()
+bool function ShouldReturnToLobby()
+{
+ bool shouldReturnToLobby = false
+ try
+ {
+ // need this in a trycatch because the var might not exist atm
+ shouldReturnToLobby = GetConVarInt( "northstar_should_return_to_lobby" ) == 1
+ } catch ( ex ) {}
+
+ return shouldReturnToLobby
+}
+
+int function GetNorthstarLobbyType()
+{
+ if ( !IsNorthstarServer() )
+ return eNorthstarLobbyType.PrivateMatchLobby
+
+ int lobbyType = eNorthstarLobbyType.PrivateMatchLobby
+ try
+ {
+ // need this in a trycatch because the var might not exist atm
+ lobbyType = GetConVarInt( "northstar_lobby_type" )
+ } catch ( ex ) {}
+
+ return lobbyType
+} \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/ui/menu_dev.nut b/bobthebob.testing/scripts/vscripts/ui/menu_dev.nut
new file mode 100644
index 000000000..d9b9234b4
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/ui/menu_dev.nut
@@ -0,0 +1,707 @@
+untyped
+
+global function InitDevMenu
+global function GetActionBlocks
+global function SetDevMenu_SinglePlayer
+global function SetupDevCommand // for dev
+global function SetupDevFunc // for dev
+global function SetDevMenu_SpawnNPCWithWeapon
+global function RepeatLastDevCommand
+global function SetDevMenu_ArmedNPC
+global function UpdatePrecachedSPWeapons
+
+struct DevCommand
+{
+ string label
+ string command
+ var opParm
+ void functionref( var ) func
+ bool storeAsLastCommand = true
+}
+
+
+struct
+{
+ void functionref() devMenuFunc
+ void functionref( var ) devMenuFuncWithOpParm
+ var devMenuOpParm
+ array<var> buttons
+ array actionBlocks
+ array<DevCommand> devCommands
+ DevCommand& lastDevCommand
+ bool lastDevCommandAssigned
+ bool precachedWeapons
+} file
+
+void function OnOpenDevMenu()
+{
+ file.devMenuFunc = null
+ file.devMenuFuncWithOpParm = null
+ file.devMenuOpParm = null
+ if ( IsMultiplayer() )
+ SetDevMenu_MP()
+ else
+ SetDevMenu_Default()
+}
+
+void function InitDevMenu()
+{
+ var menu = GetMenu( "DevMenu" )
+
+ AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnOpenDevMenu )
+
+
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" )
+ AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
+
+ OnOpenDevMenu()
+
+ file.buttons = GetElementsByClassname( menu, "DevButtonClass" )
+ foreach ( button in file.buttons )
+ {
+ Hud_AddEventHandler( button, UIE_CLICK, OnDevButton_Activate )
+
+ RuiSetString( Hud_GetRui( button ), "buttonText", "" )
+ Hud_SetEnabled( button, false )
+ }
+
+}
+
+function UpdateDevMenuButtons()
+{
+ file.devCommands.clear()
+ if ( developer() == 0 )
+ return
+
+ if ( file.devMenuOpParm != null )
+ file.devMenuFuncWithOpParm( file.devMenuOpParm )
+ else
+ file.devMenuFunc()
+
+ foreach ( index, button in file.buttons )
+ {
+ int buttonID = int( Hud_GetScriptID( button ) )
+
+ if ( buttonID < file.devCommands.len() )
+ {
+ RuiSetString( Hud_GetRui( button ), "buttonText", file.devCommands[buttonID].label )
+ Hud_SetEnabled( button, true )
+ }
+ else
+ {
+ RuiSetString( Hud_GetRui( button ), "buttonText", "" )
+ Hud_SetEnabled( button, false )
+ }
+ }
+}
+
+void function SetDevMenu_Default()
+{
+ file.devMenuFunc = SetupDefaultDevCommands
+ UpdateDevMenuButtons()
+}
+
+
+void function SetDevMenu_MP()
+{
+ file.devMenuFunc = SetupDefaultDevCommandsMP
+ UpdateDevMenuButtons()
+}
+
+void function ChangeToThisMenu( void functionref() menuFunc )
+{
+ file.devMenuFunc = menuFunc
+ file.devMenuFuncWithOpParm = null
+ file.devMenuOpParm = null
+ UpdateDevMenuButtons()
+}
+
+void function ChangeToThisMenu_WithOpParm( void functionref( var ) menuFuncWithOpParm, opParm = null )
+{
+ file.devMenuFunc = null
+ file.devMenuFuncWithOpParm = menuFuncWithOpParm
+ file.devMenuOpParm = opParm
+ UpdateDevMenuButtons()
+}
+
+void function SetDevMenu_SinglePlayer( var _ )
+{
+ CloseAllInGameMenus()
+ AdvanceMenu( GetMenu( "SinglePlayerDevMenu" ), true )
+}
+
+void function SetupDefaultDevCommands()
+{
+ SetupDevFunc( "Frontier Defense", SetDevMenu_FrontierDefense )
+ // SetupDevFunc( "Difficulty", SetDevMenu_Difficulty )
+ SetupDevFunc( "Single Player", SetDevMenu_SinglePlayer )
+ if ( GetStartPointsForMap( GetActiveLevel() ).len() )
+ {
+ SetupDevFunc( "Start Points", SetDevMenu_StartPoints )
+ }
+ SetupDevFunc( "Level Commands", SetDevMenu_LevelCommands )
+
+ SetupRepeatLastDevCommand()
+ SetupDevFunc( "Spawn IMC NPC", SetDevMenu_AISpawn, 2 )
+ SetupDevFunc( "Spawn IMC Boss Titan", SetDevMenu_BossTitans )
+ SetupDevFunc( "Spawn Militia NPC", SetDevMenu_AISpawn, 3 )
+ SetupDevFunc( "Spawn Team 4 NPC", SetDevMenu_AISpawn, 4 )
+
+ if ( IsSingleplayer() )
+ {
+ SetupDevCommand( "Spawn BT", "script thread DEV_SpawnBTAtCrosshair( false )" )
+ SetupDevCommand( "Hotdrop BT", "script thread DEV_SpawnBTAtCrosshair( true )" )
+ }
+
+ SetupDevFunc( "Spawn Titan Weapon", SetDevMenu_TitanWeapons )
+ SetupDevFunc( "Spawn Pilot Weapons", SetDevMenu_PilotWeapons )
+ SetupDevFunc( "Spawn Pilot Offhands", SetDevMenu_PilotOffhands )
+
+ SetupDevFunc( "AI Commands", SetDevMenu_AICommands )
+ SetupDevCommand( "Toggle Model Viewer", "script thread ToggleModelViewer()" )
+ SetupDevCommand( "AI Titan Duel", "script DEV_AITitanDuel()" )
+ SetupDevCommand( "Free Titans for everybody", "script GiveAllTitans()" )
+
+ if ( IsSingleplayer() )
+ {
+ SetupDevCommand( "Checkpoint", "script CheckPoint_Forced()" )
+ SetupDevCommand( "Test Next Checkpoint Spawnpoint", "script TestDevSpawnPoint()" )
+ }
+
+ SetupDevCommand( "Disable NPCs", "script disable_npcs()" )
+ // SetupDevCommand( "Disable New NPCs", "script disable_new_npcs()" )
+
+ if ( IsMultiplayer() )
+ {
+ SetupDevCommand( "Swap the teams", "script teamswap()" )
+ SetupDevCommand( "Force time limit", "script ForceTimeLimitDone()" )
+ SetupDevCommand( "Force My Team Win", "script_client GetLocalClientPlayer().ClientCommand(\"ForceMyTeamWin\")" )
+ SetupDevCommand( "Force My Team Lose", "script_client GetLocalClientPlayer().ClientCommand(\"ForceMyTeamLose\")" )
+ SetupDevCommand( "Force Match End", "script ForceMatchEnd()" )
+ SetupDevCommand( "Force Draw", "script ForceDraw()" )
+ }
+
+ SetupDevCommand( "Toggle Friendly Highlights", "script DEV_ToggleFriendlyHighlight()" )
+ SetupDevCommand( "Export precache script", "script_ui Dev_CommandLineAddParm( \"-autoprecache\", \"\" ); script_ui Dev_CommandLineRemoveParm( \"" + STARTPOINT_DEV_STRING + "\" ); reload" )
+ // SetupDevCommand( "Toggle Blood Spray Decals", "script_client BloodSprayDecals_Toggle()" )
+
+ //SetupDevCommand( "PlaySpyglassVDU", "script ForcePlayConversationToAll(\"SpyglassVDU\")" )
+ //SetupDevCommand( "PlayGravesVDU", "script ForcePlayConversationToAll(\"GravesVDU\")" )
+ //SetupDevCommand( "PlayBliskVDU", "script ForcePlayConversationToAll(\"BliskVDU\")" )
+ //SetupDevCommand( "PlaySarahVDU", "script ForcePlayConversationToAll(\"SarahVDU\")" )
+ //SetupDevCommand( "PlayMacVDU", "script ForcePlayConversationToAll(\"MacVDU\")" )
+ //SetupDevCommand( "PlayBishVDU", "script ForcePlayConversationToAll(\"BishVDU\")" )
+ //SetupDevCommand( "PlayMCORGruntBattleRifleVDU", "script ForcePlayConversationToAll(\"MCORGruntBattleRifleVDU\")" )
+ //SetupDevCommand( "PlayMCORGruntAntiTitanVDU", "script ForcePlayConversationToAll(\"MCORGruntAntiTitanVDU\")" )
+ //SetupDevCommand( "PlayIMCSoldierBattleRifleVDU", "script ForcePlayConversationToAll(\"IMCSoldierBattleRifleVDU\")" )
+ SetupDevCommand( "Doom my titan", "script_client GetLocalViewPlayer().ClientCommand( \"DoomTitan\" )" )
+ SetupDevCommand( "DoF debug (ads)", "script_client ToggleDofDebug()" )
+
+ SetupDevCommand( "ToggleTitanCallInEffects", "script FlagToggle( \"EnableIncomingTitanDropEffects\" )" )
+ //SetupDevCommand( "TrailerTitanDrop", "script_client GetLocalViewPlayer().ClientCommand( \"TrailerTitanDrop\" )" )
+ //SetupDevCommand( "AI Chatter: aichat_callout_pilot_dev", "script playconvtest(\"aichat_callout_pilot_dev\")" )
+ SetupDevCommand( "Spawn IMC grunt", "SpawnViewGrunt " + TEAM_IMC )
+ SetupDevCommand( "Spawn Militia grunt", "SpawnViewGrunt " + TEAM_MILITIA )
+ SetupDevCommand( "Enable titan-always-executes-titan", "script FlagSet( \"ForceSyncedMelee\" )" )
+ //SetupDevCommand( "Display Embark times", "script DebugEmbarkTimes()" )
+ SetupDevCommand( "Kill All Titans", "script killtitans()" )
+ SetupDevCommand( "Kill All Minions", "script killminions()" )
+ if ( IsSingleplayer() )
+ SetupDevCommand( "Kill All Enemies", "script KillAllBadguys()" )
+
+ SetupDevCommand( "Export leveled_weapons.def / r2_weapons.fgd", "script thread LeveledWeaponDump()" )
+
+
+ if ( IsMultiplayer() )
+ {
+ SetupDevCommand( "Summon Players to player 0", "script summonplayers()" )
+ SetupDevCommand( "Display Titanfall spots", "script thread ShowAllTitanFallSpots()" )
+ SetupDevCommand( "Toggle check inside Titanfall Blocker", "script thread DevCheckInTitanfallBlocker()" )
+ SetupDevCommand( "Simulate Game Scoring", "script thread SimulateGameScore()" )
+ SetupDevCommand( "Test Dropship Intro Spawns with Bots", "script thread DebugTestDropshipStartSpawnsForAll()" )
+ SetupDevCommand( "Preview Dropship Spawn at this location", "script SetCustomPlayerDropshipSpawn()" )
+ SetupDevCommand( "Test Dropship Spawn at this location", "script thread DebugTestCustomDropshipSpawn()" )
+ SetupDevCommand( "Max Activity (Pilots)", "script SetMaxActivityMode(1)" )
+ SetupDevCommand( "Max Activity (Titans)", "script SetMaxActivityMode(2)" )
+ SetupDevCommand( "Max Activity (Conger Mode)", "script SetMaxActivityMode(4)" )
+ SetupDevCommand( "Max Activity (Disabled)", "script SetMaxActivityMode(0)" )
+ }
+ else
+ {
+ SetupDevCommand( "Reset Collectibles Progress (level)", "script Dev_ResetCollectiblesProgress_Level()" )
+ SetupDevCommand( "Reset Collectibles Progress (all)", "script ResetCollectiblesProgress_All()" )
+
+ SetupDevCommand( "BT Loadouts - Reset", "script SetBTLoadoutsUnlockedBitfield( 1 )" )
+ SetupDevCommand( "BT Loadouts - Unlock All", "script SetBTLoadoutsUnlockedBitfield( 65535 )" )
+ SetupDevCommand( "BT Loadouts - Spawn Unlock Pickup", "script SPTitanLoadout_SpawnAtCrosshairDEV( -1 )" )
+ }
+
+ SetupDevCommand( "Toggle Skybox View", "script thread ToggleSkyboxView()" )
+ //SetupDevCommand( "Toggle Bubble Shield", "ToggleBubbleShield" )
+ //SetupDevCommand( "Toggle Grenade Indicators", "script_client ToggleGrenadeIndicators()" )
+ SetupDevCommand( "Toggle HUD", "ToggleHUD" )
+ SetupDevCommand( "Toggle Offhand Low Recharge", "ToggleOffhandLowRecharge" )
+ SetupDevCommand( "Map Metrics Toggle", "script_client GetLocalClientPlayer().ClientCommand( \"toggle map_metrics 0 1 2 3\" )" )
+ SetupDevCommand( "Toggle Pain Death sound debug", "script TogglePainDeathDebug()" )
+ SetupDevCommand( "Jump Randomly Forever", "script_client thread JumpRandomlyForever()" )
+}
+
+
+void function SetupDefaultDevCommandsMP()
+{
+ SetupRepeatLastDevCommand()
+
+ SetupDevFunc( "Frontier Defense", SetDevMenu_FrontierDefense )
+
+ SetupDevFunc( "Spawn IMC NPC", SetDevMenu_AISpawn, 2 )
+ SetupDevFunc( "Spawn IMC Boss Titan", SetDevMenu_BossTitans )
+ SetupDevFunc( "Spawn Militia NPC", SetDevMenu_AISpawn, 3 )
+ SetupDevFunc( "Spawn Team 4 NPC", SetDevMenu_AISpawn, 4 )
+
+ SetupDevFunc( "Spawn Titan Weapon", SetDevMenu_TitanWeapons )
+ SetupDevFunc( "Spawn Pilot Weapons", SetDevMenu_PilotWeapons )
+ SetupDevFunc( "Spawn Pilot Offhands", SetDevMenu_PilotOffhands )
+
+ SetupDevFunc( "AI Commands", SetDevMenu_AICommands )
+ SetupDevCommand( "Toggle Model Viewer", "script thread ToggleModelViewer()" )
+ SetupDevCommand( "AI Titan Duel", "script DEV_AITitanDuel()" )
+ SetupDevCommand( "Free Titans for everybody", "script GiveAllTitans()" )
+
+ SetupDevCommand( "Disable NPCs", "script disable_npcs()" )
+ // SetupDevCommand( "Disable New NPCs", "script disable_new_npcs()" )
+
+ SetupDevCommand( "Swap the teams", "script teamswap()" )
+ SetupDevCommand( "Force time limit", "script ForceTimeLimitDone()" )
+ SetupDevCommand( "Force My Team Win", "script_client GetLocalClientPlayer().ClientCommand(\"ForceMyTeamWin\")" )
+ SetupDevCommand( "Force My Team Lose", "script_client GetLocalClientPlayer().ClientCommand(\"ForceMyTeamLose\")" )
+ SetupDevCommand( "Force Match End", "script ForceMatchEnd()" )
+ SetupDevCommand( "Force Draw", "script ForceDraw()" )
+
+ SetupDevCommand( "Toggle Friendly Highlights", "script DEV_ToggleFriendlyHighlight()" )
+ SetupDevCommand( "Export precache script", "script_ui Dev_CommandLineAddParm( \"-autoprecache\", \"\" ); script_ui Dev_CommandLineRemoveParm( \"" + STARTPOINT_DEV_STRING + "\" ); reload" )
+
+ SetupDevCommand( "Doom my titan", "script_client GetLocalViewPlayer().ClientCommand( \"DoomTitan\" )" )
+ SetupDevCommand( "DoF debug (ads)", "script_client ToggleDofDebug()" )
+
+ SetupDevCommand( "ToggleTitanCallInEffects", "script FlagToggle( \"EnableIncomingTitanDropEffects\" )" )
+
+ SetupDevCommand( "Spawn IMC grunt", "SpawnViewGrunt " + TEAM_IMC )
+ SetupDevCommand( "Spawn Militia grunt", "SpawnViewGrunt " + TEAM_MILITIA )
+
+ SetupDevCommand( "Enable titan-always-executes-titan", "script FlagSet( \"ForceSyncedMelee\" )" )
+
+ SetupDevCommand( "Kill All Titans", "script killtitans()" )
+ SetupDevCommand( "Kill All Minions", "script killminions()" )
+
+ SetupDevCommand( "Export leveled_weapons.def / r2_weapons.fgd", "script thread LeveledWeaponDump()" )
+
+ SetupDevCommand( "Summon Players to player 0", "script summonplayers()" )
+ SetupDevCommand( "Display Titanfall spots", "script thread ShowAllTitanFallSpots()" )
+ SetupDevCommand( "Toggle check inside Titanfall Blocker", "script thread DevCheckInTitanfallBlocker()" )
+ SetupDevCommand( "Simulate Game Scoring", "script thread SimulateGameScore()" )
+ SetupDevCommand( "Test Dropship Intro Spawns with Bots", "script thread DebugTestDropshipStartSpawnsForAll()" )
+ SetupDevCommand( "Preview Dropship Spawn at this location", "script SetCustomPlayerDropshipSpawn()" )
+ SetupDevCommand( "Test Dropship Spawn at this location", "script thread DebugTestCustomDropshipSpawn()" )
+ SetupDevCommand( "Max Activity (Pilots)", "script SetMaxActivityMode(1)" )
+ SetupDevCommand( "Max Activity (Titans)", "script SetMaxActivityMode(2)" )
+ SetupDevCommand( "Max Activity (Conger Mode)", "script SetMaxActivityMode(4)" )
+ SetupDevCommand( "Max Activity (Disabled)", "script SetMaxActivityMode(0)" )
+
+ SetupDevCommand( "Toggle Skybox View", "script thread ToggleSkyboxView()" )
+ SetupDevCommand( "Toggle HUD", "ToggleHUD" )
+ SetupDevCommand( "Toggle Offhand Low Recharge", "ToggleOffhandLowRecharge" )
+ SetupDevCommand( "Map Metrics Toggle", "script_client GetLocalClientPlayer().ClientCommand( \"toggle map_metrics 0 1 2 3\" )" )
+ SetupDevCommand( "Toggle Pain Death sound debug", "script TogglePainDeathDebug()" )
+ SetupDevCommand( "Jump Randomly Forever", "script_client thread JumpRandomlyForever()" )
+}
+
+
+void function SetupRepeatLastDevCommand()
+{
+ DevCommand cmd
+ cmd.label = "Repeat Last Dev Command"
+ cmd.func = RepeatLastDevCommand
+ cmd.storeAsLastCommand = false
+
+ file.devCommands.append( cmd )
+}
+
+void function SetDevMenu_LevelCommands( var _ )
+{
+ ChangeToThisMenu( SetupLevelDevCommands )
+}
+
+void function SetupLevelDevCommands()
+{
+ string activeLevel = GetActiveLevel()
+ if ( activeLevel == "" )
+ return
+
+ switch ( activeLevel )
+ {
+ case "mp_titan_rodeo":
+ SetupDevCommand( "Atlas titans", "script thread TitanTypes( \"titan_atlas_stickybomb\")" )
+ SetupDevCommand( "Ogre titans", "script thread TitanTypes( \"titan_ogre_meteor\")" )
+ SetupDevCommand( "Stryder titans", "script thread TitanTypes( \"titan_stryder_leadwall\")" )
+ break
+
+ case "model_viewer":
+ SetupDevCommand( "Toggle Rebreather Masks", "script ToggleRebreatherMasks()" )
+ break
+
+ case "sp_grunt_arena":
+ SetupDevCommand( "Toggle health pickups", "script ToggleHealthRegen(); reload" )
+ break
+ }
+}
+
+void function SetDevMenu_SpawnNPCWithWeapon( var parms )
+{
+ ChangeToThisMenu_WithOpParm( SetupMenu_SpawnNPCWithWeapons, parms )
+}
+
+
+void function SetDevMenu_StartPoints( var _ )
+{
+ string currentMap = GetActiveLevel()
+ array<StartPointCSV> foundStartPoints = GetStartPointsForMap( currentMap )
+// foreach ( index, startPointEnum in foundStartPoints )
+// {
+// table parms = { currentMap = currentMap, startPointEnum = startPointEnum }
+// SetupDevCommand( "#" + startPointEnum, SetDevMenu_SelectStartPointDifficulty, parms )
+// //SetupDevCommand( "#" + startPointEnum, "script PickStartPoint( \"" + currentMap + "\", \"" + startPointEnum + "\" )" )
+// }
+
+ CloseAllInGameMenus()
+ AdvanceMenu( GetMenu( "SinglePlayerDevMenu" ), true )
+ DisplayStartPointButtons( currentMap, foundStartPoints )
+}
+
+
+void function SetDevMenu_ActionBlocks()
+{
+ ChangeToThisMenu( SetupActionBlocks )
+}
+
+function DefineActionBlocks()
+{
+ file.actionBlocks = []
+
+ /* [Menu name] [action block name] [owner] [description] [load commands] */
+ AddActionBlock( "Week 1", "Timed Switch Panel Run", "Sean", "Test your wallrunning abilities by jumping on timed platforms", "playlist Load a map on the command line; map sp_platform_test01" )
+ AddActionBlock( "Week 1", "Energon Room", "Mackey", "Combat test arena. Collect all Energon Spheres to win", "playlist Load a map on the command line; map sp_abmac1" )
+ AddActionBlock( "Week 1", "Titan Buddy + Turret Columns", "Carlos", null, "playlist Load a map on the command line; map carlos_test" )
+ AddActionBlock( "Week 1", "Titan Maze", "Soupy", null, "playlist Load a map on the command line; map sp_act-block_maze" )
+ AddActionBlock( "Week 1", "Catch me if you Can", "Chin", "Catch up to a moving pilot and kill him to win", "launchplaylist catchmeifyoucan" )
+ AddActionBlock( "Week 1", "Jumping Puzzles", "ChadG", "Various pilot jumping puzzles with moving platforms", "playlist Load a map on the command line; mp_gamemode at; map mp_chad" )
+ AddActionBlock( "Week 1", "FLANKER BOOST loadout basics", "Brent", "Non-wallrunning pilot jumping basics", "playlist Load a map on the command line; mp_gamemode at; map mp_ab_test" )
+ AddActionBlock( "Week 1", "Flee Titan Attack by wallrunning", "Roger", null, "playlist Load a map on the command line; map sp_ab_flee" )
+ AddActionBlock( "Week 1", "Protect Grunt squad from Mortar fire", "Roger", null, "playlist Load a map on the command line; map sp_ab_vortex" )
+ AddActionBlock( "Week 1", "Catch me if you Can Part 2", "Soupy", null, "playlist catchmeifyoucan;mp_gamemode ps; map mp_catchme" )
+ AddActionBlock( "Week 2", "Environment Puzzles", "ChadG", "Get your titan to the exit by solving some puzzles", "playlist Load a map on the command line; mp_gamemode at; map mp_chad2" )
+// AddActionBlock( "Week 2", "Fun House - survive to the end", "Mo", null, "playlist Load a map on the command line; map mp_ab_funhouse" )
+ AddActionBlock( "Week 2", "Assassin Arena - boss fight with the Assassin", "Mo", null, "playlist Load a map on the command line; map sp_ab_assassin" )
+ AddActionBlock( "Week 2", "Creature Ship", "LumberJake", "Explore a crashed ship with mysterious cargo", "playlist Load a map on the command line; mp_gamemode at; map mp_actionblockjake01" )
+ AddActionBlock( "Week 2", "Titan/Pilot Puzzles", "RyanR", "Get your titan to the green room", "playlist Load a map on the command line; mp_gamemode at; map mp_ryanr_actionblock_01" )
+ AddActionBlock( "Week 2", "Titan/Pilot Core Combat", "Carlos", null, "playlist Load a map on the command line; map sp_ammo_pickup" )
+ AddActionBlock( "Week 2", "Titan Overwatch", "Roger", "Your sniping titan will cover you as you advance to the bunker", "playlist Load a map on the command line; map sp_ab_titanbuddy" )
+ AddActionBlock( "Week 2", "Rodeo Express", "Chin", "Use your titan to get you through a pilot hazard area", "playlist lava; mp_gamemode at; map mp_chin_rodeo_express" )
+ AddActionBlock( "Week 2", "Wallrun Gauntlet", "McCord", "Wallrun through the geo and don't fall to your death", "playlist Load a map on the command line; map sp_zipline_action_block01" )
+ AddActionBlock( "Week 2", "Titan Mortar Targeting Test", "Soupy", null, "playlist Load a map on the command line; map sp_mortar_targeting_test" )
+ AddActionBlock( "Week 2", "Tremors", "David", null, "playlist Load a map on the command line; map sp_tremors" )
+ AddActionBlock( "Week 2", "Titan Combat Blok", "Mackey", "Combat arena. Kill all enemies.", "playlist Load a map on the command line; map sp_abmac3" )
+ AddActionBlock( "Week 2", "Smart Targeted Switch Panels", "Sean", "Wallrun from wall to wall while activating switches", "playlist Load a map on the command line; map sp_platform_test02" )
+ AddActionBlock( "Week 3", "Combat Canyon", "Mo", "Kill enemies in the canyon and extract", "playlist Load a map on the command line; map sp_ab_ski" )
+ AddActionBlock( "Week 3", "Titan v Titan", "Roger", "Test Titan vs Titan combat against various titan AI", "playlist Load a map on the command line; map sp_ab_tvt" )
+ AddActionBlock( "Week 3", "Train Raid", "ChadG", "Board a speeding train and hack the explosives on board", "playlist Load a map on the command line; map sp_ab_trainride01" )
+ AddActionBlock( "Week 3", "Super Spectre Bros", "David", "Survive 6 waves against super spectres", "playlist Load a map on the command line; map mp_ab_super_spectre_bros" )
+ AddActionBlock( "Week 4", "Nightshot", "David", "Help your titan buddy hunt in the dark.", "playlist Load a map on the command line; map sp_ab_nightshot" )
+ //AddActionBlock( "Week 4", "Space Battle", "Mo", "Pilot a ship in space. \n -Play with Always run OFF. \n -Use low sensitivity. \n -Use bug_reproNum 1 to invert flight controls.\n -Use bug_reproNum 2 for PRO flight controls. (free look) \n -Use bug_reproNum 3 for inverted PRO flight controls.", "playlist Load a map on the command line; map sp_ab_week4" )
+ AddActionBlock( "Week 4", "Buddy Fight", "Mackey", "Arena fight with buddy Titan", "playlist Load a map on the command line; map sp_buddy_fight" )
+ AddActionBlock( "Week 4", "Fastball Special", "Slayback/McCord", "Use a new Titan ability to hurl yourself to new heights.", "playlist Load a map on the command line; map sp_fastball" )
+ AddActionBlock( "Week 4", "Time Travel Mechanic", "LumberJake", "Travel back and forth through time to complete your mission.", "playlist Load a map on the command line; map sp_actionblockjake02" )
+ AddActionBlock( "Week 5", "Titan Ability: Death Blossom", "Mo", "Use Up on D-Pad to use Death Blossom\n\nUse your new ability to defeat the enemies", "playlist Load a map on the command line; bug_reproNum 0; map sp_ab_blossom" )
+ AddActionBlock( "Week 5", "Titan Ability: Arc Blast", "Mo", "Use Down on D-Pad to use Arc Blast\n\nUse your new ability to defeat the enemies", "playlist Load a map on the command line; bug_reproNum 1; map sp_ab_blossom" )
+ AddActionBlock( "Week 5", "Titan Abilities: Death Blossom + Arc Blast", "Mo", "D-Pad Up = Death Blossom\nD-Pad Down = Arc Blast\n\nUse your new abilities to defeat the enemies", "playlist Load a map on the command line; bug_reproNum 2; map sp_ab_blossom" )
+ AddActionBlock( "Week 5", "Time Stasis Gun", "LumberJake", "Titan freezes enemies allowing the Pilot \nto do a one-shot kill", "playlist Load a map on the command line; map sp_actionblockjake03" )
+ AddActionBlock( "Week 5", "Fastball Mortar Battle", "McCord/Slayback", "Freeform Buddy Titan arena battle. \n - Fastball Special \n - Mortar Crews \n - Buddy Hibernation \n - Harvester Defense", "playlist Load a map on the command line; map sp_ab_mortar_battle01" )
+ AddActionBlock( "Week 5", "Zipline Gun", "ChadG", "Create permanent ziplines in the map", "playlist Load a map on the command line; mp_gamemode tdm; bug_reproNum 1234; map mp_angel_city" )
+ AddActionBlock( "Week 6", "Titan Hulk", "David", "Buddy Titan hulks out and throws things.", "playlist Load a map on the command line; map sp_ab_titan_thrower" )
+ AddActionBlock( "Week 6", "Player & Titan vs Enemy Titan", "Roger", "Bare bones Pilot & Auto Titan vs Enemy Titan", "playlist Load a map on the command line; map sp_ab_ptvt" )
+ AddActionBlock( "Week 6", "Pilot Stasis Gun", "Soupy", "Pilot has a Stasis gun to help a friendly Titan out", "playlist Load a map on the command line; map sp_ab_synergy" )
+ AddActionBlock( "Week 6", "Acid Rain", "LumberJake", "- Destroy 3 harvesters in a poison rain storm\n- Avoid poison rain by staying in your Titan or indoors\n- Collect powerups to get rain immunity", "map sp_actionblockjake04" )
+ AddActionBlock( "Week 6", "Freeform Hallway Fight", "McCord/Slayback", "Move through the tight hallways with your Titan Buddy.", "playlist Load a map on the command line; map sp_ab_hallway_fight01" )
+ AddActionBlock( "Week 6", "Stealth Town", "Carlos", "Stealth through a group of enemy titans", "playlist Load a map on the command line; map sp_titan_stealth" )
+ AddActionBlock( "Week 6", "Titan Attack Command", "Mo", "Command your buddy titan to attack a position.", "playlist Load a map on the command line; map sp_ab_break" )
+ AddActionBlock( "Week 7", "SP Shell", "Chad/McCord", "", "playlist Load a map on the command line; map sp_shell1" )
+ AddActionBlock( "Week 7", "Smart Pistol Progression", "Soupy", "Try different smart pistol mods in different combat situations", "playlist Load a map on the command line; map sp_ab_smart_pistol_ramp" )
+}
+
+
+function AddActionBlock( subMenu, actionBlockName, owner, description, command )
+{
+ local subMenuIndex = null
+ foreach( i, Table in file.actionBlocks )
+ {
+ if ( Table.name == subMenu )
+ subMenuIndex = i
+ }
+ if ( subMenuIndex == null )
+ {
+ file.actionBlocks.append( { name = subMenu, actionBlocks = [] } )
+ subMenuIndex = file.actionBlocks.len() - 1
+ }
+
+ local Table = {}
+ Table.name <- actionBlockName
+ Table.owner <- owner
+ Table.description <- description
+ Table.command <- command
+
+ file.actionBlocks[ subMenuIndex ].actionBlocks.append( Table )
+}
+
+function GetActionBlocks()
+{
+ DefineActionBlocks()
+ return file.actionBlocks
+}
+
+void function SetupActionBlocks()
+{
+ DefineActionBlocks()
+
+ // For the in-game dev menu we only add the current week of action blocks
+ foreach ( week, actionBlock in file.actionBlocks )
+ {
+ SetupDevFunc( "Week " + ( week + 1 ), SetDevMenu_Week, week )
+ }
+}
+
+void function SetDevMenu_Week( var week )
+{
+ ChangeToThisMenu_WithOpParm( SetupActionBlocksByWeek, week )
+}
+
+void function SetupActionBlocksByWeek( var week )
+{
+ foreach ( actionBlock in file.actionBlocks[ week ].actionBlocks )
+ {
+ SetupDevCommand( actionBlock.name + " - " + actionBlock.owner, expect string( actionBlock.command ) )
+ }
+}
+
+void function SetDevMenu_AICommands( var _ )
+{
+ ChangeToThisMenu( SetupAIDevCommands )
+}
+
+void function SetDevMenu_AISpawn( var enemy )
+{
+#if DEV
+ InitNpcSettingsFileNamesForDevMenu()
+ ChangeToThisMenu_WithOpParm( SetupSpawnAIButtons, enemy )
+#endif
+}
+
+void function SetDevMenu_BossTitans( var _ )
+{
+#if DEV
+ InitNpcSettingsFileNamesForDevMenu()
+ ChangeToThisMenu( SetupSpawnBossTitans )
+#endif
+}
+
+void function SetDevMenu_FrontierDefense( var _ )
+{
+ #if DEV
+ thread ChangeToThisMenu( SetupFrontierDefense )
+ #endif
+}
+
+void function SetDevMenu_TitanWeapons( var _ )
+{
+#if DEV
+ thread ChangeToThisMenu_PrecacheWeapons( SetupTitanWeapon )
+#endif
+}
+
+void function SetDevMenu_ArmedNPC( var data )
+{
+#if DEV
+ thread ChangeToThisMenu_PrecacheWeapons_WithOpParm( SetupSpawnArmedNPC, data )
+#endif
+}
+
+void function SetDevMenu_PilotWeapons( var _ )
+{
+#if DEV
+ thread ChangeToThisMenu_PrecacheWeapons_WithOpParm( SetupPilotWeaponsFromFields, "not_set" )
+#endif
+}
+
+void function SetDevMenu_PilotOffhands( var _ )
+{
+#if DEV
+ thread ChangeToThisMenu_PrecacheWeapons_WithOpParm( SetupPilotWeaponsFromFields, "offhand" )
+#endif
+}
+
+void function ChangeToThisMenu_PrecacheWeapons( void functionref() menuFunc )
+{
+ waitthread PrecacheWeaponsIfNecessary()
+
+ file.devMenuFunc = menuFunc
+ file.devMenuFuncWithOpParm = null
+ file.devMenuOpParm = null
+ UpdateDevMenuButtons()
+}
+
+void function ChangeToThisMenu_PrecacheWeapons_WithOpParm( void functionref( var ) menuFuncWithOpParm, opParm = null )
+{
+ waitthread PrecacheWeaponsIfNecessary()
+
+ file.devMenuFunc = null
+ file.devMenuFuncWithOpParm = menuFuncWithOpParm
+ file.devMenuOpParm = opParm
+ UpdateDevMenuButtons()
+}
+
+void function PrecacheWeaponsIfNecessary()
+{
+ if ( file.precachedWeapons )
+ return
+
+ file.precachedWeapons = true
+ CloseAllInGameMenus()
+
+ DisablePrecacheErrors()
+ wait 0.1
+ ClientCommand( "script PrecacheSPWeapons()" )
+ wait 0.1
+ ClientCommand( "script_client PrecacheSPWeapons()" )
+ wait 0.1
+ RestorePrecacheErrors()
+
+ AdvanceMenu( GetMenu( "DevMenu" ) )
+}
+
+void function UpdatePrecachedSPWeapons()
+{
+ file.precachedWeapons = IsMultiplayer()
+}
+
+void function SetupMenu_SpawnNPCWithWeapons( parms )
+{
+ string weaponCapacity = expect string( parms.weaponCapacity )
+ string baseClass = expect string( parms.baseClass )
+ string aiSettings = expect string( parms.aiSettings )
+ int team = expect int( parms.team )
+
+ array<int> itemTypes
+ switch ( weaponCapacity )
+ {
+ case "PilotMainWeapons":
+ itemTypes = [ eItemTypes.PILOT_PRIMARY, eItemTypes.PILOT_SECONDARY ]
+ break
+
+ case "TitanMainWeapons":
+ itemTypes = [ eItemTypes.TITAN_PRIMARY ]
+ break
+
+ default:
+ Assert( 0, "Unknown weapon capacity " + weaponCapacity )
+ break
+ }
+
+ array<string> itemNames
+ foreach ( itemType in itemTypes )
+ {
+ array<string> items = GetAllItemRefsOfType( itemType )
+ foreach ( item in items )
+ {
+ itemNames.append( item )
+ }
+ }
+
+ foreach ( ref in itemNames )
+ {
+ string weaponName = expect string( GetWeaponInfoFileKeyField_GlobalNotNull( ref, "printname" ) )
+
+ string cmd = "thread DEV_SpawnNPCWithWeaponAtCrosshair( \"" + baseClass + "\", \"" + aiSettings + "\", " + team + ", \"" + ref + "\" )"
+ SetupDevCommand( weaponName, "script " + cmd )
+ }
+}
+
+void function SetupAIDevCommands()
+{
+}
+
+void function SetDevMenu_titanSelection( var _ )
+{
+ ChangeToThisMenu( SetupTitanSelection )
+}
+
+void function SetupTitanSelection()
+{
+}
+
+void function SetupDevCommand( string label, string command )
+{
+ DevCommand cmd
+ cmd.label = label
+ cmd.command = command
+
+ file.devCommands.append( cmd )
+}
+
+void function SetupDevFunc( string label, void functionref( var ) func, var opParm = null )
+{
+ DevCommand cmd
+ cmd.label = label
+ cmd.func = func
+ cmd.opParm = opParm
+
+ file.devCommands.append( cmd )
+}
+
+function OnDevButton_Activate( button )
+{
+ //if ( level.ui.disableDev )
+ //{
+ // CodeWarning( "Dev commands disabled on matchmaking servers." )
+ // return
+ //}
+
+ int buttonID = int( Hud_GetScriptID( button ) )
+ DevCommand cmd = file.devCommands[buttonID]
+
+ RunDevCommand( cmd )
+}
+
+void function RunDevCommand( DevCommand cmd )
+{
+ if ( cmd.storeAsLastCommand )
+ {
+ file.lastDevCommand = cmd
+ file.lastDevCommandAssigned = true
+ }
+
+ if ( cmd.command != "" )
+ {
+ ClientCommand( cmd.command )
+ CloseAllInGameMenus()
+ }
+ else
+ {
+ cmd.func( cmd.opParm )
+ }
+}
+
+void function RepeatLastDevCommand( var _ )
+{
+ if ( !file.lastDevCommandAssigned )
+ return
+
+ RunDevCommand( file.lastDevCommand )
+} \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/ui/menu_map_select.nut b/bobthebob.testing/scripts/vscripts/ui/menu_map_select.nut
new file mode 100644
index 000000000..7263a9faf
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/ui/menu_map_select.nut
@@ -0,0 +1,200 @@
+untyped
+
+
+global function MenuMapSelect_Init
+
+global function InitMapsMenu
+
+const int MAPS_PER_PAGE = 21
+
+const MAP_LIST_VISIBLE_ROWS = 21
+const MAP_LIST_SCROLL_SPEED = 0
+
+struct {
+ var menu = null
+ array<var> buttons
+ int numMapButtonsOffScreen
+ int mapListScrollState = 0
+} file
+
+// note: this does have a scrolling system in vanilla, but it's honestly really weird and jank and i don't like it
+// so for parity with menu_mode_select i'm removing it in favour of a page system
+
+function MenuMapSelect_Init()
+{
+ RegisterSignal( "OnCloseMapsMenu" )
+}
+
+void function InitMapsMenu()
+{
+ file.menu = GetMenu( "MapsMenu" )
+ var menu = file.menu
+
+ AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnOpenMapsMenu )
+ AddMenuEventHandler( menu, eUIEvent.MENU_CLOSE, OnCloseMapsMenu )
+
+ AddEventHandlerToButtonClass( menu, "MapButtonClass", UIE_GET_FOCUS, MapButton_Focused )
+ AddEventHandlerToButtonClass( menu, "MapButtonClass", UIE_LOSE_FOCUS, MapButton_LostFocus )
+ AddEventHandlerToButtonClass( menu, "MapButtonClass", UIE_CLICK, MapButton_Activate )
+ //AddEventHandlerToButtonClass( menu, "MapListScrollUpClass", UIE_CLICK, OnMapListScrollUp_Activate )
+ //AddEventHandlerToButtonClass( menu, "MapListScrollDownClass", UIE_CLICK, OnMapListScrollDown_Activate )
+
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" )
+ AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
+
+ file.buttons = GetElementsByClassname( menu, "MapButtonClass" )
+
+ AddMenuFooterOption( menu, BUTTON_SHOULDER_LEFT, "#PRIVATE_MATCH_PAGE_PREV", "#PRIVATE_MATCH_PAGE_PREV", CycleModesBack, IsNorthstarServer )
+ AddMenuFooterOption( menu, BUTTON_SHOULDER_RIGHT, "#PRIVATE_MATCH_PAGE_NEXT", "#PRIVATE_MATCH_PAGE_NEXT", CycleModesForward, IsNorthstarServer )
+}
+
+void function OnOpenMapsMenu()
+{
+ array<var> buttons = file.buttons
+ array<string> mapsArray = GetPrivateMatchMaps()
+
+ file.numMapButtonsOffScreen = int( max( mapsArray.len() - MAP_LIST_VISIBLE_ROWS, 0 ) )
+// Assert( file.numMapButtonsOffScreen >= 0 )
+
+ foreach ( button in buttons )
+ {
+ int buttonID = int( Hud_GetScriptID( button ) )
+
+ if ( buttonID >= 0 && buttonID < mapsArray.len() )
+ {
+ string name = mapsArray[buttonID]
+ SetButtonRuiText( button, GetMapDisplayName( name ) )
+ Hud_SetEnabled( button, true )
+
+ if ( IsItemInEntitlementUnlock( name ) && IsValid( GetUIPlayer() ) )
+ {
+ if ( IsItemLocked( GetUIPlayer(), name ) && GetCurrentPlaylistVarInt( name + "_available" , 0 ) == 0 )
+ {
+ SetButtonRuiText( button, Localize( "#MAP_LOCKED", Localize( GetMapDisplayName( name ) ) ) )
+ }
+ }
+
+ bool mapSupportsMode = PrivateMatch_IsValidMapModeCombo( name, PrivateMatch_GetSelectedMode() )
+ Hud_SetLocked( button, !mapSupportsMode )
+
+ if ( !mapSupportsMode )
+ SetButtonRuiText( button, Localize( "#PRIVATE_MATCH_UNAVAILABLE", Localize( GetMapDisplayName( name ) ) ) )
+ }
+ else
+ {
+ SetButtonRuiText( button, "" )
+ Hud_SetEnabled( button, false )
+ }
+
+ if ( buttonID == level.ui.privatematch_map )
+ {
+ printt( buttonID, mapsArray[buttonID] )
+ Hud_SetFocused( button )
+ }
+ }
+
+ //RegisterButtonPressedCallback( MOUSE_WHEEL_UP, OnMapListScrollUp_Activate )
+ //RegisterButtonPressedCallback( MOUSE_WHEEL_DOWN, OnMapListScrollDown_Activate )
+}
+
+void function OnCloseMapsMenu()
+{
+ //DeregisterButtonPressedCallback( MOUSE_WHEEL_UP, OnMapListScrollUp_Activate )
+ //DeregisterButtonPressedCallback( MOUSE_WHEEL_DOWN, OnMapListScrollDown_Activate )
+
+ Signal( uiGlobal.signalDummy, "OnCloseMapsMenu" )
+}
+
+void function MapButton_Focused( var button )
+{
+ int buttonID = int( Hud_GetScriptID( button ) )
+
+ var menu = file.menu
+ var nextMapImage = Hud_GetChild( menu, "NextMapImage" )
+ var nextMapName = Hud_GetChild( menu, "NextMapName" )
+ var nextMapDesc = Hud_GetChild( menu, "NextMapDesc" )
+
+ array<string> mapsArray = GetPrivateMatchMaps()
+ string mapName = mapsArray[buttonID]
+
+ asset mapImage = GetMapImageForMapName( mapName )
+ RuiSetImage( Hud_GetRui( nextMapImage ), "basicImage", mapImage )
+ Hud_SetText( nextMapName, GetMapDisplayName( mapName ) )
+
+ string modeName = PrivateMatch_GetSelectedMode()
+ bool mapSupportsMode = PrivateMatch_IsValidMapModeCombo( mapName, modeName )
+ if ( !mapSupportsMode )
+ Hud_SetText( nextMapDesc, Localize( "#PRIVATE_MATCH_MAP_NO_MODE_SUPPORT", Localize( GetMapDisplayName( mapName ) ), Localize( GetGameModeDisplayName( modeName ) ) ) )
+ else
+ Hud_SetText( nextMapDesc, GetMapDisplayDesc( mapName ) )
+
+ // Update window scrolling if we highlight a map not in view
+ int minScrollState = int( clamp( buttonID - (MAP_LIST_VISIBLE_ROWS - 1), 0, file.numMapButtonsOffScreen ) )
+ int maxScrollState = int( clamp( buttonID, 0, file.numMapButtonsOffScreen ) )
+
+ if ( file.mapListScrollState < minScrollState )
+ file.mapListScrollState = minScrollState
+ if ( file.mapListScrollState > maxScrollState )
+ file.mapListScrollState = maxScrollState
+
+ UpdateMapListScroll()
+}
+
+void function MapButton_LostFocus( var button )
+{
+ HandleLockedCustomMenuItem( file.menu, button, [], true )
+}
+
+void function MapButton_Activate( var button )
+{
+ if ( Hud_IsLocked( button ) )
+ {
+ return
+ }
+
+ if ( !AmIPartyLeader() && GetPartySize() > 1 )
+ return
+
+ array<string> mapsArray = GetPrivateMatchMaps()
+ int mapID = int( Hud_GetScriptID( button ) )
+ string mapName = mapsArray[mapID]
+
+ printt( mapName, mapID )
+
+ ClientCommand( "SetCustomMap " + mapName )
+ CloseActiveMenu()
+}
+
+
+void function OnMapListScrollUp_Activate( var button )
+{
+ file.mapListScrollState--
+ if ( file.mapListScrollState < 0 )
+ file.mapListScrollState = 0
+
+ UpdateMapListScroll()
+}
+
+void function OnMapListScrollDown_Activate( var button )
+{
+ file.mapListScrollState++
+ if ( file.mapListScrollState > file.numMapButtonsOffScreen )
+ file.mapListScrollState = file.numMapButtonsOffScreen
+
+ UpdateMapListScroll()
+}
+
+function UpdateMapListScroll()
+{
+ array<var> buttons = file.buttons
+ local basePos = buttons[0].GetBasePos()
+ local offset = buttons[0].GetHeight() * file.mapListScrollState
+
+ buttons[0].SetPos( basePos[0], basePos[1] - offset )
+}
+
+void function CycleModesBack( var button )
+{}
+
+void function CycleModesForward( var button )
+{}
diff --git a/bobthebob.testing/scripts/vscripts/ui/menu_mode_select.nut b/bobthebob.testing/scripts/vscripts/ui/menu_mode_select.nut
new file mode 100644
index 000000000..4e0ecef5b
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/ui/menu_mode_select.nut
@@ -0,0 +1,138 @@
+global function InitModesMenu
+
+struct {
+ int currentModePage
+
+} file
+
+const int MODES_PER_PAGE = 15
+
+void function InitModesMenu()
+{
+ var menu = GetMenu( "ModesMenu" )
+
+ AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnOpenModesMenu )
+
+ AddEventHandlerToButtonClass( menu, "ModeButton", UIE_GET_FOCUS, ModeButton_GetFocus )
+ AddEventHandlerToButtonClass( menu, "ModeButton", UIE_CLICK, ModeButton_Click )
+
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" )
+ AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
+
+ AddMenuFooterOption( menu, BUTTON_SHOULDER_LEFT, "#PRIVATE_MATCH_PAGE_PREV", "#PRIVATE_MATCH_PAGE_PREV", CycleModesBack, IsNorthstarServer )
+ AddMenuFooterOption( menu, BUTTON_SHOULDER_RIGHT, "#PRIVATE_MATCH_PAGE_NEXT", "#PRIVATE_MATCH_PAGE_NEXT", CycleModesForward, IsNorthstarServer )
+}
+
+void function OnOpenModesMenu()
+{
+ UpdateVisibleModes()
+
+ if ( level.ui.privatematch_mode == 0 ) // set to the first mode if there's no mode focused
+ Hud_SetFocused( GetElementsByClassname( GetMenu( "ModesMenu" ), "ModeButton" )[ 0 ] )
+}
+
+void function UpdateVisibleModes()
+{
+ // ensures that we only ever show enough buttons for the number of modes we have
+ array<var> buttons = GetElementsByClassname( GetMenu( "ModesMenu" ), "ModeButton" )
+ foreach ( var button in buttons )
+ {
+ Hud_SetEnabled( button, false )
+ Hud_SetVisible( button, false )
+ }
+
+ array<string> modesArray = GetPrivateMatchModes()
+ for ( int i = 0; i < MODES_PER_PAGE; i++ )
+ {
+ if ( i + ( file.currentModePage * MODES_PER_PAGE ) >= modesArray.len() )
+ break
+
+ int modeIndex = i + ( file.currentModePage * MODES_PER_PAGE )
+ SetButtonRuiText( buttons[ i ], GetGameModeDisplayName( modesArray[ modeIndex ] ) )
+ Hud_SetEnabled( buttons[ i ], true )
+ Hud_SetVisible( buttons[ i ], true )
+ Hud_SetLocked( buttons[ i ], false )
+
+ if ( !PrivateMatch_IsValidMapModeCombo( PrivateMatch_GetSelectedMap(), modesArray[ modeIndex ] ) && !IsNorthstarServer() )
+ {
+ Hud_SetLocked( buttons[ i ], true )
+ SetButtonRuiText( buttons[ i ], Localize( "#PRIVATE_MATCH_UNAVAILABLE", Localize( GetGameModeDisplayName( modesArray[ modeIndex ] ) ) ) )
+ }
+ }
+}
+
+void function ModeButton_GetFocus( var button )
+{
+ int modeId = int( Hud_GetScriptID( button ) ) + ( file.currentModePage * MODES_PER_PAGE )
+
+ var menu = GetMenu( "ModesMenu" )
+ var nextModeImage = Hud_GetChild( menu, "NextModeImage" )
+ var nextModeIcon = Hud_GetChild( menu, "ModeIconImage" )
+ var nextModeName = Hud_GetChild( menu, "NextModeName" )
+ var nextModeDesc = Hud_GetChild( menu, "NextModeDesc" )
+
+ array<string> modesArray = GetPrivateMatchModes()
+
+ if ( modeId > modesArray.len() )
+ return
+
+ string modeName = modesArray[modeId]
+
+ asset playlistImage = GetPlaylistImage( modeName )
+ RuiSetImage( Hud_GetRui( nextModeImage ), "basicImage", playlistImage )
+ RuiSetImage( Hud_GetRui( nextModeIcon ), "basicImage", GetPlaylistThumbnailImage( modeName ) )
+ Hud_SetText( nextModeName, GetGameModeDisplayName( modeName ) )
+
+ string mapName = PrivateMatch_GetSelectedMap()
+ bool mapSupportsMode = PrivateMatch_IsValidMapModeCombo( mapName, modeName )
+ if ( !mapSupportsMode && !IsNorthstarServer() )
+ Hud_SetText( nextModeDesc, Localize( "#PRIVATE_MATCH_MODE_NO_MAP_SUPPORT", Localize( GetGameModeDisplayName( modeName ) ), Localize( GetMapDisplayName( mapName ) ) ) )
+ else if ( IsFDMode( modeName ) ) // HACK!
+ Hud_SetText( nextModeDesc, Localize( "#FD_PLAYERS_DESC", Localize( GetGameModeDisplayHint( modeName ) ) ) )
+ else
+ Hud_SetText( nextModeDesc, GetGameModeDisplayHint( modeName ) )
+}
+
+void function ModeButton_Click( var button )
+{
+ // this never activates on custom servers, but keeping it for parity with official
+ if ( !AmIPartyLeader() && GetPartySize() > 1 )
+ return
+
+ if ( Hud_IsLocked( button ) )
+ return
+
+ int mapID = int( Hud_GetScriptID( button ) ) + ( file.currentModePage * MODES_PER_PAGE )
+
+ var menu = GetMenu( "MapsMenu" )
+
+ array<string> modesArray = GetPrivateMatchModes()
+ string modeName = modesArray[mapID]
+
+ // on modded servers set us to the first map for that mode automatically
+ // need this for coliseum mainly which is literally impossible to select without this
+ if ( IsNorthstarServer() && !PrivateMatch_IsValidMapModeCombo( PrivateMatch_GetSelectedMap(), modesArray[ mapID ] ) )
+ ClientCommand( "SetCustomMap " + GetPrivateMatchMapsForMode( modeName )[ 0 ] )
+
+ // set it
+ ClientCommand( "PrivateMatchSetMode " + modeName )
+ CloseActiveMenu()
+}
+
+void function CycleModesBack( var button )
+{
+ if ( file.currentModePage == 0 )
+ return
+
+ file.currentModePage--
+ UpdateVisibleModes()
+}
+
+void function CycleModesForward( var button )
+{
+ if ( ( file.currentModePage + 1 ) * MODES_PER_PAGE >= GetPrivateMatchModes().len() )
+ return
+
+ file.currentModePage++
+ UpdateVisibleModes()
+} \ No newline at end of file
diff --git a/bobthebob.testing/scripts/vscripts/ui/menu_team_titan_select.nut b/bobthebob.testing/scripts/vscripts/ui/menu_team_titan_select.nut
new file mode 100644
index 000000000..92bb849eb
--- /dev/null
+++ b/bobthebob.testing/scripts/vscripts/ui/menu_team_titan_select.nut
@@ -0,0 +1,722 @@
+global function InitTeamTitanSelectMenu
+global function TeamTitanSelectMenuIsOpen
+global function ServerCallback_OpenTeamTitanMenu
+global function ServerCallback_CloseTeamTitanMenu
+global function ServerCallback_UpdateTeamTitanMenuTime
+global function ServerCallback_RegisterTeamTitanMenuButtons
+global function TTSUpdateDoubleXP
+global function TTSUpdateDoubleXPStatus
+global function TTSMenuModeFD
+global function TTSMenuModeDefault
+global function TTSMenu_UpdateGameMode
+global function EnableDoubleXP
+
+const float SELECT_DELAY = 0.2
+
+enum eTTSMenuMode
+{
+ DEFAULT,
+ FD
+}
+
+struct
+{
+ var menu
+ bool allowManualClose = false
+ bool isReady = false
+ bool menuOpened = false
+ bool allowSelection = false
+
+ array<var> titanButtons
+ array<var> titanUpgradeButtons
+ var editButton
+ var readyPanel
+ var cover
+ var doubleXPButton
+ var chatBox
+
+ bool buttonsRegistered = false
+
+ int menuMode
+
+ float nextAllowSoundTime = 0.0
+} file
+
+void function InitTeamTitanSelectMenu()
+{
+ file.menu = GetMenu( "TeamTitanSelectMenu" )
+
+ AddMenuEventHandler( file.menu, eUIEvent.MENU_NAVIGATE_BACK, OnTeamTitanSelectMenu_NavigateBack )
+ AddMenuEventHandler( file.menu, eUIEvent.MENU_OPEN, OnTeamTitanSelectMenu_Open )
+ AddMenuEventHandler( file.menu, eUIEvent.MENU_SHOW, OnTeamTitanSelectMenu_Open )
+ AddMenuEventHandler( file.menu, eUIEvent.MENU_HIDE, OnTeamTitanSelectMenu_Hide )
+ AddMenuEventHandler( file.menu, eUIEvent.MENU_CLOSE, OnTeamTitanSelectMenu_Close )
+
+ RegisterSignal( "TTSMenuClosed" )
+ RegisterSignal( "Delayed_RequestTitanLoadout" )
+
+ float margin = 10.0
+ float totalWidth = 0.0
+ for ( int i=0; i<NUM_PERSISTENT_TITAN_LOADOUTS; i++ )
+ {
+ var button = Hud_GetChild( file.menu, "TitanButton" + i )
+ file.titanButtons.append( button )
+ float xPos = totalWidth * -1
+ totalWidth += Hud_GetWidth( button ) + margin
+ Hud_SetPos( button, xPos, Hud_GetY( button ) )
+ }
+
+ var bg = Hud_GetChild( file.menu, "BG" )
+ totalWidth -= margin
+ float bgWidth = float( Hud_GetWidth( bg ) )
+ float startPos = (bgWidth*0.5 - totalWidth*0.5) * -1
+ for ( int i=0; i<NUM_PERSISTENT_TITAN_LOADOUTS; i++ )
+ {
+ var button = file.titanButtons[i]
+ Hud_SetPos( button, startPos + Hud_GetX( button ), Hud_GetY( button ) )
+
+ Hud_AddEventHandler( button, UIE_CLICK, TitanButton_OnClick )
+ Hud_AddEventHandler( button, UIE_GET_FOCUS, TitanButton_OnFocused )
+ }
+
+ for ( int i=0; i<7; i++ )
+ {
+ var button = Hud_GetChild( file.menu, "BtnSub" + i )
+
+ Hud_AddEventHandler( button, UIE_LOSE_FOCUS, TitanUpgradeButton_OnLoseFocus )
+ Hud_AddEventHandler( button, UIE_GET_FOCUS, TitanUpgradeButton_OnFocused )
+ }
+
+ SetNavLeftRight( file.titanButtons, true )
+
+ //file.editButton = Hud_GetChild( file.menu, "EditTitanButton" )
+ //Hud_AddEventHandler( file.editButton, UIE_CLICK, EditTitanButton_OnClick )
+ file.readyPanel = Hud_GetChild( file.menu, "ReadyRui" )
+ file.cover = Hud_GetChild( file.menu, "Cover" )
+
+ #if PC_PROG
+ file.chatBox = Hud_GetChild( file.menu, "LobbyChatBox" )
+ #endif // PC_PROG
+
+ file.doubleXPButton = Hud_GetChild( file.menu, "DoubleXP" )
+
+ AddMenuFooterOption( file.menu, BUTTON_A, "#A_BUTTON_SELECT", "", null, TeamTitanSelect_IsNotReady )
+ AddMenuFooterOption( file.menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
+ AddMenuFooterOption( file.menu, BUTTON_X, "#MENU_X_BUTTON_EDIT_TITAN", "#MENU_EDIT_TITAN", EditTitanButton_OnClick, TeamTitanSelect_IsReady )
+ AddMenuFooterOption( file.menu, BUTTON_Y, "#MENU_Y_BUTTON_EDIT_PILOT", "#MENU_EDIT_PILOT", EditPilotButton_OnClick, CoverIsOff )
+}
+
+void function TTSUpdateDoubleXP( int count, bool avialable, float status )
+{
+ var rui = Hud_GetRui( file.doubleXPButton )
+ RuiSetInt( rui, "doubleXPCount", count )
+ RuiSetBool( rui, "doubleXPAvailable", avialable )
+ RuiSetFloat( rui, "doubleXPStatus", status )
+}
+
+void function TTSUpdateDoubleXPStatus( int status )
+{
+ var rui = Hud_GetRui( file.doubleXPButton )
+ RuiSetFloat( rui, "doubleXPStatus", float( status ) )
+}
+
+void function ServerCallback_UpdateTeamTitanMenuTime( float endTime )
+{
+ Signal( uiGlobal.signalDummy, "TTSMenuClosed" )
+
+ file.allowSelection = true
+
+ if ( file.nextAllowSoundTime < Time() )
+ {
+ EmitUISound( "ui_ctf_1p_playerscore" )
+ file.nextAllowSoundTime = Time() + 5.0
+ }
+
+ Hud_SetEnabled( file.cover, false )
+ Hud_Hide( file.cover )
+ Hud_SetAlpha( file.cover, 0 )
+
+ if ( endTime > 5 )
+ file.allowSelection = true
+
+ thread UpdateSubText( Time() + endTime )
+}
+
+void function ServerCallback_OpenTeamTitanMenu( float endTime )
+{
+ if ( TeamTitanSelectMenuIsOpen() )
+ return
+
+ if ( uiGlobal.activeMenu != null )
+ CloseAllMenus()
+
+ RunClientScript( "PlayTTSMusic" )
+
+ file.allowManualClose = false
+ file.allowSelection = true
+ file.isReady = true // set to true so selection mode kicks in
+ thread MenuFadeIn()
+ BeginSelectionMode()
+ AdvanceMenu( file.menu )
+ thread UpdateSubText( Time() + endTime )
+}
+
+void function MenuFadeIn()
+{
+ Hud_SetEnabled( file.cover, true )
+ Hud_SetAlpha( file.cover, 255 )
+ Hud_Show( file.cover )
+ wait 1.0
+ Hud_FadeOverTime( file.cover, 0, 1.0 )
+ wait 1.0
+ Hud_Hide( file.cover )
+ Hud_SetEnabled( file.cover, false )
+}
+
+void function UpdateSubText( float endTime )
+{
+ EndSignal( uiGlobal.signalDummy, "TTSMenuClosed" )
+
+ var subText = Hud_GetChild( file.menu, "MenuSubTitle" )
+ var rui = Hud_GetRui( file.readyPanel )
+ RuiSetBool( rui, "isReady", true )
+
+ thread Countdown( endTime )
+ while ( Time() < endTime )
+ {
+ int countdownTime = int( ceil( endTime - Time() ) )
+ Hud_SetText( subText, Localize( "#MENU_STARTS_IN", countdownTime ) )
+ RuiSetInt( rui, "timer", countdownTime )
+ WaitFrame()
+ }
+
+ Hud_SetText( subText, Localize( "#MENU_STARTS_IN", 0 ) )
+ RuiSetInt( rui, "timer", 0 )
+}
+
+void function Countdown( float endTime )
+{
+ EndSignal( uiGlobal.signalDummy, "TTSMenuClosed" )
+
+ float countdownTime = 5.0
+ float startCountdownTime = endTime - countdownTime
+
+ if ( Time() > startCountdownTime )
+ return
+
+ wait startCountdownTime - Time()
+
+ while ( Time() < endTime )
+ {
+ EmitUISound( "UI_InGame_MarkedForDeath_CountdownToMarked" )
+ wait 1.0
+ }
+
+ file.allowSelection = false
+ BeginEditMode(null)
+
+ Hud_SetAlpha( file.cover, 0 )
+ Hud_SetEnabled( file.cover, true )
+ Hud_Show( file.cover )
+ Hud_FadeOverTime( file.cover, 255, 1.0 )
+
+ float soundTime = DoPrematchWarpSound() ? PICK_LOADOUT_SOUND_TIME : 1.5
+ wait soundTime
+
+ thread ServerCallback_CloseTeamTitanMenu()
+}
+
+void function ServerCallback_CloseTeamTitanMenu()
+{
+ if ( TeamTitanSelectMenuIsOpen() )
+ {
+ file.allowManualClose = true
+ UI_SetPresentationType( ePresentationType.INACTIVE )
+ CloseAllMenus()
+ OnTeamTitanSelectMenu_Close()
+ }
+}
+
+void function OnTeamTitanSelectMenu_NavigateBack()
+{
+ if ( file.allowManualClose )
+ {
+ CloseActiveMenu()
+ return
+ }
+
+ if ( file.isReady && file.allowSelection )
+ {
+ BeginSelectionMode()
+ return
+ }
+
+ LeaveDialog()
+ return
+}
+
+void function TTSMenu_UpdateGameMode( string modeName )
+{
+ var title = Hud_GetChild( file.menu, "MenuTitle" )
+ Hud_SetText( title, Localize( modeName ) )
+}
+
+void function ServerCallback_RegisterTeamTitanMenuButtons()
+{
+ RegisterButtonCallbacks()
+}
+
+void function RegisterButtonCallbacks()
+{
+ if ( file.buttonsRegistered )
+ return
+
+ file.buttonsRegistered = true
+ RegisterButtonPressedCallback( BUTTON_BACK, EnableDoubleXP )
+ RegisterButtonPressedCallback( KEY_RSHIFT, EnableDoubleXP )
+}
+
+void function OnTeamTitanSelectMenu_Open()
+{
+ file.menuOpened = true
+
+ var dataTable = GetDataTable( $"datatable/titan_properties.rpak" )
+ int loadoutIconCol = GetDataTableColumnByName( dataTable, "loadoutIconFD" )
+ int titanCol = GetDataTableColumnByName( dataTable, "titanRef" )
+
+ entity player = GetUIPlayer()
+
+ var nextMapImage = Hud_GetChild( file.menu, "NextMapImage" )
+ string mapName = GetActiveLevel()
+ asset mapImage = GetMapImageForMapName( mapName )
+
+ RefreshCreditsAvailable()
+
+ RuiSetImage( Hud_GetRui( nextMapImage ), "basicImage", mapImage )
+ Hud_Show( nextMapImage )
+ Hud_SetText( Hud_GetChild( file.menu, "NextMapName" ), GetMapDisplayName( mapName ) )
+ Hud_Show( Hud_GetChild( file.menu, "NextMapName" ) )
+
+ var buttonToFocus
+
+ for ( int i=0; i<NUM_PERSISTENT_TITAN_LOADOUTS; i++ )
+ {
+ var button = file.titanButtons[i]
+ TitanLoadoutDef loadout = GetCachedTitanLoadout( i )
+ int row = GetDataTableRowMatchingStringValue( dataTable, titanCol, loadout.titanClass )
+
+ asset icon = GetDataTableAsset( dataTable, row, loadoutIconCol )
+ var rui = Hud_GetRui( button )
+
+ RuiSetImage( rui, "buttonImage", icon )
+
+ if ( !IsTitanLoadoutAvailable( player, loadout.titanClass ) )
+ {
+ Hud_SetLocked( button, true )
+ if ( !IsItemLocked( player, loadout.titanClass ) )
+ RefreshButtonCost( button, loadout.titanClass, "", 0, 0 )
+ }
+ else
+ {
+ Hud_SetLocked( button, IsItemLocked( player, loadout.titanClass ) )
+ RefreshButtonCost( button, loadout.titanClass )
+
+ if ( uiGlobal.titanSpawnLoadoutIndex == i )
+ {
+ buttonToFocus = button
+ }
+ }
+ }
+
+ if ( buttonToFocus == null )
+ buttonToFocus = FindValidTitanButton()
+ thread HACK_DelayedSetFocus_BecauseWhy( buttonToFocus )
+
+ TitanLoadoutDef loadout = GetCachedTitanLoadout( uiGlobal.titanSpawnLoadoutIndex )
+
+ for ( int i=0; i<7; i++ )
+ {
+ var button = Hud_GetChild( file.menu, "BtnSub"+i )
+ file.titanUpgradeButtons.append( button )
+
+ if ( file.menuMode == eTTSMenuMode.FD )
+ {
+ Hud_Show( button )
+ }
+ else
+ {
+ Hud_Hide( button )
+ }
+ }
+ SetNavLeftRight( file.titanUpgradeButtons, true )
+
+ SetBlurEnabled( false )
+ var title = Hud_GetChild( file.menu, "MenuTitle" )
+ string name = expect string( GetCurrentPlaylistVar( "name" ) )
+ Hud_SetText( title, Localize( name ) )
+ RunMenuClientFunction( "ShowTTSPanel" )
+
+ if ( file.isReady )
+ UI_SetPresentationType( ePresentationType.TITAN_CENTER_SELECTED )
+ else
+ UI_SetPresentationType( ePresentationType.TITAN_CENTER )
+
+
+ RunClientScript( "TTS_UpdateLocalPlayerTitan", loadout.setFile, loadout.primary, loadout.passive1, loadout.passive2 )
+}
+
+void function EnableDoubleXP( var button )
+{
+ #if PC_PROG
+ if ( Hud_IsFocused( file.chatBox ) )
+ return
+ #endif // PC_PROG
+
+ if ( CanRunClientScript() )
+ {
+ EmitUISound( "Menu_Email_Sent" )
+ RunClientScript( "UseDoubleXP" )
+ }
+}
+
+void function OnTeamTitanSelectMenu_Hide()
+{
+ RunMenuClientFunction( "HideTTSPanel" )
+
+ DeregisterButtonCallbacks()
+}
+
+void function DeregisterButtonCallbacks()
+{
+ if ( !file.buttonsRegistered )
+ return
+
+ file.buttonsRegistered = false
+
+ DeregisterButtonPressedCallback( BUTTON_BACK, EnableDoubleXP )
+ DeregisterButtonPressedCallback( KEY_RSHIFT, EnableDoubleXP )
+}
+
+void function OnTeamTitanSelectMenu_Close()
+{
+ RunMenuClientFunction( "ClearAllPilotPreview" )
+ Signal( uiGlobal.signalDummy, "TTSMenuClosed" )
+ file.menuOpened = false
+ UI_SetPresentationType( ePresentationType.INACTIVE )
+
+ DeregisterButtonCallbacks()
+}
+
+void function TitanButton_OnClick( var button )
+{
+ int scriptID = int( Hud_GetScriptID( button ) )
+ if ( Hud_IsLocked( button ) )
+ {
+ TitanLoadoutDef loadout = GetCachedTitanLoadout( scriptID )
+ if ( !IsTitanLoadoutAvailable( GetUIPlayer(), loadout.titanClass ) )
+ return
+
+ OpenBuyItemDialog( file.titanButtons, button, GetItemName( loadout.titanClass ), loadout.titanClass )
+ return
+ }
+
+ if ( file.isReady )
+ {
+ BeginSelectionMode()
+ return
+ }
+
+ uiGlobal.titanSpawnLoadoutIndex = scriptID
+ Signal( uiGlobal.signalDummy, "Delayed_RequestTitanLoadout" )
+ ClientCommand( "RequestTitanLoadout " + uiGlobal.titanSpawnLoadoutIndex )
+ BeginEditMode( button )
+}
+
+void function TitanButton_OnFocused( var button )
+{
+ int scriptID = int( Hud_GetScriptID( button ) )
+
+ var rui = Hud_GetRui( Hud_GetChild( file.menu, "TitanName" ) )
+
+ TitanLoadoutDef loadout = GetCachedTitanLoadout( scriptID )
+
+ RuiSetString( rui, "titanName", GetTitanLoadoutName( loadout ) )
+ RuiSetString( rui, "titanLevelString", "" )
+ RuiSetString( rui, "titanRole", "" )
+
+ entity player = GetUIPlayer()
+
+ var dataTable = GetDataTable( $"datatable/titan_properties.rpak" )
+ int row = GetDataTableRowMatchingStringValue( dataTable, GetDataTableColumnByName( dataTable, "titanRef" ), loadout.titanClass )
+ string role = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "fdRole" ) )
+ int titanLevel = FD_TitanGetLevelForXP( loadout.titanClass, FD_TitanGetXP( GetUIPlayer(), loadout.titanClass ) )
+ array<ItemDisplayData> titanUpgrades = FD_GetUpgradesForTitanClass( loadout.titanClass )
+
+ if ( file.menuMode == eTTSMenuMode.FD )
+ {
+ RuiSetString( rui, "titanLevelString", Localize( "#FD_TITAN_LEVEL", titanLevel ) )
+ RuiSetString( rui, "titanRole", Localize( "#FD_ROLE", Localize(role) ) )
+
+ foreach ( index, item in titanUpgrades )
+ {
+ var button = file.titanUpgradeButtons[index]
+ var upgradeRui = Hud_GetRui( button )
+
+ bool locked = IsSubItemLocked( GetUIPlayer(), item.ref, item.parentRef )
+
+ if ( locked )
+ RuiSetImage( upgradeRui, "buttonImage", expect asset( item.i.lockedImage ) )
+ else
+ RuiSetImage( upgradeRui, "buttonImage", item.image )
+
+ Hud_SetLocked( button, locked )
+ }
+ }
+
+ if ( !Hud_IsLocked( button ) )
+ {
+ uiGlobal.titanSpawnLoadoutIndex = scriptID
+ thread Delayed_RequestTitanLoadout( uiGlobal.titanSpawnLoadoutIndex )
+ SetLabelRuiText( Hud_GetChild( file.menu, "TitanSelectTitle" ), "#MENU_TITAN_SELECT" )
+ }
+ else
+ {
+ RuiSetString( rui, "titanLevelString", GetItemUnlockReqText( loadout.titanClass ) )
+ }
+
+ SetLabelRuiText( Hud_GetChild( file.menu, "TitanSelectTitle" ), GetTitanAvailableText( player, loadout.titanClass) )
+
+ RunMenuClientFunction( "UpdateTitanModel", scriptID )
+ RunClientScript( "TTS_UpdateLocalPlayerTitan", loadout.setFile, loadout.primary, loadout.passive1, loadout.passive2 )
+}
+
+string function GetTitanAvailableText( entity player, string titanClass )
+{
+ int titanAvailableState = GetTitanLoadAvailableState( player, titanClass )
+
+ if ( titanAvailableState == TITAN_CLASS_LOCK_STATE_AVAILABLE )
+ return "#MENU_TITAN_SELECT_HINT"
+
+ if ( titanAvailableState == TITAN_CLASS_LOCK_STATE_LEVELREQUIRED || titanAvailableState == TITAN_CLASS_LOCK_STATE_LEVELRECOMMENDED )
+ {
+ int difficultyLevel = GetCurrentPlaylistVarInt( "fd_difficulty", 0 )
+ int requiredTitanLevel = 0
+ switch ( difficultyLevel )
+ {
+ case eFDDifficultyLevel.EASY:
+ if ( GetItemUnlockType( "fd_easy" ) == eUnlockType.STAT )
+ requiredTitanLevel = int( GetStatUnlockStatVal( "fd_easy" ) )
+ break
+ case eFDDifficultyLevel.NORMAL:
+ if ( GetItemUnlockType( "fd_normal" ) == eUnlockType.STAT )
+ requiredTitanLevel = int( GetStatUnlockStatVal( "fd_normal" ) )
+ break
+ case eFDDifficultyLevel.HARD:
+ if ( GetItemUnlockType( "fd_hard" ) == eUnlockType.STAT )
+ requiredTitanLevel = int( GetStatUnlockStatVal( "fd_hard" ) )
+ break
+ case eFDDifficultyLevel.MASTER:
+ if ( GetItemUnlockType( "fd_master" ) == eUnlockType.STAT )
+ requiredTitanLevel = int( GetStatUnlockStatVal( "fd_master" ) )
+ break
+ case eFDDifficultyLevel.INSANE:
+ if ( GetItemUnlockType( "fd_insane" ) == eUnlockType.STAT )
+ requiredTitanLevel = int( GetStatUnlockStatVal( "fd_insane" ) )
+ break
+ }
+
+ if ( titanAvailableState == TITAN_CLASS_LOCK_STATE_LEVELREQUIRED )
+ return Localize( "#MENU_TITAN_SELECT_LEVELREQUIRED", requiredTitanLevel )
+ else
+ return Localize( "#MENU_TITAN_SELECT_LEVELRECOMMENDED", requiredTitanLevel )
+ }
+
+ if ( titanAvailableState == TITAN_CLASS_LOCK_STATE_INUSE )
+ return "#MENU_TITAN_SELECT_INUSE"
+
+ if ( titanAvailableState == TITAN_CLASS_LOCK_STATE_LOCKED )
+ return "#MENU_TITAN_SELECT_LOCKED"
+
+ return "#MENU_TITAN_SELECT_HINT"
+}
+
+void function Delayed_RequestTitanLoadout( int index )
+{
+ Signal( uiGlobal.signalDummy, "Delayed_RequestTitanLoadout" )
+ EndSignal( uiGlobal.signalDummy, "Delayed_RequestTitanLoadout" )
+ wait 0.5
+ ClientCommand( "RequestTitanLoadout " + uiGlobal.titanSpawnLoadoutIndex )
+}
+
+void function TitanUpgradeButton_OnFocused( var button )
+{
+ if ( file.menuMode == eTTSMenuMode.DEFAULT )
+ return
+
+ int scriptID = int( Hud_GetScriptID( button ) )
+ TitanLoadoutDef loadout = GetCachedTitanLoadout( uiGlobal.titanSpawnLoadoutIndex )
+ array<ItemDisplayData> titanUpgrades = FD_GetUpgradesForTitanClass( loadout.titanClass )
+
+ ItemDisplayData item = titanUpgrades[ scriptID ]
+ var panel = Hud_GetChild( file.menu, "UpgradeName" )
+ Hud_Show( panel )
+ var rui = Hud_GetRui( panel )
+ RuiSetString( rui, "upgradeName", item.name )
+ RuiSetString( rui, "upgradeDesc", item.desc )
+ RuiSetBool( rui, "isLocked", IsSubItemLocked( GetUIPlayer(), item.ref, item.parentRef ) )
+}
+
+void function TitanUpgradeButton_OnLoseFocus( var button )
+{
+ Hud_Hide( Hud_GetChild( file.menu, "UpgradeName" ) )
+}
+
+void function BeginEditMode( var button )
+{
+ if ( file.isReady )
+ return
+
+ file.isReady = true
+
+ EmitUISound( "UI_InGame_FD_TitanSelected" )
+ SetLabelRuiText( Hud_GetChild( file.menu, "TitanSelectTitle" ), "#MENU_TITAN_SELECTED" )
+ Hud_Hide( Hud_GetChild( file.menu, "TitanSelectTitle" ) )
+ Hud_Show( file.readyPanel )
+ var rui = Hud_GetRui( file.readyPanel )
+ RuiSetBool( rui, "isReady", true )
+
+ foreach ( b in file.titanButtons )
+ {
+ Hud_Hide( b )
+ }
+
+ //Hud_Show( file.editButton )
+ //Hud_SetFocused( file.editButton )
+ Hud_SetFocused( file.titanUpgradeButtons[0] )
+
+ TitanLoadoutDef loadout = GetCachedTitanLoadout( uiGlobal.titanSpawnLoadoutIndex )
+ string primeTitanString = ""
+
+ if ( loadout.isPrime == "titan_is_prime" )
+ primeTitanString = "_prime"
+
+ string modifiedAlias = "diag_gs_titan" + loadout.titanClass + primeTitanString + "_embark"
+ EmitUISound( modifiedAlias )
+
+ if ( uiGlobal.activeMenu == file.menu )
+ UI_SetPresentationType( ePresentationType.TITAN_CENTER_SELECTED )
+
+ Signal( uiGlobal.signalDummy, "Delayed_RequestTitanLoadout" )
+ ClientCommand( "RequestTitanLoadout " + uiGlobal.titanSpawnLoadoutIndex )
+ RunMenuClientFunction( "UpdateTitanModel", uiGlobal.titanSpawnLoadoutIndex )
+
+ var subText = Hud_GetChild( file.menu, "MenuSubTitle" )
+ // Hud_Hide( subText )
+}
+
+void function BeginSelectionMode()
+{
+ if ( !file.isReady )
+ return
+
+ file.isReady = false
+ //Hud_Hide( file.editButton )
+
+ SetLabelRuiText( Hud_GetChild( file.menu, "TitanSelectTitle" ), "#MENU_TITAN_SELECT_HINT" )
+ Hud_Show( Hud_GetChild( file.menu, "TitanSelectTitle" ) )
+ Hud_Hide( file.readyPanel )
+ var rui = Hud_GetRui( file.readyPanel )
+ RuiSetBool( rui, "isReady", false )
+
+ Hud_SetFocused( FindValidTitanButton() )
+
+ for ( int i=0; i<file.titanButtons.len(); i++ )
+ {
+ var b = file.titanButtons[i]
+ Hud_Show( b )
+ if ( i == uiGlobal.titanSpawnLoadoutIndex && !Hud_IsLocked( b ) )
+ {
+ Hud_SetFocused( b )
+ }
+ }
+
+ Hud_Hide( Hud_GetChild( file.menu, "UpgradeName" ) )
+
+ UI_SetPresentationType( ePresentationType.TITAN_CENTER )
+ RunMenuClientFunction( "UpdateTitanModel", uiGlobal.titanSpawnLoadoutIndex )
+
+ var subText = Hud_GetChild( file.menu, "MenuSubTitle" )
+ // Hud_Show( subText )
+}
+
+void function EditPilotButton_OnClick( var button )
+{
+ // SetEditLoadout( "pilot", uiGlobal.pilotSpawnLoadoutIndex )
+ // RunMenuClientFunction( "SetEditingPilotLoadoutIndex", uiGlobal.pilotSpawnLoadoutIndex )
+ AdvanceMenu( GetMenu( "EditPilotLoadoutsMenu" ) )
+ RunMenuClientFunction( "HideTTSPanel" )
+}
+
+void function EditTitanButton_OnClick( var button )
+{
+ SetEditLoadout( "titan", uiGlobal.titanSpawnLoadoutIndex )
+ RunMenuClientFunction( "SetEditingTitanLoadoutIndex", uiGlobal.titanSpawnLoadoutIndex )
+ AdvanceMenu( GetMenu( "EditTitanLoadoutMenu" ) )
+ RunMenuClientFunction( "HideTTSPanel" )
+}
+
+bool function CoverIsOff()
+{
+ return !Hud_IsEnabled( file.cover )
+}
+
+bool function TeamTitanSelectMenuIsOpen()
+{
+ return file.menuOpened
+}
+
+bool function TeamTitanSelect_IsReady()
+{
+ return file.isReady
+}
+
+bool function TeamTitanSelect_IsNotReady()
+{
+ return !file.isReady
+}
+
+void function TTSMenuModeFD()
+{
+ file.menuMode = eTTSMenuMode.FD
+
+ foreach ( button in file.titanUpgradeButtons )
+ {
+ Hud_Show( button )
+ }
+}
+
+void function TTSMenuModeDefault()
+{
+ file.menuMode = eTTSMenuMode.DEFAULT
+
+ foreach ( button in file.titanUpgradeButtons )
+ {
+ Hud_Hide( button )
+ }
+
+ var panel = Hud_GetChild( file.menu, "UpgradeName" )
+ Hud_Hide( panel )
+}
+
+var function FindValidTitanButton()
+{
+ foreach ( button in file.titanButtons )
+ {
+ if ( Hud_IsLocked( button ) )
+ continue
+
+ return button
+ }
+
+ return file.titanButtons[0]
+} \ No newline at end of file