aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com>2024-08-14 17:31:06 +0200
committerGitHub <noreply@github.com>2024-08-14 17:31:06 +0200
commitfa4e319c0b60cd68a3dccaa4322e3c35cfa1e385 (patch)
tree4a78391c5008a4aa50d81fbca5d9f26bb475cd96
parentbcec5a5e9edd2a2af3a017ea4b250a9ba1112e6f (diff)
parent7aa3958ccd8e32970736654dfae0c7a87f0798bb (diff)
downloadNorthstarMods-permanent-amped-weapons-fix-pr.tar.gz
NorthstarMods-permanent-amped-weapons-fix-pr.zip
Merge branch 'main' into permanent-amped-weapons-fix-prpermanent-amped-weapons-fix-pr
-rw-r--r--.gitattributes5
-rw-r--r--.github/build/README.md28
-rw-r--r--.github/build/find-missing-translations.js66
-rw-r--r--.github/nativefuncs.json754
-rw-r--r--.github/pull_request_template.md3
-rw-r--r--.github/workflows/add-to-project.yml19
-rw-r--r--.github/workflows/compile-check.yml30
-rw-r--r--.github/workflows/encoding.yml14
-rw-r--r--.github/workflows/merge-conflict-auto-label.yml19
-rw-r--r--Northstar.Client/kb_act.lst13
-rw-r--r--Northstar.Client/keyvalues/resource/fontfiletable.txt10
-rw-r--r--Northstar.Client/mod.json48
-rw-r--r--Northstar.Client/mod/materials/vgui/reset.vmt12
-rw-r--r--Northstar.Client/mod/materials/vgui/reset.vtfbin0 -> 5712 bytes
-rw-r--r--Northstar.Client/mod/resource/Lato-Regular.ttfbin0 -> 657212 bytes
-rw-r--r--Northstar.Client/mod/resource/NorthstarMono.ttfbin0 -> 170600 bytes
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_english.txt75
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_french.txt98
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_german.txt98
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_italian.txt159
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt176
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt34
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt146
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_russian.txt198
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_spanish.txt108
-rw-r--r--Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt87
-rw-r--r--Northstar.Client/mod/resource/ui/menus/mod_settings.menu511
-rw-r--r--Northstar.Client/mod/resource/ui/menus/modlist.menu1003
-rw-r--r--Northstar.Client/mod/resource/ui/menus/panels/mod_setting.res183
-rw-r--r--Northstar.Client/mod/resource/ui/menus/panels/modlist_settings.res79
-rw-r--r--Northstar.Client/mod/resource/ui/menus/server_browser.menu898
-rw-r--r--Northstar.Client/mod/scripts/kb_act.lst54
-rw-r--r--Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut42
-rw-r--r--Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut63
-rw-r--r--Northstar.Client/mod/scripts/vscripts/client/cl_gamestate.gnut8
-rw-r--r--Northstar.Client/mod/scripts/vscripts/client/cl_minimap.gnut942
-rw-r--r--Northstar.Client/mod/scripts/vscripts/client/cl_titan_cockpit.nut1676
-rw-r--r--Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut86
-rw-r--r--Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut42
-rw-r--r--Northstar.Client/mod/scripts/vscripts/sh_menu_models.gnut36
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/_menus.nut2004
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/atlas_auth.nut56
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/chatroom.nut902
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_edit_pilot_loadouts.nut170
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_ingame.nut702
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut109
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_map_select.nut2
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_mod_settings.nut1106
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut21
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut5
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut152
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut710
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut437
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_pilot_loadouts_shared.nut300
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_private_match.nut19
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/ns_slider.nut52
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/openinvites.nut5
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut21
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/ui_mouse_capture.nut60
-rw-r--r--Northstar.Custom/keyvalues/playlists_v2.txt96
-rw-r--r--Northstar.Custom/mod.json69
-rw-r--r--Northstar.Custom/mod/cfg/server/cleanup_gamemode_fw.cfg3
-rw-r--r--Northstar.Custom/mod/cfg/server/setup_gamemode_fw.cfg4
-rw-r--r--Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vmt18
-rw-r--r--Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vtfbin0 -> 524512 bytes
-rw-r--r--Northstar.Custom/mod/models/northstartree/winter_holiday_floor.mdlbin0 -> 311481 bytes
-rw-r--r--Northstar.Custom/mod/models/northstartree/winter_holiday_tree.mdlbin0 -> 3029949 bytes
-rw-r--r--Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdlbin0 -> 1277105 bytes
-rw-r--r--Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdlbin0 -> 924194 bytes
-rw-r--r--Northstar.Custom/mod/models/weapons/titan_arc_rifle/atpov_titan_arc_rifle.mdlbin0 -> 932128 bytes
-rw-r--r--Northstar.Custom/mod/models/weapons/titan_arc_rifle/w_titan_arc_rifle.mdlbin0 -> 726231 bytes
-rw-r--r--Northstar.Custom/mod/models/weapons/titan_triple_threat_og/atpov_titan_triple_threat_og.mdlbin2402701 -> 756461 bytes
-rw-r--r--Northstar.Custom/mod/models/weapons/titan_triple_threat_og/w_titan_triple_threat_og.mdlbin925524 -> 588501 bytes
-rw-r--r--Northstar.Custom/mod/resource/northstar_custom_english.txtbin5110 -> 7376 bytes
-rw-r--r--Northstar.Custom/mod/resource/northstar_custom_portuguese.txtbin5338 -> 6250 bytes
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut18
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/_event_models.gnut21
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/_testing.nut302
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut4
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut6
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut3
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fw.nut2391
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut8
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_hidden.nut79
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut15
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tffa.gnut5
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fw_custom.nut13
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut71
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/melee/sh_melee.gnut1226
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut3
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/sh_damage_types.nut778
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut505
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut11
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/sh_northstar_http_requests.gnut235
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/sh_northstar_safe_io.gnut83
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut2
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/ui/ns_custom_mod_settings.gnut8
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut1033
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/weapons/mp_titanweapon_arc_cannon.nut226
-rw-r--r--Northstar.Custom/mod/scripts/weapons/mp_titanweapon_arc_cannon.txt349
-rw-r--r--Northstar.Custom/mod/scripts/weapons/mp_titanweapon_triplethreat.txt2
-rw-r--r--Northstar.Custom/mod/scripts/weapons/mp_weapon_shotgun_doublebarrel.txt405
-rw-r--r--Northstar.Custom/paks/bt.rpakbin0 -> 308606 bytes
-rw-r--r--Northstar.Custom/paks/bt.starpakbin0 -> 19296360 bytes
-rw-r--r--Northstar.Custom/paks/giftwrap.rpakbin0 -> 264704 bytes
-rw-r--r--Northstar.Custom/paks/giftwrap.starpakbin0 -> 1335384 bytes
-rw-r--r--Northstar.Custom/paks/leaves.rpakbin0 -> 265631 bytes
-rw-r--r--Northstar.Custom/paks/leaves.starpakbin0 -> 3956824 bytes
-rw-r--r--Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.rpakbin0 -> 197556 bytes
-rw-r--r--Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.starpakbin0 -> 9605416 bytes
-rw-r--r--Northstar.Custom/paks/rpak.json10
-rw-r--r--Northstar.Custom/paks/snow.rpakbin0 -> 308548 bytes
-rw-r--r--Northstar.Custom/paks/snow.starpakbin0 -> 4616296 bytes
-rw-r--r--Northstar.Custom/paks/tree_stump.rpakbin0 -> 308670 bytes
-rw-r--r--Northstar.Custom/paks/tree_stump.starpakbin0 -> 4616296 bytes
-rw-r--r--Northstar.Custom/vpk/client_mp_northstar_common.bsp.pak000_000.vpkbin86104271 -> 86805541 bytes
-rw-r--r--Northstar.Custom/vpk/englishclient_mp_northstar_common.bsp.pak000_dir.vpkbin5374 -> 5905 bytes
-rw-r--r--Northstar.CustomServers/keyvalues/playlists_v2.txt13
-rw-r--r--Northstar.CustomServers/mod.json48
-rw-r--r--Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg6
-rw-r--r--Northstar.CustomServers/mod/maps/mp_complex3_script.ent13234
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/battle_chatter.csv24
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/battle_chatter_voices.csv19
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/burn_meter_rewards.csv21
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/burn_meter_store.csv21
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/caller_ids_mp.csv2
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/calling_cards.csv491
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/callsign_icons.csv193
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/camo_skins.csv161
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/community_entries.csv22
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/death_hints_mp.csv381
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/default_pilot_loadouts.csv11
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/default_titan_loadouts.csv8
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/earn_meter_mp.csv31
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/faction_dialogue.csv273
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/faction_leaders.csv8
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/faction_leaders_dropship_anims.csv23
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/fd_awards.csv15
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/features_mp.csv21
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/flightpath_assets.csv8
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/grunt_chatter_mp.csv47
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/non_loadout_weapons.csv29
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pain_death_sounds.csv53
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pilot_abilities.csv16
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pilot_executions.csv14
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pilot_passives.csv10
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pilot_properties.csv8
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_features.csv5
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_mods.csv250
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_mods_common.csv27
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/pilot_weapons.csv33
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/playlist_items.csv27
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/score_events.csv221
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/sp_levels.csv17
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/spectre_chatter_mp.csv8
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/spotlight_images.csv30
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/startpoints.csv313
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_abilities.csv36
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_executions.csv26
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_fd_upgrades.csv50
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_nose_art.csv191
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_os_conversations.csv67
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_passives.csv52
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_primary_mods.csv1
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_primary_mods_common.csv1
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_primary_weapons.csv10
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_properties.csv10
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_skins.csv56
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titan_voices.csv9
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/titans_mp.csv8
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/unlocks_faction_level.csv82
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/unlocks_fd_titan_level.csv26
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/unlocks_player_level.csv996
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/unlocks_random.csv359
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/unlocks_titan_level.csv402
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/unlocks_weapon_level_pilot.csv383
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/weapon_skins.csv35
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/xp_per_faction_level.csv6
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/xp_per_fd_titan_level.csv25
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/xp_per_player_level.csv51
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/xp_per_titan_level.csv21
-rw-r--r--Northstar.CustomServers/mod/scripts/datatable/xp_per_weapon_level.csv21
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_bubble_shield.gnut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_chat.gnut8
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut29
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut9
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut73
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_items.nut26
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut27
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut8
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut5
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut30
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut55
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut13
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut82
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/class/cplayer.nut20
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut195
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter.gnut8
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut236
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut12
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut144
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut162
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut1831
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut58
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut73
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut17
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut33
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut22
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut31
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut49
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut12
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut36
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut1
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype.gnut29
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut89
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_battery_port.gnut220
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut270
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut4
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut191
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut39
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut217
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_model_viewer.nut180
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut159
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut10
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut1064
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut3
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut45
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut9
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut25
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut6
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_powerup.gnut294
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut1154
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_server_to_client_stringcommands.gnut7
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_store.gnut933
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut32
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans.gnut26
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans_drop.gnut17
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut2
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan/class_titan.gnut5
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut25
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut12
-rw-r--r--README.md11
248 files changed, 47563 insertions, 2555 deletions
diff --git a/.gitattributes b/.gitattributes
index f18a3db7..1bb89156 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,4 @@
-/Northstar.Client/mod/resource/*.txt text diff working-tree-encoding=UTF-16LE-BOM \ No newline at end of file
+/Northstar.Client/mod/resource/northstar_client_localisation_*.txt text diff working-tree-encoding=UTF-16LE-BOM
+
+# Highlight `.gnut` like `.nut` files
+*.gnut linguist-language=Squirrel
diff --git a/.github/build/README.md b/.github/build/README.md
new file mode 100644
index 00000000..84d479a3
--- /dev/null
+++ b/.github/build/README.md
@@ -0,0 +1,28 @@
+# Finding missing translations
+
+This package contains a script that detects missing translation keys in Titanfall2 translation files contained in this repository (in the `Northstar.Client/mod/resource` folder).
+
+It uses english translations file as reference.
+
+You have to launch it **from the repository root folder**:
+```shell
+node .github/build/find-missing-translations.js
+```
+The script will then list all missing translations for all supported languages.
+
+If you want to display missing keys for a given language, just add it as an argument:
+```shell
+node .github/build/find-missing-translations.js french
+```
+
+Here's the list of supported languages:
+* english
+* french
+* german
+* italian
+* japanese
+* mspanish
+* portuguese
+* russian
+* spanish
+* tchinese \ No newline at end of file
diff --git a/.github/build/find-missing-translations.js b/.github/build/find-missing-translations.js
new file mode 100644
index 00000000..3f6c6c99
--- /dev/null
+++ b/.github/build/find-missing-translations.js
@@ -0,0 +1,66 @@
+const fs = require('fs');
+const { exit } = require('process');
+const langPath = "Northstar.Client/mod/resource";
+const knownLanguages = ['english', 'french', 'german', 'italian', 'japanese', 'mspanish', 'portuguese', 'russian', 'spanish', 'tchinese'];
+
+
+// Proceed checks before launch
+if (![2,3].includes(process.argv.length)) {
+ console.error('Wrong number of arguments, either call this script with no argument, or with a language.');
+ return;
+}
+const inputLang = process.argv[2];
+if (process.argv.length === 3 && !knownLanguages.includes(inputLang)) {
+ console.error(`"${inputLang}" is not a valid language.\nValid languages are: ${knownLanguages}`);
+ return;
+}
+
+
+// Get language files names
+const langs = fs.readdirSync(langPath)
+ .filter(lang => lang.indexOf('northstar_client_localisation_') !== -1);
+
+
+function getLanguageKeys (lang) {
+ if (knownLanguages.indexOf(lang) === -1) return;
+ return fs.readFileSync(`${langPath}/northstar_client_localisation_${lang}.txt`, {encoding:'utf16le', flag:'r'})
+ .split('\n')
+ .filter(line => line.length !== 0) // remove empty lines
+ .map(line => line.replaceAll(/\s+/g, ' ').trim()) // remove multiple spaces
+ .map(line => line.replaceAll('\t', '')) // remove tabs characters
+
+ // keep only lines with translation keys
+ .filter(line => {
+ const words = line.split('" "');
+ return words.length === 2 && words[1] !== 'english"'
+ })
+ .map(line => line.split('" "')[0].substring(1)); // only keep translation keys (throw values)
+}
+
+// We use english keys as reference for other languages
+const englishKeys = getLanguageKeys('english');
+const inputLanguages = inputLang !== undefined ? ["", inputLang] : [...knownLanguages];
+inputLanguages.shift();
+
+// Check for each language if there are missing keys
+var missingKeysCount = 0;
+
+for (const language of inputLanguages) {
+ const languageKeys = getLanguageKeys(language);
+ const missingKeys = [...englishKeys] // clone
+ .filter(key => languageKeys.indexOf(key) === -1);
+ const missingKeysLength = missingKeys.length;
+ console.log(
+ missingKeysLength === 0
+ ? `✔️ "${language}" doesn't have missing keys.`
+ : `❌ "${language}" has ${missingKeys.length} missing key${missingKeys.length === 1 ? '' : 's'}:`
+ );
+
+ if (missingKeysLength !== 0) {
+ console.log(missingKeys);
+ missingKeysCount += missingKeys.length;
+ }
+}
+
+process.exitCode = missingKeysCount;
+exit();
diff --git a/.github/nativefuncs.json b/.github/nativefuncs.json
new file mode 100644
index 00000000..889432d7
--- /dev/null
+++ b/.github/nativefuncs.json
@@ -0,0 +1,754 @@
+{
+ "SERVER":[
+ {
+ "name":"NSGetModNames",
+ "helpText":"",
+ "returnTypeString":"array<string>",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsModEnabled",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSSetModEnabled",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"string modName, bool enabled"
+ },
+ {
+ "name":"NSGetModDescriptionByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModVersionByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModDownloadLinkByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModLoadPriority",
+ "helpText":"",
+ "returnTypeString":"int",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSIsModRequiredOnClient",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModConvarsByModName",
+ "helpText":"",
+ "returnTypeString":"array<string>",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"DecodeJSON",
+ "helpText":"converts a json string to a squirrel table",
+ "returnTypeString":"table",
+ "argTypes":"string json, bool fatalParseErrors = false"
+ },
+ {
+ "name":"EncodeJSON",
+ "helpText":"converts a squirrel table to a json string",
+ "returnTypeString":"string",
+ "argTypes":"table data"
+ },
+ {
+ "name":"StringToAsset",
+ "helpText":"converts a given string to an asset",
+ "returnTypeString":"asset",
+ "argTypes":"string assetName"
+ },
+ {
+ "name":"NSGetLocalPlayerUID",
+ "helpText":"Returns the local player's uid.",
+ "returnTypeString":"string",
+ "argTypes":""
+ },
+ {
+ "name":"NSEarlyWritePlayerPersistenceForLeave",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"entity player"
+ },
+ {
+ "name":"NSIsWritingPlayerPersistence",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsPlayerLocalPlayer",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":"entity player"
+ },
+ {
+ "name":"NSIsDedicated",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSDisconnectPlayer",
+ "helpText":"Disconnects the player from the server with the given reason",
+ "returnTypeString":"bool",
+ "argTypes":"entity player, string reason"
+ },
+ {
+ "name":"GetUserInfoKVString_Internal",
+ "helpText":"Gets the string value of a given player's userinfo convar by name",
+ "returnTypeString":"string",
+ "argTypes":"entity player, string key, string defaultValue = \"\""
+ },
+ {
+ "name":"GetUserInfoKVAsset_Internal",
+ "helpText":"Gets the asset value of a given player's userinfo convar by name",
+ "returnTypeString":"asset",
+ "argTypes":"entity player, string key, asset defaultValue = $\"\""
+ },
+ {
+ "name":"GetUserInfoKVInt_Internal",
+ "helpText":"Gets the int value of a given player's userinfo convar by name",
+ "returnTypeString":"int",
+ "argTypes":"entity player, string key, int defaultValue = 0"
+ },
+ {
+ "name":"GetUserInfoKVFloat_Internal",
+ "helpText":"Gets the float value of a given player's userinfo convar by name",
+ "returnTypeString":"float",
+ "argTypes":"entity player, string key, float defaultValue = 0"
+ },
+ {
+ "name":"GetUserInfoKVBool_Internal",
+ "helpText":"Gets the bool value of a given player's userinfo convar by name",
+ "returnTypeString":"bool",
+ "argTypes":"entity player, string key, bool defaultValue = false"
+ },
+ {
+ "name":"NSSendMessage",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"int playerIndex, string text, bool isTeam"
+ },
+ {
+ "name":"NSBroadcastMessage",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"int fromPlayerIndex, int toPlayerIndex, string text, bool isTeam, bool isDead, int messageType"
+ },
+ {
+ "name":"NSGetCurrentModName",
+ "helpText":"Returns the mod name of the script running this function",
+ "returnTypeString":"string",
+ "argTypes":""
+ },
+ {
+ "name":"NSGetCallingModName",
+ "helpText":"Returns the mod name of the script running this function",
+ "returnTypeString":"string",
+ "argTypes":"int depth = 0"
+ },
+ {
+ "name":"NS_InternalMakeHttpRequest",
+ "helpText":"[Internal use only] Passes the HttpRequest struct fields to be reconstructed in native and used for an http request",
+ "returnTypeString":"int",
+ "argTypes":"int method, string baseUrl, table<string, array<string> > headers, table<string, array<string> > queryParams, string contentType, string body, int timeout, string userAgent"
+ },
+ {
+ "name":"NSIsHttpEnabled",
+ "helpText":"Whether or not HTTP requests are enabled. You can opt-out by starting the game with -disablehttprequests.",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsLocalHttpAllowed",
+ "helpText":"Whether or not HTTP requests can be made to a private network address. You can enable this by starting the game with -allowlocalhttp.",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NS_InternalLoadFile",
+ "helpText":"Loads a file asynchronously.",
+ "returnTypeString":"int",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSSaveFile",
+ "helpText":"Saves a file.",
+ "returnTypeString":"void",
+ "argTypes":"string file, string data"
+ },
+ {
+ "name":"NSSaveJSONFile",
+ "helpText":"Converts a squirrel table to a json string, then saves it to a file.",
+ "returnTypeString":"void",
+ "argTypes":"string file, table data"
+ },
+ {
+ "name":"NSDoesFileExist",
+ "helpText":"Checks whether or not a file exists.",
+ "returnTypeString":"bool",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSDeleteFile",
+ "helpText":"Deletes a file.",
+ "returnTypeString":"bool",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NS_InternalGetAllFiles",
+ "helpText":"Returns an array of all files in a mod's save folder.",
+ "returnTypeString":"array<string>",
+ "argTypes":"string path"
+ },
+ {
+ "name":"NSGetFileSize",
+ "helpText":"Returns the size of a file, in KB, rounded down.",
+ "returnTypeString":"int",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSIsFolder",
+ "helpText":"Returns whether or not a given path leads to a folder.",
+ "returnTypeString":"bool",
+ "argTypes":"string path"
+ }
+ ],
+ "CLIENT":[
+ {
+ "name":"NSChatWrite",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"int context, string text"
+ },
+ {
+ "name":"NSChatWriteRaw",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"int context, string text"
+ },
+ {
+ "name":"NSChatWriteLine",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"int context, string text"
+ },
+ {
+ "name":"NSGetModNames",
+ "helpText":"",
+ "returnTypeString":"array<string>",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsModEnabled",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSSetModEnabled",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"string modName, bool enabled"
+ },
+ {
+ "name":"NSGetModDescriptionByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModVersionByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModDownloadLinkByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModLoadPriority",
+ "helpText":"",
+ "returnTypeString":"int",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSIsModRequiredOnClient",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModConvarsByModName",
+ "helpText":"",
+ "returnTypeString":"array<string>",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"DecodeJSON",
+ "helpText":"converts a json string to a squirrel table",
+ "returnTypeString":"table",
+ "argTypes":"string json, bool fatalParseErrors = false"
+ },
+ {
+ "name":"EncodeJSON",
+ "helpText":"converts a squirrel table to a json string",
+ "returnTypeString":"string",
+ "argTypes":"table data"
+ },
+ {
+ "name":"StringToAsset",
+ "helpText":"converts a given string to an asset",
+ "returnTypeString":"asset",
+ "argTypes":"string assetName"
+ },
+ {
+ "name":"NSGetLocalPlayerUID",
+ "helpText":"Returns the local player's uid.",
+ "returnTypeString":"string",
+ "argTypes":""
+ },
+ {
+ "name":"NSGetCurrentModName",
+ "helpText":"Returns the mod name of the script running this function",
+ "returnTypeString":"string",
+ "argTypes":""
+ },
+ {
+ "name":"NSGetCallingModName",
+ "helpText":"Returns the mod name of the script running this function",
+ "returnTypeString":"string",
+ "argTypes":"int depth = 0"
+ },
+ {
+ "name":"NSUpdateGameStateClient",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit"
+ },
+ {
+ "name":"NSUpdateServerInfoReload",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"int maxPlayers"
+ },
+ {
+ "name":"NSUpdateTimeInfo",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"float timeInFuture"
+ },
+ {
+ "name":"NS_InternalMakeHttpRequest",
+ "helpText":"[Internal use only] Passes the HttpRequest struct fields to be reconstructed in native and used for an http request",
+ "returnTypeString":"int",
+ "argTypes":"int method, string baseUrl, table<string, array<string> > headers, table<string, array<string> > queryParams, string contentType, string body, int timeout, string userAgent"
+ },
+ {
+ "name":"NSIsHttpEnabled",
+ "helpText":"Whether or not HTTP requests are enabled. You can opt-out by starting the game with -disablehttprequests.",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsLocalHttpAllowed",
+ "helpText":"Whether or not HTTP requests can be made to a private network address. You can enable this by starting the game with -allowlocalhttp.",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NS_InternalLoadFile",
+ "helpText":"Loads a file asynchronously.",
+ "returnTypeString":"int",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSSaveFile",
+ "helpText":"Saves a file.",
+ "returnTypeString":"void",
+ "argTypes":"string file, string data"
+ },
+ {
+ "name":"NSSaveJSONFile",
+ "helpText":"Converts a squirrel table to a json string, then saves it to a file.",
+ "returnTypeString":"void",
+ "argTypes":"string file, table data"
+ },
+ {
+ "name":"NSDoesFileExist",
+ "helpText":"Checks whether or not a file exists.",
+ "returnTypeString":"bool",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSDeleteFile",
+ "helpText":"Deletes a file.",
+ "returnTypeString":"bool",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NS_InternalGetAllFiles",
+ "helpText":"Returns an array of all files in a mod's save folder.",
+ "returnTypeString":"array<string>",
+ "argTypes":"string path"
+ },
+ {
+ "name":"NSGetFileSize",
+ "helpText":"Returns the size of a file, in KB, rounded down.",
+ "returnTypeString":"int",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSIsFolder",
+ "helpText":"Returns whether or not a given path leads to a folder.",
+ "returnTypeString":"bool",
+ "argTypes":"string path"
+ }
+ ],
+ "UI":[
+ {
+ "name":"NSGetCursorPosition",
+ "helpText":"",
+ "returnTypeString":"vector ornull",
+ "argTypes":""
+ },
+ {
+ "name":"NSRequestCustomMainMenuPromos",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":""
+ },
+ {
+ "name":"NSHasCustomMainMenuPromoData",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSGetCustomMainMenuPromoData",
+ "helpText":"",
+ "returnTypeString":"var",
+ "argTypes":"int promoDataKey"
+ },
+ {
+ "name":"NSGetModNames",
+ "helpText":"",
+ "returnTypeString":"array<string>",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsModEnabled",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSSetModEnabled",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"string modName, bool enabled"
+ },
+ {
+ "name":"NSGetModDescriptionByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModVersionByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModDownloadLinkByModName",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModLoadPriority",
+ "helpText":"",
+ "returnTypeString":"int",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSIsModRequiredOnClient",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":"string modName"
+ },
+ {
+ "name":"NSGetModConvarsByModName",
+ "helpText":"",
+ "returnTypeString":"array<string>",
+ "argTypes":"string modName"
+ },
+ {
+ "name": "NSFetchVerifiedModsManifesto",
+ "helpText": "Retrieves the verified mods list from the central authority (GitHub).",
+ "returnTypeString": "void",
+ "argTypes": ""
+
+ },
+ {
+ "name": "NSIsModDownloadable",
+ "helpText": "checks whether a mod is verified and can be auto-downloaded",
+ "returnTypeString": "bool",
+ "argTypes": "string name, string version"
+
+ },
+ {
+ "name": "NSDownloadMod",
+ "helpText": "downloads a given mod from distant API to local game profile",
+ "returnTypeString": "void",
+ "argTypes": "string name, string version"
+ },
+ {
+ "name": "NSGetModInstallState",
+ "helpText": "get status of the mod currently being installed",
+ "returnTypeString": "ModInstallState",
+ "argTypes": ""
+ },
+ {
+ "name":"NSReloadMods",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsMasterServerAuthenticated",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSRequestServerList",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsRequestingServerList",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSMasterServerConnectionSuccessful",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSGetServerCount",
+ "helpText":"",
+ "returnTypeString":"int",
+ "argTypes":""
+ },
+ {
+ "name": "NSGetGameServers",
+ "helpText": "Gets all fetched servers",
+ "returnTypeString": "array<ServerInfo>",
+ "argTypes": ""
+ },
+ {
+ "name":"NSClearRecievedServerList",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":""
+ },
+ {
+ "name":"NSTryAuthWithServer",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"int serverIndex, string password = ''"
+ },
+ {
+ "name":"NSIsAuthenticatingWithServer",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSWasAuthSuccessful",
+ "helpText":"",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSConnectToAuthedServer",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":""
+ },
+ {
+ "name":"NSTryAuthWithLocalServer",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":""
+ },
+ {
+ "name":"NSCompleteAuthWithLocalServer",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":""
+ },
+ {
+ "name":"NSGetAuthFailReason",
+ "helpText":"",
+ "returnTypeString":"string",
+ "argTypes":""
+ },
+ {
+ "name":"DecodeJSON",
+ "helpText":"converts a json string to a squirrel table",
+ "returnTypeString":"table",
+ "argTypes":"string json, bool fatalParseErrors = false"
+ },
+ {
+ "name":"EncodeJSON",
+ "helpText":"converts a squirrel table to a json string",
+ "returnTypeString":"string",
+ "argTypes":"table data"
+ },
+ {
+ "name":"StringToAsset",
+ "helpText":"converts a given string to an asset",
+ "returnTypeString":"asset",
+ "argTypes":"string assetName"
+ },
+ {
+ "name":"NSGetLocalPlayerUID",
+ "helpText":"Returns the local player's uid.",
+ "returnTypeString":"string",
+ "argTypes":""
+ },
+ {
+ "name":"NSGetCurrentModName",
+ "helpText":"Returns the mod name of the script running this function",
+ "returnTypeString":"string",
+ "argTypes":""
+ },
+ {
+ "name":"NSGetCallingModName",
+ "helpText":"Returns the mod name of the script running this function",
+ "returnTypeString":"string",
+ "argTypes":"int depth = 0"
+ },
+ {
+ "name":"NSUpdateGameStateUI",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading"
+ },
+ {
+ "name":"NSUpdateServerInfo",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string playlistDisplayName"
+ },
+ {
+ "name":"NSSetLoading",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":"bool loading"
+ },
+ {
+ "name":"NSUpdateListenServer",
+ "helpText":"",
+ "returnTypeString":"void",
+ "argTypes":""
+ },
+ {
+ "name":"NS_InternalMakeHttpRequest",
+ "helpText":"[Internal use only] Passes the HttpRequest struct fields to be reconstructed in native and used for an http request",
+ "returnTypeString":"int",
+ "argTypes":"int method, string baseUrl, table<string, array<string> > headers, table<string, array<string> > queryParams, string contentType, string body, int timeout, string userAgent"
+ },
+ {
+ "name":"NSIsHttpEnabled",
+ "helpText":"Whether or not HTTP requests are enabled. You can opt-out by starting the game with -disablehttprequests.",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NSIsLocalHttpAllowed",
+ "helpText":"Whether or not HTTP requests can be made to a private network address. You can enable this by starting the game with -allowlocalhttp.",
+ "returnTypeString":"bool",
+ "argTypes":""
+ },
+ {
+ "name":"NS_InternalLoadFile",
+ "helpText":"Loads a file asynchronously.",
+ "returnTypeString":"int",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSSaveFile",
+ "helpText":"Saves a file.",
+ "returnTypeString":"void",
+ "argTypes":"string file, string data"
+ },
+ {
+ "name":"NSSaveJSONFile",
+ "helpText":"Converts a squirrel table to a json string, then saves it to a file.",
+ "returnTypeString":"void",
+ "argTypes":"string file, table data"
+ },
+ {
+ "name":"NSDoesFileExist",
+ "helpText":"Checks whether or not a file exists.",
+ "returnTypeString":"bool",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSDeleteFile",
+ "helpText":"Deletes a file.",
+ "returnTypeString":"bool",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NS_InternalGetAllFiles",
+ "helpText":"Returns an array of all files in a mod's save folder.",
+ "returnTypeString":"array<string>",
+ "argTypes":"string path"
+ },
+ {
+ "name":"NSGetFileSize",
+ "helpText":"Returns the size of a file, in KB, rounded down.",
+ "returnTypeString":"int",
+ "argTypes":"string file"
+ },
+ {
+ "name":"NSIsFolder",
+ "helpText":"Returns whether or not a given path leads to a folder.",
+ "returnTypeString":"bool",
+ "argTypes":"string path"
+ },
+ {
+ "name":"NSGetMasterServerAuthResult",
+ "helpText":"",
+ "returnTypeString":"MasterServerAuthResult",
+ "argTypes":""
+ }
+ ]
+} \ No newline at end of file
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 871b946e..8cda06a3 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -3,6 +3,9 @@ WHEN OPENING A PULL REQUEST KEEP IN MIND:
-> If the changes you made can be summarised in a screenshot, add one (e.g. you changed the layout of an in-game menu)
-> If the changes you made can be summarised in a screenrecording, add one (e.g. proof that you fixed a certain bug)
+-> For fixes, description on how to reproduce the bug are appreciated and help your PR get merged faster
+-> For features, description on how to use or a sample mod that makes use of the feature is appreciated and will help your PR get merged faster
+
-> Please use a sensible title for your pull request
-> Please describe the changes you made. The easier it is to understand what you changed, the higher the chances of your PR being merged (in a timely manner).
diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml
new file mode 100644
index 00000000..773a52b8
--- /dev/null
+++ b/.github/workflows/add-to-project.yml
@@ -0,0 +1,19 @@
+name: add-to-project
+
+on:
+ issues:
+ types:
+ - opened
+ pull_request_target:
+ types:
+ - opened
+
+jobs:
+ add-to-project:
+ name: Add to project
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/add-to-project@v0.5.0
+ with:
+ project-url: "https://github.com/orgs/R2Northstar/projects/3"
+ github-token: "${{ secrets.ADD_TO_PROJECT_PAT }}"
diff --git a/.github/workflows/compile-check.yml b/.github/workflows/compile-check.yml
new file mode 100644
index 00000000..cb7ab1d0
--- /dev/null
+++ b/.github/workflows/compile-check.yml
@@ -0,0 +1,30 @@
+# This action checks whether all Squirrel files compile successfully using standalone Squirrel compiler
+name: compile-check
+
+on: [push, pull_request]
+
+jobs:
+ compile:
+ runs-on: windows-latest
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v3
+ with:
+ path: "mods"
+
+ - name: Compile Scripts
+ uses: ASpoonPlaysGames/squirrel-re-compiler@v3
+ with:
+ mods-directory: "${{ github.workspace }}/mods"
+ native-json: "${{ github.workspace }}/mods/.github/nativefuncs.json"
+
+ # It's important that scripts compile when Northstar.Custom isn't enabled/installed, so run again without it
+ - name: Remove Northstar.Custom
+ run: rmdir ${{ github.workspace }}\mods\Northstar.Custom /s /q
+ shell: cmd
+
+ - name: Compile Scripts (No Northstar.Custom)
+ uses: ASpoonPlaysGames/squirrel-re-compiler@v3
+ with:
+ mods-directory: "${{ github.workspace }}/mods"
+ native-json: "${{ github.workspace }}/mods/.github/nativefuncs.json"
diff --git a/.github/workflows/encoding.yml b/.github/workflows/encoding.yml
index 0b54639e..5a730c20 100644
--- a/.github/workflows/encoding.yml
+++ b/.github/workflows/encoding.yml
@@ -3,11 +3,19 @@ on: [push, pull_request]
jobs:
check-loc-encoding:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Check localization files encoding
run: |
- files=$(ls Northstar.Client/mod/resource/*.txt)
+ files=$(ls Northstar.Client/mod/resource/northstar_client_localisation_*.txt)
IFS=$'\n'; files=($files); unset IFS; ! file --mime "${files[@]}" | grep -v "charset=utf-16le"
+ check-missing-translations:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Look out for missing translations
+ run: node .github/build/find-missing-translations.js
+ continue-on-error: true
diff --git a/.github/workflows/merge-conflict-auto-label.yml b/.github/workflows/merge-conflict-auto-label.yml
new file mode 100644
index 00000000..abb7cabd
--- /dev/null
+++ b/.github/workflows/merge-conflict-auto-label.yml
@@ -0,0 +1,19 @@
+name: Merge Conflict Auto Label
+on:
+ workflow_dispatch: # Manual run
+ push:
+ branches:
+ - main
+ schedule:
+ - cron: "10 21 * * *" # Runs at 21:10; time was chosen based on contributor activity and low GitHub Actions cron load.
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: mschilde/auto-label-merge-conflicts@master
+ with:
+ CONFLICT_LABEL_NAME: "merge conflicts"
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ MAX_RETRIES: 5
+ WAIT_MS: 5000
diff --git a/Northstar.Client/kb_act.lst b/Northstar.Client/kb_act.lst
new file mode 100644
index 00000000..1a4f7129
--- /dev/null
+++ b/Northstar.Client/kb_act.lst
@@ -0,0 +1,13 @@
+"blank" "=========================="
+"blank" "NORTHSTAR"
+"blank" "=========================="
+"toggleconsole" "Toggle Developer Console"
+"vote 1" "Vote 1"
+"vote 2" "Vote 2"
+"vote 3" "Vote 3"
+"vote 4" "Vote 4"
+"vote 5" "Vote 5"
+"vote 6" "Vote 6"
+"vote 7" "Vote 7"
+"vote 8" "Vote 8"
+"vote 9" "Vote 9"
diff --git a/Northstar.Client/keyvalues/resource/fontfiletable.txt b/Northstar.Client/keyvalues/resource/fontfiletable.txt
new file mode 100644
index 00000000..f0b91c07
--- /dev/null
+++ b/Northstar.Client/keyvalues/resource/fontfiletable.txt
@@ -0,0 +1,10 @@
+FontFileTable
+{
+ "arial unicode ms" "resource/Lato-Regular.ttf"
+
+ "lucida console" "resource/NorthstarMono.ttf" [$PC]
+
+ "arial" "resource/Lato-Regular.ttf"
+ "arial bold" "resource/Lato-Regular.ttf"
+ "arial narrow" "resource/Lato-Regular.ttf"
+}
diff --git a/Northstar.Client/mod.json b/Northstar.Client/mod.json
index 17b01543..44937a2b 100644
--- a/Northstar.Client/mod.json
+++ b/Northstar.Client/mod.json
@@ -1,10 +1,15 @@
{
"Name": "Northstar.Client",
"Description": "Various ui and client changes to fix bugs and add better support for mods",
- "Version": "1.9.0",
+ "Version": "1.19.0",
"LoadPriority": 0,
+ "InitScript": "cl_northstar_client_init.nut",
"ConVars": [
{
+ "Name": "allow_mod_auto_download",
+ "DefaultValue": "0"
+ },
+ {
"Name": "filter_hide_empty",
"DefaultValue": "0"
},
@@ -31,15 +36,22 @@
{
"Name": "filter_map_hide_locked",
"DefaultValue": "0"
+ },
+ {
+ "Name": "modlist_show_convars",
+ "DefaultValue": "0",
+ "Flags": "ARCHIVE_PLAYERPROFILE"
+ },
+ {
+ "Name": "modlist_reverse",
+ "DefaultValue": "0",
+ "Flags": "ARCHIVE_PLAYERPROFILE"
}
],
"Scripts": [
{
"Path": "_custom_codecallbacks_client.gnut",
- "RunOn": "CLIENT",
- "ClientCallback": {
- "Before": "NSSetupChathooksClient"
- }
+ "RunOn": "CLIENT"
},
{
"Path": "client/cl_chat.gnut",
@@ -75,6 +87,10 @@
}
},
{
+ "Path": "ui/menu_ns_moddownload.nut",
+ "RunOn": "UI"
+ },
+ {
"Path": "ui/menu_ns_serverbrowser.nut",
"RunOn": "UI",
"UICallback": {
@@ -105,6 +121,28 @@
{
"Path": "ui/controller_prompts.nut",
"RunOn": "UI"
+ },
+ {
+ "Path": "ui/ns_slider.nut",
+ "RunOn": "UI"
+ },
+ {
+ "Path": "ui/menu_mod_settings.nut",
+ "RunOn": "UI",
+ "UICallback":{
+ "Before": "AddModSettingsMenu"
+ }
+ },
+ {
+ "Path": "ui/ui_mouse_capture.nut",
+ "RunOn": "UI"
+ },
+ {
+ "Path": "ui/atlas_auth.nut",
+ "RunOn": "UI",
+ "UICallback": {
+ "After": "AtlasAuthDialog"
+ }
}
],
"Localisation": [
diff --git a/Northstar.Client/mod/materials/vgui/reset.vmt b/Northstar.Client/mod/materials/vgui/reset.vmt
new file mode 100644
index 00000000..84034586
--- /dev/null
+++ b/Northstar.Client/mod/materials/vgui/reset.vmt
@@ -0,0 +1,12 @@
+"Basic"
+{
+ "$basetexture" "vgui/reset"
+ "$translucent" 1
+ "$vertexcolor" 1
+ "$vertexalpha" 1
+ "$no_fullbright" 1
+ "$ignorez" 1
+ "$nolod" 1
+
+ "$SHADERSRGBREAD360" 1
+}
diff --git a/Northstar.Client/mod/materials/vgui/reset.vtf b/Northstar.Client/mod/materials/vgui/reset.vtf
new file mode 100644
index 00000000..5ffb86a9
--- /dev/null
+++ b/Northstar.Client/mod/materials/vgui/reset.vtf
Binary files differ
diff --git a/Northstar.Client/mod/resource/Lato-Regular.ttf b/Northstar.Client/mod/resource/Lato-Regular.ttf
new file mode 100644
index 00000000..adbfc467
--- /dev/null
+++ b/Northstar.Client/mod/resource/Lato-Regular.ttf
Binary files differ
diff --git a/Northstar.Client/mod/resource/NorthstarMono.ttf b/Northstar.Client/mod/resource/NorthstarMono.ttf
new file mode 100644
index 00000000..2814eee6
--- /dev/null
+++ b/Northstar.Client/mod/resource/NorthstarMono.ttf
Binary files differ
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
index 8c6cadcf..baceed22 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt
@@ -227,6 +227,7 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a
"classic_rodeo" "Classic Rodeo"
"oob_timer_enabled" "Out of Bounds Timer"
"riff_instagib" "Instagib Mode"
+ "player_force_respawn" "Forced Respawn"
"riff_player_bleedout" "Pilot Bleedout"
"player_bleedout_forceHolster" "Holster weapons when downed"
@@ -276,7 +277,7 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a
"PLAYERS_COLUMN" "Players"
"MAP_COLUMN" "Map"
"GAMEMODE_COLUMN" "Gamemode"
- "LATENCY_COLUMN" "Latency"
+ "REGION_COLUMN" "Region"
"SEARCHBAR_LABEL" "Search:"
"MAP_FILTER" "Map"
"GAMEMODE_FILTER" "Gamemode"
@@ -301,6 +302,9 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a
"SHOW_ALL" "All"
"SHOW_ONLY_ENABLED" "Only Enabled"
"SHOW_ONLY_DISABLED" "Only Disabled"
+ "SHOW_ONLY_NOT_REQUIRED" "Only Optional Mods"
+ "SHOW_ONLY_REQUIRED" "Only Required Mods"
+ "MOD_REQUIRED_WARNING" " : This mod may get (un)loaded when joining a server"
// Maps menu
"HIDE_LOCKED" "Hide locked"
@@ -316,8 +320,75 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a
"UNAUTHORIZED_PWD" "Wrong password"
"STRYDER_RESPONSE" "Couldn't parse stryder response"
"PLAYER_NOT_FOUND" "Couldn't find player account"
- "INVALID_MASTERSERVER_TOKEN" "Invalid or expired masterserver token"
+ "INVALID_MASTERSERVER_TOKEN" "Invalid or expired masterserver token, try restarting EA App."
"JSON_PARSE_ERROR" "Error parsing json response"
"UNSUPPORTED_VERSION" "The version you are using is no longer supported"
+
+ "AUTHENTICATION_FAILED_HEADER" "Authentication Failed"
+ "AUTHENTICATION_FAILED_BODY" "Failed to authenticate with Atlas!"
+ "AUTHENTICATION_FAILED_ERROR_CODE" "Error code: ^DB6F2C00%s1^"
+ "AUTHENTICATION_FAILED_HELP" "Help"
+
+ // Mod Settings
+ "MOD_SETTINGS" "Mod Settings"
+ "NORTHSTAR_BASE_SETTINGS" "Northstar Base Settings"
+ "ONLY_HOST_MATCH_SETTINGS" "Only Host can change Private Match settings"
+ "ONLY_HOST_CAN_START_MATCH" "Only Host can Start the Match"
+ "MATCH_COUNTDOWN_LENGTH" "Private Match Countdown Duration"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "Log Unknown Client Commands"
+ "DISALLOWED_TACTICALS" "Prohibited Tacticals"
+ "TACTICAL_REPLACEMENT" "Replacement Tactical"
+ "DISALLOWED_WEAPONS" "Prohibited Weapons"
+ "REPLACEMENT_WEAPON" "Replacement Weapon"
+ "SHOULD_RETURN_TO_LOBBY" "Return To Lobby After Match End"
+ "ARE_YOU_SURE" "Are you sure?"
+ "WILL_RESET_ALL_SETTINGS" "This will reset ALL settings that belong to this category.\n\nThis is not revertable."
+ "WILL_RESET_SETTING" "This will reset the %s1 setting to it's default value.\n\nThis is not revertable."
+ "MOD_SETTINGS_SERVER" "Server"
+ "MOD_SETTINGS_RESET" "Reset"
+ "MOD_SETTINGS_RESET_ALL" "Reset All"
+ "NO_RESULTS" "No results."
+ "NO_MODS" "No settings available! Install more mods at ^5588FF00northstar.thunderstore.io^0."
+
+ // Toggleable progression
+ "TOGGLE_PROGRESSION" "Toggle Progression"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Toggle Progression"
+
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "Disable Progression?"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "Titans, Weapons, Factions, Skins, etc. will all be unlocked and usable at any time.\n\nThis can be changed at any time in the multiplayer lobby."
+
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "Enable Progression?"
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "Titans, Weapons, Factions, Skins, etc. will need to be unlocked by levelling up, or bought with Merits.\n\nThis can be changed at any time in the multiplayer lobby.\n\n^CC000000Warning: if you have currently equipped any items that you do not have unlocked, they will be reset!"
+
+ "PROGRESSION_ENABLED_HEADER" "Progression Enabled!"
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000Progression has been enabled.^\n\nTitans, Weapons, Factions, Skins, etc. will need to be unlocked by levelling up, or bought with Merits.\n\nThis can be changed at any time in the multiplayer lobby."
+
+ "PROGRESSION_DISABLED_HEADER" "Progression Disabled!"
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000Progression has been disabled.^\n\nTitans, Weapons, Factions, Skins, etc. will all be unlocked and usable at any time.\n\nThis can be changed at any time in the multiplayer lobby."
+
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000Progression can now be enabled!^\n\nNorthstar now supports vanilla progression, meaning you can choose to unlock Weapons, Skins, Titans, etc. through levelling up and completing challenges.\n\nYou can enable progression using the button at the bottom of the lobby screen.\n\nThis can be changed at any time."
+
+ // Mod downloading
+ "MISSING_MOD" "Missing mod \"%s1\" v%s2"
+ "WRONG_MOD_VERSION" "Server has mod \"%s1\" v%s2 while you have v%s3"
+ "MOD_NOT_VERIFIED" "(mod is not verified, and couldn't be downloaded automatically)"
+ "MOD_DL_DISABLED" "(automatic mod downloading is disabled)"
+ "MANIFESTO_FETCHING_TITLE" "Setting up mod download"
+ "MANIFESTO_FETCHING_TEXT" "Retrieving the list of verified mods..."
+ "DOWNLOADING_MOD_TITLE" "Downloading mod"
+ "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Downloading mod (%s1%)"
+ "DOWNLOADING_MOD_TEXT" "Downloading %s1 v%s2..."
+ "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Downloading %s1 v%s2...\n(%s3/%s4 MB)"
+ "CHECKSUMING_TITLE" "Checksuming mod"
+ "CHECKSUMING_TEXT" "Verifying contents of %s1 v%s2..."
+ "EXTRACTING_MOD_TITLE" "Extracting mod (%s1%)"
+ "EXTRACTING_MOD_TEXT" "Extracting %s1 v%s2...\n(%s3/%s4 MB)"
+ "FAILED_DOWNLOADING" "Failed downloading mod"
+ "FAILED_READING_ARCHIVE" "An error occurred while reading mod archive."
+ "FAILED_WRITING_TO_DISK" "An error occurred while extracting mod files to the filesystem."
+ "MOD_FETCHING_FAILED" "Mod archive could not be downloaded from Thunderstore."
+ "MOD_CORRUPTED" "Downloaded archive checksum does not match verified signature."
+ "NO_DISK_SPACE_AVAILABLE" "There is not enough space on your disk."
+ "MOD_FETCHING_FAILED_GENERAL" "Mod extraction failed. Check logs for more details."
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_french.txt b/Northstar.Client/mod/resource/northstar_client_localisation_french.txt
index 0d4786a0..9444a39e 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_french.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_french.txt
@@ -64,7 +64,7 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"earn_meter_pilot_multiplier" "Multiplicateur de boost pilote"
"earn_meter_titan_multiplier" "Multiplicateur de boost titan"
- "aegis_upgrades" "Aegis Upgrades"
+ "aegis_upgrades" "Upgrades Aegis"
"infinite_doomed_state" "Etat condamné infini"
"titan_shield_regen" "Régénération des boucliers"
@@ -82,15 +82,17 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"cp_amped_capture_points" "Points de capture améliorés"
"coliseum_loadouts_enabled" "Equipements du Colisée"
+ "aitdm_archer_grunts" "Soldats (Archer)"
+
// northstar.custom localisation is just deciding not to work, so putting it here for now
"PL_sbox" "Bac Ă  sable"
- "PL_sbox_lobby" "Lobby: Bac Ă  sable"
- "PL_sbox_desc" "GMod, mais en pire..."
+ "PL_sbox_lobby" "Lobby : Bac à sable"
+ "PL_sbox_desc" "GMod, mais en pire"
"PL_sbox_abbr" "SBOX"
"GAMEMODE_SBOX" "Bac Ă  sable"
"PL_gg" "Gun game"
- "PL_gg_lobby" "Lobby: Gun game"
+ "PL_gg_lobby" "Lobby : Gun game"
"PL_gg_desc" "Obtenez une nouvelle arme Ă  chaque frag.\nTuez un pilote avec chaque arme pour gagner."
"PL_gg_hint" "Obtenez une nouvelle arme Ă  chaque frag.\nTuez un pilote avec chaque arme pour gagner."
"PL_gg_abbr" "GG"
@@ -100,21 +102,21 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"gg_execution_reward" "Récompense pourcentage d'exécutions"
"PL_tt" "Titan tag"
- "PL_tt_lobby" "Lobby: Titan tag"
+ "PL_tt_lobby" "Lobby : Titan tag"
"PL_tt_desc" "Gagnez des points lorsque vous ĂŞtes dans votre titan.\nDĂ©truisez un titan pour obtenir le vĂ´tre."
"PL_tt_hint" "Gagnez des points lorsque vous ĂŞtes dans votre titan.\nDĂ©truisez un titan pour obtenir le vĂ´tre."
"PL_tt_abbr" "TT"
"GAMEMODE_TT" "Titan tag"
"PL_chamber" "Le professionnel"
- "PL_chamber_lobby" "Lobby: Le professionnel"
+ "PL_chamber_lobby" "Lobby : Le professionnel"
"PL_chamber_desc" "Un tir, un mort.\nObtenez une balle en tuant un pilote."
"PL_chamber_hint" "Un tir, un mort.\nObtenez une balle en tuant un pilote."
"PL_chamber_abbr" "CHAMBER"
"GAMEMODE_CHAMBER" "Le professionnel"
"PL_hidden" "Chasse"
- "PL_hidden_lobby" "Lobby: Chasse"
+ "PL_hidden_lobby" "Lobby : Chasse"
"PL_hidden_desc" "Un pilote est invisible et chasse les autres."
"PL_hidden_hint" "Un pilote est invisible et chasse les autres."
"PL_hidden_abbr" "HIDDEN"
@@ -124,7 +126,7 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"HIDDEN_FIRST_HIDDEN" "%s1 est le chasseur."
"PL_sns" "Sticks and Stones"
- "PL_sns_lobby" "Lobby: Sticks and Stones"
+ "PL_sns_lobby" "Lobby : Sticks and Stones"
"PL_sns_desc" "Chacun pour soi.\nLes exécutions et les lames à impulsions réinitialisent le score de vos ennemis."
"PL_sns_abbr" "SNS"
"GAMEMODE_SNS" "Sticks and Stones"
@@ -142,7 +144,7 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"sns_softball_enabled" "Softball activé"
"PL_inf" "Infection"
- "PL_inf_lobby" "Lobby: Infection"
+ "PL_inf_lobby" "Lobby : Infection"
"PL_inf_desc" "Les pilotes survivants deviennent infectés lorsque tués."
"PL_inf_hint" "Les pilotes survivants deviennent infectés lorsque tués."
"PL_inf_abbr" "INF"
@@ -156,14 +158,14 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"INFECTION_SURVIVE_LAST_SURVIVOR" "Survivez."
"PL_tffa" "Chacun pour soi (titans)"
- "PL_tffa_lobby" "Lobby: Chacun pour soi (titans)"
+ "PL_tffa_lobby" "Lobby : Chacun pour soi (titans)"
"PL_tffa_desc" "Chacun pour soi.\nDĂ©truisez les titans ennemis."
"PL_tffa_hint" "Chacun pour soi.\nDĂ©truisez les titans ennemis."
"PL_tffa_abbr" "TFFA"
"GAMEMODE_TFFA" "Chacun pour soi (titans)"
"PL_hs" "Cache-cache"
- "PL_hs_lobby" "Lobby: Cache-cache"
+ "PL_hs_lobby" "Lobby : Cache-cache"
"PL_hs_desc" "Les pilotes doivent se cacher pour ne pas être trouvés par l'un d'eux."
"PL_hs_hint" "Les pilotes doivent se cacher pour ne pas être trouvés par l'un d'eux."
"PL_hs_abbr" "HS"
@@ -183,13 +185,13 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
// 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" "Guerre pour la Frontière"
"PL_fw" "Guerre pour la Frontière"
- "PL_fw_lobby" "Lobby: Guerre pour la Frontière"
+ "PL_fw_lobby" "Lobby : Guerre pour la Frontière"
"PL_fw_desc" "Détruisez le collecteur ennemi et protégez le vôtre."
"PL_fw_abbr" "FW"
"GAMEMODE_kr" "Course aux frags"
"PL_kr" "Course aux frags"
- "PL_kr_lobby" "Lobby: Course aux frags"
+ "PL_kr_lobby" "Lobby : Course aux frags"
"PL_kr_desc" "Capturez le drapeau pour devenir le prédateur.\nFaites des victimes pour établir un nouveau record de frags.\nLe pilote ayant le plus de frags à la fin de la partie l'emporte."
"PL_kr_hint" "Capturez le drapeau pour devenir le prédateur.\nFaites des victimes pour établir un nouveau record de frags.\nLe pilote ayant le plus de frags à la fin de la partie l'emporte."
"PL_kr_abbr" "KR"
@@ -205,12 +207,12 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"GAMEMODE_fastball" "Fastball"
"PL_fastball" "Fastball"
- "PL_fastball_lobby" "Lobby: Fastball"
+ "PL_fastball_lobby" "Lobby : Fastball"
"PL_fastball_desc" "Mort permanente.\nPiratez les panneaux de contrôle pour faire réapparaître vos équipiers."
"PL_fastball_hint" "Mort permanente.\nPiratez les panneaux de contrôle pour faire réapparaître vos équipiers."
"PL_fastball_abbr" "FB"
"FASTBALL_PANEL_CAPTURED" "%s1 a piraté le panneau de contrôle %s2 !"
- "SCOREBOARD_FASTBALL_HACKS" "Panneaux de\ncontrôle capturés"
+ "SCOREBOARD_FASTBALL_HACKS" "Panneaux de contrôle capturés"
"GAMEMODE_ctf_comp" "Capture de drapeau - Compétitif"
@@ -237,7 +239,7 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
// coop stuff
"PL_sp_coop" "(ALPHA) Campagne (coop)"
- "PL_sp_coop_lobby" "Lobby: Campagne (coop)"
+ "PL_sp_coop_lobby" "Lobby : Campagne (coop)"
"PL_sp_coop_desc" "Jouez la campagne avec des amis."
"PL_sp_coop_hint" "Jouez la campagne avec des amis."
"PL_sp_coop_abbr" "SP"
@@ -274,7 +276,7 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"PLAYERS_COLUMN" "Joueurs"
"MAP_COLUMN" "Carte"
"GAMEMODE_COLUMN" "Mode de jeu"
- "LATENCY_COLUMN" "Latence"
+ "REGION_COLUMN" "RĂ©gion"
"SEARCHBAR_LABEL" "Recherche :"
"MAP_FILTER" "Carte"
"GAMEMODE_FILTER" "Mode de jeu"
@@ -314,8 +316,68 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst
"UNAUTHORIZED_PWD" "Mot de passe incorrect"
"STRYDER_RESPONSE" "Impossible d'analyser la réponse de Stryder"
"PLAYER_NOT_FOUND" "Impossible de trouver le compte du joueur"
- "INVALID_MASTERSERVER_TOKEN" "Jeton du server maître invalide ou expiré"
+ "INVALID_MASTERSERVER_TOKEN" "Token du server maître invalide ou expiré, veuillez relancer l'application EA."
"JSON_PARSE_ERROR" "Une erreur est survenue durant l'analyse JSON"
"UNSUPPORTED_VERSION" "La version que vous utilisez n'est plus supportée"
+
+ "MOD_SETTINGS" "Paramètres de mod"
+ "NORTHSTAR_BASE_SETTINGS" "Paramètres de base de Northstar"
+ "ONLY_HOST_MATCH_SETTINGS" "Seul l'hôte peut changer les paramètres de match privé"
+ "ONLY_HOST_CAN_START_MATCH" "Seul l'hĂ´te peut lancer le match"
+ "MATCH_COUNTDOWN_LENGTH" "Durée du compte à rebours de match privé"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "Enregistrer les commandes client inconnues"
+ "DISALLOWED_TACTICALS" "Capacités interdites"
+ "TACTICAL_REPLACEMENT" "Capacités de remplacement"
+ "DISALLOWED_WEAPONS" "Armes interdites"
+ "REPLACEMENT_WEAPON" "Armes de remplacement"
+ "SHOULD_RETURN_TO_LOBBY" "Retour au lobby après la fin du match"
+ "ARE_YOU_SURE" "ĂŠtes-vous certain ?"
+ "WILL_RESET_ALL_SETTINGS" "Ceci réinitialisera tous les paramètres de cette catégorie.\n\nCette action est irréversible."
+ "WILL_RESET_SETTING" "Ceci réinitialisera le paramètre %s1 à sa valeur par défaut.\n\nCette action est irréversible."
+ "MOD_SETTINGS_SERVER" "Serveur"
+ "MOD_SETTINGS_RESET" "RĂ©initialiser"
+ "MOD_SETTINGS_RESET_ALL" "Tout réinitialiser"
+ "SHOW_ONLY_REQUIRED" "Afficher les mods requis"
+ "SHOW_ONLY_NOT_REQUIRED" "Uniquement les mods optionnels"
+ "NO_RESULTS" "Aucun résultat."
+ "NO_MODS" "Aucun paramètre trouvé ! Installez d'autres mods depuis ^5588FF00northstar.thunderstore.io^0."
+ "player_force_respawn" "Réapparition forcée"
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "DĂ©sactiver la progression ?"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "Les Titans, Armes, Factions, Skins, et autres seront débloqués et utilisables en tout temps.\n\nPeut être changé à n'importe que moment dans le salon multijoueurs."
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "Activer la progression ?"
+ "PROGRESSION_ENABLED_HEADER" "Progression activée !"
+ "PROGRESSION_DISABLED_HEADER" "Progression désactivée !"
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000La progression a été désactivée.^\n\nLes Titans, Armes, Factions, Skins, et autres seront débloqués et utilisables en tout temps.\n\nPeut être changé à n'importe que moment dans le salon multijoueurs."
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "Les Titans, Armes, Factions, Skins et autres seront débloqués par la monté en niveau ou par leur achats en mérites.\n\nPeut être changé à n'importe que moment dans le salon multijoueurs.\n\n^CC000000Warning : Si vous équipez des objets que vous n'avez pas encore débloqués, ils seront déséquipés !"
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000La progression a été activée.^\n\nLes Titans, Armes, Factions, Skins et autres seront débloqués par la monté en niveau ou par leur achats en mérites.\n\nPeut être changé à n'importe que moment dans le salon multijoueurs."
+ "TOGGLE_PROGRESSION" "Activer la progression"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Activer la progression"
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000Le système de progression peut être activé !^\n\nNorthstar supporte désormais le système de progression du jeu original, vous permettant de choisir si vous souhaitez débloquer les armes, skins, titans etc. en gagnant des niveaux et en complétant des défis.\n\nVous pouvez activer la progression en utilisant le bouton en bas de l'écran d'accueil.\n\nCeci peut être changé à tout moment."
+ "AUTHENTICATION_FAILED_HEADER" "Échec de l'authentification"
+ "AUTHENTICATION_FAILED_HELP" "Aide"
+ "AUTHENTICATION_FAILED_ERROR_CODE" "Code d'erreur : ^DB6F2C00%s1^"
+ "AUTHENTICATION_FAILED_BODY" "L'authentification avec Atlas a échoué."
+ "MISSING_MOD" "Mod manquant \"%s1\" v%s2"
+ "MOD_REQUIRED_WARNING" " : Ce mod peut être (dé)chargé automatiquement en rejoignant un serveur"
+ "EXTRACTING_MOD_TITLE" "Extraction du mod (%s1%)"
+ "MOD_NOT_VERIFIED" "(ce mod n'est pas vérifié, et n'a donc pas pu être automatiquement téléchargé)"
+ "MOD_DL_DISABLED" "(le téléchargement automatique de mods est désactivé)"
+ "DOWNLOADING_MOD_TITLE" "Téléchargement du mod"
+ "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Téléchargement du mod (%s1%)"
+ "DOWNLOADING_MOD_TEXT" "Téléchargement de %s1 v%s2..."
+ "WRONG_MOD_VERSION" "Le serveur requiert la version v%s2 du mod \"%s1\" (vous avez la version v%s3)"
+ "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Téléchargement de %s1 v%s2...\n(%s3/%s4 Mo)"
+ "CHECKSUMING_TITLE" "VĂ©rification de la somme de contrĂ´le du mod"
+ "CHECKSUMING_TEXT" "VĂ©rification du contenu de %s1 v%s2..."
+ "EXTRACTING_MOD_TEXT" "Extraction de %s1 v%s2...\n(%s3/%s4 Mo)"
+ "FAILED_DOWNLOADING" "Echec du téléchargement du mod"
+ "FAILED_READING_ARCHIVE" "Une erreur est survenue lors de la lecture de l'archive."
+ "FAILED_WRITING_TO_DISK" "Une erreur est survenue lors de l'extraction des fichiers."
+ "MOD_FETCHING_FAILED" "L'archive n'a pas pu être téléchargée depuis Thunderstore."
+ "MOD_CORRUPTED" "La somme de contrôle de l'archive ne correspond pas à la signature vérifiée."
+ "NO_DISK_SPACE_AVAILABLE" "L'espace restant sur votre disque est insuffisant."
+ "MOD_FETCHING_FAILED_GENERAL" "L'extraction du mod a échoué. Consultez le journal pour plus d'informations."
+ "MANIFESTO_FETCHING_TITLE" "Préparation du téléchargement du mod"
+ "MANIFESTO_FETCHING_TEXT" "Récupération de la liste des mods vérifiés..."
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_german.txt b/Northstar.Client/mod/resource/northstar_client_localisation_german.txt
index 0eded5bc..996a3e2b 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_german.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_german.txt
@@ -8,6 +8,9 @@
"MENU_LAUNCH_NORTHSTAR" "Northstar starten"
"MENU_TITLE_MODS" "Mods"
"RELOAD_MODS" "Mods neu laden"
+ "WARNING" "Warnung"
+ "CORE_MOD_DISABLE_WARNING" "Das Deaktivieren von essentiellen Mods kann die Funktion deines Clients beeinträchtigen!"
+ "DISABLE" "Deaktiviere"
"DIALOG_TITLE_INSTALLED_NORTHSTAR" "Danke, dass du Northstar installiert hast!"
"AUTHENTICATION_AGREEMENT_DIALOG_TEXT" "Damit Northstar funktionieren kann, muss es mithilfe des Northstar Masterservers authentifizieren. Dies setzt ein Weitergeben deines Origin Tokens an den Masterserver voraus, er wird nicht gespeichert oder fĂĽr andere Zwecke verwendet.
@@ -16,6 +19,9 @@ DrĂĽcke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im ModmenĂĽ Ă
"AUTHENTICATION_AGREEMENT" "Authentifizierungs-Einwilligung"
"AUTHENTICATION_AGREEMENT_RESTART" "Ein Neustart ist notwendig, um diese Ă„nderung zu ĂĽbernehmen"
+ "DIALOG_AUTHENTICATING_MASTERSERVER" "Authentifizierung mit Master Server"
+ "AUTHENTICATIONAGREEMENT_NO" "Du hast dich gegen die Authentifizierung mit Northstar entschieden. Du kannst die Authentifizierungs-Einwilligung im ModmenĂĽ ansehen."
+
"MENU_TITLE_SERVER_BROWSER" "Server Browser"
"NS_SERVERBROWSER_NOSERVERS" "Keine Server gefunden"
"NS_SERVERBROWSER_UNKNOWNMODE" "Unbekannter Modus"
@@ -75,6 +81,8 @@ DrĂĽcke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im ModmenĂĽ Ă
"cp_amped_capture_points" "Verstärkte Hardpoints"
"coliseum_loadouts_enabled" "Coliseum Loadouts"
+ "aitdm_archer_grunts" "Archer Frontsoldaten"
+
// northstar.custom localisation is just deciding not to work, so putting it here for now
"PL_sbox" "Sandbox"
"PL_sbox_lobby" "Sandbox Lobby"
@@ -88,6 +96,8 @@ DrĂĽcke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im ModmenĂĽ Ă
"PL_gg_hint" "Erhalte einen Kill mit jeder Waffe um zu siegen."
"PL_gg_abbr" "GG"
"GAMEMODE_GG" "Gun Game"
+ "gg_kill_reward" "Killprozentbelohnung"
+ "gg_execution_reward" "Exekutierungsprozentbelohnung"
"PL_tt" "Titan Tag"
"PL_tt_lobby" "Titan Tag Lobby"
@@ -113,6 +123,17 @@ DrĂĽcke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im ModmenĂĽ Ă
"HIDDEN_KILL_SURVIVORS" "Töte alle Überlebenden"
"HIDDEN_FIRST_HIDDEN" "%s1 ist the The Hidden."
+ "PL_sns" "Stock und Stein"
+ "PL_sns_lobby" "Stock und Stein Lobby"
+ "PL_sns_desc" "Frei fĂĽr Alle. Erziele Kills mit Impulsklingen und Exekutierungen um die Punktzahl des Gegners zurĂĽckzusetzen"
+ "PL_sns_abbr" "SuS"
+ "GAMEMODE_SNS" "Stock und Stein"
+ "sns_wme_kill_value" "Wingman Elite Killwert"
+ "sns_softball_kill_value" "Softball Killwert"
+ "sns_reset_kill_value" "Pulseklinge/Exekutierung Killwert"
+ "sns_melee_kill_value" "Nahkampfkill Killwert"
+ "sns_softball_enabled" "Softball aktiviert"
+
"PL_inf" "Infektion"
"PL_inf_lobby" "Infektion-Lobby"
"PL_inf_desc" "Ăśberlebe die Infektion. Ăśberlebende werden nach dem Tod infiziert."
@@ -246,7 +267,7 @@ DrĂĽcke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im ModmenĂĽ Ă
"PLAYERS_COLUMN" "Spieler"
"MAP_COLUMN" "Karte"
"GAMEMODE_COLUMN" "Modus"
- "LATENCY_COLUMN" "Ping"
+ "REGION_COLUMN" "Region"
"SEARCHBAR_LABEL" "Suche:"
"MAP_FILTER" "Karte"
"GAMEMODE_FILTER" "Modus"
@@ -266,6 +287,15 @@ DrĂĽcke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im ModmenĂĽ Ă
"INGAME_PLAYERS" "Spieler: ^6BA6C400%s1"
"TOTAL_SERVERS" "Server: ^C46C6C00%s1"
+ // Mods menu
+ "SHOW" "Anzeigen"
+ "SHOW_ALL" "Alle anzeigen"
+ "SHOW_ONLY_ENABLED" "Nur aktivierte"
+ "SHOW_ONLY_DISABLED" "Nur deaktivierte"
+
+ // Maps menu
+ "HIDE_LOCKED" "Verstecke gesperrte"
+
// In-game chat
"HUD_CHAT_WHISPER_PREFIX" "[WHISPER]"
"HUD_CHAT_SERVER_PREFIX" "[SERVER]"
@@ -280,5 +310,71 @@ DrĂĽcke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im ModmenĂĽ Ă
"INVALID_MASTERSERVER_TOKEN" "UngĂĽltiger oder abgelaufener Token vom Masterserver"
"JSON_PARSE_ERROR" "Fehler beim Verarbeiten der JSON-Antwort"
"UNSUPPORTED_VERSION" "Die Version die du benutzt ist nicht länger unterstützt"
+ "SNS_LEADER_BANKRUPT_SUB" "%s1 Wurde Von %s2 ZurĂĽckgesetzt"
+ "SNS_BANKRUPT_SUB" "Dein Punkestand wurde von %s1 zurĂĽckgesetzt"
+ "respawnprotection" "Respawn Schutzdauer"
+ "SNS_BANKRUPT" "Bankrott!"
+ "SNS_LEADER_BANKRUPT" "PunktzahlfĂĽhrer Bankrott!"
+ "sns_reset_pulse_blade_cooldown_on_pulse_blade_kill" "Kill Cooldown ZurĂĽcksetzungen"
+ "player_force_respawn" "Erzwungener Respawn"
+ "SHOW_ONLY_NOT_REQUIRED" "Nur optionale Mods"
+ "SHOW_ONLY_REQUIRED" "Nur notwendige Mods"
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "Fortschritt aktivieren?"
+ "TOGGLE_PROGRESSION" "Fortschritt zuschalten"
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "Fortschritt deaktivieren?"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "Titans, Waffen, Fraktionen, Skins, usw werden freigeschaltet und sind zu jeder Zeit verfügbar .\n\nDies kann in der Mehrspielerlobby zu jedem Zeitpunkt geändert werden."
+ "MATCH_COUNTDOWN_LENGTH" "Countdown fĂĽr privates Match"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "Unbekannte Clientbefehle loggen"
+ "DISALLOWED_TACTICALS" "Verbotene Taktiken"
+ "TACTICAL_REPLACEMENT" "Taktischer Austausch"
+ "AUTHENTICATION_FAILED_HEADER" "Authentifizierung fehlgeschlagen"
+ "AUTHENTICATION_FAILED_BODY" "Authentifizierung mit Atlas fehlgeschlagen!"
+ "AUTHENTICATION_FAILED_ERROR_CODE" "Fehlercode: ^DB6F2C00%s1^"
+ "AUTHENTICATION_FAILED_HELP" "Hilfe"
+ "NORTHSTAR_BASE_SETTINGS" "Northstar Grundeinstellungen"
+ "ONLY_HOST_MATCH_SETTINGS" "Nur der Host kann die Einstellungen eines privaten Matches ändern"
+ "ONLY_HOST_CAN_START_MATCH" "Nur der Host kann das Match starten"
+ "MISSING_MOD" "Fehlender Mod \"%s1\" v%s2"
+ "MOD_DL_DISABLED" "(automatisches Herunterladen ist deaktiviert)"
+ "DOWNLOADING_MOD_TITLE" "Lade Mod herunter"
+ "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Lade Mod (%s1%)"
+ "DOWNLOADING_MOD_TEXT" "Lade %s1 v%s2..."
+ "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Lade %s1 v%s2...\n(%s3/%s4 MB)"
+ "CHECKSUMING_TITLE" "ĂśberprĂĽfe Mod PrĂĽfsumme"
+ "MOD_NOT_VERIFIED" "(mod ist nicht verifiziert und kann nicht automatisch herunterladen werden)"
+ "MOD_REQUIRED_WARNING" " : Dieser Mod könnte Sie (un)ausgestattet hinterlassen, sobald Sie einem Server beitreten"
+ "MOD_SETTINGS" "Mod Einstellungen"
+ "DISALLOWED_WEAPONS" "Verbotene Waffen"
+ "REPLACEMENT_WEAPON" "Austausch Waffen"
+ "SHOULD_RETURN_TO_LOBBY" "Zur Lobby nach Matchende zurĂĽckkehren"
+ "ARE_YOU_SURE" "Sind Sie sich sicher?"
+ "MOD_SETTINGS_SERVER" "Server"
+ "MOD_SETTINGS_RESET" "ZurĂĽcksetzen"
+ "MOD_SETTINGS_RESET_ALL" "Alles zurĂĽcksetzen"
+ "NO_RESULTS" "Keine Ergebnisse."
+ "NO_MODS" "Keine Einstellungen verfĂĽgbar! Installieren sie weitere Mods ĂĽber^5588FF00northstar.thunderstore.io^0."
+ "PROGRESSION_ENABLED_HEADER" "Fortschritt aktiviert!"
+ "PROGRESSION_DISABLED_HEADER" "Fortschritt deaktiviert!"
+ "WILL_RESET_ALL_SETTINGS" "Dadurch werden ALLE Einstellungen, die zu dieser Kategorie gehören, zurückgesetzt.\n\nDies kann nicht rückgängig gemacht werden."
+ "WILL_RESET_SETTING" "Dies setzten die Einstellungen %s1 auf deren Ursprungeswert zurück.\n\nDies kann nicht rückgängig gemacht werden."
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Fortschritt zuschalten."
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "Titans, Waffen, Fraktionen, Skins usw. müssen durch Levelaufstieg freigeschaltet oder mit Verdiensten gekauft werden.\n\nDies kann jederzeit in der Mehrspieler-Lobby geändert werden.\n\n^CC000000Warnung: Wenn Sie derzeit ausgerüstete Gegenstände besitzen, die Sie nicht freigeschaltet haben, werden diese zurückgesetzt!"
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000Fortschritt wurde aktiviert.^\n\nTitans, Waffen, Fraktionen, Skins usw. müssen durch Levelaufstieg freigeschaltet oder mit Verdiensten gekauft werden.\n\nDies kann jederzeit in der Mehrspieler-Lobby geändert werden."
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000Fortschritt wurde deaktiviert.^\n\nTitans, Waffen, Fraktionen, Skins usw. werden alle freigeschaltet und jederzeit nutzbar sein.\n\nDies kann jederzeit in der Mehrspieler-Lobby geändert werden."
+ "CHECKSUMING_TEXT" "Verifiziere Inhalte von %s1 v%s2..."
+ "EXTRACTING_MOD_TITLE" "Extrahiere Mod (%s1%)"
+ "EXTRACTING_MOD_TEXT" "Extrahiere %s1 v%s2...\n(%s3/%s4 MB)"
+ "FAILED_DOWNLOADING" "Herunterladen der Mod fehlgeschlagen"
+ "FAILED_READING_ARCHIVE" "Während des Lesens der Mod Archivs ist ein Fehler aufgetreten."
+ "FAILED_WRITING_TO_DISK" "Während des Extrahierens der Mod in das Dateisysteme ist ein Fehler aufgetreten."
+ "WRONG_MOD_VERSION" "Der Server verfügt über Mod \"%s1\" v%s2 während Sie v%s3 haben"
+ "MOD_FETCHING_FAILED" "Mod Archiv konnte nicht von Thunderstore heruntergeladen werden."
+ "MOD_CORRUPTED" "PrĂĽfsumme des heruntergeladenen Archivs stimmt nicht mit der verifizierten Signatur ĂĽberein."
+ "NO_DISK_SPACE_AVAILABLE" "Sie verfĂĽgen nicht ĂĽber ausreichend Speicherplatz auf ihrer Festplatte."
+ "MOD_FETCHING_FAILED_GENERAL" "Mod Extraktion fehlgeschlagen. ĂśberprĂĽfen Sie die Logs fĂĽr weitere Details."
+ "gg_assist_reward" "Assist Anteilige Belohnung"
+ "SCOREBOARD_BANKRUPTS" "Bankrott Kills"
+ "sns_offhand_kill_value" "Freihand Killwert"
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000Fortschritt kann jetzt aktiviert werden!^\n\nNorthstar unterstützt nun den standardmäßigen Fortschritt, was bedeutet, dass Sie Waffen, Skins, Titans usw. durch Levelaufstieg und das Abschließen von Herausforderungen freischalten können.\n\nSie können den Fortschritt mit dem Button am unteren Rand des Lobbybildschirms aktivieren.\n\nDies kann jederzeit geändert werden."
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_italian.txt b/Northstar.Client/mod/resource/northstar_client_localisation_italian.txt
index b8253ad9..38e67dea 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_italian.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_italian.txt
@@ -6,16 +6,23 @@
"MENU_LAUNCH_NORTHSTAR" "Avvia Northstar"
"MENU_TITLE_MODS" "Mods"
"RELOAD_MODS" "Ricarica Mods"
+ "WARNING", "Attenzione"
+ "CORE_MOD_DISABLE_WARNING", "Disattivare le Mods Principali può rompere il tuo Client!"
+ "DISABLE", "Disattiva"
"DIALOG_TITLE_INSTALLED_NORTHSTAR" "Grazie per aver installato Northstar!"
"AUTHENTICATION_AGREEMENT_DIALOG_TEXT" "Affinché Northstar funzioni, è necessario autenticarsi utilizzando il server principale di Northstar. Ciò richiederà l'invio del tuo token di Origin al server principale, non verrà archiviato o utilizzato per altri scopi.
-Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi momento nel menu mods."
+Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi momento nel Menu delle Mods."
"BACK_AUTHENTICATION_AGREEMENT" "Accordo di autenticazione"
"AUTHENTICATION_AGREEMENT" "Accordo di autenticazione"
"AUTHENTICATION_AGREEMENT_RESTART" "Dovrai riavviare Titanfall 2 affinché questa scelta abbia effetto."
+ "DIALOG_AUTHENTICATING_MASTERSERVER", "Autenticazione Sul Master Server in corso"
+ "AUTHENTICATIONAGREEMENT_NO", "Hai Scelto di non autenticarti con Northstar. Puoi vedere l'Accordo nel Menu delle Mods"
+
"MENU_TITLE_SERVER_BROWSER" "Server Browser"
"NS_SERVERBROWSER_NOSERVERS" "Nessun server trovato"
+ "NS_SERVERBROWSER_UNKNOWNMODE", "ModalitĂ  Sconosciuta"
"NS_SERVERBROWSER_WAITINGFORSERVERS" "In attesa dei server..."
"NS_SERVERBROWSER_CONNECTIONFAILED" "Connessione fallita!"
"REFRESH_SERVERS" "Aggiorna"
@@ -32,7 +39,7 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"PRIVATE_MATCH_SINGLEPLAYER_LEVEL" "%s1 (Single-Player)"
// fra hint for private match menu, because fra only has PL_fra_desc in vanilla
- "PL_fra_hint" "Tutti contro tutti. Uccidi nemici per vincere. Raccogli 3 batterie per chiamare il Titan."
+ "PL_fra_hint" "Tutti contro tutti. Uccidi i nemici per vincere. Raccogli 3 batterie per chiamare il tuo Titan."
// mode settings
"MODE_SETTING_CATEGORY_PILOT" "Pilota"
@@ -46,6 +53,7 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"roundscorelimit" "Limite Punteggio (per round)"
"timelimit" "Limite di Tempo"
"roundtimelimit" "Limite di Tempo (per round)"
+ "respawnprotection", "Tempo Protezione di Respawn"
"pilot_health_multiplier" "Moltiplicatore di Salute"
"respawn_delay" "Tempo di Respawn"
@@ -55,7 +63,7 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"earn_meter_titan_multiplier" "Moltiplicatore nucleo Titan"
"aegis_upgrades" "Upgrade Aegis"
- "infinite_doomed_state" "Doom state Infinito"
+ "infinite_doomed_state" "Stato Doom Infinito"
"titan_shield_regen" "Rigenerazione Scudi"
"riff_floorislava" "Terreno Mortale"
@@ -72,10 +80,12 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"cp_amped_capture_points" "Hardpoints Amplificati"
"coliseum_loadouts_enabled" "Equipaggiamento Colosseo"
+ "aitdm_archer_grunts", "Scagnozzi Archer"
+
// northstar.custom localisation is just deciding not to work, so putting it here for now
"PL_sbox" "Sandbox"
- "PL_sbox_lobby" "Sandbox Lobby"
- "PL_sbox_desc" "come gmod ma peggio."
+ "PL_sbox_lobby" "Lobby: Sandbox"
+ "PL_sbox_desc" "Come gmod ma peggio"
"PL_sbox_abbr" "SBOX"
"GAMEMODE_SBOX" "Sandbox"
@@ -84,7 +94,11 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"PL_gg_desc" "Ottieni un'uccisione con ogni arma per vincere."
"PL_gg_hint" "Ottieni una nuova arma ogni uccisione."
"PL_gg_abbr" "GG"
- "GAMEMODE_GG" "Gioco d'Armi"
+ "GAMEMODE_GG" "Gioco delle Armi"
+ "aitdm_archer_grunts", "Scagnozzi Archer"
+ "gg_kill_reward", "Punteggio Ricompensa Uccisione"
+ "gg_assist_reward", "Punteggio Ricompensa Assist"
+ "gg_execution_reward", "Punteggio Ricompensa Esecuzione"
"PL_tt" "Titan Tag"
"PL_tt_lobby" "Lobby: Titan Tag"
@@ -110,6 +124,24 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"HIDDEN_KILL_SURVIVORS" "Uccidi tutti i giocatori."
"HIDDEN_FIRST_HIDDEN" "%s1 è il Cacciatore."
+ "PL_sns", "Sticks and Stones"
+ "PL_sns_lobby", "Sticks and Stones Lobby"
+ "PL_sns_desc", "Tutti contro Tutti, Usa la Pulse Blade e le Esecuzioni per Resettare il Punteggio Nemico"
+ "PL_sns_abbr", "SNS"
+ "GAMEMODE_SNS", "Sticks and Stones"
+ "SCOREBOARD_BANKRUPTS", "Uccisioni Bancarotta"
+ "SNS_LEADER_BANKRUPT", "Il Leader punteggio è andato in Bancarotta!"
+ "SNS_LEADER_BANKRUPT_SUB", "%s1 è stato Resettato da %s2"
+ "SNS_BANKRUPT", "Bancarotta!"
+ "SNS_BANKRUPT_SUB", "Il Tuo Punteggio è stato Resettato da %s1"
+ "sns_wme_kill_value", "Valore Uccisione Wingman d'Elite"
+ "sns_softball_kill_value", "Valore Uccisione Softball"
+ "sns_offhand_kill_value", "Valore Uccisione Improvvisa"
+ "sns_reset_kill_value", "Valore Uccisione Pulse/Esecuzione"
+ "sns_melee_kill_value", "Valore Uccisione Corpo a Corpo"
+ "sns_reset_pulse_blade_cooldown_on_pulse_blade_kill", "Reset Cooldown Uccisione"
+ "sns_softball_enabled", "Softball Attivato"
+
"PL_inf" "Infetto"
"PL_inf_lobby" "Lobby: Infetto"
"PL_inf_desc" "Sopravvivi all'infezione. I sopravvissuti vengono infettati quando uccisi."
@@ -124,6 +156,13 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"INFECTION_YOU_ARE_LAST_SURVIVOR" "Sei l'Ultimo Sopravvissuto!"
"INFECTION_SURVIVE_LAST_SURVIVOR" "Sopravvivi."
+ "PL_tffa", "Titan Tutti contro Tutti"
+ "PL_tffa_lobby", "Lobby: Titan Tutti contro Tutti"
+ "PL_tffa_desc", "Ogni Pilota per sè, distruggi tutti i Titan Nemici."
+ "PL_tffa_hint", "Ogni Pilota per sè, distruggi tutti i Titan Nemici."
+ "PL_tffa_abbr", "TFFA"
+ "GAMEMODE_TFFA", "Titan Tutti contro Tutti"
+
"PL_hs" "Nascondino"
"PL_hs_lobby" "Lobby: Nascondino"
"PL_hs_desc" "Nasconditi oppure trova gli avversari."
@@ -131,22 +170,22 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"PL_hs_abbr" "NS"
"GAMEMODE_hs" "Nascondino"
"HIDEANDSEEK_YOU_ARE_SEEKER" "TROVA I GIOCATORI NASCOSTI"
- "HIDEANDSEEK_SEEKER_DESC" "Trova ed uccidi tutti i giocatori nascosti. \nRespawnerai in %s1 secondi."
+ "HIDEANDSEEK_SEEKER_DESC" "Trova ed uccidi tutti i giocatori nascosti. \nRespawnerai in %s1 secondi"
"HIDEANDSEEK_YOU_ARE_HIDER" "NASCONDITI"
"HIDEANDSEEK_HIDER_DESC" "Nasconditi e cerca di non farti trovare."
"HIDEANDSEEK_SEEKERS_INCOMING" "HANNO INIZIATO A CERCARTI"
"HIDEANDSEEK_DONT_GET_FOUND" "Non farti trovare!"
"HIDEANDSEEK_GET_LAST_HIDER" "%s1 è L'ULTIMO CERCATORE"
"HIDEANDSEEK_YOU_ARE_LAST_HIDER" "SEI L'ULTIMO NASCOSTO"
- "HIDEANDSEEK_GOT_STIM" "Sei stimmato! Non farti prendere!"
- "hideandseek_balance_teams" "Bilanciamento Squadre..."
+ "HIDEANDSEEK_GOT_STIM" "Sei stimolato! Non farti prendere!"
+ "hideandseek_balance_teams" "Bilanciamento Squadre"
"hideandseek_hiding_time" "Tempo per Nascondersi"
// 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" "Lobby: Frontier War"
- "PL_fw_desc" "Distruggi il mietiore nemico e proteggi il tuo."
+ "GAMEMODE_fw" "Guerra di Frontiera"
+ "PL_fw" "Guerra di Frontiera"
+ "PL_fw_lobby" "Lobby: Guerra di Frontiera"
+ "PL_fw_desc" "Distruggi il mietiore nemico e proteggi il tuo"
"PL_fw_abbr" "FW"
"GAMEMODE_kr" "Killrace Amplificata"
@@ -159,10 +198,10 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"KR_NEW_RACER" "%s1 è il killracer amplificato"
"KR_YOU_ARE_NEW_RACER" "Sei il killracer amplificato"
"KR_YOU_SET_NEW_RECORD" "Stabilisci un nuovo record di uccisioni!"
- "KR_FLAG_INCOMING" "Bandiera in Arrivo!"
+ "KR_FLAG_INCOMING" "Bandiera in Arrivo"
"KR_COLLECT_FLAG" "Raccoglila per diventare il killracer!"
- "KR_ENEMY_KILLRACE_OVER" "La killrace di %s1 è finita."
- "KR_YOUR_KILLRACE_OVER" "La tua killrace è finita."
+ "KR_ENEMY_KILLRACE_OVER" "La killrace di %s1 è finita"
+ "KR_YOUR_KILLRACE_OVER" "La tua killrace è finita"
"KR_YOUR_KILLRACE_SCORE" "Hai ottenuto %s1 uccisioni."
"GAMEMODE_fastball" "Fastball"
@@ -183,7 +222,7 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"custom_air_accel_pilot" "Accelerazione Aerea"
"no_pilot_collision" "Collisione tra Piloti"
"promode_enable" "Armi ModalitĂ  Competitiva"
- "fp_embark_enabled" "Imbarchi/esecuzioni in 1Âşpers."
+ "fp_embark_enabled" "Imbarchi/esecuzioni in 1Âş persona"
"classic_rodeo" "Rodeo classico"
"oob_timer_enabled" "Timer Fuori dai Limiti"
"riff_instagib" "ModalitĂ  Instagib"
@@ -211,7 +250,7 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"SP_CRASHSITE_CLASSIC_DESC" "Jack Cooper incontra BT-7274."
"SP_SEWERS1" "Sangue e Ruggine"
- "SP_SEWERS1_CLASSIC_DESC" "Cooper e BT cercano di raggiungere il Major Anderson."
+ "SP_SEWERS1_CLASSIC_DESC" "Cooper e BT cercano di raggiungere il Maggiore Anderson."
"SP_BOOMTOWN_START" "Nell'abisso"
"SP_BOOMTOWN_START_CLASSIC_DESC" "Una scorciatoia sotterranea porta a conseguenze impreviste."
@@ -223,7 +262,7 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"SP_BEACON_CLASSIC_DESC" "Cooper e BT tentano di informare la flotta rimanente dei piani dell'IMC."
"SP_TDAY" "Prova del fuoco"
- "SP_TDAY_CLASSIC_DESC" "Le abilitĂ  del Titan di Cooper vengono messe alla prova in una battaglia senza quartiere per la cattura dell'Arca."
+ "SP_TDAY_CLASSIC_DESC" "Le abilitĂ  del Titan di Cooper vengono messe alla prova in una battaglia senza quartiere per la cattura dell'Arca"
"SP_S2S" "L'Arca"
"SP_S2S_CLASSIC_DESC" "Cooper e BT inseguono l'Arca affrontando una nave dopo l'altra."
@@ -233,10 +272,10 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
// Better.Serverbrowser
"SERVERS_COLUMN" "Server"
- "PLAYERS_COLUMN" "Players"
+ "PLAYERS_COLUMN" "Giocatori"
"MAP_COLUMN" "Mappa"
"GAMEMODE_COLUMN" "ModalitĂ "
- "LATENCY_COLUMN" "Latenza"
+ "REGION_COLUMN" "Regione"
"SEARCHBAR_LABEL" "Cerca:"
"MAP_FILTER" "Mappa"
"GAMEMODE_FILTER" "ModalitĂ "
@@ -257,19 +296,89 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo
"INGAME_PLAYERS" "Players: ^6BA6C400%s1"
"TOTAL_SERVERS" "Server: ^C46C6C00%s1"
+ // Mods menu
+ "SHOW", "Mostra"
+ "SHOW_ALL", "Mostra Tutti"
+ "SHOW_ONLY_ENABLED", "Mostra solo Attivi"
+ "SHOW_ONLY_DISABLED", "Mostra solo Inattivi"
+
+ // Maps menu
+ "HIDE_LOCKED" "Nascondi bloccati"
+
// In-game chat
- "HUD_CHAT_WHISPER_PREFIX" "[WHISPER]"
+ "HUD_CHAT_WHISPER_PREFIX" [BISBIGLIO]"
"HUD_CHAT_SERVER_PREFIX" "[SERVER]"
"NO_GAMESERVER_RESPONSE" "Non è stato possibile raggiungere il server"
"BAD_GAMESERVER_RESPONSE" "Il server ha dato una risposta invalida"
"UNAUTHORIZED_GAMESERVER" "Il server non è autorizzato a fare quella richiesta"
- "UNAUTHORIZED_GAME" "Stryder non è riuscito a confermare che questo account possiede Titanfall 2"
+ "UNAUTHORIZED_GAME" "Stryder non è riuscito a confermare che questo account possieda Titanfall 2"
"UNAUTHORIZED_PWD" "Password errata"
- "STRYDER_RESPONSE" "Non è stato possibile analizzare la risposta Stryder"
+ "STRYDER_RESPONSE" "Non è stato possibile analizzare la risposta di Stryder"
"PLAYER_NOT_FOUND" "Non è stato trovato l'account player"
"INVALID_MASTERSERVER_TOKEN" "Token Masterserver invalido o scaduto"
- "JSON_PARSE_ERROR" "Errore nell'analisi di risposta json"
+ "JSON_PARSE_ERROR" "Errore nell'analisi della risposta json"
"UNSUPPORTED_VERSION" "La versione che stai usando non è più supportata"
+
+ "MOD_SETTINGS" "Impostazioni Mod"
+ "NORTHSTAR_BASE_SETTINGS" "Impostazioni base Northstar"
+ "ONLY_HOST_MATCH_SETTINGS" "Solo l'Host può modificare le impostazioni della Partita Privata"
+ "ONLY_HOST_CAN_START_MATCH" "Solo l'Host può Iniziare la Partita"
+ "MATCH_COUNTDOWN_LENGTH" "Durata Countdown della Partita Privata"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "Registra Comandi Client Sconosciuti"
+ "DISALLOWED_TACTICALS" "AbilitĂ  Proibite"
+ "TACTICAL_REPLACEMENT" "Sostituzione AbilitĂ "
+ "DISALLOWED_WEAPONS" "Armi Proibite"
+ "REPLACEMENT_WEAPON" "Sostituzione Armi"
+ "SHOULD_RETURN_TO_LOBBY" "Ritorna alla Lobby dopo Fine Partita"
+ "ARE_YOU_SURE" "Sei sicuro?"
+ "WILL_RESET_ALL_SETTINGS" "Questo ripristinerà TUTTE le impostazioni che appartengono a questa categoria.\n\nNON può essere annullato."
+ "WILL_RESET_SETTING" "Questo ripristinerà l'impostazione %s1 al suo valore predefinito.\n\nNON è reversibile." // obviously, don't translate %s1.
+ "MOD_SETTINGS_SERVER" "Server"
+ "MOD_SETTINGS_RESET" "Ripristina"
+ "MOD_SETTINGS_RESET_ALL" "Ripristina Tutto"
+ "HUD_CHAT_WHISPER_PREFIX" "[WHISPER]"
+ "NO_RESULTS" "Nessun Risultato."
+ "DISABLE" "Disattiva"
+ "DIALOG_AUTHENTICATING_MASTERSERVER" "Autenticazione al master server."
+ "NS_SERVERBROWSER_UNKNOWNMODE" "ModalitĂ  sconosciuta"
+ "respawnprotection" "Tempo di protezione al respawn"
+ "NO_MODS" "Nessuna impostazione disponibile! Installa piĂą mods a ^5588FF00northstar.thunderstore.io^0."
+ "gg_assist_reward" "Percentuale ricompensa per assist"
+ "gg_execution_reward" "Percentuale ricompensa per esecuzione"
+ "PL_sns" "Sticks and Stones"
+ "PL_sns_lobby" "Lobby: Sticks and Stones"
+ "PL_sns_abbr" "SNS"
+ "GAMEMODE_SNS" "Sticks and Stones"
+ "SCOREBOARD_BANKRUPTS" "Uccisioni bancarotta"
+ "SNS_LEADER_BANKRUPT" "Leader punteggio in bancarotta!"
+ "SNS_LEADER_BANKRUPT_SUB" "%s1 è stato resettato da %s2"
+ "SNS_BANKRUPT" "Bancarotta!"
+ "sns_softball_kill_value" "Valore per uccisione Softball"
+ "sns_offhand_kill_value" "Valore per uccisione manuale"
+ "sns_melee_kill_value" "Valore per uccisione corpo a corpo"
+ "sns_softball_enabled" "Softball abilitato"
+ "PL_tffa" "Tutti contro tutti Titan"
+ "PL_tffa_lobby" "Lobby: Tutti contro tutti Titan"
+ "PL_tffa_hint" "Ogni pilota per sè, distruggi tutti i titan nemici."
+ "PL_tffa_abbr" "TFFA"
+ "GAMEMODE_TFFA" "Tutti contro tutti Titan"
+ "sns_reset_pulse_blade_cooldown_on_pulse_blade_kill" "Cooldown reset all'uccisione"
+ "SHOW" "Mostra"
+ "SHOW_ALL" "Tutto"
+ "SHOW_ONLY_ENABLED" "Solo Abilitate"
+ "SHOW_ONLY_DISABLED" "Solo Disabilitate"
+ "SHOW_ONLY_NOT_REQUIRED" "Solo Mods opzionali"
+ "SHOW_ONLY_REQUIRED" "Solo Mods richieste"
+ "WARNING" "Attenzione"
+ "CORE_MOD_DISABLE_WARNING" "Disattivare mods di base può rompere il client!"
+ "AUTHENTICATIONAGREEMENT_NO" "Hai scelto di non autenticarti con Northstar. Puoi vedere l'accordo nel menu delle Mods."
+ "aitdm_archer_grunts" "Soldati Archer"
+ "gg_kill_reward" "Percentuale ricompensa per uccisione"
+ "PL_sns_desc" "Tutti contro tutti. Usa la Lama Impulsi e l'esecuzione per resettare il punteggio avversario"
+ "SNS_BANKRUPT_SUB" "Il you punteggio è stato resettato da %s1"
+ "sns_wme_kill_value" "Valore per uccisione Wingman d'Elite"
+ "sns_reset_kill_value" "Valore per uccisione Lama Impulsi/Esecuzione"
+ "PL_tffa_desc" "Ogni pilota per sè, distruggi tutti i titan nemici."
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt b/Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt
index 08772933..798d603e 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt
@@ -3,9 +3,15 @@
"Language" "japanese"
"Tokens"
{
+ // ă•ă‚ˇă‚¤ă«ăŻUTF-16 LEă§ă‚¨ăłă‚łăĽă‰ă—ă¦ăŹă ă•ă„ă€ăˇă˘ĺ¸łăŞă‚‰"ĺŤĺ‰Ťă‚’ä»ă‘ă¦äżťĺ­"ă‹ă‚‰
+
"MENU_LAUNCH_NORTHSTAR" "Northstară‚’čµ·ĺ‹•"
"MENU_TITLE_MODS" "Modă®ç®ˇç†"
"RELOAD_MODS" "Modă‚’ăŞă­ăĽă‰"
+ "WARNING" "警告"
+ "CORE_MOD_DISABLE_WARNING" "コアModを無効化ă™ă‚‹ă¨ă€ă‚Żă©ă‚¤ă‚˘ăłăăŚç ´ćŤă™ă‚‹ĺŹŻč˝ć€§ăŚă‚ă‚Šăľă™ďĽ"
+ // 多ĺ†ăśă‚żăłă«ä˝żă‚Źă‚Śă‚‹ă¨ć€ťă†ă®ă§ă€Śç„ˇĺŠąĺŚ–」ă¨ă—ăźăŚă€ä»ŠĺľŚĺ¤‰ć›´ă™ă‚‹ĺż…č¦ăŚă‚ă‚‹ă‹ă‚‚ă—ă‚ŚăŞă„
+ "DISABLE" "無効化"
"MENU_MAIN_AUTHENTICATING" "認証中..."
"MENU_MAIN_CONNECTING" "ă­ăĽă‚«ă«ă‚µăĽăăĽă¸ă®ćŽĄç¶š"
@@ -17,8 +23,12 @@
"AUTHENTICATION_AGREEMENT" "認証ă¸ă®ĺŚć„Ź"
"AUTHENTICATION_AGREEMENT_RESTART" "変更をé©ç”¨ă™ă‚‹ă«ăŻă€ä¸€ĺş¦Titanfall 2を再起動ă™ă‚‹ĺż…č¦ăŚă‚ă‚Šăľă™ă€‚"
+ "DIALOG_AUTHENTICATING_MASTERSERVER" "ăžă‚ąă‚żăĽă‚µăĽăăĽă¸čŞŤč¨Ľä¸­..."
+ "AUTHENTICATIONAGREEMENT_NO" "Northstară®čŞŤč¨Ľă‚’čˇŚă‚ŹăŞă„ă“ă¨ă‚’é¸ćŠžă—ăľă—ăźă€‚Modăˇă‹ăĄăĽă‹ă‚‰ĺ†Ťĺş¦ă“ă®ă€ă‚¤ă‚˘ă­ă‚°ă‚’é–‹ăŹă“ă¨ăŚă§ăŤăľă™ă€‚"
+
"MENU_TITLE_SERVER_BROWSER" "サăĽăăĽă–ă©ă‚¦ă‚¶ăĽ"
"NS_SERVERBROWSER_NOSERVERS" "サăĽăăĽăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚"
+ "NS_SERVERBROWSER_UNKNOWNMODE" "不ćŽăŞă˘ăĽă‰"
"NS_SERVERBROWSER_WAITINGFORSERVERS" "サăĽăăĽă‚’ĺľ…ăŁă¦ă„ăľă™..."
"NS_SERVERBROWSER_CONNECTIONFAILED" "接続ă«ĺ¤±ć•—ă—ăľă—ăźďĽ"
// č¦č­°č«–
@@ -60,6 +70,7 @@
// č¦č­°č«–: "時間ĺ¶é™"ă®ăľăľă‹ă€"ăžăă時間"ă€"タイă ä¸Šé™"ă€ăťă‚Śä»Ąĺ¤–ă¸ă®ĺ·®ă—替ă
"timelimit" "時間ĺ¶é™"
"roundtimelimit" "時間ĺ¶é™ (ă©ă‚¦ăłă‰ă™ăĽă‚ą)"
+ "respawnprotection" "ăŞă‚ąăťăĽăłäżťč­·ć™‚é–“"
"pilot_health_multiplier" "ăă«ă‚ąĺ€ŤçŽ‡"
// č¦č­°č«–: "ăŞă‚ąăťăĽăłé…延" ă®ăľăľă‹ă€"ăŞă‚ąăťăĽăłăľă§ă®ć™‚é–“"ă€ăťă‚Śä»Ąĺ¤–ă¸ă®ĺ·®ă—替ă
@@ -89,29 +100,76 @@
"cp_amped_capture_points" "拠点増幅"
"coliseum_loadouts_enabled" "ă‚łă­ă‚·ă‚˘ă ă­ăĽă‰ă‚˘ă‚¦ă"
+ // ăťă®ăľăľă€Śă‚°ă©ăłă」ă‚ă‚Šă‚‚ă€Respawn翻訳ă§ä˝żă‚Źă‚Śă¦ă„ă‚‹
+ //「ăźă‹ă‚Şăłă€Ťă®ă»ă†ăŚäĽťă‚Źă‚Šă‚„ă™ă„ă‹ă‚‚ă—ă‚ŚăŞă„ă®ă§ă“ăŁăˇă«ă™ă‚‹
+ // 「アăĽăăŁăĽă‚’ćŚăŁăźăźă‹ă‚Şăłă€Ťă¨ă‹ă ă¨ćµçźłă«ă‚ăŹăŞă•ăťă†ăŞă®ă§ă¨ă‚Šă‚ăăšăťă®ăľăľć—Ąćś¬čŞžă«ă€‚
+ "aitdm_archer_grunts" "アăĽăăŁăĽćŚăˇăźă‹ă‚Şăł"
+
// northstar.custom localisation is just deciding not to work, so putting it here for now
"PL_sbox" "サăłă‰ăśăă‚Żă‚ą"
- "PL_sbox_lobby" "サăłă‰ăśăă‚Żă‚ąă­ă“ăĽ"
+ "PL_sbox_lobby" "サăłă‰ăśăă‚Żă‚ą ă­ă“ăĽ"
"PL_sbox_desc" "サăłă‰ăśăă‚Żă‚ą"
"PL_sbox_abbr" "SBOX"
"GAMEMODE_SBOX" "Sandbox"
"PL_gg" "ガăłă»ă‚˛ăĽă "
- "PL_gg_lobby" "ガăłă»ă‚˛ăĽă ă­ă“ăĽ"
+ "PL_gg_lobby" "ガăłă»ă‚˛ăĽă  ă­ă“ăĽ"
"PL_gg_desc" "ĺ…¨ă¦ă®éŠă§ă‚­ă«ă‚’取ăŁă¦ĺ‹ťĺ©ă—ろ。"
"PL_gg_hint" "ĺ…¨ă¦ă®éŠă§ă‚­ă«ă‚’取ăŁă¦ĺ‹ťĺ©ă—ろ。"
"PL_gg_abbr" "GG"
"GAMEMODE_GG" "ガăłă»ă‚˛ăĽă "
+ "gg_kill_reward" "ă‚­ă«ăŞăŻăĽă‰ĺ€ŤçŽ‡"
+ "gg_assist_reward" "アシスăăŞăŻăĽă‰ĺ€ŤçŽ‡"
+ "gg_execution_reward" "処ĺ‘ăŞăŻăĽă‰ĺ€ŤçŽ‡"
"PL_tt" "タイタăłă»ă‚żă‚°"
- "PL_tt_lobby" "タイタăłă»ă‚żă‚°ă­ă“ăĽ"
+ "PL_tt_lobby" "タイタăłă»ă‚żă‚° ă­ă“ăĽ"
"PL_tt_desc" "タイタăłă¨ă—ă¦ăťă‚¤ăłăを稼ă’。敵ă®ă‚żă‚¤ă‚żăłă‚’破壊ă—自ĺ†ă®ă‚żă‚¤ă‚żăłă‚’確保ă—ろ。"
"PL_tt_hint" "タイタăłă¨ă—ă¦ăťă‚¤ăłăを稼ă’。敵ă®ă‚żă‚¤ă‚żăłă‚’破壊ă—自ĺ†ă®ă‚żă‚¤ă‚żăłă‚’確保ă—ろ。"
"PL_tt_abbr" "TT"
"GAMEMODE_TT" "タイタăłă»ă‚żă‚°"
+ "PL_chamber" "ăŻăłă»ă‚¤ăłă»ă‚¶ă»ăăŁăłăăĽ"
+ "PL_chamber_lobby" "ăŻăłă»ă‚¤ăłă»ă‚¶ă»ăăŁăłă㼠ă­ă“ăĽ"
+ "PL_chamber_desc" "ăŻăłă‚·ă§ăăă»ăŻăłă‚­ă«ă€‚敵をキă«ă—ă¦ă€ĺĽľĺ€‰ă«ć–°ăźăŞĺĽľä¸¸ă‚’込ă‚ろ。"
+ "PL_chamber_hint" "ăŻăłă‚·ă§ăăă»ăŻăłă‚­ă«ă€‚敵をキă«ă—ă¦ă€ĺĽľĺ€‰ă«ć–°ăźăŞĺĽľä¸¸ă‚’込ă‚ろ。"
+ "PL_chamber_abbr" "CHAMBER"
+ "GAMEMODE_CHAMBER" "ăŻăłă»ă‚¤ăłă»ă‚¶ă»ăăŁăłăăĽ"
+
+ "PL_hidden" "ザă»ă’ă‡ă‚Ąăł"
+ "PL_hidden_lobby" "ザă»ă’ă‡ă‚Ąăł ă­ă“ăĽ"
+ "PL_hidden_desc" "透ćŽĺŚ–ă—ă¦ă„ă‚‹ă—ă¬ă‚¤ă¤ăĽăŚä¸€äşşć˝śă‚“ă§ă„る。ă’ă‡ă‚Ąăłă‚’ć’ç ´ă›ă‚。"
+ "PL_hidden_hint" "透ćŽĺŚ–ă—ă¦ă„ă‚‹ă—ă¬ă‚¤ă¤ăĽăŚä¸€äşşć˝śă‚“ă§ă„る。ă’ă‡ă‚Ąăłă‚’ć’ç ´ă›ă‚。"
+ "PL_hidden_abbr" "HIDDEN"
+ "GAMEMODE_HIDDEN" "ザă»ă’ă‡ă‚Ąăł"
+ "HIDDEN_YOU_ARE_HIDDEN" "ă’ă‡ă‚Ąăłă«ăŞăŁăźďĽ"
+ "HIDDEN_KILL_SURVIVORS" "ă™ăąă¦ă®ă‚µăイăăĽă‚’ć’ç ´ă›ă‚。"
+ "HIDDEN_FIRST_HIDDEN" "%s1 ăŻă’ă‡ă‚Ąăłă«ăŞăŁăźă€‚"
+
+ "PL_sns" "ă‚ąă†ă‚Łăă‚Żă»ă‚˘ăłă‰ă»ă‚ąăăĽăł"
+ "PL_sns_lobby" "ă‚ąă†ă‚Łăă‚Żă»ă‚˘ăłă‰ă»ă‚ąăăĽăł ă­ă“ăĽ"
+ "PL_sns_desc" "ă•ăŞăĽă»ă•ă‚©ăĽă»ă‚ŞăĽă«ă€‚ă‘ă«ă‚ąă–ă¬ăĽă‰ă‹ă€ĺ‡¦ĺ‘を使用ă—ă¦ć•µă®ă‚ąă‚łă‚˘ă‚’ăŞă‚»ăăă§ăŤă‚‹ă€‚"
+ "PL_sns_abbr" "SNS"
+ "GAMEMODE_SNS" "ă‚ąă†ă‚Łăă‚Żă»ă‚˘ăłă‰ă»ă‚ąăăĽăł"
+ // č¦ĺ¤‰ć›´: 破産以外ă®č¨€č‘‰ă«ç˝®ăŤćŹ›ăă‚‹
+ "SCOREBOARD_BANKRUPTS" "破産キă«"
+ "SNS_LEADER_BANKRUPT" "スコアăŞăĽă€ăĽăŚç ´ç”Łă—ăźďĽ"
+ "SNS_LEADER_BANKRUPT_SUB" "%s1 㯠%s2 ă«ă‚ąă‚łă‚˘ă‚’ăŞă‚»ăăă•ă‚Śăź"
+ "SNS_BANKRUPT" "破産ďĽ"
+ "SNS_BANKRUPT_SUB" "ă‚ăŞăźă®ă‚ąă‚łă‚˘ăŻ %s1 ă«ă‚ăŁă¦ăŞă‚»ăăă•ă‚Śăź"
+ // č¦č­°č«–: value -> "ć•°"?ăťă‚Śă¨ă‚‚"値"?
+ "sns_wme_kill_value" "ウィăłă‚°ăžăłă»ă‚¨ăŞăĽăă‚­ă«ć•°"
+ "sns_softball_kill_value" "ă‚˝ă•ăăśăĽă«ă‚­ă«ć•°"
+ "sns_offhand_kill_value" "ă‚Şă•ăŹăłă‰ă‚­ă«ć•°"
+ "sns_reset_kill_value" "ă‘ă«ă‚ąă–ă¬ăĽă‰/処ĺ‘ă‚­ă«ć•°"
+ "sns_melee_kill_value" "ć Ľé—ă‚­ă«ć•°"
+ // č¦ĺ¤‰ć›´: 「キă«ăŞă‚»ăăăľă§ă®ă‚ŻăĽă«ă€ă‚¦ăłă‚’有効化ă™ă‚‹ă‹ă©ă†ă‹ă€ŤăŞă®ă‹ă€ă€Śă‚­ă«ă«ă‚ăŁă¦ă‚ŻăĽă«ă€ă‚¦ăłă‚’ăŞă‚»ăăă™ă‚‹ă‹ă©ă†ă‹ă€ŤăŞă®ă‹ă€‚
+ // ă˛ă¨ăľăšăťă®ăľăľć—Ąćś¬čŞžă«ç›´ă—ăźăŚă€ă‚Źă‹ă‚Šă«ăŹă„ă®ă§ĺ¤‰ć›´ă™ă‚‹ăąăŤă€‚
+ "sns_reset_pulse_blade_cooldown_on_pulse_blade_kill" "ă‚­ă«ă‚ŻăĽă«ă€ă‚¦ăłăŞă‚»ăă"
+ "sns_softball_enabled" "ă‚˝ă•ăăśăĽă«ă‚’有効化"
+
"PL_inf" "イăłă•ă‚§ă‚Żă‚·ă§ăł"
- "PL_inf_lobby" "イăłă•ă‚§ă‚Żă‚·ă§ăłă­ă“ăĽ"
+ "PL_inf_lobby" "イăłă•ă‚§ă‚Żă‚·ă§ăł ă­ă“ăĽ"
"PL_inf_desc" "生ăŤć®‹ă‚ŠăŻć­»äşˇă™ă‚‹ă¨ă‚¤ăłă•ă‚§ă‚Żă‚żăĽă«ăŞă‚‹ă€‚"
"PL_inf_hint" "生ăŤć®‹ă‚ŠăŻć­»äşˇă™ă‚‹ă¨ă‚¤ăłă•ă‚§ă‚Żă‚żăĽă«ăŞă‚‹ă€‚"
"PL_inf_abbr" "INF"
@@ -125,8 +183,15 @@
"INFECTION_YOU_ARE_LAST_SURVIVOR" "ăŠĺ‰ŤăŚćś€ĺľŚă®ç”źăŤć®‹ă‚Šă ďĽ"
"INFECTION_SURVIVE_LAST_SURVIVOR" "生ăŤă‚Ť"
+ "PL_tffa" "タイタ㳠ă•ăŞăĽă»ă•ă‚©ăĽă»ă‚ŞăĽă«"
+ "PL_tffa_lobby" "タイタ㳠ă•ăŞăĽă»ă•ă‚©ăĽă»ă‚ŞăĽă« ă­ă“ăĽ"
+ "PL_tffa_desc" "ă™ăąă¦ă®ă‘イă­ăăăŻă‚żă‚¤ă‚żăłă«ć­äą—ă—ă¦ă„る。ă™ăąă¦ă®ć•µă‚żă‚¤ă‚żăłă‚’ć’ç ´ă›ă‚。"
+ "PL_tffa_hint" "ă™ăąă¦ă®ă‘イă­ăăăŻă‚żă‚¤ă‚żăłă«ć­äą—ă—ă¦ă„る。ă™ăąă¦ă®ć•µă‚żă‚¤ă‚żăłă‚’ć’ç ´ă›ă‚。"
+ "PL_tffa_abbr" "TFFA"
+ "GAMEMODE_TFFA" "タイタ㳠ă•ăŞăĽă»ă•ă‚©ăĽă»ă‚ŞăĽă«"
+
"PL_hs" "ăŹă‚¤ă‰ă»ă‚˘ăłă‰ă»ă‚·ăĽă‚Ż"
- "PL_hs_lobby" "ăŹă‚¤ă‰ă»ă‚˘ăłă‰ă»ă‚·ăĽă‚Żă­ă“ăĽ"
+ "PL_hs_lobby" "ăŹă‚¤ă‰ă»ă‚˘ăłă‰ă»ă‚·ăĽă‚Ż ă­ă“ăĽ"
"PL_hs_desc" "ăŹă‚¤ă€ăĽăŻéš ă‚Śă€ă‚·ăĽă‚«ăĽăŻăŹă‚¤ă€ăĽă‚’探ă›ďĽ"
"PL_hs_hint" "ăŹă‚¤ă€ăĽăŻéš ă‚Śă€ă‚·ăĽă‚«ăĽăŻăŹă‚¤ă€ăĽă‚’探ă›ďĽ"
"PL_hs_abbr" "HS"
@@ -145,13 +210,13 @@
"GAMEMODE_fw" "ă•ă­ăłă†ă‚Łă‚˘ć¦äş‰"
"PL_fw" "ă•ă­ăłă†ă‚Łă‚˘ć¦äş‰"
- "PL_fw_lobby" "ă•ă­ăłă†ă‚Łă‚˘ć¦äş‰ă­ă“ăĽ"
+ "PL_fw_lobby" "ă•ă­ăłă†ă‚Łă‚˘ć¦äş‰ ă­ă“ăĽ"
"PL_fw_desc" "敵ă®ăŹăĽă™ă‚ąă‚żăĽă‚’破壊ă—ă€č‡Şĺ†ă®ă‚’ĺ®ă‚ŚďĽ"
"PL_fw_abbr" "FW"
"GAMEMODE_kr" "強化キă«ă¬ăĽă‚ą"
"PL_kr" "強化キă«ă¬ăĽă‚ą"
- "PL_kr_lobby" "強化キă«ă¬ăĽă‚ąă­ă“ăĽ"
+ "PL_kr_lobby" "強化キă«ă¬ăĽă‚ą ă­ă“ăĽ"
"PL_kr_desc" "ć——ă‚’ć‹ľă„ă€ă‚­ă«ă¬ăĽă‚ąă‚’開始ă—ろ。キă«ă§ăťă‚¤ăłăを上ă’ă€ă‚­ă«ă¬ăĽă‚ąă®ć™‚間を延ă°ă›ă€‚最é«ăťă‚¤ăłăč¨éŚ˛ăŚä¸€ç•Şé«ă„者ăŚĺ‹ťĺ©ă™ă‚‹"
"PL_kr_hint" "ć——ă‚’ć‹ľă„ă€ă‚­ă«ă¬ăĽă‚ąă‚’開始ă—ろ。キă«ă§ăťă‚¤ăłăを上ă’ă€ă‚­ă«ă¬ăĽă‚ąă®ć™‚間を延ă°ă›ă€‚最é«ăťă‚¤ăłăč¨éŚ˛ăŚä¸€ç•Şé«ă„者ăŚĺ‹ťĺ©ă™ă‚‹"
"PL_kr_abbr" "KR"
@@ -167,9 +232,9 @@
"GAMEMODE_fastball" "ă•ă‚ˇă‚ąăăśăĽă«"
"PL_fastball" "ă•ă‚ˇă‚ąăăśăĽă«"
- "PL_fastball_lobby" "ă•ă‚ˇă‚ąăăśăĽă«ă­ă“ăĽ"
- "PL_fastball_desc" "ă©ă‚¤ă–ă•ă‚ˇă‚¤ă‚˘ă€‚ă‘ăŤă«ă‚’ăŹăă‚Żă—ă€ĺ‘łć–ąă‚’č‡ç”źă§ăŤă‚‹"
- "PL_fastball_hint" "ă©ă‚¤ă–ă•ă‚ˇă‚¤ă‚˘ă€‚ă‘ăŤă«ă‚’ăŹăă‚Żă—ă€ĺ‘łć–ąă‚’č‡ç”źă§ăŤă‚‹"
+ "PL_fastball_lobby" "ă•ă‚ˇă‚ąăăśăĽă« ă­ă“ăĽ"
+ "PL_fastball_desc" "ă©ă‚¤ă–ă•ă‚ˇă‚¤ă‚˘ă€‚ă‘ăŤă«ă‚’ăŹăă‚Żă—ă€ĺ‘łć–ąă‚’č‡ç”źă§ăŤă‚‹ă€‚"
+ "PL_fastball_hint" "ă©ă‚¤ă–ă•ă‚ˇă‚¤ă‚˘ă€‚ă‘ăŤă«ă‚’ăŹăă‚Żă—ă€ĺ‘łć–ąă‚’č‡ç”źă§ăŤă‚‹ă€‚"
"PL_fastball_abbr" "FB"
"FASTBALL_PANEL_CAPTURED" "%s1 ăŚă‘ăŤă«%s2ă‚’ĺ¶ĺś§ă—ăź"
"SCOREBOARD_FASTBALL_HACKS" "ĺ¶ĺś§ă‘ăŤă«"
@@ -182,6 +247,7 @@
"MODE_SETTING_CATEGORY_BLEEDOUT" "ă‘イă­ăăă®ă€ă‚¦ăł"
"custom_air_accel_pilot" "空中加速度"
+ "no_pilot_collision" "ă‘イă­ăăĺŚĺŁ«ă®ĺ˝“ăźă‚Šĺ¤ĺ®š"
"promode_enable" "Proă˘ăĽă‰ă®ć­¦ĺ™¨"
"fp_embark_enabled" "ć­äą—ă¨ĺ‡¦ĺ‘ă®ä¸€äşşç§°č¦–ç‚ą"
"classic_rodeo" "ă‚Żă©ă‚·ăă‚Żă­ă‡ă‚Ş"
@@ -199,10 +265,10 @@
"player_bleedout_aiBleedingPlayerMissChance" "ă€ă‚¦ăłć™‚ă®AI命中率"
// coop stuff
- "PL_sp_coop" "(UNFINISHED) Singleplayer Coop"
- "PL_sp_coop_lobby" "Singleplayer Coop Lobby"
- "PL_sp_coop_desc" "Play through the singleplayer campaign with friends"
- "PL_sp_coop_hint" "Play through the singleplayer campaign with friends"
+ "PL_sp_coop" "(未完ć) ă‚·ăłă‚°ă«ă—ă¬ă‚¤ă¤ăĽ 協力ă˘ăĽă‰"
+ "PL_sp_coop_lobby" "ă‚·ăłă‚°ă«ă—ă¬ă‚¤ă¤ăĽ 協力ă˘ăĽă‰ ă­ă“ăĽ"
+ "PL_sp_coop_desc" "ă‚·ăłă‚°ă«ă—ă¬ă‚¤ă¤ăĽă®ă‚­ăŁăłăšăĽăłă˘ăĽă‰ă‚’ă•ă¬ăłă‰ă¨ä¸€ç·’ă«ă—ă¬ă‚¤ă§ăŤă‚‹"
+ "PL_sp_coop_hint" "ă‚·ăłă‚°ă«ă—ă¬ă‚¤ă¤ăĽă®ă‚­ăŁăłăšăĽăłă˘ăĽă‰ă‚’ă•ă¬ăłă‰ă¨ä¸€ç·’ă«ă—ă¬ă‚¤ă§ăŤă‚‹"
"PL_sp_coop_abbr" "SP"
"SP_TRAINING" "ă‘イă­ăăă»ă‚¬ăłăă¬ăă"
@@ -237,7 +303,7 @@
"PLAYERS_COLUMN" "ă—ă¬ă‚¤ă¤ăĽ"
"MAP_COLUMN" "ăžăă—"
"GAMEMODE_COLUMN" "ゲăĽă ă˘ăĽă‰"
- "LATENCY_COLUMN" "ă¬ă‚¤ă†ăłă‚·ăĽ"
+ "REGION_COLUMN" "é ĺźź"
"SEARCHBAR_LABEL" "検索:"
"MAP_FILTER" "ăžăă—"
"GAMEMODE_FILTER" "ゲăĽă ă˘ăĽă‰"
@@ -257,10 +323,88 @@
"CONNECTING" "接続中..."
"INGAME_PLAYERS" "ă—ă¬ă‚¤ă¤ăĽć•°: ^6BA6C400%s1"
"TOTAL_SERVERS" "サăĽăăĽć•°: ^C46C6C00%s1"
- // Translation done by Zetryox and CYakigasi
+
+ // Mods menu
+ "SHOW" "表示"
+ "SHOW_ALL" "ĺ…¨ă¦"
+ "SHOW_ONLY_ENABLED" "有効ă®ăż"
+ "SHOW_ONLY_DISABLED" "無効ă®ăż"
+
+ // Maps menu
+ "HIDE_LOCKED" "ă­ăク中を隠ă™"
// In-game chat
"HUD_CHAT_WHISPER_PREFIX" "[WHISPER]"
"HUD_CHAT_SERVER_PREFIX" "[SERVER]"
+
+ "NO_GAMESERVER_RESPONSE" "ゲăĽă ă‚µăĽăăĽă«ćŽĄç¶šă§ăŤăľă›ă‚“\n(Couldn't reach game server)"
+ "BAD_GAMESERVER_RESPONSE" "ゲăĽă ă‚µăĽăăĽăŚä¸ŤćŽăŞă¬ă‚ąăťăłă‚ąă‚’čż”ă—ăľă—ăź\n(Game server gave an invalid response)"
+ "UNAUTHORIZED_GAMESERVER" "ゲăĽă ă‚µăĽăăĽă«ăťă®ăŞă‚Żă‚¨ă‚ąăを作ćă™ă‚‹č¨±ĺŹŻăŚă‚ă‚Šăľă›ă‚“\n(Game server is not authorized to make that request)"
+ "UNAUTHORIZED_GAME" "StryderăŻă“ă®ă‚˘ă‚«ă‚¦ăłăăŚTitanfall 2を所ćŚă—ă¦ă„ă‚‹ă‹ă©ă†ă‹ă‚’確認ă§ăŤăľă›ă‚“ă§ă—ăź\n(Stryder couldn't confirm that this account owns Titanfall 2)"
+ "UNAUTHORIZED_PWD" "ă‘ă‚ąăŻăĽă‰ăŚé–“é•ăŁă¦ă„ăľă™\n(Wrong password)"
+ "STRYDER_RESPONSE" "Stryderă‹ă‚‰ă®ă¬ă‚ąăťăłă‚ąă®ĺ‡¦ç†ă«ĺ¤±ć•—ă—ăľă—ăź\n(Couldn't parse stryder response)"
+ "PLAYER_NOT_FOUND" "ă—ă¬ă‚¤ă¤ăĽă®ă‚˘ă‚«ă‚¦ăłăăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“\n(Couldn't find player account)"
+ "INVALID_MASTERSERVER_TOKEN" "ăžă‚ąă‚żăĽă‚µăĽăăĽă®ăăĽă‚ŻăłăŚä¸ŤćŽă‹ćśźé™ĺ‡ă‚Śă§ă™ă€‚EAアă—ăŞă®ĺ†Ťčµ·ĺ‹•ă‚’ăŠč©¦ă—ăŹă ă•ă„。\n(Invalid or expired masterserver token, try restarting EA App.)"
+ "JSON_PARSE_ERROR" "JSONă¬ă‚ąăťăłă‚ąă®ĺ‡¦ç†ă«ĺ¤±ć•—ă—ăľă—ăź\n(Error parsing json response)"
+ "UNSUPPORTED_VERSION" "現在使用ă—ă¦ă„ă‚‹ăăĽă‚¸ă§ăłăŻă‚µăťăĽăă•ă‚Śă¦ă„ăľă›ă‚“\n(The version you are using is no longer supported)"
+ "player_force_respawn" "強ĺ¶ăŞă‚ąăťăĽăł"
+ "SHOW_ONLY_REQUIRED" "ĺż…é ă®Modă®ăż"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% 進行シスă†ă ĺ‡ă‚Šć›żă"
+ "TOGGLE_PROGRESSION" "進行シスă†ă ĺ‡ă‚Šć›żă"
+ "AUTHENTICATION_FAILED_HEADER" "認証ă«ĺ¤±ć•—"
+ "DOWNLOADING_MOD_TITLE" "Modă‚’ă€ă‚¦ăłă­ăĽă‰ä¸­"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "タイタăłă€ć­¦ĺ™¨ă€ĺ‹˘ĺŠ›ă€ă‚ąă‚­ăłăŞă©ăŚĺ…¨ă¦ă‚˘ăłă­ăă‚Żă•ă‚Śă€ă„ă¤ă§ă‚‚使ăă‚‹ă‚ă†ă«ăŞă‚‹ă€‚\n\nă“ă®č¨­ĺ®šăŻă€ăžă«ăă—ă¬ă‚¤ă¤ăĽă­ă“ăĽă§ă„ă¤ă§ă‚‚変更可č˝ă ă€‚"
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "進行シスă†ă ă‚’有効ă«ă—ăľă™ă‹ďĽź"
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "進行シスă†ă ă‚’無効ă«ă—ăľă™ă‹ďĽź"
+ "PROGRESSION_DISABLED_HEADER" "進行シスă†ă ăŚç„ˇĺŠąă«ăŞă‚Šăľă—ăźďĽ"
+ "PROGRESSION_ENABLED_HEADER" "進行シスă†ă ăŚćś‰ĺŠąă«ăŞă‚Šăľă—ăźďĽ"
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000進行シスă†ă ăŚç„ˇĺŠąĺŚ–ă•ă‚Śăźă€‚^\n\nタイタăłă€ć­¦ĺ™¨ă€ĺ‹˘ĺŠ›ă€ă‚ąă‚­ăłăŞă©ăŚĺ…¨ă¦ă‚˘ăłă­ăă‚Żă•ă‚Śă€ă„ă¤ă§ă‚‚使ăă‚‹ă‚ă†ă«ăŞă‚‹ă€‚\n\nă“ă®č¨­ĺ®šăŻă€ăžă«ăă—ă¬ă‚¤ă¤ăĽă­ă“ăĽă§ă„ă¤ă§ă‚‚変更可č˝ă ă€‚"
+ "SHOULD_RETURN_TO_LOBBY" "ăžăă終了後ă«ă­ă“ăĽă¸ć»ă‚‹"
+ "REPLACEMENT_WEAPON" "武器ă®ç˝®ăŤćŹ›ă"
+ "TACTICAL_REPLACEMENT" "ć¦čˇ“ă®ç˝®ăŤćŹ›ă"
+ "DISALLOWED_TACTICALS" "ć¦čˇ“ă®ç¦ć­˘"
+ "DISALLOWED_WEAPONS" "武器ă®ç¦ć­˘"
+ "ONLY_HOST_CAN_START_MATCH" "ăžăăを開始ă§ăŤă‚‹ă®ăŻă›ă‚ąăă®ăż"
+ "ONLY_HOST_MATCH_SETTINGS" "ă—ă©ă‚¤ă™ăĽăăžăăă®č¨­ĺ®šă‚’変更ă§ăŤă‚‹ă®ăŻă›ă‚ąăă®ăż"
+ "AUTHENTICATION_FAILED_HELP" "ăă«ă—"
+ "MATCH_COUNTDOWN_LENGTH" "ă—ă©ă‚¤ă™ăĽăăžăăă®ă‚«ă‚¦ăłăă€ă‚¦ăłć™‚é–“"
+ "ARE_YOU_SURE" "ă‚ă‚Ťă—ă„ă§ă™ă‹ďĽź"
+ "MOD_SETTINGS_SERVER" "サăĽăăĽ"
+ "MOD_SETTINGS_RESET" "ăŞă‚»ăă"
+ "MOD_SETTINGS_RESET_ALL" "ĺ…¨ă¦ăŞă‚»ăă"
+ "NO_RESULTS" "ăŞă‚¶ă«ăăŞă—。"
+ "MOD_SETTINGS" "Modă®č¨­ĺ®š"
+ "NORTHSTAR_BASE_SETTINGS" "Northstară®ĺźşćś¬č¨­ĺ®š"
+ "NO_DISK_SPACE_AVAILABLE" "ă‡ă‚Łă‚ąă‚Żă«ĺŤĺ†ăŞé ĺźźăŚă‚ă‚Šăľă›ă‚“。"
+ "FAILED_DOWNLOADING" "Modă®ă€ă‚¦ăłă­ăĽă‰ă«ĺ¤±ć•—"
+ "FAILED_READING_ARCHIVE" "ModアăĽă‚«ă‚¤ă–ă®čŞ­čľĽä¸­ă«ă‚¨ă©ăĽăŚç™şç”źă—ăľă—ăźă€‚"
+ "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Modă‚’ă€ă‚¦ăłă­ăĽă‰ä¸­ (%s1%)"
+ "DOWNLOADING_MOD_TEXT" "ă€ă‚¦ăłă­ăĽă‰ä¸­ %s1 v%s2..."
+ "DOWNLOADING_MOD_TEXT_W_PROGRESS" "ă€ă‚¦ăłă­ăĽă‰ä¸­ %s1 v%s2...\n(%s3/%s4 MB)"
+ "MOD_FETCHING_FAILED" "Thunderstoreă‹ă‚‰ModアăĽă‚«ă‚¤ă–ă‚’ă€ă‚¦ăłă­ăĽă‰ă§ăŤăľă›ă‚“ă§ă—ăźă€‚"
+ "EXTRACTING_MOD_TITLE" "Modを抽出中 (%s1%)"
+ "EXTRACTING_MOD_TEXT" "抽出中 %s1 v%s2...\n(%s3/%s4 MB)"
+ "FAILED_WRITING_TO_DISK" "Modă•ă‚ˇă‚¤ă«ă‚’ă•ă‚ˇă‚¤ă«ă‚·ă‚ąă†ă ă«ćŠ˝ĺ‡şă™ă‚‹éš›ă«ă‚¨ă©ăĽăŚç™şç”źă—ăľă—ăźă€‚"
+ "MOD_FETCHING_FAILED_GENERAL" "Modă®ćŠ˝ĺ‡şă«ĺ¤±ć•—。詳細ăŻă­ă‚°ă‚’確認ă—ă¦ăŹă ă•ă„。"
+ "MOD_CORRUPTED" "ă€ă‚¦ăłă­ăĽă‰ă—ăźă‚˘ăĽă‚«ă‚¤ă–ă®ăェăクサă ăŚć¤śč¨Ľć¸ăżç˝˛ĺŤă¨ä¸€č‡´ă—ăľă›ă‚“ă§ă—ăźă€‚"
+ "CHECKSUMING_TITLE" "Modă‚’ăェăクサă ä¸­"
+ "CHECKSUMING_TEXT" "ă‚łăłă†ăłă„を検証中 %s1 v%s2..."
+ "MOD_DL_DISABLED" "(自動Modă€ă‚¦ăłă­ăĽă‰ăŻç„ˇĺŠąă§ă™)"
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "タイタăłă€ć­¦ĺ™¨ă€ĺ‹˘ĺŠ›ă€ă‚ąă‚­ăłăŞă©ă®ă‚˘ăłă­ăă‚Żă«ă¬ă™ă«ä¸Šă’ă‚„ăˇăŞăăăŚĺż…č¦ă«ăŞă‚‹ă€‚\n\nă“ă®č¨­ĺ®šăŻă€ăžă«ăă—ă¬ă‚¤ă¤ăĽă­ă“ăĽă§ă„ă¤ă§ă‚‚変更可č˝ă ă€‚\n\n^CC000000警告: 現在装備ă—ă¦ă„るアイă†ă ăŚă­ăク中ă®ĺ ´ĺă€čŁ…備状ćłăŻĺťćśźĺŚ–ă•ă‚Śăľă™ďĽ"
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000進行シスă†ă ăŚćś‰ĺŠąĺŚ–ă•ă‚Śăźă€‚^\n\nタイタăłă€ć­¦ĺ™¨ă€ĺ‹˘ĺŠ›ă€ă‚ąă‚­ăłăŞă©ă®ă‚˘ăłă­ăă‚Żă«ă¬ă™ă«ä¸Šă’ă‚„ăˇăŞăăăŚĺż…č¦ă«ăŞă‚‹ă€‚\n\nă“ă®č¨­ĺ®šăŻă€ăžă«ăă—ă¬ă‚¤ă¤ăĽă­ă“ăĽă§ă„ă¤ă§ă‚‚変更可č˝ă ă€‚"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "不ćŽăŞă‚Żă©ă‚¤ă‚˘ăłăă‚łăžăłă‰ă‚’č¨éŚ˛ă™ă‚‹"
+ "SHOW_ONLY_NOT_REQUIRED" "任意ă®Modă®ăż"
+ "AUTHENTICATION_FAILED_ERROR_CODE" "エă©ăĽă‚łăĽă‰: ^DB6F2C00%s1^"
+ "AUTHENTICATION_FAILED_BODY" "Atlasă®čŞŤč¨Ľă«ĺ¤±ć•—ă—ăľă—ăźďĽ\n(Failed to authenticate with Atlas!)"
+ "MISSING_MOD" "Modć¶ĺ¤± \"%s1\" v%s2"
+ "MOD_REQUIRED_WARNING" " :ă“ă®ModăŻă‚µăĽăăĽă«ĺŹ‚加ă—ăźéš›ă«čŞ­ăżčľĽăľă‚‹(ăľăźăŻčŞ­ăżčľĽăľă‚ŚăŞă„)ă“ă¨ăŚă‚ă‚Šăľă™"
+ "WILL_RESET_ALL_SETTINGS" "ă‚«ă†ă‚´ăŞăĽĺ†…ă®ĺ…¨č¨­ĺ®šă‚’ăŞă‚»ăăă—ă‚ă†ă¨ă—ă¦ă„ăľă™ă€‚\n\nă“ă®ć“Ťä˝śăŻĺŹ–ă‚Šć¶ă›ăľă›ă‚“。"
+ "WILL_RESET_SETTING" "%s1 ă®č¨­ĺ®šă‚’ĺťćśźĺ€¤ă«ć»ăťă†ă¨ă—ă¦ă„ăľă™ă€‚\n\nă“ă®ć“Ťä˝śăŻĺŹ–ă‚Šć¶ă›ăľă›ă‚“。"
+ "NO_MODS" "設定ăŚĺ©ç”¨ă§ăŤăľă›ă‚“ďĽModă®ă‚¤ăłă‚ąăăĽă«ăŻă“ăˇă‚‰ă‹ă‚‰: ^5588FF00northstar.thunderstore.io^0"
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000進行シスă†ă ăŚé–‹ć”ľă•ă‚Śăľă—ăźďĽ^\n\nNorthstarăŻăă‹ă©ă®é€˛čˇŚă‚·ă‚ąă†ă ă€ă¤ăľă‚Šć­¦ĺ™¨ă€ă‚ąă‚­ăłă€ă‚żă‚¤ă‚żăłç­‰ă‚’ă¬ă™ă«ă‚˘ăă—ă‚„ăăŁă¬ăłă‚¸é”ćă§č§Łé™¤ă™ă‚‹ć©źč˝ă‚’サăťăĽăă—ă¦ă„ăľă™ă€‚\n\nă“ă®é€˛čˇŚă‚·ă‚ąă†ă ăŻă€ă­ă“ăĽç”»éť˘ä¸‹ă®ăśă‚żăłă‹ă‚‰ćś‰ĺŠąă«ă§ăŤăľă™ă€‚\n\nă“ă®č¨­ĺ®šăŻă„ă¤ă§ă‚‚変更可č˝ă§ă™ă€‚"
+ "MOD_NOT_VERIFIED" "(ModăŚć¤śč¨Ľă•ă‚Śă¦ă„ăŞă„ăźă‚ă€č‡Şĺ‹•ă€ă‚¦ăłă­ăĽă‰ă§ăŤăľă›ă‚“ă§ă—ăź)"
+ "WRONG_MOD_VERSION" "サăĽăăĽăŻMod\"%s1\"v%s2 ă‚’č¦ć±‚ă—ă¦ă„ăľă™ăŚă€ă‚ăŞăźă®ModăăĽă‚¸ă§ăłăŻ%s3ă§ă™"
+
+ // Translation done by Zetryox and CYakigasi
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt b/Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt
index ea62415e..50d1ef17 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt
@@ -82,6 +82,8 @@ Si estas de acuerdo con esto, presiona SI. Esta decision puede ser cambiada en e
"cp_amped_capture_points" "Fortalezas amplificadas"
"coliseum_loadouts_enabled" "Equipamientos de coliseo"
+ "aitdm_archer_grunts" "Soldado Archer"
+
// northstar.custom localisation is just deciding not to work, so putting it here for now
"PL_sbox" "Sandbox"
"PL_sbox_lobby" "Lobby sandbox"
@@ -274,7 +276,7 @@ Si estas de acuerdo con esto, presiona SI. Esta decision puede ser cambiada en e
"PLAYERS_COLUMN" "Jugadores"
"MAP_COLUMN" "Mapa"
"GAMEMODE_COLUMN" "Modo de juego"
- "LATENCY_COLUMN" "Latencia"
+ "REGION_COLUMN" "RegiĂłn"
"SEARCHBAR_LABEL" "Buscar:"
"MAP_FILTER" "Mapa"
"GAMEMODE_FILTER" "Modo de juego"
@@ -317,5 +319,35 @@ Si estas de acuerdo con esto, presiona SI. Esta decision puede ser cambiada en e
"INVALID_MASTERSERVER_TOKEN" "Token de jugador expirado o invalido"
"JSON_PARSE_ERROR" "Error procesando respuesta json"
"UNSUPPORTED_VERSION" "La versiĂłn que estas usando ya no esta soportada"
+ "SHOW_ONLY_REQUIRED" "Solo Mods requeridos"
+ "player_force_respawn" "ReapariciĂłn Forzada"
+ "TOGGLE_PROGRESSION" "Alternar Progreso"
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "Desactivar Progreso?"
+ "NO_RESULTS" "No hay resultados."
+ "NO_MODS" "No hay configuraciones disponibles! Instala mas mods en: ^5588FF00northstar.thunderstore.io^0."
+ "AUTHENTICATION_FAILED_HEADER" "Verificacion fallida"
+ "AUTHENTICATION_FAILED_BODY" "AutenticaciĂłn fallada con Atlas!"
+ "AUTHENTICATION_FAILED_ERROR_CODE" "Codigo de error: ^DB6F2C00%s1^"
+ "AUTHENTICATION_FAILED_HELP" "Ayuda"
+ "WILL_RESET_ALL_SETTINGS" "Esto reiniciará TODAS las configuraciones de categoría\nEsto no es reversible"
+ "WILL_RESET_SETTING" "Esto revertirá %s1 a la configuracion por defecto. \n \nEsto no es revertible."
+ "MOD_SETTINGS_SERVER" "Servidor"
+ "MOD_SETTINGS_RESET" "Reiniciar"
+ "MOD_SETTINGS_RESET_ALL" "Reiniciar todo"
+ "MOD_REQUIRED_WARNING" " Este mod puede ser deshabilitado al entrar a un servidor"
+ "MOD_SETTINGS" "Configuracion de Mods"
+ "NORTHSTAR_BASE_SETTINGS" "Configuracion base de Northstar"
+ "ONLY_HOST_MATCH_SETTINGS" "Solo el Host puede cambiar ajustes de partida"
+ "ONLY_HOST_CAN_START_MATCH" "Solo el host puede iniciar la partida"
+ "MATCH_COUNTDOWN_LENGTH" "Cuenta Atrás de partida privada"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "Registro desconocido de comandos de cliente"
+ "DISALLOWED_TACTICALS" "Tactica Prohibida"
+ "TACTICAL_REPLACEMENT" "Reemplazo de Tactica"
+ "DISALLOWED_WEAPONS" "Arma Prohibida"
+ "REPLACEMENT_WEAPON" "Arma de Reemplazo"
+ "SHOULD_RETURN_TO_LOBBY" "Volver al lobby al finalizar partida"
+ "ARE_YOU_SURE" "Seguro?"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Alternar Progreso"
+ "SHOW_ONLY_NOT_REQUIRED" "Solo Mods Opcionales"
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt b/Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt
index d398e557..75a5faad 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt
@@ -15,14 +15,14 @@
"DIALOG_TITLE_INSTALLED_NORTHSTAR" "Obrigado por instalar Northstar!"
"AUTHENTICATION_AGREEMENT_DIALOG_TEXT" "Para o Northstar funcionar é necessário autenticar com o servidor mestre Northstar. Isso requer enviar seu token da Origin para o servidor, ele não será utilizado pra outros propósitos.
Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momento no menu de mods."
- "BACK_AUTHENTICATION_AGREEMENT" "Aceitação de autorização"
- "AUTHENTICATION_AGREEMENT" "Aceitação de autorização"
+ "BACK_AUTHENTICATION_AGREEMENT" "Acordo de Autenticação"
+ "AUTHENTICATION_AGREEMENT" "Acordo de Autenticação"
"AUTHENTICATION_AGREEMENT_RESTART" "Você precisa reiniciar o Titanfall 2 para esta mudança ter efeito."
"DIALOG_AUTHENTICATING_MASTERSERVER" "Autenticando com o servidor mestre."
- "AUTHENTICATIONAGREEMENT_NO" "Você não autorizou a autenticação com Northstar. Troque a configuração no menu de mods."
+ "AUTHENTICATIONAGREEMENT_NO" "VocĂŞ escolheu nĂŁo autenticar com Northstar. VocĂŞ pode ver o acordo no menu de Mods."
- "MENU_TITLE_SERVER_BROWSER" "Servidores"
+ "MENU_TITLE_SERVER_BROWSER" "Lista de Servidores"
"NS_SERVERBROWSER_NOSERVERS" "Nenhum servidor encontrado"
"NS_SERVERBROWSER_UNKNOWNMODE" "Modo desconhecido"
"NS_SERVERBROWSER_WAITINGFORSERVERS" "Aguardando servidores..."
@@ -52,9 +52,9 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"classic_mp" "MP Clássico"
"run_epilogue" "Executar epĂ­logo"
"scorelimit" "Limite de pontos"
- "roundscorelimit" "Limite de pontos (por round)"
+ "roundscorelimit" "Limite de pontos (por rodada)"
"timelimit" "Tempo limite"
- "roundtimelimit" "Tempo limite (por round)"
+ "roundtimelimit" "Tempo limite (por rodada)"
"respawnprotection" "Proteção de reaparecimento"
"pilot_health_multiplier" "Multiplicador de vida"
@@ -64,16 +64,16 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"earn_meter_pilot_multiplier" "Multiplicador de bĂ´nus"
"earn_meter_titan_multiplier" "Multiplicador de bĂ´nus do titĂŁ"
- "aegis_upgrades" "Upgrades Aegis"
+ "aegis_upgrades" "Melhorias de Égide"
"infinite_doomed_state" "Estado condenado infinito"
"titan_shield_regen" "Escudos regeneradores"
- "riff_floorislava" "Deadly Ground"
- "featured_mode_all_holopilot" "The Great Bamboozle"
- "featured_mode_all_grapple" "Attack on Titanfall"
- "featured_mode_all_phase" "The Otherside"
- "featured_mode_all_ticks" "Spicy"
- "featured_mode_tactikill" "Tactikill"
+ "riff_floorislava" "Terreno Mortal"
+ "featured_mode_all_holopilot" "A Grande Enganação"
+ "featured_mode_all_grapple" "Ataque aos TitĂŁs"
+ "featured_mode_all_phase" "O Outro Lado"
+ "featured_mode_all_ticks" "Picante"
+ "featured_mode_tactikill" "Mortático"
"featured_mode_amped_tacticals" "Habilidades táticas melhoradas"
"featured_mode_rocket_arena" "Arena Foguete"
"featured_mode_shotguns_snipers" "Armado e perigoso"
@@ -89,14 +89,14 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"PL_sbox_abbr" "SBOX"
"GAMEMODE_SBOX" "Sandbox"
- "PL_gg" "Gun Game"
- "PL_gg_lobby" "SaguĂŁo de Gun Game"
- "PL_gg_desc" "Mate com todas as armas para vencer"
- "PL_gg_hint" "Mate com todas as armas para vencer"
+ "PL_gg" "Jogo de Armas"
+ "PL_gg_lobby" "SaguĂŁo de Jogo de Armas"
+ "PL_gg_desc" "Mate com cada arma para vencer."
+ "PL_gg_hint" "Mate com cada arma para vencer."
"PL_gg_abbr" "GG"
- "GAMEMODE_GG" "Gun Game"
+ "GAMEMODE_GG" "Jogo de Armas"
"gg_kill_reward" "% recompensa/morte"
- "gg_assist_reward" "% recompensa/assist"
+ "gg_assist_reward" "% recompensa/assistĂŞncia"
"gg_execution_reward" "% recompensa/execução"
"PL_tt" "Titan Tag"
@@ -106,20 +106,20 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"PL_tt_abbr" "TT"
"GAMEMODE_TT" "Titan Tag"
- "PL_chamber" "One in the Chamber"
- "PL_chamber_lobby" "SaguĂŁo de One in the Chamber"
- "PL_chamber_desc" "Um tiro, uma morte. Ganhe outra munição ao abater um inimigo."
- "PL_chamber_hint" "Um tiro, uma morte. Ganhe outra munição ao abater um inimigo."
- "PL_chamber_abbr" "CHAMBER"
- "GAMEMODE_CHAMBER" "One in the Chamber"
+ "PL_chamber" "Um na Câmara"
+ "PL_chamber_lobby" "Saguão de Um na Câmara"
+ "PL_chamber_desc" "Um tiro, uma morte. Ganhe uma bala no pente ao abater um inimigo."
+ "PL_chamber_hint" "Um tiro, uma morte. Ganhe uma bala no pente ao abater um inimigo."
+ "PL_chamber_abbr" "CĂ‚MARA"
+ "GAMEMODE_CHAMBER" "Um na Câmara"
- "PL_hidden" "O Fantasma"
- "PL_hidden_lobby" "SaguĂŁo de O Fantasma"
+ "PL_hidden" "O Oculto"
+ "PL_hidden_lobby" "SaguĂŁo de O Oculto"
"PL_hidden_desc" "Um jogador se torna invisível enquanto o resto o caça."
"PL_hidden_hint" "Um jogador se torna invisível enquanto o resto o caça."
"PL_hidden_abbr" "HIDDEN"
- "GAMEMODE_HIDDEN" "The Hidden"
- "HIDDEN_YOU_ARE_HIDDEN" "VocĂŞ Ă© o fantasma!"
+ "GAMEMODE_HIDDEN" "O Oculto"
+ "HIDDEN_YOU_ARE_HIDDEN" "VocĂŞ Ă© o ocultado!"
"HIDDEN_KILL_SURVIVORS" "Mate todos os sobreviventes."
"HIDDEN_FIRST_HIDDEN" "%s1 Ă© O Fantasma."
@@ -155,12 +155,12 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"INFECTION_YOU_ARE_LAST_SURVIVOR" "VocĂŞ Ă© o Ăşltimo sobrevivente!"
"INFECTION_SURVIVE_LAST_SURVIVOR" "Sobreviva."
- "PL_tffa" "Free for All TitĂŁ"
- "PL_tffa_lobby" "SaguĂŁo de Free for All TitĂŁ"
+ "PL_tffa" "Cada TitĂŁ por si"
+ "PL_tffa_lobby" "SaguĂŁo de Cada TitĂŁ por si"
"PL_tffa_desc" "Cada um por si, destrua todos os titĂŁs inimigos."
"PL_tffa_hint" "Cada um por si, destrua todos os titĂŁs inimigos."
"PL_tffa_abbr" "TFFA"
- "GAMEMODE_TFFA" "Free for All TitĂŁ"
+ "GAMEMODE_TFFA" "Cada TitĂŁ por si"
"PL_hs" "Esconde-esconde"
"PL_hs_lobby" "SaguĂŁo de Esconde-esconde"
@@ -168,7 +168,7 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"PL_hs_hint" "Jogo clássico de esconde-esconde."
"PL_hs_abbr" "HS"
"GAMEMODE_hs" "Esconde-esconde"
- "HIDEANDSEEK_YOU_ARE_SEEKER" "VOCĂŠ PEGA!"
+ "HIDEANDSEEK_YOU_ARE_SEEKER" "VOCĂŠ PEGA"
"HIDEANDSEEK_SEEKER_DESC" "Ache os outros e bata neles.\nVocê reaparecerá em %s1 segundos"
"HIDEANDSEEK_YOU_ARE_HIDER" "VOCĂŠ ESCONDE"
"HIDEANDSEEK_HIDER_DESC" "Esconda-se."
@@ -203,8 +203,8 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"KR_YOUR_KILLRACE_OVER" "Sua matança acabou"
"KR_YOUR_KILLRACE_SCORE" "VocĂŞ conseguiu %s1 mortes."
- "GAMEMODE_fastball" "Fastball"
- "PL_fastball" "Fastball"
+ "GAMEMODE_fastball" "Travessia Impulsionada"
+ "PL_fastball" "Travessia Impulsionada"
"PL_fastball_lobby" "SaguĂŁo de Fastball"
"PL_fastball_desc" "Sem reaparecimento. Invada painéis de controle para ganhar rounds e reaparecer seus aliados."
"PL_fastball_hint" "Sem reaparecimento. Invada painéis de controle para ganhar rounds e reaparecer seus aliados."
@@ -236,8 +236,8 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"player_bleedout_aiBleedingPlayerMissChance" "Chance de erro da IA ao cair"
// coop stuff
- "PL_sp_coop" "(UNFINISHED) Singleplayer Coop"
- "PL_sp_coop_lobby" "SaguĂŁo de Singleplayer em grupo"
+ "PL_sp_coop" "(NĂO-TERMINADO) Campanha Cooperativa"
+ "PL_sp_coop_lobby" "SaguĂŁo de Campanha Cooperativa"
"PL_sp_coop_desc" "Jogue através da campanha com seus amigos"
"PL_sp_coop_hint" "Jogue através da campanha com seus amigos"
"PL_sp_coop_abbr" "SP"
@@ -274,7 +274,7 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
"PLAYERS_COLUMN" "Jogadores"
"MAP_COLUMN" "Mapa"
"GAMEMODE_COLUMN" "Modo"
- "LATENCY_COLUMN" "LatĂŞncia"
+ "REGION_COLUMN" "RegiĂŁo"
"SEARCHBAR_LABEL" "Buscar:"
"MAP_FILTER" "Mapa"
"GAMEMODE_FILTER" "Modo"
@@ -306,5 +306,75 @@ Clique em Sim se vocĂŞ concorda. Esta escolha pode ser alterada a qualquer momen
// In-game chat
"HUD_CHAT_WHISPER_PREFIX" "[WHISPER]"
"HUD_CHAT_SERVER_PREFIX" "[SERVER]"
+ "ONLY_HOST_MATCH_SETTINGS" "Somente o Host pode mudar as configurações da Partida Privada"
+ "ONLY_HOST_CAN_START_MATCH" "Somente o Host pode Iniciar a Partida"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "Registrar Comandos desconhecidos de Clientes"
+ "DISALLOWED_TACTICALS" "Táticos Proibidos"
+ "TACTICAL_REPLACEMENT" "Tático Substituto"
+ "DISALLOWED_WEAPONS" "Armas Proibidas"
+ "REPLACEMENT_WEAPON" "Armas Substitutas"
+ "ARE_YOU_SURE" "Tem certeza?"
+ "WILL_RESET_ALL_SETTINGS" "Isso irá resetar TODAS as configurações que pertecem a essa categoria.\n\nAção não reversível."
+ "MOD_SETTINGS_SERVER" "Servidor"
+ "MOD_SETTINGS_RESET" "Resetar"
+ "MOD_SETTINGS_RESET_ALL" "Resetar Tudo"
+ "NO_RESULTS" "Sem resultados."
+ "NO_MODS" "Sem configurações disponíveis. Instale mais mods em ^5588FF00northstar.thunderstore.io^0."
+ "SHOW_ONLY_NOT_REQUIRED" "Somente Mods Opcionais"
+ "NO_GAMESERVER_RESPONSE" "Não foi possível alcançar o servidor da partida"
+ "BAD_GAMESERVER_RESPONSE" "Servidor da partida sem resposta válida"
+ "UNAUTHORIZED_GAMESERVER" "Servidor da partida não está autorizado a fazer tal requisição"
+ "UNAUTHORIZED_PWD" "Senha inválida"
+ "PLAYER_NOT_FOUND" "NĂŁo foi possĂ­vel encontrar conta do jogador"
+ "INVALID_MASTERSERVER_TOKEN" "Token do servidor mestre inválido ou vencido, tente reiniciar o EA App."
+ "JSON_PARSE_ERROR" "Erro ao ler a resposta json"
+ "SHOW_ONLY_REQUIRED" "Somente Mods MandatĂłrios"
+ "UNAUTHORIZED_GAME" "Stryder nĂŁo pode confirmar que esta conta possui Titanfall 2"
+ "STRYDER_RESPONSE" "NĂŁo foi possĂ­vel ler a resposta do Stryder"
+ "UNSUPPORTED_VERSION" "A versão que você está usando não é mais suportada"
+ "MOD_SETTINGS" "Configurações de Mod"
+ "NORTHSTAR_BASE_SETTINGS" "Configurações Base do Northstar"
+ "MATCH_COUNTDOWN_LENGTH" "Duração da contagem da Partida Privada"
+ "SHOULD_RETURN_TO_LOBBY" "Retornar ao saguão após término de Partida"
+ "WILL_RESET_SETTING" "Isso irá resetar a configuração %s1 ao valor padrão.\n\nAção não reversível."
+ "aitdm_archer_grunts" "Soldados com Archer"
+ "player_force_respawn" "Resurgimento Forçado"
+ "PROGRESSION_ENABLED_HEADER" "ProgressĂŁo Habilitada!"
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000Progressão foi habilitada.^\n\nTitãs, Armas, Facções, Cosméticos, etc. serão destravados ao ganhar níveis, ou comprados com Méritos.\n\nIsso pode ser alterado a qualquer momento no menu de multijogador."
+ "PROGRESSION_DISABLED_HEADER" "ProgressĂŁo Desabilitada!"
+ "TOGGLE_PROGRESSION" "Habilitar ProgressĂŁo"
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "Desabilitar ProgressĂŁo?"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "Titãs, Armas, Facções, Cosméticos etc. serão todos destravados e usáveis a qualquer momento.\n\nIsso pode ser alterado a qualquer hora no menu de multijogador."
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "Habilitar ProgressĂŁo?"
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "Titãs, Armas, Facções, Cosméticos, etc. terão que ser destravados ao ganhar níveis, ou comprados com Méritos.\n\nIsso pode ser alterado a qualquer momento no menu de multijogador.\n\n^CC000000Aviso: Se você atualmente tem qualquer item equipado que não foi préviamente destravado, ele será resetado!"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Alternar ProgressĂŁo"
+ "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Baixando mod (%s1%)"
+ "DOWNLOADING_MOD_TEXT" "Baixando %s1 v%s2..."
+ "MISSING_MOD" "Mod em falta \"%s1\" v%s2"
+ "MOD_NOT_VERIFIED" "(o mod nĂŁo Ă© verificado, e nĂŁo pode ser baixado automaticamente)"
+ "MOD_DL_DISABLED" "(download automático de mod está desabilitado)"
+ "DOWNLOADING_MOD_TITLE" "Baixando mod"
+ "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Baixando %s1 v%s2...\n(%s3/%s4 MB)"
+ "CHECKSUMING_TITLE" "Verificando o mod"
+ "CHECKSUMING_TEXT" "Verificando conteĂşdo de %s1 v%s2..."
+ "EXTRACTING_MOD_TITLE" "ExtraĂ­ndo mod (%s1%)"
+ "MOD_REQUIRED_WARNING" " Este mod pode ser desativado quando entrar em um servidor"
+ "AUTHENTICATION_FAILED_HEADER" "Autenticação Falhou"
+ "AUTHENTICATION_FAILED_BODY" "Falha ao autenticar com Atlas!"
+ "AUTHENTICATION_FAILED_ERROR_CODE" "CĂłdigo de Erro: ^DB6F2C00%s1^"
+ "AUTHENTICATION_FAILED_HELP" "Ajuda"
+ "EXTRACTING_MOD_TEXT" "ExtraĂ­ndo %s1 v%s2...\n(%s3/%s4 MB)"
+ "FAILED_DOWNLOADING" "Falha ao baixar mod"
+ "FAILED_READING_ARCHIVE" "Um erro ocorreu ao ler o aqruivo do mod."
+ "MOD_FETCHING_FAILED" "O arquivo do mod nĂŁo pode ser baixado da Thunderstore."
+ "MOD_CORRUPTED" "O aquivo baixado não condiz com a assinatura de verificação registrada."
+ "NO_DISK_SPACE_AVAILABLE" "Não há espaço suficiente no disco."
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000Progressão foi desabilitada.^\n\nTitãs, Facções, Armas, Cosméticos, etc. estarão disponíveis para uso a qualquer momento.\n\nIsso pode ser alterado a qualquer hora no menu de multijogador."
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000Progressão agora pode ser habilitada!^\n\nNorthstar agora suporta a progressão original, o que significa que você pode optar por destravar Armas, Cosméticos, Titãs e outros através do ganho de níveis e completando os desafios.\n\nVocê pode habilitar a progressão usando o botão no rodapé do menu de multijogador.\n\nIsso pode ser alterado a qualquer momento."
+ "WRONG_MOD_VERSION" "O servidor tem o mod \"%s1\" v%s2 enquanto vocĂŞ possui v%s3"
+ "FAILED_WRITING_TO_DISK" "Um erro ocorreu enquanto se extraĂ­a o conteĂşdo do mod para o sistema."
+ "MOD_FETCHING_FAILED_GENERAL" "Extração do mod falhou. Verifique os logs para mais detalhes."
+ "MANIFESTO_FETCHING_TEXT" "Retornando a lista de mods verificados..."
+ "MANIFESTO_FETCHING_TITLE" "Preparando o download do mod"
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_russian.txt b/Northstar.Client/mod/resource/northstar_client_localisation_russian.txt
index af9eb0ff..91a0dede 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_russian.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_russian.txt
@@ -17,7 +17,7 @@
"MENU_TITLE_SERVER_BROWSER" "СпиŃок Ńерверов"
"NS_SERVERBROWSER_NOSERVERS" "Серверов не найдено"
"NS_SERVERBROWSER_WAITINGFORSERVERS" "Ожидание Ńерверов..."
- "NS_SERVERBROWSER_CONNECTIONFAILED" "Соединение провалено!"
+ "NS_SERVERBROWSER_CONNECTIONFAILED" "Не ŃдалоŃŃŚ подключитьŃŃŹ!"
"REFRESH_SERVERS" "Обновить"
"MENU_TITLE_CONNECT_PASSWORD" "ПодключитьŃŃŹ Ń ĐżĐľĐĽĐľŃ‰ŃŚŃŽ пароля"
@@ -41,7 +41,7 @@
"MODE_SETTING_CATEGORY_MATCH" "Матч"
"classic_mp" "КлаŃŃичеŃкий ĐĽŃльтиплеер"
- "run_epilogue" "Đ—Đ°ĐżŃŃтить эпилог"
+ "run_epilogue" "Показывать эпилог"
"scorelimit" "Лимит очков"
"roundscorelimit" "Лимит очков (по раŃндам)"
"timelimit" "Лимит времени"
@@ -58,19 +58,19 @@
"infinite_doomed_state" "БеŃконечное обречённое ŃĐľŃтояние"
"titan_shield_regen" "Регенерация щитов"
- "riff_floorislava" "Смертельная Земля"
+ "riff_floorislava" "Пол это лава"
"featured_mode_all_holopilot" "Великий обманщик"
- "featured_mode_all_grapple" "Зацепщик"
- "featured_mode_all_phase" "Đльтернативное проŃтранŃтво"
- "featured_mode_all_ticks" "ĐžŃтрый"
+ "featured_mode_all_grapple" "Зацепер"
+ "featured_mode_all_phase" "Мир иной"
+ "featured_mode_all_ticks" "КрŃтые перцы"
"featured_mode_tactikill" "ТактичеŃкий ŃĐ´Đ°Ń€"
"featured_mode_amped_tacticals" "ĐŁŃиленные тактики"
"featured_mode_rocket_arena" "Ракетная Đрена"
"featured_mode_shotguns_snipers" "ВоорŃжён и опаŃен"
"iron_rules" "Правила Железного Титана"
- "cp_amped_capture_points" "ĐŁŃиленные точки опоры"
- "coliseum_loadouts_enabled" "ВыгрŃзка коллизея"
+ "cp_amped_capture_points" "ĐŁŃиленные опорные ĐżŃнкты"
+ "coliseum_loadouts_enabled" "Экипировка Колизея"
// northstar.custom localisation is just deciding not to work, so putting it here for now
"PL_sbox" "ПеŃочница"
@@ -81,9 +81,9 @@
"PL_gg" "Гонка воорŃжений"
"PL_gg_lobby" "Лобби гонки воорŃжений"
- "PL_gg_desc" "Уничтожьте противника из вŃех видов орŃжия чтобы победить."
- "PL_gg_hint" "Уничтожьте противника из вŃех видов орŃжия чтобы победить."
- "PL_gg_abbr" "GG"
+ "PL_gg_desc" "Убейте по ĐľĐ´Đ˝ĐľĐĽŃ ĐżŃ€ĐľŃ‚Đ¸Đ˛Đ˝Đ¸ĐşŃ Đ¸Đ· каждого вида орŃжия."
+ "PL_gg_hint" "Убейте по ĐľĐ´Đ˝ĐľĐĽŃ ĐżŃ€ĐľŃ‚Đ¸Đ˛Đ˝Đ¸ĐşŃ Đ¸Đ· каждого вида орŃжия."
+ "PL_gg_abbr" "Đ“Đ’"
"GAMEMODE_GG" "Гонка воорŃжений"
"PL_tt" "Разборки Титанов"
@@ -99,10 +99,10 @@
"PL_inf_hint" "Переживите инфекцию. ВыживŃие ŃтановятŃŃŹ заражёнными при ŃбийŃтве."
"PL_inf_abbr" "INF"
"GAMEMODE_INF" "Заражение"
- "INFECTION_YOU_ARE_INFECTED" "Вы были заражены!"
+ "INFECTION_YOU_ARE_INFECTED" "Đ’Đ°Ń Đ·Đ°Ń€Đ°Đ·Đ¸Đ»Đ¸!"
"INFECTION_KILL_SURVIVORS" "Заразите вŃех ĐľŃтавŃихŃŃŹ выживŃих."
"INFECTION_FIRST_INFECTED" "%s1 был заражён первым."
- "INFECTION_LAST_SURVIVOR" "%s1 выжил поŃледним!"
+ "INFECTION_LAST_SURVIVOR" "%s1 — поŃледний выживŃий!"
"INFECTION_KILL_LAST_SURVIVOR" "Зарази их, пока время не выŃло!"
"INFECTION_YOU_ARE_LAST_SURVIVOR" "Đ’Ń‹ поŃледний выживŃий!"
"INFECTION_SURVIVE_LAST_SURVIVOR" "Выживите."
@@ -116,19 +116,19 @@
"HIDEANDSEEK_YOU_ARE_SEEKER" "Đ’Đ« ĐЩЕТЕ"
"HIDEANDSEEK_SEEKER_DESC" "Найдите прячŃщихŃŃŹ и Ńдарьте их.\nĐ’Ń‹ появитеŃŃŚ через %s1 ŃекŃнд(Ń)"
"HIDEANDSEEK_YOU_ARE_HIDER" "ВЫ ПРЯЧЕТЕСЬ"
- "HIDEANDSEEK_HIDER_DESC" "СпрятайтеŃŃŚ."
+ "HIDEANDSEEK_HIDER_DESC" "СпрячьтеŃŃŚ."
"HIDEANDSEEK_SEEKERS_INCOMING" "ĐĐ©ĐŁĐ©ĐĐ• ВЫДВĐĐ“ĐЮТСЯ"
"HIDEANDSEEK_DONT_GET_FOUND" "Не дайте Ńебя найти!"
"HIDEANDSEEK_GET_LAST_HIDER" "%s1 ПОСЛЕДНĐĐ™ СПРЯТĐВШĐЙСЯ"
"HIDEANDSEEK_YOU_ARE_LAST_HIDER" "Đ’Đ« ПОСЛЕДНĐĐ™ СПРЯТĐВШĐЙСЯ"
- "HIDEANDSEEK_GOT_STIM" "Đ’Đ°Ń ĐľŃтановили! Не попадитеŃŃŚ!"
+ "HIDEANDSEEK_GOT_STIM" "Применён Ńтим! Не попадитеŃŃŚ!"
"hideandseek_balance_teams" "ĐĐ˛Ń‚ĐľĐ±Đ°Đ»Đ°Đ˝Ń ĐľĐ±ĐµĐ¸Ń… Ńторон"
"hideandseek_hiding_time" "Время ŃпрятатьŃŃŹ"
// 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" "Пограничная война"
- "PL_fw" "Пограничная война"
- "PL_fw_lobby" "Лобби пограничной войны"
+ "GAMEMODE_fw" "Война за Фронтир"
+ "PL_fw" "Война за Фронтир"
+ "PL_fw_lobby" "Лобби Войны за Фронтир"
"PL_fw_desc" "Уничтожьте харвеŃтер противника и защитите Ńвой"
"PL_fw_abbr" "FW"
@@ -136,22 +136,22 @@
"PL_kr" "ĐŁŃиленные гонки ŃбийŃтв"
"PL_kr_lobby" "Лобби ŃŃиленных гонок ŃбийŃтв"
- "PL_kr_desc" "ПолŃчайте ŃбийŃтва, чтобы Ńвеличить продолжительноŃŃ‚ŃŚ ваŃей гонки Đ·Đ° ŃбийŃтвами. Соберите флаг, чтобы начать его. ĐŁŃтановите рекорд ŃбийŃтв, чтобы победить."
- "PL_kr_hint" "ПолŃчайте ŃбийŃтва, чтобы Ńвеличить продолжительноŃŃ‚ŃŚ ваŃей гонки Đ·Đ° ŃбийŃтвами. Соберите флаг, чтобы начать его. ĐŁŃтановите рекорд ŃбийŃтв, чтобы победить."
+ "PL_kr_desc" "ПолŃчайте ŃбийŃтва, чтобы Ńвеличить продолжительноŃŃ‚ŃŚ ваŃей гонки Đ·Đ° ŃбийŃтвами. Соберите флаг, чтобы начать её. ĐŁŃтановите рекорд ŃбийŃтв, чтобы победить."
+ "PL_kr_hint" "ПолŃчайте ŃбийŃтва, чтобы Ńвеличить продолжительноŃŃ‚ŃŚ ваŃей гонки Đ·Đ° ŃбийŃтвами. Соберите флаг, чтобы начать её. ĐŁŃтановите рекорд ŃбийŃтв, чтобы победить."
"PL_kr_abbr" "KR"
"SCOREBOARD_KR_RECORD" "Рекорд ŃбийŃтв"
"KR_NEW_RACER" "%s1 ŃŃиленный гонщик Đ·Đ° ŃбийŃтва"
"KR_YOU_ARE_NEW_RACER" "Đ’Ń‹ ŃŃиленный гонщик Đ·Đ° ŃбийŃтва"
"KR_YOU_SET_NEW_RECORD" "ĐŁŃтановите новый рекорд ŃбийŃтв!"
"KR_FLAG_INCOMING" "Флаг прибывает"
- "KR_COLLECT_FLAG" "Возьмите его чтобы ŃŃ‚Đ°Ń‚ŃŚ гонщиком Đ·Đ° ŃбиŃтва!"
+ "KR_COLLECT_FLAG" "Возьмите его чтобы ŃŃ‚Đ°Ń‚ŃŚ гонщиком Đ·Đ° ŃбийŃтва!"
"KR_ENEMY_KILLRACE_OVER" "Гонки ŃбийŃтв Ń %s1 закончилиŃŃŚ"
"KR_YOUR_KILLRACE_OVER" "Đ’Đ°Ńи гонки ŃбийŃтв кончилиŃŃŚ"
"KR_YOUR_KILLRACE_SCORE" "ĐŁ Đ˛Đ°Ń %s1 ŃбийŃтв."
- "GAMEMODE_fastball" "ФаŃтболл"
- "PL_fastball" "ФаŃтболл"
- "PL_fastball_lobby" "Лобби Ń„Đ°Ńтболла"
+ "GAMEMODE_fastball" "ФаŃтбол"
+ "PL_fastball" "ФаŃтбол"
+ "PL_fastball_lobby" "Лобби Ń„Đ°Ńтбола"
"PL_fastball_desc" "Перманентная Ńмерть. Взломайте панели Ńправления чтобы побеждать в раŃндах и возродить Ńвоих членов команды."
"PL_fastball_hint" "Перманентная Ńмерть. Взломайте панели Ńправления чтобы побеждать в раŃндах и возродить Ńвоих членов команды."
"PL_fastball_abbr" "FB"
@@ -161,20 +161,20 @@
"GAMEMODE_ctf_comp" "Соревновательное CTF"
// mode settings
- "MODE_SETTING_CATEGORY_PROMODE" "ПродвинŃтое орŃжие"
+ "MODE_SETTING_CATEGORY_PROMODE" "Режим про"
"MODE_SETTING_CATEGORY_BLEEDOUT" "Кровотечение пилота"
"custom_air_accel_pilot" "ĐŁŃкорение в воздŃхе"
- "promode_enable" "ПродвинŃтое орŃжие"
- "fp_embark_enabled" "Отправления/Казни от первого лица"
+ "promode_enable" "ОрŃжие из режима про"
+ "fp_embark_enabled" "Казни/поŃадки в титана от первого лица"
"classic_rodeo" "КлаŃŃичеŃкое родео"
"oob_timer_enabled" "Таймер Ńничтожения Đ·Đ° пределами карты"
"riff_instagib" "Моментальная Ńмерть при попадании"
"riff_player_bleedout" "Кровотечение пилота"
- "player_bleedout_forceHolster" "ĐŃпользовать орŃжие из кобŃры при кровотечении"
+ "player_bleedout_forceHolster" "Убирать орŃжие при кровотечении"
"player_bleedout_forceDeathOnTeamBleedout" "Смерть при кровотечении во вŃей команде"
- "player_bleedout_bleedoutTime" "Кремя кровотечения"
+ "player_bleedout_bleedoutTime" "Время кровотечения"
"player_bleedout_firstAidTime" "Время оказания первой помощи"
"player_bleedout_firstAidTimeSelf" "Время оказания помощи ŃĐ°ĐĽĐľĐĽŃ Ńебе"
"player_bleedout_firstAidHealPercent" "Процент здоровья поŃле лечения"
@@ -187,8 +187,8 @@
"PL_sp_coop_hint" "Сыграть в кампанию Ń Đ´Ń€Ńзьями"
"PL_sp_coop_abbr" "SP"
- "SP_TRAINING" "Тренировачная полоŃĐ° пилотов"
- "SP_TRAINING_CLASSIC_DESC" "Тренировачная имитация капитана ЛаŃтимозы."
+ "SP_TRAINING" "ПолоŃĐ° препятŃтвий"
+ "SP_TRAINING_CLASSIC_DESC" "СимŃляция капитана ЛаŃтимозы."
"SP_CRASHSITE" "Đ‘T-7274"
"SP_CRASHSITE_CLASSIC_DESC" "Джек ĐšŃпер вŃтречает BT-7274."
@@ -200,7 +200,7 @@
"SP_BOOMTOWN_START_CLASSIC_DESC" "Подземная Ńрезка приводит Đş неожиданным поŃледŃтвиям."
"SP_HUB_TIMESHIFT" "СледŃтвие и Причина"
- "SP_HUB_TIMESHIFT_CLASSIC_DESC" "Странный феномен был обнарŃжен на меŃтополжении Майора ĐндерŃена."
+ "SP_HUB_TIMESHIFT_CLASSIC_DESC" "Странный феномен был обнарŃжен на меŃтоположении Майора ĐндерŃена."
"SP_BEACON" "Маяк"
"SP_BEACON_CLASSIC_DESC" "ĐšŃпер и БТ пытаютŃŃŹ извеŃтить ĐľŃтавŃийŃŃŹ флот Đľ планах IMC."
@@ -211,7 +211,7 @@
"SP_S2S" "Ковчег"
"SP_S2S_CLASSIC_DESC" "ĐšŃпер и БТ перемещаютŃŃŹ по кораблям, преŃледŃŃŹ Ковчег."
- "SP_SKYWAY_V1" "Сложенное орŃжие"
+ "SP_SKYWAY_V1" "ĐŃкажающее орŃдие"
"SP_SKYWAY_V1_CLASSIC_DESC" "Đ‘T и ĐšŃпер были захвачены ĐšŃбеном БлиŃком."
// Better.Serverbrowser
@@ -219,7 +219,7 @@
"PLAYERS_COLUMN" "Đгроки"
"MAP_COLUMN" "Карта"
"GAMEMODE_COLUMN" "Режим игры"
- "LATENCY_COLUMN" "Задержка"
+ "REGION_COLUMN" "Регион"
"SEARCHBAR_LABEL" "ПоиŃĐş:"
"MAP_FILTER" "Карта"
"GAMEMODE_FILTER" "Режим игры"
@@ -229,7 +229,7 @@
"SERVER_DESCRIPTION" "ОпиŃание"
"SERVER_MODS" "Моды"
"CLEAR_FILTERS" "ОЧĐСТĐТЬ"
- "JOIN_BUTTON" "ПРĐСОЕДĐĐťĐТЬСЯ"
+ "JOIN_BUTTON" "Đ—ĐЙТĐ"
"SWITCH_YES" "Да"
"SWITCH_NO" "Нет"
@@ -240,18 +240,132 @@
"TOTAL_SERVERS" "Серверов: ^C46C6C00%s1"
// In-game chat
- "HUD_CHAT_WHISPER_PREFIX" "[WHISPER]"
- "HUD_CHAT_SERVER_PREFIX" "[SERVER]"
+ "HUD_CHAT_WHISPER_PREFIX" "[Đ›ĐЧНОЕ]"
+ "HUD_CHAT_SERVER_PREFIX" "[СЕРВЕР]"
"NO_GAMESERVER_RESPONSE" "Đгровой Ńервер не отвечает"
- "BAD_GAMESERVER_RESPONSE" "Đгровой Ńервер не Đ´Đ°Đ» правильного ответа"
+ "BAD_GAMESERVER_RESPONSE" "Đгровой Ńервер вернŃĐ» некорректный ответ"
"UNAUTHORIZED_GAMESERVER" "Đгровой Ńервер не авторизирован чтобы Ńделать данный запроŃ"
- "UNAUTHORIZED_GAME" "Не ŃдалоŃŃŚ найти Titanfall 2 на этом аккаŃнте"
+ "UNAUTHORIZED_GAME" "Stryder не Ńмог найти Titanfall 2 на этом аккаŃнте"
"UNAUTHORIZED_PWD" "Неправильный пароль"
- "STRYDER_RESPONSE" "Не ŃдалоŃŃŚ разобрать ответ stryder"
+ "STRYDER_RESPONSE" "Не ŃдалоŃŃŚ разобрать ответ Stryder"
"PLAYER_NOT_FOUND" "Не ŃдалоŃŃŚ найти аккаŃнт игрока"
- "INVALID_MASTERSERVER_TOKEN" "Срок дейŃтвия жетона главного Ńервера иŃтек или не являетŃŃŹ правильным"
- "JSON_PARSE_ERROR" "ĐžŃибка разбора ответа json"
+ "INVALID_MASTERSERVER_TOKEN" "Некорректный или иŃŃ‚Ń‘ĐşŃий токен главного Ńервера. ПерезапŃŃтите EA App, чтобы обновить токен."
+ "JSON_PARSE_ERROR" "ĐžŃибка разбора json-ответа"
"UNSUPPORTED_VERSION" "ĐŃпользŃемая вами верŃия больŃе не поддерживаетŃŃŹ"
+ "DISABLE" "Выключить"
+ "DIALOG_AUTHENTICATING_MASTERSERVER" "ĐŃтентификация на главном Ńервере."
+ "WARNING" "ПредŃпреждение"
+ "CORE_MOD_DISABLE_WARNING" "Выключение ĐľŃновных модов может Ńломать Đ˛Đ°Ń ĐşĐ»Đ¸ĐµĐ˝Ń‚!"
+ "AUTHENTICATIONAGREEMENT_NO" "Đ’Ń‹ выбрали не Đ°ŃтентифицироватьŃŃŹ Ń Northstar-ом. Đ’Ń‹ можете поŃмотреть ŃоглаŃение в меню модов."
+ "NS_SERVERBROWSER_UNKNOWNMODE" "НеизвеŃтный режим"
+ "SNS_BANKRUPT" "Банкрот!"
+ "MOD_SETTINGS" "НаŃтройки модов"
+ "NORTHSTAR_BASE_SETTINGS" "ĐžŃновные наŃтройки Northstar"
+ "ONLY_HOST_MATCH_SETTINGS" "Только Ńоздатель матча может изменять наŃтройки"
+ "ONLY_HOST_CAN_START_MATCH" "Только Ńоздатель матча может его начать"
+ "MATCH_COUNTDOWN_LENGTH" "ДлительноŃŃ‚ŃŚ обратного отŃчёта чаŃтного матча"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "ЗапиŃывать в лог неизвеŃтные команды"
+ "DISALLOWED_TACTICALS" "Запрещённое ŃпецорŃжие"
+ "TACTICAL_REPLACEMENT" "Заменять запрещённое ŃпецорŃжие на"
+ "DISALLOWED_WEAPONS" "Запрещённое орŃжие"
+ "REPLACEMENT_WEAPON" "Заменять запрещённое орŃжие на"
+ "SHOULD_RETURN_TO_LOBBY" "ВозвращатьŃŃŹ в лобби поŃле матча"
+ "ARE_YOU_SURE" "Đ’Ń‹ Ńверены?"
+ "WILL_RESET_ALL_SETTINGS" "ВСЕ наŃтройки этой категории бŃĐ´ŃŃ‚ ŃброŃены.\n\nĐ­Ń‚Đľ дейŃтвие нельзя бŃдет откатить."
+ "WILL_RESET_SETTING" "Đ­Ń‚Đľ ŃброŃит значение наŃтройки %s1.\n\nĐ­Ń‚Đľ дейŃтвие нельзя бŃдет откатить."
+ "MOD_SETTINGS_SERVER" "Сервер"
+ "MOD_SETTINGS_RESET" "СброŃ"
+ "MOD_SETTINGS_RESET_ALL" "СброŃить вŃе"
+ "NO_RESULTS" "Нет резŃльтатов."
+ "NO_MODS" "Нечего наŃтраивать! ЗагрŃзите моды Ń ^5588FF00northstar.thunderstore.io^0."
+ "respawnprotection" "Длит. защиты поŃле возрождения"
+ "SNS_BANKRUPT_SUB" "Đ’Đ°Ń ĐľĐ±Đ˝Ńлил %s1"
+ "PL_hidden" "Невидимка"
+ "PL_hidden_lobby" "Невидимка — лобби"
+ "GAMEMODE_HIDDEN" "Невидимка"
+ "HIDDEN_YOU_ARE_HIDDEN" "Вы — Невидимка!"
+ "HIDDEN_KILL_SURVIVORS" "Убейте вŃех."
+ "aitdm_archer_grunts" "Пехотинцы ŃĐľ Стрельцами"
+ "PL_chamber" "ПоŃледний патрон"
+ "PL_chamber_lobby" "ПоŃледний патрон — лобби"
+ "PL_chamber_abbr" "ĐźĐТРОН"
+ "GAMEMODE_CHAMBER" "ПоŃледний патрон"
+ "PL_sns" "Камни и палки"
+ "PL_sns_lobby" "Камни и палки — лобби"
+ "PL_sns_desc" "Đ’Ńе против вŃех. УбийŃтва ĐżŃльŃ. клинком и казни ŃбраŃывают очки противника"
+ "PL_sns_abbr" "ĐšĐĐź"
+ "GAMEMODE_SNS" "Камни и палки"
+ "PL_hidden_hint" "Один из игроков невидим."
+ "PL_hidden_abbr" "НЕВĐĐ”"
+ "SCOREBOARD_BANKRUPTS" "Врагов обанкрочено"
+ "SNS_LEADER_BANKRUPT" "Лидер обанкрочен!"
+ "SNS_LEADER_BANKRUPT_SUB" "%s1 был обнŃлён игроком %s2"
+ "gg_kill_reward" "Множитель награды Đ·Đ° ŃбийŃтво"
+ "gg_execution_reward" "Множитель награды за казнь"
+ "PL_tffa" "Đ’Ńе против вŃех на титанах"
+ "PL_tffa_lobby" "Đ’Ńе против вŃех на титанах — лобби"
+ "PL_tffa_desc" "Каждый пилот ŃĐ°ĐĽ Đ·Đ° Ńебя. Уничтожьте вŃех вражеŃких титанов."
+ "PL_tffa_abbr" "ТВПВ"
+ "sns_wme_kill_value" "Очков Đ·Đ° ŃбийŃтво Элитным ведомым"
+ "sns_offhand_kill_value" "Очков Đ·Đ° ŃбийŃтво ŃпецорŃжием"
+ "sns_reset_kill_value" "Очков Đ·Đ° ŃбийŃтво ĐżŃльŃ./казнь"
+ "sns_melee_kill_value" "Очков Đ·Đ° ŃбийŃтво врŃкопаŃĐ˝ŃŃŽ"
+ "sns_softball_enabled" "Включить Софтбол"
+ "no_pilot_collision" "Столкновения ĐĽĐµĐ¶Đ´Ń ĐżĐ¸Đ»ĐľŃ‚Đ°ĐĽĐ¸"
+ "SHOW" "Показать"
+ "SHOW_ALL" "Đ’Ńе"
+ "SHOW_ONLY_ENABLED" "Только включенные"
+ "SHOW_ONLY_NOT_REQUIRED" "Только необязательные"
+ "SHOW_ONLY_REQUIRED" "Только обязательные"
+ "HIDDEN_FIRST_HIDDEN" "%s1 — Невидимка."
+ "GAMEMODE_TFFA" "Đ’Ńе против вŃех на титанах"
+ "SHOW_ONLY_DISABLED" "Только выключенные"
+ "HIDE_LOCKED" "Скрыть недоŃŃ‚Ńпные"
+ "PL_chamber_desc" "Один выŃтрел — один Ń‚Ń€ŃĐż. Заработайте ещё один патрон, Ńбив врага первым."
+ "PL_chamber_hint" "Один выŃтрел — один Ń‚Ń€ŃĐż. Заработайте ещё один патрон, Ńбив врага первым."
+ "PL_hidden_desc" "Один из игроков невидим."
+ "PL_tffa_hint" "Каждый пилот ŃĐ°ĐĽ Đ·Đ° Ńебя. Уничтожьте вŃех вражеŃких титанов."
+ "sns_softball_kill_value" "Очков Đ·Đ° ŃбийŃтво Софтболом"
+ "sns_reset_pulse_blade_cooldown_on_pulse_blade_kill" "ĐˇĐ±Ń€ĐľŃ ĐżĐµŃ€ĐµĐ·Đ°Ń€ŃŹĐ´ĐşĐ¸ ĐżŃльŃ. клинка при ŃбийŃтве им"
+ "gg_assist_reward" "Множитель награды Đ·Đ° помощь в ŃбийŃтве"
+ "TOGGLE_PROGRESSION" "Вкл/выкл прогреŃŃ"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Вкл/выкл прогреŃŃ"
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "Включить прогреŃŃ?"
+ "PROGRESSION_ENABLED_HEADER" "ПрогреŃŃ Đ˛ĐşĐ»ŃŽŃ‡Ń‘Đ˝!"
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000ПрогреŃŃ Đ˛ĐşĐ»ŃŽŃ‡Ń‘Đ˝.^\n\nНедоŃŃ‚Ńпных титанов, орŃжие, фракции, раŃкраŃки, и Ń‚.Đż. теперь Đ˝Ńжно разблокировать, или ĐşŃпить Đ·Đ° Đ—Đ°ŃĐ»Ńги.\n\nĐ­Ń‚Ń Đ˝Đ°ŃŃ‚Ń€ĐľĐąĐşŃ Đ˛Ńегда можно изменить в лобби Ńетевой игры."
+ "PROGRESSION_DISABLED_HEADER" "ПрогреŃŃ Đ˛Ń‹ĐşĐ»ŃŽŃ‡ĐµĐ˝!"
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000ПрогреŃŃ Đ˛Ń‹ĐşĐ»ŃŽŃ‡ĐµĐ˝.^\n\nĐ’Ńе титаны, орŃжие, фракции, раŃкраŃки, и Ń‚.Đż. теперь Đ´ĐľŃŃ‚Ńпны.\n\nĐ­Ń‚Ń Đ˝Đ°ŃŃ‚Ń€ĐľĐąĐşŃ Đ˛Ńегда можно изменить в лобби Ńетевой игры."
+ "player_force_respawn" "ПринŃдительное возрождение"
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "Отключить прогреŃŃ?"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "Đ’Ńе титаны, орŃжие, фракции, раŃкраŃки, и Ń‚.Đż. ŃŃ‚Đ°Đ˝ŃŃ‚ разблокированы.\n\nĐ­Ń‚Ń Đ˝Đ°ŃŃ‚Ń€ĐľĐąĐşŃ Đ˛Ńегда можно изменить в лобби Ńетевой игры."
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "Đ‘ŃĐ´ŃŃ‚ Đ´ĐľŃŃ‚Ńпны только разблокированные или ĐşŃпленные вами титаны, орŃжие, фракции, раŃкраŃки, и Ń‚.Đż.\n\nĐ­Ń‚Ń Đ˝Đ°ŃŃ‚Ń€ĐľĐąĐşŃ Đ˛Ńегда можно изменить в лобби Ńетевой игры.\n\n^CC000000Внимание: недоŃŃ‚Ńпные предметы в экипировке бŃĐ´ŃŃ‚ заменены на Đ´ĐľŃŃ‚Ńпные!"
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000ПрогреŃŃ!^\n\nĐ’ Northstar теперь работает оригинальная ŃиŃтема прогреŃŃĐ° разблокировок. Титанов, орŃжие, фракции, раŃкраŃки, и Ń‚.Đż. теперь можно разблокировать через полŃчение новых Ńровней и прохождение иŃпытаний.\n\nПрогреŃŃ ĐĽĐľĐ¶Đ˝Đľ включить Ń ĐżĐľĐĽĐľŃ‰ŃŚŃŽ кнопки Đ˛Đ˝Đ¸Đ·Ń ĐĽĐµĐ˝ŃŽ лобби.\n\nĐ­Ń‚Ń Đ˝Đ°ŃŃ‚Ń€ĐľĐąĐşŃ Đ˛Ńегда можно изменить."
+ "AUTHENTICATION_FAILED_BODY" "Не ŃдалоŃŃŚ войти в Atlas!"
+ "AUTHENTICATION_FAILED_ERROR_CODE" "Код ĐľŃибки: ^DB6F2C00%s1^"
+ "AUTHENTICATION_FAILED_HELP" "Справка"
+ "AUTHENTICATION_FAILED_HEADER" "ĐžŃибка Đ°Ńтентификации"
+ "MISSING_MOD" "ОтŃŃŃ‚ŃтвŃет мод \"%s1\" v%s2"
+ "MOD_NOT_VERIFIED" "(мод не был загрŃжен автоматичеŃки, Ń‚.Đş. он не проверенный)"
+ "MOD_DL_DISABLED" "(автоматичеŃкая загрŃзка модов не включена)"
+ "DOWNLOADING_MOD_TITLE" "ЗагрŃзка мода"
+ "DOWNLOADING_MOD_TEXT" "ЗагрŃжаем %s1 v%s2..."
+ "DOWNLOADING_MOD_TEXT_W_PROGRESS" "ЗагрŃжаем %s1 v%s2...\n(%s3/%s4 МБ)"
+ "DOWNLOADING_MOD_TITLE_W_PROGRESS" "ЗагрŃзка мода (%s1%)"
+ "CHECKSUMING_TITLE" "Проверка целоŃтноŃти"
+ "MOD_REQUIRED_WARNING" " : Этот мод может автоматичеŃки включитьŃŃŹ / отключитьŃŃŹ при подключении Đş ŃерверŃ"
+ "WRONG_MOD_VERSION" "ВерŃии мода \"%s1\" не Ńовпадают. На Ńервере: v%s2. ĐŁ ваŃ: v%s3"
+ "CHECKSUMING_TEXT" "Проверяем целоŃтноŃŃ‚ŃŚ %s1 v%s2..."
+ "EXTRACTING_MOD_TITLE" "Đ Đ°Ńпаковка мода (%s1%)"
+ "EXTRACTING_MOD_TEXT" "Đ Đ°Ńпаковываем %s1 v%s2...\n(%s3/%s4 МБ)"
+ "FAILED_DOWNLOADING" "ĐžŃибка загрŃзки мода"
+ "FAILED_READING_ARCHIVE" "ĐžŃибка чтения архива мода."
+ "FAILED_WRITING_TO_DISK" "ĐžŃибка раŃпаковки файлов мода."
+ "MOD_FETCHING_FAILED" "ĐžŃибка Ńкачивания мода Ń Thunderstore."
+ "MOD_CORRUPTED" "Файл архива мода повреждён: контрольная ŃŃĐĽĐĽĐ° не Ńовпадает."
+ "NO_DISK_SPACE_AVAILABLE" "НедоŃтаточно меŃŃ‚Đ° на диŃке."
+ "MOD_FETCHING_FAILED_GENERAL" "ĐžŃибка раŃпаковки мода. Проверьте файл лога, чтобы Ńзнать подробноŃти."
+ "MANIFESTO_FETCHING_TEXT" "Скачиваем ŃпиŃок проверенных модов..."
+ "MANIFESTO_FETCHING_TITLE" "Начало загрŃзки модов"
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_spanish.txt b/Northstar.Client/mod/resource/northstar_client_localisation_spanish.txt
index 8d2df53b..d4172232 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_spanish.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_spanish.txt
@@ -9,10 +9,10 @@
"MENU_TITLE_MODS" "Mods"
"RELOAD_MODS" "Recargar Mods"
"WARNING" "Advertencia"
- "CORE_MOD_DISABLE_WARNING" "Puedes romper el cliente si deshabilitas los mods principales!"
+ "CORE_MOD_DISABLE_WARNING" "¡Puedes romper el cliente si deshabilitas los mods principales!"
"DISABLE" "Deshabilitar"
- "DIALOG_TITLE_INSTALLED_NORTHSTAR" "Gracias por instalar Northstar!."
+ "DIALOG_TITLE_INSTALLED_NORTHSTAR" "¡Gracias por instalar Northstar!"
"AUTHENTICATION_AGREEMENT_DIALOG_TEXT" "Para que Northstar funcione, necesita autentificarse con el servidor maestro Northstar. Esto requiere enviar tu token Origin al servidor maestro, no se guardará ni se usará para cualquier otro fin.
Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mods en cualquier momento."
"BACK_AUTHENTICATION_AGREEMENT" "Acuerdo de autentificaciĂłn"
@@ -26,7 +26,7 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"NS_SERVERBROWSER_NOSERVERS" "No se encontraron servidores"
"NS_SERVERBROWSER_UNKNOWNMODE" "Modo desconocido"
"NS_SERVERBROWSER_WAITINGFORSERVERS" "Esperando por servidores..."
- "NS_SERVERBROWSER_CONNECTIONFAILED" "ConnexiĂłn fallida!"
+ "NS_SERVERBROWSER_CONNECTIONFAILED" "¡Conexión fallida!"
"REFRESH_SERVERS" "Recargar"
"MENU_TITLE_CONNECT_PASSWORD" "Conectar con contraseña"
@@ -36,7 +36,7 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"PRIVATE_MATCH_PAGE_NEXT" "Página siguiente"
"MENU_MATCH_SETTINGS" "ConfiguraciĂłn de partida"
- "MENU_MATCH_SETTINGS_SUBMENU" "%s1 Configuraciones"
+ "MENU_MATCH_SETTINGS_SUBMENU" "ConfiguraciĂłn de %s1"
"PRIVATE_MATCH_SINGLEPLAYER_LEVEL" "%s1 (Un jugador)"
@@ -46,16 +46,16 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
// mode settings
"MODE_SETTING_CATEGORY_PILOT" "Piloto"
"MODE_SETTING_CATEGORY_TITAN" "Titán"
- "MODE_SETTING_CATEGORY_RIFF" "Riffs"
+ "MODE_SETTING_CATEGORY_RIFF" "Fragmento"
"MODE_SETTING_CATEGORY_MATCH" "Partida"
"classic_mp" "Multijugador Clasico"
"run_epilogue" "Habilitar EpĂ­logo"
"scorelimit" "Limite de puntuaciĂłn"
- "roundscorelimit" "Puntaje limite (rondas)"
- "timelimit" "Tiempo limite"
- "roundtimelimit" "Tiempo limite (rondas)"
- "respawnprotection" "Tiempo de proteccion en reapariciĂłn"
+ "roundscorelimit" "LĂ­mite de punataje (rondas)"
+ "timelimit" "LĂ­mite de tiempo"
+ "roundtimelimit" "LĂ­mite de tiempo (rondas)"
+ "respawnprotection" "Tiempo de protecciĂłn en reapariciĂłn"
"pilot_health_multiplier" "Multiplicador de salĂşd"
"respawn_delay" "Retraso de apariciĂłn"
@@ -73,7 +73,7 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"featured_mode_all_grapple" "Ataque a los titanes"
"featured_mode_all_phase" "El otro lado"
"featured_mode_all_ticks" "Picante"
- "featured_mode_tactikill" "EliminaciĂłn tactica"
+ "featured_mode_tactikill" "EliminaciĂłn con tactica"
"featured_mode_amped_tacticals" "Tacticas mejoradas"
"featured_mode_rocket_arena" "Arena de cohetes"
"featured_mode_shotguns_snipers" "Armado y peligroso"
@@ -82,12 +82,14 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"cp_amped_capture_points" "Fortines cargados"
"coliseum_loadouts_enabled" "Arsenales de coliseo"
+ "aitdm_archer_grunts" "Soldado Archer"
+
// northstar.custom localisation is just deciding not to work, so putting it here for now
- "PL_sbox" "Sandbox"
- "PL_sbox_lobby" "VestĂ­bulo de Sandbox"
- "PL_sbox_desc" "como gmod pero peor"
+ "PL_sbox" "Custom"
+ "PL_sbox_lobby" "VestĂ­bulo de juego abierto"
+ "PL_sbox_desc" "Como \"gmod\" pero peor"
"PL_sbox_abbr" "SBOX"
- "GAMEMODE_SBOX" "Sandbox"
+ "GAMEMODE_SBOX" "CUstom"
"PL_gg" "Carrera armamentĂ­stica"
"PL_gg_lobby" "VestĂ­bulo de Carrera armamentĂ­stica"
@@ -101,37 +103,37 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"PL_tt" "Titan Tag"
"PL_tt_lobby" "VestĂ­bulo de Titan Tag"
- "PL_tt_desc" "Obtén puntos en tu titán. Destruye un titán para obtener el tuyo."
- "PL_tt_hint" "Obtén puntos en tu titán. Destruye un titán para obtener el tuyo."
+ "PL_tt_desc" "Obtén puntos estando en tu titán. Destruye un titán para obtener el tuyo."
+ "PL_tt_hint" "Obtén puntos estando en tu titán. Destruye un titán para obtener el tuyo."
"PL_tt_abbr" "TT"
"GAMEMODE_TT" "Titan Tag"
"PL_chamber" "Uno en la recamara"
"PL_chamber_lobby" "VestĂ­bulo de uno en la recamara"
- "PL_chamber_desc" "Un disparo, Una eliminación. Obtén otra bala en tu cargador eliminando a alguien."
- "PL_chamber_hint" "Un disparo, Una eliminación. Obtén otra bala en tu cargador eliminando a alguien."
- "PL_chamber_abbr" "CHAMBER"
- "GAMEMODE_CHAMBER" "Uno en la recamara"
+ "PL_chamber_desc" "Un disparo, una eliminación. Obtén otra bala en tu cargador eliminando a alguien."
+ "PL_chamber_hint" "Un disparo, una eliminación. Obtén otra bala en tu cargador eliminando a alguien."
+ "PL_chamber_abbr" "RECĂMARA"
+ "GAMEMODE_CHAMBER" "Uno en la recámara"
"PL_hidden" "El escondido"
"PL_hidden_lobby" "VestĂ­bulo de El escondido"
"PL_hidden_desc" "Un jugador es invisible. El escondido caza."
"PL_hidden_hint" "Un jugador es invisible. El escondido caza."
- "PL_hidden_abbr" "HIDDEN"
+ "PL_hidden_abbr" "OCULTO"
"GAMEMODE_HIDDEN" "El escondido"
- "HIDDEN_YOU_ARE_HIDDEN" "Eres el escondido!"
+ "HIDDEN_YOU_ARE_HIDDEN" "¡Eres el escondido!"
"HIDDEN_KILL_SURVIVORS" "Mata a todos los supervivientes."
"HIDDEN_FIRST_HIDDEN" "%s1 es El Escondido."
- "PL_sns" "Piedras y palos"
- "PL_sns_lobby" "VestĂ­bulo de Piedras y palos"
+ "PL_sns" "Palos y Piedras"
+ "PL_sns_lobby" "VestĂ­bulo de Palos y Piedras"
"PL_sns_desc" "Todos contra todos. Usa cuchillas de pulso y ejecucciones para reestablecer el puntaje del enemigo"
"PL_sns_abbr" "SNS"
- "GAMEMODE_SNS" "Piedras y palos"
+ "GAMEMODE_SNS" "Palos y Piedras"
"SCOREBOARD_BANKRUPTS" "Eliminaciones de bancarrota"
- "SNS_LEADER_BANKRUPT" "Lider de puntaje en bancarrota!"
- "SNS_LEADER_BANKRUPT_SUB" "%s1 Fue reseteado por %s2"
- "SNS_BANKRUPT" "Arruinado!"
+ "SNS_LEADER_BANKRUPT" "¡Lider de puntaje en bancarrota!"
+ "SNS_LEADER_BANKRUPT_SUB" "%s1 fue reseteado por %s2"
+ "SNS_BANKRUPT" "¡Arruinado!"
"SNS_BANKRUPT_SUB" "Tu puntaje fue reseteado por %s1"
"sns_wme_kill_value" "Valor por eliminacion con Wingman Elite"
"sns_softball_kill_value" "Valor por eliminacion con Softball"
@@ -150,7 +152,7 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"INFECTION_YOU_ARE_INFECTED" "Has sido infectado!"
"INFECTION_KILL_SURVIVORS" "Infecta a todos los supervivientes restantes."
"INFECTION_FIRST_INFECTED" "%s1 es el primer Infectado."
- "INFECTION_LAST_SURVIVOR" "%s1 es el ultimo superviviente!"
+ "INFECTION_LAST_SURVIVOR" "¡%s1 es el ultimo superviviente!"
"INFECTION_KILL_LAST_SURVIVOR" "Infectalo antes de que el tiempo se acabe!"
"INFECTION_YOU_ARE_LAST_SURVIVOR" "Eres el ultimo superviviente!"
"INFECTION_SURVIVE_LAST_SURVIVOR" "Sobrevive."
@@ -184,7 +186,7 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"GAMEMODE_fw" "Guerra fronteriza"
"PL_fw" "Guerra fronteriza"
"PL_fw_lobby" "VestĂ­bulo de Guerra fronteriza"
- "PL_fw_desc" "Destruye el cosechador del enemigo y protege el tuyo."
+ "PL_fw_desc" "Destruye el cosechador del enemigo y protege el tuyo"
"PL_fw_abbr" "FW"
"GAMEMODE_kr" "Carrera por muertes"
@@ -203,8 +205,8 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"KR_YOUR_KILLRACE_OVER" "Tu carrera ha acabado"
"KR_YOUR_KILLRACE_SCORE" "Has obtenido %s1 bajas."
- "GAMEMODE_fastball" "Fastball"
- "PL_fastball" "Fastball"
+ "GAMEMODE_fastball" "Lanzamiento"
+ "PL_fastball" "Lanzamiento"
"PL_fastball_lobby" "VestĂ­bulo de Fastball"
"PL_fastball_desc" "Muerte permanente. Piratea paneles de control para ganar rondas y hacer reaparecer a tus compañeros de equipo."
"PL_fastball_hint" "Muerte permanente. Piratea paneles de control para ganar rondas y hacer reaparecer a tus compañeros de equipo."
@@ -261,7 +263,7 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"SP_BEACON_CLASSIC_DESC" "Cooper y BT intentan contactar con lo que queda de la flota para alertar de los planes de IMC."
"SP_TDAY" "Prueba de fuego"
- "SP_TDAY_CLASSIC_DESC" "Las habilidades de Cooper al mando de su titán se ponen a prueba en una batalla total para capturar el Arca."
+ "SP_TDAY_CLASSIC_DESC" "Las habilidades de Cooper al mando de su titán se ponen a prueba en una batalla total para capturar el Arca"
"SP_S2S" "El Arca"
"SP_S2S_CLASSIC_DESC" "Cooper y BT van de nave en nave en busca del Arca."
@@ -274,7 +276,7 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"PLAYERS_COLUMN" "Jugadores"
"MAP_COLUMN" "Mapa"
"GAMEMODE_COLUMN" "Modo de juego"
- "LATENCY_COLUMN" "Latencia"
+ "REGION_COLUMN" "RegiĂłn"
"SEARCHBAR_LABEL" "Buscar:"
"MAP_FILTER" "Mapa"
"GAMEMODE_FILTER" "Modo de juego"
@@ -282,7 +284,7 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"HIDE_EMPTY_FILTER" "Esconder servidores vacios"
"HIDE_PROT_FILTER" "Esconder servidores protegidos"
"SERVER_DESCRIPTION" "DescripciĂłn"
- "SERVER_MODS" "Mods"
+ "SERVER_MODS" "Modificaciones"
"CLEAR_FILTERS" "LIMPIAR"
"JOIN_BUTTON" "UNIR"
@@ -316,6 +318,40 @@ Presiona SĂ­ al estar de acuerdo. Esta opcion se puede cambiar en el menĂş de mo
"PLAYER_NOT_FOUND" "No se encontrĂł la cuenta del jugador"
"INVALID_MASTERSERVER_TOKEN" "Token de jugador expirado o invalido"
"JSON_PARSE_ERROR" "Error procesando respuesta json"
- "UNSUPPORTED_VERSION" "La versiĂłn que estas usando ya no esta soportada"
+ "UNSUPPORTED_VERSION" "La versión que estás usando ya no está siendo mantenida"
+ "MATCH_COUNTDOWN_LENGTH" "DuraciĂłn cronometrada de la partida privada"
+ "ONLY_HOST_MATCH_SETTINGS" "SĂłlo el anfitriĂłn puede cambiar las configuraciones de la partida"
+ "ONLY_HOST_CAN_START_MATCH" "SĂłlo el anfitriĂłn puede iniciar la partida"
+ "DISALLOWED_WEAPONS" "Armas prohibidas"
+ "REPLACEMENT_WEAPON" "Arma de reemplazo"
+ "MOD_SETTINGS" "ConfiguraciĂłn de Mods"
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "Registrar comandos no reconocidos desde el cliente"
+ "DISALLOWED_TACTICALS" "Tácticas prohibidas"
+ "TACTICAL_REPLACEMENT" "Reemplazo de la táctica"
+ "SHOULD_RETURN_TO_LOBBY" "Regresar a la sala de espera después de la partida"
+ "ARE_YOU_SURE" "ÂżSegur@?"
+ "WILL_RESET_ALL_SETTINGS" "Esto reiniciará TODAS las opciones de ésta categoría.\n\nNo se podrá revertir el proceso."
+ "NORTHSTAR_BASE_SETTINGS" "Opciones de Northstar por defecto"
+ "NO_RESULTS" "No hay resultados."
+ "MOD_SETTINGS_RESET" "Reiniciar"
+ "SHOW_ONLY_NOT_REQUIRED" "Mostrar sĂłlo mods opcionales"
+ "SHOW_ONLY_REQUIRED" "Mostrar sĂłlo mods obligatorios"
+ "NO_MODS" "¡No hay configuraciones disponibles! Instala más mods desde ^5588FF00northstar.thunderstore.io^0."
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "ÂżHabilitar progreso?"
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "ÂżDeshabilitar progresiĂłn?"
+ "PROGRESSION_ENABLED_HEADER" "¡Progreso habilitado!"
+ "PROGRESSION_DISABLED_HEADER" "¡Progreso deshabilitado!"
+ "TOGGLE_PROGRESSION" "Cambiar modo de progresiĂłn"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Cambiar modo de progresiĂłn"
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "Titanes, Armas, Facciones, Aspectos y otros serán desbloqueados sólo al subir de nivel, o a través de Logros.\n\nÉsta opción puede ser cambiada cuando quieras en la sala de espera.\n\n^CC000000Advertencia: ¡Cualquier equipamiento o utensilio será reiniciado si no lo tienes desbloqueado!"
+ "MOD_SETTINGS_SERVER" "Servidor"
+ "MOD_SETTINGS_RESET_ALL" "Reiniciar completamente"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "Los Titanes, Armas, Facciones, Aspectos y otros serán desbloqueados y permanecerán utilizables.\n\nÉsta opción puede ser cambiada cuando quieras desde la sala de espera."
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000El modo de progreso ha sido habilitado.^\n\nLos Titanes, Armas, Facciones, Aspectos y otros se desbloquearán al subir de nivel o por Logros.\n\nPuedes cambiar ésta opción cuando quieras en la sala de espera."
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000El modo de progreso ha sido deshabilitado.^\n\nLos Titanes, Armas, Facciones, Aspectos y otros están desbloqueados y puedes utilizarlos cuando quieras.\n\nPuedes cambiar ésta opción cuando quieras desde la sala de espera."
+ "WILL_RESET_SETTING" "Ésta acción reiniciará %s1 a su valor por defecto.\n\nNo se podrá revertir el proceso."
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000El progreso puede ser habilitado desde ahora^\n\nNorthstar ahora es compatible con el progreso normal del juego, esto significa que puedes elegir desbloquear Armas, Aspectos, Titanes y otros a través de desafíos y subiendo de nivel.\n\nPuedes habilitar el progreso normal del juego en la opción ubicada al final de la pantalla de la sala de espera.\n\nÉsta opción puede ser cambiada en cualquier momento."
+ "player_force_respawn" "ReapariciĂłn Forzada"
+ "MOD_REQUIRED_WARNING" " : Esta modificacion puede ser desactivada al entrar a un servidor"
}
}
diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt b/Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt
index 5e6721a9..500f8a96 100644
--- a/Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt
+++ b/Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt
@@ -82,6 +82,8 @@
"cp_amped_capture_points" "強化據點"
"coliseum_loadouts_enabled" "競技場裝備"
+ "aitdm_archer_grunts" "射手飛ĺ˝ć­Ąĺ…µ"
+
// northstar.custom localisation is just deciding not to work, so putting it here for now
"PL_sbox" "沙盒"
"PL_sbox_lobby" "沙盒大廳"
@@ -125,7 +127,7 @@
"PL_sns" "冷兵器"
"PL_sns_lobby" "冷兵器大廳"
- "PL_sns_desc" "模擬ĺŤĺ­—弓和飛斧的作ć°ć¨ˇĺĽŹă€‚使用č„衝ĺ€ĺŹŠč™•ć±şć“Šć®şäľ†é‡Ťç˝®ć•µäşşçš„ĺ†ć•¸ă€‚"
+ "PL_sns_desc" "无规ĺ™. 使用č„衝ĺ€ĺŹŠč™•ć±şć“Šć®şäľ†é‡Ťç˝®ć•µäşşçš„ĺ†ć•¸"
"PL_sns_abbr" "SNS"
"GAMEMODE_SNS" "冷兵器"
"SCOREBOARD_BANKRUPTS" "破產次數"
@@ -153,7 +155,7 @@
"INFECTION_LAST_SURVIVOR" "%s1ćŻćś€ĺľŚçš„倖ĺ­č€…ďĽ"
"INFECTION_KILL_LAST_SURVIVOR" "在時間用盡前感染他ďĽ"
"INFECTION_YOU_ARE_LAST_SURVIVOR" "ä˝ ćŻćś€ĺľŚçš„倖ĺ­č€…ďĽ"
- "INFECTION_SURVIVE_LAST_SURVIVOR" "倖ĺ­"
+ "INFECTION_SURVIVE_LAST_SURVIVOR" "最後幸ĺ­č€…."
"PL_tffa" "泰坦混ć°"
"PL_tffa_lobby" "泰坦混ć°ĺ¤§ĺ»ł"
@@ -236,45 +238,45 @@
"player_bleedout_aiBleedingPlayerMissChance" "倒地時AI的失誤率"
// coop stuff
- "PL_sp_coop" "(UNFINISHED) Singleplayer Coop"
- "PL_sp_coop_lobby" "Singleplayer Coop Lobby"
- "PL_sp_coop_desc" "Play through the singleplayer campaign with friends"
- "PL_sp_coop_hint" "Play through the singleplayer campaign with friends"
+ "PL_sp_coop" "(未完ć) 單人ĺ作"
+ "PL_sp_coop_lobby" "單人ĺ作模式大廳"
+ "PL_sp_coop_desc" "č‡ćś‹ĺŹ‹ä¸€čµ·éŠçŽ©ĺ–®äşşć°ĺ˝ą"
+ "PL_sp_coop_hint" "č‡ćś‹ĺŹ‹ä¸€čµ·éŠçŽ©ĺ–®äşşć°ĺ˝ą"
"PL_sp_coop_abbr" "SP"
- "SP_TRAINING" "The Pilot's Gauntlet"
- "SP_TRAINING_CLASSIC_DESC" "Captain Lastimosa's training simulation."
+ "SP_TRAINING" "éµĺľˇçš„試煉"
+ "SP_TRAINING_CLASSIC_DESC" "拉絲ćŹčŽ«ć˛™ä¸Šĺ°‰çš„模擬訓練."
"SP_CRASHSITE" "BT-7274"
- "SP_CRASHSITE_CLASSIC_DESC" "Jack Cooper meets BT-7274."
+ "SP_CRASHSITE_CLASSIC_DESC" "傑克庫博é č¦‹BT-7274."
- "SP_SEWERS1" "Blood and Rust"
- "SP_SEWERS1_CLASSIC_DESC" "Cooper and BT set out to rendezvous with Major Anderson."
+ "SP_SEWERS1" "鮮血č‡éµéŹ˝"
+ "SP_SEWERS1_CLASSIC_DESC" "庫博和BT一起出發前往č‡ĺ®‰ĺľ·ćŁ®ä¸Šĺ°‰ĺ›žĺ."
- "SP_BOOMTOWN_START" "Into the Abyss"
- "SP_BOOMTOWN_START_CLASSIC_DESC" "An underground shortcut yields unexpected consequences."
+ "SP_BOOMTOWN_START" "踏入虛空"
+ "SP_BOOMTOWN_START_CLASSIC_DESC" "一個地下捷徑造ć了不可é č¦‹çš„後果."
- "SP_HUB_TIMESHIFT" "Effect and Cause"
- "SP_HUB_TIMESHIFT_CLASSIC_DESC" "A strange phenomenon is discovered at Major Anderson's coordinates."
+ "SP_HUB_TIMESHIFT" "因果報應"
+ "SP_HUB_TIMESHIFT_CLASSIC_DESC" "在安德森上尉的ĺťć¨™č™•ç™ĽçŹľäş†ĺĄ‡ç•°çš„現象."
- "SP_BEACON" "The Beacon"
- "SP_BEACON_CLASSIC_DESC" "Cooper and BT attempt to inform the remaining fleet of the IMC's plans."
+ "SP_BEACON" "信號台"
+ "SP_BEACON_CLASSIC_DESC" "庫博和BTĺ—試將IMCçš„č¨ĺŠé€šçźĄçµ¦ĺ‰©é¤ĺŹŤćŠ—軍艦隊."
- "SP_TDAY" "Trial by Fire"
- "SP_TDAY_CLASSIC_DESC" "Cooper's Titan skills are put to the test in an all-out battle to capture the Ark"
+ "SP_TDAY" "çç«ĺŻ©ĺ¤"
+ "SP_TDAY_CLASSIC_DESC" "庫博驾馭泰坦的技術在ç­ĺĄŞč–ć«çš„全面ć°ç­ä¸­ĺľ—ĺ°č€é©—"
- "SP_S2S" "The Ark"
- "SP_S2S_CLASSIC_DESC" "Cooper and BT go ship to ship in pursuit of the Ark."
+ "SP_S2S" "č–ć«"
+ "SP_S2S_CLASSIC_DESC" "庫博和BT在艦艇中穿梭追尋č–ć«."
- "SP_SKYWAY_V1" "The Fold Weapon"
- "SP_SKYWAY_V1_CLASSIC_DESC" "BT and Cooper are captured by Kuben Blisk."
+ "SP_SKYWAY_V1" "ćŠç–Šć™‚空武器"
+ "SP_SKYWAY_V1_CLASSIC_DESC" "BT和庫博被庫本ĺ¸é‡Ść–Żĺ…‹ć“’ć‹ż."
// Better.Serverbrowser
"SERVERS_COLUMN" "伺服器"
"PLAYERS_COLUMN" "玩家"
"MAP_COLUMN" "ĺś°ĺś–"
"GAMEMODE_COLUMN" "éŠć˛ć¨ˇĺĽŹ"
- "LATENCY_COLUMN" "延é˛"
+ "REGION_COLUMN" "地區"
"SEARCHBAR_LABEL" "ćśĺ°‹ďĽš"
"MAP_FILTER" "ĺś°ĺś–"
"GAMEMODE_FILTER" "éŠć˛ć¨ˇĺĽŹ"
@@ -308,8 +310,8 @@
"HUD_CHAT_SERVER_PREFIX" "[伺服器]"
// In-game chat
- "HUD_CHAT_WHISPER_PREFIX" "[WHISPER]"
- "HUD_CHAT_SERVER_PREFIX" "[SERVER]"
+ "HUD_CHAT_WHISPER_PREFIX" "[悄悄話]"
+ "HUD_CHAT_SERVER_PREFIX" "[伺服器]"
"NO_GAMESERVER_RESPONSE" "無法連接ĺ°éŠć˛äĽşćśŤĺ™¨'"
"BAD_GAMESERVER_RESPONSE" "éŠć˛äĽşćśŤĺ™¨ĺ›žć‡‰ç„ˇć•"
@@ -321,5 +323,38 @@
"INVALID_MASTERSERVER_TOKEN" "主伺服器tokenéŽćśźć–無ć•"
"JSON_PARSE_ERROR" "讀取json回應時發生錯誤"
"UNSUPPORTED_VERSION" "您的éŠć˛ç‰ćś¬éŽä˝Ž"
+ "NORTHSTAR_BASE_SETTINGS" "北ćžćźĺźşçˇ€č®ľç˝®"
+ "ONLY_HOST_CAN_START_MATCH" "只有服主可以開始對局"
+ "MATCH_COUNTDOWN_LENGTH" "ç§äşşĺ°Ťĺ±€ĺ€’č¨ć™‚時間"
+ "DISALLOWED_TACTICALS" "ç¦ç”¨çš„技č˝"
+ "TACTICAL_REPLACEMENT" "替換的技č˝"
+ "NO_RESULTS" "無çµćžś."
+ "LOG_UNKNOWN_CLIENTCOMMANDS" "ç™»č¨ćśŞçźĄĺ®˘ć¶ç«ŻćŚ‡ä»¤"
+ "SHOULD_RETURN_TO_LOBBY" "ĺ°Ťĺ±€çµćťźĺľŚčż”回大廳"
+ "ARE_YOU_SURE" "確定?"
+ "WILL_RESET_SETTING" "這將ćśé‡Ťç˝® %s1 的設置為é»čŞŤĺ€Ľ.\n\n此操作不可復原."
+ "MOD_SETTINGS_SERVER" "服務器"
+ "MOD_SETTINGS_RESET" "重置"
+ "MOD_SETTINGS_RESET_ALL" "重置所有"
+ "SHOW_ONLY_NOT_REQUIRED" "ĺ…展示非必需模組"
+ "SHOW_ONLY_REQUIRED" "ĺ…展示必é ć¨ˇçµ„"
+ "MOD_SETTINGS" "模组设置"
+ "ONLY_HOST_MATCH_SETTINGS" "只有服主可以修改ç§äşşĺ°Ťĺ±€č¨­ç˝®"
+ "DISALLOWED_WEAPONS" "ç¦ç”¨çš„武器"
+ "REPLACEMENT_WEAPON" "替换的武器"
+ "WILL_RESET_ALL_SETTINGS" "這將ćśé‡Ťç˝®ć‰€ćś‰ĺ±¬ć–Ľć”ąć˘ťç›®çš„設置.\n\n此操作不可復原."
+ "NO_MODS" "無可用模組! 前往 ^5588FF00northstar.thunderstore.io^0 下載更多."
+ "player_force_respawn" "強ĺ¶é‡Ťç”ź"
+ "PROGRESSION_TOGGLE_DISABLED_HEADER" "啟用個人進度?"
+ "PROGRESSION_ENABLED_HEADER" "個人進度已開啟ďĽ"
+ "PROGRESSION_TOGGLE_DISABLED_BODY" "泰坦,武器, 陣營,皮膚以及其他一ĺ‡éś€č¦č§ŁéŽ–的物ĺ“將通éŽĺŤ‡ç´šć–ćŻä˝żç”¨é»žć•¸čłĽč˛·äľ†é€˛čˇŚč§ŁéŽ–。.\n\n您可以隨時在多人大廳中更改此項。\n\n^CC000000警告:如果您已經裝備了尚未解鎖的物ĺ“,ĺ®ĺ€‘ĺ°‡ćśč˘«é‡Ťç˝®ďĽ"
+ "PROGRESSION_TOGGLE_ENABLED_BODY" "泰坦,武器,陣營,皮膚及所有其他一ĺ‡éś€č¦č§ŁéŽ–的物ĺ“é˝ĺ°‡éš¨ć™‚可以進行解鎖並使用。\n\n您可以隨時在多人大廳中更改此項。"
+ "TOGGLE_PROGRESSION" "éŠć˛é€˛ĺş¦"
+ "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% éŠć˛é€˛ĺş¦"
+ "PROGRESSION_TOGGLE_ENABLED_HEADER" "ĺśç”¨ĺ€‹äşşé€˛ĺş¦ďĽź"
+ "PROGRESSION_DISABLED_HEADER" "個人進度已關閉ďĽ"
+ "PROGRESSION_DISABLED_BODY" "^CCCC0000個人進度已ĺśç”¨^\n\n泰坦,武器,陣營,皮膚及所有其他一ĺ‡éś€č¦č§ŁéŽ–的物ĺ“將隨時可以進行解鎖並使用。\n\n您可以隨時在多人大廳中更改此項。"
+ "PROGRESSION_ENABLED_BODY" "^CCCC0000個人進度已啟用^\n\n泰坦,武器, 陣營,皮膚以及其他一ĺ‡éś€č¦č§ŁéŽ–的物ĺ“將通éŽĺŤ‡ç´šć–ćŻä˝żç”¨é»žć•¸čłĽč˛·äľ†é€˛čˇŚč§ŁéŽ–。.\n\n您可以隨時在多人大廳中更改此項。"
+ "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000現在可以隨時開啟個人進度ďĽ^\n\nNorthstar 現在支ćŚéˇžäĽĽĺ®ćśŤçš„進度系統, 这意味着你可以选择通过升级和完ć挑ć来解é”武器ă€çš®č‚¤ă€ćł°ĺť¦ç­‰ă€‚\n\n您可以通éŽĺ¤šäşşĺ¤§ĺ»łĺş•é¨çš„“éŠć˛é€˛ĺş¦â€ťćŚ‰é•äľ†é€˛čˇŚé–‹ĺ•źă€‚\n\n您可以隨時在多人大廳中更改此項。"
}
}
diff --git a/Northstar.Client/mod/resource/ui/menus/mod_settings.menu b/Northstar.Client/mod/resource/ui/menus/mod_settings.menu
new file mode 100644
index 00000000..2fed2bd1
--- /dev/null
+++ b/Northstar.Client/mod/resource/ui/menus/mod_settings.menu
@@ -0,0 +1,511 @@
+"resource/ui/menus/mods_browse.menu"
+{
+ "menu"
+ {
+ "ControlName" "Frame"
+ "xpos" "0"
+ "ypos" "0"
+ "zpos" "3"
+ "wide" "f0"
+ "tall" "f0"
+ "autoResize" "1"
+ "visible" "1"
+ "enabled" "1"
+ "pinCorner" "0"
+ "PaintBackgroundType" "0"
+ "infocus_bgcolor_override" "0 0 0 0"
+ "outoffocus_bgcolor_override" "0 0 0 0"
+ "Vignette"
+ {
+ "ControlName" "ImagePanel"
+ "InheritProperties" "MenuVignette"
+ }
+ "Title"
+ {
+ "ControlName" "Label"
+ "InheritProperties" "MenuTitle"
+ "labelText" "#MOD_SETTINGS"
+ }
+ "ImgTopBar"
+ {
+ "ControlName" "ImagePanel"
+ "InheritProperties" "MenuTopBar"
+ }
+ "DarkenBackground"
+ {
+ "ControlName" "Label"
+ "classname" "ConnectingHUD"
+ "xpos" "0"
+ "ypos" "0"
+ "zpos" "99"
+ "wide" "%100"
+ "tall" "%100"
+ "labelText" ""
+ "bgcolor_override" "0 0 0 0"
+ "visible" "0"
+ "paintbackground" "1"
+ }
+ "ButtonRowAnchor"
+ {
+ "ControlName" "Label"
+ "labelText" ""
+ "pin_to_sibling" "DarkenBackground"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ "pin_corner_to_sibling" "BOTTOM_LEFT"
+ "xpos" "-150"
+ "ypos" "-200"
+ }
+ "FilterButtonsRowAnchor"
+ {
+ "ControlName" "Label"
+ "pin_to_sibling" "LabelDetails"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "labelText" ""
+ "ypos" "12"
+ }
+ "NoResultLabel"
+ {
+ "ControlName" "Label"
+ "xpos" "0"
+ ypos "0"
+ wide "1200"
+ tall "675"
+ //auto_tall_tocontents 1
+ visible "1"
+ enabled "1"
+ //auto_wide_tocontents 1
+ labelText "No results."
+ textAlignment "center"
+ //auto_wide_tocontents "1"
+ //auto_tall_tocontents "1"
+ //fgcolor_override "255 255 255 255"
+ //bgcolor_override "0 0 0 200"
+ font Default_41
+
+ pin_to_sibling ButtonRowAnchor
+ pin_to_sibling_corner TOP_LEFT
+ pin_corner_to_sibling TOP_LEFT
+ }
+ // pain //
+ "BtnMod1"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "pin_to_sibling" "ButtonRowAnchor"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ "navUp" "BtnMod15"
+ "navDown" "BtnMod2"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "scriptID" "0"
+ }
+ "BtnMod2"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "pin_to_sibling" "BtnMod1"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod1"
+ "navDown" "BtnMod3"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "scriptID" "1"
+ }
+ "BtnMod3"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "2"
+ "pin_to_sibling" "BtnMod2"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod2"
+ "navDown" "BtnMod4"
+ }
+ "BtnMod4"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "3"
+ "pin_to_sibling" "BtnMod3"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod3"
+ "navDown" "BtnMod5"
+ }
+ "BtnMod5"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "4"
+ "pin_to_sibling" "BtnMod4"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod4"
+ "navDown" "BtnMod6"
+ }
+ "BtnMod6"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "5"
+ "pin_to_sibling" "BtnMod5"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod5"
+ "navDown" "BtnMod7"
+ }
+ "BtnMod7"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "6"
+ "pin_to_sibling" "BtnMod6"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod6"
+ "navDown" "BtnMod8"
+ }
+ "BtnMod8"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "7"
+ "pin_to_sibling" "BtnMod7"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod7"
+ "navDown" "BtnMod9"
+ }
+ "BtnMod9"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "8"
+ "pin_to_sibling" "BtnMod8"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod8"
+ "navDown" "BtnMod10"
+ }
+ "BtnMod10"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "9"
+ "pin_to_sibling" "BtnMod9"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod9"
+ "navDown" "BtnMod11"
+ }
+ "BtnMod11"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "10"
+ "pin_to_sibling" "BtnMod10"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod10"
+ "navDown" "BtnMod12"
+ }
+ "BtnMod12"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "11"
+ "pin_to_sibling" "BtnMod11"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod11"
+ "navDown" "BtnMod13"
+ }
+ "BtnMod13"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "12"
+ "pin_to_sibling" "BtnMod12"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod12"
+ "navDown" "BtnMod14"
+ }
+ "BtnMod14"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "13"
+ "pin_to_sibling" "BtnMod13"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod13"
+ "navDown" "BtnMod15"
+ }
+ "BtnMod15"
+ {
+ "ControlName" "CNestedPanel"
+ "classname" "ModButton"
+ "tall" "45"
+ "wide" "1200"
+ "controlSettingsFile" "resource/UI/menus/panels/mod_setting.res"
+ "classname" "ModButton"
+ "scriptID" "14"
+ "pin_to_sibling" "BtnMod14"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ "navUp" "BtnMod14"
+ "navDown" "BtnMod1"
+ }
+ // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ "FilterPanel"
+ {
+ "ControlName" "RuiPanel"
+ "wide" "1220"
+ "tall" "112"
+ //"xpos" "-8"
+ "classname" "FilterPanelChild"
+ "rui" "ui/knowledgebase_panel.rpak"
+ "visible" "1"
+ "zpos" "-1"
+ "pin_to_sibling" "FilterButtonsRowAnchor"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "LabelDetails"
+ {
+ "ControlName" "RuiPanel"
+ "tall" "695"
+ "wide" "1220"
+ "xpos" "10"
+ "ypos" "10"
+ "pin_to_sibling" "ButtonRowAnchor"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ "rui" "ui/knowledgebase_panel.rpak"
+ "wrap" "1"
+ "visible" "1"
+ "zpos" "-1"
+ }
+ "BtnSearchLabel"
+ {
+ "ControlName" "RuiButton"
+ "InheritProperties" "RuiSmallButton"
+ "labelText" "#SEARCHBAR_LABEL"
+ "textAlignment" "west"
+ "classname" "FilterPanelChild"
+ "wide" "500"
+ "xpos" "-23"
+ "ypos" "-16"
+ "wrap" "1"
+ "visible" "1"
+ "zpos" "0"
+ "pin_to_sibling" "FilterButtonsRowAnchor"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "BtnModsSearch"
+ {
+ "ControlName" "TextEntry"
+ "classname" "FilterPanelChild"
+ "zpos" "100" // This works around input weirdness when the control is constructed by code instead of VGUI blackbox.
+ "xpos" "-400"
+ "ypos" "-5"
+ "wide" "390"
+ "tall" "30"
+ "textHidden" "0"
+ "editable" "1"
+ "font" "Default_21"
+ "allowRightClickMenu" "0"
+ "allowSpecialCharacters" "1"
+ "unicode" "1"
+ "pin_to_sibling" "BtnSearchLabel"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "TOP_RIGHT"
+ }
+ "BtnFiltersClear"
+ {
+ "ControlName" "RuiButton"
+ "InheritProperties" "RuiSmallButton"
+ "labelText" "#CLEAR_FILTERS"
+ "classname" "FilterPanelChild"
+ "wide" "100"
+ "xpos" "0"
+ "ypos" "0"
+ "zpos" "90"
+ "scriptID" "999"
+ "pin_to_sibling" "BtnSearchLabel"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "TOP_RIGHT"
+ }
+ // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ "BtnModListUpArrow"
+ {
+ "ControlName" "RuiButton"
+ "InheritProperties" "RuiSmallButton"
+ // labelText "A"F
+ "wide" "40"
+ "tall" "40"
+ "xpos" "2"
+ "ypos" "0"
+ "image" "vgui/hud/white"
+ "drawColor" "255 255 255 128"
+ "pin_to_sibling" "LabelDetails"
+ "pin_corner_to_sibling" "TOP_RIGHT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "BtnModListUpArrowPanel"
+ {
+ "ControlName" "RuiPanel"
+ "wide" "40"
+ "tall" "40"
+ "xpos" "2"
+ "ypos" "0"
+ "rui" "ui/knowledgebase_panel.rpak"
+ "visible" "1"
+ "zpos" "-1"
+ "pin_to_sibling" "LabelDetails"
+ "pin_corner_to_sibling" "TOP_RIGHT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "BtnModListDownArrow"
+ {
+ "ControlName" "RuiButton"
+ "InheritProperties" "RuiSmallButton"
+ // labelText "V"
+ "wide" "40"
+ "tall" "40"
+ "xpos" "2"
+ "ypos" "-655"
+ "image" "vgui/hud/white"
+ "drawColor" "255 255 255 128"
+ "pin_to_sibling" "LabelDetails"
+ "pin_corner_to_sibling" "TOP_RIGHT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "BtnModListDownArrowPanel"
+ {
+ "ControlName" "RuiPanel"
+ "wide" "40"
+ "tall" "40"
+ "xpos" "2"
+ "ypos" "-655"
+ "rui" "ui/knowledgebase_panel.rpak"
+ "visible" "1"
+ "zpos" "-1"
+ "pin_to_sibling" "LabelDetails"
+ "pin_corner_to_sibling" "TOP_RIGHT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "BtnModListSlider"
+ {
+ "ControlName" "RuiButton"
+ "InheritProperties" "RuiSmallButton"
+ // labelText "V"
+ "wide" "40"
+ "tall" "420"
+ "xpos" "2"
+ "ypos" "-40"
+ "zpos" "0"
+ "image" "vgui/hud/white"
+ "drawColor" "255 255 255 128"
+ "pin_to_sibling" "LabelDetails"
+ "pin_corner_to_sibling" "TOP_RIGHT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "BtnModListSliderPanel"
+ {
+ "ControlName" "RuiPanel"
+ "wide" "40"
+ "tall" "420"
+ "xpos" "2"
+ "ypos" "-40"
+ "rui" "ui/knowledgebase_panel.rpak"
+ "visible" "1"
+ "zpos" "-1"
+ "pin_to_sibling" "LabelDetails"
+ "pin_corner_to_sibling" "TOP_RIGHT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ // sh_menu_models.gnut has a global function which gets called when
+ // left mouse button gets called while hovering and has mouse
+ // deltaX; deltaY which we can yoink for ourselfes
+ "MouseMovementCapture"
+ {
+ "ControlName" "CMouseMovementCapturePanel"
+ "wide" "40"
+ "tall" "604"
+ "xpos" "2"
+ "ypos" "-40"
+ "zpos" "1"
+ "pin_to_sibling" "LabelDetails"
+ "pin_corner_to_sibling" "TOP_RIGHT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "ButtonTooltip"
+ {
+ "ControlName" "CNestedPanel"
+ "InheritProperties" "ButtonTooltip"
+ }
+ // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ "FooterButtons"
+ {
+ "ControlName" "CNestedPanel"
+ "InheritProperties" "FooterButtons"
+ }
+ }
+}
diff --git a/Northstar.Client/mod/resource/ui/menus/modlist.menu b/Northstar.Client/mod/resource/ui/menus/modlist.menu
index ffe9a257..bd350a33 100644
--- a/Northstar.Client/mod/resource/ui/menus/modlist.menu
+++ b/Northstar.Client/mod/resource/ui/menus/modlist.menu
@@ -1,4 +1,4 @@
-resource/ui/menus/mods_browse.menu
+resource/ui/menus/modlist.menu
{
menu
{
@@ -18,497 +18,654 @@ resource/ui/menus/mods_browse.menu
Vignette
{
- ControlName ImagePanel
- InheritProperties MenuVignette
+ ControlName ImagePanel
+ InheritProperties MenuVignette
}
Title
{
- ControlName Label
- InheritProperties MenuTitle
- labelText "#MENU_TITLE_MODS"
+ ControlName Label
+ InheritProperties MenuTitle
+ labelText "#MENU_TITLE_MODS"
}
ImgTopBar
{
- ControlName ImagePanel
- InheritProperties MenuTopBar
+ ControlName ImagePanel
+ InheritProperties MenuTopBar
}
-
+
ButtonRowAnchor
{
- ControlName Label
- labelText ""
+ ControlName Label
+ labelText ""
- xpos 120
- ypos 160
+ xpos 120
+ ypos 160
}
-
+
FilterButtonsRowAnchor
{
- ControlName Label
- labelText ""
+ ControlName Label
+ labelText ""
- xpos 90
- ypos 848
+ xpos 90
+ ypos 848
}
-
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// PANELS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- BtnMod1
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 0
- navUp BtnMod15
- navDown BtnMod2
-
- pin_to_sibling ButtonRowAnchor
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_LEFT
- }
- BtnMod2
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 1
- pin_to_sibling BtnMod1
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod1
- navDown BtnMod3
- }
- BtnMod3
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 2
- pin_to_sibling BtnMod2
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod2
- navDown BtnMod4
- }
- BtnMod4
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 3
- pin_to_sibling BtnMod3
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- //ypos 11
- navUp BtnMod3
- navDown BtnMod5
- }
- BtnMod5
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 4
- pin_to_sibling BtnMod4
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod4
- navDown BtnMod6
- }
- BtnMod6
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 5
- pin_to_sibling BtnMod5
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod5
- navDown BtnMod7
- }
- BtnMod7
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 6
- pin_to_sibling BtnMod6
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod6
- navDown BtnMod8
- }
- BtnMod8
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 7
- pin_to_sibling BtnMod7
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod7
- navDown BtnMod9
- }
- BtnMod9
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 8
- pin_to_sibling BtnMod8
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod8
- navDown BtnMod10
- }
- BtnMod10
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 9
- pin_to_sibling BtnMod9
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod9
- navDown BtnMod11
- }
- BtnMod11
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 10
- pin_to_sibling BtnMod10
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod10
- navDown BtnMod12
- }
- BtnMod12
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 11
- pin_to_sibling BtnMod11
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod11
- navDown BtnMod13
- }
- BtnMod13
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 12
- pin_to_sibling BtnMod12
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod12
- navDown BtnMod14
- }
- BtnMod14
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 13
- pin_to_sibling BtnMod13
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod13
- navDown BtnMod15
- }
- BtnMod15
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 14
- pin_to_sibling BtnMod14
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod14
- navDown BtnMod1
- }
- BtnMod16
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 15
- pin_to_sibling BtnMod15
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod15
- navDown BtnMod17
- }
- BtnMod17
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- classname ModButton
- scriptID 16
- pin_to_sibling BtnMod16
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- navUp BtnMod16
- navDown BtnMod18
+ Panel1
+ {
+ ControlName CNestedPanel
+ classname ModSelectorPanel
+ scriptID 1
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling ButtonRowAnchor
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel2
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 2
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel1
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel3
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 3
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel2
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel4
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 4
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel3
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel5
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 5
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel4
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel6
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 6
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel5
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel7
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 7
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel6
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel8
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 8
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel7
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel9
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 9
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel8
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel10
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 10
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel9
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel11
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 11
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel10
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel12
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 12
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel11
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
}
+ Panel13
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 13
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel12
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel14
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 14
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel13
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ Panel15
+ {
+ ControlName "CNestedPanel"
+ classname ModSelectorPanel
+ scriptID 15
+
+ controlSettingsFile "resource/ui/menus/panels/modlist_settings.res"
+ wide %100
+ tall 45
+
+ pin_to_sibling Panel14
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
+// FILTERS
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
FilterPanel
{
- ControlName RuiPanel
- wide 800
- tall 112
- xpos -8
- classname FilterPanelChild
-
- rui "ui/knowledgebase_panel.rpak"
+ ControlName RuiPanel
+ classname FilterPanelChild
+
+ wide 800
+ tall 112
+ xpos -8
+ zpos -1
- visible 1
- zpos -1
+ rui "ui/knowledgebase_panel.rpak"
+ visible 1
- pin_to_sibling FilterButtonsRowAnchor
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_LEFT
+ pin_to_sibling FilterButtonsRowAnchor
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner TOP_LEFT
}
-
+
BtnSearchLabel
{
- ControlName RuiButton
- InheritProperties RuiSmallButton
- labelText "#SEARCHBAR_LABEL"
- textAlignment west
- classname FilterPanelChild
-
- wide 500
- xpos -23
- ypos -16
-
- wrap 1
- visible 1
- zpos 0
-
- pin_to_sibling FilterButtonsRowAnchor
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_LEFT
- }
-
+ ControlName RuiButton
+ InheritProperties RuiSmallButton
+ classname FilterPanelChild
+
+ labelText #SEARCHBAR_LABEL
+ textAlignment west
+
+ wide 500
+ xpos -23
+ ypos -16
+ zpos 0
+ wrap 1
+ visible 1
+
+
+
+ pin_to_sibling FilterButtonsRowAnchor
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
BtnModsSearch
{
- ControlName TextEntry
- classname FilterPanelChild
- zpos 100 // This works around input weirdness when the control is constructed by code instead of VGUI blackbox.
- xpos -400
- ypos -5
- wide 390
- tall 30
- textHidden 0
- editable 1
- font Default_21
- allowRightClickMenu 0
- allowSpecialCharacters 0
- unicode 0
-
- pin_to_sibling BtnSearchLabel
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_RIGHT
+ ControlName TextEntry
+ classname FilterPanelChild
+
+ zpos 100 // This works around input weirdness when the control is constructed by code instead of VGUI blackbox.
+ xpos -400
+ ypos -5
+ wide 390
+ tall 30
+
+ textHidden 0 // Why?
+ editable 1
+ font Default_21
+
+ allowRightClickMenu 0
+ allowSpecialCharacters 0
+ unicode 1
+
+ pin_to_sibling BtnSearchLabel
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner TOP_RIGHT
}
-
+
SwtBtnShowFilter
{
- ControlName RuiButton
- InheritProperties SwitchButton
- labelText "#SHOW"
- ConVar "filter_mods"
- classname FilterPanelChild
+ ControlName RuiButton
+ InheritProperties SwitchButton
+ classname FilterPanelChild
+
+ labelText "#SHOW"
+ ConVar "filter_mods"
wide 500
-
+
list
{
- "#SHOW_ALL" 0
- "#SHOW_ONLY_ENABLED" 1
- "#SHOW_ONLY_DISABLED" 2
+ "#SHOW_ALL" 0
+ "#SHOW_ONLY_ENABLED" 1
+ "#SHOW_ONLY_DISABLED" 2
+ "#SHOW_ONLY_NOT_REQUIRED" 3
+ "#SHOW_ONLY_REQUIRED" 4
}
-
- pin_to_sibling BtnSearchLabel
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
+
+ pin_to_sibling BtnSearchLabel
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
}
-
+
+ BtnListReverse
+ {
+ ControlName RuiButton
+ InheritProperties SwitchButton
+ classname FilterPanelChild
+
+ xpos -15
+ ypos -15
+
+ labelText "Reverse"
+ ConVar "modlist_reverse"
+ wide 260
+
+ list
+ {
+ "low first" 0
+ "high first" 1
+ }
+
+ pin_to_sibling FilterPanel
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner TOP_RIGHT
+ }
+
BtnFiltersClear
{
- ControlName RuiButton
- InheritProperties RuiSmallButton
- labelText "#CLEAR_FILTERS"
- classname FilterPanelChild
- wide 100
- xpos -15
- ypos -55
- zpos 90
-
- scriptID 999
-
- pin_to_sibling FilterPanel
- pin_corner_to_sibling TOP_RIGHT
- pin_to_sibling_corner BOTTOM_RIGHT
+ ControlName RuiButton
+ InheritProperties RuiSmallButton
+ classname FilterPanelChild
+
+ labelText "#CLEAR_FILTERS"
+ wide 100
+ xpos -15
+ ypos -55
+ zpos 90
+
+ pin_to_sibling FilterPanel
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner BOTTOM_RIGHT
}
-
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// MOD INFO
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ModButtonsPanel
+ {
+ ControlName RuiPanel
+ classname FilterPanelChild
+
+ wide 950
+ tall 112
+
+ rui "ui/knowledgebase_panel.rpak"
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP
+ pin_to_sibling_corner BOTTOM
+ }
+
+ HideCVButton
+ {
+ ControlName RuiButton
+ InheritProperties SwitchButton
+
+ labelText "ConVars"
+ ConVar "modlist_show_convars"
+ wide 300
+
+ list
+ {
+ "Hidden" 0
+ "Shown" 1
+ }
+
+ pin_to_sibling ModButtonsPanel
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
+ ModPageButton
+ {
+ ControlName RuiButton
+ InheritProperties RuiSmallButton
+
+ textAlignment west
+ visible 0
+
+ pin_to_sibling HideCVButton
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ WarningLegendImage
+ {
+ ControlName RuiPanel
+
+ rui ui/basic_image.rpak
+ wide 30
+ tall 30
+ xpos -10
+ ypos -5
+ visible 0
+
+ pin_to_sibling ModButtonsPanel
+ pin_corner_to_sibling BOTTOM_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ WarningLegendLabel
+ {
+ ControlName Label
+
+ labelText "#MOD_REQUIRED_WARNING"
+ auto_wide_tocontents 1
+ tall 50
+ visible 0
+
+ pin_to_sibling WarningLegendImage
+ pin_corner_to_sibling LEFT
+ pin_to_sibling_corner RIGHT
+ }
+
LabelDetails
{
- ControlName RuiPanel
- xpos 900
- ypos 160
- tall 800
- wide 950
- rui "ui/knowledgebase_panel.rpak"
- wrap 1
- visible 1
- zpos 1
+ ControlName RuiPanel
+
+ xpos 900
+ ypos 160
+ zpos 1
+
+ tall 688
+ wide 950
+ rui "ui/knowledgebase_panel.rpak"
+ wrap 1
+ visible 1
}
-
+
+ ModEnabledBar
+ {
+ ControlName RuiPanel
+
+ rui ui/basic_image.rpak
+ wide 950
+ tall 7
+ zpos 2
+ visible 0
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// SLIDER
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
BtnModListUpArrow
{
- ControlName RuiButton
- InheritProperties RuiSmallButton
- //labelText "A"
- wide 40
- tall 40
- xpos 2
- ypos 2
-
- image "vgui/hud/white"
- drawColor "255 255 255 128"
-
- pin_to_sibling LabelDetails
- pin_corner_to_sibling TOP_RIGHT
- pin_to_sibling_corner TOP_LEFT
- }
-
+ ControlName RuiButton
+ InheritProperties RuiSmallButton
+
+ wide 40
+ tall 40
+ xpos 2
+ ypos 2
+
+ image "vgui/hud/white"
+ drawColor "255 255 255 128"
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
BtnModListUpArrowPanel
{
- ControlName RuiPanel
- wide 40
- tall 40
- xpos 2
- ypos 2
-
- rui "ui/knowledgebase_panel.rpak"
-
- visible 1
- zpos -1
-
- pin_to_sibling LabelDetails
- pin_corner_to_sibling TOP_RIGHT
- pin_to_sibling_corner TOP_LEFT
- }
-
+ ControlName RuiPanel
+
+ wide 40
+ tall 40
+ xpos 2
+ ypos 2
+
+ rui "ui/knowledgebase_panel.rpak"
+ visible 1
+ zpos -1
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
BtnModListDownArrow
{
- ControlName RuiButton
- InheritProperties RuiSmallButton
- //labelText "V"
- wide 40
- tall 40
- xpos 2
- ypos -646
-
- image "vgui/hud/white"
- drawColor "255 255 255 128"
-
- pin_to_sibling LabelDetails
- pin_corner_to_sibling TOP_RIGHT
- pin_to_sibling_corner TOP_LEFT
- }
-
+ ControlName RuiButton
+ InheritProperties RuiSmallButton
+
+ wide 40
+ tall 40
+ xpos 2
+ ypos -646
+
+ image "vgui/hud/white"
+ drawColor "255 255 255 128"
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
BtnModListDownArrowPanel
{
- ControlName RuiPanel
- wide 40
- tall 40
- xpos 2
- ypos -646
-
- rui "ui/knowledgebase_panel.rpak"
-
- visible 1
- zpos -1
-
- pin_to_sibling LabelDetails
- pin_corner_to_sibling TOP_RIGHT
- pin_to_sibling_corner TOP_LEFT
- }
-
+ ControlName RuiPanel
+
+ wide 40
+ tall 40
+ xpos 2
+ ypos -646
+ zpos -1
+
+ rui "ui/knowledgebase_panel.rpak"
+ visible 1
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
BtnModListSlider
{
- ControlName RuiButton
- InheritProperties RuiSmallButton
- //labelText "V"
- wide 40
- tall 604
- xpos 2
- ypos -40
- zpos 0
-
- image "vgui/hud/white"
- drawColor "255 255 255 128"
-
- pin_to_sibling LabelDetails
- pin_corner_to_sibling TOP_RIGHT
- pin_to_sibling_corner TOP_LEFT
- }
-
+ ControlName RuiButton
+ InheritProperties RuiSmallButton
+
+ wide 40
+ tall 604
+ xpos 2
+ ypos -40
+ zpos 0
+
+ image "vgui/hud/white"
+ drawColor "255 255 255 128"
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
BtnModListSliderPanel
{
- ControlName RuiPanel
- wide 40
- tall 604
- xpos 2
- ypos -40
-
- rui "ui/knowledgebase_panel.rpak"
-
- visible 1
- zpos -1
-
- pin_to_sibling LabelDetails
- pin_corner_to_sibling TOP_RIGHT
- pin_to_sibling_corner TOP_LEFT
- }
-
+ ControlName RuiPanel
+
+ wide 40
+ tall 604
+ xpos 2
+ ypos -40
+ zpos -1
+
+ rui "ui/knowledgebase_panel.rpak"
+ visible 1
+
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner TOP_LEFT
+ }
+
// sh_menu_models.gnut has a global function which gets called when
// left mouse button gets called while hovering and has mouse
// deltaX; deltaY which we can yoink for ourselfes
MouseMovementCapture
{
- ControlName CMouseMovementCapturePanel
- wide 40
- tall 604
- xpos 2
- ypos -40
- zpos 1
-
- pin_to_sibling LabelDetails
- pin_corner_to_sibling TOP_RIGHT
- pin_to_sibling_corner TOP_LEFT
+ ControlName CMouseMovementCapturePanel
+
+ wide 40
+ tall 604
+ xpos 2
+ ypos -40
+ zpos 1
+
+ pin_to_sibling LabelDetails
+ pin_corner_to_sibling TOP_RIGHT
+ pin_to_sibling_corner TOP_LEFT
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FooterButtons
{
- ControlName CNestedPanel
- InheritProperties FooterButtons
+ ControlName CNestedPanel
+ InheritProperties FooterButtons
}
}
}
diff --git a/Northstar.Client/mod/resource/ui/menus/panels/mod_setting.res b/Northstar.Client/mod/resource/ui/menus/panels/mod_setting.res
new file mode 100644
index 00000000..92dce922
--- /dev/null
+++ b/Northstar.Client/mod/resource/ui/menus/panels/mod_setting.res
@@ -0,0 +1,183 @@
+"resource/ui/menus/panels/mod_setting.res"
+{
+ "FULL"
+ {
+ "ControlName" "Label"
+ "classname" "ConnectingHUD"
+ "xpos" "0"
+ "ypos" "0"
+ "zpos" "99"
+ "wide" "1200"
+ "tall" "45"
+ "labelText" ""
+ "bgcolor_override" "0 0 0 0"
+ "visible" "0"
+ "paintbackground" "1"
+ }
+ "BtnMod"
+ {
+ "ControlName" "Label"
+ "InheritProperties" "RuiSmallButton"
+ "labelText" "Mod"
+ //"auto_wide_tocontents" "1"
+ "navRight" "EnumSelectButton"
+ "navLeft" "TextEntrySetting"
+ "wide" "390"
+ "tall" "45"
+ }
+ // we're getting to the top of this :)
+ "TopLine"
+ {
+ "ControlName" "ImagePanel"
+ "InheritProperties" "MenuTopBar"
+ "ypos" "0"
+ "wide" "%100"
+ "pin_to_sibling" "BtnMod"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "TOP_LEFT"
+ }
+ "ModTitle"
+ {
+ "ControlName" "Label"
+ "InheritProperties" "RuiSmallButton"
+ "labelText" "Mod"
+ "font" "DefaultBold_43"
+ //"auto_wide_tocontents" "1"
+ "zpos" "-999"
+ "textAlignment" "center"
+ "navRight" "EnumSelectButton"
+ "navLeft" "TextEntrySetting"
+ "wide" "1200"
+ "tall" "45"
+
+ }
+ "Slider"
+ {
+ "ControlName" "SliderControl"
+ //"InheritProperties" "RuiSmallButton"
+ minValue 0.0
+ maxValue 2.0
+ stepSize 0.05
+ "pin_to_sibling" "BtnMod"
+ "pin_corner_to_sibling" "TOP_LEFT"
+ "pin_to_sibling_corner" "TOP_RIGHT"
+ "navRight" "ResetModToDefault"
+ "navLeft" "TextEntrySetting"
+ //isValueClampedToStepSize 1
+ BtnDropButton
+ {
+ ControlName RuiButton
+ //InheritProperties WideButton
+ style SliderButton
+ "wide" "320"
+ "tall" "45"
+ "labelText" ""
+ "auto_wide_tocontents" "0"
+ }
+ "wide" "320"
+ "tall" "45"
+ }
+ "EnumSelectButton"
+ {
+ "ControlName" "RuiButton"
+ "InheritProperties" "RuiSmallButton"
+ "style" "DialogListButton"
+ "labelText" ""
+ "zpos" "4"
+ "wide" "225"
+ "tall" "45"
+ //"xpos" "10"
+ "scriptID" "0"
+ "pin_to_sibling" "FULL"
+ "pin_corner_to_sibling" "RIGHT"
+ "pin_to_sibling_corner" "RIGHT"
+ "navLeft" "ResetModToDefault"
+ "navRight" "TextEntrySetting"
+ }
+ "ResetModToDefault"
+ {
+ "ControlName" "RuiButton"
+ "InheritProperties" "RuiSmallButton"
+ "labelText" ""
+ "zpos" "0"
+ "xpos" "10"
+ "wide" "45"
+ "tall" "45"
+ "scriptID" "0"
+ "pin_to_sibling" "EnumSelectButton"
+ "pin_corner_to_sibling" "RIGHT"
+ "pin_to_sibling_corner" "LEFT"
+ "navLeft" "Slider"
+ "navRight" "TextEntrySetting"
+ }
+ "ResetModImage"
+ {
+ "ControlName" "ImagePanel"
+ "image" "vgui/reset"
+ "scaleImage" "1"
+ "drawColor" "180 180 180 255" // vanilla label color
+ "visible" "0"
+ "wide" "30"
+ "tall" "30"
+ "enabled" "0"
+
+ "pin_to_sibling" "ResetModToDefault"
+ "pin_corner_to_sibling" "CENTER"
+ "pin_to_sibling_corner" "CENTER"
+ }
+ "OpenCustomMenu"
+ {
+ "ControlName" "RuiButton"
+ "InheritProperties" "RuiSmallButton"
+ "labelText" "Open"
+ //"auto_wide_tocontents" "1"
+ "zpos" "4"
+ "wide" "1200"
+ "textAlignment" "center"
+ //"font" "Default_41"
+ //"xpos" "10"
+ "tall" "40"
+ "scriptID" "0"
+ "visible" "0"
+ "pin_to_sibling" "FULL"
+ "pin_corner_to_sibling" "RIGHT"
+ "pin_to_sibling_corner" "RIGHT"
+ "navLeft" "TextEntrySetting"
+ "navRight" "TextEntrySetting"
+ }
+ "TextEntrySetting"
+ {
+ "ControlName" "TextEntry"
+ "classname" "MatchSettingTextEntry"
+ //"xpos" "-35"
+ //"ypos" "-5"
+ "zpos" "100" // This works around input weirdness when the control is constructed by code instead of VGUI blackbox.
+ "wide" "160"
+ "tall" "30"
+ "scriptID" "0"
+ "textHidden" "0"
+ "editable" "1"
+ // NumericInputOnly 1
+ "font" "Default_21"
+ "allowRightClickMenu" "0"
+ "allowSpecialCharacters" "1"
+ "unicode" "0"
+ "pin_to_sibling" "EnumSelectButton"
+ "pin_corner_to_sibling" "CENTER"
+ "pin_to_sibling_corner" "CENTER"
+ "navLeft" "EnumSelectButton"
+ "navRight" "EnumSelectButton"
+ }
+ // we're getting to the bottom of this :)
+ "BottomLine"
+ {
+ "ControlName" "ImagePanel"
+ "InheritProperties" "MenuTopBar"
+ "ypos" "5"
+ "wide" "%100"
+ //"tall" "0"
+ "pin_to_sibling" "FULL"
+ "pin_corner_to_sibling" "BOTTOM_LEFT"
+ "pin_to_sibling_corner" "BOTTOM_LEFT"
+ }
+}
diff --git a/Northstar.Client/mod/resource/ui/menus/panels/modlist_settings.res b/Northstar.Client/mod/resource/ui/menus/panels/modlist_settings.res
new file mode 100644
index 00000000..cd596238
--- /dev/null
+++ b/Northstar.Client/mod/resource/ui/menus/panels/modlist_settings.res
@@ -0,0 +1,79 @@
+resource/ui/menus/panels/modlist_setting.res
+{
+ BtnMod
+ {
+ ControlName RuiButton
+ InheritProperties RuiSmallButton
+ classname ModButton
+ labelText "please show up"
+
+ pin_to_sibling ControlBox
+ pin_corner_to_sibling LEFT
+ pin_to_sibling_corner RIGHT
+ }
+
+ Header
+ {
+ ControlName Label
+ wide 400
+ labelText "labelText"
+
+ pin_to_sibling ControlBox
+ pin_corner_to_sibling LEFT
+ pin_to_sibling_corner RIGHT
+ }
+
+ ControlBox
+ {
+ ControlName RuiPanel
+ classname ControlBox
+
+ tall 30
+ wide 5
+ ypos 5
+ rui "ui/basic_image.rpak"
+
+ pin_corner_to_sibling LEFT
+ pin_to_sibling_corner LEFT
+ }
+
+ BottomLine
+ {
+ ControlName ImagePanel
+ InheritProperties MenuTopBar
+ ypos 0
+ wide %50
+
+ pin_to_sibling BtnMod
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+
+ WarningImage
+ {
+ ControlName RuiPanel
+
+ rui ui/basic_image.rpak
+ wide 30
+ tall 30
+ visible 0
+
+ pin_to_sibling BtnMod
+ pin_corner_to_sibling LEFT
+ pin_to_sibling_corner RIGHT
+ }
+
+ EnabledImage
+ {
+ ControlName RuiPanel
+
+ rui ui/basic_image.rpak
+ wide 30
+ tall 30
+ visible 0
+
+ pin_to_sibling BtnMod
+ pin_corner_to_sibling RIGHT
+ pin_to_sibling_corner LEFT
+ }
+}
diff --git a/Northstar.Client/mod/resource/ui/menus/server_browser.menu b/Northstar.Client/mod/resource/ui/menus/server_browser.menu
index 89fb951d..4a84a714 100644
--- a/Northstar.Client/mod/resource/ui/menus/server_browser.menu
+++ b/Northstar.Client/mod/resource/ui/menus/server_browser.menu
@@ -419,6 +419,252 @@ resource/ui/menus/mods_browse.menu
pin_corner_to_sibling TOP_LEFT
pin_to_sibling_corner BOTTOM_LEFT
}
+
+ // Region
+ BtnServerRegionTab
+ {
+ ControlName RuiButton
+ InheritProperties RuiSmallButton
+ labelText "#REGION_COLUMN"
+ wide 110
+ xpos -4
+ ypos -1
+
+ scriptID 999
+
+ pin_to_sibling BtnServerPasswordProtectedTab
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner TOP_RIGHT
+ navDown BtnServer1
+ navRight BtnServerNameTab
+ navUp BtnFiltersClear
+ }
+
+ BtnServerRegion1
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer1
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion2
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer2
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion3
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer3
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion4
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer4
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion5
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer5
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion6
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer6
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion7
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer7
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion8
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer8
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion9
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer9
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion10
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer10
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion11
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer11
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion12
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer12
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion13
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer13
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion14
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer14
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
+ BtnServerRegion15
+ {
+ ControlName Label
+ labelText ""
+ classname Serverregion
+ textAlignment center
+ wide 110
+ tall 44
+ ypos -42
+ xpos 0
+
+ pin_to_sibling BtnServer15
+ pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling_corner BOTTOM_LEFT
+ }
// Name
BtnServerNameTab
@@ -427,16 +673,16 @@ resource/ui/menus/mods_browse.menu
InheritProperties RuiSmallButton
labelText "#SERVERS_COLUMN"
wide 600
- xpos -4
- ypos -1
+ xpos 4
scriptID 999
- pin_to_sibling BtnServerPasswordProtectedTab
+ pin_to_sibling BtnServerRegionTab
pin_corner_to_sibling TOP_LEFT
pin_to_sibling_corner TOP_RIGHT
navUp BtnFiltersClear
navDown BtnServer1
+ navLeft BtnServerRegionTab
navRight BtnServerPlayersTab
}
@@ -448,8 +694,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
interactive false
@@ -465,8 +711,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer2
pin_corner_to_sibling TOP_LEFT
@@ -480,8 +726,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer3
pin_corner_to_sibling TOP_LEFT
@@ -495,8 +741,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer4
pin_corner_to_sibling TOP_LEFT
@@ -510,8 +756,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer5
pin_corner_to_sibling TOP_LEFT
@@ -525,8 +771,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer6
pin_corner_to_sibling TOP_LEFT
@@ -540,8 +786,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer7
pin_corner_to_sibling TOP_LEFT
@@ -555,8 +801,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer8
pin_corner_to_sibling TOP_LEFT
@@ -570,8 +816,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer9
pin_corner_to_sibling TOP_LEFT
@@ -585,8 +831,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer10
pin_corner_to_sibling TOP_LEFT
@@ -600,8 +846,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer11
pin_corner_to_sibling TOP_LEFT
@@ -615,8 +861,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer12
pin_corner_to_sibling TOP_LEFT
@@ -630,8 +876,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer13
pin_corner_to_sibling TOP_LEFT
@@ -645,8 +891,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer14
pin_corner_to_sibling TOP_LEFT
@@ -660,8 +906,8 @@ resource/ui/menus/mods_browse.menu
classname ServerName
wide 586
tall 44
- ypos -44
- xpos -14
+ ypos -42
+ xpos -122
pin_to_sibling BtnServer15
pin_corner_to_sibling TOP_LEFT
@@ -697,8 +943,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer1
pin_corner_to_sibling TOP_LEFT
@@ -712,8 +958,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer2
pin_corner_to_sibling TOP_LEFT
@@ -727,8 +973,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer3
pin_corner_to_sibling TOP_LEFT
@@ -742,8 +988,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer4
pin_corner_to_sibling TOP_LEFT
@@ -757,8 +1003,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer5
pin_corner_to_sibling TOP_LEFT
@@ -772,8 +1018,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer6
pin_corner_to_sibling TOP_LEFT
@@ -787,8 +1033,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer7
pin_corner_to_sibling TOP_LEFT
@@ -802,8 +1048,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer8
pin_corner_to_sibling TOP_LEFT
@@ -817,8 +1063,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer9
pin_corner_to_sibling TOP_LEFT
@@ -832,8 +1078,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer10
pin_corner_to_sibling TOP_LEFT
@@ -847,8 +1093,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer11
pin_corner_to_sibling TOP_LEFT
@@ -862,8 +1108,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer12
pin_corner_to_sibling TOP_LEFT
@@ -877,8 +1123,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer13
pin_corner_to_sibling TOP_LEFT
@@ -892,8 +1138,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer14
pin_corner_to_sibling TOP_LEFT
@@ -907,8 +1153,8 @@ resource/ui/menus/mods_browse.menu
textAlignment center
wide 104
tall 44
- ypos -44
- xpos -600
+ ypos -42
+ xpos -718
pin_to_sibling BtnServer15
pin_corner_to_sibling TOP_LEFT
@@ -944,8 +1190,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer1
pin_corner_to_sibling TOP_LEFT
@@ -959,8 +1205,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer2
pin_corner_to_sibling TOP_LEFT
@@ -974,8 +1220,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer3
pin_corner_to_sibling TOP_LEFT
@@ -989,8 +1235,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer4
pin_corner_to_sibling TOP_LEFT
@@ -1004,8 +1250,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer5
pin_corner_to_sibling TOP_LEFT
@@ -1019,8 +1265,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer6
pin_corner_to_sibling TOP_LEFT
@@ -1034,8 +1280,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer7
pin_corner_to_sibling TOP_LEFT
@@ -1049,8 +1295,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer8
pin_corner_to_sibling TOP_LEFT
@@ -1064,8 +1310,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer9
pin_corner_to_sibling TOP_LEFT
@@ -1079,8 +1325,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer10
pin_corner_to_sibling TOP_LEFT
@@ -1094,8 +1340,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer11
pin_corner_to_sibling TOP_LEFT
@@ -1109,8 +1355,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer12
pin_corner_to_sibling TOP_LEFT
@@ -1124,8 +1370,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer13
pin_corner_to_sibling TOP_LEFT
@@ -1139,8 +1385,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer14
pin_corner_to_sibling TOP_LEFT
@@ -1154,8 +1400,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -709
+ ypos -42
+ xpos -828
pin_to_sibling BtnServer15
pin_corner_to_sibling TOP_LEFT
@@ -1178,7 +1424,7 @@ resource/ui/menus/mods_browse.menu
pin_to_sibling_corner TOP_RIGHT
navDown BtnServer1
navLeft BtnServerMapTab
- navRight BtnServerLatencyTab
+ navRight BtnServerJoin
navUp BtnFiltersClear
}
@@ -1190,8 +1436,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer1
pin_corner_to_sibling TOP_LEFT
@@ -1205,8 +1451,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer2
pin_corner_to_sibling TOP_LEFT
@@ -1220,8 +1466,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer3
pin_corner_to_sibling TOP_LEFT
@@ -1235,8 +1481,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer4
pin_corner_to_sibling TOP_LEFT
@@ -1250,8 +1496,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer5
pin_corner_to_sibling TOP_LEFT
@@ -1265,8 +1511,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer6
pin_corner_to_sibling TOP_LEFT
@@ -1280,8 +1526,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer7
pin_corner_to_sibling TOP_LEFT
@@ -1295,8 +1541,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer8
pin_corner_to_sibling TOP_LEFT
@@ -1310,8 +1556,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer9
pin_corner_to_sibling TOP_LEFT
@@ -1325,8 +1571,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer10
pin_corner_to_sibling TOP_LEFT
@@ -1340,8 +1586,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer11
pin_corner_to_sibling TOP_LEFT
@@ -1355,8 +1601,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer12
pin_corner_to_sibling TOP_LEFT
@@ -1370,8 +1616,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer13
pin_corner_to_sibling TOP_LEFT
@@ -1385,8 +1631,8 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer14
pin_corner_to_sibling TOP_LEFT
@@ -1400,366 +1646,120 @@ resource/ui/menus/mods_browse.menu
wide 140
textAlignment center
tall 44
- ypos -44
- xpos -860
+ ypos -42
+ xpos -972
pin_to_sibling BtnServer15
pin_corner_to_sibling TOP_LEFT
pin_to_sibling_corner BOTTOM_LEFT
}
- // Latency
- BtnServerLatencyTab
- {
- ControlName RuiButton
- InheritProperties RuiSmallButton
- labelText "#LATENCY_COLUMN"
- wide 110
- xpos 4
-
- scriptID 999
-
- pin_to_sibling BtnServerGamemodeTab
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_RIGHT
- navDown BtnServer1
- navLeft BtnServerGamemodeTab
- navRight BtnServerJoin
- navUp BtnFiltersClear
- }
-
- BtnServerLatency1
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
-
- pin_to_sibling BtnServer1
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency2
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
-
- pin_to_sibling BtnServer2
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency3
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
+ // Dividers:
- pin_to_sibling BtnServer3
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency4
+ // Y
+ YDivider0
{
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
+ ControlName ImagePanel
+ wide 2
+ tall 641
+ visible 1
+ image "vgui/hud/white"
+ drawColor "160 157 149 255"
+ scaleImage 1
+ xpos 3
+ ypos -1
- pin_to_sibling BtnServer4
+ pin_to_sibling BtnServerNameTab
pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
+ pin_to_sibling_corner TOP_LEFT
}
- BtnServerLatency5
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
- pin_to_sibling BtnServer5
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency6
+ YDivider1
{
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
+ ControlName ImagePanel
+ wide 2
+ tall 641
+ visible 1
+ image "vgui/hud/white"
+ drawColor "160 157 149 255"
+ scaleImage 1
+ xpos 3
+ ypos -1
- pin_to_sibling BtnServer6
+ pin_to_sibling BtnServerPlayersTab
pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
+ pin_to_sibling_corner TOP_LEFT
}
- BtnServerLatency7
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
- pin_to_sibling BtnServer7
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency8
+ YDivider2
{
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
+ ControlName ImagePanel
+ wide 2
+ tall 641
+ visible 1
+ image "vgui/hud/white"
+ drawColor "160 157 149 255"
+ scaleImage 1
+ xpos 3
+ ypos -1
- pin_to_sibling BtnServer8
+ pin_to_sibling BtnServerMapTab
pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
+ pin_to_sibling_corner TOP_LEFT
}
- BtnServerLatency9
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
- pin_to_sibling BtnServer9
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency10
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
-
- pin_to_sibling BtnServer10
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency11
+ YDivider3
{
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
+ ControlName ImagePanel
+ wide 2
+ tall 641
+ visible 1
+ image "vgui/hud/white"
+ drawColor "160 157 149 255"
+ scaleImage 1
+ xpos 3
+ ypos -1
- pin_to_sibling BtnServer11
+ pin_to_sibling BtnServerGamemodeTab
pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
+ pin_to_sibling_corner TOP_LEFT
}
- BtnServerLatency12
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
- pin_to_sibling BtnServer12
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency13
+ YDivider4
{
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
+ ControlName ImagePanel
+ wide 2
+ tall 641
+ visible 1
+ image "vgui/hud/white"
+ drawColor "160 157 149 255"
+ scaleImage 1
+ xpos 3
+ ypos -1
- pin_to_sibling BtnServer13
+ pin_to_sibling BtnServerRegionTab
pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
+ pin_to_sibling_corner TOP_LEFT
}
- BtnServerLatency14
- {
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
- pin_to_sibling BtnServer14
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
- BtnServerLatency15
+ // X
+ XDivider0
{
- ControlName Label
- labelText ""
- classname Serverlatency
- textAlignment center
- wide 110
- tall 44
- ypos -44
- xpos -1006
+ ControlName ImagePanel
+ wide 1150
+ tall 2
+ visible 1
+ image "vgui/hud/white"
+ drawColor "160 157 149 255"
+ scaleImage 1
+ ypos 3
+ xpos 37
- pin_to_sibling BtnServer15
- pin_corner_to_sibling TOP_LEFT
+ pin_to_sibling BtnServerRegionTab
+ pin_corner_to_sibling BOTTOM_LEFT
pin_to_sibling_corner BOTTOM_LEFT
}
- // Dividers:
-
- // Y
- YDivider0
- {
- ControlName ImagePanel
- wide 2
- tall 641
- visible 1
- image "vgui/hud/white"
- drawColor "160 157 149 255"
- scaleImage 1
- xpos 3
- ypos -1
-
- pin_to_sibling BtnServerNameTab
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_LEFT
- }
-
- YDivider1
- {
- ControlName ImagePanel
- wide 2
- tall 641
- visible 1
- image "vgui/hud/white"
- drawColor "160 157 149 255"
- scaleImage 1
- xpos 3
- ypos -1
-
- pin_to_sibling BtnServerPlayersTab
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_LEFT
- }
-
- YDivider2
- {
- ControlName ImagePanel
- wide 2
- tall 641
- visible 1
- image "vgui/hud/white"
- drawColor "160 157 149 255"
- scaleImage 1
- xpos 3
- ypos -1
-
- pin_to_sibling BtnServerMapTab
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_LEFT
- }
-
- YDivider3
- {
- ControlName ImagePanel
- wide 2
- tall 641
- visible 1
- image "vgui/hud/white"
- drawColor "160 157 149 255"
- scaleImage 1
- xpos 3
- ypos -1
-
- pin_to_sibling BtnServerGamemodeTab
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_LEFT
- }
-
- YDivider4
- {
- ControlName ImagePanel
- wide 2
- tall 641
- visible 1
- image "vgui/hud/white"
- drawColor "160 157 149 255"
- scaleImage 1
- xpos 3
- ypos -1
-
- pin_to_sibling BtnServerLatencyTab
- pin_corner_to_sibling TOP_LEFT
- pin_to_sibling_corner TOP_LEFT
- }
-
- // X
- XDivider0
- {
- ControlName ImagePanel
- wide 1150
- tall 2
- visible 1
- image "vgui/hud/white"
- drawColor "160 157 149 255"
- scaleImage 1
- ypos 3
- xpos 37
-
- pin_to_sibling BtnServerNameTab
- pin_corner_to_sibling BOTTOM_LEFT
- pin_to_sibling_corner BOTTOM_LEFT
- }
-
// List:
BtnServerDummmyTop {
ControlName RuiButton
@@ -2082,7 +2082,7 @@ resource/ui/menus/mods_browse.menu
wide 40
tall 562
xpos 2
- ypos -40
+ ypos -42
zpos 0
image "vgui/hud/white"
@@ -2099,7 +2099,7 @@ resource/ui/menus/mods_browse.menu
wide 40
tall 562
xpos 2
- ypos -40
+ ypos -42
rui "ui/control_options_description.rpak"
@@ -2120,7 +2120,7 @@ resource/ui/menus/mods_browse.menu
wide 40
tall 562
xpos 2
- ypos -40
+ ypos -42
zpos 1
pin_to_sibling ServerDetailsPanel
@@ -2197,7 +2197,7 @@ resource/ui/menus/mods_browse.menu
font Default_21
allowRightClickMenu 0
allowSpecialCharacters 0
- unicode 0
+ unicode 1
pin_to_sibling BtnSearchLabel
pin_corner_to_sibling TOP_LEFT
diff --git a/Northstar.Client/mod/scripts/kb_act.lst b/Northstar.Client/mod/scripts/kb_act.lst
deleted file mode 100644
index ace98222..00000000
--- a/Northstar.Client/mod/scripts/kb_act.lst
+++ /dev/null
@@ -1,54 +0,0 @@
-"blank" "=========================="
-"blank" "NORTHSTAR"
-"blank" "=========================="
-"toggleconsole" "Toggle Developer Console"
-"blank" "=========================="
-"blank" "#KEY_BINDINGS_HEADER_ACTIONS"
-"blank" "=========================="
-"+attack" "#FIRE"
-"+zoom" "#AIM_MODIFIER"
-"+toggle_zoom" "#TOGGLE_AIM_MODIFIER"
-"+reload" "#RELOAD"
-"+weaponCycle" "#SWITCH_WEAPONS_PILOT"
-"weaponSelectPrimary0" "#SWITCH_TO_WEAPON1"
-"weaponSelectPrimary1" "#SWITCH_TO_WEAPON2"
-"weaponSelectPrimary2" "#SWITCH_TO_WEAPON3"
-"+melee" "#MELEE"
-"+offhand0" "#ORDNANCE_GRENADE"
-"+offhand1" "#TACTICAL_ABILITY"
-"+offhand2" "#TITAN_UTILITY_BIND"
-"+use" "#USE_DISEMBARK"
-"+scriptCommand1" "#DISABLE_EJECT_SAFETY_TITAN"
-"+ability 1" "#SWITCH_TITAN_AI_MODE_PILOT"
-"+ability 6" "#KEYBINDING_MENU_BURN_CARD"
-"titan_loadout_select" "#TITAN_LOADOUT_SELECT"
-"blank" "=========================="
-"blank" "#KEY_BINDINGS_HEADER_MOVEMENT"
-"blank" "=========================="
-"+forward" "#MOVE_FORWARD"
-"+back" "#MOVE_BACK"
-"+moveleft" "#MOVE_LEFT"
-"+moveright" "#MOVE_RIGHT"
-"+speed" "#SPRINT"
-"+ability 3" "#JUMP_PILOT_DASH_TITAN"
-"+duck" "#CROUCH"
-"+toggle_duck" "#TOGGLE_CROUCH"
-"blank" "=========================="
-"blank" "#KEY_BINDINGS_HEADER_COMMUNICATION"
-"blank" "=========================="
-"+pushtotalk" "#PUSH_TO_TALK_KEY"
-"say" "#CHAT_MESSAGE"
-"say_team" "#TEAM_CHAT_MESSAGE"
-"blank" "=========================="
-"blank" "#KEY_BINDINGS_HEADER_SCOREBOARD"
-"blank" "=========================="
-"+showscores" "#TOGGLE_SCOREBOARD"
-"scoreboard_toggle_focus" "#SCOREBOARD_FOCUS"
-"scoreboard_up" "#SCOREBOARD_SCROLL_UP"
-"scoreboard_down" "#SCOREBOARD_SCROLL_DOWN"
-"scoreboard_profile" "#SCOREBOARD_VIEW_PROFILE"
-"scoreboard_mute" "#SCOREBOARD_MUTE"
-"blank" "=========================="
-"blank" "#KEY_BINDINGS_HEADER_MISCELLANEOUS"
-"blank" "=========================="
-"jpeg" "#SCREENSHOT"
diff --git a/Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut b/Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut
index 66a40cb0..db4865ee 100644
--- a/Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut
+++ b/Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut
@@ -1,7 +1,10 @@
untyped
global function AddCallback_OnReceivedSayTextMessage
-global function NSSetupChathooksClient
+
+// this is global due to squirrel bridge v3 making native not be able to find non-global funcs properly
+// temp fix (surely it will get replaced), do not use this function please (although there isnt rly a downside to it?)
+global function CHudChat_ProcessMessageStartThread
global struct ClClient_MessageStruct {
string message
@@ -11,6 +14,7 @@ global struct ClClient_MessageStruct {
bool isDead
bool isWhisper
bool shouldBlock
+ bool noServerTag
}
struct {
@@ -18,6 +22,10 @@ struct {
} NsCustomCallbacksClient
void function OnReceivedMessage(ClClient_MessageStruct localMessage) {
+
+ if ( IsWatchingReplay() && localMessage.player == null )
+ return
+
if (localMessage.player != null)
{
foreach (callbackFunc in NsCustomCallbacksClient.OnReceivedSayTextMessageCallbacks)
@@ -29,6 +37,7 @@ void function OnReceivedMessage(ClClient_MessageStruct localMessage) {
localMessage.isDead = returnStruct.isDead
localMessage.isWhisper = returnStruct.isWhisper
localMessage.shouldBlock = localMessage.shouldBlock || returnStruct.shouldBlock
+ localMessage.noServerTag = returnStruct.noServerTag
}
}
@@ -39,7 +48,13 @@ void function OnReceivedMessage(ClClient_MessageStruct localMessage) {
NSChatWriteRaw(1, "\n")
- if (localMessage.player == null) NSChatWrite(1, "\x1b[95m")
+ if (localMessage.player == null)
+ {
+ if (!localMessage.noServerTag || localMessage.isWhisper)
+ {
+ NSChatWrite(1, "\x1b[95m")
+ }
+ }
else
{
bool isFriendly = localMessage.player.GetTeam() == GetLocalClientPlayer().GetTeam()
@@ -54,15 +69,18 @@ void function OnReceivedMessage(ClClient_MessageStruct localMessage) {
if (localMessage.player == null)
{
- NSChatWriteRaw(1, Localize("#HUD_CHAT_SERVER_PREFIX") + " ")
+ if (!localMessage.noServerTag)
+ {
+ NSChatWriteRaw(1, Localize("#HUD_CHAT_SERVER_PREFIX") + " ")
+ }
}
- else {
+ else
+ {
NSChatWriteRaw(1, localMessage.playerName)
NSChatWriteRaw(1, ": ")
}
- NSChatWrite(1, "\x1b[0m")
-
+ if (localMessage.player != null || !localMessage.noServerTag || localMessage.isWhisper) NSChatWrite(1, "\x1b[0m")
NSChatWrite(1, localMessage.message)
}
@@ -87,16 +105,19 @@ void function CHudChat_OnReceivedSayTextMessageCallback(int fromPlayerIndex, str
fromPlayerName = fromPlayer.GetPlayerNameWithClanTag()
}
+ // Null player + isTeam == true: Server with no tag.
+
if (messageType == 0 || messageType == 1)
{
ClClient_MessageStruct localMessage
localMessage.message = message
localMessage.player = fromPlayer
localMessage.playerName = fromPlayerName
- localMessage.isTeam = isTeam
+ localMessage.isTeam = fromPlayer != null && isTeam
localMessage.isDead = isDead
localMessage.isWhisper = false
localMessage.shouldBlock = false
+ localMessage.noServerTag = fromPlayer == null && isTeam
OnReceivedMessage(localMessage)
return
}
@@ -107,10 +128,11 @@ void function CHudChat_OnReceivedSayTextMessageCallback(int fromPlayerIndex, str
localMessage.message = message
localMessage.player = fromPlayer
localMessage.playerName = fromPlayerName
- localMessage.isTeam = isTeam
+ localMessage.isTeam = fromPlayer != null && isTeam
localMessage.isDead = isDead
localMessage.isWhisper = true
localMessage.shouldBlock = false
+ localMessage.noServerTag = fromPlayer == null && isTeam
OnReceivedMessage(localMessage)
return
}
@@ -120,7 +142,3 @@ void function AddCallback_OnReceivedSayTextMessage( ClClient_MessageStruct funct
{
NsCustomCallbacksClient.OnReceivedSayTextMessageCallbacks.append(callbackFunc)
}
-
-void function NSSetupChathooksClient() {
- getroottable().rawset("CHudChat_ProcessMessageStartThread", CHudChat_ProcessMessageStartThread)
-}
diff --git a/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut
new file mode 100644
index 00000000..3560fd56
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut
@@ -0,0 +1,63 @@
+global enum eDiscordGameState
+{
+ LOADING = 0
+ MAINMENU
+ LOBBY
+ INGAME
+}
+
+global struct GameStateStruct {
+
+ string map
+ string mapDisplayname
+
+ string playlist
+ string playlistDisplayname
+
+ int currentPlayers
+ int maxPlayers
+ int ownScore
+ int otherHighestScore
+ int maxScore
+ float timeEnd
+}
+
+global struct UIPresenceStruct {
+ int gameState
+}
+
+global struct RequiredModInfo
+{
+ string name
+ string version
+}
+
+global struct ServerInfo
+{
+ int index
+ string id
+ string name
+ string description
+ string map
+ string playlist
+ int playerCount
+ int maxPlayerCount
+ bool requiresPassword
+ string region
+ array< RequiredModInfo > requiredMods
+}
+
+global struct MasterServerAuthResult
+{
+ bool success
+ string errorCode
+ string errorMessage
+}
+
+global struct ModInstallState
+{
+ int status
+ int progress
+ int total
+ float ratio
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_gamestate.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_gamestate.gnut
index 93be01ea..1baeae9f 100644
--- a/Northstar.Client/mod/scripts/vscripts/client/cl_gamestate.gnut
+++ b/Northstar.Client/mod/scripts/vscripts/client/cl_gamestate.gnut
@@ -1105,8 +1105,16 @@ void function DisplayPostMatchTop3()
RuiSetBool( rui, "isFriendly" + i, localTeam == players[i].GetTeam() )
}
}
+
+ if ( GetConVarBool( "hud_takesshots" ) )
+ thread TakeScoreboardScreenshot_Delayed()
}
+void function TakeScoreboardScreenshot_Delayed()
+{
+ wait 1.5
+ GetLocalClientPlayer().ClientCommand( "jpeg" )
+}
float function GetGameStartTime()
{
diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_minimap.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_minimap.gnut
new file mode 100644
index 00000000..4237a0be
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/client/cl_minimap.gnut
@@ -0,0 +1,942 @@
+global function ClMinimap_Init
+
+global function ClientCodeCallback_MinimapEntitySpawned
+
+global function Minimap_AddLayer
+global function Minimap_AddCustomLayer
+
+global function RegisterMinimapPackage
+
+global function Minimap_SetZoomScale
+global function Minimap_SetSizeScale
+global function Minimap_GetZoomScale
+global function Minimap_GetSizeScale
+global function Minimap_IsUsingLargeMinimap
+
+global function Minimap_Ping
+global function ServerCallback_PingMinimap
+
+global function Minimap_EnableDraw
+global function Minimap_DisableDraw
+
+#if DEV
+global function DumpMinimapHandles
+#endif
+
+struct {
+ var minimap_base
+ var minimap_wedges
+
+ int activeMinimapObjectCount
+
+ var minimap_you
+ var minimap_jammed_layer
+
+ var minimap_indicator
+
+ #if DEV
+ table< int, entity > minimapHandles
+ #endif
+
+ array<var> minimapOtherRuis
+
+ float threatMaxDist
+
+ float minimapZoomScale = 1
+ float minimapSizeScale = 1
+
+ bool minimapEnabled = true
+} file
+
+
+struct MinmapPackage
+{
+ asset minimapAsset = $""
+ void functionref( entity, var ) initFunction
+}
+
+table< string, array<MinmapPackage> > minimapAssetMap = {}
+
+const int OF_IS_VISIBLE = 1 << 0
+const int OF_TEAM_SAME = 1 << 1
+const int OF_TEAM_ENEMY = 1 << 2
+const int OF_IN_OUR_PARTY = 1 << 3
+const int OF_IS_OWNED_BY_US = 1 << 4
+const int OF_IS_PLAYER = 1 << 5
+const int OF_IS_NPC = 1 << 6
+const int OF_IS_TITAN = 1 << 7
+const int OF_ORIENT_UP = 1 << 8
+const int OF_NO_TEAM_COLOR = 1 << 9
+
+
+void function RegisterMinimapPackage( string entityClassname, int customStateIndex, asset minimapAsset, void functionref( entity, var ) initFunction )
+{
+ Assert ( (entityClassname in minimapAssetMap), "minimap is not currently setup to handle this type of entity: " + entityClassname )
+
+ MinmapPackage minimapPackage
+ minimapPackage.minimapAsset = minimapAsset
+ minimapPackage.initFunction = initFunction
+
+ switch ( entityClassname )
+ {
+ case "npc_soldier":
+ case "npc_spectre":
+ case "npc_stalker":
+ case "npc_drone":
+ case "npc_frag_drone":
+ case "npc_super_spectre":
+ case "npc_turret_sentry":
+ Assert( customStateIndex > 0 && customStateIndex < eMinimapObject_npc.COUNT )
+ minimapAssetMap[entityClassname].resize( eMinimapObject_npc.COUNT )
+ minimapAssetMap[entityClassname][customStateIndex] = minimapPackage
+ break
+ case "npc_titan":
+ Assert( customStateIndex > 0 && customStateIndex < eMinimapObject_npc_titan.COUNT )
+ minimapAssetMap[entityClassname].resize( eMinimapObject_npc_titan.COUNT )
+ minimapAssetMap[entityClassname][customStateIndex] = minimapPackage
+ break
+
+ case "prop_script":
+ Assert( customStateIndex > 0 && customStateIndex < eMinimapObject_prop_script.COUNT )
+ minimapAssetMap[entityClassname].resize( eMinimapObject_prop_script.COUNT )
+ minimapAssetMap[entityClassname][customStateIndex] = minimapPackage
+ break
+
+ case "info_hardpoint":
+ Assert( customStateIndex > 0 && customStateIndex < eMinimapObject_info_hardpoint.COUNT )
+ minimapAssetMap[entityClassname].resize( eMinimapObject_info_hardpoint.COUNT )
+ minimapAssetMap[entityClassname][customStateIndex] = minimapPackage
+ break
+
+ default:
+ Assert( false, "minimap is not currently setup to handle this type of entity: " + entityClassname )
+ }
+}
+
+void function RegisterDefaultMinimapPackage( string entityClassname, asset minimapAsset, void functionref( entity, var ) initFunction )
+{
+ Assert ( !(entityClassname in minimapAssetMap) )
+
+ MinmapPackage minimapPackage
+ minimapPackage.minimapAsset = minimapAsset
+ minimapPackage.initFunction = initFunction
+
+ minimapAssetMap[entityClassname] <- [minimapPackage]
+}
+
+void function ClMinimap_Init()
+{
+ RegisterDefaultMinimapPackage( "player", $"ui/minimap_player.rpak", MinimapPackage_PlayerInit )
+ RegisterDefaultMinimapPackage( "npc_titan", $"ui/minimap_object.rpak", MinimapPackage_NPCTitanInit )
+ RegisterDefaultMinimapPackage( "npc_soldier", $"ui/minimap_object.rpak", MinimapPackage_NPCHumanSizedInit )
+ RegisterDefaultMinimapPackage( "npc_spectre", $"ui/minimap_object.rpak", MinimapPackage_NPCHumanSizedInit )
+ RegisterDefaultMinimapPackage( "npc_stalker", $"ui/minimap_object.rpak", MinimapPackage_NPCHumanSizedInit )
+ RegisterDefaultMinimapPackage( "npc_super_spectre", $"ui/minimap_object.rpak", MinimapPackage_NPCHumanSizedInit )
+ RegisterDefaultMinimapPackage( "npc_drone_rocket", $"ui/minimap_object.rpak", MinimapPackage_NPCHumanSizedInit )
+ RegisterDefaultMinimapPackage( "npc_frag_drone", $"ui/minimap_object.rpak", MinimapPackage_NPCHumanSizedInit )
+ RegisterDefaultMinimapPackage( "npc_drone", $"ui/minimap_object.rpak", MinimapPackage_NPCHumanSizedInit )
+ RegisterDefaultMinimapPackage( "npc_dropship", $"ui/minimap_object.rpak", MinimapPackage_NPCDropShipInit )
+ RegisterDefaultMinimapPackage( "npc_turret_sentry", $"ui/minimap_object.rpak", MinimapPackage_NPCSentryTurretInit )
+ RegisterDefaultMinimapPackage( "prop_script", $"", MinimapPackage_DummyInit )
+ RegisterDefaultMinimapPackage( "item_titan_battery", $"ui/minimap_fw_battery.rpak", MinimapPackage_BatteryInit )
+ RegisterDefaultMinimapPackage( "item_flag", $"ui/minimap_object.rpak", MinimapPackage_FlagInit )
+ RegisterDefaultMinimapPackage( "item_bomb", $"ui/minimap_object.rpak", MinimapPackage_LTSBomb )
+ RegisterDefaultMinimapPackage( "info_hardpoint", $"", MinimapPackage_DummyInit )
+ RegisterDefaultMinimapPackage( "item_powerup", $"ui/minimap_object.rpak", MinimapPackage_PowerUp )
+
+ RegisterMinimapPackage( "npc_titan", eMinimapObject_npc_titan.AT_BOUNTY_BOSS, $"ui/minimap_object.rpak", MinimapPackage_BossTitanInit )
+
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.AT_DROPZONE_A, $"ui/minimap_obj_area.rpak", MinimapPackage_ATAreaInit )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.AT_DROPZONE_B, $"ui/minimap_obj_area.rpak", MinimapPackage_ATAreaInit )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.AT_DROPZONE_C, $"ui/minimap_obj_area.rpak", MinimapPackage_ATAreaInit )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.AT_BANK, $"ui/minimap_object.rpak", MinimapPackage_ATBank )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FW_BUILDSITE, $"ui/minimap_fw_build_site.rpak", MinimapPackage_FWBuildSite )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FW_BUILDSITE_TURRET, $"ui/minimap_fw_build_site.rpak", MinimapPackage_FWBuildSite )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FW_BUILDSITE_SHIELDED, $"ui/minimap_fw_build_site.rpak", MinimapPackage_FWBuildSite )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FD_HARVESTER, $"ui/minimap_obj_area.rpak", MinimapPackage_FDHarvester )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FD_LOADOUT_CHEST, $"ui/minimap_obj_area.rpak", MinimapPackage_FDLoadoutChest )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FD_BATTERY_EXCHANGE, $"ui/minimap_obj_area.rpak", MinimapPackage_FDBatteryExchange )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.BOOST_STORE, $"ui/minimap_obj_area.rpak", MinimapPackage_BoostStore )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FD_MORTAR_POSITION, $"ui/minimap_mortar_spectre.rpak", MinimapPackage_MortarPosition )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.ARC_TRAP, $"ui/minimap_obj_area.rpak", MinimapPackage_ArcTrap )
+
+ //RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FW_CAMP_A, $"ui/minimap_fw_camp.rpak", MinimapPackage_FWCampA )
+ //RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FW_CAMP_B, $"ui/minimap_fw_camp.rpak", MinimapPackage_FWCampB )
+ //RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.FW_CAMP_C, $"ui/minimap_fw_camp.rpak", MinimapPackage_FWCampC )
+
+ RegisterMinimapPackage( "info_hardpoint", eMinimapObject_info_hardpoint.HARDPOINT_A, $"ui/minimap_obj_area.rpak", MinimapPackage_HardpointA )
+ RegisterMinimapPackage( "info_hardpoint", eMinimapObject_info_hardpoint.HARDPOINT_B, $"ui/minimap_obj_area.rpak", MinimapPackage_HardpointB )
+ RegisterMinimapPackage( "info_hardpoint", eMinimapObject_info_hardpoint.HARDPOINT_C, $"ui/minimap_obj_area.rpak", MinimapPackage_HardpointC )
+
+ //if ( IsPlayingDemo() )
+ {
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.SPAWNZONE_IMC, $"ui/minimap_obj_area.rpak", MinimapPackage_SpawnZoneAreaInit )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.SPAWNZONE_MIL, $"ui/minimap_obj_area.rpak", MinimapPackage_SpawnZoneAreaInit )
+ }
+ //else
+ //{
+ // RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.SPAWNZONE_IMC, $"", MinimapPackage_SpawnZoneAreaInit )
+ // RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.SPAWNZONE_MIL, $"", MinimapPackage_SpawnZoneAreaInit )
+ //}
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.LTS_SITE_A, $"ui/minimap_object.rpak", MinimapPackage_LTSBombSiteA )
+ RegisterMinimapPackage( "prop_script", eMinimapObject_prop_script.LTS_SITE_B, $"ui/minimap_object.rpak", MinimapPackage_LTSBombSiteB )
+ /*
+ {
+ MinmapPackage atAreaPacakage
+ atAreaPacakage.minimapAsset = $"ui/minimap_obj_area.rpak"
+ atAreaPacakage.initFunction = MinimapPackage_ATAreaInit
+ MinmapPackage spawnZonePacakage
+ spawnZonePacakage.minimapAsset = $"ui/minimap_obj_area.rpak"
+ spawnZonePacakage.initFunction = MinimapPackage_SpawnZoneAreaInit
+ MinmapPackage fwBuildSitePackage
+ fwBuildSitePackage.minimapAsset = $"ui/minimap_fw_build_site.rpak"
+ fwBuildSitePackage.initFunction = MinimapPackage_FWBuildSite
+ MinmapPackage fwCampAPackage
+ fwCampAPackage.minimapAsset = $"ui/minimap_fw_camp.rpak"
+ fwCampAPackage.initFunction = MinimapPackage_FWCampA
+ MinmapPackage fwCampBPackage
+ fwCampBPackage.minimapAsset = $"ui/minimap_fw_camp.rpak"
+ fwCampBPackage.initFunction = MinimapPackage_FWCampB
+ MinmapPackage fwCampCPackage
+ fwCampCPackage.minimapAsset = $"ui/minimap_fw_camp.rpak"
+ fwCampCPackage.initFunction = MinimapPackage_FWCampC
+ minimapAssetMap["prop_script"] <-[blankPackage, atAreaPacakage, atAreaPacakage, atAreaPacakage, spawnZonePacakage, spawnZonePacakage,
+ fwCampAPackage, fwCampBPackage, fwCampCPackage,
+ fwBuildSitePackage, fwBuildSitePackage, fwBuildSitePackage ]
+ }
+*/
+
+ AddCreateCallback( "player", OnPlayerCreate )
+
+ float threatMaxDist = Minimap_GetFloatForKey( "threatMaxDist" )
+ float threatDistNear = Minimap_GetFloatForKey( "threatNearDist" )
+ float threatDistFar = Minimap_GetFloatForKey( "threatFarDist" )
+
+ if ( GameRules_GetGameMode() == FORT_WAR )
+ file.threatMaxDist = max( threatMaxDist, 2200 )
+ else
+ file.threatMaxDist = max( threatMaxDist, 1800 )
+
+ file.minimap_base = CreateCockpitRui( $"ui/minimap_base.rpak", MINIMAP_Z_BASE )
+
+ RuiSetFloat( file.minimap_base, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( file.minimap_base, "minimapSizeScale", file.minimapSizeScale )
+
+ file.minimap_wedges = CreateCockpitRui( $"ui/minimap_wedges.rpak", MINIMAP_Z_THREAT_WEDGES )
+
+ RuiSetFloat( file.minimap_wedges, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( file.minimap_wedges, "minimapSizeScale", file.minimapSizeScale )
+ RuiSetBool( file.minimap_wedges, "isVisible", file.minimapZoomScale == 1.0 )
+
+ file.minimap_you = CreateCockpitRui( $"ui/minimap_you.rpak", MINIMAP_Z_YOU )
+
+ RuiSetFloat( file.minimap_you, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( file.minimap_you, "minimapSizeScale", file.minimapSizeScale )
+
+ file.minimap_jammed_layer = null
+
+ file.minimap_indicator = CreateCockpitRui( $"ui/minimap_indicator.rpak", -1 )
+
+ RuiSetFloat( file.minimap_indicator, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( file.minimap_indicator, "minimapSizeScale", file.minimapSizeScale )
+
+ StatusEffect_RegisterEnabledCallback( eStatusEffect.minimap_jammed, MinimapJammed_Enabled )
+ StatusEffect_RegisterDisabledCallback( eStatusEffect.minimap_jammed, MinimapJammed_Disabled )
+ RegisterSignal( "LoopRadarJammerSounds" )
+}
+
+#if DEV
+void function DumpMinimapHandles()
+{
+ int index = 0
+ foreach ( handle, ent in file.minimapHandles )
+ {
+ printt( index, handle, ent )
+ ++index
+ }
+}
+#endif
+
+
+void function Minimap_DisableDraw()
+{
+ file.minimapEnabled = false
+
+ RuiSetDrawGroup( file.minimap_base, RUI_DRAW_NONE )
+ RuiSetDrawGroup( file.minimap_wedges, RUI_DRAW_NONE )
+ RuiSetDrawGroup( file.minimap_you, RUI_DRAW_NONE )
+ RuiSetDrawGroup( file.minimap_indicator, RUI_DRAW_NONE )
+
+ foreach ( var rui in file.minimapOtherRuis )
+ {
+ RuiSetDrawGroup( rui, RUI_DRAW_NONE )
+ }
+}
+
+void function Minimap_EnableDraw()
+{
+ file.minimapEnabled = true
+
+ RuiSetDrawGroup( file.minimap_base, RUI_DRAW_COCKPIT )
+ RuiSetDrawGroup( file.minimap_wedges, RUI_DRAW_COCKPIT )
+ RuiSetDrawGroup( file.minimap_you, RUI_DRAW_COCKPIT )
+ RuiSetDrawGroup( file.minimap_indicator, RUI_DRAW_COCKPIT )
+
+ foreach ( var rui in file.minimapOtherRuis )
+ {
+ RuiSetDrawGroup( rui, RUI_DRAW_COCKPIT )
+ }
+}
+
+
+void function ClientCodeCallback_MinimapEntitySpawned( entity ent )
+{
+ foreach ( callbackFunc in clGlobal.onMinimapEntSpawnedCallbacks )
+ {
+ callbackFunc( ent )
+ }
+
+ if ( ent == GetLocalViewPlayer() )
+ return
+
+ thread AddMinimapObject( ent )
+}
+
+
+asset function GetMinimapAsset( string className, int customState )
+{
+ if ( !(className in minimapAssetMap) )
+ return $""
+
+ if ( customState > minimapAssetMap[className].len() - 1 )
+ return $""
+
+ return minimapAssetMap[className][customState].minimapAsset
+}
+
+
+void function AddMinimapObject( entity ent ) //TODO: If we want radar jammer boost to hide friendly players we need to be able to get the rui handles back.
+{
+ Assert( IsValid( ent ) )
+
+ string className = expect string( ent.GetSignifierName() )
+ int customState = ent.Minimap_GetCustomState()
+
+ asset minimapAsset = GetMinimapAsset( className, customState )
+ if ( minimapAsset == $"" )
+ {
+ return
+ }
+
+ int zOrder = ent.Minimap_GetZOrder()
+ entity viewPlayer = GetLocalViewPlayer()
+
+ ent.SetDoDestroyCallback( true )
+
+ #if DEV
+ int eHandle = ent.Dev_GetEncodedEHandle()
+
+ {
+ array< int > eHandlesToRemove
+ foreach ( eHandleIter, entIter in file.minimapHandles )
+ {
+ if ( !IsValid( entIter ) )
+ {
+ eHandlesToRemove.append( eHandleIter )
+ }
+ }
+
+ foreach ( eHandleIter in eHandlesToRemove )
+ {
+ delete file.minimapHandles[eHandleIter]
+ }
+ }
+
+ if ( eHandle in file.minimapHandles )
+ {
+ // Should have been removed in above loop
+ Assert( IsValid( file.minimapHandles[eHandle] ) )
+
+ DumpMinimapHandles()
+ Assert( false, "Duplicate minimap entity added - " + ent )
+ }
+
+ file.minimapHandles[eHandle] <- ent
+ #endif
+
+ var rui = CreateCockpitRui( minimapAsset, MINIMAP_Z_BASE + zOrder )
+
+ //RuiTrackGameTime( rui, "lastFireTime", ent, RUI_TRACK_LAST_FIRED_TIME )
+
+ RuiTrackFloat3( rui, "playerPos", viewPlayer, RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiTrackFloat3( rui, "playerAngles", viewPlayer, RUI_TRACK_EYEANGLES_FOLLOW )
+
+ RuiTrackFloat3( rui, "objectPos", ent, RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiTrackFloat3( rui, "objectAngles", ent, RUI_TRACK_EYEANGLES_FOLLOW )
+ RuiTrackInt( rui, "objectFlags", ent, RUI_TRACK_MINIMAP_FLAGS )
+ RuiTrackInt( rui, "customState", ent, RUI_TRACK_MINIMAP_CUSTOM_STATE )
+ RuiSetFloat( rui, "displayDist", file.threatMaxDist )
+ RuiSetFloat( rui, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( rui, "minimapSizeScale", file.minimapSizeScale )
+
+ minimapAssetMap[className][customState].initFunction( ent, rui )
+
+ file.minimapOtherRuis.append( rui )
+
+ RuiSetDrawGroup( rui, file.minimapEnabled ? RUI_DRAW_COCKPIT : RUI_DRAW_NONE )
+ OnThreadEnd(
+ function() : ( rui )
+ {
+ file.minimapOtherRuis.removebyvalue( rui )
+ RuiDestroy( rui )
+ }
+ )
+
+ ent.EndSignal( "OnDestroy" )
+
+ if ( ent.IsPlayer() )
+ {
+ while ( IsValid( ent ) )
+ {
+ WaitSignal( ent, "SettingsChanged", "OnDeath" )
+ }
+ }
+ else
+ {
+ ent.WaitSignal( "OnDestroy" )
+ }
+}
+
+
+void function Minimap_PingCount( vector origin, float radius, float duration, vector color, int count, bool reverse = false )
+{
+ float delay = duration / count
+
+ while ( count )
+ {
+ count--
+
+ Minimap_Ping( origin, radius, delay + (delay * 0.25), color, reverse )
+ wait delay
+ }
+}
+
+
+void function Minimap_Ping( vector origin, float radius, float duration, vector color, bool reverse = false )
+{
+ entity viewPlayer = GetLocalViewPlayer()
+ int zOrder = viewPlayer.Minimap_GetZOrder()
+
+ if ( !file.minimapEnabled )
+ return
+
+ var rui = CreateCockpitRui( $"ui/minimap_ping.rpak", MINIMAP_Z_BASE + zOrder )
+
+ RuiTrackFloat3( rui, "playerPos", viewPlayer, RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiTrackFloat3( rui, "playerAngles", viewPlayer, RUI_TRACK_EYEANGLES_FOLLOW )
+
+ RuiSetFloat3( rui, "objColor", color )
+ RuiSetFloat3( rui, "objectPos", origin )
+ RuiSetFloat3( rui, "objectAngles", <0,0,0> )
+
+ RuiSetFloat( rui, "objectRadius", radius / ( file.threatMaxDist * file.minimapZoomScale ) )
+
+ RuiSetFloat( rui, "displayDist", file.threatMaxDist )
+ RuiSetFloat( rui, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( rui, "minimapSizeScale", file.minimapSizeScale )
+
+ RuiSetGameTime( rui, "startTime", Time() )
+ RuiSetGameTime( rui, "endTime", Time() + duration )
+
+ RuiSetBool( rui, "reverse", reverse )
+}
+
+
+
+var function Minimap_AddLayer( asset layerImage, bool isFriendly )
+{
+ entity player = GetLocalViewPlayer()
+
+ var rui = CreateCockpitRui( $"ui/minimap_layer.rpak", MINIMAP_Z_LAYER )
+
+ RuiTrackFloat3( rui, "playerPos", player, RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiTrackFloat3( rui, "playerAngles", player, RUI_TRACK_EYEANGLES_FOLLOW )
+
+ asset mapImage = Minimap_GetAssetForKey( "minimap" )
+ float mapCornerX = Minimap_GetFloatForKey( "pos_x" )
+ float mapCornerY = Minimap_GetFloatForKey( "pos_y" )
+ float displayDist = Minimap_GetFloatForKey( "displayDist" )
+ float threatDistNear = Minimap_GetFloatForKey( "threatNearDist" )
+ float threatDistFar = Minimap_GetFloatForKey( "threatFarDist" )
+
+ float mapScale = Minimap_GetFloatForKey( "scale" )
+
+ RuiSetImage( rui, "layerImage", layerImage )
+ RuiSetFloat3( rui, "mapCorner", <mapCornerX, mapCornerY, 0> )
+ RuiSetFloat( rui, "displayDist", file.threatMaxDist )
+ RuiSetFloat( rui, "mapScale", mapScale )
+ RuiSetFloat( rui, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( rui, "minimapSizeScale", file.minimapSizeScale )
+
+ RuiSetBool( rui, "isFriendly", isFriendly )
+
+ return rui
+}
+
+var function Minimap_AddCustomLayer( asset ruiAsset, int sortKey = MINIMAP_Z_LAYER )
+{
+ entity player = GetLocalViewPlayer()
+
+ var rui = CreateCockpitRui( ruiAsset, sortKey )
+
+ RuiTrackFloat3( rui, "playerPos", player, RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiTrackFloat3( rui, "playerAngles", player, RUI_TRACK_EYEANGLES_FOLLOW )
+
+ asset mapImage = Minimap_GetAssetForKey( "minimap" )
+ float mapCornerX = Minimap_GetFloatForKey( "pos_x" )
+ float mapCornerY = Minimap_GetFloatForKey( "pos_y" )
+ float displayDist = Minimap_GetFloatForKey( "displayDist" )
+ float threatDistNear = Minimap_GetFloatForKey( "threatNearDist" )
+ float threatDistFar = Minimap_GetFloatForKey( "threatFarDist" )
+
+ float mapScale = Minimap_GetFloatForKey( "scale" )
+
+ RuiSetFloat3( rui, "mapCorner", <mapCornerX, mapCornerY, 0> )
+ RuiSetFloat( rui, "displayDist", file.threatMaxDist )
+ RuiSetFloat( rui, "mapScale", mapScale )
+ RuiSetFloat( rui, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( rui, "minimapSizeScale", file.minimapSizeScale )
+
+ return rui
+}
+
+
+void function OnPlayerCreate( entity player )
+{
+ if ( player != GetLocalViewPlayer() )
+ return
+
+ RuiTrackFloat3( file.minimap_base, "playerPos", player, RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiTrackFloat3( file.minimap_base, "playerAngles", player, RUI_TRACK_EYEANGLES_FOLLOW )
+
+ RuiTrackInt( file.minimap_indicator, "indicatorId", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "indicatorId" ) )
+
+ asset mapImage = Minimap_GetAssetForKey( "minimap" )
+ float mapCornerX = Minimap_GetFloatForKey( "pos_x" )
+ float mapCornerY = Minimap_GetFloatForKey( "pos_y" )
+ float displayDist = Minimap_GetFloatForKey( "displayDist" )
+ float threatDistNear = Minimap_GetFloatForKey( "threatNearDist" )
+ float threatDistFar = Minimap_GetFloatForKey( "threatFarDist" )
+
+ float mapScale = Minimap_GetFloatForKey( "scale" )
+
+ RuiSetImage( file.minimap_base, "mapImage", mapImage )
+ RuiSetFloat3( file.minimap_base, "mapCorner", <mapCornerX, mapCornerY, 0> )
+ RuiSetFloat( file.minimap_base, "displayDist", file.threatMaxDist )
+ RuiSetFloat( file.minimap_base, "mapScale", mapScale )
+
+ RuiTrackFloat3( file.minimap_wedges, "playerPos", player, RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiTrackFloat3( file.minimap_wedges, "playerAngles", player, RUI_TRACK_EYEANGLES_FOLLOW )
+ RuiSetFloat3( file.minimap_wedges, "distances", <threatDistNear / file.threatMaxDist, 1.0 - (threatDistFar / file.threatMaxDist), 1.0> )
+ RuiSetBool( file.minimap_wedges, "isVisible", file.minimapZoomScale == 1.0 )
+
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeCenter", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_CENTER )
+
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeMid0", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_NEAR_N )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeMid1", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_NEAR_NE )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeMid2", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_NEAR_E )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeMid3", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_NEAR_SE )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeMid4", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_NEAR_S )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeMid5", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_NEAR_SW )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeMid6", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_NEAR_W )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeMid7", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_NEAR_NW )
+
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeOuter0", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_FAR_N )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeOuter1", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_FAR_NE )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeOuter2", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_FAR_E )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeOuter3", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_FAR_SE )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeOuter4", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_FAR_S )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeOuter5", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_FAR_SW )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeOuter6", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_FAR_W )
+ RuiTrackGameTime( file.minimap_wedges, "lastFireTimeOuter7", player, RUI_TRACK_MINIMAP_THREAT_SECTOR, MOTS_FAR_NW )
+
+
+ RuiSetFloat( file.minimap_wedges, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( file.minimap_wedges, "minimapSizeScale", file.minimapSizeScale )
+
+ RuiSetFloat( file.minimap_base, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( file.minimap_base, "minimapSizeScale", file.minimapSizeScale )
+
+ RuiTrackInt( file.minimap_you, "objectFlags", player, RUI_TRACK_MINIMAP_FLAGS )
+ RuiTrackInt( file.minimap_you, "customState", player, RUI_TRACK_MINIMAP_CUSTOM_STATE )
+
+}
+
+
+void function MinimapPackage_DummyInit( entity ent, var rui )
+{
+
+}
+
+void function MinimapPackage_PlayerInit( entity ent, var rui )
+{
+ RuiTrackGameTime( rui, "lastFireTime", ent, RUI_TRACK_LAST_FIRED_TIME )
+ if ( !IsFFAGame() ) //JFS: Too much work to get FFA to work correctly with Minimap logic, so disabling it for FFA
+ {
+ RuiTrackFloat( rui, "sonarDetectedFrac", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.sonar_detected )
+ RuiTrackFloat( rui, "maphackDetectedFrac", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.maphack_detected )
+ }
+}
+
+void function MinimapPackage_NPCTitanInit( entity ent, var rui )
+{
+ entity player = GetLocalClientPlayer()
+
+ RuiSetBool( rui, "useTeamColor", false )
+
+ RuiTrackGameTime( rui, "lastFireTime", ent, RUI_TRACK_LAST_FIRED_TIME )
+ if ( !IsFFAGame() ) //JFS: Too much work to get FFA to work correctly with Minimap logic, so disabling it for FFA
+ RuiTrackFloat( rui, "sonarDetectedFrac", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.sonar_detected )
+}
+
+void function MinimapPackage_NPCHumanSizedInit( entity ent, var rui )
+{
+ entity player = GetLocalClientPlayer()
+
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/minimap/compass_icon_small_dot" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"" )
+
+ //if ( ent == GetLocalClientPlayer().GetPetTitan() )
+ //{
+ // RuiSetBool( rui, "useTeamColor", false )
+ // RuiSetFloat3( rui, "iconColor", TEAM_COLOR_YOU / 255.0 )
+ //}
+ RuiTrackGameTime( rui, "lastFireTime", ent, RUI_TRACK_LAST_FIRED_TIME )
+ //if ( !IsFFAGame() ) //JFS: Too much work to get FFA to work correctly with Minimap logic, so disabling it for FFA
+ // RuiTrackFloat( rui, "sonarDetectedFrac", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.sonar_detected )
+}
+
+void function MinimapPackage_NPCDropShipInit( entity ent, var rui )
+{
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/scoreboard/status_evac" )
+}
+
+void function MinimapPackage_NPCSentryTurretInit( entity ent, var rui )
+{
+ RuiSetImage( rui, "defaultIcon", $"" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"" )
+}
+
+void function MinimapPackage_BossTitanInit( entity ent, var rui )
+{
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/gametype_icons/bounty_hunt/bh_titan" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"rui/hud/gametype_icons/bounty_hunt/bh_titan" )
+ RuiSetBool( rui, "useTeamColor", false )
+}
+
+void function MinimapPackage_BatteryInit( entity ent, var rui )
+{
+ entity player = GetLocalViewPlayer()
+ RuiTrackInt( rui, "batteryCount", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "batteryCount" ) )
+ RuiSetBool( rui, "useTeamColor", false )
+ RuiTrackFloat( rui, "batteryCarried", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.battery_carried )
+}
+
+void function MinimapPackage_ATAreaInit( entity ent, var rui )
+{
+ RuiTrackFloat( rui, "objectRadius", ent, RUI_TRACK_MINIMAP_SCALE )
+}
+
+void function MinimapPackage_ATBank( entity ent, var rui )
+{
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/gametype_icons/bounty_hunt/bh_bank_icon" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"rui/hud/gametype_icons/bounty_hunt/bh_bank_icon" )
+ RuiSetBool( rui, "useTeamColor", false )
+}
+
+void function MinimapPackage_SpawnZoneAreaInit( entity ent, var rui )
+{
+ RuiTrackFloat( rui, "objectRadius", ent, RUI_TRACK_MINIMAP_SCALE )
+ if ( !IsPlayingDemo() )
+ {
+ RuiSetImage( rui, "centerImage", $"" ) // hide diamond
+ RuiSetImage( rui, "clampedImage", $"rui/hud/gametype_icons/obj_foreground_diamond" )
+ }
+}
+
+void function MinimapPackage_FWBuildSite( entity ent, var rui )
+{
+ entity player = GetLocalViewPlayer()
+ RuiTrackInt( rui, "batteryCount", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "batteryCount" ) )
+ //RuiTrackFloat( rui, "objectRadius", ent, RUI_TRACK_MINIMAP_SCALE )
+}
+
+void function MinimapPackage_HardpointA( entity ent, var rui )
+{
+ RuiSetFloat( rui, "objectRadius", 0.001 )
+}
+
+void function MinimapPackage_HardpointB( entity ent, var rui )
+{
+ RuiSetFloat( rui, "objectRadius", 0.001 )
+}
+
+void function MinimapPackage_HardpointC( entity ent, var rui )
+{
+ RuiSetFloat( rui, "objectRadius", 0.001 )
+}
+
+void function MinimapPackage_FDHarvester( entity ent, var rui )
+{
+ RuiSetImage( rui, "centerImage", $"rui/hud/gametype_icons/fd/coop_harvester" )
+ RuiSetImage( rui, "clampedImage", $"rui/hud/gametype_icons/fd/coop_harvester" )
+ RuiSetFloat( rui, "objectRadius", 0.01 )
+ RuiSetBool( rui, "useOverrideColor", true )
+ RuiSetColorAlpha( rui, "overrideColor", <1.0,1.0,1.0>, 1.0 )
+}
+
+void function MinimapPackage_FDLoadoutChest( entity ent, var rui )
+{
+ RuiSetImage( rui, "centerImage", $"rui/hud/gametype_icons/fd/coop_ammo_locker_icon" )
+ RuiSetImage( rui, "clampedImage", $"rui/hud/gametype_icons/fd/coop_ammo_locker_icon" )
+ RuiSetFloat( rui, "objectRadius", 0.01 )
+ RuiSetBool( rui, "useOverrideColor", true )
+ RuiSetColorAlpha( rui, "overrideColor", <1.0,1.0,1.0>, 1.0 )
+}
+
+void function MinimapPackage_FDBatteryExchange( entity ent, var rui )
+{
+ RuiSetImage( rui, "centerImage", $"rui/hud/gametype_icons/bounty_hunt/bh_bank_icon" )
+ RuiSetImage( rui, "clampedImage", $"rui/hud/gametype_icons/bounty_hunt/bh_bank_icon" )
+ RuiSetFloat( rui, "objectRadius", 0.01 )
+ RuiSetBool( rui, "useOverrideColor", true )
+ RuiSetColorAlpha( rui, "overrideColor", <1.0,1.0,1.0>, 1.0 )
+}
+
+void function MinimapPackage_BoostStore( entity ent, var rui )
+{
+ RuiSetImage( rui, "centerImage", $"rui/hud/gametype_icons/bounty_hunt/bh_bonus_icon" )
+ RuiSetImage( rui, "clampedImage", $"rui/hud/gametype_icons/bounty_hunt/bh_bonus_icon" )
+ RuiSetFloat( rui, "objectRadius", 0.01 )
+ RuiSetBool( rui, "useOverrideColor", true )
+ RuiSetColorAlpha( rui, "overrideColor", <1.0,1.0,1.0>, 1.0 )
+}
+
+void function MinimapPackage_MortarPosition( entity ent, var rui )
+{
+ RuiSetImage( rui, "bgImage", $"rui/hud/gametype_icons/fd/fd_icon_spectre_mortar_bg" )
+ RuiSetImage( rui, "centerImage", $"rui/hud/gametype_icons/fd/fd_icon_spectre_mortar" )
+ RuiSetImage( rui, "clampedImage", $"rui/hud/gametype_icons/fd/fd_icon_spectre_mortar" )
+ RuiTrackFloat( rui, "arcPercent", ent, RUI_TRACK_SHIELD_FRACTION )
+}
+
+void function MinimapPackage_ArcTrap( entity ent, var rui )
+{
+ RuiSetImage( rui, "centerImage", $"rui/hud/minimap/compass_icon_arc_trap" )
+ RuiSetImage( rui, "clampedImage", $"" )
+ RuiTrackFloat( rui, "objectRadius", ent, RUI_TRACK_MINIMAP_SCALE )
+}
+
+void function MinimapPackage_FWCampA( entity ent, var rui )
+{
+ RuiTrackInt( rui, "alertLevel", null, RUI_TRACK_SCRIPT_NETWORK_VAR_GLOBAL_INT, GetNetworkedVariableIndex( "fwCampAlertA" ) )
+ RuiTrackFloat( rui, "objectRadius", ent, RUI_TRACK_MINIMAP_SCALE )
+}
+
+void function MinimapPackage_FWCampB( entity ent, var rui )
+{
+ RuiTrackInt( rui, "alertLevel", null, RUI_TRACK_SCRIPT_NETWORK_VAR_GLOBAL_INT, GetNetworkedVariableIndex( "fwCampAlertB" ) )
+ RuiTrackFloat( rui, "objectRadius", ent, RUI_TRACK_MINIMAP_SCALE )
+}
+
+void function MinimapPackage_FWCampC( entity ent, var rui )
+{
+ RuiTrackInt( rui, "alertLevel", null, RUI_TRACK_SCRIPT_NETWORK_VAR_GLOBAL_INT, GetNetworkedVariableIndex( "fwCampAlertC" ) )
+ RuiTrackFloat( rui, "objectRadius", ent, RUI_TRACK_MINIMAP_SCALE )
+}
+
+void function MinimapPackage_LTSBombSiteA( entity ent, var rui )
+{
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/gametype_icons/last_titan_standing/bomb_site_a_attacking" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"rui/hud/gametype_icons/last_titan_standing/bomb_site_a_attacking" )
+}
+
+void function MinimapPackage_LTSBombSiteB( entity ent, var rui )
+{
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/gametype_icons/last_titan_standing/bomb_site_b_attacking" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"rui/hud/gametype_icons/last_titan_standing/bomb_site_b_attacking" )
+}
+
+void function MinimapPackage_LTSBomb( entity ent, var rui )
+{
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/gametype_icons/last_titan_standing/bomb_neutral" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"rui/hud/gametype_icons/last_titan_standing/bomb_neutral" )
+ RuiSetBool( rui, "useTeamColor", false )
+}
+
+void function MinimapPackage_FlagInit( entity ent, var rui )
+{
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/gametype_icons/ctf/ctf_flag_neutral" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"rui/hud/gametype_icons/ctf/ctf_flag_neutral" )
+ RuiSetBool( rui, "useTeamColor", true )
+}
+
+void function MinimapPackage_PowerUp( entity ent, var rui )
+{
+ //Battery spawners are the only power ups in use atm. This would need to be updated if we use them differently.
+ RuiSetImage( rui, "defaultIcon", $"rui/hud/battery/battery_capture_friendly" )
+ RuiSetImage( rui, "clampedDefaultIcon", $"rui/hud/battery/battery_capture_friendly" )
+ RuiSetBool( rui, "useTeamColor", false )
+}
+
+
+void function MinimapJammed_Enabled( entity ent, int statusEffect, bool actuallyChanged )
+{
+ if ( !actuallyChanged )
+ return
+
+ if ( ent != GetLocalClientPlayer() )
+ return
+
+ thread LoopRadarJammerSounds( ent )
+ thread FadeOutStaticSoundAfterDelay( ent, BURN_METER_RADAR_JAMMER_PULSE_DURATION - BURN_METER_RADAR_JAMMER_EASE_OFF_TIME )
+
+ if ( file.minimap_jammed_layer != null )
+ RuiDestroy( file.minimap_jammed_layer )
+
+ file.minimap_jammed_layer = Minimap_AddCustomLayer( $"ui/minimap_jammer_layer.rpak", MINIMAP_Z_YOU + 1 )
+ if ( actuallyChanged )
+ RuiSetGameTime( file.minimap_jammed_layer, "startTime", Time() )
+
+ RuiSetFloat( file.minimap_jammed_layer, "minimapZoomScale", file.minimapZoomScale )
+ RuiSetFloat( file.minimap_jammed_layer, "minimapSizeScale", file.minimapSizeScale )
+ RuiTrackFloat( file.minimap_jammed_layer, "scriptAlphaVar", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, statusEffect )
+
+ RuiTrackFloat( file.minimap_wedges, "scriptInverseAlphaVar", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, statusEffect )
+
+}
+
+void function MinimapJammed_Disabled( entity ent, int statusEffect, bool actuallyChanged )
+{
+ if ( !actuallyChanged )
+ return
+
+ if ( ent != GetLocalClientPlayer() )
+ return
+
+ clGlobal.levelEnt.Signal( "LoopRadarJammerSounds" )
+
+ if ( file.minimap_jammed_layer != null )
+ {
+ RuiDestroy( file.minimap_jammed_layer )
+ file.minimap_jammed_layer = null
+ }
+}
+
+void function LoopRadarJammerSounds( entity ent )
+{
+ clGlobal.levelEnt.Signal( "LoopRadarJammerSounds" )
+ clGlobal.levelEnt.EndSignal( "LoopRadarJammerSounds" )
+ ent.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ StopSoundOnEntity( ent, "HUD_Boost_Card_Radar_Jammer_RedTextBeep_1P" )
+ }
+ )
+
+ float currentTime = Time()
+ float fractionalComponent = currentTime - floor( currentTime )
+ float timeToWait
+ if ( fractionalComponent <= 0.5 )
+ timeToWait = 0.5 - fractionalComponent
+ else
+ timeToWait = 1.5 - fractionalComponent
+
+ wait ( timeToWait ) //Red text flashes in with regards to game time, so we need to wait till the appropriate time (0.5) to play the next sound
+
+ while( true )
+ {
+ if ( IsValid( ent ) )
+ EmitSoundOnEntity( ent, "HUD_Boost_Card_Radar_Jammer_RedTextBeep_1P" )
+
+ wait 1.0 //This is dependent on the rui logic, and will need to be changed if the rui logic changes
+ }
+}
+
+void function FadeOutStaticSoundAfterDelay( entity ent, float delay )
+{
+ EmitSoundOnEntity( ent, "HUD_Boost_Card_Radar_Jammer_Signal_Static_1P" )
+
+ ent.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ clGlobal.levelEnt.Signal( "LoopRadarJammerSounds" ) //stop red text beeping sounds
+ if ( IsValid( ent ) )
+ StopSoundOnEntity( ent, "HUD_Boost_Card_Radar_Jammer_Signal_Static_1P" )
+
+ }
+ )
+ wait delay
+}
+void function Minimap_SetZoomScale( float scale )
+{
+ file.minimapZoomScale = scale
+ if(file.minimap_you != null)
+ RuiSetFloat( file.minimap_you, "minimapZoomScale", file.minimapZoomScale )
+ if(file.minimap_indicator != null)
+ RuiSetFloat( file.minimap_indicator, "minimapZoomScale", file.minimapZoomScale )
+ if(file.minimap_base != null)
+ RuiSetFloat( file.minimap_base, "minimapZoomScale", file.minimapZoomScale )
+
+ if ( file.minimap_wedges != null )
+ {
+ RuiSetBool( file.minimap_wedges, "isVisible", file.minimapZoomScale == 1.0 )
+ RuiSetFloat( file.minimap_wedges, "minimapZoomScale", file.minimapZoomScale )
+ }
+ foreach ( var rui in file.minimapOtherRuis )
+ {
+ RuiSetFloat( rui, "minimapZoomScale", file.minimapZoomScale )
+ }
+}
+
+void function Minimap_SetSizeScale( float scale )
+{
+ file.minimapSizeScale = scale
+ if(file.minimap_base != null)
+ RuiSetFloat( file.minimap_base, "minimapSizeScale", file.minimapSizeScale )
+ if(file.minimap_indicator != null)
+ RuiSetFloat( file.minimap_indicator, "minimapSizeScale", file.minimapSizeScale )
+ if(file.minimap_you != null)
+ RuiSetFloat( file.minimap_you, "minimapSizeScale", file.minimapSizeScale )
+ if(file.minimap_wedges != null)
+ RuiSetFloat( file.minimap_wedges, "minimapSizeScale", file.minimapSizeScale )
+ foreach ( var rui in file.minimapOtherRuis )
+ {
+ RuiSetFloat( rui, "minimapSizeScale", file.minimapSizeScale )
+ }
+
+}
+float function Minimap_GetZoomScale()
+{
+ return file.minimapZoomScale
+}
+float function Minimap_GetSizeScale()
+{
+ return file.minimapSizeScale
+}
+bool function Minimap_IsUsingLargeMinimap()
+{
+ return file.minimapSizeScale > 1.0
+}
+
+void function ServerCallback_PingMinimap( float originX, float originY, float originZ, float radius, float duration, float colorR, float colorG, float colorB, int count, bool reverse )
+{
+ if ( count > 1 )
+ thread Minimap_PingCount( <originX, originY, originZ>, radius, duration, <colorR, colorG, colorB>, count, reverse )
+ else
+ Minimap_Ping( <originX, originY, originZ>, radius, duration, <colorR, colorG, colorB>, reverse )
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_titan_cockpit.nut b/Northstar.Client/mod/scripts/vscripts/client/cl_titan_cockpit.nut
new file mode 100644
index 00000000..8261b3fd
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/client/cl_titan_cockpit.nut
@@ -0,0 +1,1676 @@
+untyped
+
+global function ClTitanCockpit_Init
+
+global function ServerCallback_TitanEMP
+global function ServerCallback_TitanCockpitBoot
+global function TitanCockpit_DamageFeedback
+global function TitanCockpit_AddPlayer
+global function RegisterTitanBindings
+global function GetTitanBindings
+global function DeregisterTitanBindings
+global function ServerCallback_TitanEmbark
+global function ServerCallback_TitanDisembark
+global function PlayerPressed_EjectEnable // so can be called directly for debugging
+global function PlayerPressed_Eject // so can be called directly for debugging
+global function TitanCockpit_IsBooting
+global function ServerCallback_TitanCockpitEMP
+global function TitanCockpit_EMPFadeScale
+global function TitanCockpit_DoEMP
+global function TitanEMP_Internal
+global function ServerCallback_EjectConfirmed
+global function LinkCoreHint
+
+global function AddTitanCockpitManagedRUI
+global function UpdateTitanCockpitVisibility
+global function TitanCockpitDestroyRui
+global function TitanCockpitDoomedThink
+global function PlayerEjects
+global function IsDisplayingEjectInterface
+global function FlashCockpitLight
+global function PlayCockpitSparkFX
+
+global function FlashCockpitHealth
+
+global function UpdateEjectHud_SetButtonPressTime
+global function UpdateEjectHud_SetButtonPressCount
+
+global function SetUnlimitedDash
+
+// Added by northstar
+global function AddCommonEjectMessage
+global function AddRareEjectMessage
+
+#if MP
+global function NetworkedVarChangedCallback_UpdateVanguardRUICoreStatus
+global function DisplayFrontierRank
+#endif
+struct TitanCockpitManagedRUI
+{
+ bool exists = false
+ var functionref() create
+ void functionref() destroy
+ bool functionref() shouldCreate
+ int drawGroup = RUI_DRAW_COCKPIT
+}
+
+const TITAN_ALARM_SOUND = "titan_alarm"
+const TITAN_NUCLEAR_DEATH_ALARM = "titan_nuclear_death_alarm"
+const TITAN_EJECT_BOOST = "titan_eject_boost"
+const TITAN_EJECT_ASCENT = "player_eject_windrush"
+const TITAN_EJECT_APEX = "player_eject_apex_wind"
+const TITAN_EJECT_DESCENT = "player_fallingdescent_windrush"
+
+const EJECT_MIN_VELOCITY = 200.0
+const EJECT_MAX_VELOCITY = 1000.0
+
+struct
+{
+ var coreHintRui
+ var cockpitRui
+ var cockpitLowerRui
+ var cockpitAdditionalRui
+ array<TitanCockpitManagedRUI> titanCockpitManagedRUIs
+
+ string lastPilotSettings
+
+ bool isFirstBoot = true
+ var scorchHotstreakRui
+ // Added by northstar
+ array<string> moddedRareEjectMessages
+ array<string> moddedCommonEjectMessages
+} file
+
+function ClTitanCockpit_Init()
+{
+ if ( reloadingScripts )
+ return
+
+ RegisterSignal( "DisembarkCheck" )
+ RegisterSignal( "Rumble_Forward_End" )
+ RegisterSignal( "Rumble_Back_End" )
+ RegisterSignal( "Rumble_Left_End" )
+ RegisterSignal( "Rumble_Right_End" )
+ RegisterSignal( "EMP" )
+ RegisterSignal( "Ejecting" )
+ RegisterSignal( "TitanEMP_Internal" )
+ RegisterSignal( "TitanUnDoomed" )
+ RegisterSignal( "MonitorPlayerEjectAnimBeingStuck" )
+ RegisterSignal( "DisplayFrontierRank" )
+
+ PrecacheParticleSystem( $"xo_cockpit_spark_01" )
+
+ if ( !IsModelViewer() && !IsLobby() )
+ {
+ AddCreateCallback( "titan_cockpit", TitanCockpitInit )
+ }
+
+ if ( !reloadingScripts )
+ {
+ level.cockpitGeoRef <- null
+ }
+
+ AddPlayerFunc( TitanCockpit_AddPlayer )
+
+ AddCinematicEventFlagChangedCallback( CE_FLAG_TITAN_3P_CAM, CinematicEventFlagChanged )
+ AddCinematicEventFlagChangedCallback( CE_FLAG_INTRO, CinematicEventFlagChanged )
+
+ AddCallback_PlayerClassChanged( UpdateLastPlayerSettings )
+
+ AddTitanCockpitManagedRUI( Scorch_CreateHotstreakBar, Scorch_DestroyHotstreakBar, Scorch_ShouldCreateHotstreakBar, RUI_DRAW_COCKPIT ) //RUI_DRAW_HUD
+}
+
+void function UpdateLastPlayerSettings( entity player )
+{
+ if ( IsPilot( player ) )
+ file.lastPilotSettings = player.GetPlayerSettings()
+}
+
+TitanBindings function GetTitanBindings()
+{
+ TitanBindings Table
+ Table.PlayerPressed_Eject = PlayerPressed_Eject
+ Table.PlayerPressed_EjectEnable = PlayerPressed_EjectEnable
+ return Table
+}
+
+bool function RegisterTitanBindings( entity player, TitanBindings bind )
+{
+ if ( player != GetLocalViewPlayer() )
+ return false
+
+ if ( player != GetLocalClientPlayer() )
+ return false
+
+ RegisterConCommandTriggeredCallback( "+useAndReload", bind.PlayerPressed_Eject )
+ RegisterConCommandTriggeredCallback( "+use", bind.PlayerPressed_Eject )
+
+ RegisterConCommandTriggeredCallback( "+scriptCommand1", bind.PlayerPressed_EjectEnable )
+
+ return true
+}
+
+void function DeregisterTitanBindings( TitanBindings bind )
+{
+ DeregisterConCommandTriggeredCallback( "+useAndReload", bind.PlayerPressed_Eject )
+ DeregisterConCommandTriggeredCallback( "+use", bind.PlayerPressed_Eject )
+
+ if ( GetMapName() != "" )
+ {
+ DeregisterConCommandTriggeredCallback( "+scriptCommand1", bind.PlayerPressed_EjectEnable )
+ }
+}
+
+
+void function TitanCockpit_AddPlayer( entity player )
+{
+ if ( IsModelViewer() )
+ return
+
+ player.s.lastCockpitDamageSoundTime <- 0
+ player.s.inTitanCockpit <- false
+ player.s.lastDialogTime <- 0
+ player.s.titanCockpitDialogActive <- false
+ player.s.titanCockpitDialogAliasList <- []
+
+ player.s.hitVectors <- []
+}
+
+void function TitanCockpitInit( entity cockpit )
+{
+ entity player = GetLocalViewPlayer()
+ Assert( player.GetCockpit() == cockpit )
+
+ cockpit.s.ejectStartTime <- 0 // placed here to fix http://bugzilla.respawn.net/show_bug.cgi?id=156786
+
+ if ( !IsAlive( player ) )
+ return
+
+ if ( !IsTitanCockpitModelName( cockpit.GetModelName() ) || IsWatchingThirdPersonKillReplay() )
+ {
+ player.s.inTitanCockpit = false
+ return
+ }
+
+ if ( !player.s.inTitanCockpit )
+ TitanEmbarkDSP( 0.5 )
+
+ player.s.inTitanCockpit = true
+
+ // code aint callin this currently
+ CodeCallback_PlayerInTitanCockpit( GetLocalViewPlayer(), GetLocalViewPlayer() )
+
+ // move this
+ array<entity> targets = GetClientEntArrayBySignifier( "info_target" )
+ foreach ( target in targets )
+ {
+ if ( target.GetTargetName() != "cockpit_geo_ref" )
+ continue
+
+ level.cockpitGeoRef = target
+ }
+
+ entity cockpitParent = expect entity( level.cockpitGeoRef )
+
+ if ( !IsValid( cockpitParent ) )
+ cockpitParent = GetLocalViewPlayer()
+
+ cockpit.s.empInfo <- {}
+ cockpit.s.empInfo["xOffset"] <- 0
+ cockpit.s.empInfo["yOffset"] <- 0
+ cockpit.s.empInfo["startTime"] <- 0
+ cockpit.s.empInfo["duration"] <- 0
+ cockpit.s.empInfo["sub_count"] <- 0
+ cockpit.s.empInfo["sub_start"] <- 0
+ cockpit.s.empInfo["sub_duration"] <- 0
+ cockpit.s.empInfo["sub_pause"] <- 0
+ cockpit.s.empInfo["sub_alpha"] <- 0
+
+ cockpit.s.cockpitType <- 1
+ cockpit.s.FOV <- 70
+
+ cockpit.e.body = CreateCockpitBody( cockpit, player, cockpitParent )
+
+ thread TitanCockpitAnimThink( cockpit, cockpit.e.body )
+
+ if ( player.IsTitan() && IsAlive( player ) ) // pilot with titan cockpit gets thrown from titan
+ thread TitanCockpitDoomedThink( cockpit, player )
+
+ SetCockpitLightingEnabled( 0, true )
+ ShowRUIHUD( cockpit )
+}
+
+//bind r "script_client ReloadScripts();script_client GetLocalViewPlayer().GetCockpit().Destroy()"
+void function ShowRUIHUD( entity cockpit )
+{
+ // update topo positions
+ int cameraAttachId = cockpit.LookupAttachment( "CAMERA" )
+ vector cameraOrigin = cockpit.GetAttachmentOrigin( cameraAttachId )
+
+ int lowerScreenAttachId = cockpit.LookupAttachment( "COCKPIT_HUD_BOTTOM" )
+ vector lowerScreenOrigin = cockpit.GetAttachmentOrigin( lowerScreenAttachId )
+ vector lowerScreenAngles = cockpit.GetAttachmentAngles( lowerScreenAttachId )
+
+ int instrument1AttachId = cockpit.LookupAttachment( "COCKPIT_OBJ_1" )
+ vector instrument1Origin = cockpit.GetAttachmentOrigin( instrument1AttachId )
+ vector instrument1Angles = cockpit.GetAttachmentAngles( instrument1AttachId )
+
+ lowerScreenOrigin = lowerScreenOrigin - cameraOrigin
+ vector lowerScreenPosition = <lowerScreenOrigin.x, lowerScreenOrigin.y + TITAN_COCKPIT_LOWER_RUI_SCREEN_SQUARE_SIZE * .5, lowerScreenOrigin.z + (TITAN_COCKPIT_LOWER_RUI_SCREEN_SQUARE_SIZE) * .5>
+
+ instrument1Origin = instrument1Origin - cameraOrigin
+ vector instrument1Position = <instrument1Origin.x, instrument1Origin.y, instrument1Origin.z>
+ vector instrument1RightVector = AnglesToRight( instrument1Angles ) * -1
+ vector instrument1DownVector = AnglesToUp( instrument1Angles ) * -1
+
+ RuiTopology_UpdatePos( clGlobal.topoTitanCockpitLowerHud, lowerScreenPosition, <0, -TITAN_COCKPIT_LOWER_RUI_SCREEN_SQUARE_SIZE, 0>, <0, 0, -(TITAN_COCKPIT_LOWER_RUI_SCREEN_SQUARE_SIZE * TITAN_COCKPIT_LOWER_RUI_SCREEN_HEIGHT_SCALE)> )
+ RuiTopology_UpdatePos( clGlobal.topoTitanCockpitInstrument1, instrument1Position - (instrument1RightVector * TITAN_COCKPIT_INSTRUMENT1_RUI_SCREEN_SQUARE_SIZE * 0.5) - (instrument1DownVector * TITAN_COCKPIT_INSTRUMENT1_RUI_SCREEN_SQUARE_SIZE * 0.5), instrument1RightVector * TITAN_COCKPIT_INSTRUMENT1_RUI_SCREEN_SQUARE_SIZE, instrument1DownVector * TITAN_COCKPIT_INSTRUMENT1_RUI_SCREEN_SQUARE_SIZE )
+
+ // create ruis
+ entity player = GetLocalViewPlayer()
+
+ #if SP
+ file.coreHintRui = CreateTitanCockpitRui( $"ui/core_hint.rpak" )
+ #endif
+
+ file.cockpitRui = CreateTitanCockpitRui( $"ui/ajax_cockpit_base.rpak" )
+ RuiTrackFloat3( file.cockpitRui, "playerOrigin", player, RUI_TRACK_ABSORIGIN_FOLLOW )
+ RuiTrackFloat3( file.cockpitRui, "playerEyeAngles", player, RUI_TRACK_EYEANGLES_FOLLOW )
+ RuiTrackFloat( file.cockpitRui, "healthFrac", player, RUI_TRACK_HEALTH )
+ RuiTrackFloat( file.cockpitRui, "shieldFrac", player, RUI_TRACK_SHIELD_FRACTION )
+ RuiTrackFloat( file.cockpitRui, "dashFrac", player, RUI_TRACK_PLAYER_SUIT_POWER )
+ RuiSetFloat( file.cockpitRui, "ejectManualTimeOut", EJECT_FADE_TIME )
+ RuiSetFloat( file.cockpitRui, "ejectButtonTimeOut", TITAN_EJECT_MAX_PRESS_DELAY )
+ RuiSetGameTime( file.cockpitRui, "ejectManualStartTime", -60.0 )
+ RuiSetGameTime( file.cockpitRui, "ejectButtonPressTime", -60.0 )
+ #if MP
+ string titanName = GetTitanCharacterName( player )
+ if ( titanName == "vanguard" )
+ {
+ RuiSetString( file.cockpitRui, "titanInfo1", GetVanguardCoreString( player, 1 ) )
+ RuiSetString( file.cockpitRui, "titanInfo2", GetVanguardCoreString( player, 2 ) )
+ RuiSetString( file.cockpitRui, "titanInfo3", GetVanguardCoreString( player, 3 ) )
+ RuiSetString( file.cockpitRui, "titanInfo4", GetVanguardCoreString( player, 4 ) )
+ }
+
+ file.cockpitAdditionalRui = CreateTitanCockpitRui( $"ui/ajax_cockpit_fd.rpak" )
+ RuiSetFloat( file.cockpitAdditionalRui, "ejectManualTimeOut", EJECT_FADE_TIME )
+ RuiSetFloat( file.cockpitAdditionalRui, "ejectButtonTimeOut", TITAN_EJECT_MAX_PRESS_DELAY )
+ RuiSetGameTime( file.cockpitAdditionalRui, "ejectManualStartTime", -60.0 )
+
+ RuiSetDrawGroup( file.cockpitAdditionalRui, RUI_DRAW_NONE )
+ #endif
+
+#if SP
+ bool ejectIsAllowed = false
+#else
+ bool ejectIsAllowed = !TitanEjectIsDisabled()
+#endif
+ RuiSetBool( file.cockpitRui, "ejectIsAllowed", ejectIsAllowed )
+
+ string playerSettings = GetLocalViewPlayer().GetPlayerSettings()
+ float health = player.GetPlayerModHealth()
+ float healthPerSegment = GetPlayerSettingsFieldForClassName_HealthPerSegment( playerSettings )
+ RuiSetInt( file.cockpitRui, "numHealthSegments", int( health / healthPerSegment ) )
+ RuiTrackFloat( file.cockpitRui, "cockpitColor", player, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.cockpitColor )
+
+ file.cockpitLowerRui = CreateTitanCockpitLowerRui( $"ui/ajax_cockpit_lower.rpak" )
+ RuiTrackFloat( file.cockpitLowerRui, "dashFrac", player, RUI_TRACK_PLAYER_SUIT_POWER )
+ RuiTrackFloat3( file.cockpitLowerRui, "playerEyeAngles", player, RUI_TRACK_EYEANGLES_FOLLOW )
+ RuiTrackFloat( file.cockpitLowerRui, "cockpitColor", player, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.cockpitColor )
+
+ var instrument1Rui = CreateTitanCockpitInstrument1Rui( $"ui/ajax_cockpit_insturment1.rpak" )
+ RuiTrackFloat3( instrument1Rui, "playerEyeAngles", player, RUI_TRACK_EYEANGLES_FOLLOW )
+
+ int numDashPips = int( floor( 100 / GetSettingsForPlayer_DodgeTable( GetLocalViewPlayer() )["dodgePowerDrain"] ) )
+ RuiSetInt( file.cockpitRui, "numDashSegments", numDashPips )
+ RuiSetInt( file.cockpitLowerRui, "numDashSegments", numDashPips )
+
+ thread CockpitDoomedThink( cockpit )
+ thread TitanCockpitDestroyRuisOnDeath( cockpit )
+ thread TitanCockpitHealthChangedThink( cockpit, player )
+
+ #if MP
+ if ( GetCurrentPlaylistVarInt( "aegis_upgrades", 0 ) == 1 && !IsSpectating() && !IsWatchingKillReplay() )
+ thread DisplayFrontierRank( file.isFirstBoot )
+ #endif
+ file.isFirstBoot = false
+
+ UpdateTitanCockpitVisibility()
+}
+
+#if MP
+void function DisplayFrontierRank( bool isFirstBoot = true )
+{
+ GetLocalClientPlayer().Signal( "DisplayFrontierRank" )
+ GetLocalClientPlayer().EndSignal( "DisplayFrontierRank" )
+
+ wait 2.0
+
+ TitanLoadoutDef titanLoadout = GetTitanLoadoutFromPersistentData( GetLocalClientPlayer(), GetPersistentSpawnLoadoutIndex( GetLocalClientPlayer(), "titan" ) )
+ string titanClass = titanLoadout.titanClass
+
+ array<ItemDisplayData> titanUpgrades = FD_GetUpgradesForTitanClass( titanClass )
+ int maxActiveIndex
+ foreach ( index, item in titanUpgrades )
+ {
+ RuiSetImage( file.cockpitAdditionalRui, "upgradeIcon" + (index + 1), item.image )
+ RuiSetString( file.cockpitAdditionalRui, "upgradeName" + (index + 1), item.name )
+
+ if ( !IsSubItemLocked( GetLocalClientPlayer(), item.ref, item.parentRef ) )
+ maxActiveIndex++
+ }
+
+ RuiSetDrawGroup( file.cockpitAdditionalRui, RUI_DRAW_COCKPIT )
+
+ bool firstBootDisplay
+ if ( GameRules_GetGameMode() == FD )
+ firstBootDisplay = isFirstBoot || !GetGlobalNetBool( "FD_waveActive" )
+ else
+ firstBootDisplay = isFirstBoot
+
+ RuiSetBool( file.cockpitAdditionalRui, "isFirstBoot", firstBootDisplay )
+ RuiSetImage( file.cockpitAdditionalRui, "titanIcon", GetIconForTitanClass( titanClass ) )
+ RuiSetInt( file.cockpitAdditionalRui, "titanRank", FD_TitanGetLevel( GetLocalClientPlayer(), titanClass ) )
+ RuiSetInt( file.cockpitAdditionalRui, "maxActiveIndex", maxActiveIndex )
+ RuiSetGameTime( file.cockpitAdditionalRui, "updateTime", Time() )
+
+ EmitSoundOnEntity( GetLocalClientPlayer(), "UI_InGame_FD_MetaUpgradeAnnouncement" )
+
+ if ( firstBootDisplay )
+ {
+ wait 2.0
+
+ for ( int index = 0; index < maxActiveIndex; index++ )
+ {
+ EmitSoundOnEntity( GetLocalClientPlayer(), "UI_InGame_FD_MetaUpgradeTextAppear" )
+
+ wait 0.85
+
+ EmitSoundOnEntity( GetLocalClientPlayer(), "UI_InGame_FD_MetaUpgradeBarFill" )
+
+ wait 0.15
+ }
+ }
+ else
+ {
+ wait 0.5
+
+ for ( int index = 0; index < maxActiveIndex; index++ )
+ {
+ EmitSoundOnEntity( GetLocalClientPlayer(), "UI_InGame_FD_MetaUpgradeBarFill" )
+ wait 0.05
+ }
+ }
+}
+
+string function GetVanguardCoreString( entity player, int index )
+{
+ Assert( player.IsTitan() )
+
+ if ( !IsConnected() ) //Persistence isn't available when we disconnect
+ return ""
+
+ if ( player != GetLocalClientPlayer() ) //Client Persistence doesn't know about other players.
+ return ""
+
+ TitanLoadoutDef loadout = GetActiveTitanLoadout( player )
+
+ entity soul = player.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return ""
+
+ if ( index == 1 )
+ {
+ if ( soul.GetTitanSoulNetInt( "upgradeCount" ) >= 1 )
+ {
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE1_TITLE" ), Localize( GetItemName( loadout.passive4 ) ) )
+ }
+ else
+ {
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE1_TITLE" ), Localize( "#UPGRADE_IN_PROGRESS" ) )
+ }
+ }
+ if ( index == 2 )
+ {
+ if ( soul.GetTitanSoulNetInt( "upgradeCount" ) >= 2 )
+ {
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE2_TITLE" ), Localize( GetItemName( loadout.passive5 ) ) )
+ }
+ else
+ {
+ if ( soul.GetTitanSoulNetInt( "upgradeCount" ) >= 1 )
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE2_TITLE" ), Localize( "#UPGRADE_IN_PROGRESS" ) )
+ else
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE2_TITLE" ), Localize( "#UPGRADE_NOT_INSTALLED" ) )
+ }
+ }
+ if ( index == 3 )
+ {
+ if ( soul.GetTitanSoulNetInt( "upgradeCount" ) >= 3 )
+ {
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE3_TITLE" ), Localize( GetItemName( loadout.passive6 ) ) )
+ }
+ else
+ {
+ if ( soul.GetTitanSoulNetInt( "upgradeCount" ) >= 2 )
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE3_TITLE" ), Localize( "#UPGRADE_IN_PROGRESS" ) )
+ else
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE3_TITLE" ), Localize( "#UPGRADE_NOT_INSTALLED" ) )
+ }
+ }
+ if ( index == 4 )
+ {
+ printt( loadout.passive4 )
+ if ( loadout.passive4 == "pas_vanguard_core1" ) // Arc Rounds
+ {
+ entity offhandWeapon = player.GetOffhandWeapon( OFFHAND_RIGHT )
+ if ( IsValid( offhandWeapon ) && ( offhandWeapon.HasMod( "missile_racks" ) || offhandWeapon.HasMod( "upgradeCore_MissileRack_Vanguard" ) ) )
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE4_TITLE" ), Localize( "#GEAR_VANGUARD_CORE2" ) )
+ offhandWeapon = player.GetOffhandWeapon( OFFHAND_LEFT )
+ if ( IsValid( offhandWeapon ) && ( offhandWeapon.HasMod( "energy_transfer" ) || offhandWeapon.HasMod( "energy_field_energy_transfer" ) ) )
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE4_TITLE" ), Localize( "#GEAR_VANGUARD_CORE3" ) )
+ }
+ else if ( loadout.passive4 == "pas_vanguard_core2" ) // Missile Racks
+ {
+ entity weapon = player.GetMainWeapons()[0]
+ if ( IsValid( weapon ) && ( weapon.HasMod( "arc_rounds" ) || weapon.HasMod( "arc_rounds_with_battle_rifle" ) ) )
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE4_TITLE" ), Localize( "#GEAR_VANGUARD_CORE1" ) )
+ entity offhandWeapon = player.GetOffhandWeapon( OFFHAND_LEFT )
+ if ( IsValid( offhandWeapon ) && ( offhandWeapon.HasMod( "energy_transfer" ) || offhandWeapon.HasMod( "energy_field_energy_transfer" ) ) )
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE4_TITLE" ), Localize( "#GEAR_VANGUARD_CORE3" ) )
+ }
+ else if ( loadout.passive4 == "pas_vanguard_core3" ) // Energy Transfer
+ {
+ entity weapon = player.GetMainWeapons()[0]
+ if ( IsValid( weapon ) && ( weapon.HasMod( "arc_rounds" ) || weapon.HasMod( "arc_rounds_with_battle_rifle" ) ) )
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE4_TITLE" ), Localize( "#GEAR_VANGUARD_CORE1" ) )
+ entity offhandWeapon = player.GetOffhandWeapon( OFFHAND_RIGHT )
+ if ( IsValid( offhandWeapon ) && ( offhandWeapon.HasMod( "missile_racks" ) || offhandWeapon.HasMod( "upgradeCore_MissileRack_Vanguard" ) ) )
+ return Localize( "#TITAN_UPGRADE_STATUS_N_N", Localize( "#TITAN_UPGRADE4_TITLE" ), Localize( "#GEAR_VANGUARD_CORE2" ) )
+ }
+ return ""
+ }
+
+ unreachable
+}
+#endif
+
+void function SetUnlimitedDash( bool active )
+{
+ if ( file.cockpitLowerRui == null )
+ return
+
+ RuiSetBool( file.cockpitLowerRui, "hasUnlimitedDash", active )
+}
+
+void function UpdateEjectHud_SetManualEjectStartTime( entity player )
+{
+ float timeNow = Time()
+ player.p.ejectEnableTime = timeNow
+
+ if ( file.cockpitRui != null )
+ RuiSetGameTime( file.cockpitRui, "ejectManualStartTime", timeNow )
+
+ if ( file.cockpitAdditionalRui != null )
+ RuiSetGameTime( file.cockpitAdditionalRui, "ejectManualStartTime", timeNow )
+}
+
+void function UpdateEjectHud_SetButtonPressTime( entity player )
+{
+ float timeNow = Time()
+ player.p.ejectPressTime = timeNow
+
+ if ( file.cockpitRui != null )
+ RuiSetGameTime( file.cockpitRui, "ejectButtonPressTime", timeNow )
+
+ //if ( file.cockpitAdditionalRui != null )
+ // RuiSetGameTime( file.cockpitAdditionalRui, "ejectButtonPressTime", timeNow )
+}
+
+void function UpdateEjectHud_SetButtonPressCount( entity player, int buttonCount )
+{
+ player.p.ejectPressCount = buttonCount
+
+ if ( file.cockpitRui != null )
+ RuiSetInt( file.cockpitRui, "ejectButtonCount", buttonCount )
+
+ //if ( file.cockpitAdditionalRui != null )
+ // RuiSetInt( file.cockpitAdditionalRui, "ejectButtonCount", buttonCount )
+}
+
+void function UpdateTitanCockpitVisibility()
+{
+ entity player = GetLocalViewPlayer()
+ if ( !IsValid( player ) )
+ return
+
+ if ( Tone_ShouldCreateTrackerHud( player ) )
+ thread Tone_HudThink( player )
+ else
+ player.Signal( "StopToneHud" )
+
+ foreach ( managedRUI in file.titanCockpitManagedRUIs )
+ {
+ bool shouldCreate = managedRUI.shouldCreate()
+ if ( !managedRUI.exists && shouldCreate )
+ {
+ var rui = managedRUI.create()
+
+ bool found = false
+ foreach ( cockpitRui in player.p.titanCockpitRUIs )
+ {
+ if ( cockpitRui.rui == rui )
+ found = true
+ }
+ if ( !found )
+ {
+ TitanCockpitRUI tcRUI
+ tcRUI.rui = rui
+ tcRUI.drawGroup = managedRUI.drawGroup
+ player.p.titanCockpitRUIs.append( tcRUI )
+ }
+
+ managedRUI.exists = true
+ }
+ else if ( managedRUI.exists && !shouldCreate )
+ {
+ managedRUI.destroy()
+ managedRUI.exists = false
+ }
+ }
+
+ bool isVisible = true
+
+ int ceFlags = player.GetCinematicEventFlags()
+ if ( (ceFlags & CE_FLAG_INTRO) || (ceFlags & CE_FLAG_TITAN_3P_CAM) )
+ isVisible = false
+ if ( clGlobal.isSoloDialogMenuOpen )
+ isVisible = false
+
+ for ( int i = player.p.titanCockpitRUIs.len() - 1; i >= 0; i-- )
+ {
+ TitanCockpitRUI tcRUI = player.p.titanCockpitRUIs[ i ]
+ RuiSetDrawGroup( tcRUI.rui, isVisible ? tcRUI.drawGroup : RUI_DRAW_NONE )
+ }
+}
+
+void function AddTitanCockpitManagedRUI( var functionref() createFunc, void functionref() destroyFunc, bool functionref() shouldCreateFunc, int drawGroup )
+{
+ TitanCockpitManagedRUI managedRUI
+ managedRUI.create = createFunc
+ managedRUI.destroy = destroyFunc
+ managedRUI.shouldCreate = shouldCreateFunc
+ managedRUI.drawGroup = drawGroup
+
+ file.titanCockpitManagedRUIs.append( managedRUI )
+}
+
+void function CinematicEventFlagChanged( entity player )
+{
+ UpdateTitanCockpitVisibility()
+}
+
+void function CockpitDoomedThink( entity cockpit )
+{
+ entity player = GetLocalViewPlayer()
+ cockpit.EndSignal( "OnDestroy" )
+
+ while ( IsAlive( player ) )
+ {
+ entity soul = player.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Defensive fix for bug 227087. Assumption is that the cockpit is likely to be destroyed soon if the soul is invalid.
+ return
+ if ( !soul.IsDoomed() )
+ player.WaitSignal( "Doomed" )
+
+ SetCockpitUIDoomedState( true )
+
+ if ( !IsValid( soul ) ) //Defensive fix for bug 227087. Assumption is that the cockpit is likely to be destroyed soon if the soul is invalid.
+ return
+ if ( soul.IsDoomed() )
+ player.WaitSignal( "TitanUnDoomed" )
+
+ SetCockpitUIDoomedState( false )
+ }
+}
+
+void function SetCockpitUIEjectingState( bool state )
+{
+ if ( file.cockpitRui != null )
+ RuiSetBool( file.cockpitRui, "isEjecting", state )
+
+ if ( file.cockpitAdditionalRui != null )
+ RuiSetBool( file.cockpitAdditionalRui, "isEjecting", state )
+
+ if ( file.cockpitLowerRui != null )
+ {
+ RuiSetBool( file.cockpitLowerRui, "isEjecting", state )
+ if ( state )
+ RuiSetString( file.cockpitLowerRui, "ejectPrompt", Localize( RollRandomEjectString() ) )
+ else
+ RuiSetString( file.cockpitLowerRui, "ejectPrompt", "" )
+ }
+}
+
+void function SetCockpitUIDoomedState( bool state )
+{
+ if ( file.cockpitRui != null )
+ RuiSetBool( file.cockpitRui, "isDoomed", state )
+
+ if ( file.cockpitAdditionalRui != null )
+ RuiSetBool( file.cockpitAdditionalRui, "isDoomed", state )
+}
+
+void function TitanCockpitDestroyRui( var ruiToDestroy )
+{
+ if ( ruiToDestroy == null )
+ return
+
+ entity player = GetLocalViewPlayer()
+
+ for ( int i = player.p.titanCockpitRUIs.len() - 1; i >= 0; i-- )
+ {
+ TitanCockpitRUI tcRUI = player.p.titanCockpitRUIs[ i ]
+ if ( tcRUI.rui == ruiToDestroy )
+ {
+ RuiDestroy( tcRUI.rui )
+ player.p.titanCockpitRUIs.remove( i )
+ }
+ }
+}
+
+void function TitanCockpitDestroyRuisOnDeath( entity cockpit )
+{
+ entity player = GetLocalViewPlayer()
+
+ OnThreadEnd(
+ function() : ( cockpit )
+ {
+ foreach ( managedRUI in file.titanCockpitManagedRUIs )
+ {
+ if ( managedRUI.exists )
+ {
+ managedRUI.destroy()
+ managedRUI.exists = false
+ }
+ }
+
+ entity player = GetLocalViewPlayer()
+ for ( int i = player.p.titanCockpitRUIs.len() - 1; i >= 0; i-- )
+ {
+ RuiDestroy( player.p.titanCockpitRUIs[ i ].rui )
+ player.p.titanCockpitRUIs.remove( i )
+ }
+
+ player = GetLocalClientPlayer()
+ if ( IsValid( player ) )
+ player.Signal( "DisplayFrontierRank" )
+ file.cockpitAdditionalRui = null
+ file.cockpitRui = null
+ file.cockpitLowerRui = null
+ file.coreHintRui = null
+ }
+ )
+
+ player.EndSignal( "OnDeath" )
+ cockpit.EndSignal( "OnDestroy" )
+ WaitForever()
+}
+
+function CockpitBodyThink( cockpit, cockpitBody )
+{
+ cockpitBody.EndSignal( "OnDestroy" )
+
+ cockpit.WaitSignal( "OnDestroy" )
+
+ cockpitBody.Destroy()
+}
+
+
+entity function CreateCockpitBody( entity cockpit, entity player, entity cockpitParent )
+{
+ #if SP
+ string bodySettings = DEFAULT_PILOT_SETTINGS
+ #else
+ string bodySettings = file.lastPilotSettings
+ if ( bodySettings == "" || bodySettings == "spectator" )
+ bodySettings = Loadouts_GetSetFileForRequestedClass( player )
+ if ( bodySettings == "" )
+ bodySettings = "pilot_base"
+ #endif
+
+ asset bodyModelName = GetPlayerSettingsAssetForClassName( bodySettings, "armsmodel" )
+ #if DEV
+ if ( bodySettings == "" )
+ {
+ CodeWarning( "Couldn't find armsmodel for set file: " + bodySettings )
+ }
+ #endif
+
+ entity cockpitBody = CreateClientSidePropDynamic( cockpitParent.GetOrigin(), Vector( 0, 0, 0 ), bodyModelName )
+ cockpitBody.EnableRenderWithCockpit()
+ cockpitBody.SetOrigin( cockpit.GetOrigin() )
+ cockpitBody.SetParent( cockpit )
+
+ thread CockpitBodyThink( cockpit, cockpitBody )
+
+ return cockpitBody
+}
+
+function TitanEmbarkDSP( transitionTime )
+{
+}
+
+function TitanDisembarkDSP( transitionTime )
+{
+}
+
+function TitanCockpit_EMPFadeScale( entity cockpit, elapsedMod = 0 )
+{
+ local fadeInTime = 0.0
+ local fadeOutTime = 1.5
+ local elapsedTime = Time() - cockpit.s.empInfo.startTime
+ elapsedTime += elapsedMod
+
+ // ToDo:
+ // Fade in/out from last frames amount so it doesnt pop
+ // Make strength var to control max fade ( less strength returns max of like 0.5 )
+
+ //------------------------
+ // EMP effect is finished
+ //------------------------
+
+ //printt( "elapsedTime:" + elapsedTime + " cockpit.s.empInfo.duration:" + cockpit.s.empInfo.duration + " fadeOutTime:" + fadeOutTime )
+ if ( elapsedTime < cockpit.s.empInfo.duration - fadeOutTime )
+ {
+ return 1.0
+ }
+
+
+ if ( elapsedTime >= fadeInTime + cockpit.s.empInfo.duration + fadeOutTime )
+ {
+ cockpit.s.empInfo.startTime = 0
+ return 0.0
+ }
+
+ //------------------------
+ // EMP effect is starting
+ //------------------------
+
+ if ( elapsedTime < fadeInTime )
+ {
+ return GraphCapped( elapsedTime, 0.0, fadeInTime, 0.0, 1.0 )
+ }
+
+ //----------------------
+ // EMP effect is ending
+ //----------------------
+
+ if ( elapsedTime > fadeInTime + cockpit.s.empInfo.duration )
+ {
+ cockpit.s.empInfo["sub_count"] = 0
+ return GraphCapped( elapsedTime, fadeInTime + cockpit.s.empInfo.duration, fadeInTime + cockpit.s.empInfo.duration + fadeOutTime, 1.0, 0.0 )
+ }
+
+ //---------------------
+ // EMP flicker effect
+ //---------------------
+
+ // Time to start a new flicker
+ if ( cockpit.s.empInfo["sub_start"] == 0 )
+ {
+ cockpit.s.empInfo["sub_start"] <- Time()
+ if ( cockpit.s.empInfo["sub_count"] == 0 )
+ cockpit.s.empInfo["sub_pause"] <- RandomFloatRange( 0.5, 1.5 )
+ else
+ cockpit.s.empInfo["sub_pause"] <- RandomFloat( 0.5 )
+ cockpit.s.empInfo["sub_duration"] <- RandomFloatRange( 0.1, 0.4 )
+ cockpit.s.empInfo["sub_alpha"] <- RandomFloatRange( 0.4, 0.9 )
+ cockpit.s.empInfo["sub_count"]++;
+ }
+ local flickerElapsedTime = Time() - cockpit.s.empInfo["sub_start"]
+
+ // Start a new flicker if the current one is finished
+ if ( flickerElapsedTime > cockpit.s.empInfo["sub_pause"] + cockpit.s.empInfo["sub_duration"] )
+ cockpit.s.empInfo["sub_start"] = 0
+
+ if ( flickerElapsedTime < cockpit.s.empInfo["sub_pause"] )
+ {
+ // Pause before the flicker
+ return 1.0
+ }
+ else if ( flickerElapsedTime < cockpit.s.empInfo["sub_pause"] + ( cockpit.s.empInfo["sub_duration"] / 2.0 ) )
+ {
+ // First half of the flicker
+ return GraphCapped( flickerElapsedTime, 0.0, cockpit.s.empInfo["sub_duration"] / 2.0, 1.0, cockpit.s.empInfo["sub_alpha"] )
+ }
+ else
+ {
+ // Second half of the flicker
+ return GraphCapped( flickerElapsedTime, cockpit.s.empInfo["sub_duration"] / 2.0, cockpit.s.empInfo["sub_duration"], cockpit.s.empInfo["sub_alpha"], 1.0 )
+ }
+}
+
+function ServerCallback_TitanCockpitEMP( duration )
+{
+ thread TitanCockpit_DoEMP( duration / 4 )
+}
+
+function TitanCockpit_DoEMP( duration )
+{
+ entity player = GetLocalViewPlayer()
+ entity cockpit = player.GetCockpit()
+
+ if ( !IsValid( cockpit ) )
+ return
+
+ if ( !player.IsTitan() )
+ return
+
+ if ( !player.s.inTitanCockpit )
+ return
+
+ Signal( player, "EMP" )
+ EndSignal( player, "EMP" )
+ player.EndSignal( "OnDestroy" )
+
+ // this needs tweaking... looks a bit artificial
+ ClientCockpitShake( 0.25, 3, 1.0, Vector( 0, 0, 1 ) ) // amplitude, frequency, duration, direction
+
+ thread PlayCockpitEMPLights( cockpit, duration )
+
+ // Start the screens and vdu power outages
+ cockpit.s.empInfo.xOffset = RandomFloatRange( 0.5, 0.75 )
+ cockpit.s.empInfo.yOffset = RandomFloatRange( 0.5, 0.75 )
+ if ( CoinFlip() )
+ cockpit.s.empInfo.xOffset *= -1
+ if ( CoinFlip() )
+ cockpit.s.empInfo.yOffset *= -1
+
+ cockpit.s.empInfo.startTime = Time()
+ cockpit.s.empInfo.duration = duration
+
+ EmitSoundOnEntity( player, EMP_IMPARED_SOUND )
+ wait duration
+ FadeOutSoundOnEntity( player, EMP_IMPARED_SOUND, 1.5 )
+}
+
+function PlayCockpitEMPLights( cockpit, duration )
+{
+ duration += 1.5 // blend out
+ local attachID
+ local origin
+ local angles
+ local fxLights = []
+
+ string tagName = "COCKPIT" // SCR_CL_BL"
+ attachID = cockpit.LookupAttachment( tagName )
+ origin = cockpit.GetAttachmentOrigin( attachID )
+ origin.z -= 25
+ angles = Vector( 0, 0, 0 )
+ local lightTable = {}
+ lightTable.light <- CreateClientSideDynamicLight( origin, angles, Vector( 0.0, 0.0, 0.0 ), 80.0 )
+ lightTable.modulate <- true
+ fxLights.append( lightTable )
+
+ wait 0.5
+
+ foreach ( fxLight in fxLights )
+ {
+ fxLight.light.SetCockpitLight( true )
+ }
+
+ local startTime = Time()
+ local rate = 1.2
+
+ local endTime = Time() + duration
+
+ while ( IsValid( cockpit ) )
+ {
+ if ( Time() > endTime )
+ break
+
+ float subtractColor = GraphCapped( Time(), endTime - 0.25, endTime, 1.0, 0.0 )
+ local pulseFrac = GetPulseFrac( rate, startTime )
+ pulseFrac *= subtractColor
+ //pulseFrac -= fadeInColor
+
+ foreach ( index, fxLight in fxLights )
+ {
+ Assert( fxLight.modulate )
+ fxLight.light.SetLightColor( Vector( pulseFrac, 0, 0 ) )
+
+ // the case where fxLight.modulate == false used to be handled by this script, which used undefined variable fadeInColor:
+ // fxLight.light.SetLightColor( Vector( fadeInColor, fadeInColor, fadeInColor ) )
+ }
+
+ WaitFrame()
+ }
+
+ foreach ( fxLight in fxLights )
+ {
+ fxLight.light.Destroy()
+ }
+}
+
+
+function TitanCockpit_IsBooting( cockpit )
+{
+ return cockpit.GetTimeInCockpit() < 1.3
+}
+
+function TitanCockpitAnimThink( cockpit, body )
+{
+ cockpit.SetOpenViewmodelOffset( 20.0, 0.0, 10.0 )
+ cockpit.Anim_NonScriptedPlay( "atpov_cockpit_hatch_close_idle" )
+
+ if ( body )
+ body.Anim_NonScriptedPlay( "atpov_cockpit_hatch_close_idle" )
+}
+
+
+bool function IsDisplayingEjectInterface( entity player )
+{
+ if ( !player.IsTitan() )
+ return false
+
+ if ( player.ContextAction_IsMeleeExecution() ) //Could just check for ContextAction_IsActive() if we need to be more general
+ return false
+
+ if ( !GetDoomedState( player ) && Time() - player.p.ejectEnableTime > EJECT_FADE_TIME )
+ return false
+
+ if ( Riff_TitanExitEnabled() == eTitanExitEnabled.Never || Riff_TitanExitEnabled() == eTitanExitEnabled.DisembarkOnly )
+ return false
+
+ //if ( !CanDisembark( player ) )
+ // return false
+
+ return true
+}
+
+void function PlayerPressed_Eject( entity player )
+{
+ if ( !IsDisplayingEjectInterface( player ) )
+ return
+
+ if ( Time() - player.p.ejectPressTime > TITAN_EJECT_MAX_PRESS_DELAY )
+ UpdateEjectHud_SetButtonPressCount( player, 0 )
+
+ if ( !IsAlive( player ) )
+ return
+
+ EmitSoundOnEntity( player, "titan_eject_xbutton" )
+ EmitSoundOnEntity( player, "hud_boost_card_radar_jammer_redtextbeep_1p" )
+ UpdateEjectHud_SetButtonPressTime( player )
+ UpdateEjectHud_SetButtonPressCount( player, (player.p.ejectPressCount + 1) )
+
+ player.ClientCommand( "TitanEject " + player.p.ejectPressCount )
+
+ entity cockpit = player.GetCockpit()
+ if ( player.p.ejectPressCount < 3 || cockpit.s.ejectStartTime )
+ return
+
+ PlayerEjects( player, cockpit )
+}
+
+void function AddCommonEjectMessage( string message )
+{
+ file.moddedCommonEjectMessages.append( message )
+}
+
+void function AddRareEjectMessage( string message )
+{
+ file.moddedRareEjectMessages.append( message )
+}
+
+string function RollRandomEjectString()
+{
+ const int COCKPIT_EJECT_COMMON_COUNT = 6
+ const int COCKPIT_EJECT_RARE_COUNT = 36
+ const float CHANCE_FOR_RARE = 0.15
+
+ float randForType = RandomFloat( 1.0 )
+ if ( randForType < CHANCE_FOR_RARE )
+ {
+ int index = RandomInt( COCKPIT_EJECT_RARE_COUNT + file.moddedRareEjectMessages.len() )
+ if ( index < COCKPIT_EJECT_RARE_COUNT )
+ return "#COCKPIT_EJECT_RARE_" + index
+ else
+ return file.moddedRareEjectMessages[index - COCKPIT_EJECT_RARE_COUNT]
+ }
+
+ int index = RandomInt( COCKPIT_EJECT_COMMON_COUNT + file.moddedCommonEjectMessages.len() )
+ if ( index < COCKPIT_EJECT_COMMON_COUNT )
+ return "#COCKPIT_EJECT_COMMON_" + index
+ else
+ return file.moddedCommonEjectMessages[index - COCKPIT_EJECT_COMMON_COUNT]
+
+ unreachable
+}
+
+void function PlayerEjects( entity player, entity cockpit ) //Note that this can be run multiple times in a frame, e.g. get damaged by 4 pellets of a shotgun that brings the Titan into a doomed state with auto eject. Not ideal
+{
+ // prevent animation from playing if player is in the middle of execution
+ if ( player.ContextAction_IsActive() && !player.ContextAction_IsBusy() )
+ return
+
+ player.Signal( "Ejecting" )
+
+ SetCockpitUIEjectingState( true )
+
+ local ejectAlarmSound
+ cockpit.s.ejectStartTime = Time()
+ string animationName
+ if ( GetNuclearPayload( player ) > 0 )
+ {
+ animationName = "atpov_cockpit_eject_nuclear"
+ cockpit.Anim_NonScriptedPlay( animationName )
+ if ( IsValid( cockpit.e.body ) )
+ cockpit.e.body.Anim_NonScriptedPlay( "atpov_cockpit_eject_nuclear" )
+ ejectAlarmSound = TITAN_NUCLEAR_DEATH_ALARM
+ }
+ else
+ {
+ animationName = "atpov_cockpit_eject"
+ cockpit.Anim_NonScriptedPlay( animationName )
+ if ( IsValid( cockpit.e.body ) )
+ cockpit.e.body.Anim_NonScriptedPlay( "atpov_cockpit_eject" )
+
+ ejectAlarmSound = TITAN_ALARM_SOUND
+ }
+
+ thread LightingUpdateAfterOpeningCockpit()
+ thread EjectAudioThink( player, ejectAlarmSound )
+
+ float animDuration = cockpit.GetSequenceDuration( animationName )
+
+ thread MonitorPlayerEjectAnimBeingStuck( player, animDuration )
+}
+
+void function MonitorPlayerEjectAnimBeingStuck( entity player, float duration )
+{
+ player.Signal( "MonitorPlayerEjectAnimBeingStuck" )
+ player.EndSignal( "MonitorPlayerEjectAnimBeingStuck" )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "SettingsChanged" )
+
+
+ wait duration + 2.0 // 1s as a buffer
+
+ if ( player.IsTitan() )
+ {
+ entity cockpit = player.GetCockpit()
+ cockpit.Anim_NonScriptedPlay( "atpov_cockpit_hatch_close_idle" )
+ if ( IsValid( cockpit.e.body ) )
+ cockpit.e.body.Anim_NonScriptedPlay( "atpov_cockpit_hatch_close_idle" )
+
+ SetCockpitUIEjectingState( false )
+ }
+}
+
+function ServerCallback_EjectConfirmed()
+{
+ if ( !IsWatchingReplay() )
+ return
+
+ entity player = GetLocalViewPlayer()
+ entity cockpit = player.GetCockpit()
+
+ if ( !cockpit || !IsTitanCockpitModelName( cockpit.GetModelName() ) )
+ return
+
+ PlayerEjects( player, cockpit )
+}
+
+function EjectAudioThink( entity player, ejectAlarmSound = TITAN_ALARM_SOUND )
+{
+ EmitSoundOnEntity( player, ejectAlarmSound )
+ TitanCockpit_PlayDialog( player, "manualEjectNotice" )
+
+ player.EndSignal( "OnDeath" )
+
+ player.WaitSignal( "SettingsChanged" )
+
+ if ( player.GetPlayerClass() != "pilot" )
+ return
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( !IsAlive( player ) )
+ {
+ StopSoundOnEntity( player, TITAN_EJECT_ASCENT )
+ StopSoundOnEntity( player, TITAN_EJECT_DESCENT )
+ }
+ else
+ {
+ FadeOutSoundOnEntity( player, TITAN_EJECT_ASCENT, 0.25 )
+ FadeOutSoundOnEntity( player, TITAN_EJECT_DESCENT, 0.25 )
+ }
+
+ StopSoundOnEntity( player, TITAN_EJECT_APEX )
+ }
+ )
+
+ EmitSoundOnEntity( player, TITAN_EJECT_BOOST )
+
+ float startTime = Time()
+ float duration = GetSoundDuration( TITAN_EJECT_ASCENT )
+ EmitSoundOnEntity( player, TITAN_EJECT_ASCENT )
+ float timeOut = duration - 0.25
+ vector velocity
+ float diff = 0.0
+
+ const int STAGE_ASCENT = 1
+ const int STAGE_APEX = 2
+ const int STAGE_DESCENT = 3
+
+ int ejectStage = STAGE_ASCENT
+
+ string currentSound = TITAN_EJECT_ASCENT
+
+ while ( diff < timeOut )
+ {
+ PerfStart( 127 )
+
+ diff = (Time() - startTime)
+
+ velocity = player.GetVelocity()
+ float length = Length( velocity )
+
+ if ( diff > 0.5 )
+ {
+ if ( player.IsOnGround() )
+ {
+ PerfEnd( 127 )
+ break
+ }
+ }
+
+ if ( ejectStage != STAGE_DESCENT && velocity.z < 0 )
+ {
+ FadeOutSoundOnEntity( player, TITAN_EJECT_ASCENT, 0.25 )
+ timeOut = GetSoundDuration( TITAN_EJECT_DESCENT )
+ EmitSoundOnEntity( player, TITAN_EJECT_DESCENT )
+ currentSound = TITAN_EJECT_DESCENT
+ ejectStage = STAGE_DESCENT
+ }
+ else if ( ejectStage == STAGE_ASCENT && length < 400 )
+ {
+ EmitSoundOnEntity( player, TITAN_EJECT_APEX )
+ ejectStage = STAGE_APEX
+ }
+
+ PerfEnd( 127 )
+
+ WaitFrame()
+ }
+}
+
+function LightingUpdateAfterOpeningCockpit()
+{
+ while ( true )
+ {
+ if ( !GetLocalViewPlayer().s.inTitanCockpit )
+ break
+ WaitFrame()
+ }
+
+ SetCockpitLightingEnabled( 0, false )
+}
+
+
+function TonemappingUpdateAfterOpeningCockpit() //Deprecated, no longer used
+{
+ local duration = 3
+ local tonemapMin = 2
+ local tonemapMax = 5
+
+ while ( true )
+ {
+ if ( !GetLocalViewPlayer().s.inTitanCockpit )
+ break
+ WaitFrame()
+ }
+
+ SetCockpitLightingEnabled( 0, false )
+
+ AutoExposureSetExposureCompensationBias( tonemapMax )
+ AutoExposureSnap()
+ wait( 0.1 )
+
+ TitanDisembarkDSP( 0.5 )
+
+ local startTime = Time()
+ while ( true )
+ {
+ local time = Time() - startTime
+ float factor = GraphCapped( time, 0, duration, 1, 0 )
+ factor = factor * factor * factor
+ local toneMapScale = tonemapMin + (tonemapMax - tonemapMin) * factor
+ AutoExposureSetExposureCompensationBias( toneMapScale )
+ AutoExposureSnap()
+ wait 0
+ if ( factor == 0 )
+ break
+ }
+
+ AutoExposureSetExposureCompensationBias( 0 )
+}
+
+function ServerCallback_TitanEmbark()
+{
+ TitanCockpit_PlayDialog( GetLocalViewPlayer(), "embark" )
+}
+
+function ServerCallback_TitanDisembark()
+{
+ entity player = GetLocalViewPlayer()
+
+ thread LightingUpdateAfterOpeningCockpit()
+
+ //HideFriendlyIndicatorAndCrosshairNames()
+
+ //PlayMusic( "Music_FR_Militia_PilotAction2" )
+}
+
+
+function PlayerPressed_QuickDisembark( player )
+{
+ player.ClientCommand( "TitanDisembark" )
+}
+
+void function PlayerPressed_EjectEnable( entity player )
+{
+ if ( !player.IsTitan() )
+ return
+
+ if ( !IsAlive( player ) )
+ return
+
+ if ( IsValid( player.GetParent() ) )
+ return
+
+ if ( TitanEjectIsDisabled() )
+ {
+ EmitSoundOnEntity( player, "CoOp_SentryGun_DeploymentDeniedBeep" )
+ SetTimedEventNotification( 1.5, "" )
+ SetTimedEventNotification( 1.5, "#NOTIFY_EJECT_DISABLED" )
+ return
+ }
+
+ if ( Riff_TitanExitEnabled() == eTitanExitEnabled.Never || Riff_TitanExitEnabled() == eTitanExitEnabled.DisembarkOnly )
+ return
+
+ //if ( !CanDisembark( player ) )
+ // return
+
+ if ( player.ContextAction_IsMeleeExecution() ) //Could just check for ContextAction_IsActive() if we need to be more general
+ return
+
+ if ( player.GetHealth() == 1 )
+ {
+ #if MP
+ if ( !FD_ReadyUpEnabled() )
+ #endif
+ {
+ player.ClientCommand( "TitanEject " + 3 )
+ return
+ }
+ }
+
+ EmitSoundOnEntity( player, "titan_eject_dpad" )
+ UpdateEjectHud_SetManualEjectStartTime( player )
+ player.Signal( "UpdateRodeoAlert" ) // need this to hide titan stomp hint
+}
+
+float function CalcJoltMagnitude( player, cockpit, joltDir, float damageAmount, damageType, int damageSourceID )
+{
+ const float COCKPIT_MAX_JOLT_DAMAGE = 2000.0
+
+ float resultRaw = damageAmount / COCKPIT_MAX_JOLT_DAMAGE
+ return clamp( resultRaw, 0.0, 1.0 )
+}
+
+function JoltCockpit( cockpit, player, joltDir, float damageAmount, damageType, damageSourceId )
+{
+ float severity = CalcJoltMagnitude( player, cockpit, joltDir, damageAmount, damageType, expect int( damageSourceId ) )
+ player.CockpitJolt( joltDir, severity )
+}
+
+function RandomizeDir( dir, randPitch = 0, randYaw = 0, basePitch = 0, baseYaw = 0 )
+{
+ local pitch = RandomFloatRange( -randPitch, randPitch )
+ local yaw = RandomFloatRange( -randYaw, randYaw )
+ local angles = VectorToAngles( dir )
+ angles = AnglesCompose( angles, Vector( pitch, yaw, 0 ) )
+ angles = AnglesCompose( angles, Vector( basePitch, baseYaw, 0 ) )
+ return AnglesToForward( angles )
+}
+
+function TitanCockpitDoomedThink( cockpit, player )
+{
+ cockpit.EndSignal( "OnDestroy" )
+
+ local titanSoul = player.GetTitanSoul()
+
+ if ( titanSoul == null || !titanSoul.IsDoomed() )
+ WaitSignal( player, "Doomed", "Ejecting" )
+
+ local color = Vector( 0.6, 0.06, 0 )
+ local radius = 70.0
+
+ FlashCockpitLight( cockpit, color, radius, -1 )
+}
+
+void function TitanCockpitHealthChangedThink( cockpit, entity player )
+{
+ cockpit.EndSignal( "OnDestroy" )
+
+ while ( true )
+ {
+ table results = WaitSignal( player, "HealthChanged" )
+
+ if ( !IsAlive( player ) )
+ continue
+
+ float oldHealthFrac = float( results.oldHealth ) / float( player.GetMaxHealth() )
+ float newHealthFrac = float( results.newHealth ) / float( player.GetMaxHealth() )
+
+ if ( oldHealthFrac > newHealthFrac )
+ {
+ var rui = RuiCreate( $"ui/ajax_cockpit_lost_health_segment.rpak", clGlobal.topoTitanCockpitHud, RUI_DRAW_COCKPIT, 10 )
+ RuiSetGameTime( rui, "startTime", Time() )
+ RuiSetFloat( rui, "oldHealthFrac", oldHealthFrac )
+ RuiSetFloat( rui, "newHealthFrac", newHealthFrac )
+
+ string playerSettings = GetLocalViewPlayer().GetPlayerSettings()
+ float health = player.GetPlayerModHealth()
+ float healthPerSegment = GetPlayerSettingsFieldForClassName_HealthPerSegment( playerSettings )
+ RuiSetInt( rui, "numHealthSegments", int( health / healthPerSegment ) )
+ }
+ }
+}
+
+
+function FlashCockpitLight( cockpit, color, radius, duration, tag = "SCR_CL_BL" )
+{
+ cockpit.EndSignal( "TitanUnDoomed" )
+ cockpit.EndSignal( "OnDestroy" )
+
+ local attachID = cockpit.LookupAttachment( tag )
+ local origin = cockpit.GetAttachmentOrigin( attachID )
+ local angles = Vector( 0, 0, 0 )
+
+ local fxLight = CreateClientSideDynamicLight( origin, angles, color, radius )
+ fxLight.SetCockpitLight( true )
+ fxLight.SetParent( cockpit )
+
+ OnThreadEnd(
+ function() : ( fxLight )
+ {
+ fxLight.Destroy()
+ }
+ )
+
+ local startTime = Time()
+ local rate = 3.0
+
+ while ( IsValid( cockpit ) && (Time() < startTime + duration || duration == -1 ) )
+ {
+ local pulseFrac = GetPulseFrac( rate, startTime )
+ pulseFrac += 0.5
+ fxLight.SetLightColor( Vector( color.x * pulseFrac, color.y * pulseFrac, color.z * pulseFrac ) )
+
+ WaitFrame()
+ }
+}
+
+function PlayCockpitSparkFX_Internal( cockpit, string tagName )
+{
+ expect entity( cockpit )
+
+ // this is called from a delaythread so needs valid check
+ if ( !IsValid( cockpit ) )
+ return
+
+ int attachID = cockpit.LookupAttachment( tagName )
+ if ( attachID == 0 )
+ {
+ tagName = CoinFlip() ? "FX_TL_PANEL" : "FX_TR_PANEL"
+ attachID = cockpit.LookupAttachment( tagName )
+ Assert( attachID, "Could not find fallback attachment index " + attachID + " for '" + tagName + "'' in model " + GetLocalViewPlayer().GetCockpit().GetModelName() )
+ }
+
+ int fxID = GetParticleSystemIndex( $"xo_cockpit_spark_01" )
+ int fxInstID = PlayFXOnTag( cockpit, fxID, attachID )
+
+ EffectSetIsWithCockpit( fxInstID, true )
+}
+
+function PlayCockpitSparkFX( cockpit, int sparkCount )
+{
+ const int TAG_COUNT = 6
+ const string[TAG_COUNT] cockpitFXEmitTags = [ "FX_TL_PANEL", "FX_TR_PANEL", "FX_TC_PANELA", "FX_TC_PANELB", "FX_BL_PANEL", "FX_BR_PANEL" ]
+ array<int> playlist = [0,1,2,3,4,5]
+ playlist.randomize()
+
+ for ( int idx = 0; idx < sparkCount; idx++ )
+ {
+ int lookup = (idx % TAG_COUNT)
+ int tagIndex = playlist[lookup]
+ string tagName = cockpitFXEmitTags[tagIndex]
+ PlayCockpitSparkFX_Internal( cockpit, tagName )
+ }
+}
+
+const int DAMAGE_PER_SPARK = 1000
+const int SPARK_MULTIPLIER = 3
+
+int function CalSparkCountForHit( entity player, float damageAmount, bool becameDoomed )
+{
+ if ( becameDoomed )
+ return 20
+ if ( damageAmount <= 0 )
+ return 0
+
+ int healthNow = player.GetHealth()
+ int healthPrev = healthNow + int( damageAmount )
+ int healthMax = player.GetMaxHealth()
+
+ bool isDoomed = GetDoomedState( player )
+ int sparksNow = (healthNow / DAMAGE_PER_SPARK)
+ int sparksPrev = (healthPrev / DAMAGE_PER_SPARK)
+ if ( (healthPrev == healthMax) && !isDoomed )
+ --sparksPrev // no spark on first damage
+
+ int delta = (sparksPrev - sparksNow)
+ if ( delta < 0 )
+ return 0
+
+ return (delta * SPARK_MULTIPLIER)
+}
+
+function TitanCockpit_DamageFeedback( entity player, cockpit, float damageAmount, damageType, damageOrigin, damageSourceId, bool doomedNow, int doomedDamage )
+{
+ RumbleForTitanDamage( damageAmount )
+
+ vector joltDir = Normalize( player.CameraPosition() - damageOrigin )
+ float joltDamage = doomedNow ? float( doomedDamage ) : damageAmount
+ JoltCockpit( cockpit, player, joltDir, joltDamage, damageType, damageSourceId )
+
+ bool isShieldHit = (damageType & DF_SHIELD_DAMAGE) ? true : false
+ if ( isShieldHit )
+ return
+
+ int sparkCount = CalSparkCountForHit( player, damageAmount, doomedNow );
+ //printt( "sparks: " + sparkCount + " dmg: " + damageAmount + " - " + player.GetHealth() + " / " + player.GetMaxHealth() )
+ PlayCockpitSparkFX( cockpit, sparkCount )
+}
+
+function ServerCallback_TitanCockpitBoot()
+{
+ thread ServerCallback_TitanCockpitBoot_Internal()
+}
+
+function ServerCallback_TitanCockpitBoot_Internal()
+{
+ AutoExposureSetExposureCompensationBias( -6 )
+ AutoExposureSnap()
+ wait 0.1
+ AutoExposureSetExposureCompensationBias( 0 )
+}
+
+function ServerCallback_TitanEMP( maxValue, duration, fadeTime, doFlash = true, doSound = true )
+{
+ thread TitanEMP_Internal( maxValue, duration, fadeTime, doFlash, doSound )
+}
+
+function TitanEMP_Internal( maxValue, duration, fadeTime, doFlash = true, doSound = true )
+{
+ entity player = GetLocalViewPlayer()
+
+ player.Signal( "TitanEMP_Internal" )
+ player.EndSignal( "TitanEMP_Internal" )
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "SettingsChanged" )
+
+ local angles = Vector( 0, -90, 90 )
+
+ local wide = 16
+ local tall = 9
+
+ float fovOffset = Graph( player.GetFOV(), 75, 120, 4, 2.5 )
+
+ local empVgui = CreateClientsideVGuiScreen( "vgui_titan_emp", VGUI_SCREEN_PASS_VIEWMODEL, Vector(0,0,0), Vector(0,0,0), wide, tall );
+
+ //empVgui.SetParent( player.GetViewModelEntity(), "CAMERA_BASE" )
+ empVgui.SetRefract( true ) // Force refract resolve before drawing vgui. (This can cost GPU!)
+ empVgui.SetParent( player )
+ empVgui.SetAttachOffsetOrigin( < fovOffset, wide / 2, -tall / 2 > )
+ empVgui.SetAttachOffsetAngles( angles )
+
+ empVgui.GetPanel().WarpEnable()
+
+ local EMPScreenFX = HudElement( "EMPScreenFX", empVgui.GetPanel() )
+ local EMPScreenFlash = HudElement( "EMPScreenFlash", empVgui.GetPanel() )
+
+ OnThreadEnd(
+ function() : ( player, empVgui )
+ {
+ empVgui.Destroy()
+ }
+ )
+
+ EMPScreenFX.Show()
+ EMPScreenFX.SetAlpha( maxValue * 255 )
+ EMPScreenFX.FadeOverTimeDelayed( 0, fadeTime, duration )
+
+ if ( doFlash )
+ {
+ EMPScreenFlash.Show()
+ EMPScreenFlash.SetAlpha( 255 )
+ EMPScreenFlash.FadeOverTimeDelayed( 0, fadeTime + duration, 0 )
+ }
+
+ if ( doSound )
+ {
+ EmitSoundOnEntity( player, EMP_IMPARED_SOUND )
+ wait duration
+ FadeOutSoundOnEntity( player, EMP_IMPARED_SOUND, fadeTime )
+ }
+
+ wait fadeTime
+}
+
+
+void function LinkCoreHint( entity soul )
+{
+ if ( file.coreHintRui == null )
+ return
+
+ RuiTrackFloat( file.coreHintRui, "coreFrac", soul, RUI_TRACK_SCRIPT_NETWORK_VAR, GetNetworkedVariableIndex( "coreAvailableFrac" ) )
+}
+
+
+void function FlashCockpitHealth( vector color )
+{
+ if ( file.cockpitRui == null )
+ return
+
+ RuiSetGameTime( file.cockpitRui, "startFlashTime", Time() )
+ RuiSetFloat3( file.cockpitRui, "flashColor", color )
+}
+
+void function UpdateHealthSegmentCount()
+{
+ if ( file.cockpitRui == null )
+ return
+
+ entity player = GetLocalViewPlayer()
+ string playerSettings = player.GetPlayerSettings()
+ float health = player.GetPlayerModHealth()
+ float healthPerSegment = GetPlayerSettingsFieldForClassName_HealthPerSegment( playerSettings )
+ RuiSetInt( file.cockpitRui, "numHealthSegments", int( health / healthPerSegment ) )
+}
+#if MP
+void function NetworkedVarChangedCallback_UpdateVanguardRUICoreStatus( entity soul, int oldValue, int newValue, bool actuallyChanged )
+{
+ if ( file.cockpitRui == null )
+ return
+
+ if ( actuallyChanged == false )
+ return
+
+ entity player = GetLocalViewPlayer()
+ if ( !IsValid( player ) || !player.IsTitan() )
+ return
+
+ UpdateHealthSegmentCount()
+
+ string titanName = GetTitanCharacterName( player )
+ if ( titanName == "vanguard" )
+ {
+ RuiSetString( file.cockpitRui, "titanInfo1", GetVanguardCoreString( player, 1 ) )
+ RuiSetString( file.cockpitRui, "titanInfo2", GetVanguardCoreString( player, 2 ) )
+ RuiSetString( file.cockpitRui, "titanInfo3", GetVanguardCoreString( player, 3 ) )
+ RuiSetString( file.cockpitRui, "titanInfo4", GetVanguardCoreString( player, 4 ) )
+ }
+}
+#endif
+
+var function Scorch_CreateHotstreakBar()
+{
+ Assert( file.scorchHotstreakRui == null )
+
+ file.scorchHotstreakRui = CreateFixedTitanCockpitRui( $"ui/scorch_hotstreak_bar.rpak" )
+
+ RuiTrackFloat( file.scorchHotstreakRui, "coreMeterMultiplier", GetLocalViewPlayer(), RUI_TRACK_SCRIPT_NETWORK_VAR, GetNetworkedVariableIndex( "coreMeterModifier" ) )
+
+ return file.scorchHotstreakRui
+}
+
+void function Scorch_DestroyHotstreakBar()
+{
+ TitanCockpitDestroyRui( file.scorchHotstreakRui )
+ file.scorchHotstreakRui = null
+}
+
+bool function Scorch_ShouldCreateHotstreakBar()
+{
+ entity player = GetLocalViewPlayer()
+
+ if ( !IsAlive( player ) )
+ return false
+
+ array<entity> mainWeapons = player.GetMainWeapons()
+ if ( mainWeapons.len() == 0 )
+ return false
+
+ entity primaryWeapon = mainWeapons[0]
+ return primaryWeapon.HasMod( "fd_hot_streak" )
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut b/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut
index 755396e3..f17216fb 100644
--- a/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut
+++ b/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut
@@ -1,60 +1,42 @@
untyped
globalize_all_functions
-struct {
- int highestScore = 0
- int secondHighestScore = 0
-} file
-
-void function OnPrematchStart()
+GameStateStruct function DiscordRPC_GenerateGameState( GameStateStruct gs )
{
- if ( GetServerVar( "roundBased" ) )
- NSUpdateTimeInfo( level.nv.roundEndTime - Time() )
- else
- NSUpdateTimeInfo( level.nv.gameEndTime - Time() )
-}
+ int highestScore = 0
+ int secondHighest = 0
-void function NSUpdateGameStateClientStart()
-{
- #if MP
- AddCallback_GameStateEnter( eGameState.Prematch, OnPrematchStart )
- #endif
-
- thread NSUpdateGameStateLoopClient()
- OnPrematchStart()
-}
-
-void function NSUpdateGameStateLoopClient()
-{
- while ( true )
+ foreach ( player in GetPlayerArray() )
{
- if ( IsSingleplayer() )
- {
- NSUpdateGameStateClient( GetPlayerArray().len(), GetCurrentPlaylistVarInt( "max_players", 65535 ), 1, 1, 1, GetServerVar( "roundBased" ), 1 )
- wait 1.0
- }
- else
- {
- foreach ( player in GetPlayerArray() )
- {
- if ( GameRules_GetTeamScore( player.GetTeam() ) >= file.highestScore )
- {
- file.highestScore = GameRules_GetTeamScore( player.GetTeam() )
- }
- else if ( GameRules_GetTeamScore( player.GetTeam() ) > file.secondHighestScore )
- {
- file.secondHighestScore = GameRules_GetTeamScore( player.GetTeam() )
- }
- }
-
- int ourScore = 0
- if ( IsValid( GetLocalClientPlayer() ) )
- ourScore = GameRules_GetTeamScore( GetLocalClientPlayer().GetTeam() )
-
- int limit = IsRoundBased() ? GetCurrentPlaylistVarInt( "roundscorelimit", 0 ) : GetCurrentPlaylistVarInt( "scorelimit", 0 )
- NSUpdateGameStateClient( GetPlayerArray().len(), GetCurrentPlaylistVarInt( "max_players", 65535 ), ourScore, file.secondHighestScore, file.highestScore, GetServerVar( "roundBased" ), limit )
- OnPrematchStart()
- wait 1.0
- }
+ if ( GameRules_GetTeamScore( player.GetTeam() ) >= highestScore )
+ {
+ highestScore = GameRules_GetTeamScore( player.GetTeam() )
+ }
+ else if ( GameRules_GetTeamScore( player.GetTeam() ) > secondHighest )
+ {
+ secondHighest = GameRules_GetTeamScore( player.GetTeam() )
+ }
}
+
+ gs.map = GetMapName()
+ gs.mapDisplayname = Localize(GetMapDisplayName(GetMapName()))
+
+ gs.playlist = GetCurrentPlaylistName()
+ gs.playlistDisplayname = Localize(GetCurrentPlaylistVarString("name", GetCurrentPlaylistName()))
+
+ gs.currentPlayers = GetPlayerArray().len()
+ gs.maxPlayers = GetCurrentPlaylistVarInt( "maxPlayers", -1 )
+
+ if ( IsValid( GetLocalClientPlayer() ) )
+ gs.ownScore = GameRules_GetTeamScore( GetLocalClientPlayer().GetTeam() )
+
+ gs.otherHighestScore = gs.ownScore == highestScore ? secondHighest : highestScore
+
+ gs.maxScore = IsRoundBased() ? GetCurrentPlaylistVarInt( "roundscorelimit", 0 ) : GetCurrentPlaylistVarInt( "scorelimit", 0 )
+
+ if ( GetServerVar( "roundBased" ) )
+ gs.timeEnd = expect float(level.nv.roundEndTime - Time())
+ else
+ gs.timeEnd = expect float(level.nv.gameEndTime - Time())
+ return gs
}
diff --git a/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut b/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut
index 1e381989..ce5abe86 100644
--- a/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut
+++ b/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut
@@ -1,38 +1,16 @@
untyped
globalize_all_functions
-void function NSUpdateGameStateUIStart()
+UIPresenceStruct function DiscordRPC_GenerateUIPresence( UIPresenceStruct uis )
{
- thread NSUpdateGameStateLoopUI()
-}
-
-void function NSUpdateGameStateLoopUI()
-{
- while ( true )
- {
- wait 1.0
+ if ( uiGlobal.isLoading )
+ uis.gameState = eDiscordGameState.LOADING;
+ else if ( uiGlobal.loadedLevel == "" )
+ uis.gameState = eDiscordGameState.MAINMENU;
+ else if ( IsLobby() || uiGlobal.loadedLevel == "mp_lobby" )
+ uis.gameState = eDiscordGameState.LOBBY;
+ else
+ uis.gameState = eDiscordGameState.INGAME;
- if ( uiGlobal.loadedLevel == "" )
- {
- if ( uiGlobal.isLoading )
- NSSetLoading( true )
- else
- {
- NSSetLoading( false )
- NSUpdateGameStateUI( "", "", "", "", true, false )
- }
-
- continue
- }
-
- NSSetLoading( false )
- if( GetConVarString( "mp_gamemode" ) == "solo" )
- {
- NSUpdateGameStateUI( GetActiveLevel(), Localize( GetMapDisplayName( GetActiveLevel() + "_CAMPAIGN_NAME" ) ), "Campaign", "Campaign", IsFullyConnected(), false )
- }
- else
- {
- NSUpdateGameStateUI( GetActiveLevel(), Localize( GetMapDisplayName( GetActiveLevel() ) ), GetConVarString( "mp_gamemode" ), Localize( GetPlaylistDisplayName( GetConVarString( "mp_gamemode" ) ) ), IsFullyConnected(), false )
- }
- }
+ return uis
}
diff --git a/Northstar.Client/mod/scripts/vscripts/sh_menu_models.gnut b/Northstar.Client/mod/scripts/vscripts/sh_menu_models.gnut
index 6d446654..0bcb7864 100644
--- a/Northstar.Client/mod/scripts/vscripts/sh_menu_models.gnut
+++ b/Northstar.Client/mod/scripts/vscripts/sh_menu_models.gnut
@@ -207,19 +207,9 @@
#endif // CLIENT && MP
#if UI
- struct
- {
- table MouseMovementCaptureFunctionsTable = {}
- } file
-
- const MOUSE_ROTATE_MULTIPLIER = 25.0
-
global function UpdateUIMapSupportsMenuModels
global function RunMenuClientFunction
global function UI_SetPresentationType
-
- global function AddMouseMovementCaptureHandler
- global function UICodeCallback_MouseMovementCapture
#endif // UI
global const STORE_BG_DEFAULT = 0
@@ -2897,30 +2887,4 @@
RunClientScript( "UpdateMenuToHarvester" )
}
}
-
- void function AddMouseMovementCaptureHandler( var menu, void functionref( int, int ) func )
- {
- file.MouseMovementCaptureFunctionsTable.rawset( menu, func )
- }
-
- void function UpdateMouseMovementCaptureFunctions( int deltaX, int deltaY )
- {
- var activeMenu = GetActiveMenu()
- if ( file.MouseMovementCaptureFunctionsTable.rawin( activeMenu ) )
- file.MouseMovementCaptureFunctionsTable.rawget( activeMenu )(deltaX, deltaY)
- }
-
- void function UICodeCallback_MouseMovementCapture( var capturePanel, int deltaX, int deltaY )
- {
- float screenScaleXModifier = 1920.0 / GetScreenSize()[0] // 1920 is base screen width
- float mouseXRotateDelta = deltaX * screenScaleXModifier * MOUSE_ROTATE_MULTIPLIER
- //printt( "deltaX:", deltaX, "deltaY:", deltaY )
-
- float screenScaleYModifier = 1080.0 / GetScreenSize()[1] // 1920 is base screen width
- float mouseYRotationDelta = deltaY * screenScaleYModifier * MOUSE_ROTATE_MULTIPLIER
-
- UpdateMouseMovementCaptureFunctions( deltaX, deltaY )
-
- RunMenuClientFunction( "UpdateMouseRotateDelta", mouseXRotateDelta, mouseYRotationDelta )
- }
#endif // UI
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut b/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut
new file mode 100644
index 00000000..c83381fd
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut
@@ -0,0 +1,2004 @@
+untyped
+
+global const bool EDIT_LOADOUT_SELECTS = true
+global const string PURCHASE_SUCCESS_SOUND = "UI_Menu_Store_Purchase_Success"
+
+global function UICodeCallback_CloseAllMenus
+global function UICodeCallback_ActivateMenus
+global function UICodeCallback_LevelInit
+global function UICodeCallback_LevelLoadingStarted
+global function UICodeCallback_LevelLoadingFinished
+global function UICodeCallback_LevelShutdown
+global function UICodeCallback_OnConnected
+global function UICodeCallback_OnFocusChanged
+global function UICodeCallback_NavigateBack
+global function UICodeCallback_ToggleInGameMenu
+global function UICodeCallback_TryCloseDialog
+global function UICodeCallback_UpdateLoadingLevelName
+global function UICodeCallback_ConsoleKeyboardClosed
+global function UICodeCallback_ErrorDialog
+global function UICodeCallback_AcceptInvite
+global function UICodeCallback_OnDetenteDisplayed
+global function UICodeCallback_OnSpLogDisplayed
+global function UICodeCallback_EntitlementsChanged
+global function UICodeCallback_StoreTransactionCompleted
+global function UICodeCallback_GamePurchased
+global function UICodeCallback_PartyUpdated
+global function UICodeCallback_KeyBindOverwritten
+
+global function AdvanceMenu
+global function OpenSubmenu // REMOVE
+global function CloseSubmenu // REMOVE
+global function CloseActiveMenu
+global function CloseActiveMenuNoParms
+global function CloseAllMenus
+global function CloseAllInGameMenus
+global function CloseAllDialogs
+global function CloseAllToTargetMenu
+global function PrintMenuStack
+global function CleanupInGameMenus
+global function GetActiveMenu
+global function GetMenu
+global function GetPanel
+global function GetAllMenuPanels
+global function InitGamepadConfigs
+global function InitMenus
+global function AdvanceMenuEventHandler
+global function PCSwitchTeamsButton_Activate
+global function PCToggleSpectateButton_Activate
+global function AddMenuElementsByClassname
+global function FocusDefault
+global function SetPanelDefaultFocus
+global function PanelFocusDefault
+global function OpenMenuWrapper
+global function CloseMenuWrapper
+global function IsLevelMultiplayer
+global function AddMenuEventHandler
+global function AddPanelEventHandler
+global function AddButtonEventHandler
+global function AddEventHandlerToButton
+global function AddEventHandlerToButtonClass
+global function DisableMusic
+global function EnableMusic
+global function PlayMusic
+global function StopMusic
+global function IsMenuInMenuStack
+global function GetTopNonDialogMenu
+global function IsDialog
+global function IsDialogActive
+global function IsDialogOnlyActiveMenu
+global function SetNavUpDown
+global function SetNavLeftRight
+global function IsTrialPeriodActive
+global function LaunchGamePurchaseOrDLCStore
+global function SetMenuThinkFunc
+
+global function PCBackButton_Activate
+
+global function RegisterMenuVarInt
+global function GetMenuVarInt
+global function SetMenuVarInt
+global function RegisterMenuVarBool
+global function GetMenuVarBool
+global function SetMenuVarBool
+global function RegisterMenuVarVar
+global function GetMenuVarVar
+global function SetMenuVarVar
+global function AddMenuVarChangeHandler
+
+global function InviteFriends
+
+global function HACK_DelayedSetFocus_BecauseWhy
+
+#if DURANGO_PROG
+ global function OpenXboxPartyApp
+ global function OpenXboxHelp
+#endif // DURANGO_PROG
+
+global function OpenReviewTermsDialog
+global function ClassicMusic_OnChange
+global function IsClassicMusicAvailable
+
+
+void function UICodeCallback_CloseAllMenus()
+{
+ printt( "UICodeCallback_CloseAllMenus" )
+ CloseAllMenus()
+ // This is usually followed by a call to UICodeCallback_ActivateMenus().
+}
+
+// Bringing up the console will cause this, and it probably shouldn't
+void function UICodeCallback_ActivateMenus()
+{
+ if ( IsConnected() )
+ return
+
+ printt( "UICodeCallback_ActivateMenus:", uiGlobal.activeMenu && Hud_GetHudName( uiGlobal.activeMenu ) )
+
+ if ( uiGlobal.menuStack.len() == 0 )
+ {
+ AdvanceMenu( GetMenu( "MainMenu" ) )
+ }
+
+ if ( uiGlobal.activeMenu == GetMenu( "MainMenu" ) )
+ Signal( uiGlobal.signalDummy, "OpenErrorDialog" )
+
+ PlayMusic()
+
+ #if DURANGO_PROG
+ Durango_LeaveParty()
+ #endif // DURANGO_PROG
+}
+
+void function UICodeCallback_ToggleInGameMenu()
+{
+ if ( !IsFullyConnected() )
+ return
+
+ var activeMenu = uiGlobal.activeMenu
+ bool isMP = IsLevelMultiplayer( GetActiveLevel() )
+ bool isLobby = IsLobby()
+
+ var ingameMenu
+ if ( isMP )
+ {
+ ingameMenu = GetMenu( "InGameMPMenu" )
+ }
+ else
+ {
+ // Disable this callback for this special case menu so players can't skip it.
+ var spTitanTutorialMenu = GetMenu( "SPTitanLoadoutTutorialMenu" )
+ if ( activeMenu == spTitanTutorialMenu )
+ return
+
+ ingameMenu = GetMenu( "InGameSPMenu" )
+ }
+
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ {
+ // Do nothing if a dialog is showing
+ }
+ else if ( TeamTitanSelectMenuIsOpen() )
+ {
+ if ( uiGlobal.activeMenu == GetMenu( "TeamTitanSelectMenu" ) )
+ {
+ // Do nothing here either
+ }
+ else
+ {
+ CloseActiveMenu()
+ }
+ }
+ else if ( ( isMP && !isLobby ) || !isMP )
+ {
+ if ( !activeMenu )
+ AdvanceMenu( ingameMenu )
+ else
+ CloseAllInGameMenus()
+ }
+}
+
+// Return true to show load screen, false to not show load screen.
+// levelname can be "" because the level to load isn't always known when the load screen starts
+bool function UICodeCallback_LevelLoadingStarted( string levelname )
+{
+ printt( "UICodeCallback_LevelLoadingStarted: " + levelname )
+
+ CloseAllDialogs()
+
+ uiGlobal.loadingLevel = levelname
+ uiGlobal.isLoading = true
+
+ StopMusic()
+
+ if ( uiGlobal.playingVideo )
+ Signal( uiGlobal.signalDummy, "PlayVideoEnded" )
+
+ if ( uiGlobal.playingCredits )
+ Signal( uiGlobal.signalDummy, "PlayingCreditsDone" )
+
+ // kill lingering postgame summary since persistent data may not be available at this point
+ Signal( uiGlobal.signalDummy, "PGDisplay" )
+
+#if CONSOLE_PROG
+ if ( !Console_IsSignedIn() )
+ return false
+#endif
+
+ return true
+}
+
+// Return true to show load screen, false to not show load screen.
+bool function UICodeCallback_UpdateLoadingLevelName( string levelname )
+{
+ printt( "UICodeCallback_UpdateLoadingLevelName: " + levelname )
+
+#if CONSOLE_PROG
+ if ( !Console_IsSignedIn() )
+ return false
+#endif
+
+ return true
+}
+
+void function UICodeCallback_LevelLoadingFinished( bool error )
+{
+ printt( "UICodeCallback_LevelLoadingFinished: " + uiGlobal.loadingLevel + " (" + error + ")" )
+
+ if ( !IsLobby() )
+ {
+ HudChat_ClearTextFromAllChatPanels()
+ ResetActiveChatroomLastModified()
+ }
+ else
+ {
+ uiGlobal.lobbyFromLoadingScreen = true
+ }
+
+ uiGlobal.loadingLevel = ""
+ uiGlobal.isLoading = false
+ Signal( uiGlobal.signalDummy, "LevelFinishedLoading" )
+}
+
+void function UICodeCallback_LevelInit( string levelname )
+{
+ Assert( IsConnected() )
+
+ StopVideo()
+
+ uiGlobal.loadedLevel = levelname
+
+ printt( "UICodeCallback_LevelInit: " + uiGlobal.loadedLevel )
+
+ if ( !uiGlobal.loadoutsInitialized )
+ {
+ string gameModeString = GetConVarString( "mp_gamemode" )
+ if ( gameModeString != "solo" )
+ {
+ InitStatsTables()
+ }
+ }
+
+ InitItems()
+
+ if ( IsMultiplayer() )
+ {
+ ShWeaponXP_Init()
+ ShTitanXP_Init()
+ ShFactionXP_Init()
+ }
+ else
+ {
+ SPObjectiveStringsInit()
+ }
+
+ #if DEV
+ UpdatePrecachedSPWeapons()
+ #endif
+
+
+ if ( !uiGlobal.loadoutsInitialized )
+ {
+ string gameModeString = GetConVarString( "mp_gamemode" )
+ if ( gameModeString != "solo" )
+ {
+ DeathHints_Init()
+ InitDefaultLoadouts()
+ CreateChallenges()
+ uiGlobal.loadoutsInitialized = true
+ }
+ }
+
+ if ( IsLevelMultiplayer( levelname ) || IsLobbyMapName( levelname ) )
+ {
+ thread UpdateCachedLoadouts()
+ thread UpdateCachedNewItems()
+ thread InitUISpawnLoadoutIndexes()
+
+ if ( !uiGlobal.eventHandlersAdded )
+ {
+ uiGlobal.eventHandlersAdded = true
+ }
+
+ UI_GetAllChallengesProgress()
+
+ bool isLobby = IsLobbyMapName( levelname )
+
+ string gameModeString = GetConVarString( "mp_gamemode" )
+ if ( gameModeString == "" )
+ gameModeString = "<null>"
+
+ Assert( gameModeString == GetConVarString( "mp_gamemode" ) )
+ Assert( gameModeString != "" )
+
+ int gameModeId = GameMode_GetGameModeId( gameModeString )
+
+ int mapId = eMaps.invalid
+ if ( levelname in getconsttable().eMaps )
+ {
+ mapId = expect int( getconsttable().eMaps[ levelname ] )
+ }
+ else
+ {
+ // Don't worry about this until we have to consider R2 Durango TCRs (10/2015)
+ //if ( !IsTestMap() )
+ // CodeWarning( "No map named '" + levelname + "' exists in eMaps, all shipping maps should be in this enum" )
+ }
+
+ int difficultyLevelId = 0
+ int roundId = 0
+
+ if ( isLobby )
+ Durango_OnLobbySessionStart( gameModeId, difficultyLevelId )
+ else
+ Durango_OnMultiplayerRoundStart( gameModeId, mapId, difficultyLevelId, roundId, 0 )
+ }
+ else
+ {
+ // SP loadout stuff
+ UI_GetAllChallengesProgress() // TODO: Can this be moved so we don't call it twice? It's called above.
+
+ SP_ResetObjectiveStringIndex() // Since this persists thru level load, we need to explicitely clear it.
+ }
+
+ if ( IsMultiplayer() )
+ {
+ foreach ( callbackFunc in uiGlobal.onLevelInitCallbacks )
+ {
+ thread callbackFunc()
+ }
+
+ }
+ thread UpdateMenusOnConnect( levelname )
+
+ uiGlobal.previousLevel = uiGlobal.loadedLevel
+ uiGlobal.previousPlaylist = GetCurrentPlaylistName()
+}
+
+void function UICodeCallback_LevelShutdown()
+{
+ Signal( uiGlobal.signalDummy, "LevelShutdown" )
+
+ printt( "UICodeCallback_LevelShutdown: " + uiGlobal.loadedLevel )
+
+ StopVideo()
+
+ if ( uiGlobal.loadedLevel != "" )
+ CleanupInGameMenus()
+
+ uiGlobal.loadedLevel = ""
+ uiGlobal.mapSupportsMenuModelsUpdated = false
+ uiGlobal.sp_showAlternateMissionLog = false
+}
+
+void function UICodeCallback_NavigateBack()
+{
+ if ( uiGlobal.activeMenu == null )
+ return
+
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ {
+ if ( uiGlobal.menuData[ uiGlobal.activeMenu ].dialogData.noChoice ||
+ uiGlobal.menuData[ uiGlobal.activeMenu ].dialogData.forceChoice ||
+ Time() < uiGlobal.dialogInputEnableTime )
+ return
+ }
+
+ Assert( uiGlobal.activeMenu in uiGlobal.menuData )
+ if ( uiGlobal.menuData[ uiGlobal.activeMenu ].navBackFunc != null )
+ {
+ thread uiGlobal.menuData[ uiGlobal.activeMenu ].navBackFunc()
+ return
+ }
+
+ if ( uiGlobal.activeMenu.GetType() == "submenu" ) // REMOVE
+ {
+ CloseSubmenu()
+ return
+ }
+
+ CloseActiveMenu( true )
+}
+
+// Called when IsConnected() will start returning true.
+void function UICodeCallback_OnConnected()
+{
+
+}
+
+void function UICodeCallback_OnFocusChanged( var oldFocusedPanel, var newFocusedPanel )
+{
+
+}
+
+// Accepting an origin invite closes dialogs, or aborts if they can't be closed
+bool function UICodeCallback_TryCloseDialog()
+{
+ if ( !IsDialog( uiGlobal.activeMenu ) )
+ return true
+
+ if ( uiGlobal.menuData[ uiGlobal.activeMenu ].dialogData.forceChoice )
+ return false
+
+ CloseAllDialogs()
+ Assert( !IsDialog( uiGlobal.activeMenu ) )
+ return true
+}
+
+void function UICodeCallback_ConsoleKeyboardClosed()
+{
+ switch ( uiGlobal.activeMenu )
+ {
+ case GetMenu( "EditPilotLoadoutMenu" ):
+ string oldName = GetPilotLoadoutName( GetCachedPilotLoadout( uiGlobal.editingLoadoutIndex ) )
+ string newName = GetPilotLoadoutRenameText()
+
+ // strip doesn't work on UTF-8 strings
+ // newName = strip( newName ) // Remove leading/trailing whitespace
+ if ( newName == "" ) // If all whitespace entered reset to previous name
+ newName = oldName
+
+ SetPilotLoadoutName( newName )
+ SelectPilotLoadoutRenameText()
+ if ( newName != oldName )
+ EmitUISound( "Menu.Accept" ) // No callback when cancelled so for now assume name was changed
+ break
+
+ default:
+ break
+ }
+}
+
+void function UICodeCallback_OnDetenteDisplayed()
+{
+// thread PlayDetentSound()
+//}
+//
+//void function PlayDetentSound()
+//{
+// WaitFrame() // otherwise gets killed off by code pause
+// WaitFrame() // otherwise gets killed off by code pause
+// EmitUISound( "Pilot_Killed_Indicator" )
+}
+
+void function UICodeCallback_OnSpLogDisplayed()
+{
+}
+
+void function UICodeCallback_ErrorDialog( string errorDetails )
+{
+ printt( "UICodeCallback_ErrorDialog: " + errorDetails )
+ thread OpenErrorDialog( errorDetails )
+}
+
+void function UICodeCallback_AcceptInviteThread( string accesstoken )
+{
+ printt( "UICodeCallback_AcceptInviteThread '" + accesstoken + "'")
+
+ #if PS4_PROG
+ if ( !Ps4_PSN_Is_Loggedin() )
+ {
+ Ps4_LoginDialog_Schedule();
+ 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 )
+ {
+ return
+ }
+
+ if( Ps4_ScreenPlusDialog_Schedule() )
+ {
+ while( Ps4_ScreenPlusDialog_Running() )
+ WaitFrame()
+ if( !Ps4_ScreenPlusDialog_Allowed() )
+ return;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+ #endif // #if PS4_PROG
+
+ SubscribeToChatroomPartyChannel( accesstoken );
+
+}
+
+
+void function UICodeCallback_AcceptInvite( string accesstoken )
+{
+ printt( "UICodeCallback_AcceptInvite '" + accesstoken + "'")
+ thread UICodeCallback_AcceptInviteThread( accesstoken )
+}
+
+// TODO: replaceCurrent should not be an option. It should be a different function.
+void function AdvanceMenu( var menu, bool replaceCurrent = false )
+{
+ //foreach ( index, menu in uiGlobal.menuStack )
+ //{
+ // if ( menu != null )
+ // printt( "menu index " + index + " is named " + menu.GetDisplayName() )
+ //}
+
+ if ( uiGlobal.activeMenu )
+ {
+ // Don't open the same menu again if it's already open
+ if ( uiGlobal.activeMenu == menu )
+ return
+
+ // Opening a normal menu while a dialog is open
+ Assert( !IsDialog( uiGlobal.activeMenu ), "Tried opening menu: " + Hud_GetHudName( menu ) + " when uiGlobal.activeMenu was: " + Hud_GetHudName( uiGlobal.activeMenu ) )
+ }
+
+ if ( uiGlobal.activeMenu && !IsDialog( menu ) ) // Dialogs show on top so don't close existing menu when opening them
+ {
+ SetBlurEnabled( false )
+
+ if ( replaceCurrent )
+ {
+ CloseMenuWrapper( uiGlobal.activeMenu )
+ uiGlobal.menuStack.pop()
+ }
+ else
+ {
+ CloseMenu( uiGlobal.activeMenu )
+ printt( Hud_GetHudName( uiGlobal.activeMenu ), "menu closed" )
+ }
+ }
+
+ if ( IsDialog( menu ) && uiGlobal.activeMenu )
+ SetFooterPanelVisibility( uiGlobal.activeMenu, false )
+
+ uiGlobal.menuStack.push( menu )
+ uiGlobal.activeMenu = menu
+
+ uiGlobal.lastMenuNavDirection = MENU_NAV_FORWARD
+
+ if ( uiGlobal.activeMenu )
+ {
+ if ( !IsLobby() && !uiGlobal.mapSupportsMenuModels )
+ SetBlurEnabled( true )
+
+ OpenMenuWrapper( uiGlobal.activeMenu, true )
+ }
+
+ Signal( uiGlobal.signalDummy, "ActiveMenuChanged" )
+}
+
+void function SetFooterPanelVisibility( var menu, bool visible )
+{
+ if ( !Hud_HasChild( menu, "FooterButtons" ) )
+ return
+
+ var panel = Hud_GetChild( menu, "FooterButtons" )
+ Hud_SetVisible( panel, visible )
+}
+
+void function OpenSubmenu( var menu, bool updateMenuPos = true )
+{
+ Assert( menu )
+ Assert( menu.GetType() == "submenu" )
+
+ if ( uiGlobal.activeMenu )
+ {
+ // Don't open the same menu again if it's already open
+ if ( uiGlobal.activeMenu == menu )
+ return
+ }
+
+ local submenuPos = Hud_GetAbsPos( GetFocus() )
+
+ uiGlobal.menuStack.push( menu )
+ uiGlobal.activeMenu = menu
+
+ OpenMenuWrapper( uiGlobal.activeMenu, true )
+
+ if ( updateMenuPos )
+ {
+ var vguiButtonFrame = Hud_GetChild( uiGlobal.activeMenu, "ButtonFrame" )
+ Hud_SetPos( vguiButtonFrame, submenuPos[0], submenuPos[1] )
+ }
+
+ uiGlobal.lastMenuNavDirection = MENU_NAV_FORWARD
+
+ Signal( uiGlobal.signalDummy, "ActiveMenuChanged" )
+}
+
+void function CloseSubmenu( bool openStackMenu = true )
+{
+ if ( !uiGlobal.activeMenu )
+ return
+
+ if ( uiGlobal.activeMenu.GetType() != "submenu" )
+ return
+
+ CloseMenuWrapper( uiGlobal.activeMenu )
+ uiGlobal.menuStack.pop()
+
+ uiGlobal.lastMenuNavDirection = MENU_NAV_FORWARD
+
+ if ( uiGlobal.menuStack.len() )
+ {
+ uiGlobal.activeMenu = uiGlobal.menuStack.top()
+
+ // This runs any OnOpen function for the menu and sets focus, but doesn't actually open the menu because it is already open
+ if ( openStackMenu )
+ OpenMenuWrapper( uiGlobal.activeMenu, false )
+ }
+ else
+ {
+ uiGlobal.activeMenu = null
+ }
+
+ Signal( uiGlobal.signalDummy, "ActiveMenuChanged" )
+}
+
+void function CloseActiveMenuNoParms()
+{
+ CloseActiveMenu()
+}
+
+void function CloseActiveMenu( bool cancelled = false, bool openStackMenu = true )
+{
+ bool updateBlur = true
+ bool wasDialog = false
+
+ if ( uiGlobal.activeMenu )
+ {
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ {
+ updateBlur = false
+ wasDialog = true
+ uiGlobal.dialogInputEnableTime = 0.0
+
+ if ( uiGlobal.dialogCloseCallback )
+ {
+ uiGlobal.dialogCloseCallback( cancelled )
+ uiGlobal.dialogCloseCallback = null
+ }
+ }
+
+ if ( updateBlur )
+ SetBlurEnabled( false )
+
+ CloseMenuWrapper( uiGlobal.activeMenu )
+ }
+
+ uiGlobal.menuStack.pop()
+ if ( uiGlobal.menuStack.len() )
+ uiGlobal.activeMenu = uiGlobal.menuStack.top()
+ else
+ uiGlobal.activeMenu = null
+
+ uiGlobal.lastMenuNavDirection = MENU_NAV_BACK
+
+ if ( wasDialog )
+ {
+ if ( uiGlobal.activeMenu )
+ SetFooterPanelVisibility( uiGlobal.activeMenu, true )
+
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ openStackMenu = true
+ else
+ openStackMenu = false
+ }
+
+ if ( uiGlobal.activeMenu )
+ {
+ if ( uiGlobal.activeMenu.GetType() == "submenu" )
+ {
+ Hud_SetFocused( uiGlobal.menuData[ uiGlobal.activeMenu ].lastFocus )
+ }
+ else if ( openStackMenu )
+ {
+ OpenMenuWrapper( uiGlobal.activeMenu, false )
+
+ if ( updateBlur && !IsLobby() && !uiGlobal.mapSupportsMenuModels )
+ SetBlurEnabled( true )
+ }
+ }
+
+ Signal( uiGlobal.signalDummy, "ActiveMenuChanged" )
+}
+
+void function CloseAllMenus()
+{
+ if ( IsDialog( uiGlobal.activeMenu ) )
+ CloseActiveMenu( true )
+
+ if ( uiGlobal.activeMenu && uiGlobal.activeMenu.GetType() == "submenu" )
+ CloseSubmenu( false )
+
+ if ( uiGlobal.activeMenu )
+ {
+ SetBlurEnabled( false )
+ CloseMenuWrapper( uiGlobal.activeMenu )
+ }
+
+ uiGlobal.menuStack = []
+ uiGlobal.activeMenu = null
+
+ uiGlobal.lastMenuNavDirection = MENU_NAV_BACK
+
+ Signal( uiGlobal.signalDummy, "ActiveMenuChanged" )
+}
+
+void function CloseAllInGameMenus()
+{
+ while ( uiGlobal.activeMenu )
+ {
+ if ( uiGlobal.activeMenu.GetType() == "submenu" )
+ CloseSubmenu( false )
+
+ CloseActiveMenu( true, false )
+ }
+}
+
+void function CloseAllDialogs()
+{
+ while ( IsDialog( uiGlobal.activeMenu ) )
+ CloseActiveMenu( true )
+}
+
+void function CloseAllToTargetMenu( var targetMenu )
+{
+ while ( uiGlobal.activeMenu != targetMenu )
+ CloseActiveMenu( true, false )
+}
+
+void function PrintMenuStack()
+{
+ array<var> stack = clone uiGlobal.menuStack
+ stack.reverse()
+
+ printt( "MENU STACK:" )
+
+ foreach ( menu in stack )
+ {
+ if ( menu )
+ printt( " ", Hud_GetHudName( menu ) )
+ else
+ printt( " null" )
+ }
+}
+
+// Happens on any level load
+void function UpdateMenusOnConnect( string levelname )
+{
+ EndSignal( uiGlobal.signalDummy, "LevelShutdown" ) // HACK fix because UICodeCallback_LevelInit() incorrectly runs when disconnected by client error. Test with "script_error_client" while a level is loaded.
+
+ CloseAllDialogs()
+
+ var mainMenu = GetMenu( "MainMenu" )
+ if ( IsMenuInMenuStack( mainMenu ) && !IsMenuInMenuStack( null ) )
+ CloseAllToTargetMenu( mainMenu )
+
+ Assert( uiGlobal.activeMenu != null || uiGlobal.menuStack.len() == 0 )
+
+ AdvanceMenu( null )
+
+ // TODO: The order things are called in should be predictable so this isn't needed
+ while ( !uiGlobal.mapSupportsMenuModelsUpdated )
+ {
+ //printt( Time(), "beginning waitframe, uiGlobal.mapSupportsMenuModelsUpdated is:", uiGlobal.mapSupportsMenuModelsUpdated )
+ WaitFrame()
+ //printt( Time(), "ended waitframe, uiGlobal.mapSupportsMenuModelsUpdated is:", uiGlobal.mapSupportsMenuModelsUpdated )
+ }
+
+ if ( IsLevelMultiplayer( levelname ) )
+ {
+ bool isLobby = IsLobbyMapName( levelname )
+
+ if ( isLobby )
+ {
+ if ( IsPrivateMatch() )
+ {
+ AdvanceMenu( GetMenu( "PrivateLobbyMenu" ) )
+ }
+ else
+ {
+ AdvanceMenu( GetMenu( "LobbyMenu" ) )
+ }
+
+ thread UpdateAnnouncementDialog()
+ }
+ else
+ {
+ UI_SetPresentationType( ePresentationType.INACTIVE )
+ }
+ }
+}
+
+bool function IsMenuInMenuStack( var searchMenu )
+{
+ foreach ( menu in uiGlobal.menuStack )
+ {
+ // loading a map pushes a null sentinel onto the menu stack
+ if ( !menu )
+ continue
+
+ if ( menu == searchMenu )
+ return true
+ }
+
+ return false
+}
+
+var function GetTopNonDialogMenu()
+{
+ array<var> menuArray = clone uiGlobal.menuStack
+ menuArray.reverse()
+
+ foreach ( menu in menuArray )
+ {
+ if ( menu == null || IsDialog( menu ) )
+ continue
+
+ return menu
+ }
+
+ return null
+}
+
+void function CleanupInGameMenus()
+{
+ Signal( uiGlobal.signalDummy, "CleanupInGameMenus" )
+
+ CloseAllInGameMenus()
+ Assert( uiGlobal.activeMenu == null )
+ if ( uiGlobal.menuStack.len() )
+ {
+ if ( uiGlobal.loadingLevel == "" )
+ CloseActiveMenu() // Disconnected. Remove stack null and open main menu.
+ else
+ CloseActiveMenu( true, false ) // Level to level transition. Remove stack null and DON'T open main menu.
+ }
+}
+
+var function GetActiveMenu()
+{
+ return uiGlobal.activeMenu
+}
+
+var function GetMenu( string menuName )
+{
+ return uiGlobal.menus[ menuName ]
+}
+
+var function GetPanel( string panelName )
+{
+ return uiGlobal.panels[ panelName ]
+}
+
+array<var> function GetAllMenuPanels( var menu )
+{
+ array<var> menuPanels
+
+ foreach ( panel in uiGlobal.allPanels )
+ {
+ if ( Hud_GetParent( panel ) == menu )
+ menuPanels.append( panel )
+ }
+
+ return menuPanels
+}
+
+void function InitGamepadConfigs()
+{
+ uiGlobal.buttonConfigs = [ { orthodox = "gamepad_button_layout_default.cfg", southpaw = "gamepad_button_layout_default_southpaw.cfg" } ]
+ uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_bumper_jumper.cfg", southpaw = "gamepad_button_layout_bumper_jumper_southpaw.cfg" } )
+ uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_bumper_jumper_alt.cfg", southpaw = "gamepad_button_layout_bumper_jumper_alt_southpaw.cfg" } )
+ uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_pogo_stick.cfg", southpaw = "gamepad_button_layout_pogo_stick_southpaw.cfg" } )
+ uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_button_kicker.cfg", southpaw = "gamepad_button_layout_button_kicker_southpaw.cfg" } )
+ uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_circle.cfg", southpaw = "gamepad_button_layout_circle_southpaw.cfg" } )
+ uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_ninja.cfg", southpaw = "gamepad_button_layout_ninja_southpaw.cfg" } )
+ uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_custom.cfg", southpaw = "gamepad_button_layout_custom.cfg" } )
+
+ uiGlobal.stickConfigs = []
+ uiGlobal.stickConfigs.append( "gamepad_stick_layout_default.cfg" )
+ uiGlobal.stickConfigs.append( "gamepad_stick_layout_southpaw.cfg" )
+ uiGlobal.stickConfigs.append( "gamepad_stick_layout_legacy.cfg" )
+ uiGlobal.stickConfigs.append( "gamepad_stick_layout_legacy_southpaw.cfg" )
+
+ foreach ( key, val in uiGlobal.buttonConfigs )
+ {
+ VPKNotifyFile( "cfg/" + val.orthodox )
+ VPKNotifyFile( "cfg/" + val.southpaw )
+ }
+
+ foreach ( key, val in uiGlobal.stickConfigs )
+ VPKNotifyFile( "cfg/" + val )
+
+ ExecCurrentGamepadButtonConfig()
+ ExecCurrentGamepadStickConfig()
+
+ SetStandardAbilityBindingsForPilot( GetLocalClientPlayer() )
+}
+
+void function InitMenus()
+{
+ InitGlobalMenuVars()
+ SpShWeaponsInit()
+
+ AddMenu( "MainMenu", $"resource/ui/menus/main.menu", InitMainMenu, "#MAIN" )
+ AddPanel( GetMenu( "MainMenu" ), "EstablishUserPanel", InitEstablishUserPanel )
+ AddPanel( GetMenu( "MainMenu" ), "MainMenuPanel", InitMainMenuPanel )
+
+ AddMenu( "PlayVideoMenu", $"resource/ui/menus/play_video.menu", InitPlayVideoMenu )
+ AddMenu( "LobbyMenu", $"resource/ui/menus/lobby.menu", InitLobbyMenu, "#LOBBY" )
+
+ AddMenu( "FDMenu", $"resource/ui/menus/playlist_fd.menu", InitFDPlaylistMenu )
+ AddMenu( "TeamTitanSelectMenu", $"resource/ui/menus/team_titan_select.menu", InitTeamTitanSelectMenu )
+ AddMenu( "PlaylistMenu", $"resource/ui/menus/playlist.menu", InitPlaylistMenu )
+ AddMenu( "PlaylistMixtapeMenu", $"resource/ui/menus/playlist_mixtape.menu", InitPlaylistMixtapeMenu )
+ AddMenu( "PlaylistMixtapeChecklistMenu", $"resource/ui/menus/playlist_mixtape_checklist.menu", InitPlaylistMixtapeChecklistMenu )
+
+ AddMenu( "SinglePlayerDevMenu", $"resource/ui/menus/singleplayer_dev.menu", InitSinglePlayerDevMenu, "SINGLE PLAYER DEV" )
+ AddMenu( "SinglePlayerMenu", $"resource/ui/menus/singleplayer.menu", InitSinglePlayerMenu, "SINGLE PLAYER" )
+
+ AddMenu( "SearchMenu", $"resource/ui/menus/search.menu", InitSearchMenu )
+
+ AddMenu( "GammaMenu", $"resource/ui/menus/gamma.menu", InitGammaMenu, "#BRIGHTNESS" )
+
+ AddMenu( "CommunitiesMenu", $"resource/ui/menus/community.menu", InitCommunitiesMenu )
+ AddMenu( "Notifications", $"resource/ui/menus/notifications.menu", InitNotificationsMenu )
+ AddMenu( "MyNetworks", $"resource/ui/menus/communities_mine.menu", InitMyNetworksMenu )
+ AddMenu( "InboxFrontMenu", $"resource/ui/menus/inbox_front.menu", InitInboxFrontMenu )
+ AddMenu( "Inbox", $"resource/ui/menus/inbox.menu", InitInboxMenu )
+ AddMenu( "BrowseCommunities", $"resource/ui/menus/communities_browse.menu" )
+ AddMenu( "CommunityEditMenu", $"resource/ui/menus/community_edit.menu" )
+ AddMenu( "CommunityAdminSendMessage", $"resource/ui/menus/community_sendMessage.menu" )
+ AddMenu( "CommunityAdminInviteRequestMenu", $"resource/ui/menus/community_inviteRequest.menu" )
+#if NETWORK_INVITE
+ AddMenu( "InviteFriendsToNetworkMenu", $"resource/ui/menus/invite_friends.menu", InitInviteFriendsToNetworkMenu )
+#endif
+
+ AddMenu( "InGameMPMenu", $"resource/ui/menus/ingame_mp.menu", InitInGameMPMenu )
+ AddMenu( "InGameSPMenu", $"resource/ui/menus/ingame_sp.menu", InitInGameSPMenu )
+
+ AddMenu( "Dialog", $"resource/ui/menus/dialog.menu", InitDialogMenu )
+ AddMenu( "AnnouncementDialog", $"resource/ui/menus/dialog_announcement.menu", InitAnnouncementDialog )
+ AddMenu( "ConnectingDialog", $"resource/ui/menus/dialog_connecting.menu", InitConnectingDialog )
+ AddMenu( "DataCenterDialog", $"resource/ui/menus/dialog_datacenter.menu", InitDataCenterDialogMenu )
+ AddMenu( "EULADialog", $"resource/ui/menus/dialog_eula.menu", InitEULADialog )
+ AddMenu( "ReviewTermsDialog", $"resource/ui/menus/dialog_review_terms.menu", InitReviewTermsDialog )
+ AddMenu( "RegistrationDialog", $"resource/ui/menus/dialog_registration.menu", InitRegistrationDialog )
+ AddMenu( "AdvocateGiftDialog", $"resource/ui/menus/dialog_advocate_gift.menu", InitAdvocateGiftDialog )
+
+ AddMenu( "ControlsMenu", $"resource/ui/menus/controls.menu", InitControlsMenu, "#CONTROLS" )
+ AddMenu( "ControlsAdvancedLookMenu", $"resource/ui/menus/controls_advanced_look.menu", InitControlsAdvancedLookMenu, "#CONTROLS_ADVANCED_LOOK" )
+ AddMenu( "GamepadLayoutMenu", $"resource/ui/menus/gamepadlayout.menu", InitGamepadLayoutMenu )
+#if PC_PROG
+ AddMenu_WithCreateFunc( "MouseKeyboardBindingsMenu", $"resource/ui/menus/mousekeyboardbindings.menu", InitMouseKeyboardMenu, CreateKeyBindingMenu )
+ AddMenu( "AudioMenu", $"resource/ui/menus/audio.menu", InitAudioMenu, "#AUDIO" )
+ AddMenu_WithCreateFunc( "VideoMenu", $"resource/ui/menus/video.menu", InitVideoMenu, CreateVideoOptionsMenu )
+#elseif CONSOLE_PROG
+ AddMenu( "AudioVideoMenu", $"resource/ui/menus/audio_video.menu", InitAudioVideoMenu, "#AUDIO_VIDEO" )
+#endif
+
+ AddMenu( "AdvancedHudMenu", $"resource/ui/menus/advanced_hud.menu", InitAdvancedHudMenu, "#ADVANCED_HUD" )
+
+ AddMenu( "PilotLoadoutsMenu", $"resource/ui/menus/pilotloadouts.menu", InitPilotLoadoutsMenu )
+ AddMenu( "TitanLoadoutsMenu", $"resource/ui/menus/titanloadouts.menu", InitTitanLoadoutsMenu )
+ AddMenu( "EditPilotLoadoutsMenu", $"resource/ui/menus/pilotloadouts.menu", InitEditPilotLoadoutsMenu )
+ AddMenu( "EditTitanLoadoutsMenu", $"resource/ui/menus/titanloadouts.menu", InitEditTitanLoadoutsMenu )
+ AddMenu( "EditPilotLoadoutMenu", $"resource/ui/menus/editpilotloadout.menu", InitEditPilotLoadoutMenu )
+ AddMenu( "EditTitanLoadoutMenu", $"resource/ui/menus/edittitanloadout.menu", InitEditTitanLoadoutMenu )
+
+ AddMenu( "SPTitanLoadoutMenu", $"resource/ui/menus/sptitanloadout.menu", InitSPTitanLoadoutMenu )
+ AddMenu( "SPTitanLoadoutTutorialMenu", $"resource/ui/menus/sptitanloadout_tutorial.menu", InitSPTitanLoadoutTutorialMenu )
+
+ AddMenu( "SuitSelectMenu", $"resource/ui/menus/suitselect.menu", InitSuitSelectMenu )
+ AddMenu( "WeaponSelectMenu", $"resource/ui/menus/weaponselect.menu", InitWeaponSelectMenu )
+ AddMenu( "CategorySelectMenu", $"resource/ui/menus/categoryselect.menu", InitCategorySelectMenu )
+ AddMenu( "AbilitySelectMenu", $"resource/ui/menus/abilityselect.menu", InitAbilitySelectMenu )
+ AddMenu( "PassiveSelectMenu", $"resource/ui/menus/passiveselect.menu", InitPassiveSelectMenu )
+ AddSubmenu( "ModSelectMenu", $"resource/ui/menus/modselect.menu", InitModSelectMenu )
+ AddMenu( "CamoSelectMenu", $"resource/ui/menus/camoselect.menu", InitCamoSelectMenu )
+ AddMenu( "NoseArtSelectMenu", $"resource/ui/menus/noseartselect.menu", InitNoseArtSelectMenu )
+ AddMenu( "CallsignCardSelectMenu", $"resource/ui/menus/callsigncardselect.menu", InitCallsignCardSelectMenu )
+ AddMenu( "CallsignIconSelectMenu", $"resource/ui/menus/callsigniconselect.menu", InitCallsignIconSelectMenu )
+ AddMenu( "BoostStoreMenu", $"resource/ui/menus/booststore.menu", InitBoostStoreMenu )
+
+ AddMenu( "PrivateLobbyMenu", $"resource/ui/menus/private_lobby.menu", InitPrivateMatchMenu, "#PRIVATE_MATCH" )
+ AddMenu( "MapsMenu", $"resource/ui/menus/map_select.menu", InitMapsMenu )
+ AddMenu( "ModesMenu", $"resource/ui/menus/mode_select.menu", InitModesMenu )
+ AddMenu( "MatchSettingsMenu", $"resource/ui/menus/match_settings.menu", InitMatchSettingsMenu )
+
+ AddMenu( "Advocate_Letter", $"resource/ui/menus/advocate_letter.menu", InitAdvocateLetterMenu )
+ AddMenu( "Generation_Respawn", $"resource/ui/menus/generation_respawn.menu", InitGenerationRespawnMenu )
+ AddMenu( "ChallengesMenu", $"resource/ui/menus/challenges.menu", InitChallengesMenu )
+
+ AddMenu( "ViewStatsMenu", $"resource/ui/menus/viewstats.menu", InitViewStatsMenu, "#PERSONAL_STATS" )
+ AddMenu( "ViewStats_Overview_Menu", $"resource/ui/menus/viewstats_overview.menu", InitViewStatsOverviewMenu )
+ //AddMenu( "ViewStats_Kills_Menu", $"resource/ui/menus/viewstats_kills.menu", InitViewStatsKillsMenu )
+ AddMenu( "ViewStats_Time_Menu", $"resource/ui/menus/viewstats_time.menu", InitViewStatsTimeMenu )
+ //AddMenu( "ViewStats_Distance_Menu", $"resource/ui/menus/viewstats_distance.menu", InitViewStatsDistanceMenu )
+ AddMenu( "ViewStats_Weapons_Menu", $"resource/ui/menus/viewstats_weapons.menu", InitViewStatsWeaponsMenu )
+ AddMenu( "ViewStats_Titans_Menu", $"resource/ui/menus/viewstats_titans.menu", InitViewStatsTitansMenu )
+ AddMenu( "ViewStats_Misc_Menu", $"resource/ui/menus/viewstats_misc.menu", InitViewStatsMiscMenu )
+ AddMenu( "ViewStats_Maps_Menu", $"resource/ui/menus/viewstats_maps.menu", InitViewStatsMapsMenu )
+
+ AddMenu( "PostGameMenu", $"resource/ui/menus/postgame.menu", InitPostGameMenu )
+ AddMenu( "EOG_XP", $"resource/ui/menus/eog_xp.menu", InitEOG_XPMenu )
+ AddMenu( "EOG_Coins", $"resource/ui/menus/eog_coins.menu", InitEOG_CoinsMenu )
+ AddMenu( "EOG_Challenges", $"resource/ui/menus/eog_challenges.menu", InitEOG_ChallengesMenu )
+ AddMenu( "EOG_Unlocks", $"resource/ui/menus/eog_unlocks.menu", InitEOG_UnlocksMenu )
+ AddMenu( "EOG_Scoreboard", $"resource/ui/menus/eog_scoreboard.menu", InitEOG_ScoreboardMenu )
+
+ AddMenu( "CreditsMenu", $"resource/ui/menus/credits.menu", InitCreditsMenu, "#CREDITS" )
+
+ AddMenu( "BurnCardMenu", $"resource/ui/menus/burn_cards.menu", InitBurnCardMenu, "#MENU_BURNCARD_MENU" )
+ AddMenu( "FactionChoiceMenu", $"resource/ui/menus/faction_choice.menu", InitFactionChoiceMenu, "#FACTION_CHOICE_MENU" )
+ AddMenu( "ArmoryMenu", $"resource/ui/menus/armory.menu", InitArmoryMenu, "#ARMORY_MENU" )
+
+ AddMenu( "StoreMenu", $"resource/ui/menus/store.menu", InitStoreMenu, "#STORE_MENU" )
+ AddMenu( "StoreMenu_NewReleases", $"resource/ui/menus/store_new_releases.menu", InitStoreMenuNewReleases, "#STORE_NEW_RELEASES" )
+ AddMenu( "StoreMenu_Limited", $"resource/ui/menus/store_limited.menu", InitStoreMenuLimited, "#STORE_LIMITED" )
+ AddMenu( "StoreMenu_Sales", $"resource/ui/menus/store_bundles.menu", InitStoreMenuSales, "#STORE_BUNDLES" )
+ AddMenu( "StoreMenu_Titans", $"resource/ui/menus/store_prime_titans.menu", InitStoreMenuTitans, "#STORE_TITANS" ) // reusing store_prime_titans.menu
+ AddMenu( "StoreMenu_PrimeTitans", $"resource/ui/menus/store_prime_titans.menu", InitStoreMenuPrimeTitans, "#STORE_PRIME_TITANS" )
+ //AddMenu( "StoreMenu_WeaponSelect", $"resource/ui/menus/store_weapon_select.menu", InitStoreMenuWeaponSelect )
+ //AddMenu( "StoreMenu_WeaponSkinPreview", $"resource/ui/menus/store_weapon_skin_preview.menu", InitStoreMenuWeaponSkinPreview )
+ AddMenu( "StoreMenu_WeaponSkinBundles", $"resource/ui/menus/store_weapon_skin_bundles.menu", InitStoreMenuWeaponSkinBundles )
+ AddMenu( "StoreMenu_WeaponSkins", $"resource/ui/menus/store_weapons.menu", InitStoreMenuWeaponSkins )
+ AddMenu( "StoreMenu_Customization", $"resource/ui/menus/store_customization.menu", InitStoreMenuCustomization, "#STORE_CUSTOMIZATION_PACKS" )
+ AddMenu( "StoreMenu_CustomizationPreview", $"resource/ui/menus/store_customization_preview.menu", InitStoreMenuCustomizationPreview, "#STORE_CUSTOMIZATION_PACKS" )
+ AddMenu( "StoreMenu_Camo", $"resource/ui/menus/store_camo.menu", InitStoreMenuCamo, "#STORE_CAMO_PACKS" )
+ AddMenu( "StoreMenu_CamoPreview", $"resource/ui/menus/store_camo_preview.menu", InitStoreMenuCamoPreview, "#STORE_CAMO_PACKS" )
+ AddMenu( "StoreMenu_Callsign", $"resource/ui/menus/store_callsign.menu", InitStoreMenuCallsign, "#STORE_CALLSIGN_PACKS" )
+ AddMenu( "StoreMenu_CallsignPreview", $"resource/ui/menus/store_callsign_preview.menu", InitStoreMenuCallsignPreview, "#STORE_CALLSIGN_PACKS" )
+
+ AddMenu( "KnowledgeBaseMenu", $"resource/ui/menus/knowledgebase.menu", InitKnowledgeBaseMenu )
+ AddMenu( "KnowledgeBaseMenuSubMenu", $"resource/ui/menus/knowledgebase_submenu.menu", InitKnowledgeBaseMenuSubMenu )
+
+ AddMenu( "DevMenu", $"resource/ui/menus/dev.menu", InitDevMenu, "Dev" )
+ InitSharedStartPoints()
+
+ foreach ( menu in uiGlobal.allMenus )
+ {
+ if ( uiGlobal.menuData[ menu ].initFunc != null )
+ uiGlobal.menuData[ menu ].initFunc()
+
+ array<var> elems = GetElementsByClassname( menu, "TabsCommonClass" )
+ if ( elems.len() )
+ uiGlobal.menuData[ menu ].hasTabs = true
+
+ elems = GetElementsByClassname( menu, "EnableKeyBindingIcons" )
+ foreach ( elem in elems )
+ Hud_EnableKeyBindingIcons( elem )
+ }
+
+ InitTabs()
+
+ var tabbedMenu = GetMenu( "PostGameMenu" )
+ AddPanel( tabbedMenu, "PVEPanel", InitPVEPanel )
+ AddPanel( tabbedMenu, "SummaryPanel", InitSummaryPanel )
+ AddPanel( tabbedMenu, "FDAwardsPanel", InitFDAwardsPanel )
+
+ AddPanel( tabbedMenu, "ScoreboardPanel", InitScoreboardPanel )
+
+ foreach ( panel in uiGlobal.allPanels )
+ {
+ if ( uiGlobal.panelData[ panel ].initFunc != null )
+ uiGlobal.panelData[ panel ].initFunc()
+ }
+
+ // A little weird, but GetElementsByClassname() uses menu scope rather than parent scope.
+ foreach ( menu in uiGlobal.allMenus )
+ {
+ array<var> buttons = GetElementsByClassname( menu, "DefaultFocus" )
+ foreach ( button in buttons )
+ {
+ var panel = Hud_GetParent( button )
+
+ //Assert( elems.len() == 1, "More than 1 panel element set as DefaultFocus!" )
+ Assert( panel != null, "no parent panel found for button " + Hud_GetHudName( button ) )
+ Assert( panel in uiGlobal.panelData, "panel " + Hud_GetHudName( panel ) + " isn't in uiGlobal.panelData, but button " + Hud_GetHudName( button ) + " has defaultFocus set!" )
+ uiGlobal.panelData[ panel ].defaultFocus = button
+ //printt( "Found DefaultFocus, button was:", Hud_GetHudName( button ), "panel was:", Hud_GetHudName( panel ) )
+ }
+ }
+
+ InitFooterOptions()
+
+ #if DEV
+ if ( Dev_CommandLineHasParm( "-autoprecache_all" ) )
+ {
+ // repreache all levels
+ ExecuteLoadingClientCommands_SetStartPoint( "sp_training" )
+ ClientCommand( "map sp_training" )
+ CloseAllMenus()
+ }
+ #endif
+}
+
+void functionref( var ) function AdvanceMenuEventHandler( var menu )
+{
+ return void function( var item ) : ( menu )
+ {
+ if ( Hud_IsLocked( item ) )
+ return
+
+ AdvanceMenu( menu )
+ }
+}
+
+void function PCBackButton_Activate( var button )
+{
+ UICodeCallback_NavigateBack()
+}
+
+void function PCSwitchTeamsButton_Activate( var button )
+{
+ ClientCommand( "PrivateMatchSwitchTeams" )
+}
+
+void function PCToggleSpectateButton_Activate( var button )
+{
+ ClientCommand( "PrivateMatchToggleSpectate" )
+}
+
+void function ToggleButtonStates( var button )
+{
+ for ( ;; )
+ {
+ Hud_SetEnabled( button, true )
+ wait 1
+ Hud_SetSelected( button, true )
+ wait 1
+ Hud_SetLocked( button, true )
+ wait 1
+ Hud_SetNew( button, true )
+ wait 1
+ Hud_SetNew( button, false )
+ wait 1
+ Hud_SetLocked( button, false )
+ wait 1
+ Hud_SetSelected( button, false )
+ wait 1
+ Hud_SetEnabled( button, false )
+ wait 1
+ }
+}
+
+void function AddMenuElementsByClassname( var menu, string classname )
+{
+ array<var> elements = GetElementsByClassname( menu, classname )
+
+ if ( !(classname in menu.classElements) )
+ menu.classElements[classname] <- []
+
+ menu.classElements[classname].extend( elements )
+}
+
+void function FocusDefault( var menu )
+{
+ if (
+ menu == GetMenu( "MainMenu" ) ||
+ menu == GetMenu( "CategorySelectMenu" ) ||
+ menu == GetMenu( "AbilitySelectMenu" ) ||
+ menu == GetMenu( "PassiveSelectMenu" ) ||
+ menu == GetMenu( "WeaponSelectMenu" ) ||
+ menu == GetMenu( "SuitSelectMenu" ) ||
+ menu == GetMenu( "CamoSelectMenu" ) ||
+ menu == GetMenu( "NoseArtSelectMenu" ) ||
+ menu == GetMenu( "FactionChoiceMenu" ) ||
+ menu == GetMenu( "BurnCardMenu" ) ||
+ menu == GetMenu( "CallsignCardSelectMenu" ) ||
+ menu == GetMenu( "CallsignIconSelectMenu" ) )
+ {
+ }
+ else
+ {
+ //printt( "FocusDefaultMenuItem() called" )
+ FocusDefaultMenuItem( menu )
+ }
+}
+
+void function SetPanelDefaultFocus( var panel, var button )
+{
+ uiGlobal.panelData[ panel ].defaultFocus = button
+}
+
+void function PanelFocusDefault( var panel )
+{
+ //printt( "PanelFocusDefault called" )
+ if ( uiGlobal.panelData[ panel ].defaultFocus )
+ {
+ Hud_SetFocused( uiGlobal.panelData[ panel ].defaultFocus )
+ //printt( "PanelFocusDefault if passed,", Hud_GetHudName( uiGlobal.panelData[ panel ].defaultFocus ), "focused" )
+ }
+}
+
+void function SetMenuThinkFunc( var menu, void functionref() func )
+{
+ Assert( uiGlobal.menuData[ menu ].thinkFunc == null )
+ uiGlobal.menuData[ menu ].thinkFunc = func
+}
+
+void function AddMenuEventHandler( var menu, int event, void functionref() func )
+{
+ if ( event == eUIEvent.MENU_OPEN )
+ {
+ Assert( uiGlobal.menuData[ menu ].openFunc == null )
+ uiGlobal.menuData[ menu ].openFunc = func
+ }
+ else if ( event == eUIEvent.MENU_CLOSE )
+ {
+ Assert( uiGlobal.menuData[ menu ].closeFunc == null )
+ uiGlobal.menuData[ menu ].closeFunc = func
+ }
+ else if ( event == eUIEvent.MENU_SHOW )
+ {
+ Assert( uiGlobal.menuData[ menu ].showFunc == null )
+ uiGlobal.menuData[ menu ].showFunc = func
+ }
+ else if ( event == eUIEvent.MENU_HIDE )
+ {
+ Assert( uiGlobal.menuData[ menu ].hideFunc == null )
+ uiGlobal.menuData[ menu ].hideFunc = func
+ }
+ else if ( event == eUIEvent.MENU_NAVIGATE_BACK )
+ {
+ Assert( uiGlobal.menuData[ menu ].navBackFunc == null )
+ uiGlobal.menuData[ menu ].navBackFunc = func
+ }
+ else if ( event == eUIEvent.MENU_TAB_CHANGED )
+ {
+ Assert( uiGlobal.menuData[ menu ].tabChangedFunc == null )
+ uiGlobal.menuData[ menu ].tabChangedFunc = func
+ }
+ else if ( event == eUIEvent.MENU_ENTITLEMENTS_CHANGED )
+ {
+ Assert( uiGlobal.menuData[ menu ].entitlementsChangedFunc == null )
+ uiGlobal.menuData[ menu ].entitlementsChangedFunc = func
+ }
+ else if ( event == eUIEvent.MENU_INPUT_MODE_CHANGED )
+ {
+ Assert( uiGlobal.menuData[ menu ].inputModeChangedFunc == null )
+ uiGlobal.menuData[ menu ].inputModeChangedFunc = func
+ }
+}
+
+void function AddPanelEventHandler( var panel, int event, void functionref() func )
+{
+ if ( event == eUIEvent.PANEL_SHOW )
+ uiGlobal.panelData[ panel ].showFunc = func
+ else if ( event == eUIEvent.PANEL_HIDE )
+ uiGlobal.panelData[ panel ].hideFunc = func
+}
+
+// TODO: Get a real on open event from code?
+void function OpenMenuWrapper( var menu, bool focusDefault )
+{
+ OpenMenu( menu )
+ printt( Hud_GetHudName( menu ), "menu opened" )
+
+ Assert( menu in uiGlobal.menuData )
+ if ( uiGlobal.menuData[ menu ].openFunc != null )
+ {
+ thread uiGlobal.menuData[ menu ].openFunc()
+ //printt( "Called openFunc for:", menu.GetHudName() )
+ }
+
+ if ( focusDefault )
+ FocusDefault( menu )
+
+ //UpdateMenuTabs()
+ UpdateFooterOptions()
+}
+
+void function CloseMenuWrapper( var menu )
+{
+ CloseMenu( menu )
+ printt( Hud_GetHudName( menu ), "menu closed" )
+
+ Assert( menu in uiGlobal.menuData )
+ if ( uiGlobal.menuData[ menu ].closeFunc != null )
+ {
+ thread uiGlobal.menuData[ menu ].closeFunc()
+ //printt( "Called closeFunc for:", Hud_GetHudName( menu ) )
+ }
+}
+
+bool function IsLevelMultiplayer( string levelname )
+{
+ return levelname.find( "mp_" ) == 0
+}
+
+void function AddButtonEventHandler( var button, int event, void functionref( var ) func )
+{
+ Hud_AddEventHandler( button, event, func )
+}
+
+void function AddEventHandlerToButton( var menu, string buttonName, int event, void functionref( var ) func )
+{
+ var button = Hud_GetChild( menu, buttonName )
+ Hud_AddEventHandler( button, event, func )
+}
+
+void function AddEventHandlerToButtonClass( var menu, string classname, int event, void functionref( var ) func )
+{
+ array<var> buttons = GetElementsByClassname( menu, classname )
+
+ foreach ( button in buttons )
+ {
+ //printt( "button name:", Hud_GetHudName( button ) )
+ Hud_AddEventHandler( button, event, func )
+ }
+}
+
+// Added slight delay to main menu music to work around a hitch caused when the game first starts up
+void function PlayMusicAfterDelay()
+{
+ wait MAINMENU_MUSIC_DELAY
+ if ( uiGlobal.playingMusic )
+ EmitUISound( "MainMenu_Music" )
+}
+
+void function DisableMusic()
+{
+ EmitUISound( "Movie_MuteAllGameSound" )
+}
+
+void function EnableMusic()
+{
+ StopUISoundByName( "Movie_MuteAllGameSound" )
+}
+
+void function PlayMusic()
+{
+ if ( !uiGlobal.playingMusic && !uiGlobal.playingVideo && !uiGlobal.playingCredits )
+ {
+ //printt( "PlayMusic() called. Playing: MainMenu_Music. uiGlobal.playingMusic:", uiGlobal.playingMusic, "uiGlobal.playingVideo:", uiGlobal.playingVideo, "uiGlobal.playingCredits:", uiGlobal.playingCredits )
+ uiGlobal.playingMusic = true
+ thread PlayMusicAfterDelay()
+ }
+ else
+ {
+ //printt( "PlayMusic() called, but doing nothing. uiGlobal.playingMusic:", uiGlobal.playingMusic, "uiGlobal.playingVideo:", uiGlobal.playingVideo, "uiGlobal.playingCredits:", uiGlobal.playingCredits )
+ }
+}
+
+void function StopMusic()
+{
+ //printt( "StopMusic() called. Stopping: MainMenu_Music" )
+ StopUISound( "MainMenu_Music" )
+ uiGlobal.playingMusic = false
+}
+
+void function RegisterMenuVarInt( string varName, int value )
+{
+ table<string, int> intVars = uiGlobal.intVars
+
+ Assert( !( varName in intVars ) )
+
+ intVars[varName] <- value
+}
+
+void function RegisterMenuVarBool( string varName, bool value )
+{
+ table<string, bool> boolVars = uiGlobal.boolVars
+
+ Assert( !( varName in boolVars ) )
+
+ boolVars[varName] <- value
+}
+
+void function RegisterMenuVarVar( string varName, var value )
+{
+ table<string, var> varVars = uiGlobal.varVars
+
+ Assert( !( varName in varVars ) )
+
+ varVars[varName] <- value
+}
+
+int function GetMenuVarInt( string varName )
+{
+ table<string, int> intVars = uiGlobal.intVars
+
+ Assert( varName in intVars )
+
+ return intVars[varName]
+}
+
+bool function GetMenuVarBool( string varName )
+{
+ table<string, bool> boolVars = uiGlobal.boolVars
+
+ Assert( varName in boolVars )
+
+ return boolVars[varName]
+}
+
+var function GetMenuVarVar( string varName )
+{
+ table<string, var> varVars = uiGlobal.varVars
+
+ Assert( varName in varVars )
+
+ return varVars[varName]
+}
+
+void function SetMenuVarInt( string varName, int value )
+{
+ table<string, int> intVars = uiGlobal.intVars
+
+ Assert( varName in intVars )
+
+ if ( intVars[varName] == value )
+ return
+
+ intVars[varName] = value
+
+ table<string, array<void functionref()> > varChangeFuncs = uiGlobal.varChangeFuncs
+
+ if ( varName in varChangeFuncs )
+ {
+ foreach ( func in varChangeFuncs[varName] )
+ {
+ //printt( varName, "changed, calling changeFunc:", string( func ) )
+ func()
+ }
+ }
+}
+
+void function SetMenuVarBool( string varName, bool value )
+{
+ table<string, bool> boolVars = uiGlobal.boolVars
+
+ Assert( varName in boolVars )
+
+ if ( boolVars[varName] == value )
+ return
+
+ boolVars[varName] = value
+
+ table<string, array<void functionref()> > varChangeFuncs = uiGlobal.varChangeFuncs
+
+ if ( varName in varChangeFuncs )
+ {
+ foreach ( func in varChangeFuncs[varName] )
+ {
+ //printt( varName, "changed, calling changeFunc:", string( func ) )
+ func()
+ }
+ }
+}
+
+void function SetMenuVarVar( string varName, var value )
+{
+ table<string, var> varVars = uiGlobal.varVars
+
+ Assert( varName in varVars )
+
+ if ( varVars[varName] == value )
+ return
+
+ varVars[varName] = value
+
+ table<string, array<void functionref()> > varChangeFuncs = uiGlobal.varChangeFuncs
+
+ if ( varName in varChangeFuncs )
+ {
+ foreach ( func in varChangeFuncs[varName] )
+ {
+ //printt( varName, "changed, calling changeFunc:", string( func ) )
+ func()
+ }
+ }
+}
+
+void function AddMenuVarChangeHandler( string varName, void functionref() func )
+{
+ table<string, array<void functionref()> > varChangeFuncs = uiGlobal.varChangeFuncs
+
+ if ( !( varName in varChangeFuncs ) )
+ varChangeFuncs[varName] <- []
+
+ // TODO: Verify we're not duplicating an existing func
+ varChangeFuncs[varName].append( func )
+}
+
+// These are common menu statuses that trigger menu logic any time they change
+// They should become code callbacks, so script doesn't poll
+void function InitGlobalMenuVars()
+{
+ RegisterMenuVarVar( "focus", null )
+ RegisterMenuVarBool( "isConnected", false )
+ RegisterMenuVarBool( "isFullyConnected", false )
+ RegisterMenuVarBool( "isPartyLeader", false )
+ RegisterMenuVarBool( "isPrivateMatch", false )
+ RegisterMenuVarBool( "isGamepadActive", IsControllerModeActive() )
+
+ #if CONSOLE_PROG
+ RegisterMenuVarBool( "CONSOLE_isOnline", false )
+ RegisterMenuVarBool( "CONSOLE_isSignedIn", false )
+ #endif // CONSOLE_PROG
+
+ #if DURANGO_PROG
+ RegisterMenuVarBool( "DURANGO_isGameFullyInstalled", false )
+ RegisterMenuVarBool( "DURANGO_canInviteFriends", false )
+ RegisterMenuVarBool( "DURANGO_isJoinable", false )
+ #elseif PS4_PROG
+ RegisterMenuVarBool( "PS4_canInviteFriends", false)
+ #elseif PC_PROG
+ RegisterMenuVarBool( "ORIGIN_isEnabled", false )
+ RegisterMenuVarBool( "ORIGIN_isJoinable", false )
+ #endif
+
+ thread UpdateFocus()
+ thread UpdateIsConnected()
+ thread UpdateIsFullyConnected()
+ thread UpdateAmIPartyLeader()
+ thread UpdateIsPrivateMatch()
+ thread UpdateActiveMenuThink()
+
+ #if CONSOLE_PROG
+ thread UpdateConsole_IsOnline()
+ thread UpdateConsole_IsSignedIn()
+ #endif // CONSOLE_PROG
+
+ #if DURANGO_PROG
+ thread UpdateDurango_IsGameFullyInstalled()
+ thread UpdateDurango_CanInviteFriends()
+ thread UpdateDurango_IsJoinable()
+ #elseif PS4_PROG
+ thread UpdatePS4_CanInviteFriends()
+ #elseif PC_PROG
+ thread UpdateOrigin_IsEnabled()
+ thread UpdateOrigin_IsJoinable()
+ thread UpdateIsGamepadActive()
+ #endif
+}
+
+void function UpdateFocus()
+{
+ while ( true )
+ {
+ SetMenuVarVar( "focus", GetFocus() )
+ WaitFrame()
+ }
+}
+
+void function UpdateActiveMenuThink()
+{
+ while ( true )
+ {
+ var menu = GetActiveMenu()
+ if ( menu )
+ {
+ Assert( menu in uiGlobal.menuData )
+ if ( uiGlobal.menuData[ menu ].thinkFunc != null )
+ uiGlobal.menuData[ menu ].thinkFunc()
+ }
+
+ WaitFrame()
+ }
+}
+
+void function UpdateIsConnected()
+{
+ while ( true )
+ {
+ SetMenuVarBool( "isConnected", IsConnected() )
+ WaitFrame()
+ }
+}
+
+void function UpdateIsFullyConnected()
+{
+ while ( true )
+ {
+ SetMenuVarBool( "isFullyConnected", IsFullyConnected() )
+ WaitFrame()
+ }
+}
+
+void function UpdateAmIPartyLeader()
+{
+ while ( true )
+ {
+ SetMenuVarBool( "isPartyLeader", AmIPartyLeader() )
+ WaitFrame()
+ }
+}
+
+void function UpdateIsPrivateMatch()
+{
+ while ( true )
+ {
+ SetMenuVarBool( "isPrivateMatch", IsPrivateMatch() )
+ WaitFrame()
+ }
+}
+
+#if CONSOLE_PROG
+ void function UpdateConsole_IsOnline()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "CONSOLE_isOnline", Console_IsOnline() )
+ WaitFrame()
+ }
+ }
+
+ void function UpdateConsole_IsSignedIn()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "CONSOLE_isSignedIn", Console_IsSignedIn() )
+ WaitFrame()
+ }
+ }
+#endif // CONSOLE_PROG
+
+
+#if PS4_PROG
+ void function UpdatePS4_CanInviteFriends()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "PS4_canInviteFriends", PS4_canInviteFriends() )
+ WaitFrame()
+ }
+ }
+#endif // PS4_PROG
+
+
+
+#if DURANGO_PROG
+ void function UpdateDurango_IsGameFullyInstalled()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "DURANGO_isGameFullyInstalled", IsGameFullyInstalled() )
+ wait 1 // Poll less frequent
+ }
+ }
+
+ void function UpdateDurango_CanInviteFriends()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "DURANGO_canInviteFriends", Durango_CanInviteFriends() )
+ WaitFrame()
+ }
+ }
+
+ void function UpdateDurango_IsJoinable()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "DURANGO_isJoinable", Durango_IsJoinable() )
+ WaitFrame()
+ }
+ }
+#endif // DURANGO_PROG
+
+#if PC_PROG
+ void function UpdateOrigin_IsEnabled()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "ORIGIN_isEnabled", Origin_IsEnabled() )
+ WaitFrame()
+ }
+ }
+
+ void function UpdateOrigin_IsJoinable()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "ORIGIN_isJoinable", Origin_IsJoinable() )
+ WaitFrame()
+ }
+ }
+
+ void function UpdateIsGamepadActive()
+ {
+ while ( true )
+ {
+ SetMenuVarBool( "isGamepadActive", IsControllerModeActive() )
+ WaitFrame()
+ }
+ }
+#endif // PC_PROG
+
+void function InviteFriends( var button )
+{
+ //AdvanceMenu( GetMenu( "InviteFriendsToPartyMenu" ) )
+
+ #if DURANGO_PROG
+ Durango_InviteFriends()
+ #elseif PS4_PROG
+ ClientCommand("session_debug_invite");
+ #elseif PC_PROG
+ Assert( Origin_IsEnabled() )
+ Assert( Origin_IsJoinable() )
+
+ Origin_ShowInviteFriendsDialog()
+ #endif
+}
+
+#if DURANGO_PROG
+void function OpenXboxPartyApp( var button )
+{
+ Durango_OpenPartyApp()
+}
+
+void function OpenXboxHelp( var button )
+{
+ Durango_ShowHelpWindow()
+}
+#endif // DURANGO_PROG
+
+void function OpenReviewTermsDialog( var button )
+{
+ AdvanceMenu( GetMenu( "ReviewTermsDialog" ) )
+}
+
+void function OpenErrorDialog( string errorDetails )
+{
+ DialogData dialogData
+ dialogData.header = "#ERROR"
+ dialogData.message = errorDetails
+ dialogData.image = $"ui/menu/common/dialog_error"
+
+#if PC_PROG
+ AddDialogButton( dialogData, "#DISMISS" )
+
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+#endif // PC_PROG
+ AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" )
+
+ while ( uiGlobal.activeMenu != GetMenu( "MainMenu" ) )
+ {
+ WaitSignal( uiGlobal.signalDummy, "OpenErrorDialog", "ActiveMenuChanged" )
+ }
+
+ OpenDialog( dialogData )
+}
+
+bool function IsDialog( var menu )
+{
+ if ( menu == null )
+ return false
+
+ return uiGlobal.menuData[ menu ].isDialog
+}
+
+bool function IsDialogActive( DialogData dialogData )
+{
+ if ( !IsDialog( uiGlobal.activeMenu ) )
+ return false
+
+ return uiGlobal.menuData[ uiGlobal.activeMenu ].dialogData == dialogData
+}
+
+bool function IsDialogOnlyActiveMenu()
+{
+ if ( !IsDialog( uiGlobal.activeMenu ) )
+ return false
+
+ int stackLen = uiGlobal.menuStack.len()
+ if ( stackLen < 1 )
+ return false
+
+ if ( uiGlobal.menuStack[stackLen - 1] != uiGlobal.activeMenu )
+ return false
+
+ if ( stackLen == 1 )
+ return true
+
+ if ( uiGlobal.menuStack[stackLen - 2] == null )
+ return true
+
+ return false
+}
+
+void function SetNavUpDown( array<var> buttons, var wrap = true )
+{
+ Assert( buttons.len() > 0 )
+
+ var first = buttons[0]
+ var last = buttons[buttons.len() - 1]
+ var prev
+ var next
+ var button
+
+ for ( int i = 0; i < buttons.len(); i++ )
+ {
+ button = buttons[i]
+
+ if ( button == first )
+ prev = last
+ else
+ prev = buttons[i - 1]
+
+ if ( button == last )
+ next = first
+ else
+ next = buttons[i + 1]
+
+ button.SetNavUp( prev )
+ button.SetNavDown( next )
+
+ //printt( "SetNavUP for:", Hud_GetHudName( button ), "to:", Hud_GetHudName( prev ) )
+ //printt( "SetNavDown for:", Hud_GetHudName( button ), "to:", Hud_GetHudName( next ) )
+ }
+}
+
+void function SetNavLeftRight( array<var> buttons, var wrap = true )
+{
+ Assert( buttons.len() > 0 )
+
+ var first = buttons[0]
+ var last = buttons[buttons.len() - 1]
+ var prev
+ var next
+ var button
+
+ for ( int i = 0; i < buttons.len(); i++ )
+ {
+ button = buttons[i]
+
+ if ( button == first )
+ prev = last
+ else
+ prev = buttons[i - 1]
+
+ if ( button == last )
+ next = first
+ else
+ next = buttons[i + 1]
+
+ button.SetNavLeft( prev )
+ button.SetNavRight( next )
+
+ //printt( "SetNavUP for:", Hud_GetHudName( button ), "to:", Hud_GetHudName( prev ) )
+ //printt( "SetNavDown for:", Hud_GetHudName( button ), "to:", Hud_GetHudName( next ) )
+ }
+}
+
+void function UICodeCallback_EntitlementsChanged()
+{
+ if ( uiGlobal.activeMenu == null )
+ return
+
+ if ( uiGlobal.menuData[ uiGlobal.activeMenu ].entitlementsChangedFunc != null )
+ thread uiGlobal.menuData[ uiGlobal.activeMenu ].entitlementsChangedFunc()
+}
+
+#if PC_PROG
+void function QuitGame()
+{
+ ClientCommand( "quit" )
+}
+#endif
+
+void function UICodeCallback_StoreTransactionCompleted()
+{
+ // this callback is only supported and needed on PS4 currently
+#if PS4_PROG
+ if ( InStoreMenu() )
+ OnOpenDLCStore()
+#endif
+}
+
+void function UICodeCallback_GamePurchased()
+{
+ // this callback is only supported and needed on PC currently
+#if PC_PROG
+ DialogData dialogData
+ dialogData.header = "#PURCHASE_GAME_COMPLETE"
+ dialogData.message = "#PURCHASE_GAME_RESTART"
+ AddDialogButton( dialogData, "#QUIT", QuitGame )
+
+ OpenDialog( dialogData )
+#endif
+}
+
+bool function IsTrialPeriodActive()
+{
+ return GetConVarBool( "trialPeriodIsActive" )
+}
+
+void function LaunchGamePurchaseOrDLCStore( array<string> menuNames = [ "StoreMenu" ] )
+{
+ if ( Script_IsRunningTrialVersion() )
+ {
+ LaunchGamePurchase()
+ }
+ else
+ {
+ void functionref() preOpenFunc = null
+
+ foreach ( menuName in menuNames )
+ {
+ // Special case because this menu needs a few properties set before opening
+ if ( menuName == "StoreMenu_WeaponSkins" )
+ {
+ preOpenFunc = DefaultToDLC11WeaponWarpaintBundle
+ break
+ }
+ }
+
+ OpenStoreMenu( menuNames, preOpenFunc )
+ }
+}
+
+void function UICodeCallback_PartyUpdated()
+{
+ if ( AmIPartyLeader() )
+ {
+ string activeSearchingPlaylist = GetActiveSearchingPlaylist()
+ if ( activeSearchingPlaylist != "" && !CanPlaylistFitMyParty( activeSearchingPlaylist ) )
+ {
+ CancelMatchSearch()
+
+ DialogData dialogData
+ dialogData.header = "#MATCHMAKING_CANCELED"
+ dialogData.message = "#MATCHMAKING_CANCELED_REASON_PARTY_SIZE"
+ AddDialogButton( dialogData, "#OK" )
+
+ OpenDialog( dialogData )
+ }
+ }
+}
+
+
+void function HACK_DelayedSetFocus_BecauseWhy( var item )
+{
+ wait 0.1
+ if ( IsValid( item ) )
+ Hud_SetFocused( item )
+}
+
+void function ClassicMusic_OnChange( var button )
+{
+ bool isEnabled = GetConVarBool( "sound_classic_music" )
+
+ if ( IsFullyConnected() && IsMultiplayer() && GetUIPlayer() )
+ {
+ if ( IsItemLocked( GetUIPlayer(), "classic_music" ) )
+ SetConVarBool( "sound_classic_music", false )
+
+ if ( IsLobby() )
+ thread RunClientScript( "OnSoundClassicMusicChanged" )
+ }
+}
+
+bool function IsClassicMusicAvailable()
+{
+ bool classicMusicAvailable = false
+ if ( IsFullyConnected() && IsMultiplayer() && GetUIPlayer() )
+ classicMusicAvailable = !IsItemLocked( GetUIPlayer(), "classic_music" )
+
+ return classicMusicAvailable
+}
+
+void function UICodeCallback_KeyBindOverwritten( string key, string oldbinding, string newbinding )
+{
+ DialogData dialogData
+ dialogData.header = Localize( "#MENU_KEYBIND_WAS_BEING_USED", key )
+ dialogData.message = Localize( "#MENU_KEYBIND_WAS_BEING_USED_SUB", key, Localize( oldbinding ) )
+
+ AddDialogButton( dialogData, "#OK" )
+
+ OpenDialog( dialogData )
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/atlas_auth.nut b/Northstar.Client/mod/scripts/vscripts/ui/atlas_auth.nut
new file mode 100644
index 00000000..89b7f719
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/atlas_auth.nut
@@ -0,0 +1,56 @@
+global function AtlasAuthDialog
+
+void function AtlasAuthDialog()
+{
+ thread AtlasAuthDialog_Threaded()
+}
+
+void function AtlasAuthDialog_Threaded()
+{
+ // wait at least 1 frame so that the main menu can be loaded first
+ WaitFrame()
+
+ while ( !NSIsMasterServerAuthenticated() || GetConVarBool( "ns_auth_allow_insecure" ) )
+ WaitFrame()
+
+ if ( GetConVarBool( "ns_auth_allow_insecure" ) )
+ return
+
+ MasterServerAuthResult res = NSGetMasterServerAuthResult()
+
+ // do nothing on successful authentication
+ if ( res.success )
+ return
+
+ EmitUISound( "blackmarket_purchase_fail" )
+
+ DialogData dialogData
+ dialogData.image = $"ui/menu/common/dialog_error"
+ dialogData.header = Localize( "#AUTHENTICATION_FAILED_HEADER" )
+
+ // if we got a special error message from Atlas, display it
+ if ( res.errorMessage != "" )
+ dialogData.message = res.errorMessage
+ else
+ dialogData.message = Localize( "#AUTHENTICATION_FAILED_BODY" )
+
+ if ( res.errorCode != "" )
+ dialogData.message += format( "\n\n%s", Localize( "#AUTHENTICATION_FAILED_ERROR_CODE", res.errorCode ) )
+
+ string link = "https://r2northstar.gitbook.io/r2northstar-wiki/installing-northstar/troubleshooting"
+ // link to generic troubleshooting page if we don't have an error code from Atlas
+ if ( res.errorCode != "" )
+ link = format( "%s#%s", link, res.errorCode )
+
+ CloseAllDialogs()
+ AddDialogButton( dialogData, "#OK" )
+ AddDialogButton( dialogData, Localize( "#AUTHENTICATION_FAILED_HELP" ), void function() : ( dialogData, link )
+ {
+ // todo: get MS to redirect, so i can use an MS link or something?
+ LaunchExternalWebBrowser( link, WEBBROWSER_FLAG_FORCEEXTERNAL )
+ // keep the dialog open
+ OpenDialog( dialogData )
+ } )
+
+ OpenDialog( dialogData )
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/chatroom.nut b/Northstar.Client/mod/scripts/vscripts/ui/chatroom.nut
new file mode 100644
index 00000000..4e98ee8a
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/chatroom.nut
@@ -0,0 +1,902 @@
+untyped
+
+global function Chatroom_GlobalInit
+global function InitChatroom
+global function UpdateChatroomUI
+global function UICodeCallback_ShowUserInfo
+global function UICodeCallback_RemoteMatchInfoUpdated
+global function UICodeCallback_SetChatroomMode
+global function UpdateOpenInvites
+global function HideOpenInvite
+global function ShowOpenInvite
+global function FillInUserInfoPanel
+global function UpdateChatroomThread
+global function IsVoiceChatPushToTalk
+
+global function bsupdate
+
+global const LOBBY_MATERIAL_OWNER = $"rui/menu/common/lobby_icon_owner"
+global const LOBBY_MATERIAL_ADMIN = $"rui/menu/common/lobby_icon_admin"
+
+struct RemoteMatchPlayerInfoRow
+{
+ var playerPanel
+ var name
+ var score
+ var kills
+ var deaths
+}
+
+struct RemoteMatchInfoPanel
+{
+ var panel
+ var PlaylistName
+ var MapName
+ var ModeName
+ var TimeLeft
+ var ScoreLimit
+ var Team1Score
+ var Team2Score
+ array<RemoteMatchPlayerInfoRow> team1Players
+ array<RemoteMatchPlayerInfoRow> team2Players
+}
+
+struct OpenInviteUI
+{
+ var openInviteJoinButton
+ var openInvitePanel
+ var openInviteMessage
+ var openInviteCountdownText
+ array openInvitePlayerSlots
+ array openInvitePlaylistSlots
+}
+
+global struct UserInfoPanel
+{
+ var Panel
+ var Name
+ var Kills
+ var Wins
+ var Losses
+ var Deaths
+ var XP
+ var callsignCard
+ array communityLabels
+ array communityNames
+}
+
+struct ChatroomWidget
+{
+ var chatroomPanel
+ var chatroomWidget
+ var chatroomTextChat
+
+ var chatroomBackground
+ var chatroomDivider
+ var chatroomHappyHour
+ var chatroomMode
+
+ UserInfoPanel userInfoPanel
+
+ RemoteMatchInfoPanel remoteMatchInfoPanelWidgets
+
+ var communityChatroomModeButton
+ var chatroomHintText
+ var happyHourTimeLeft
+
+ OpenInviteUI openInviteUI
+}
+
+struct
+{
+ string userInfoPanel_hardware = ""
+ string userInfoPanel_userId = "0"
+ var communityChatroomMode
+ bool currentUserIsStreaming = false
+ array<ChatroomWidget> chatroomUIs
+ bool hasFocus
+} file
+
+bool function IsVoiceChatPushToTalk()
+{
+ if ( GetPartySize() > 1 )
+ return true
+ return DoesCurrentCommunitySupportChat()
+}
+
+void function UICodeCallback_SetChatroomMode( string mode )
+{
+ file.communityChatroomMode = mode
+ UpdateChatroomUI()
+}
+
+void function UpdateChatroomUI()
+{
+ foreach ( chatroomUI in file.chatroomUIs )
+ {
+ if ( file.communityChatroomMode == "chatroom" )
+ {
+ int communityId = GetCurrentCommunityId()
+ CommunitySettings ornull communitySettings = GetCommunitySettings( communityId )
+
+ string communityName
+ if ( communitySettings != null )
+ {
+ expect CommunitySettings( communitySettings )
+ communityName = GetCurrentCommunityName() + " [" + communitySettings.clanTag + "]"
+ }
+ else
+ {
+ communityName = expect string( GetCurrentCommunityName() )
+ }
+
+ if ( IsChatroomMuted() )
+ SetLabelRuiText( chatroomUI.communityChatroomModeButton, Localize( "#COMMUNITY_CHATROOM_MUTED", communityName ) )
+ else
+ SetLabelRuiText( chatroomUI.communityChatroomModeButton, Localize( "#COMMUNITY_CHATROOM", communityName ) )
+ }
+ else if ( file.communityChatroomMode == "party" )
+ {
+ SetLabelRuiText( chatroomUI.communityChatroomModeButton, Localize( "#COMMUNITY_PARTY", GetPartyLeaderName() ) )
+ }
+ else
+ {
+ SetLabelRuiText( chatroomUI.communityChatroomModeButton, Localize( file.communityChatroomMode ) )
+ }
+
+ int meritsLeft = GetHappyHourMeritsLeft()
+ if ( meritsLeft == 0 )
+ {
+ SetLabelRuiText( chatroomUI.happyHourTimeLeft, Localize( "#HAPPYHOUR_NOMERITSLEFT", meritsLeft ) )
+ SetNamedRuiText( chatroomUI.happyHourTimeLeft, "happyHourHintString", "" )
+ //SetNamedRuiText( chatroomUI.happyHourTimeLeft, "happyHourHintString", Localize( "#HAPPYHOUR_HINT_ACTIVE_01" ) )
+ }
+ else if ( meritsLeft >= 1 )
+ {
+ SetLabelRuiText( chatroomUI.happyHourTimeLeft, Localize( GetHappyHourStatus() ) )
+ SetNamedRuiText( chatroomUI.happyHourTimeLeft, "happyHourHintString", Localize( "#HAPPYHOUR_HINT_MERITS", 5 ) )
+ }
+ UICodeCallback_ShowUserInfo( file.userInfoPanel_hardware, file.userInfoPanel_userId )
+ }
+
+ UpdateFooterOptions()
+}
+
+bool function FillInCommunityMembership( UserInfoPanel userInfoPanel, CommunityMembership membershipData, int communityIndex )
+{
+ if ( userInfoPanel.communityNames.len() <= communityIndex )
+ return false;
+
+ string title
+ title = "[" + membershipData.communityClantag + "] " + Localize( membershipData.communityName );
+
+ if ( membershipData.membershipLevel == "owner" )
+ Hud_SetText( userInfoPanel.communityLabels[communityIndex], "#COMMUNITY_MEMBERSHIP_OWNER" )
+ else if ( membershipData.membershipLevel == "admin" )
+ Hud_SetText( userInfoPanel.communityLabels[communityIndex], "#COMMUNITY_MEMBERSHIP_ADMIN" )
+ else if ( membershipData.membershipLevel == "member" )
+ Hud_SetText( userInfoPanel.communityLabels[communityIndex], "#COMMUNITY_MEMBERSHIP_MEMBER" )
+ else
+ Assert( false, "Unknown membership level " + membershipData.membershipLevel + " in FillInCommunityMembership" )
+
+ Hud_SetText( userInfoPanel.communityNames[communityIndex], title )
+ Hud_Show( userInfoPanel.communityLabels[communityIndex] );
+ Hud_Show( userInfoPanel.communityNames[communityIndex] );
+
+ return true
+}
+
+void function FillInUserInfoPanel( UserInfoPanel userInfoPanel, CommunityUserInfo userInfo )
+{
+ file.currentUserIsStreaming = userInfo.isLivestreaming
+
+ Hud_SetText( userInfoPanel.Name, userInfo.name )
+ string killsText = "" + userInfo.kills
+ Hud_SetText( userInfoPanel.Kills, killsText )
+ string winsText = "" + userInfo.wins
+ Hud_SetText( userInfoPanel.Wins, winsText )
+ string lossesText = "" + userInfo.losses
+ Hud_SetText( userInfoPanel.Losses, lossesText )
+ string deathsText = "" + userInfo.deaths
+ Hud_SetText( userInfoPanel.Deaths, deathsText )
+ string xpText = ShortenNumber( userInfo.xp )
+ Hud_EnableKeyBindingIcons( userInfoPanel.XP )
+ Hud_SetText( userInfoPanel.XP, Localize( "#CREDITSIGN_N", xpText ) )
+
+ CallingCard callingCard = CallingCard_GetByIndex( userInfo.callingCardIdx )
+ CallsignIcon callsignIcon = CallsignIcon_GetByIndex( userInfo.callSignIdx )
+
+ var card = userInfoPanel.callsignCard
+ var rui = Hud_GetRui( userInfoPanel.callsignCard )
+ RuiSetImage( rui, "cardImage", callingCard.image )
+ RuiSetImage( rui, "iconImage", callsignIcon.image )
+ RuiSetInt( rui, "layoutType", callingCard.layoutType )
+ RuiSetImage( rui, "cardGenImage", GetGenIcon( userInfo.gen, userInfo.lvl ) )
+ RuiSetString( rui, "playerLevel", PlayerXPDisplayGenAndLevel( userInfo.gen, userInfo.lvl ) )
+ RuiSetString( rui, "playerName", userInfo.name )
+
+ array<CommunityMembership> ownerCommunities
+ array<CommunityMembership> adminCommunities
+ array<CommunityMembership> memberCommunities
+
+ for ( int i = 0; i < userInfo.numCommunities; i++ )
+ {
+ CommunityMembership ornull communityInfo = GetCommunityUserMembershipInfo( userInfo.hardware, userInfo.uid, i )
+ if ( !communityInfo )
+ continue;
+ expect CommunityMembership( communityInfo )
+ string membershipLevel = communityInfo.membershipLevel
+ if ( membershipLevel == "owner" )
+ {
+ ownerCommunities.append( communityInfo )
+ }
+ else if ( membershipLevel == "admin" )
+ {
+ adminCommunities.append( communityInfo )
+ }
+ else if ( membershipLevel == "member" )
+ {
+ memberCommunities.append( communityInfo )
+ }
+ else
+ {
+ printt( "Unknown membershipLevel " + membershipLevel )
+ Assert( false, "Unknown membershipLevel" )
+ }
+ }
+
+ array<CommunityMembership> allCommunities
+ for ( int i = 0; i < ownerCommunities.len(); i++ )
+ allCommunities.append( ownerCommunities[i] )
+ for ( int i = 0; i < adminCommunities.len(); i++ )
+ allCommunities.append( adminCommunities[i] )
+ for ( int i = 0; i < memberCommunities.len(); i++ )
+ allCommunities.append( memberCommunities[i] )
+
+ int currentCommunityIndex = 0
+ for ( ; currentCommunityIndex < allCommunities.len(); currentCommunityIndex++ )
+ {
+ if ( !FillInCommunityMembership( userInfoPanel, allCommunities[currentCommunityIndex], currentCommunityIndex ) )
+ break;
+ }
+
+ for ( ; currentCommunityIndex < userInfoPanel.communityNames.len(); currentCommunityIndex++ )
+ {
+ Hud_Hide( userInfoPanel.communityLabels[currentCommunityIndex] );
+ Hud_Hide( userInfoPanel.communityNames[currentCommunityIndex] );
+ }
+
+ UpdateFooterOptions()
+}
+
+void function GetUserInfoThread( string hardware, string userId )
+{
+ EndSignal( uiGlobal.signalDummy, "StopUserInfoLookups" )
+
+ printt( "getting userinfo for user " + userId )
+
+ CommunityUserInfo fakeSettings
+ fakeSettings.name = Localize( "#COMMUNITY_FETCHING" )
+ foreach ( chatroomUI in file.chatroomUIs )
+ FillInUserInfoPanel( chatroomUI.userInfoPanel, fakeSettings )
+
+ while ( true )
+ {
+ printt( "asking for userinfo for " + hardware + "=" + userId )
+
+ CommunityUserInfo ornull userInfo = GetCommunityUserInfo( hardware, userId )
+ if ( !userInfo )
+ {
+ wait 0.05
+ }
+ else
+ {
+ printt( "Got user info for user " + userId + " on hardware " + hardware )
+ expect CommunityUserInfo( userInfo )
+
+ printt( "User " + userId + " is in " + userInfo.numCommunities + " communities" )
+
+ foreach ( chatroomUI in file.chatroomUIs )
+ FillInUserInfoPanel( chatroomUI.userInfoPanel, userInfo )
+ break
+ }
+ }
+}
+
+
+void function UICodeCallback_ShowUserInfo( string hardware, string userId )
+{
+ Signal( uiGlobal.signalDummy, "StopUserInfoLookups" )
+
+ // printt( "Showing user info for UID " + userId + " on hardware " + hardware )
+
+ file.userInfoPanel_userId = userId
+ file.userInfoPanel_hardware = hardware
+
+ if ( hardware == "" && userId == "0" )
+ {
+ foreach ( chatroomUI in file.chatroomUIs )
+ Hud_Hide( chatroomUI.userInfoPanel.Panel )
+
+ foreach ( chatroomUI in file.chatroomUIs )
+ {
+ // Hud_SetWidth( chatrooUI.chatroomWidget, Hud_GetBaseWidth( chatrooUI.chatroomWidget ) )
+ Hud_SetWidth( chatroomUI.chatroomBackground, Hud_GetBaseWidth( chatroomUI.chatroomBackground ) )
+ Hud_Show( chatroomUI.chatroomDivider )
+ // Hud_SetWidth( chatrooUI.chatroomHeader, Hud_GetBaseWidth( chatrooUI.chatroomHeader ) )
+ // Hud_SetWidth( chatrooUI.chatroomMode, Hud_GetBaseWidth( chatrooUI.chatroomBackground ) )
+ #if CONSOLE_PROG
+ Hud_Show( chatroomUI.chatroomHintText )
+ #else
+ Hud_Show( chatroomUI.chatroomTextChat )
+ #endif
+ }
+ }
+ else
+ {
+ foreach ( chatroomUI in file.chatroomUIs )
+ Hud_Show( chatroomUI.userInfoPanel.Panel )
+
+ foreach ( chatroomUI in file.chatroomUIs )
+ {
+ // Hud_SetWidth( chatroomUI.chatroomWidget, Hud_GetBaseWidth( chatroomUI.chatroomWidget ) - Hud_GetBaseWidth( chatroomUI.userInfoPanel.Panel ) - 24 )
+ Hud_SetWidth( chatroomUI.chatroomBackground, Hud_GetBaseWidth( chatroomUI.chatroomBackground ) - Hud_GetBaseWidth( chatroomUI.userInfoPanel.Panel ) - 12 )
+ Hud_Hide( chatroomUI.chatroomDivider )
+ // Hud_SetWidth( chatroomUI.chatroomHeader, Hud_GetBaseWidth( chatroomUI.chatroomHeader ) - Hud_GetBaseWidth( chatroomUI.userInfoPanel.Panel ) - 12 )
+ // Hud_SetWidth( chatroomUI.chatroomMode, Hud_GetBaseWidth( chatroomUI.chatroomBackground ) - Hud_GetBaseWidth( chatroomUI.userInfoPanel.Panel ) - 24 )
+ #if CONSOLE_PROG
+ Hud_Hide( chatroomUI.chatroomHintText )
+ #else
+ Hud_Hide( chatroomUI.chatroomTextChat )
+ #endif
+ }
+
+ thread GetUserInfoThread( hardware, userId )
+ }
+}
+
+void function FindRemoteMatchInfoWidgetsInPanel( RemoteMatchInfoPanel infostruct, var panel )
+{
+ infostruct.PlaylistName = Hud_GetChild( panel, "PlaylistName" )
+ infostruct.MapName = Hud_GetChild( panel, "MapName" )
+ infostruct.ModeName = Hud_GetChild( panel, "ModeName" )
+ infostruct.TimeLeft = Hud_GetChild( panel, "TimeLeft" )
+ infostruct.ScoreLimit = Hud_GetChild( panel, "ScoreLimit" )
+ infostruct.Team1Score = Hud_GetChild( panel, "Team1Score" )
+ infostruct.Team2Score = Hud_GetChild( panel, "Team2Score" )
+ for ( int i = 1; i <= 2; i++ )
+ {
+ array<RemoteMatchPlayerInfoRow> teamPlayers
+ if ( i == 1 )
+ teamPlayers = infostruct.team1Players
+ else
+ teamPlayers = infostruct.team2Players
+
+ for ( int j = 1; j <= 8; j++ )
+ {
+ RemoteMatchPlayerInfoRow teamPlayer
+ string key = "Team" + i + "Player" + j
+ teamPlayer.playerPanel = Hud_GetChild( panel, key )
+ teamPlayer.name = Hud_GetChild( teamPlayer.playerPanel, "Name" )
+ teamPlayer.score = Hud_GetChild( teamPlayer.playerPanel, "Score" )
+ teamPlayer.kills = Hud_GetChild( teamPlayer.playerPanel, "Kills" )
+ teamPlayer.deaths = Hud_GetChild( teamPlayer.playerPanel, "Deaths" )
+ teamPlayers.append( teamPlayer )
+ }
+ }
+}
+
+int function RemoteMatchInfoPlayerSort( RemoteClientInfoFromMatchInfo a, RemoteClientInfoFromMatchInfo b )
+{
+ return ( b.score - a.score )
+}
+
+void function FillInRemoteMatchInfoPanel( RemoteMatchInfo info, RemoteMatchInfoPanel panel )
+{
+ Hud_Show( panel.panel )
+
+ Hud_SetText( panel.PlaylistName, GetPlaylistDisplayName( info.playlist ) )
+ Hud_SetText( panel.MapName, Localize( "#" + info.map ) )
+
+ string modeName
+
+ if ( IsFDMode( info.gamemode ) )
+ {
+ modeName = "#GAMEMODE_COOP"
+ // HACK because fd has multiple gamemodes in playlists
+ }
+ else
+ {
+ modeName = GAMETYPE_TEXT[ info.gamemode ]
+ }
+
+ if ( IsFullyConnected() )
+ modeName = GetGameModeDisplayName( info.gamemode )
+
+ Hud_SetText( panel.ModeName, modeName )
+ int minsLeft = info.timeLeftSecs / 60
+ int secsLeft = info.timeLeftSecs % 60
+ string timeLeft = "" + minsLeft
+ if ( secsLeft < 10 )
+ timeLeft = timeLeft + ":0" + secsLeft
+ else
+ timeLeft = timeLeft + ":" + secsLeft
+ Hud_SetText( panel.TimeLeft, timeLeft )
+ string scoreLimit = "" + info.maxScore
+ Hud_SetText( panel.ScoreLimit, scoreLimit )
+ string imcScore = "" + info.teamScores[TEAM_IMC]
+ string milScore = "" + info.teamScores[TEAM_MILITIA]
+ Hud_SetText( panel.Team1Score, imcScore )
+ Hud_SetText( panel.Team2Score, milScore )
+
+ int team1PlayerCount = 0
+ int team2PlayerCount = 0
+
+ info.clients.sort( RemoteMatchInfoPlayerSort )
+
+ for ( int i = 0; i < info.clients.len(); i++ )
+ {
+ RemoteMatchPlayerInfoRow teamPlayer
+ if ( info.clients[i].teamNum == TEAM_IMC )
+ {
+ if ( team1PlayerCount >= panel.team1Players.len() )
+ {
+ printt( "too many team players" )
+ continue
+ }
+
+ teamPlayer = panel.team1Players[team1PlayerCount]
+ team1PlayerCount++
+ }
+ else if ( info.clients[i].teamNum == TEAM_MILITIA )
+ {
+ if ( team2PlayerCount >= panel.team2Players.len() )
+ {
+ printt( "too many team players" )
+ continue
+ }
+
+ teamPlayer = panel.team2Players[team2PlayerCount]
+ team2PlayerCount++
+ }
+ else
+ {
+ printt( "Unhandled player team " + info.clients[i].teamNum )
+ continue
+ }
+ string score = "" + info.clients[i].score
+ string kills = "" + info.clients[i].kills
+ string deaths = "" + info.clients[i].deaths
+
+ Hud_Hide( teamPlayer.playerPanel ) // not enough room for these
+ Hud_SetText( teamPlayer.name, info.clients[i].name )
+ Hud_SetText( teamPlayer.score, score )
+ Hud_SetText( teamPlayer.kills, kills )
+ Hud_SetText( teamPlayer.deaths, deaths )
+ }
+ for ( int i = team1PlayerCount; i < panel.team1Players.len(); i++ )
+ Hud_Hide( panel.team1Players[i].playerPanel )
+ for ( int i = team2PlayerCount; i < panel.team2Players.len(); i++ )
+ Hud_Hide( panel.team2Players[i].playerPanel )
+
+}
+
+void function RemoteMatchInfoVisibilityThread()
+{
+ EndSignal( uiGlobal.signalDummy, "StopRemoteMatchInfoThread" )
+ wait 2
+ foreach ( chatroomUI in file.chatroomUIs )
+ Hud_Hide( chatroomUI.remoteMatchInfoPanelWidgets.panel )
+}
+
+void function UICodeCallback_RemoteMatchInfoUpdated()
+{
+ printt( "Remote Match Info Updated!" )
+
+ RemoteMatchInfo info = GetRemoteMatchInfo()
+ foreach ( chatroomUI in file.chatroomUIs )
+ FillInRemoteMatchInfoPanel( info, chatroomUI.remoteMatchInfoPanelWidgets )
+
+ Signal( uiGlobal.signalDummy, "StopRemoteMatchInfoThread" )
+ thread RemoteMatchInfoVisibilityThread()
+}
+
+void function Chatroom_GlobalInit()
+{
+ RegisterSignal( "StopRemoteMatchInfoThread" )
+}
+
+bool function IsSelectedUserStreaming()
+{
+ if ( !ChatroomHasFocus() )
+ return false
+ return file.currentUserIsStreaming
+}
+
+void function InitChatroom( var parentMenu )
+{
+ RegisterSignal( "StopUserInfoLookups" )
+
+ file.communityChatroomMode = "chatroom"
+
+ var menu = Hud_GetChild( parentMenu, "ChatroomPanel" )
+
+ ChatroomWidget chatroomUI
+ file.chatroomUIs.append( chatroomUI )
+
+ chatroomUI.chatroomPanel = menu
+ chatroomUI.chatroomWidget = Hud_GetChild( menu, "ChatRoom" )
+ chatroomUI.chatroomTextChat = Hud_GetChild( menu, "ChatRoomTextChat" )
+ chatroomUI.chatroomBackground = Hud_GetChild( menu, "ChatbarBackground" )
+ chatroomUI.chatroomDivider = Hud_GetChild( menu, "ChatroomHeaderBackground" )
+ chatroomUI.chatroomHappyHour = Hud_GetChild( menu, "HappyHourTimeLeft" )
+ chatroomUI.chatroomMode = Hud_GetChild( menu, "CommunityChatRoomMode" )
+
+ var remoteMatchInfoPanel = Hud_GetChild( parentMenu, "MatchDetails" )
+ chatroomUI.remoteMatchInfoPanelWidgets.panel = remoteMatchInfoPanel
+ FindRemoteMatchInfoWidgetsInPanel( chatroomUI.remoteMatchInfoPanelWidgets, remoteMatchInfoPanel )
+
+ chatroomUI.userInfoPanel.Panel = Hud_GetChild( parentMenu, "UserInfo" )
+ chatroomUI.userInfoPanel.Name = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "Name" )
+ chatroomUI.userInfoPanel.Kills = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "Kills" )
+ chatroomUI.userInfoPanel.Wins = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "Wins" )
+ chatroomUI.userInfoPanel.Losses = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "Losses" )
+ chatroomUI.userInfoPanel.Deaths = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "Deaths" )
+ chatroomUI.userInfoPanel.XP = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "XP" )
+ chatroomUI.userInfoPanel.callsignCard = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "CallsignCard" )
+
+ // chatroomUI.userInfoPanel.ViewUserCardButton = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "ViewUserCard" )
+
+ for ( int i = 0; i < 6; i++ )
+ {
+ if ( !Hud_HasChild( chatroomUI.userInfoPanel.Panel, "Community" + i + "Label" ) )
+ break;
+ var communityLabel = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "Community" + i + "Label" )
+ var communityName = Hud_GetChild( chatroomUI.userInfoPanel.Panel, "Community" + i )
+ Assert( communityName, "found Community" + i + "Label, but no Community" + i + " in userInfo panel" );
+ chatroomUI.userInfoPanel.communityLabels.append( communityLabel )
+ chatroomUI.userInfoPanel.communityNames.append( communityName )
+ }
+
+ chatroomUI.communityChatroomModeButton = Hud_GetChild( menu, "CommunityChatRoomMode" )
+#if CONSOLE_PROG
+ chatroomUI.chatroomHintText = Hud_GetChild( menu, "TextChatHintForConsole" )
+#endif
+ chatroomUI.happyHourTimeLeft = Hud_GetChild( menu, "HappyHourTimeLeft" )
+ // Hud_EnableKeyBindingIcons( chatroomUI.communityChatroomModeButton )
+
+ OpenInviteUI openInviteUI = chatroomUI.openInviteUI
+ openInviteUI.openInvitePanel = Hud_GetChild( parentMenu, "OpenInvitePanel" )
+ openInviteUI.openInviteMessage = Hud_GetChild( openInviteUI.openInvitePanel, "OpenInviteMessage" )
+ openInviteUI.openInviteCountdownText = Hud_GetChild( openInviteUI.openInvitePanel, "OpenInviteCountdownText" )
+
+ var openInviteBackground = Hud_GetChild( openInviteUI.openInvitePanel, "OpenInviteBox" )
+ RuiSetColorAlpha( Hud_GetRui( openInviteBackground ), "backgroundColor", <0, 0, 0>, 0.9 )
+
+ int i = 0;
+ while ( i < 8 )
+ {
+ int count = i + 1
+ var widget = Hud_GetChild( openInviteUI.openInvitePanel, "OpenInvitePlayer" + count )
+ if ( !widget )
+ break
+ openInviteUI.openInvitePlayerSlots.append( widget )
+ i++
+ }
+ for ( int idx = 0; idx < 9; ++idx )
+ {
+ string widgetName = ("OpenInvitePlaylist" + format( "%02d", idx ))
+ var widget = Hud_GetChild( openInviteUI.openInvitePanel, widgetName )
+ openInviteUI.openInvitePlaylistSlots.append( widget )
+ i++
+ }
+
+ openInviteUI.openInviteJoinButton = Hud_GetChild( openInviteUI.openInvitePanel, "JoinOpenInviteButton" )
+ Hud_EnableKeyBindingIcons( openInviteUI.openInviteJoinButton )
+
+ AddEventHandlerToButton( openInviteUI.openInvitePanel, "JoinOpenInviteButton", UIE_CLICK, JoinOpenInvite_OnClick )
+ AddEventHandlerToButton( openInviteUI.openInvitePanel, "OpenInviteCountdownText", UIE_CLICK, JoinOpenInvite_OnClick )
+ AddEventHandlerToButton( openInviteUI.openInvitePanel, "OpenInviteMessageButtonOverlay", UIE_CLICK, JoinOpenInvite_OnClick )
+
+ AddMenuFooterOption( parentMenu, BUTTON_SHOULDER_LEFT, "#LB_MUTEROOM", "#MUTEROOM", MuteRoom, ChatroomIsNotMuted )
+ AddMenuFooterOption( parentMenu, BUTTON_SHOULDER_LEFT, "#LB_UNMUTEROOM", "#UNMUTEROOM", UnmuteRoom, ChatroomIsMuted )
+ AddMenuFooterOption( parentMenu, BUTTON_Y, "#Y_BUTTON_OPENINVITE_DESTROY_FOOTER", "#OPENINVITE_DESTROY", LeaveOpenInviteButton, CanDestroyOpenInvite )
+ AddMenuFooterOption( parentMenu, BUTTON_Y, "#Y_BUTTON_OPENINVITE_JOIN_FOOTER", "#OPENINVITE_JOIN", JoinOpenInvite, CanJoinOpenInvite )
+ AddMenuFooterOption( parentMenu, BUTTON_Y, "#Y_BUTTON_OPENINVITE_LEAVE_FOOTER", "#OPENINVITE_LEAVE", LeaveOpenInviteButton, CanLeaveOpenInvite )
+ AddMenuFooterOption( parentMenu, BUTTON_A, "#BUTTON_VIEW_PLAYER_PROFILE", "#MOUSE1_VIEW_PROFILE", null, IsChatroomViewProfileValid )
+ AddMenuFooterOption( parentMenu, BUTTON_SHOULDER_RIGHT, "#COMMUNITY_RB_CHATROOM_VIEWSTREAM", "#COMMUNITY_CHATROOM_VIEWSTREAM", null, IsSelectedUserStreaming )
+ AddMenuFooterOption( parentMenu, BUTTON_X, "#BUTTON_MUTE", "#MOUSE2_MUTE", null, ChatroomHasFocus )
+
+ UpdateChatroomUI()
+
+ Hud_AddEventHandler( chatroomUI.chatroomWidget, UIE_LOSE_FOCUS, LostFocus )
+ Hud_AddEventHandler( chatroomUI.chatroomWidget, UIE_GET_FOCUS, GotFocus )
+}
+
+void function bsupdate()
+{
+ ShowOpenInvite()
+
+ OpenInvite openInvite
+ openInvite.amILeader = false
+ openInvite.amIInThis = false
+ openInvite.numSlots = 5
+ openInvite.numClaimedSlots = 1
+
+ string inviteString = openInvite.amILeader ? "#OPENINVITE_SENDER_PLAYLIST" : "#OPENINVITE_PLAYLIST"
+
+ float endTime = Time() + 10.0
+ while ( Time() < endTime )
+ {
+ openInvite.timeLeft = endTime - Time()
+
+ int remainingTime = int( ceil( endTime - Time() ) )
+ UpdateOpenInvites( openInvite, inviteString, "scriptacus", "Bounty Hunt", remainingTime )
+
+ if ( remainingTime == 8 )
+ openInvite.numClaimedSlots = 2
+
+ if ( remainingTime == 6 )
+ openInvite.numClaimedSlots = 4
+
+ //if ( remainingTime == 5 )
+ // openInvite.numClaimedSlots = 5
+ //
+ //if ( remainingTime == 4 )
+ // openInvite.numClaimedSlots = 6
+
+ WaitFrame()
+ }
+}
+
+void function UpdateOpenInvites( OpenInvite openInvite, string message, string param1, string ornull param2, int countdown )
+{
+ foreach ( chatroomUI in file.chatroomUIs )
+ {
+ if ( param2 )
+ Hud_SetText( chatroomUI.openInviteUI.openInviteMessage, message, param1, param2 );
+ else
+ Hud_SetText( chatroomUI.openInviteUI.openInviteMessage, message, param1 );
+
+ string countdownText = "" + countdown
+// Hud_SetText( chatroomUI.openInviteUI.openInviteCountdownText, "#OPENINVITE_COUNTDOWN", countdownText )
+ var countdownRui = Hud_GetRui( chatroomUI.openInviteUI.openInviteCountdownText )
+ RuiSetFloat( countdownRui, "timeLeft", openInvite.timeLeft )
+ RuiSetFloat( countdownRui, "maxTime", GetConVarFloat( "openinvite_duration_default" ) )
+
+ bool started = openInvite.timeLeft <= 0 || openInvite.numFreeSlots == 0
+
+ if ( started )
+ {
+ Hud_Hide( chatroomUI.openInviteUI.openInviteJoinButton )
+ }
+ else if ( CanDestroyOpenInvite() )
+ {
+ Hud_Show( chatroomUI.openInviteUI.openInviteJoinButton )
+ if ( IsControllerModeActive() )
+ SetNamedRuiText( chatroomUI.openInviteUI.openInviteJoinButton, "buttonText", "#Y_BUTTON_OPENINVITE_DESTROY" )
+ else
+ SetNamedRuiText( chatroomUI.openInviteUI.openInviteJoinButton, "buttonText", "#OPENINVITE_DESTROY" )
+ }
+ else if ( CanLeaveOpenInvite() )
+ {
+ Hud_Show( chatroomUI.openInviteUI.openInviteJoinButton )
+ if ( IsControllerModeActive() )
+ SetNamedRuiText( chatroomUI.openInviteUI.openInviteJoinButton, "buttonText", "#Y_BUTTON_OPENINVITE_LEAVE" )
+ else
+ SetNamedRuiText( chatroomUI.openInviteUI.openInviteJoinButton, "buttonText", "#OPENINVITE_LEAVE" )
+ }
+ else if ( CanJoinOpenInvite() )
+ {
+ Hud_Show( chatroomUI.openInviteUI.openInviteJoinButton )
+ if ( IsControllerModeActive() )
+ SetNamedRuiText( chatroomUI.openInviteUI.openInviteJoinButton, "buttonText", "#Y_BUTTON_OPENINVITE_JOIN" )
+ else
+ SetNamedRuiText( chatroomUI.openInviteUI.openInviteJoinButton, "buttonText", "#OPENINVITE_JOIN" )
+ }
+
+ string myUID = GetPlayerUID()
+ int i = 0
+ foreach ( player in chatroomUI.openInviteUI.openInvitePlayerSlots )
+ {
+ var rui = Hud_GetRui( player )
+ Hud_Show( player )
+
+ if ( i >= openInvite.numSlots )
+ {
+ //Hud_Hide( player )
+ RuiSetBool( rui, "isEnabled", false )
+ RuiSetBool( rui, "hasPlayer", false )
+ }
+ else
+ {
+ CallsignIcon callsignIcon
+ bool isMe = false
+ // asset playerImage
+
+ if ( i < openInvite.numClaimedSlots )
+ {
+ PartyMember member = openInvite.members[i]
+ isMe = ( member.uid == myUID )
+ if ( isMe )
+ {
+ if ( GetUIPlayer() != null ) // this can happen sometimes after a resolution/windowed mode change
+ callsignIcon = PlayerCallsignIcon_GetActive( GetUIPlayer() )
+ else
+ callsignIcon = CallsignIcon_GetByRef( "gc_icon_happyface" )
+ }
+ else
+ {
+ callsignIcon = CallsignIcon_GetByIndex( member.callsignIdx )
+ }
+ }
+ else
+ {
+ callsignIcon = CallsignIcon_GetByRef( "gc_icon_happyface" )
+ }
+
+ Hud_Show( player )
+ RuiSetBool( rui, "isEnabled", true )
+
+ RuiSetBool( rui, "hasPlayer", i < openInvite.numClaimedSlots )
+ RuiSetBool( rui, "isViewPlayer", isMe )
+ RuiSetImage( rui, "playerImage", callsignIcon.image )
+
+ //if ( i < openInvite.numClaimedSlots )
+ // Hud_SetImage( player, $"ui/menu/main_menu/openinvite_occupiedslot" )
+ //else
+ // Hud_SetImage( player, $"ui/menu/main_menu/openinvite_emptyslot" )
+ }
+ i++
+ }
+
+ array<string> checklistPlaylists = GetChecklistPlaylistsArray()
+
+ if ( Lobby_IsFDMode() )
+ checklistPlaylists = GetFDDifficultyArray()
+
+ array<string> invitePlaylists = split( openInvite.playlistName, "," )
+ bool shouldShowCheckboxPlaylists = true
+ if ( !MixtapeMatchmakingIsEnabled() )
+ shouldShowCheckboxPlaylists = false
+ else if ( invitePlaylists.len() == 0 )
+ shouldShowCheckboxPlaylists = false
+ else if ( (invitePlaylists.len() == 1) && (!checklistPlaylists.contains( invitePlaylists[0] )) )
+ shouldShowCheckboxPlaylists = false
+
+ int playlistSlotCount = chatroomUI.openInviteUI.openInvitePlaylistSlots.len()
+ for( int idx = 0; idx < playlistSlotCount; ++idx )
+ {
+ var slot = chatroomUI.openInviteUI.openInvitePlaylistSlots[idx]
+
+ string thisPlaylistName = idx < checklistPlaylists.len() ? checklistPlaylists[idx] : ""
+ if ( (thisPlaylistName == "") || !shouldShowCheckboxPlaylists )
+ {
+ Hud_Hide( slot )
+ continue
+ }
+
+ Hud_Show( slot )
+ var slotRui = Hud_GetRui( slot )
+ asset playlistThumbnail = GetPlaylistThumbnailImage( thisPlaylistName )
+ RuiSetImage( slotRui, "checkImage", playlistThumbnail )
+
+ bool isChecked = invitePlaylists.contains( thisPlaylistName )
+ RuiSetBool( slotRui, "isChecked", isChecked )
+
+ string abbr = GetPlaylistVarOrUseValue( thisPlaylistName, "abbreviation", "" )
+ RuiSetString( slotRui, "abbreviation", Localize( abbr ) )
+ }
+ }
+}
+
+
+void function HideOpenInvite()
+{
+ foreach ( chatroomUI in file.chatroomUIs )
+ {
+ Hud_Hide( chatroomUI.openInviteUI.openInvitePanel )
+ }
+}
+
+void function ShowOpenInvite()
+{
+ foreach ( chatroomUI in file.chatroomUIs )
+ {
+ Hud_Show( chatroomUI.openInviteUI.openInvitePanel )
+ }
+}
+
+
+void function LostFocus( panel )
+{
+ Signal( uiGlobal.signalDummy, "StopUserInfoLookups" )
+ printt( "Chatroom lost focus" )
+ foreach ( chatroomUI in file.chatroomUIs )
+ Hud_Hide( chatroomUI.userInfoPanel.Panel )
+ file.hasFocus = false
+
+ foreach ( chatroomUI in file.chatroomUIs )
+ {
+ // Hud_SetWidth( chatrooUI.chatroomWidget, Hud_GetBaseWidth( chatrooUI.chatroomWidget ) )
+ Hud_SetWidth( chatroomUI.chatroomBackground, Hud_GetBaseWidth( chatroomUI.chatroomBackground ) )
+ Hud_Show( chatroomUI.chatroomDivider )
+ // Hud_SetWidth( chatrooUI.chatroomMode, Hud_GetBaseWidth( chatrooUI.chatroomBackground ) )
+ Hud_Show( chatroomUI.chatroomTextChat )
+ }
+}
+
+
+void function OnChatroomWidgetGetFocus( var widget )
+{
+}
+
+void function OnChatroomWidgetLoseFocus( var widget )
+{
+}
+
+void function GotFocus( panel )
+{
+ printt( "Chatroom got focus" )
+ file.hasFocus = true
+
+ foreach ( chatroomUI in file.chatroomUIs )
+ {
+ // Hud_SetWidth( chatroomUI.chatroomWidget, Hud_GetBaseWidth( chatroomUI.chatroomWidget ) - Hud_GetBaseWidth( chatroomUI.userInfoPanel.Panel ) - 24 )
+ Hud_SetWidth( chatroomUI.chatroomBackground, Hud_GetBaseWidth( chatroomUI.chatroomBackground ) - Hud_GetBaseWidth( chatroomUI.userInfoPanel.Panel ) - 12 )
+ Hud_Hide( chatroomUI.chatroomDivider )
+ // Hud_SetWidth( chatroomUI.chatroomMode, Hud_GetBaseWidth( chatroomUI.chatroomBackground ) - Hud_GetBaseWidth( chatroomUI.userInfoPanel.Panel ) - 24 )
+ Hud_Hide( chatroomUI.chatroomTextChat )
+ }
+}
+
+bool function IsChatroomViewProfileValid()
+{
+ #if PC_PROG
+ if ( !Origin_IsOverlayAvailable() )
+ return false
+ #endif // PC_PROG
+
+ return ChatroomHasFocus()
+}
+
+bool function ChatroomHasFocus()
+{
+ return file.hasFocus
+}
+
+bool function ChatroomIsMuted()
+{
+ if ( IsControllerModeActive() )
+ return ChatroomHasFocus() && IsChatroomMuted()
+ return IsChatroomMuted()
+}
+
+bool function ChatroomIsNotMuted()
+{
+ if ( IsControllerModeActive() )
+ return ChatroomHasFocus() && !IsChatroomMuted()
+ return !IsChatroomMuted()
+}
+
+void function MuteRoom( var button )
+{
+ printt( "muting the room" )
+ ClientCommand( "muteroom" )
+}
+
+void function UnmuteRoom( var button )
+{
+ printt( "unmuting the room" )
+ ClientCommand( "unmuteroom" )
+}
+
+void function UpdateChatroomThread()
+{
+ EndSignal( uiGlobal.signalDummy, "OnCloseLobbyMenu" )
+ while ( true )
+ {
+ UpdateChatroomUI()
+ wait 30
+ }
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_edit_pilot_loadouts.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_edit_pilot_loadouts.nut
new file mode 100644
index 00000000..89479a76
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_edit_pilot_loadouts.nut
@@ -0,0 +1,170 @@
+untyped
+
+global function InitEditPilotLoadoutsMenu
+
+struct
+{
+ var menu
+ var loadoutPanel
+ var[NUM_PERSISTENT_PILOT_LOADOUTS] loadoutHeaders
+ var[NUM_PERSISTENT_PILOT_LOADOUTS] activateButtons
+ bool enteringEdit = false
+ var unlockReq
+} file
+
+void function InitEditPilotLoadoutsMenu()
+{
+ file.menu = GetMenu( "EditPilotLoadoutsMenu" )
+ var menu = file.menu
+
+ AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnPilotLoadoutsMenu_Open )
+ AddMenuEventHandler( menu, eUIEvent.MENU_CLOSE, OnPilotLoadoutsMenu_Close )
+ AddMenuEventHandler( menu, eUIEvent.MENU_INPUT_MODE_CHANGED, OnPilotLoadoutsMenu_InputModeChanged )
+
+ for ( int i = 0; i < NUM_PERSISTENT_PILOT_LOADOUTS; i++ )
+ {
+ var activateButton = Hud_GetChild( menu, "Button" + i )
+ activateButton.s.rowIndex <- i
+ Hud_SetVisible( activateButton, true )
+ Hud_AddEventHandler( activateButton, UIE_CLICK, OnLoadoutButton_Activate )
+ Hud_AddEventHandler( activateButton, UIE_GET_FOCUS, OnLoadoutButton_Focused )
+ Hud_AddEventHandler( activateButton, UIE_LOSE_FOCUS, OnLoadoutButton_LostFocus )
+ file.activateButtons[i] = activateButton
+ }
+
+ Hud_SetFocused( file.activateButtons[0] )
+
+ file.loadoutPanel = Hud_GetChild( menu, "PilotLoadoutDisplay" )
+ file.unlockReq = Hud_GetChild( menu, "UnlockReq" )
+
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" )
+ AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
+}
+
+void function OnPilotLoadoutsMenu_Open()
+{
+ entity player = GetUIPlayer()
+ if ( player == null )
+ return
+
+ RunMenuClientFunction( "ClearEditingPilotLoadoutIndex" )
+
+ int loadoutIndex = uiGlobal.pilotSpawnLoadoutIndex
+ UpdatePilotLoadoutButtons( loadoutIndex, file.activateButtons )
+ UpdatePilotLoadoutPanel( file.loadoutPanel, GetCachedPilotLoadout( loadoutIndex ) )
+ UI_SetPresentationType( ePresentationType.PILOT )
+
+ RefreshCreditsAvailable()
+}
+
+void function OnPilotLoadoutsMenu_Close()
+{
+ entity player = GetUIPlayer()
+ if ( player == null )
+ return
+
+ foreach ( i, button in file.activateButtons )
+ {
+ string pilotLoadoutRef = "pilot_loadout_" + ( i + 1 )
+ if ( !IsItemNew( player, pilotLoadoutRef ) )
+ continue
+
+ ClearNewStatus( button, pilotLoadoutRef )
+ }
+}
+
+void function OnPilotLoadoutsMenu_InputModeChanged()
+{
+ UpdatePilotLoadoutPanelBinds( file.loadoutPanel )
+}
+
+void function OnLoadoutButton_Focused( var button )
+{
+ int index = expect int( button.s.rowIndex )
+
+ // update the editingLoadoutIndex on focus so that it always matches
+ // with the pilot loadout panel
+ uiGlobal.editingLoadoutIndex = index
+ uiGlobal.editingLoadoutType = "pilot"
+
+ UpdatePilotLoadout( index )
+
+ string pilotLoadoutRef = "pilot_loadout_" + ( index + 1 )
+ string unlockReq = GetItemUnlockReqText( pilotLoadoutRef )
+ RHud_SetText( file.unlockReq, unlockReq )
+}
+
+void function UpdatePilotLoadout( int loadoutIndex )
+{
+ PilotLoadoutDef loadout = GetCachedPilotLoadout( loadoutIndex )
+
+ UpdatePilotLoadoutPanel( file.loadoutPanel, loadout )
+ RunMenuClientFunction( "UpdatePilotModel", loadoutIndex )
+}
+
+void function OnLoadoutButton_Activate( var button )
+{
+ if ( !IsFullyConnected() )
+ return
+
+ if ( Hud_IsLocked( button ) )
+ {
+ int index = expect int ( button.s.rowIndex )
+ string pilotLoadoutRef = "pilot_loadout_" + ( index + 1 )
+
+ array<var> buttons
+ foreach ( button in file.activateButtons )
+ {
+ buttons.append( button )
+ }
+
+ OpenBuyItemDialog( buttons, button, GetItemName( pilotLoadoutRef ), pilotLoadoutRef )
+ return
+ }
+
+ int loadoutIndex = expect int ( button.s.rowIndex )
+ SetEditLoadout( "pilot", loadoutIndex )
+
+ if ( EDIT_LOADOUT_SELECTS )
+ {
+ bool indexChanged = loadoutIndex != uiGlobal.pilotSpawnLoadoutIndex
+
+ if ( indexChanged )
+ {
+ EmitUISound( "Menu_LoadOut_Pilot_Select" )
+
+ if ( !IsLobby() )
+ uiGlobal.updatePilotSpawnLoadout = true
+ }
+
+ uiGlobal.pilotSpawnLoadoutIndex = loadoutIndex
+ ClientCommand( "RequestPilotLoadout " + loadoutIndex )
+ }
+
+ if ( PRE_RELEASE_DEMO && loadoutIndex < 3 )
+ {
+ UpdatePilotLoadoutButtons( loadoutIndex, file.activateButtons )
+ return
+ }
+
+ RunMenuClientFunction( "SetEditingPilotLoadoutIndex", loadoutIndex )
+ AdvanceMenu( GetMenu( "EditPilotLoadoutMenu" ) )
+}
+
+void function OnLoadoutButton_LostFocus( var button )
+{
+ entity player = GetUIPlayer()
+ if ( !IsValid( player ) )
+ return
+
+ int loadoutIndex = expect int ( button.s.rowIndex )
+ string pilotLoadoutRef = "pilot_loadout_" + ( loadoutIndex + 1 )
+ ClearNewStatus( button, pilotLoadoutRef )
+
+ if ( IsItemLocked( player, pilotLoadoutRef ) )
+ return
+
+ PilotLoadoutDef loadout = GetCachedPilotLoadout( loadoutIndex )
+ if ( (RefHasAnyNewSubitem( player, loadout.primary ) || RefHasAnyNewSubitem( player, loadout.secondary ) || RefHasAnyNewSubitem( player, loadout.weapon3 )) )
+ Hud_SetNew( button, true )
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ingame.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ingame.nut
new file mode 100644
index 00000000..35c9e9ba
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ingame.nut
@@ -0,0 +1,702 @@
+//global function InitLobbyStartMenu
+global function InitInGameMPMenu
+global function InitInGameSPMenu
+global function ServerCallback_UI_ObjectiveUpdated
+global function ServerCallback_UI_UpdateMissionLog
+global function SP_ResetObjectiveStringIndex
+global function SCB_SetDoubleXPStatus
+
+global function SCB_SetCompleteMeritState
+global function SCB_SetEvacMeritState
+global function SCB_SetMeritCount
+global function SCB_SetScoreMeritState
+global function SCB_SetWinMeritState
+global function SCB_SetWeaponMeritCount
+global function SCB_SetTitanMeritCount
+
+const DATA_TABLE = $"datatable/sp_difficulty.rpak"
+
+struct
+{
+ var menuMP
+ var menuSP
+ var BtnTrackedChallengeBackground
+ var BtnTrackedChallengeTitle
+ array trackedChallengeButtons
+ var BtnLastCheckpoint
+ int objectiveStringIndex
+ bool SP_displayObjectiveOnClose
+ var settingsHeader
+ var faqButton
+ int titanHeaderIndex
+ var titanHeader
+ var titanSelectButton
+ var titanEditButton
+
+ ComboStruct &comboStruct
+
+ array<var> loadoutButtons
+ array<var> loadoutHeaders
+} file
+
+void function InitInGameMPMenu()
+{
+ var menu = GetMenu( "InGameMPMenu" )
+ file.menuMP = menu
+
+ SP_ResetObjectiveStringIndex()
+ file.SP_displayObjectiveOnClose = true
+
+ AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnInGameMPMenu_Open )
+ AddMenuEventHandler( menu, eUIEvent.MENU_CLOSE, OnInGameMPMenu_Close )
+
+ AddUICallback_OnLevelInit( OnInGameLevelInit )
+
+ ComboStruct comboStruct = ComboButtons_Create( menu )
+
+ int headerIndex = 0
+ int buttonIndex = 0
+ var pilotHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_PILOT" )
+ file.loadoutHeaders.append( pilotHeader )
+ var pilotSelectButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#SELECT" )
+ Hud_AddEventHandler( pilotSelectButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "PilotLoadoutsMenu" ) ) )
+ file.loadoutButtons.append( pilotSelectButton )
+ var pilotEditButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#EDIT" )
+ Hud_AddEventHandler( pilotEditButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "EditPilotLoadoutsMenu" ) ) )
+ file.loadoutButtons.append( pilotEditButton )
+
+ headerIndex++
+ buttonIndex = 0
+ var titanHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_TITAN" )
+ file.titanHeader = titanHeader
+ file.titanHeaderIndex = headerIndex
+ file.loadoutHeaders.append( titanHeader )
+ var titanSelectButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#SELECT" )
+ file.titanSelectButton = titanSelectButton
+ file.loadoutButtons.append( titanSelectButton )
+ Hud_AddEventHandler( titanSelectButton, UIE_CLICK, TitanSelectButtonHandler )
+ var titanEditButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#EDIT" )
+ Hud_AddEventHandler( titanEditButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "EditTitanLoadoutsMenu" ) ) )
+ file.titanEditButton = titanEditButton
+ file.loadoutButtons.append( titanEditButton )
+
+ headerIndex++
+ buttonIndex = 0
+ var gameHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_GAME" )
+ var leaveButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#LEAVE_MATCH" )
+ Hud_AddEventHandler( leaveButton, UIE_CLICK, OnLeaveButton_Activate )
+ #if DEV
+ var devButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "Dev" )
+ Hud_AddEventHandler( devButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "DevMenu" ) ) )
+ #endif
+
+ headerIndex++
+ buttonIndex = 0
+ var dummyHeader = AddComboButtonHeader( comboStruct, headerIndex, "" )
+ var dummyButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "" )
+ Hud_SetVisible( dummyHeader, false )
+ Hud_SetVisible( dummyButton, false )
+
+ headerIndex++
+ buttonIndex = 0
+ file.settingsHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_SETTINGS" )
+ var controlsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#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
+
+ // MOD SETTINGS
+ var modSettingsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "Mod Settings" )
+ Hud_AddEventHandler( modSettingsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ModSettings" ) ) )
+
+ // Nobody reads the FAQ so we replace it with ModSettings because of the limited combobutton space available
+ //file.faqButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#KNB_MENU_HEADER" )
+ //Hud_AddEventHandler( file.faqButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "KnowledgeBaseMenu" ) ) )
+
+ //var dataCenterButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#DATA_CENTER" )
+ //Hud_AddEventHandler( dataCenterButton, UIE_CLICK, OpenDataCenterDialog )
+
+ ComboButtons_Finalize( comboStruct )
+
+ file.comboStruct = comboStruct
+
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" )
+ AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_CLOSE", "#CLOSE" )
+}
+
+void function OnInGameMPMenu_Open()
+{
+ Lobby_SetFDMode( GetCurrentPlaylistVarInt( "ingame_menu_fd_mode", 0 ) == 1 )
+ UI_SetPresentationType( ePresentationType.DEFAULT )
+
+ bool faqIsNew = !GetConVarBool( "menu_faq_viewed" ) || HaveNewPatchNotes() || HaveNewCommunityNotes()
+ RuiSetBool( Hud_GetRui( file.settingsHeader ), "isNew", faqIsNew )
+ //ComboButton_SetNew( file.faqButton, faqIsNew )
+
+ UpdateLoadoutButtons()
+ RefreshCreditsAvailable()
+ thread UpdateCachedNewItems()
+}
+
+void function OnInGameMPMenu_Close()
+{
+ UI_SetPresentationType( ePresentationType.INACTIVE )
+
+ if ( IsConnected() && !IsLobby() && IsLevelMultiplayer( GetActiveLevel() ) )
+ {
+ //printt( "OnInGameMPMenu_Close() uiGlobal.updatePilotSpawnLoadout is:", uiGlobal.updatePilotSpawnLoadout )
+ //printt( "OnInGameMPMenu_Close() uiGlobal.updateTitanSpawnLoadout is:", uiGlobal.updateTitanSpawnLoadout )
+
+ string updatePilotSpawnLoadout = uiGlobal.updatePilotSpawnLoadout ? "1" : "0"
+ string updateTitanSpawnLoadout = uiGlobal.updateTitanSpawnLoadout ? "1" : "0"
+
+ ClientCommand( "InGameMPMenuClosed " + updatePilotSpawnLoadout + " " + updateTitanSpawnLoadout )
+
+ uiGlobal.updatePilotSpawnLoadout = false
+ uiGlobal.updateTitanSpawnLoadout = false
+
+ RunClientScript( "RefreshIntroLoadoutDisplay", GetLocalClientPlayer(), uiGlobal.pilotSpawnLoadoutIndex, uiGlobal.titanSpawnLoadoutIndex )
+ }
+}
+
+void function UpdateLoadoutButtons()
+{
+ bool loadoutSelectionEnabled = (GetCurrentPlaylistVarInt( "loadout_selection_enabled", 1 ) == 1)
+
+ SetTitanSelectButtonVisibleState( true )
+
+ foreach ( button in file.loadoutButtons )
+ {
+ Hud_SetEnabled( button, loadoutSelectionEnabled )
+ }
+
+ foreach ( header in file.loadoutHeaders )
+ {
+ if ( loadoutSelectionEnabled )
+ Hud_Show( header )
+ else
+ Hud_Hide( header )
+ }
+
+ entity player = GetUIPlayer()
+
+ if ( GetAvailableTitanRefs( player ).len() > 1 )
+ {
+ SetComboButtonHeaderTitle( file.menuMP, file.titanHeaderIndex, "#MENU_HEADER_TITAN" )
+ ComboButton_SetText( file.titanSelectButton, "#SELECT" )
+ Hud_Show( file.titanEditButton )
+ }
+ else if ( GetAvailableTitanRefs( player ).len() == 1 )
+ {
+ TitanLoadoutDef loadout = GetCachedTitanLoadout( uiGlobal.titanSpawnLoadoutIndex )
+
+ SetComboButtonHeaderTitle( file.menuMP, file.titanHeaderIndex, GetTitanLoadoutName( loadout ) )
+ ComboButton_SetText( file.titanSelectButton, "#EDIT" )
+
+ Hud_Hide( file.titanEditButton )
+ ComboButtons_ResetColumnFocus( file.comboStruct )
+ }
+ else
+ {
+ SetTitanSelectButtonVisibleState( true )
+ }
+}
+
+//////////
+
+//////////
+
+void function InitInGameSPMenu()
+{
+ var menu = GetMenu( "InGameSPMenu" )
+ file.menuSP = menu
+
+ AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnOpenInGameSPMenu )
+ AddMenuEventHandler( menu, eUIEvent.MENU_CLOSE, OnCloseInGameSPMenu )
+
+ ComboStruct comboStruct = ComboButtons_Create( menu )
+
+ int headerIndex = 0
+ int buttonIndex = 0
+
+ // MISSION Menu
+ var missionHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_MISSION" )
+ var resumeButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#RESUME_GAME_SHORT" )
+ Hud_AddEventHandler( resumeButton, UIE_CLICK, OnResumeGame_Activate )
+ var lastCheckpointButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#LAST_CHECKPOINT" )
+ Hud_AddEventHandler( lastCheckpointButton, UIE_CLICK, OnReloadCheckpoint_Activate )
+ file.BtnLastCheckpoint = lastCheckpointButton
+ var restartButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#RESTART_LEVEL_SHORT" )
+ Hud_AddEventHandler( restartButton, UIE_CLICK, OnRestartLevel_Activate )
+
+ // GAME Menu
+ // headerIndex++
+ // buttonIndex = 0
+ // var gameHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_GAME" )
+ // var difficultyButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#CHANGE_DIFFICULTY" )
+ // Hud_AddEventHandler( difficultyButton, UIE_CLICK, OnChangeDifficulty_Activate )
+ // var leaveButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#QUIT" )
+ // Hud_AddEventHandler( leaveButton, UIE_CLICK, OnLeaveButton_Activate )
+
+ // SETTINGS Menu
+ headerIndex++
+ buttonIndex = 0
+ var settingsHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_SETTINGS" )
+ var controlsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#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 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
+
+ // MOD SETTINGS
+ var modSettingsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "Mod Settings" )
+ Hud_AddEventHandler( modSettingsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ModSettings" ) ) )
+
+ array<var> orderedButtons
+
+ var changeDifficultyBtn = Hud_GetChild( menu, "BtnChangeDifficulty" )
+
+ AddButtonEventHandler( changeDifficultyBtn, UIE_CLICK, OnChangeDifficulty_Activate )
+ Hud_Show( changeDifficultyBtn )
+ orderedButtons.append( changeDifficultyBtn )
+
+ var quitBtn = Hud_GetChild( menu, "BtnQuit" )
+ SetButtonRuiText( quitBtn, "#QUIT" )
+ AddButtonEventHandler( quitBtn, UIE_CLICK, OnLeaveButton_Activate )
+ Hud_Show( quitBtn )
+ orderedButtons.append( quitBtn )
+
+ // DEV button
+ var devButton = Hud_GetChild( menu, "BtnDev" )
+ #if DEV
+ SetButtonRuiText( devButton, "--- Dev" )
+ AddButtonEventHandler( devButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "DevMenu" ) ) )
+ Hud_Show( devButton )
+ orderedButtons.append( devButton )
+ comboStruct.navUpButton = devButton
+ #else
+ Hud_Hide( devButton )
+ comboStruct.navUpButton = quitBtn
+ #endif // DEV
+
+ SetNavUpDown( orderedButtons )
+ comboStruct.navDownButton = changeDifficultyBtn
+
+ ComboButtons_Finalize( comboStruct )
+
+ AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" )
+ AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_CLOSE", "#CLOSE" )
+}
+
+
+void function OnOpenInGameSPMenu()
+{
+ var collectiblesFoundDesc = Hud_GetChild( file.menuSP, "CollectiblesFoundDesc" )
+ var missionLogDesc = Hud_GetChild( file.menuSP, "MissionLogDesc" )
+ var changeDifficultyBtn = Hud_GetChild( file.menuSP, "BtnChangeDifficulty" )
+
+ Hud_SetEnabled( file.BtnLastCheckpoint, HasValidSaveGame() )
+
+ int currentDifficulty = GetConVarInt( "sp_difficulty" )
+ string newDifficultyString
+
+ switch ( currentDifficulty )
+ {
+ case 0:
+ newDifficultyString = "#CHANGE_DIFFICULTY_EASY"
+ break
+
+ case 1:
+ newDifficultyString = "#CHANGE_DIFFICULTY_REGULAR"
+ break
+
+ case 2:
+ newDifficultyString = "#CHANGE_DIFFICULTY_HARD"
+ break
+
+ case 3:
+ newDifficultyString = "#CHANGE_DIFFICULTY_MASTER"
+ break
+
+ default:
+ Assert( 0, "Unknown difficulty " + currentDifficulty )
+ break
+ }
+ SetButtonRuiText( changeDifficultyBtn, newDifficultyString )
+
+ string activeLevelName = GetActiveLevel()
+ if ( activeLevelName != "" )
+ {
+ var dataTable = GetDataTable( $"datatable/sp_levels_data.rpak" )
+
+ // Make sure this level actually has data to display.
+ bool levelHasData = false
+ int numRows = GetDatatableRowCount( dataTable )
+ for ( int i = 0; i < numRows; i++ )
+ {
+ string levelName = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "level" ) )
+ if ( activeLevelName == levelName )
+ {
+ levelHasData = true
+ break
+ }
+ }
+
+ if ( levelHasData )
+ {
+ // Mission Log
+ int row = GetDataTableRowMatchingStringValue( dataTable, GetDataTableColumnByName( dataTable, "level" ), activeLevelName )
+ string missionLog = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "missionLog" ) )
+
+ if ( uiGlobal.sp_showAlternateMissionLog )
+ {
+ string alternateMissionLog = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "alternateMissionLog" ) )
+ missionLog = alternateMissionLog
+ }
+
+ Hud_SetText( missionLogDesc, missionLog )
+
+ // Collectibles
+ int foundLions = GetCollectiblesFoundForLevel( activeLevelName )
+ int maxLions = GetMaxLionsInLevel( activeLevelName )
+ Hud_SetText( collectiblesFoundDesc, "#MENU_SP_COLLECTIBLE_DESC", foundLions, maxLions )
+ }
+ else
+ {
+ Hud_SetText( missionLogDesc, "#MENU_SP_OBJECTIVES_NO_ENTRY" )
+ Hud_SetText( collectiblesFoundDesc, "#MENU_SP_COLLECTIBLE_DESC", 0, 0 )
+ }
+
+ // Make sure trial mode doesn't reveal any spoilers!
+ if ( Script_IsRunningTrialVersion() )
+ Hud_SetText( missionLogDesc, "#MENU_SP_OBJECTIVES_NO_ENTRY" )
+ }
+
+ SPMenu_UpdateReloadCheckpointButton()
+}
+
+
+void function OnCloseInGameSPMenu()
+{
+ if ( file.SP_displayObjectiveOnClose )
+ ClientCommand( "ShowObjective closedSPMenu" )
+}
+
+void function SPMenu_UpdateReloadCheckpointButton()
+{
+ if ( level.ui.playerRunningGauntlet )
+ ComboButton_SetText( file.BtnLastCheckpoint, "#GAUNTLET_RESTART" )
+ else
+ ComboButton_SetText( file.BtnLastCheckpoint, "#LAST_CHECKPOINT" )
+}
+
+void function MobilityDifficultyButton_Activate( var button )
+{
+ OpenMobilityDifficultyMenu()
+}
+
+void function OnLeaveButton_Activate( var button )
+{
+ file.SP_displayObjectiveOnClose = false
+ LeaveDialog()
+}
+
+void function OnRestartLevel_Activate( var button )
+{
+ ShowAreYouSureDialog( "#MENU_RESTART_MISSION_CONFIRM", RestartMission, "#WARNING_LOSE_PROGRESS" )
+}
+
+void function OnChangeDifficulty_Activate( var button )
+{
+ SPDifficultyButton_Click( button )
+}
+
+void function OnResumeGame_Activate( var button )
+{
+ CloseActiveMenu()
+}
+
+void function OnReloadCheckpoint_Activate( var button )
+{
+ if ( level.ui.playerRunningGauntlet )
+ {
+ CloseActiveMenu()
+ ClientCommand( "Gauntlet_PlayerRestartedFromMenu" )
+ }
+ else
+ {
+ ShowAreYouSureDialog( "#MENU_RESTART_CHECKPOINT_CONFIRM", ReloadLastCheckpoint, "#EMPTY_STRING" )
+ }
+}
+
+void function ShowAreYouSureDialog( string header, void functionref() func, string details )
+{
+ DialogData dialogData
+ dialogData.header = header
+ dialogData.message = details
+
+ AddDialogButton( dialogData, "#NO" )
+ AddDialogButton( dialogData, "#YES", func )
+
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+ AddDialogFooter( dialogData, "#B_BUTTON_BACK" )
+
+ OpenDialog( dialogData )
+}
+
+void function RestartMission()
+{
+ file.SP_displayObjectiveOnClose = false
+ ClientCommand( "RestartMission" )
+}
+
+void function ReloadLastCheckpoint()
+{
+ file.SP_displayObjectiveOnClose = false
+
+ printt( "SAVEGAME: Trying to load saveName" )
+ if ( HasValidSaveGame() )
+ {
+ printt( "SAVEGAME: Trying to load checkpoint from menu_ingame" )
+ SaveGame_LoadWithStartPointFallback()
+ return
+ }
+
+ ClientCommand( "RestartFromLevelTransition" )
+}
+
+void function SP_ResetObjectiveStringIndex()
+{
+ file.objectiveStringIndex = -1
+}
+
+void function ServerCallback_UI_ObjectiveUpdated( int stringIndex )
+{
+ file.objectiveStringIndex = stringIndex
+}
+
+void function ServerCallback_UI_UpdateMissionLog( bool showAltLog )
+{
+ uiGlobal.sp_showAlternateMissionLog = showAltLog
+}
+
+void function SPDifficultyButton_Click( var button )
+{
+ DialogData dialogData
+ dialogData.header = "#SP_DIFFICULTY_MISSION_SELECT_TITLE"
+
+ int currentDifficulty = GetConVarInt( "sp_difficulty" )
+ dialogData.coloredButton[ currentDifficulty ] <- true
+
+ if ( currentDifficulty == DIFFICULTY_EASY )
+ AddDialogButton( dialogData, "#SP_DIFFICULTY_EASY_TITLE", SPPickEasy, "#SP_DIFFICULTY_EASY_DESCRIPTION", true )
+ else
+ AddDialogButton( dialogData, "#SP_DIFFICULTY_EASY_TITLE", SPPickEasy, "#SP_DIFFICULTY_EASY_DESCRIPTION", false )
+
+
+ if ( currentDifficulty == DIFFICULTY_NORMAL )
+ AddDialogButton( dialogData, "#SP_DIFFICULTY_NORMAL_TITLE", SPPickNormal, "#SP_DIFFICULTY_NORMAL_DESCRIPTION", true )
+ else
+ AddDialogButton( dialogData, "#SP_DIFFICULTY_NORMAL_TITLE", SPPickNormal, "#SP_DIFFICULTY_NORMAL_DESCRIPTION", false )
+
+
+ if ( currentDifficulty == DIFFICULTY_HARD )
+ AddDialogButton( dialogData, "#SP_DIFFICULTY_HARD_TITLE", SPPickHard, "#SP_DIFFICULTY_HARD_DESCRIPTION", true )
+ else
+ AddDialogButton( dialogData, "#SP_DIFFICULTY_HARD_TITLE", SPPickHard, "#SP_DIFFICULTY_HARD_DESCRIPTION", false )
+
+
+ if ( currentDifficulty == DIFFICULTY_MASTER )
+ AddDialogButton( dialogData, "#SP_DIFFICULTY_MASTER_TITLE", SPPickMaster, "#SP_DIFFICULTY_MASTER_DESCRIPTION", true )
+ else
+ AddDialogButton( dialogData, "#SP_DIFFICULTY_MASTER_TITLE", SPPickMaster, "#SP_DIFFICULTY_MASTER_DESCRIPTION", false )
+
+
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+ AddDialogFooter( dialogData, "#B_BUTTON_BACK" )
+ AddDialogPCBackButton( dialogData )
+
+ OpenDialog( dialogData )
+}
+
+void function SPPickEasy()
+{
+ RequestSPDifficultyChange( DIFFICULTY_EASY )
+ CloseAllMenus()
+}
+
+void function SPPickNormal()
+{
+ RequestSPDifficultyChange( DIFFICULTY_NORMAL )
+ CloseAllMenus()
+}
+
+void function SPPickHard()
+{
+ RequestSPDifficultyChange( DIFFICULTY_HARD )
+ CloseAllMenus()
+}
+
+void function SPPickMaster()
+{
+ RequestSPDifficultyChange( DIFFICULTY_MASTER )
+ CloseAllMenus()
+}
+
+void function RequestSPDifficultyChange( int selectedDifficulty )
+{
+ var dataTable = GetDataTable( DATA_TABLE )
+ int difficulty = GetDataTableInt( dataTable, selectedDifficulty, GetDataTableColumnByName( dataTable, "index" ) )
+
+ ClientCommand( "ClientCommand_RequestSPDifficultyChange " + difficulty )
+}
+
+void function SCB_SetDoubleXPStatus( int status )
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ RuiSetInt( Hud_GetRui( doubleXPWidget ), "doubleXPStatus", status )
+
+ // update this menu too
+ TTSUpdateDoubleXPStatus( status )
+}
+
+void function OnInGameLevelInit()
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ var rui = Hud_GetRui( doubleXPWidget )
+ RuiSetInt( rui, "doubleXPStatus", 0 )
+ RuiSetBool( rui, "isVisible", false )
+
+ string gameModeScoreHint = expect string( GetCurrentPlaylistVar( "gamemode_score_hint" ) )
+ if ( gameModeScoreHint != "" )
+ {
+ RuiSetString( rui, "scoreMeritText", Localize( gameModeScoreHint ) )
+ RuiSetInt( rui, "matchScoreMerit", MERIT_STATE_AVAILABLE )
+ }
+ else
+ {
+ RuiSetString( rui, "scoreMeritText", "" )
+ RuiSetInt( rui, "matchScoreMerit", MERIT_STATE_HIDDEN )
+ }
+
+ Hud_SetVisible( doubleXPWidget, !IsPrivateMatch() )
+}
+/*
+int matchScoreMerit = MERIT_STATE_AVAILABLE
+int matchCompleteMerit = MERIT_STATE_AVAILABLE
+int matchWinMerit = MERIT_STATE_AVAILABLE
+int matchEvacMerit = MERIT_STATE_HIDDEN
+int happyHourMerits = MERIT_STATE_HIDDEN
+
+int meritCount = 0
+*/
+
+void function SCB_SetScoreMeritState( int meritState )
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ var rui = Hud_GetRui( doubleXPWidget )
+
+ RuiSetInt( rui, "matchScoreMerit", meritState )
+}
+
+void function SCB_SetCompleteMeritState( int meritState )
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ var rui = Hud_GetRui( doubleXPWidget )
+
+ RuiSetInt( rui, "matchCompleteMerit", meritState )
+}
+
+void function SCB_SetWinMeritState( int meritState )
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ var rui = Hud_GetRui( doubleXPWidget )
+
+ RuiSetInt( rui, "matchWinMerit", meritState )
+}
+
+void function SCB_SetEvacMeritState( int meritState )
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ var rui = Hud_GetRui( doubleXPWidget )
+
+ RuiSetInt( rui, "matchEvacMerit", meritState )
+}
+
+void function SCB_SetMeritCount( int meritCount )
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ var rui = Hud_GetRui( doubleXPWidget )
+
+ RuiSetInt( rui, "meritCount", meritCount )
+}
+
+void function SCB_SetWeaponMeritCount( int meritCount )
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ var rui = Hud_GetRui( doubleXPWidget )
+
+ RuiSetInt( rui, "weaponMeritCount", meritCount )
+}
+
+void function SCB_SetTitanMeritCount( int meritCount )
+{
+ var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" )
+ var rui = Hud_GetRui( doubleXPWidget )
+
+ RuiSetInt( rui, "titanMeritCount", meritCount )
+}
+
+void function TitanSelectButtonHandler( var button )
+{
+ if ( !IsFullyConnected() )
+ return
+
+ entity player = GetUIPlayer()
+ if ( GetAvailableTitanRefs( player ).len() > 1 )
+ {
+ AdvanceMenu( GetMenu( "TitanLoadoutsMenu" ) )
+ }
+ else if ( GetAvailableTitanRefs( player ).len() == 1 )
+ {
+ uiGlobal.updateTitanSpawnLoadout = false
+ SetEditLoadout( "titan", uiGlobal.titanSpawnLoadoutIndex )
+
+ RunMenuClientFunction( "SetEditingTitanLoadoutIndex", uiGlobal.titanSpawnLoadoutIndex )
+ AdvanceMenu( GetMenu( "EditTitanLoadoutMenu" ) )
+ }
+ else
+ {
+ // HIDE
+ }
+}
+
+void function SetTitanSelectButtonVisibleState( bool state )
+{
+ if ( state )
+ {
+ Hud_Show( file.titanHeader )
+ Hud_Show( file.titanEditButton )
+ Hud_Show( file.titanSelectButton )
+ }
+ else
+ {
+ ComboButtons_ResetColumnFocus( file.comboStruct )
+ Hud_Hide( file.titanHeader )
+ Hud_Hide( file.titanEditButton )
+ Hud_Hide( file.titanSelectButton )
+ }
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut
index 938e0d3f..23dae99d 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut
@@ -178,6 +178,8 @@ void function InitLobbyMenu()
AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
AddMenuFooterOption( menu, BUTTON_BACK, "#BACK_BUTTON_POSTGAME_REPORT", "#POSTGAME_REPORT", OpenPostGameMenu, IsPostGameMenuValid )
AddMenuFooterOption( menu, BUTTON_TRIGGER_RIGHT, "#R_TRIGGER_CHAT", "", null, IsVoiceChatPushToTalk )
+ // Client side progression toggle
+ AddMenuFooterOption( menu, BUTTON_Y, "#Y_BUTTON_TOGGLE_PROGRESSION", "#TOGGLE_PROGRESSION", ShowToggleProgressionDialog )
InitChatroom( menu )
@@ -226,6 +228,57 @@ void function InitLobbyMenu()
RegisterSignal( "LeaveParty" )
}
+void function ShowToggleProgressionDialog( var button )
+{
+ bool enabled = Progression_GetPreference()
+
+ DialogData dialogData
+ dialogData.menu = GetMenu( "AnnouncementDialog" )
+ dialogData.header = enabled ? "#PROGRESSION_TOGGLE_ENABLED_HEADER" : "#PROGRESSION_TOGGLE_DISABLED_HEADER"
+ dialogData.message = enabled ? "#PROGRESSION_TOGGLE_ENABLED_BODY" : "#PROGRESSION_TOGGLE_DISABLED_BODY"
+ dialogData.image = $"ui/menu/common/dialog_announcement_1"
+
+ AddDialogButton( dialogData, "#NO" )
+ AddDialogButton( dialogData, "#YES", enabled ? DisableProgression : EnableProgression )
+
+ OpenDialog( dialogData )
+}
+
+void function EnableProgression()
+{
+ Progression_SetPreference( true )
+
+ // update the cache just in case something changed
+ UpdateCachedLoadouts_Delayed()
+
+ DialogData dialogData
+ dialogData.menu = GetMenu( "AnnouncementDialog" )
+ dialogData.header = "#PROGRESSION_ENABLED_HEADER"
+ dialogData.message = "#PROGRESSION_ENABLED_BODY"
+ dialogData.image = $"ui/menu/common/dialog_announcement_1"
+
+ AddDialogButton( dialogData, "#OK" )
+
+ EmitUISound( "UI_Menu_Item_Purchased_Stinger" )
+
+ OpenDialog( dialogData )
+}
+
+void function DisableProgression()
+{
+ Progression_SetPreference( false )
+
+ DialogData dialogData
+ dialogData.menu = GetMenu( "AnnouncementDialog" )
+ dialogData.header = "#PROGRESSION_DISABLED_HEADER"
+ dialogData.message = "#PROGRESSION_DISABLED_BODY"
+ dialogData.image = $"ui/menu/common/dialog_announcement_1"
+
+ AddDialogButton( dialogData, "#OK" )
+
+ OpenDialog( dialogData )
+}
+
void function SetupComboButtonTest( var menu )
{
ComboStruct comboStruct = ComboButtons_Create( menu )
@@ -235,44 +288,21 @@ void function SetupComboButtonTest( var menu )
int buttonIndex = 0
file.playHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_PLAY" )
- bool isModded = IsNorthstarServer()
-
-
- // this will be the server browser
- if ( isModded )
- {
- file.findGameButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_SERVER_BROWSER" )
- file.lobbyButtons.append( file.findGameButton )
- Hud_SetLocked( file.findGameButton, true )
- Hud_AddEventHandler( file.findGameButton, UIE_CLICK, OpenServerBrowser )
- }
- else
- {
- file.findGameButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_FIND_GAME" )
- file.lobbyButtons.append( file.findGameButton )
- Hud_AddEventHandler( file.findGameButton, UIE_CLICK, BigPlayButton1_Activate )
- }
+ // server browser
+ file.findGameButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_SERVER_BROWSER" )
+ file.lobbyButtons.append( file.findGameButton )
+ Hud_SetLocked( file.findGameButton, true )
+ Hud_AddEventHandler( file.findGameButton, UIE_CLICK, OpenServerBrowser )
- // this is used for launching private matches now
- if ( isModded )
- {
- file.inviteRoomButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#PRIVATE_MATCH" )
- Hud_AddEventHandler( file.inviteRoomButton, UIE_CLICK, StartPrivateMatch )
- }
- else
- {
- file.inviteRoomButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_INVITE_ROOM" )
- Hud_AddEventHandler( file.inviteRoomButton, UIE_CLICK, DoRoomInviteIfAllowed )
- }
+ // private match
+ file.inviteRoomButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#PRIVATE_MATCH" )
+ Hud_AddEventHandler( file.inviteRoomButton, UIE_CLICK, StartPrivateMatch )
file.inviteFriendsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_INVITE_FRIENDS" )
Hud_AddEventHandler( file.inviteFriendsButton, UIE_CLICK, InviteFriendsIfAllowed )
-
- if ( isModded )
- {
- Hud_SetEnabled( file.inviteFriendsButton, false )
- Hud_SetVisible( file.inviteFriendsButton, false )
- }
+
+ Hud_SetEnabled( file.inviteFriendsButton, false )
+ Hud_SetVisible( file.inviteFriendsButton, false )
// file.toggleMenuModeButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_LOBBY_SWITCH_FD" )
// Hud_AddEventHandler( file.toggleMenuModeButton, UIE_CLICK, ToggleLobbyMode )
@@ -352,8 +382,9 @@ void function SetupComboButtonTest( var menu )
var soundButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#VIDEO" )
Hud_AddEventHandler( soundButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "VideoMenu" ) ) )
#endif
- file.faqButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#KNB_MENU_HEADER" )
- Hud_AddEventHandler( file.faqButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "KnowledgeBaseMenu" ) ) )
+ // MOD SETTINGS
+ var modSettingsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MOD_SETTINGS" )
+ Hud_AddEventHandler( modSettingsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ModSettings" ) ) )
comboStruct.navUpButtonDisabled = true
comboStruct.navDownButton = file.genUpButton
@@ -372,8 +403,6 @@ void function StartPrivateMatch( var button )
return
ClientCommand( "StartPrivateMatchSearch" )
- NSSetLoading(true)
- NSUpdateListenServer()
}
void function DoRoomInviteIfAllowed( var button )
@@ -637,9 +666,9 @@ void function OnLobbyMenu_Open()
ComboButton_SetNew( file.factionButton, anyNewFactions )
}
- bool faqIsNew = !GetConVarBool( "menu_faq_viewed" ) || HaveNewPatchNotes() || HaveNewCommunityNotes()
+ /*bool faqIsNew = !GetConVarBool( "menu_faq_viewed" ) || HaveNewPatchNotes() || HaveNewCommunityNotes()
RuiSetBool( Hud_GetRui( file.settingsHeader ), "isNew", faqIsNew )
- ComboButton_SetNew( file.faqButton, faqIsNew )
+ ComboButton_SetNew( file.faqButton, faqIsNew )*/
TryUnlockSRSCallsign()
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_map_select.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_map_select.nut
index 930e472b..8e8071f5 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_map_select.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_map_select.nut
@@ -43,7 +43,7 @@ void function InitMapsMenu()
{
file.menu = GetMenu( "MapsMenu" )
- AddMouseMovementCaptureHandler( file.menu, UpdateMouseDeltaBuffer )
+ AddMouseMovementCaptureHandler( Hud_GetChild(file.menu, "MouseMovementCapture"), UpdateMouseDeltaBuffer )
AddMenuEventHandler( file.menu, eUIEvent.MENU_CLOSE, OnCloseMapsMenu )
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_mod_settings.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_mod_settings.nut
new file mode 100644
index 00000000..8c13955c
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_mod_settings.nut
@@ -0,0 +1,1106 @@
+untyped
+global function AddModSettingsMenu
+global function ModSettings_AddSetting
+global function ModSettings_AddEnumSetting
+global function ModSettings_AddSliderSetting
+global function ModSettings_AddButton
+global function ModSettings_AddModTitle
+global function ModSettings_AddModCategory
+global function PureModulo
+
+// Legacy functions for backwards compatability. These will be removed eventually
+global function AddConVarSetting
+global function AddConVarSettingEnum
+global function AddConVarSettingSlider
+global function AddModSettingsButton
+global function AddModTitle
+global function AddModCategory
+
+const int BUTTONS_PER_PAGE = 15
+const string SETTING_ITEM_TEXT = " " // this is long enough to be the same size as the textentry field
+
+enum eEmptySpaceType
+{
+ None,
+ TopBar,
+ BottomBar
+}
+
+struct ConVarData {
+ string displayName
+ bool isEnumSetting = false
+ string conVar
+ string type
+
+ string modName
+ string catName
+ bool isCategoryName = false
+ bool isModName = false
+
+ bool isEmptySpace = false
+ int spaceType = 0
+
+ // SLIDER BULLSHIT
+ bool sliderEnabled = false
+ float min = 0.0
+ float max = 1.0
+ float stepSize = 0.05
+ bool forceClamp = false
+
+ bool isCustomButton = false
+ void functionref() onPress
+
+ array<string> values
+ var customMenu
+ bool hasCustomMenu = false
+}
+
+struct {
+ var menu
+ int scrollOffset = 0
+ bool updatingList = false
+ bool isOpen
+
+ array<ConVarData> conVarList
+ // if people use searches - i hate them but it'll do : )
+ array<ConVarData> filteredList
+ string filterText = ""
+ table<int, int> enumRealValues
+ table<string, bool> setFuncs
+ array<var> modPanels
+ array<var> resetModButtons
+ array<MS_Slider> sliders
+ string currentMod = ""
+ string currentCat = ""
+} file
+
+struct {
+ int deltaX = 0
+ int deltaY = 0
+} mouseDeltaBuffer
+
+void function AddModSettingsMenu()
+{
+ AddMenu( "ModSettings", $"resource/ui/menus/mod_settings.menu", InitModMenu )
+}
+
+void function InitModMenu()
+{
+ file.menu = GetMenu( "ModSettings" )
+ // DumpStack(2)
+ AddMenuFooterOption( file.menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
+
+ /////////////////////////////
+ // BASE NORTHSTAR SETTINGS //
+ /////////////////////////////
+
+ /*
+ ModSettings_AddModTitle( "^FF000000EXAMPLE" )
+ ModSettings_AddModCategory( "I wasted way too much time on this..." )
+ ModSettings_AddButton( "This is a custom button you can click on!", void function() : ()
+ {
+ print( "HELLOOOOOO" )
+ } )
+ ModSettings_AddEnumSetting( "filter_mods", "Very Huge Enum Example", split( "Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you", " " ) )
+ */
+ // Nuke weird rui on filter switch :D
+ // RuiSetString( Hud_GetRui( Hud_GetChild( file.menu, "SwtBtnShowFilter" ) ), "buttonText", "" )
+
+ file.modPanels = GetElementsByClassname( file.menu, "ModButton" )
+
+ AddMenuEventHandler( file.menu, eUIEvent.MENU_OPEN, OnModMenuOpened )
+ AddMenuEventHandler( file.menu, eUIEvent.MENU_CLOSE, OnModMenuClosed )
+
+ int len = file.modPanels.len()
+ for ( int i = 0; i < len; i++ )
+ {
+
+ // AddButtonEventHandler( button, UIE_CHANGE, OnSettingButtonPressed )
+ // get panel
+ var panel = file.modPanels[i]
+
+ // reset to default nav
+ var child = Hud_GetChild( panel, "BtnMod" )
+
+
+ child.SetNavUp( Hud_GetChild( file.modPanels[ int( PureModulo( i - 1, len ) ) ], "BtnMod" ) )
+ child.SetNavDown( Hud_GetChild( file.modPanels[ int( PureModulo( i + 1, len ) ) ], "BtnMod" ) )
+
+ // Enum button nav
+ child = Hud_GetChild( panel, "EnumSelectButton" )
+ Hud_DialogList_AddListItem( child, SETTING_ITEM_TEXT, "main" )
+ Hud_DialogList_AddListItem( child, SETTING_ITEM_TEXT, "next" )
+ Hud_DialogList_AddListItem( child, SETTING_ITEM_TEXT, "prev" )
+
+ child.SetNavUp( Hud_GetChild( file.modPanels[ int( PureModulo( i - 1, len ) ) ], "EnumSelectButton" ) )
+ child.SetNavDown( Hud_GetChild( file.modPanels[ int( PureModulo( i + 1, len ) ) ], "EnumSelectButton" ) )
+ Hud_AddEventHandler( child, UIE_CLICK, UpdateEnumSetting )
+
+ // reset button nav
+
+ child = Hud_GetChild( panel, "ResetModToDefault" )
+ Hud_AddEventHandler( child, UIE_GET_FOCUS, void function( var child ) : (panel)
+ {
+ Hud_SetColor( Hud_GetChild( panel, "ResetModImage" ), 0, 0, 0, 255 )
+ })
+ Hud_AddEventHandler( child, UIE_LOSE_FOCUS, void function( var child ) : (panel)
+ {
+ Hud_SetColor( Hud_GetChild( panel, "ResetModImage" ), 180, 180, 180, 180 )
+ })
+
+ child.SetNavUp( Hud_GetChild( file.modPanels[ int( PureModulo( i - 1, len ) ) ], "ResetModToDefault" ) )
+ child.SetNavDown( Hud_GetChild( file.modPanels[ int( PureModulo( i + 1, len ) ) ], "ResetModToDefault" ) )
+
+ Hud_AddEventHandler( child, UIE_CLICK, ResetConVar )
+ file.resetModButtons.append(child)
+
+ // text field nav
+ child = Hud_GetChild( panel, "TextEntrySetting" )
+
+ Hud_AddEventHandler( child, UIE_LOSE_FOCUS, SendTextPanelChanges )
+
+ child.SetNavUp( Hud_GetChild( file.modPanels[ int( PureModulo( i - 1, len ) ) ], "TextEntrySetting" ) )
+ child.SetNavDown( Hud_GetChild( file.modPanels[ int( PureModulo( i + 1, len ) ) ], "TextEntrySetting" ) )
+
+ child = Hud_GetChild( panel, "Slider" )
+
+ child.SetNavUp( Hud_GetChild( file.modPanels[ int( PureModulo( i - 1, len ) ) ], "Slider" ) )
+ child.SetNavDown( Hud_GetChild( file.modPanels[ int( PureModulo( i + 1, len ) ) ], "Slider" ) )
+
+ file.sliders.append( MS_Slider_Setup( child ) )
+
+ Hud_AddEventHandler( child, UIE_CHANGE, OnSliderChange )
+
+ child = Hud_GetChild( panel, "OpenCustomMenu" )
+
+ Hud_AddEventHandler( child, UIE_CLICK, CustomButtonPressed )
+ }
+
+ // Hud_AddEventHandler( Hud_GetChild( file.menu, "BtnModsSearch" ), UIE_LOSE_FOCUS, OnFilterTextPanelChanged )
+ Hud_AddEventHandler( Hud_GetChild( file.menu, "BtnFiltersClear" ), UIE_CLICK, OnClearButtonPressed )
+ // mouse delta
+ AddMouseMovementCaptureHandler( file.menu, UpdateMouseDeltaBuffer )
+
+ Hud_AddEventHandler( Hud_GetChild( file.menu, "BtnModsSearch" ), UIE_CHANGE, void function ( var inputField ) : ()
+ {
+ file.filterText = Hud_GetUTF8Text( inputField )
+ OnFiltersChange()
+ } )
+}
+
+// "PureModulo"
+// Used instead of modulo in some places.
+// Why? beacuse PureModulo loops back onto positive numbers instead of going into the negatives.
+// DO NOT TOUCH.
+// a / b != floor( float( a ) / b )
+// int( float( a ) / b ) != floor( float( a ) / b )
+// Examples:
+// -1 % 5 = -1
+// PureModulo( -1, 5 ) = 4
+float function PureModulo( int a, int b )
+{
+ return b * ( ( float( a ) / b ) - floor( float( a ) / b ) )
+}
+
+void function ResetConVar( var button )
+{
+ ConVarData conVar = file.filteredList[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset ]
+
+ if ( conVar.isCategoryName )
+ ShowAreYouSureDialog( "#ARE_YOU_SURE", ResetAllConVarsForModEventHandler( conVar.catName ), "#WILL_RESET_ALL_SETTINGS" )
+ else
+ ShowAreYouSureDialog( "#ARE_YOU_SURE", ResetConVarEventHandler( int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset ), Localize( "#WILL_RESET_SETTING", Localize( conVar.displayName ) ) )
+}
+
+void function ShowAreYouSureDialog( string header, void functionref() func, string details )
+{
+ DialogData dialogData
+ dialogData.header = header
+ dialogData.message = details
+
+ AddDialogButton( dialogData, "#NO" )
+ AddDialogButton( dialogData, "#YES", func )
+
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+ AddDialogFooter( dialogData, "#B_BUTTON_BACK" )
+
+ OpenDialog( dialogData )
+}
+
+void functionref() function ResetAllConVarsForModEventHandler( string catName )
+{
+ return void function() : ( catName )
+ {
+ for ( int i = 0; i < file.conVarList.len(); i++ )
+ {
+ ConVarData c = file.conVarList[ i ]
+ if ( c.catName != catName || c.isCategoryName || c.isEmptySpace || c.isCustomButton )
+ continue
+ SetConVarToDefault( c.conVar )
+
+ int index = file.filteredList.find( c )
+ if ( file.filteredList.find( c ) < 0 )
+ continue
+
+ if ( min( BUTTONS_PER_PAGE - 1, max( 0, index - file.scrollOffset ) ) == index - file.scrollOffset )
+ {
+ Hud_SetText( Hud_GetChild( file.modPanels[ index - file.scrollOffset ], "TextEntrySetting" ), c.isEnumSetting ? c.values[ GetConVarInt( c.conVar ) ] : GetConVarString( c.conVar ) )
+ if( c.sliderEnabled )
+ MS_Slider_SetValue( file.sliders[ index - file.scrollOffset ], GetConVarFloat( c.conVar ) )
+ }
+ }
+ }
+}
+
+void functionref() function ResetConVarEventHandler( int modIndex )
+{
+ return void function() : ( modIndex )
+ {
+ ConVarData c = file.filteredList[ modIndex ]
+ SetConVarToDefault( c.conVar )
+ if ( min( BUTTONS_PER_PAGE - 1, max( 0, modIndex - file.scrollOffset ) ) == modIndex - file.scrollOffset )
+ {
+ Hud_SetText( Hud_GetChild( file.modPanels[ modIndex - file.scrollOffset ], "TextEntrySetting" ), c.isEnumSetting ? c.values[ GetConVarInt( c.conVar ) ] : GetConVarString( c.conVar ) )
+ if( c.sliderEnabled )
+ MS_Slider_SetValue( file.sliders[ modIndex - file.scrollOffset ], GetConVarFloat( c.conVar ) )
+ }
+ }
+}
+
+////////////
+// slider //
+////////////
+void function UpdateMouseDeltaBuffer( int x, int y )
+{
+ mouseDeltaBuffer.deltaX += x
+ mouseDeltaBuffer.deltaY += y
+
+ SliderBarUpdate()
+}
+
+void function FlushMouseDeltaBuffer()
+{
+ mouseDeltaBuffer.deltaX = 0
+ mouseDeltaBuffer.deltaY = 0
+}
+
+void function SliderBarUpdate()
+{
+ if ( file.filteredList.len() <= 15 )
+ {
+ FlushMouseDeltaBuffer()
+ return
+ }
+
+ var sliderButton = Hud_GetChild( file.menu, "BtnModListSlider" )
+ var sliderPanel = Hud_GetChild( file.menu, "BtnModListSliderPanel" )
+ var movementCapture = Hud_GetChild( file.menu, "MouseMovementCapture" )
+
+ Hud_SetFocused( sliderButton )
+
+ float minYPos = -40.0 * ( GetScreenSize()[1] / 1080.0 ) // why the hardcoded positions?!?!?!?!?!
+ float maxHeight = 615.0 * ( GetScreenSize()[1] / 1080.0 )
+ float maxYPos = minYPos - ( maxHeight - Hud_GetHeight( sliderPanel ) )
+ float useableSpace = ( maxHeight - Hud_GetHeight( sliderPanel ) )
+
+ float jump = minYPos - ( useableSpace / ( float( file.filteredList.len() ) ) )
+
+ int pos = expect int( expect array( Hud_GetPos( sliderButton ) )[1] )
+ float newPos = float( pos - mouseDeltaBuffer.deltaY )
+ FlushMouseDeltaBuffer()
+
+ if ( newPos < maxYPos ) newPos = maxYPos
+ if ( newPos > minYPos ) newPos = minYPos
+
+ Hud_SetPos( sliderButton, 2, newPos )
+ Hud_SetPos( sliderPanel, 2, newPos )
+ Hud_SetPos( movementCapture, 2, newPos )
+
+ file.scrollOffset = -int( ( ( newPos - minYPos ) / useableSpace ) * ( file.filteredList.len() - BUTTONS_PER_PAGE ) )
+ UpdateList()
+}
+
+void function UpdateListSliderHeight()
+{
+ var sliderButton = Hud_GetChild( file.menu, "BtnModListSlider" )
+ var sliderPanel = Hud_GetChild( file.menu, "BtnModListSliderPanel" )
+ var movementCapture = Hud_GetChild( file.menu, "MouseMovementCapture" )
+
+ float mods = float ( file.filteredList.len() )
+
+ float maxHeight = 615.0 * ( GetScreenSize()[1] / 1080.0 ) // why the hardcoded 320/80???
+ float minHeight = 80.0 * ( GetScreenSize()[1] / 1080.0 )
+
+ float height = maxHeight * ( float( BUTTONS_PER_PAGE ) / mods )
+
+ if ( height > maxHeight ) height = maxHeight
+ if ( height < minHeight ) height = minHeight
+
+ Hud_SetHeight( sliderButton, height )
+ Hud_SetHeight( sliderPanel, height )
+ Hud_SetHeight( movementCapture, height )
+}
+
+void function UpdateList()
+{
+ Hud_SetFocused( Hud_GetChild( file.menu, "BtnModsSearch" ) )
+ file.updatingList = true
+
+ array<ConVarData> filteredList = []
+
+ array<string> filters = split( file.filterText, "," )
+ array<ConVarData> list = file.conVarList
+ if ( filters.len() <= 0 )
+ filters.append( "" )
+ foreach( string f in filters )
+ {
+ string filter = strip( f )
+ string lastCatNameInFilter = ""
+ string lastModNameInFilter = ""
+ int curCatIndex = 0
+ int curModTitleIndex = -1
+ for ( int i = 0; i < list.len(); i++ )
+ {
+ ConVarData prev = list[ maxint( 0, i - 1 ) ]
+ ConVarData c = list[i]
+ ConVarData next = list[ minint( list.len() - 1, i + 1 ) ]
+ if ( c.isEmptySpace )
+ continue
+
+ string displayName = c.displayName
+ if ( c.isModName )
+ {
+ displayName = c.modName
+ curModTitleIndex = i
+ }
+ if ( c.isCategoryName )
+ {
+ displayName = c.catName
+ curCatIndex = i
+ }
+ if ( filter == "" || SanitizeDisplayName( Localize( displayName ) ).tolower().find( filter.tolower() ) != null )
+ {
+ if ( c.isModName )
+ {
+ lastModNameInFilter = c.modName
+ array<ConVarData> modVars = GetAllVarsInMod( list, c.modName )
+ if ( filteredList.len() <= 0 && modVars[0].spaceType == eEmptySpaceType.None )
+ filteredList.extend( modVars.slice( 1, modVars.len() ) )
+ else filteredList.extend( modVars )
+
+ i += modVars.len() - 1
+ }
+ else if ( c.isCategoryName )
+ {
+ if ( lastModNameInFilter != c.modName )
+ {
+ array<ConVarData> modVars = GetModConVarDatas( list, curModTitleIndex )
+ if ( filteredList.len() <= 0 && modVars[0].spaceType == eEmptySpaceType.None )
+ filteredList.extend( modVars.slice( 1, modVars.len() ) )
+ else filteredList.extend( modVars )
+
+ lastModNameInFilter = c.modName
+ }
+ filteredList.extend( GetAllVarsInCategory( list, c.catName ) )
+ i += GetAllVarsInCategory( list, c.catName ).len() - 1
+ lastCatNameInFilter = c.catName
+ }
+ else
+ {
+ if ( lastModNameInFilter != c.modName )
+ {
+ array<ConVarData> modVars = GetModConVarDatas( list, curModTitleIndex )
+ if ( filteredList.len() <= 0 && modVars[0].spaceType == eEmptySpaceType.None )
+ filteredList.extend( modVars.slice( 1, modVars.len() ) )
+ else filteredList.extend( modVars )
+
+ lastModNameInFilter = c.modName
+ }
+ if ( lastCatNameInFilter != c.catName )
+ {
+ filteredList.extend( GetCatConVarDatas( curCatIndex ) )
+ lastCatNameInFilter = c.catName
+ }
+ filteredList.append( c )
+ }
+ }
+ }
+ list = filteredList
+ filteredList = []
+ }
+ filteredList = list
+
+
+ file.filteredList = filteredList
+
+ int j = int( min( file.filteredList.len() + file.scrollOffset, BUTTONS_PER_PAGE ) )
+
+ for ( int i = 0; i < BUTTONS_PER_PAGE; i++ )
+ {
+ Hud_SetEnabled( file.modPanels[i], i < j )
+ Hud_SetVisible( file.modPanels[i], i < j )
+
+ if ( i < j )
+ SetModMenuNameText( file.modPanels[i] )
+ }
+ file.updatingList = false
+
+ if ( file.conVarList.len() <= 0 )
+ {
+ Hud_SetVisible( Hud_GetChild( file.menu, "NoResultLabel" ), true )
+ Hud_SetText( Hud_GetChild( file.menu, "NoResultLabel" ), "#NO_MODS" )
+ }
+ else if ( file.filteredList.len() <= 0 )
+ {
+ Hud_SetVisible( Hud_GetChild( file.menu, "NoResultLabel" ), true )
+ Hud_SetText( Hud_GetChild( file.menu, "NoResultLabel" ), "#NO_RESULTS" )
+ }
+ else
+ {
+ Hud_Hide( Hud_GetChild( file.menu, "NoResultLabel" ) )
+ }
+}
+
+array<ConVarData> function GetModConVarDatas( array<ConVarData> arr, int index )
+{
+ if ( index <= 1 )
+ return [ arr[ index - 1 ], arr[ index ], arr[ index + 1 ] ]
+ return [ arr[ index - 2 ], arr[ index - 1 ], arr[ index ], arr[ index + 1 ] ]
+}
+
+array<ConVarData> function GetCatConVarDatas( int index )
+{
+ if ( file.conVarList[ index - 1 ].spaceType != eEmptySpaceType.None )
+ return [ file.conVarList[ index ] ]
+ return [ file.conVarList[ index - 1 ], file.conVarList[ index ] ]
+}
+
+array<ConVarData> function GetAllVarsInCategory( array<ConVarData> arr, string catName )
+{
+ array<ConVarData> vars = []
+ for ( int i = 0; i < arr.len(); i++ )
+ {
+ ConVarData c = arr[i]
+ if ( c.catName == catName )
+ {
+ vars.append( arr[i] )
+ }
+ }
+ return vars
+}
+
+array<ConVarData> function GetAllVarsInMod( array<ConVarData> arr, string modName )
+{
+ array<ConVarData> vars = []
+ for ( int i = 0; i < arr.len(); i++ )
+ {
+ ConVarData c = arr[i]
+ if ( c.modName == modName )
+ {
+ vars.append( arr[i] )
+ }
+ }
+ return vars
+}
+
+void function SetModMenuNameText( var button )
+{
+ int index = int ( Hud_GetScriptID( button ) ) + file.scrollOffset
+ ConVarData conVar = file.filteredList[ int ( Hud_GetScriptID( button ) ) + file.scrollOffset ]
+
+ var panel = file.modPanels[ int ( Hud_GetScriptID( button ) ) ]
+
+ var label = Hud_GetChild( panel, "BtnMod" )
+ var textField = Hud_GetChild( panel, "TextEntrySetting" )
+ var enumButton = Hud_GetChild( panel, "EnumSelectButton" )
+ var resetButton = Hud_GetChild( panel, "ResetModToDefault" )
+ var resetVGUI = Hud_GetChild( panel, "ResetModImage" )
+ var bottomLine = Hud_GetChild( panel, "BottomLine" )
+ var topLine = Hud_GetChild( panel, "TopLine" )
+ var modTitle = Hud_GetChild( panel, "ModTitle" )
+ var customMenuButton = Hud_GetChild( panel, "OpenCustomMenu")
+ var slider = Hud_GetChild( panel, "Slider" )
+ Hud_SetVisible( slider, false )
+ Hud_SetEnabled( slider, true )
+
+
+ if ( conVar.isEmptySpace )
+ {
+ string s = ""
+ Hud_SetPos( label, 0, 0 )
+ Hud_SetVisible( label, false )
+ Hud_SetVisible( textField, false )
+ Hud_SetVisible( enumButton, false )
+ Hud_SetVisible( resetButton, false )
+ Hud_SetVisible( resetVGUI, false )
+ Hud_SetVisible( modTitle, false )
+ Hud_SetVisible( customMenuButton, false )
+ Hud_SetVisible( bottomLine, false )
+ Hud_SetVisible( topLine, false )
+ switch ( conVar.spaceType )
+ {
+ case eEmptySpaceType.TopBar:
+ Hud_SetVisible( topLine, true )
+ return
+
+ case eEmptySpaceType.BottomBar:
+ Hud_SetVisible( bottomLine, true )
+ return
+
+ case eEmptySpaceType.None:
+ return
+ }
+ }
+
+ Hud_SetVisible( textField, !conVar.isCategoryName )
+ Hud_SetVisible( bottomLine, conVar.isCategoryName || conVar.spaceType == eEmptySpaceType.BottomBar )
+ Hud_SetVisible( topLine, false )
+ Hud_SetVisible( enumButton, !conVar.isCategoryName && conVar.isEnumSetting )
+ Hud_SetVisible( modTitle, conVar.isModName )
+ Hud_SetVisible( customMenuButton, false )
+ float scaleX = GetScreenSize()[1] / 1080.0
+ float scaleY = GetScreenSize()[1] / 1080.0
+ if ( conVar.sliderEnabled )
+ {
+ Hud_SetSize( slider, int( 320 * scaleX ), int( 45 * scaleY ) )
+ MS_Slider s = file.sliders[ int ( Hud_GetScriptID( button ) ) ]
+ MS_Slider_SetMin( s, conVar.min )
+ MS_Slider_SetMax( s, conVar.max )
+ MS_Slider_SetStepSize( s, conVar.stepSize )
+ MS_Slider_SetValue( s, GetConVarFloat( conVar.conVar ) )
+ }
+ else Hud_SetSize( slider, 0, int( 45 * scaleY ) )
+ if ( conVar.isCustomButton )
+ {
+ Hud_SetVisible( label, false )
+ Hud_SetVisible( textField, false )
+ Hud_SetVisible( enumButton, false )
+ Hud_SetVisible( resetButton, false )
+ Hud_SetVisible( modTitle, false )
+ Hud_SetVisible( resetVGUI, false )
+ Hud_SetVisible( customMenuButton, true )
+ Hud_SetText( customMenuButton, conVar.displayName )
+ }
+ else if ( conVar.isModName )
+ {
+ Hud_SetText( modTitle, conVar.modName )
+ Hud_SetPos( label, 0, 0 )
+ Hud_SetVisible( label, false )
+ Hud_SetVisible( textField, false )
+ Hud_SetVisible( enumButton, false )
+ Hud_SetVisible( resetButton, false )
+ Hud_SetVisible( resetVGUI, false )
+ Hud_SetVisible( bottomLine, false )
+ Hud_SetVisible( topLine, false )
+ }
+ else if ( conVar.isCategoryName )
+ {
+ Hud_SetText( label, conVar.catName )
+ Hud_SetPos( label, 0, 0 )
+ Hud_SetSize( label, int( scaleX * ( 1180 - 420 - 85 ) ), int( scaleY * 40 ) )
+ Hud_SetVisible( label, true )
+ Hud_SetVisible( textField, false )
+ Hud_SetVisible( enumButton, false )
+ Hud_SetVisible( resetButton, true )
+ Hud_SetVisible( resetVGUI, true )
+
+ Hud_SetSize( resetButton, int( scaleX * 90 ), int( scaleY * 40 ) )
+ }
+ else {
+ Hud_SetVisible( slider, conVar.sliderEnabled )
+
+ Hud_SetText( label, conVar.displayName )
+ if (conVar.type == "float")
+ Hud_SetText( textField, string( GetConVarFloat(conVar.conVar) ) )
+ else Hud_SetText( textField, conVar.isEnumSetting ? conVar.values[ GetConVarInt( conVar.conVar ) ] : GetConVarString( conVar.conVar ) )
+ Hud_SetPos( label, int(scaleX * 25), 0 )
+ Hud_SetText( resetButton, "" )
+ if (conVar.sliderEnabled)
+ Hud_SetSize( label, int(scaleX * (375 + 85)), int(scaleY * 40) )
+ else Hud_SetSize( label, int(scaleX * (375 + 405)), int(scaleY * 40) )
+ if ( conVar.type == "float" )
+ Hud_SetText( textField, string( GetConVarFloat( conVar.conVar ) ) )
+ else Hud_SetText( textField, conVar.isEnumSetting ? conVar.values[ GetConVarInt( conVar.conVar ) ] : GetConVarString( conVar.conVar ) )
+ Hud_SetPos( label, int( scaleX * 25 ), 0 )
+ Hud_SetText( resetButton, "" )
+ Hud_SetSize( resetButton, int( scaleX * 90 ), int( scaleY * 40 ) )
+ if ( conVar.sliderEnabled )
+ Hud_SetSize( label, int( scaleX * ( 375 + 85 ) ), int( scaleY * 40 ) )
+ else Hud_SetSize( label, int( scaleX * ( 375 + 405 ) ), int( scaleY * 40 ) )
+ Hud_SetVisible( label, true )
+ Hud_SetVisible( textField, true )
+ Hud_SetVisible( resetButton, true )
+ Hud_SetVisible( resetVGUI, true )
+ }
+}
+
+void function CustomButtonPressed( var button )
+{
+ var panel = Hud_GetParent( button )
+ ConVarData c = file.filteredList[ int( Hud_GetScriptID( panel ) ) + file.scrollOffset ]
+ c.onPress()
+}
+
+void function OnScrollDown( var button )
+{
+ if ( file.filteredList.len() <= BUTTONS_PER_PAGE ) return
+ file.scrollOffset += 5
+ if ( file.scrollOffset + BUTTONS_PER_PAGE > file.filteredList.len() )
+ {
+ file.scrollOffset = file.filteredList.len() - BUTTONS_PER_PAGE
+ }
+ UpdateList()
+ UpdateListSliderPosition()
+}
+
+void function OnScrollUp( var button )
+{
+ file.scrollOffset -= 5
+ if ( file.scrollOffset < 0 )
+ {
+ file.scrollOffset = 0
+ }
+ UpdateList()
+ UpdateListSliderPosition()
+}
+
+void function UpdateListSliderPosition()
+{
+ var sliderButton = Hud_GetChild( file.menu , "BtnModListSlider" )
+ var sliderPanel = Hud_GetChild( file.menu , "BtnModListSliderPanel" )
+ var movementCapture = Hud_GetChild( file.menu , "MouseMovementCapture" )
+
+ float mods = float ( file.filteredList.len() )
+
+ float minYPos = -40.0 * ( GetScreenSize()[1] / 1080.0 )
+ float useableSpace = ( 615.0 * ( GetScreenSize()[1] / 1080.0 ) - Hud_GetHeight( sliderPanel ) )
+
+ float jump = minYPos - ( useableSpace / ( mods - float( BUTTONS_PER_PAGE ) ) * file.scrollOffset )
+
+
+ if ( jump > minYPos ) jump = minYPos
+
+ Hud_SetPos( sliderButton , 2, jump )
+ Hud_SetPos( sliderPanel , 2, jump )
+ Hud_SetPos( movementCapture , 2, jump )
+}
+
+void function OnModMenuOpened()
+{
+ if( !file.isOpen )
+ {
+ file.scrollOffset = 0
+ file.filterText = ""
+
+ RegisterButtonPressedCallback( MOUSE_WHEEL_UP , OnScrollUp )
+ RegisterButtonPressedCallback( MOUSE_WHEEL_DOWN , OnScrollDown )
+ RegisterButtonPressedCallback( MOUSE_LEFT , OnClick )
+
+ OnFiltersChange()
+ file.isOpen = true
+ }
+}
+
+void function OnClick( var button )
+{
+ if (file.resetModButtons.contains(GetFocus()))
+ thread CheckFocus(GetFocus())
+ if (GetFocus() == Hud_GetChild(file.menu, "NoResultLabel"))
+ thread CheckFocus(GetFocus())
+}
+
+void function CheckFocus( var button )
+{
+ wait 0.05
+ if (file.resetModButtons.contains(GetFocus()))
+ {
+ thread ResetConVar(GetFocus())
+ }
+ if (GetFocus() == Hud_GetChild(file.menu, "NoResultLabel"))
+ LaunchExternalWebBrowser( "https://northstar.thunderstore.io/", WEBBROWSER_FLAG_FORCEEXTERNAL )
+}
+
+void function OnFiltersChange()
+{
+ file.scrollOffset = 0
+
+ UpdateList()
+
+ UpdateListSliderHeight()
+}
+
+void function OnModMenuClosed()
+{
+ DeregisterButtonPressedCallback( MOUSE_WHEEL_UP , OnScrollUp )
+ DeregisterButtonPressedCallback( MOUSE_WHEEL_DOWN , OnScrollDown )
+ DeregisterButtonPressedCallback( MOUSE_LEFT , OnClick )
+
+ file.scrollOffset = 0
+ UpdateListSliderPosition()
+ file.isOpen = false
+}
+
+void function ModSettings_AddModTitle( string modName, int stackPos = 2 )
+{
+ file.currentMod = modName
+ if ( file.conVarList.len() > 0 )
+ {
+ ConVarData catData
+
+ catData.isEmptySpace = true
+ catData.modName = file.currentMod
+
+ file.conVarList.append( catData )
+ }
+ ConVarData topBar
+ topBar.isEmptySpace = true
+ topBar.modName = modName
+ topBar.spaceType = eEmptySpaceType.TopBar
+
+
+ ConVarData modData
+
+ modData.modName = modName
+ modData.displayName = modName
+ modData.isModName = true
+
+
+ ConVarData botBar
+ botBar.isEmptySpace = true
+ botBar.modName = modName
+ botBar.spaceType = eEmptySpaceType.BottomBar
+ file.conVarList.extend( [ topBar, modData, botBar ] )
+ file.setFuncs[ expect string( getstackinfos( stackPos )[ "func" ] ) ] <- false
+}
+
+void function AddModTitle( string modName, int stackPos = 2 )
+{
+ ModSettings_AddModTitle( modName, stackPos + 1 )
+}
+
+void function ModSettings_AddModCategory( string catName, int stackPos = 2 )
+{
+ if ( !( getstackinfos( stackPos )[ "func" ] in file.setFuncs ) )
+ throw getstackinfos( stackPos )[ "src" ] + " #" + getstackinfos( stackPos )[ "line" ] + "\nCannot add a category before a mod title!"
+
+ ConVarData space
+ space.isEmptySpace = true
+ space.modName = file.currentMod
+ space.catName = catName
+ file.conVarList.append( space )
+
+ ConVarData catData
+
+ catData.catName = catName
+ catData.displayName = catName
+ catData.modName = file.currentMod
+ catData.isCategoryName = true
+
+ file.conVarList.append( catData )
+
+ file.currentCat = catName
+ file.setFuncs[ expect string( getstackinfos( stackPos )[ "func" ] ) ] = true
+}
+
+void function AddModCategory( string catName, int stackPos = 2 )
+{
+ ModSettings_AddModCategory( catName, stackPos + 1 )
+}
+
+void function ModSettings_AddButton( string buttonLabel, void functionref() onPress, int stackPos = 2 )
+{
+ if ( !( getstackinfos( stackPos )[ "func" ] in file.setFuncs ) || !file.setFuncs[ expect string( getstackinfos( stackPos )[ "func" ] ) ] )
+ throw getstackinfos( stackPos )[ "src" ] + " #" + getstackinfos( stackPos )[ "line" ] + "\nCannot add a button before a category and mod title!"
+
+ ConVarData data
+
+ data.isCustomButton = true
+ data.displayName = buttonLabel
+ data.modName = file.currentMod
+ data.catName = file.currentCat
+ data.onPress = onPress
+
+ file.conVarList.append( data )
+}
+
+void function AddModSettingsButton( string buttonLabel, void functionref() onPress, int stackPos = 2 )
+{
+ ModSettings_AddButton( buttonLabel, onPress, stackPos + 1 )
+}
+
+void function ModSettings_AddSetting( string conVar, string displayName, string type = "", int stackPos = 2 )
+{
+ if ( !( getstackinfos( stackPos )[ "func" ] in file.setFuncs ) || !file.setFuncs[ expect string( getstackinfos( stackPos )[ "func" ] ) ] )
+ throw getstackinfos( stackPos )[ "src" ] + " #" + getstackinfos( stackPos )[ "line" ] + "\nCannot add a setting before a category and mod title!"
+ ConVarData data
+
+ data.catName = file.currentCat
+ data.conVar = conVar
+ data.modName = file.currentMod
+ data.displayName = displayName
+ data.type = type
+
+ file.conVarList.append( data )
+}
+
+void function AddConVarSetting( string conVar, string displayName, string type = "", int stackPos = 2 )
+{
+ ModSettings_AddSetting( conVar, displayName, type, stackPos + 1 )
+}
+
+void function ModSettings_AddSliderSetting( string conVar, string displayName, float min = 0.0, float max = 1.0, float stepSize = 0.1, bool forceClamp = false, int stackPos = 2 )
+{
+ if ( !( getstackinfos( stackPos )[ "func" ] in file.setFuncs ) || !file.setFuncs[ expect string( getstackinfos( stackPos )[ "func" ] ) ] )
+ throw getstackinfos( stackPos )[ "src" ] + " #" + getstackinfos( stackPos )[ "line" ] + "\nCannot add a setting before a category and mod title!"
+ ConVarData data
+
+ data.catName = file.currentCat
+ data.conVar = conVar
+ data.modName = file.currentMod
+ data.displayName = displayName
+ data.type = "float"
+ data.sliderEnabled = true
+ data.forceClamp = false
+ data.min = min
+ data.max = max
+ data.stepSize = stepSize
+
+ file.conVarList.append( data )
+}
+
+void function AddConVarSettingSlider( string conVar, string displayName, float min = 0.0, float max = 1.0, float stepSize = 0.1, bool forceClamp = false, int stackPos = 2 )
+{
+ ModSettings_AddSliderSetting( conVar, displayName, min, max, stepSize, forceClamp, stackPos + 1 )
+}
+
+void function ModSettings_AddEnumSetting( string conVar, string displayName, array<string> values, int stackPos = 2 )
+{
+ if ( !( getstackinfos( stackPos )[ "func" ] in file.setFuncs ) || !file.setFuncs[ expect string( getstackinfos( stackPos )[ "func" ] ) ] )
+ throw getstackinfos( stackPos )[ "src" ] + " #" + getstackinfos( stackPos )[ "line" ] + "\nCannot add a setting before a category and mod title!"
+ ConVarData data
+
+ data.catName = file.currentCat
+ data.modName = file.currentMod
+ data.conVar = conVar
+ data.displayName = displayName
+ data.values = values
+ data.isEnumSetting = true
+ data.min = 0
+ data.max = values.len() - 1.0
+ data.sliderEnabled = values.len() > 2
+ data.forceClamp = true
+ data.stepSize = 1
+
+ file.conVarList.append( data )
+}
+
+void function AddConVarSettingEnum( string conVar, string displayName, array<string> values, int stackPos = 2 )
+{
+ ModSettings_AddEnumSetting( conVar, displayName, values, stackPos + 1 )
+}
+
+void function OnSliderChange( var button )
+{
+ if ( file.updatingList )
+ return
+ var panel = Hud_GetParent( button )
+ ConVarData c = file.filteredList[ int( Hud_GetScriptID( panel ) ) + file.scrollOffset ]
+ var textPanel = Hud_GetChild( panel, "TextEntrySetting" )
+
+ if ( c.isEnumSetting )
+ {
+ int val = int( RoundToNearestInt( Hud_SliderControl_GetCurrentValue( button ) ) )
+ SetConVarInt( c.conVar, val )
+ Hud_SetText( textPanel, ( c.values[ GetConVarInt( c.conVar ) ] ) )
+ MS_Slider_SetValue( file.sliders[ int( Hud_GetScriptID( Hud_GetParent( textPanel ) ) ) ], float( val ) )
+
+ return
+ }
+ float val = Hud_SliderControl_GetCurrentValue( button )
+ if ( c.forceClamp )
+ {
+ int mod = int( RoundToNearestInt( val % c.stepSize / c.stepSize ) )
+ val = ( int( val / c.stepSize ) + mod ) * c.stepSize
+ }
+ SetConVarFloat( c.conVar, val )
+ MS_Slider_SetValue( file.sliders[ int( Hud_GetScriptID( Hud_GetParent( textPanel ) ) ) ], val )
+
+ Hud_SetText( textPanel, string( GetConVarFloat( c.conVar ) ) )
+}
+
+void function SendTextPanelChanges( var textPanel )
+{
+ ConVarData c = file.filteredList[ int( Hud_GetScriptID( Hud_GetParent( textPanel ) ) ) + file.scrollOffset ]
+ if ( c.conVar == "" ) return
+ // enums don't need to do this
+ if ( !c.isEnumSetting )
+ {
+ string newSetting = Hud_GetUTF8Text( textPanel )
+
+ switch ( c.type )
+ {
+ case "int":
+ try
+ {
+ SetConVarInt( c.conVar, newSetting.tointeger() )
+ }
+ catch ( ex )
+ {
+ ThrowInvalidValue( "This setting is an integer, and only accepts whole numbers." )
+ Hud_SetText( textPanel, GetConVarString( c.conVar ) )
+ }
+ break
+ case "bool":
+ if ( newSetting != "0" && newSetting != "1" )
+ {
+ ThrowInvalidValue( "This setting is a boolean, and only accepts values of 0 or 1." )
+
+ // set back to previous value : )
+ Hud_SetText( textPanel, string( GetConVarBool( c.conVar ) ) )
+
+ break
+ }
+ SetConVarBool( c.conVar, newSetting == "1" )
+ break
+ case "float":
+ try
+ {
+ SetConVarFloat( c.conVar, newSetting.tofloat() )
+ }
+ catch ( ex )
+ {
+ printt( ex )
+ ThrowInvalidValue( "This setting is a float, and only accepts a number - we could not parse this!\n\n( Use \".\" for the floating point, not \",\". )" )
+ }
+ if ( c.sliderEnabled )
+ {
+ var panel = Hud_GetParent( textPanel )
+ MS_Slider s = file.sliders[ int ( Hud_GetScriptID( panel ) ) ]
+
+ MS_Slider_SetValue( s, GetConVarFloat( c.conVar ) )
+ }
+ break
+ case "float2":
+ try
+ {
+ array<string> split = split( newSetting, " " )
+ if ( split.len() != 2 )
+ {
+ ThrowInvalidValue( "This setting is a float2, and only accepts a pair of numbers - you put in " + split.len() + "!" )
+ Hud_SetText( textPanel, GetConVarString( c.conVar ) )
+ break
+ }
+ vector settingTest = < split[0].tofloat(), split[1].tofloat(), 0 >
+
+ SetConVarString( c.conVar, newSetting )
+ }
+ catch ( ex )
+ {
+ ThrowInvalidValue( "This setting is a float2, and only accepts a pair of numbers - you put something we could not parse!\n\n( Use \".\" for the floating point, not \",\". )" )
+ Hud_SetText( textPanel, GetConVarString( c.conVar ) )
+ }
+ break
+ // idk sometimes it's called Float3 most of the time it's called vector, I am not complaining.
+ case "vector":
+ case "float3":
+ try
+ {
+ array<string> split = split( newSetting, " " )
+ if ( split.len() != 3 )
+ {
+ ThrowInvalidValue( "This setting is a float3, and only accepts a trio of numbers - you put in " + split.len() + "!" )
+ Hud_SetText( textPanel, GetConVarString( c.conVar ) )
+ break
+ }
+ vector settingTest = < split[0].tofloat(), split[1].tofloat(), 0 >
+
+ SetConVarString( c.conVar, newSetting )
+ }
+ catch ( ex )
+ {
+ ThrowInvalidValue( "This setting is a float3, and only accepts a trio of numbers - you put something we could not parse!\n\n( Use \".\" for the floating point, not \",\". )" )
+ Hud_SetText( textPanel, GetConVarString( c.conVar ) )
+ }
+ break
+ default:
+ SetConVarString( c.conVar, newSetting )
+ break;
+ }
+ }
+ else Hud_SetText( textPanel, Localize( c.values[ GetConVarInt( c.conVar ) ] ) )
+}
+
+void function ThrowInvalidValue( string desc )
+{
+ DialogData dialogData
+ dialogData.header = "Invalid Value"
+ dialogData.image = $"ui/menu/common/dialog_error"
+ dialogData.message = desc
+ AddDialogButton( dialogData, "#OK" )
+ OpenDialog( dialogData )
+}
+
+void function UpdateEnumSetting( var button )
+{
+ int scriptId = int( Hud_GetScriptID( Hud_GetParent( button ) ) )
+ ConVarData c = file.filteredList[ scriptId + file.scrollOffset ]
+
+ var panel = file.modPanels[ scriptId ]
+
+ var textPanel = Hud_GetChild( panel, "TextEntrySetting" )
+
+ string selectionVal = Hud_GetDialogListSelectionValue( button )
+
+ if ( selectionVal == "main" )
+ return
+
+ int enumVal = GetConVarInt( c.conVar )
+ if ( selectionVal == "next" ) // enum val += 1
+ enumVal = ( enumVal + 1 ) % c.values.len()
+ else // enum val -= 1
+ {
+ enumVal--
+ if ( enumVal == -1 )
+ enumVal = c.values.len() - 1
+ }
+
+ SetConVarInt( c.conVar, enumVal )
+ Hud_SetText( textPanel, c.values[ enumVal ] )
+
+ Hud_SetDialogListSelectionValue( button, "main" )
+}
+
+void function OnClearButtonPressed( var button )
+{
+ file.filterText = ""
+ Hud_SetText( Hud_GetChild( file.menu, "BtnModsSearch" ), "" )
+
+ OnFiltersChange()
+}
+
+string function SanitizeDisplayName( string displayName )
+{
+ array<string> parts = split( displayName, "^" )
+ string result = ""
+ if ( parts.len() == 1 )
+ return parts[0]
+ foreach ( string p in parts )
+ {
+ if ( p == "" )
+ {
+ result += "^"
+ continue
+ }
+ int i = 0
+ for ( i = 0; i < 8 && i < p.len(); i++ )
+ {
+ var c = p[i]
+ if ( ( c < 'a' || c > 'f' ) && ( c < 'A' || c > 'F' ) && ( c < '0' || c > '9' ) )
+ break
+ }
+ if ( i == 0 )
+ result += p
+ else result += p.slice( i, p.len() )
+ }
+ return result
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut
index 32a3c8f5..605af383 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut
@@ -18,8 +18,8 @@ void function InitModesMenu()
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 )
+ AddMenuFooterOption( menu, BUTTON_SHOULDER_LEFT, "#PRIVATE_MATCH_PAGE_PREV", "#PRIVATE_MATCH_PAGE_PREV", CycleModesBack )
+ AddMenuFooterOption( menu, BUTTON_SHOULDER_RIGHT, "#PRIVATE_MATCH_PAGE_NEXT", "#PRIVATE_MATCH_PAGE_NEXT", CycleModesForward )
}
void function OnOpenModesMenu()
@@ -52,16 +52,11 @@ void function UpdateVisibleModes()
Hud_SetEnabled( buttons[ i ], true )
Hud_SetVisible( buttons[ i ], true )
- if ( !ModeSettings_RequiresAI( modesArray[ modeIndex ] ) || modesArray[ modeIndex ] == "aitdm" )
+ // This check is refactored in the new mode menu so we can just ignore this atrocity
+ if ( !ModeSettings_RequiresAI( modesArray[ modeIndex ] ) || modesArray[ modeIndex ] == "aitdm" || modesArray[ modeIndex ] == "at" )
Hud_SetLocked( buttons[ i ], false )
else
- Hud_SetLocked( buttons[ i ], true )
-
- if ( !PrivateMatch_IsValidMapModeCombo( PrivateMatch_GetSelectedMap(), modesArray[ modeIndex ] ) && !IsNorthstarServer() )
- {
- Hud_SetLocked( buttons[ i ], true )
- SetButtonRuiText( buttons[ i ], Localize( "#PRIVATE_MATCH_UNAVAILABLE", Localize( GetGameModeDisplayName( modesArray[ modeIndex ] ) ) ) )
- }
+ Hud_SetLocked( buttons[ i ], true )
}
}
@@ -89,9 +84,7 @@ void function ModeButton_GetFocus( var button )
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!
+ if ( IsFDMode( modeName ) ) // HACK!
Hud_SetText( nextModeDesc, Localize( "#FD_PLAYERS_DESC", Localize( GetGameModeDisplayHint( modeName ) ) ) )
else
Hud_SetText( nextModeDesc, GetGameModeDisplayHint( modeName ) )
@@ -113,7 +106,7 @@ void function ModeButton_Click( var button )
// 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[ modeID ] ) )
+ if ( !PrivateMatch_IsValidMapModeCombo( PrivateMatch_GetSelectedMap(), modesArray[ modeID ] ) )
ClientCommand( "SetCustomMap " + GetPrivateMatchMapsForMode( modeName )[ 0 ] )
// set it
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut
index b5a2e9b6..b89e665b 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut
@@ -54,5 +54,8 @@ void function OnConnectWithPasswordMenuOpened()
void function ConnectWithPassword( var button )
{
if ( GetTopNonDialogMenu() == file.menu )
- thread ThreadedAuthAndConnectToServer( Hud_GetUTF8Text( Hud_GetChild( file.menu, "EnterPasswordBox" ) ) )
+ {
+ TriggerConnectToServerCallbacks()
+ thread ThreadedAuthAndConnectToServer( Hud_GetUTF8Text( Hud_GetChild( file.menu, "EnterPasswordBox" ) ), true )
+ }
} \ No newline at end of file
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut
new file mode 100644
index 00000000..4968714c
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut
@@ -0,0 +1,152 @@
+global function DownloadMod
+global function DisplayModDownloadErrorDialog
+global function FetchVerifiedModsManifesto
+
+global enum eModInstallStatus
+{
+ MANIFESTO_FETCHING,
+ DOWNLOADING,
+ CHECKSUMING,
+ EXTRACTING,
+ DONE,
+ FAILED,
+ FAILED_READING_ARCHIVE,
+ FAILED_WRITING_TO_DISK,
+ MOD_FETCHING_FAILED,
+ MOD_CORRUPTED,
+ NO_DISK_SPACE_AVAILABLE,
+ NOT_FOUND
+}
+
+const int MB = 1024*1000;
+
+
+void function FetchVerifiedModsManifesto()
+{
+ print("Start fetching verified mods manifesto from the Internet")
+
+ // Fetching UI
+ DialogData dialogData
+ dialogData.header = Localize( "#MANIFESTO_FETCHING_TITLE" )
+ dialogData.message = Localize( "#MANIFESTO_FETCHING_TEXT" )
+ dialogData.showSpinner = true;
+
+ // Prevent user from closing dialog
+ dialogData.forceChoice = true;
+ OpenDialog( dialogData )
+
+ // Do the actual fetching
+ NSFetchVerifiedModsManifesto()
+
+ ModInstallState state = NSGetModInstallState()
+ while ( state.status == eModInstallStatus.MANIFESTO_FETCHING )
+ {
+ state = NSGetModInstallState()
+ WaitFrame()
+ }
+
+ // Close dialog when manifesto has been received
+ CloseActiveMenu()
+}
+
+bool function DownloadMod( RequiredModInfo mod )
+{
+ // Downloading mod UI
+ DialogData dialogData
+ dialogData.header = Localize( "#DOWNLOADING_MOD_TITLE" )
+ dialogData.message = Localize( "#DOWNLOADING_MOD_TEXT", mod.name, mod.version )
+ dialogData.showSpinner = true;
+
+ // Prevent user from closing dialog
+ dialogData.forceChoice = true;
+ OpenDialog( dialogData )
+
+ // Save reference to UI elements, to update their content
+ var menu = GetMenu( "Dialog" )
+ var header = Hud_GetChild( menu, "DialogHeader" )
+ var body = GetSingleElementByClassname( menu, "DialogMessageClass" )
+
+ // Start actual mod downloading
+ NSDownloadMod( mod.name, mod.version )
+
+ ModInstallState state = NSGetModInstallState()
+ while ( state.status < eModInstallStatus.DONE )
+ {
+ state = NSGetModInstallState()
+ UpdateModDownloadDialog( mod, state, menu, header, body )
+ WaitFrame()
+ }
+
+ printt( "Mod status:", state.status )
+
+ // Close loading dialog
+ CloseActiveMenu()
+
+ return state.status == eModInstallStatus.DONE
+}
+
+void function UpdateModDownloadDialog( RequiredModInfo mod, ModInstallState state, var menu, var header, var body )
+{
+ switch ( state.status )
+ {
+ case eModInstallStatus.MANIFESTO_FETCHING:
+ Hud_SetText( header, Localize( "#MANIFESTO_FETCHING_TITLE" ) )
+ Hud_SetText( body, Localize( "#MANIFESTO_FETCHING_TEXT" ) )
+ break
+ case eModInstallStatus.DOWNLOADING:
+ Hud_SetText( header, Localize( "#DOWNLOADING_MOD_TITLE_W_PROGRESS", string( state.ratio ) ) )
+ Hud_SetText( body, Localize( "#DOWNLOADING_MOD_TEXT_W_PROGRESS", mod.name, mod.version, floor( state.progress / MB ), floor( state.total / MB ) ) )
+ break
+ case eModInstallStatus.CHECKSUMING:
+ Hud_SetText( header, Localize( "#CHECKSUMING_TITLE" ) )
+ Hud_SetText( body, Localize( "#CHECKSUMING_TEXT", mod.name, mod.version ) )
+ break
+ case eModInstallStatus.EXTRACTING:
+ Hud_SetText( header, Localize( "#EXTRACTING_MOD_TITLE", string( state.ratio ) ) )
+ Hud_SetText( body, Localize( "#EXTRACTING_MOD_TEXT", mod.name, mod.version, floor( state.progress / MB ), floor( state.total / MB ) ) )
+ break
+ default:
+ break
+ }
+}
+
+void function DisplayModDownloadErrorDialog( string modName )
+{
+ ModInstallState state = NSGetModInstallState()
+
+ DialogData dialogData
+ dialogData.header = Localize( "#FAILED_DOWNLOADING", modName )
+ dialogData.image = $"ui/menu/common/dialog_error"
+
+ switch ( state.status )
+ {
+ case eModInstallStatus.FAILED_READING_ARCHIVE:
+ dialogData.message = Localize( "#FAILED_READING_ARCHIVE" )
+ break
+ case eModInstallStatus.FAILED_WRITING_TO_DISK:
+ dialogData.message = Localize( "#FAILED_WRITING_TO_DISK" )
+ break
+ case eModInstallStatus.MOD_FETCHING_FAILED:
+ dialogData.message = Localize( "#MOD_FETCHING_FAILED" )
+ break
+ case eModInstallStatus.MOD_CORRUPTED:
+ dialogData.message = Localize( "#MOD_CORRUPTED" )
+ break
+ case eModInstallStatus.NO_DISK_SPACE_AVAILABLE:
+ dialogData.message = Localize( "#NO_DISK_SPACE_AVAILABLE" )
+ break
+ case eModInstallStatus.NOT_FOUND:
+ dialogData.message = Localize( "#NOT_FOUND" )
+ break
+ case eModInstallStatus.FAILED:
+ default:
+ dialogData.message = Localize( "#MOD_FETCHING_FAILED_GENERAL" )
+ break
+ }
+
+ AddDialogButton( dialogData, "#DISMISS" )
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+ AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" )
+
+ OpenDialog( dialogData )
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut
index 15d78025..3f643aa3 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut
@@ -5,18 +5,26 @@ global function AddNorthstarModMenu_MainMenuFooter
global function ReloadMods
-const int BUTTONS_PER_PAGE = 17
-
+struct modData {
+ string name = ""
+ string version = ""
+ string link = ""
+ int loadPriority = 0
+ bool enabled = false
+ array<string> conVars = []
+}
-struct modStruct {
- int modIndex
- string modName
+struct panelContent {
+ modData& mod
+ bool isHeader = false
}
enum filterShow {
ALL = 0,
ONLY_ENABLED = 1,
ONLY_DISABLED = 2
+ ONLY_NOT_REQUIRED = 3,
+ ONLY_REQUIRED = 4
}
struct {
@@ -25,16 +33,19 @@ struct {
} mouseDeltaBuffer
struct {
- bool shouldReloadModsOnEnd
- string currentMod
- var currentButton
- int scrollOffset = 0
-
- array<modStruct> modsArrayFiltered
-
+ array<panelContent> mods
var menu
+ array<var> panels
+ int scrollOffset = 0
+ array<string> enabledMods
+ var currentButton
+ string searchTerm
+ modData& lastMod
} file
+const int PANELS_LEN = 15
+const string[3] CORE_MODS = ["Northstar.Client", "Northstar.Coop", "Northstar.CustomServers"] // Shows a warning if you try to disable these
+
void function AddNorthstarModMenu()
{
AddMenu( "ModListMenu", $"resource/ui/menus/modlist.menu", InitModMenu )
@@ -54,11 +65,46 @@ void function AdvanceToModListMenu( var button )
void function InitModMenu()
{
file.menu = GetMenu( "ModListMenu" )
-
- AddMouseMovementCaptureHandler( file.menu, UpdateMouseDeltaBuffer )
+ file.panels = GetElementsByClassname( file.menu, "ModSelectorPanel" )
+
+ var rui = Hud_GetRui( Hud_GetChild( file.menu, "WarningLegendImage" ) )
+ RuiSetImage( rui, "basicImage", $"ui/menu/common/dialog_error" )
+ RuiSetFloat( Hud_GetRui( Hud_GetChild( file.menu, "ModEnabledBar" ) ), "basicImageAlpha", 0.8 )
+
+ // Mod buttons
+ foreach ( var panel in file.panels )
+ {
+ var button = Hud_GetChild( panel, "BtnMod" )
+ AddButtonEventHandler( button, UIE_GET_FOCUS, OnModButtonFocused )
+ AddButtonEventHandler( button, UIE_CLICK, OnModButtonPressed )
+
+ var rui = Hud_GetRui( Hud_GetChild( panel, "WarningImage" ) )
+ RuiSetImage( rui, "basicImage", $"ui/menu/common/dialog_error" )
+ }
+
+ AddMouseMovementCaptureHandler( Hud_GetChild(file.menu, "MouseMovementCapture"), UpdateMouseDeltaBuffer )
+
+ // UI Events
AddMenuEventHandler( file.menu, eUIEvent.MENU_OPEN, OnModMenuOpened )
AddMenuEventHandler( file.menu, eUIEvent.MENU_CLOSE, OnModMenuClosed )
+
+ // up / down buttons
+ AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModListUpArrow" ), UIE_CLICK, OnUpArrowSelected )
+ AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModListDownArrow" ), UIE_CLICK, OnDownArrowSelected )
+
+ // Mod info buttons
+ AddButtonEventHandler( Hud_GetChild( file.menu, "ModPageButton" ), UIE_CLICK, OnModLinkButtonPressed )
+
+ // Filter buttons
+ AddButtonEventHandler( Hud_GetChild( file.menu, "SwtBtnShowFilter"), UIE_CHANGE, OnFiltersChange )
+ AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModsSearch"), UIE_CHANGE, OnFiltersChange )
+ AddButtonEventHandler( Hud_GetChild( file.menu, "BtnListReverse"), UIE_CHANGE, OnFiltersChange )
+ AddButtonEventHandler( Hud_GetChild( file.menu, "BtnFiltersClear"), UIE_CLICK, OnBtnFiltersClear_Activate )
+
+ AddButtonEventHandler( Hud_GetChild( file.menu, "HideCVButton"), UIE_CHANGE, OnHideConVarsChange )
+
+ // Footers
AddMenuFooterOption( file.menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
AddMenuFooterOption(
file.menu,
@@ -74,37 +120,25 @@ void function InitModMenu()
"#AUTHENTICATION_AGREEMENT",
OnAuthenticationAgreementButtonPressed
)
-
- foreach ( var button in GetElementsByClassname( file.menu, "ModButton" ) )
- {
- AddButtonEventHandler( button, UIE_GET_FOCUS, OnModMenuButtonFocused )
- AddButtonEventHandler( button, UIE_CLICK, OnModMenuButtonPressed )
- }
-
- AddButtonEventHandler( Hud_GetChild( file.menu, "SwtBtnShowFilter"), UIE_CHANGE, OnFiltersChange )
- AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModsSearch"), UIE_CHANGE, OnFiltersChange )
-
- AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModListUpArrow"), UIE_CLICK, OnUpArrowSelected )
- AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModListDownArrow"), UIE_CLICK, OnDownArrowSelected )
-
- AddButtonEventHandler( Hud_GetChild( file.menu, "BtnFiltersClear"), UIE_CLICK, OnBtnFiltersClear_Activate )
-
+
// Nuke weird rui on filter switch
RuiSetString( Hud_GetRui( Hud_GetChild( file.menu, "SwtBtnShowFilter")), "buttonText", "")
+ RuiSetString( Hud_GetRui( Hud_GetChild( file.menu, "HideCVButton")), "buttonText", "")
+ RuiSetString( Hud_GetRui( Hud_GetChild( file.menu, "BtnListReverse")), "buttonText", "")
}
+// EVENTS
+
void function OnModMenuOpened()
{
- file.shouldReloadModsOnEnd = false
- file.scrollOffset = 0
-
- RegisterButtonPressedCallback(MOUSE_WHEEL_UP , OnScrollUp)
- RegisterButtonPressedCallback(MOUSE_WHEEL_DOWN , OnScrollDown)
+ file.enabledMods = GetEnabledModsArray() // used to check if mods should be reloaded
- Hud_SetText( Hud_GetChild( file.menu, "Title" ), "#MENU_TITLE_MODS" )
+ UpdateList()
+ UpdateListSliderHeight()
+ UpdateListSliderPosition()
-
- OnFiltersChange(0)
+ RegisterButtonPressedCallback(MOUSE_WHEEL_UP , OnScrollUp)
+ RegisterButtonPressedCallback(MOUSE_WHEEL_DOWN , OnScrollDown)
}
void function OnModMenuClosed()
@@ -115,120 +149,123 @@ void function OnModMenuClosed()
DeregisterButtonPressedCallback(MOUSE_WHEEL_DOWN , OnScrollDown)
}
catch ( ex ) {}
-
- if ( file.shouldReloadModsOnEnd )
- ReloadMods()
-}
-
-void function OnFiltersChange( var n )
-{
- file.scrollOffset = 0
-
- HideAllButtons()
-
- RefreshModsArray()
-
- UpdateList()
-
- UpdateListSliderHeight()
-}
-void function RefreshModsArray()
-{
- string searchTerm = Hud_GetUTF8Text( Hud_GetChild( file.menu, "BtnModsSearch" ) ).tolower()
-
- file.modsArrayFiltered.clear()
-
-
- bool useSearch = searchTerm != ""
-
-
- array<string> modNames = NSGetModNames()
- int modCount = modNames.len()
-
- foreach ( int index_, mod in modNames ) {
- modStruct tempMod
- tempMod.modIndex = index_
- tempMod.modName = mod
-
- int filter = GetConVarInt( "filter_mods" )
- bool enabled = NSIsModEnabled( tempMod.modName )
-
- bool containsTerm = tempMod.modName.tolower().find(searchTerm) != null
-
- if ( filter == filterShow.ALL && (useSearch == true ? containsTerm : true ) )
- {
- file.modsArrayFiltered.append( tempMod )
- }
- else if ( filter == filterShow.ONLY_ENABLED && enabled && (useSearch == true ? containsTerm : true ))
- {
- file.modsArrayFiltered.append( tempMod )
- }
- else if ( filter == filterShow.ONLY_DISABLED && !enabled && (useSearch == true ? containsTerm : true ))
+ array<string> current = GetEnabledModsArray()
+ bool reload
+ foreach ( string mod in current )
+ {
+ if ( file.enabledMods.find(mod) == -1 )
{
- file.modsArrayFiltered.append( tempMod )
+ reload = true
+ break
}
}
+ if ( current.len() != file.enabledMods.len() || reload ) // Only reload if we have to
+ ReloadMods()
}
-void function HideAllButtons()
+void function OnModButtonFocused( var button )
{
- array<var> buttons = GetElementsByClassname( file.menu, "ModButton" )
-
- // disable all buttons, we'll enable the ones we need later
- foreach ( var button in buttons )
+ if( int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) > file.mods.len() )
+ return
+
+ file.currentButton = button
+ file.lastMod = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset - 1 ].mod
+ string modName = file.lastMod.name
+ var rui = Hud_GetRui( Hud_GetChild( file.menu, "LabelDetails" ) )
+
+ RuiSetGameTime( rui, "startTime", -99999.99 ) // make sure it skips the whole animation for showing this
+ RuiSetString( rui, "headerText", modName )
+ RuiSetString( rui, "messageText", FormatModDescription( modName ) )
+
+ // Add a button to open the link with if required
+ string link = NSGetModDownloadLinkByModName( modName )
+ var linkButton = Hud_GetChild( file.menu, "ModPageButton" )
+ if ( link.len() )
{
- Hud_SetEnabled( button, false )
- Hud_SetVisible( button, false )
+ Hud_SetEnabled( linkButton, true )
+ Hud_SetVisible( linkButton, true )
+ Hud_SetText( linkButton, link )
}
+ else
+ {
+ Hud_SetEnabled( linkButton, false )
+ Hud_SetVisible( linkButton, false )
+ }
+
+ SetControlBarColor( modName )
+
+ bool required = NSIsModRequiredOnClient( modName )
+ Hud_SetVisible( Hud_GetChild( file.menu, "WarningLegendLabel" ), required )
+ Hud_SetVisible( Hud_GetChild( file.menu, "WarningLegendImage" ), required )
}
-void function UpdateList()
+void function OnModButtonPressed( var button )
{
- array<var> buttons = GetElementsByClassname( file.menu, "ModButton" )
-
-
- int j = file.modsArrayFiltered.len() > 17 ? 17 : file.modsArrayFiltered.len()
-
- for ( int i = 0; i < j; i++ )
+ string modName = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset - 1 ].mod.name
+ if ( StaticFind( modName ) && NSIsModEnabled( modName ) )
+ CoreModToggleDialog( modName )
+ else
{
- Hud_SetEnabled( buttons[ i ], true )
- Hud_SetVisible( buttons[ i ], true )
-
- SetModMenuNameText( buttons[ i ] )
+ NSSetModEnabled( modName, !NSIsModEnabled( modName ) )
+ var panel = file.panels[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) - 1 ]
+ SetControlBoxColor( Hud_GetChild( panel, "ControlBox" ), modName )
+ SetControlBarColor( modName )
+ SetModEnabledHelperImageAsset( Hud_GetChild( panel, "EnabledImage" ), modName )
+ // RefreshMods()
+ UpdateListSliderPosition()
+ UpdateListSliderHeight()
}
}
-void function SetModMenuNameText( var button )
+void function OnReloadModsButtonPressed( var button )
{
- modStruct mod = file.modsArrayFiltered[ int ( Hud_GetScriptID( button ) ) + file.scrollOffset ]
+ ReloadMods()
+}
- // should be localisation at some point
- if ( NSIsModEnabled( mod.modName ) )
- SetButtonRuiText( button, mod.modName + " v" + NSGetModVersionByModName( mod.modName ) )
- else
- SetButtonRuiText( button, mod.modName + " (DISABLED)" )
+void function OnAuthenticationAgreementButtonPressed( var button )
+{
+ NorthstarMasterServerAuthDialog()
}
-void function OnModMenuButtonPressed( var button )
+void function OnModLinkButtonPressed( var button )
{
- string modName = file.modsArrayFiltered[ int ( Hud_GetScriptID( button ) ) + file.scrollOffset ].modName
- if ( ( modName == "Northstar.Client" || modName == "Northstar.Coop" || modName == "Northstar.CustomServers") && NSIsModEnabled( modName ) )
- {
- file.currentMod = modName
- file.currentButton = button
- CoreModToggleDialog( modName )
- }
- else
- {
- NSSetModEnabled( modName, !NSIsModEnabled( modName ) )
+ string modName = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) + file.scrollOffset - 1 ].mod.name
+ string link = NSGetModDownloadLinkByModName( modName )
+ if ( link.find("http://") != 0 && link.find("https://") != 0 )
+ link = "http://" + link // links without the http or https protocol get opened in the internal browser
+ LaunchExternalWebBrowser( link, WEBBROWSER_FLAG_FORCEEXTERNAL )
+}
- SetModMenuNameText( button )
+void function OnFiltersChange( var n )
+{
+ file.scrollOffset = 0
- file.shouldReloadModsOnEnd = true
- }
+ UpdateList()
+ UpdateListSliderHeight()
+ UpdateListSliderPosition()
}
+void function OnBtnFiltersClear_Activate( var button )
+{
+ Hud_SetText( Hud_GetChild( file.menu, "BtnModsSearch" ), "" )
+
+ SetConVarInt( "filter_mods", 0 )
+
+ OnFiltersChange( null )
+}
+
+void function OnHideConVarsChange( var n )
+{
+ string modName = file.lastMod.name
+ if ( modName == "" )
+ return
+ var rui = Hud_GetRui( Hud_GetChild( file.menu, "LabelDetails" ) )
+ RuiSetString( rui, "messageText", FormatModDescription( modName ) )
+}
+
+// LIST LOGIC
+
void function CoreModToggleDialog( string mod )
{
DialogData dialogData
@@ -247,46 +284,235 @@ void function CoreModToggleDialog( string mod )
void function DisableMod()
{
- NSSetModEnabled( file.currentMod, false )
+ string modName = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) + file.scrollOffset - 1 ].mod.name
+ NSSetModEnabled( modName, false )
- SetModMenuNameText( file.currentButton )
+ var panel = file.panels[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) - 1]
+ SetControlBoxColor( Hud_GetChild( panel, "ControlBox" ), modName )
+ SetControlBarColor( modName )
+ SetModEnabledHelperImageAsset( Hud_GetChild( panel, "EnabledImage" ), modName )
- file.shouldReloadModsOnEnd = true
+ RefreshMods()
}
-void function OnModMenuButtonFocused( var button )
+array<string> function GetEnabledModsArray()
{
- string modName = file.modsArrayFiltered[ int ( Hud_GetScriptID( button ) ) + file.scrollOffset ].modName
+ array<string> enabledMods
+ foreach ( string mod in NSGetModNames() )
+ {
+ if ( NSIsModEnabled( mod ) )
+ enabledMods.append( mod )
+ }
+ return enabledMods
+}
- var rui = Hud_GetRui( Hud_GetChild( file.menu, "LabelDetails" ) )
-
- RuiSetGameTime( rui, "startTime", -99999.99 ) // make sure it skips the whole animation for showing this
- RuiSetString( rui, "headerText", modName )
- RuiSetString( rui, "messageText", FormatModDescription( modName ) )
+void function HideAllPanels()
+{
+ foreach ( var panel in file.panels )
+ {
+ Hud_SetEnabled( panel, false )
+ Hud_SetVisible( panel, false )
+ }
+}
+
+void function UpdateList()
+{
+ HideAllPanels()
+ RefreshMods()
+ DisplayModPanels()
+}
+
+void function RefreshMods()
+{
+ array<string> modNames = NSGetModNames()
+ file.mods.clear()
+
+ bool reverse = GetConVarBool( "modlist_reverse" )
+
+ int lastLoadPriority = reverse ? NSGetModLoadPriority( modNames[ modNames.len() - 1 ] ) + 1 : -1
+ string searchTerm = Hud_GetUTF8Text( Hud_GetChild( file.menu, "BtnModsSearch" ) ).tolower()
+
+ for ( int i = reverse ? modNames.len() - 1 : 0;
+ reverse ? ( i >= 0 ) : ( i < modNames.len() );
+ i += ( reverse ? -1 : 1) )
+ {
+ string mod = modNames[i]
+
+ if ( searchTerm.len() && mod.tolower().find( searchTerm ) == null )
+ continue
+
+ bool enabled = NSIsModEnabled( mod )
+ bool required = NSIsModRequiredOnClient( mod )
+ switch ( GetConVarInt( "filter_mods" ) )
+ {
+ case filterShow.ONLY_ENABLED:
+ if ( !enabled )
+ continue
+ break
+ case filterShow.ONLY_DISABLED:
+ if ( enabled )
+ continue
+ break
+ case filterShow.ONLY_REQUIRED:
+ if ( !required )
+ continue
+ break
+ case filterShow.ONLY_NOT_REQUIRED:
+ if( required )
+ continue
+ break
+ }
+
+ int pr = NSGetModLoadPriority( mod )
+
+ if ( reverse ? pr < lastLoadPriority : pr > lastLoadPriority )
+ {
+ modData m
+ m.name = pr.tostring()
+
+ panelContent c
+ c.mod = m
+ c.isHeader = true
+ file.mods.append( c )
+ lastLoadPriority = pr
+ }
+
+ modData m
+ m.name = mod
+ m.version = NSGetModVersionByModName( mod )
+ m.link = NSGetModDownloadLinkByModName( mod )
+ m.loadPriority = NSGetModLoadPriority( mod )
+ m.enabled = enabled
+ m.conVars = NSGetModConvarsByModName( mod )
+
+ panelContent c
+ c.mod = m
+
+ file.mods.append( c )
+ }
+}
+
+void function DisplayModPanels()
+{
+ foreach ( int i, var panel in file.panels)
+ {
+ if ( i >= file.mods.len() || file.scrollOffset + i >= file.mods.len() ) // don't try to show more panels than needed
+ break
+
+ panelContent c = file.mods[ file.scrollOffset + i ]
+ modData mod = c.mod
+ var btn = Hud_GetChild( panel, "BtnMod" )
+ var headerLabel = Hud_GetChild( panel, "Header" )
+ var box = Hud_GetChild( panel, "ControlBox" )
+ var line = Hud_GetChild( panel, "BottomLine" )
+ var warning = Hud_GetChild( panel, "WarningImage" )
+ var enabledImage = Hud_GetChild( panel, "EnabledImage" )
+
+ if ( c.isHeader )
+ {
+ Hud_SetEnabled( btn, false )
+ Hud_SetVisible( btn, false )
+
+ Hud_SetText( headerLabel, "Load Priority: " + mod.name )
+ Hud_SetVisible( headerLabel, true )
+
+ Hud_SetVisible( box, false )
+ Hud_SetVisible( line, true )
+
+ Hud_SetVisible( warning, false )
+ Hud_SetVisible( enabledImage, false )
+ }
+ else
+ {
+ Hud_SetEnabled( btn, true )
+ Hud_SetVisible( btn, true )
+ Hud_SetText( btn, mod.name )
+
+ Hud_SetVisible( headerLabel, false )
+
+ SetControlBoxColor( box, mod.name )
+ Hud_SetVisible( box, true )
+ Hud_SetVisible( line, false )
+
+ Hud_SetVisible( warning, NSIsModRequiredOnClient( c.mod.name ) )
+
+ SetModEnabledHelperImageAsset( enabledImage, c.mod.name )
+ }
+ Hud_SetVisible( panel, true )
+ }
+}
+
+void function SetModEnabledHelperImageAsset( var panel, string modName )
+{
+ if( NSIsModEnabled( modName ) )
+ RuiSetImage( Hud_GetRui( panel ), "basicImage", $"rui/menu/common/merit_state_success" )
+ else
+ RuiSetImage( Hud_GetRui( panel ), "basicImage", $"rui/menu/common/merit_state_failure" )
+ RuiSetFloat3(Hud_GetRui( panel ), "basicImageColor", GetControlColorForMod( modName ) )
+ Hud_SetVisible( panel, true )
+}
+
+void function SetControlBoxColor( var box, string modName )
+{
+ var rui = Hud_GetRui( box )
+ // if ( NSIsModEnabled( modName ) )
+ // RuiSetFloat3(rui, "basicImageColor", <0,1,0>)
+ // else
+ // RuiSetFloat3(rui, "basicImageColor", <1,0,0>)
+ RuiSetFloat3(rui, "basicImageColor", GetControlColorForMod( modName ) )
+}
+
+void function SetControlBarColor( string modName )
+{
+ var bar_element = Hud_GetChild( file.menu, "ModEnabledBar" )
+ var bar = Hud_GetRui( bar_element )
+ // if ( NSIsModEnabled( modName ) )
+ // RuiSetFloat3(bar, "basicImageColor", <0,1,0>)
+ // else
+ // RuiSetFloat3(bar, "basicImageColor", <1,0,0>)
+ RuiSetFloat3(bar, "basicImageColor", GetControlColorForMod( modName ) )
+ Hud_SetVisible( bar_element, true )
+}
+
+vector function GetControlColorForMod( string modName )
+{
+ if ( NSIsModEnabled( modName ) )
+ switch ( GetConVarInt( "colorblind_mode" ) )
+ {
+ case 1:
+ case 2:
+ case 3:
+ default:
+ return <0,1,0>
+ }
+ else
+ switch ( GetConVarInt( "colorblind_mode" ) )
+ {
+ case 1:
+ case 2:
+ return <0.29,0,0.57>
+ case 3:
+ default:
+ return <1,0,0>
+ }
+ unreachable
}
string function FormatModDescription( string modName )
{
string ret
// version
- ret += format( "Version %s\n", NSGetModVersionByModName( modName ) )
-
- // download link
- string modLink = NSGetModDownloadLinkByModName( modName )
- if ( modLink.len() != 0 )
- ret += format( "Download link: \"%s\"\n", modLink )
-
+ ret += format( "Version %s\n", NSGetModVersionByModName( modName ) )
+
// load priority
ret += format( "Load Priority: %i\n", NSGetModLoadPriority( modName ) )
-
- // todo: add ClientRequired here
-
+
// convars
array<string> modCvars = NSGetModConvarsByModName( modName )
- if ( modCvars.len() != 0 )
+ if ( modCvars.len() != 0 && GetConVarBool( "modlist_show_convars" ) )
{
ret += "ConVars: "
-
+
for ( int i = 0; i < modCvars.len(); i++ )
{
if ( i != modCvars.len() - 1 )
@@ -294,76 +520,34 @@ string function FormatModDescription( string modName )
else
ret += format( "\"%s\"", modCvars[ i ] )
}
-
+
ret += "\n"
}
-
+
// description
ret += format( "\n%s\n", NSGetModDescriptionByModName( modName ) )
-
- return ret
-}
-
-void function OnReloadModsButtonPressed( var button )
-{
- ReloadMods()
-}
-
-void function ReloadMods()
-{
- NSReloadMods()
- ClientCommand( "reload_localization" )
- ClientCommand( "loadPlaylists" )
-
- bool svCheatsOriginal = GetConVarBool( "sv_cheats" )
- SetConVarBool( "sv_cheats", true )
- ClientCommand( "weapon_reparse" ) // weapon_reparse only works if a server is running and sv_cheats is 1, gotta figure this out eventually
- SetConVarBool( "sv_cheats", svCheatsOriginal )
-
- // note: the logic for this seems really odd, unsure why it doesn't seem to update, since the same code seems to get run irregardless of whether we've read weapon data before
- ClientCommand( "uiscript_reset" )
-}
-void function OnAuthenticationAgreementButtonPressed( var button )
-{
- NorthstarMasterServerAuthDialog()
+ return ret
}
+////////////
+// SLIDER
+////////////
-void function OnBtnFiltersClear_Activate( var button )
-{
- Hud_SetText( Hud_GetChild( file.menu, "BtnModsSearch" ), "" )
-
- SetConVarInt( "filter_mods", 0 )
-
- OnFiltersChange(0)
-}
-
-//////////////////////////////
-// Slider
-//////////////////////////////
void function UpdateMouseDeltaBuffer(int x, int y)
{
- mouseDeltaBuffer.deltaX += x
- mouseDeltaBuffer.deltaY += y
+ mouseDeltaBuffer.deltaX = x
+ mouseDeltaBuffer.deltaY = y
+ UpdateListSliderHeight()
SliderBarUpdate()
}
-void function FlushMouseDeltaBuffer()
-{
- mouseDeltaBuffer.deltaX = 0
- mouseDeltaBuffer.deltaY = 0
-}
-
void function SliderBarUpdate()
{
- if ( file.modsArrayFiltered.len() <= 17 )
- {
- FlushMouseDeltaBuffer()
+ if ( file.mods.len() <= 15 )
return
- }
var sliderButton = Hud_GetChild( file.menu , "BtnModListSlider" )
var sliderPanel = Hud_GetChild( file.menu , "BtnModListSliderPanel" )
@@ -376,12 +560,11 @@ void function SliderBarUpdate()
float maxYPos = minYPos - (maxHeight - Hud_GetHeight( sliderPanel ))
float useableSpace = (maxHeight - Hud_GetHeight( sliderPanel ))
- float jump = minYPos - (useableSpace / ( float( file.modsArrayFiltered.len())))
+ float jump = minYPos - (useableSpace / ( float( file.mods.len())))
// got local from official respaw scripts, without untyped throws an error
local pos = Hud_GetPos(sliderButton)[1]
local newPos = pos - mouseDeltaBuffer.deltaY
- FlushMouseDeltaBuffer()
if ( newPos < maxYPos ) newPos = maxYPos
if ( newPos > minYPos ) newPos = minYPos
@@ -390,22 +573,42 @@ void function SliderBarUpdate()
Hud_SetPos( sliderPanel , 2, newPos )
Hud_SetPos( movementCapture , 2, newPos )
- file.scrollOffset = -int( ( (newPos - minYPos) / useableSpace ) * ( file.modsArrayFiltered.len() - BUTTONS_PER_PAGE) )
+ file.scrollOffset = -int( ( (newPos - minYPos) / useableSpace ) * ( file.mods.len() - PANELS_LEN) )
UpdateList()
}
+void function UpdateListSliderPosition()
+{
+ var sliderButton = Hud_GetChild( file.menu , "BtnModListSlider" )
+ var sliderPanel = Hud_GetChild( file.menu , "BtnModListSliderPanel" )
+ var movementCapture = Hud_GetChild( file.menu , "MouseMovementCapture" )
+
+ float mods = float ( file.mods.len() )
+
+ float minYPos = -40.0 * (GetScreenSize()[1] / 1080.0)
+ float useableSpace = (604.0 * (GetScreenSize()[1] / 1080.0) - Hud_GetHeight( sliderPanel ))
+
+ float jump = minYPos - (useableSpace / ( mods - float( PANELS_LEN ) ) * file.scrollOffset)
+
+ if ( jump > minYPos ) jump = minYPos
+
+ Hud_SetPos( sliderButton , 2, jump )
+ Hud_SetPos( sliderPanel , 2, jump )
+ Hud_SetPos( movementCapture , 2, jump )
+}
+
void function UpdateListSliderHeight()
{
var sliderButton = Hud_GetChild( file.menu , "BtnModListSlider" )
var sliderPanel = Hud_GetChild( file.menu , "BtnModListSliderPanel" )
var movementCapture = Hud_GetChild( file.menu , "MouseMovementCapture" )
-
- float mods = float ( file.modsArrayFiltered.len() )
+
+ float mods = float ( file.mods.len() )
float maxHeight = 604.0 * (GetScreenSize()[1] / 1080.0)
float minHeight = 80.0 * (GetScreenSize()[1] / 1080.0)
- float height = maxHeight * ( float( BUTTONS_PER_PAGE ) / mods )
+ float height = maxHeight * ( float( PANELS_LEN ) / mods )
if ( height > maxHeight ) height = maxHeight
if ( height < minHeight ) height = minHeight
@@ -415,68 +618,75 @@ void function UpdateListSliderHeight()
Hud_SetHeight( movementCapture , height )
}
-
-void function UpdateListSliderPosition()
+void function OnScrollDown( var button )
{
- var sliderButton = Hud_GetChild( file.menu , "BtnModListSlider" )
- var sliderPanel = Hud_GetChild( file.menu , "BtnModListSliderPanel" )
- var movementCapture = Hud_GetChild( file.menu , "MouseMovementCapture" )
-
- float mods = float ( file.modsArrayFiltered.len() )
-
- float minYPos = -40.0 * (GetScreenSize()[1] / 1080.0)
- float useableSpace = (604.0 * (GetScreenSize()[1] / 1080.0) - Hud_GetHeight( sliderPanel ))
-
- float jump = minYPos - (useableSpace / ( mods - float( BUTTONS_PER_PAGE ) ) * file.scrollOffset)
-
- //jump = jump * (GetScreenSize()[1] / 1080.0)
-
- if ( jump > minYPos ) jump = minYPos
+ if ( file.mods.len() <= PANELS_LEN ) return
+ file.scrollOffset += 5
+ if (file.scrollOffset + PANELS_LEN > file.mods.len())
+ file.scrollOffset = file.mods.len() - PANELS_LEN
+ Hud_SetFocused( Hud_GetChild( file.menu, "BtnModListSlider" ) )
+ ValidateScrollOffset()
+}
- Hud_SetPos( sliderButton , 2, jump )
- Hud_SetPos( sliderPanel , 2, jump )
- Hud_SetPos( movementCapture , 2, jump )
+void function OnScrollUp( var button )
+{
+ file.scrollOffset -= 5
+ if (file.scrollOffset < 0)
+ file.scrollOffset = 0
+ Hud_SetFocused( Hud_GetChild( file.menu, "BtnModListSlider" ) )
+ ValidateScrollOffset()
}
void function OnDownArrowSelected( var button )
{
- if ( file.modsArrayFiltered.len() <= BUTTONS_PER_PAGE ) return
+ if ( file.mods.len() <= PANELS_LEN ) return
file.scrollOffset += 1
- if (file.scrollOffset + BUTTONS_PER_PAGE > file.modsArrayFiltered.len()) {
- file.scrollOffset = file.modsArrayFiltered.len() - BUTTONS_PER_PAGE
- }
- UpdateList()
- UpdateListSliderPosition()
+ if (file.scrollOffset + PANELS_LEN > file.mods.len())
+ file.scrollOffset = file.mods.len() - PANELS_LEN
+ ValidateScrollOffset()
}
-
void function OnUpArrowSelected( var button )
{
file.scrollOffset -= 1
- if (file.scrollOffset < 0) {
+ if (file.scrollOffset < 0)
file.scrollOffset = 0
- }
- UpdateList()
- UpdateListSliderPosition()
+ ValidateScrollOffset()
}
-void function OnScrollDown( var button )
+void function ValidateScrollOffset()
{
- if ( file.modsArrayFiltered.len() <= BUTTONS_PER_PAGE ) return
- file.scrollOffset += 5
- if (file.scrollOffset + BUTTONS_PER_PAGE > file.modsArrayFiltered.len()) {
- file.scrollOffset = file.modsArrayFiltered.len() - BUTTONS_PER_PAGE
- }
- UpdateList()
+ RefreshMods()
+ if( file.scrollOffset + 15 > file.mods.len() )
+ file.scrollOffset = file.mods.len() - 15
+ if( file.scrollOffset < 0 )
+ file.scrollOffset = 0
+ HideAllPanels()
+ DisplayModPanels()
+ UpdateListSliderHeight()
UpdateListSliderPosition()
}
-void function OnScrollUp( var button )
+// Static arrays don't have the .find method for some reason
+bool function StaticFind( string mod )
{
- file.scrollOffset -= 5
- if (file.scrollOffset < 0) {
- file.scrollOffset = 0
- }
- UpdateList()
- UpdateListSliderPosition()
+ foreach( string smod in CORE_MODS )
+ if ( mod == smod )
+ return true
+ return false
+}
+
+void function ReloadMods()
+{
+ NSReloadMods()
+ ClientCommand( "reload_localization" )
+ ClientCommand( "loadPlaylists" )
+
+ bool svCheatsOriginal = GetConVarBool( "sv_cheats" )
+ SetConVarBool( "sv_cheats", true )
+ ClientCommand( "weapon_reparse" ) // weapon_reparse only works if a server is running and sv_cheats is 1, gotta figure this out eventually
+ SetConVarBool( "sv_cheats", svCheatsOriginal )
+
+ // note: the logic for this seems really odd, unsure why it doesn't seem to update, since the same code seems to get run irregardless of whether we've read weapon data before
+ ClientCommand( "uiscript_reset" )
} \ No newline at end of file
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut
index eb068374..cdeb8b3e 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut
@@ -4,6 +4,9 @@ untyped
global function AddNorthstarServerBrowserMenu
global function ThreadedAuthAndConnectToServer
+global function AddConnectToServerCallback
+global function RemoveConnectToServerCallback
+global function TriggerConnectToServerCallbacks
// Stop peeking
@@ -38,7 +41,7 @@ enum sortingBy
PLAYERS,
MAP,
GAMEMODE,
- LATENCY
+ REGION
}
// Column sort direction, only one of these can be aplied at once
@@ -48,8 +51,8 @@ struct {
bool serverPlayers = true
bool serverMap = true
bool serverGamemode = true
- bool serverLatency = true
- // 0 = none; 1 = default; 2 = name; 3 = players; 4 = map; 5 = gamemode; 6 = latency
+ bool serverRegion = true
+ // 0 = none; 1 = default; 2 = name; 3 = players; 4 = map; 5 = gamemode; 6 = region
int sortingBy = 1
} filterDirection
@@ -61,13 +64,12 @@ struct serverStruct {
int serverPlayersMax
string serverMap
string serverGamemode
- int serverLatency
+ string serverRegion
}
struct {
// UI state vars
var menu
- int lastSelectedServer = 999
int focusedServerIndex = 0
int scrollOffset = 0
bool serverListRequestFailed = false
@@ -79,6 +81,10 @@ struct {
// filtered array of servers
array<serverStruct> serversArrayFiltered
+
+ array<ServerInfo> filteredServers
+ ServerInfo& focusedServer
+ ServerInfo& lastSelectedServer
// UI references
array<var> serverButtons
@@ -87,7 +93,9 @@ struct {
array<var> serversProtected
array<var> serversMap
array<var> serversGamemode
- array<var> serversLatency
+ array<var> serversRegion
+
+ array< void functionref( ServerInfo ) > connectCallbacks
} file
@@ -142,7 +150,7 @@ void function InitServerBrowserMenu()
{
file.menu = GetMenu( "ServerBrowserMenu" )
- AddMouseMovementCaptureHandler( file.menu, UpdateMouseDeltaBuffer )
+ AddMouseMovementCaptureHandler( Hud_GetChild(file.menu, "MouseMovementCapture"), UpdateMouseDeltaBuffer )
// Get menu stuff
file.serverButtons = GetElementsByClassname( file.menu, "ServerButton" )
@@ -151,7 +159,7 @@ void function InitServerBrowserMenu()
file.serversProtected = GetElementsByClassname( file.menu, "ServerLock" )
file.serversMap = GetElementsByClassname( file.menu, "ServerMap" )
file.serversGamemode = GetElementsByClassname( file.menu, "ServerGamemode" )
- file.serversLatency = GetElementsByClassname( file.menu, "ServerLatency" )
+ file.serversRegion = GetElementsByClassname( file.menu, "Serverregion" )
filterArguments.filterMaps = [ "SWITCH_ANY" ]
Hud_DialogList_AddListItem( Hud_GetChild( file.menu, "SwtBtnSelectMap" ), "SWITCH_ANY", "0" )
@@ -194,7 +202,7 @@ void function InitServerBrowserMenu()
AddButtonEventHandler( Hud_GetChild( file.menu, "BtnServerPlayersTab"), UIE_CLICK, SortServerListByPlayers_Activate )
AddButtonEventHandler( Hud_GetChild( file.menu, "BtnServerMapTab"), UIE_CLICK, SortServerListByMap_Activate )
AddButtonEventHandler( Hud_GetChild( file.menu, "BtnServerGamemodeTab"), UIE_CLICK, SortServerListByGamemode_Activate )
- AddButtonEventHandler( Hud_GetChild( file.menu, "BtnServerLatencyTab"), UIE_CLICK, SortServerListByLatency_Activate )
+ AddButtonEventHandler( Hud_GetChild( file.menu, "BtnServerRegionTab"), UIE_CLICK, SortServerListByRegion_Activate )
AddButtonEventHandler( Hud_GetChild( file.menu, "SwtBtnSelectMap"), UIE_CHANGE, FilterAndUpdateList )
@@ -218,8 +226,6 @@ void function InitServerBrowserMenu()
Hud_SetText( Hud_GetChild( file.menu, "BtnServerDescription"), "" )
Hud_SetText( Hud_GetChild( file.menu, "BtnServerMods"), "" )
- // Unfinished features
- Hud_SetLocked( Hud_GetChild( file.menu, "BtnServerLatencyTab" ), true )
// Rui is a pain
RuiSetString( Hud_GetRui( Hud_GetChild( file.menu, "SwtBtnHideFull") ), "buttonText", "" )
@@ -255,7 +261,7 @@ void function FlushMouseDeltaBuffer()
void function SliderBarUpdate()
{
- if ( file.serversArrayFiltered.len() <= BUTTONS_PER_PAGE )
+ if ( file.filteredServers.len() <= BUTTONS_PER_PAGE )
{
FlushMouseDeltaBuffer()
return
@@ -272,7 +278,7 @@ void function SliderBarUpdate()
float maxYPos = minYPos - ( maxHeight - Hud_GetHeight( sliderPanel ) )
float useableSpace = ( maxHeight - Hud_GetHeight( sliderPanel ) )
- float jump = minYPos - ( useableSpace / ( float( file.serversArrayFiltered.len() ) ) )
+ float jump = minYPos - ( useableSpace / ( float( file.filteredServers.len() ) ) )
// got local from official respaw scripts, without untyped throws an error
local pos = Hud_GetPos( sliderButton )[1]
@@ -286,7 +292,7 @@ void function SliderBarUpdate()
Hud_SetPos( sliderPanel , 2, newPos )
Hud_SetPos( movementCapture , 2, newPos )
- file.scrollOffset = -int( ( ( newPos - minYPos ) / useableSpace ) * ( file.serversArrayFiltered.len() - BUTTONS_PER_PAGE ) )
+ file.scrollOffset = -int( ( ( newPos - minYPos ) / useableSpace ) * ( file.filteredServers.len() - BUTTONS_PER_PAGE ) )
UpdateShownPage()
}
@@ -330,13 +336,13 @@ void function UpdateListSliderPosition( int servers )
void function OnScrollDown( var button )
{
- if (file.serversArrayFiltered.len() <= BUTTONS_PER_PAGE) return
+ if (file.filteredServers.len() <= BUTTONS_PER_PAGE) return
file.scrollOffset += 5
- if (file.scrollOffset + BUTTONS_PER_PAGE > file.serversArrayFiltered.len()) {
- file.scrollOffset = file.serversArrayFiltered.len() - BUTTONS_PER_PAGE
+ if (file.scrollOffset + BUTTONS_PER_PAGE > file.filteredServers.len()) {
+ file.scrollOffset = file.filteredServers.len() - BUTTONS_PER_PAGE
}
UpdateShownPage()
- UpdateListSliderPosition( file.serversArrayFiltered.len() )
+ UpdateListSliderPosition( file.filteredServers.len() )
}
void function OnScrollUp( var button )
@@ -346,7 +352,7 @@ void function OnScrollUp( var button )
file.scrollOffset = 0
}
UpdateShownPage()
- UpdateListSliderPosition( file.serversArrayFiltered.len() )
+ UpdateListSliderPosition( file.filteredServers.len() )
}
////////////////////////////
@@ -486,7 +492,7 @@ void function OnHitDummyTop( var button )
{
// only update if list position changed
UpdateShownPage()
- UpdateListSliderPosition( file.serversArrayFiltered.len() )
+ UpdateListSliderPosition( file.filteredServers.len() )
DisplayFocusedServerInfo( file.serverButtonFocusedID )
Hud_SetFocused( Hud_GetChild( file.menu, "BtnServer1" ) )
}
@@ -495,10 +501,10 @@ void function OnHitDummyTop( var button )
void function OnHitDummyBottom( var button )
{
file.scrollOffset += 1
- if ( file.scrollOffset + BUTTONS_PER_PAGE > file.serversArrayFiltered.len() )
+ if ( file.scrollOffset + BUTTONS_PER_PAGE > file.filteredServers.len() )
{
// was at bottom already
- file.scrollOffset = file.serversArrayFiltered.len() - BUTTONS_PER_PAGE
+ file.scrollOffset = file.filteredServers.len() - BUTTONS_PER_PAGE
Hud_SetFocused( Hud_GetChild( file.menu, "BtnServerSearch" ) )
HideServerInfo()
}
@@ -506,7 +512,7 @@ void function OnHitDummyBottom( var button )
{
// only update if list position changed
UpdateShownPage()
- UpdateListSliderPosition( file.serversArrayFiltered.len() )
+ UpdateListSliderPosition( file.filteredServers.len() )
DisplayFocusedServerInfo( file.serverButtonFocusedID )
Hud_SetFocused( Hud_GetChild( file.menu, "BtnServer15" ) )
}
@@ -520,15 +526,15 @@ void function OnHitDummyAfterFilterClear( var button )
void function OnDownArrowSelected( var button )
{
- if ( file.serversArrayFiltered.len() <= BUTTONS_PER_PAGE ) return
+ if ( file.filteredServers.len() <= BUTTONS_PER_PAGE ) return
file.scrollOffset += 1
- if ( file.scrollOffset + BUTTONS_PER_PAGE > file.serversArrayFiltered.len() )
+ if ( file.scrollOffset + BUTTONS_PER_PAGE > file.filteredServers.len() )
{
- file.scrollOffset = file.serversArrayFiltered.len() - BUTTONS_PER_PAGE
+ file.scrollOffset = file.filteredServers.len() - BUTTONS_PER_PAGE
}
UpdateShownPage()
- UpdateListSliderPosition( file.serversArrayFiltered.len() )
+ UpdateListSliderPosition( file.filteredServers.len() )
}
@@ -541,7 +547,7 @@ void function OnUpArrowSelected( var button )
}
UpdateShownPage()
- UpdateListSliderPosition( file.serversArrayFiltered.len() )
+ UpdateListSliderPosition( file.filteredServers.len() )
}
////////////////////////
@@ -644,7 +650,7 @@ void function FilterAndUpdateList( var n )
filterArguments.hideProtected = GetConVarBool( "filter_hide_protected" )
file.scrollOffset = 0
- UpdateListSliderPosition( file.serversArrayFiltered.len() )
+ UpdateListSliderPosition( file.filteredServers.len() )
HideServerInfo()
FilterServerList()
@@ -675,9 +681,9 @@ void function FilterAndUpdateList( var n )
filterDirection.serverGamemode = !filterDirection.serverGamemode
SortServerListByGamemode_Activate(0)
break
- case sortingBy.LATENCY:
- filterDirection.serverLatency = !filterDirection.serverLatency
- SortServerListByLatency_Activate(0)
+ case sortingBy.REGION:
+ filterDirection.serverRegion = !filterDirection.serverRegion
+ SortServerListByRegion_Activate(0)
break
default:
printt( "How the f did you get here" )
@@ -715,7 +721,7 @@ void function WaitForServerListRequest()
Hud_SetText( file.playerCountLabels[ i ], "" )
Hud_SetText( file.serversMap[ i ], "" )
Hud_SetText( file.serversGamemode[ i ], "" )
- Hud_SetText( file.serversLatency[ i ], "" )
+ Hud_SetText( file.serversRegion[ i ], "" )
}
HideServerInfo()
@@ -743,49 +749,42 @@ void function WaitForServerListRequest()
void function FilterServerList()
{
- file.serversArrayFiltered.clear()
+ file.filteredServers.clear()
int totalPlayers = 0
- for ( int i = 0; i < NSGetServerCount(); i++ )
- {
- serverStruct tempServer
- tempServer.serverIndex = i
- tempServer.serverProtected = NSServerRequiresPassword( i )
- tempServer.serverName = NSGetServerName( i )
- tempServer.serverPlayers = NSGetServerPlayerCount( i )
- tempServer.serverPlayersMax = NSGetServerMaxPlayerCount( i )
- tempServer.serverMap = NSGetServerMap( i )
- tempServer.serverGamemode = GetGameModeDisplayName( NSGetServerPlaylist ( i ) )
-
- totalPlayers += tempServer.serverPlayers
+ array<ServerInfo> servers = NSGetGameServers()
+ foreach ( ServerInfo server in servers )
+ {
+ totalPlayers += server.playerCount
// Filters
- if ( filterArguments.hideEmpty && tempServer.serverPlayers == 0 )
+ if ( filterArguments.hideEmpty && server.playerCount == 0 )
continue;
- if ( filterArguments.hideFull && tempServer.serverPlayers == tempServer.serverPlayersMax )
+ if ( filterArguments.hideFull && server.playerCount == server.maxPlayerCount )
continue;
- if ( filterArguments.hideProtected && tempServer.serverProtected )
+ if ( filterArguments.hideProtected && server.requiresPassword )
continue;
- if ( filterArguments.filterMap != "SWITCH_ANY" && filterArguments.filterMap != tempServer.serverMap )
+ if ( filterArguments.filterMap != "SWITCH_ANY" && filterArguments.filterMap != server.map )
continue;
- if ( filterArguments.filterGamemode != "SWITCH_ANY" && filterArguments.filterGamemode != tempServer.serverGamemode )
+ if ( filterArguments.filterGamemode != "SWITCH_ANY" && filterArguments.filterGamemode != GetGameModeDisplayName(server.playlist) )
continue;
-
+
// Search
if ( filterArguments.useSearch )
{
array<string> sName
- sName.append( tempServer.serverName.tolower() )
- sName.append( Localize( GetMapDisplayName( tempServer.serverMap ) ).tolower() )
- sName.append( tempServer.serverMap.tolower() )
- sName.append( tempServer.serverGamemode.tolower() )
- sName.append( Localize( tempServer.serverGamemode ).tolower() )
- sName.append( NSGetServerDescription( i ).tolower() )
+ sName.append( server.name.tolower() )
+ sName.append( Localize( GetMapDisplayName( server.map ) ).tolower() )
+ sName.append( server.map.tolower() )
+ sName.append( server.playlist.tolower() )
+ sName.append( Localize( server.playlist ).tolower() )
+ sName.append( server.description.tolower() )
+ sName.append( server.region.tolower() )
string sTerm = filterArguments.searchTerm.tolower()
@@ -799,9 +798,8 @@ void function FilterServerList()
if ( !found )
continue;
}
-
- // Server fits our requirements, add it to the list
- file.serversArrayFiltered.append( tempServer )
+
+ file.filteredServers.append( server )
}
// Update player and server count
@@ -821,25 +819,25 @@ void function UpdateShownPage()
Hud_SetText( file.playerCountLabels[ i ], "" )
Hud_SetText( file.serversMap[ i ], "" )
Hud_SetText( file.serversGamemode[ i ], "" )
- Hud_SetText( file.serversLatency[ i ], "" )
+ Hud_SetText( file.serversRegion[ i ], "" )
}
- int j = file.serversArrayFiltered.len() > BUTTONS_PER_PAGE ? BUTTONS_PER_PAGE : file.serversArrayFiltered.len()
+ int j = file.filteredServers.len() > BUTTONS_PER_PAGE ? BUTTONS_PER_PAGE : file.filteredServers.len()
for ( int i = 0; i < j; i++ )
{
-
int buttonIndex = file.scrollOffset + i
- int serverIndex = file.serversArrayFiltered[ buttonIndex ].serverIndex
+ ServerInfo server = file.filteredServers[ buttonIndex ]
Hud_SetEnabled( file.serverButtons[ i ], true )
Hud_SetVisible( file.serverButtons[ i ], true )
- Hud_SetVisible( file.serversProtected[ i ], file.serversArrayFiltered[ buttonIndex ].serverProtected )
- Hud_SetText( file.serversName[ i ], file.serversArrayFiltered[ buttonIndex ].serverName )
- Hud_SetText( file.playerCountLabels[ i ], format( "%i/%i", file.serversArrayFiltered[ buttonIndex ].serverPlayers, file.serversArrayFiltered[ buttonIndex ].serverPlayersMax ) )
- Hud_SetText( file.serversMap[ i ], GetMapDisplayName( file.serversArrayFiltered[ buttonIndex ].serverMap ) )
- Hud_SetText( file.serversGamemode[ i ], file.serversArrayFiltered[ buttonIndex ].serverGamemode )
+ Hud_SetVisible( file.serversProtected[ i ], server.requiresPassword )
+ Hud_SetText( file.serversName[ i ], server.name )
+ Hud_SetText( file.playerCountLabels[ i ], format( "%i/%i", server.playerCount, server.maxPlayerCount ) )
+ Hud_SetText( file.serversMap[ i ], GetMapDisplayName( server.map ) )
+ Hud_SetText( file.serversGamemode[ i ], GetGameModeDisplayName( server.playlist ) )
+ Hud_SetText( file.serversRegion[ i ], server.region )
}
@@ -849,7 +847,7 @@ void function UpdateShownPage()
Hud_SetVisible( file.serverButtons[ 0 ], true )
Hud_SetText( file.serversName[ 0 ], "#NS_SERVERBROWSER_NOSERVERS" )
}
- UpdateListSliderHeight( float( file.serversArrayFiltered.len() ) )
+ UpdateListSliderHeight( float( file.filteredServers.len() ) )
}
void function OnServerButtonFocused( var button )
@@ -859,8 +857,9 @@ void function OnServerButtonFocused( var button )
int scriptID = int ( Hud_GetScriptID( button ) )
file.serverButtonFocusedID = scriptID
- if ( file.serversArrayFiltered.len() > 0 )
- file.focusedServerIndex = file.serversArrayFiltered[ file.scrollOffset + scriptID ].serverIndex
+ if ( file.filteredServers.len() > 0 )
+ // file.focusedServerIndex = file.filteredServers[ file.scrollOffset + scriptID ].serverIndex
+ file.focusedServer = file.filteredServers[ file.scrollOffset + scriptID ]
DisplayFocusedServerInfo( scriptID )
}
@@ -881,13 +880,12 @@ void function CheckDoubleClick( int scriptID, bool wasClickNav )
int serverIndex = file.scrollOffset + scriptID
bool sameServer = false
- if ( file.lastSelectedServer == serverIndex ) sameServer = true
-
+ if ( file.lastSelectedServer == file.filteredServers[ serverIndex ] ) sameServer = true
file.serverSelectedTimeLast = file.serverSelectedTime
file.serverSelectedTime = Time()
- file.lastSelectedServer = serverIndex
+ file.lastSelectedServer = file.filteredServers[ serverIndex ]
if ( wasClickNav && ( file.serverSelectedTime - file.serverSelectedTimeLast < DOUBLE_CLICK_TIME_MS ) && sameServer )
{
@@ -899,7 +897,7 @@ void function DisplayFocusedServerInfo( int scriptID )
{
if ( scriptID == 999 || scriptID == -1 || scriptID == 16 ) return
- if ( NSIsRequestingServerList() || NSGetServerCount() == 0 || file.serverListRequestFailed || file.serversArrayFiltered.len() == 0 )
+ if ( NSIsRequestingServerList() || NSGetServerCount() == 0 || file.serverListRequestFailed || file.filteredServers.len() == 0 )
return
var menu = GetMenu( "ServerBrowserMenu" )
@@ -907,6 +905,7 @@ void function DisplayFocusedServerInfo( int scriptID )
int serverIndex = file.scrollOffset + scriptID
if ( serverIndex < 0 ) serverIndex = 0
+ ServerInfo server = file.filteredServers[ serverIndex ]
Hud_SetVisible( Hud_GetChild( menu, "BtnServerDescription" ), true )
Hud_SetVisible( Hud_GetChild( menu, "BtnServerMods" ), true )
@@ -914,78 +913,153 @@ void function DisplayFocusedServerInfo( int scriptID )
// text panels
Hud_SetVisible( Hud_GetChild( menu, "LabelDescription" ), true )
Hud_SetVisible( Hud_GetChild( menu, "LabelMods" ), false )
- Hud_SetText( Hud_GetChild( menu, "LabelDescription" ), NSGetServerDescription( file.serversArrayFiltered[ serverIndex ].serverIndex ) + "\n\nRequired Mods:\n" + FillInServerModsLabel( file.serversArrayFiltered[ serverIndex ].serverIndex ) )
+ Hud_SetText( Hud_GetChild( menu, "LabelDescription" ), server.description + "\n\nRequired Mods:\n" + FillInServerModsLabel( server.requiredMods ) )
// map name/image/server name
- string map = file.serversArrayFiltered[ serverIndex ].serverMap
+ string map = server.map
Hud_SetVisible( Hud_GetChild( menu, "NextMapImage" ), true )
Hud_SetVisible( Hud_GetChild( menu, "NextMapBack" ), true )
RuiSetImage( Hud_GetRui( Hud_GetChild( menu, "NextMapImage" ) ), "basicImage", GetMapImageForMapName( map ) )
Hud_SetVisible( Hud_GetChild( menu, "NextMapName" ), true )
Hud_SetText( Hud_GetChild( menu, "NextMapName" ), GetMapDisplayName( map ) )
Hud_SetVisible( Hud_GetChild( menu, "ServerName" ), true )
- Hud_SetText( Hud_GetChild( menu, "ServerName" ), NSGetServerName( file.serversArrayFiltered[ serverIndex ].serverIndex ) )
+ Hud_SetText( Hud_GetChild( menu, "ServerName" ), server.name )
// mode name/image
- string mode = file.serversArrayFiltered[ serverIndex ].serverGamemode
+ string mode = server.playlist
Hud_SetVisible( Hud_GetChild( menu, "NextModeIcon" ), true )
RuiSetImage( Hud_GetRui( Hud_GetChild( menu, "NextModeIcon" ) ), "basicImage", GetPlaylistThumbnailImage( mode ) )
Hud_SetVisible( Hud_GetChild( menu, "NextGameModeName" ), true )
if ( mode.len() != 0 )
- Hud_SetText( Hud_GetChild( menu, "NextGameModeName" ), mode )
+ Hud_SetText( Hud_GetChild( menu, "NextGameModeName" ), GetGameModeDisplayName( mode ) )
else
Hud_SetText( Hud_GetChild( menu, "NextGameModeName" ), "#NS_SERVERBROWSER_UNKNOWNMODE" )
}
-string function FillInServerModsLabel( int server )
+string function FillInServerModsLabel( array<RequiredModInfo> mods )
{
string ret
- for ( int i = 0; i < NSGetServerRequiredModsCount( server ); i++ )
+ foreach ( RequiredModInfo mod in mods )
{
- ret += " "
- ret += NSGetServerRequiredModName( server, i ) + " v" + NSGetServerRequiredModVersion( server, i ) + "\n"
+ ret += format( " %s v%s\n", mod.name, mod.version )
}
+
return ret
}
void function OnServerSelected( var button )
{
+ thread OnServerSelected_Threaded( button )
+}
+
+void function OnServerSelected_Threaded( var button )
+{
if ( NSIsRequestingServerList() || NSGetServerCount() == 0 || file.serverListRequestFailed )
return
- int serverIndex = file.focusedServerIndex
+ ServerInfo server = file.focusedServer
+ file.lastSelectedServer = server
+
+ // Count mods that have been successfully downloaded
+ bool autoDownloadAllowed = GetConVarBool( "allow_mod_auto_download" )
+ int downloadedMods = 0;
+
+ // Check out if there's any server-required mod that is not locally installed
+ array<string> modNames = NSGetModNames()
+ bool uninstalledModFound = false
+ foreach ( requiredModInfo in server.requiredMods )
+ {
+ // Tolerate core mods having different versions
+ if ( requiredModInfo.name.len() > 10 && requiredModInfo.name.slice(0, 10) == "Northstar." )
+ continue
- file.lastSelectedServer = serverIndex
+ if ( !modNames.contains( requiredModInfo.name ) )
+ {
+ print( format ( "\"%s\" was not found locally, triggering manifesto fetching.", requiredModInfo.name ) )
+ uninstalledModFound = true
+ break
+ } else if ( NSGetModVersionByModName( requiredModInfo.name ) != requiredModInfo.version ) {
+ print( format ( "\"%s\" was found locally but has version \"%s\" while server requires \"%s\", triggering manifesto fetching.", requiredModInfo.name, NSGetModVersionByModName( requiredModInfo.name ), requiredModInfo.version ) )
+ uninstalledModFound = true
+ break
+ }
+ }
+
+ // If yes, we fetch the verified mods manifesto, to check whether uninstalled
+ // mods can be installed through auto-download
+ if ( uninstalledModFound && autoDownloadAllowed )
+ {
+ print("Auto-download is allowed, checking if missing mods can be installed automatically.")
+ FetchVerifiedModsManifesto()
+ }
- // check mods
- for ( int i = 0; i < NSGetServerRequiredModsCount( serverIndex ); i++ )
+ foreach ( RequiredModInfo mod in server.requiredMods )
{
- if ( !NSGetModNames().contains( NSGetServerRequiredModName( serverIndex, i ) ) )
+ // Tolerate core mods having different versions
+ if ( mod.name.len() > 10 && mod.name.slice(0, 10) == "Northstar." )
+ continue
+
+ if ( !NSGetModNames().contains( mod.name ) || NSGetModVersionByModName( mod.name ) != mod.version )
{
- DialogData dialogData
- dialogData.header = "#ERROR"
- dialogData.message = "Missing mod \"" + NSGetServerRequiredModName( serverIndex, i ) + "\" v" + NSGetServerRequiredModVersion( serverIndex, i )
- dialogData.image = $"ui/menu/common/dialog_error"
+ // Auto-download mod
+ if ( autoDownloadAllowed )
+ {
+ bool modIsVerified = NSIsModDownloadable( mod.name, mod.version )
- #if PC_PROG
- AddDialogButton( dialogData, "#DISMISS" )
+ // Display error message if mod is not verified
+ if ( !modIsVerified )
+ {
+ DialogData dialogData
+ dialogData.header = "#ERROR"
+ dialogData.message = Localize( "#MISSING_MOD", mod.name, mod.version )
+ dialogData.message += "\n" + Localize( "#MOD_NOT_VERIFIED" )
+ dialogData.image = $"ui/menu/common/dialog_error"
- AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
- #endif // PC_PROG
- AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" )
+ AddDialogButton( dialogData, "#DISMISS" )
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+ AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" )
+
+ OpenDialog( dialogData )
+ return
+ }
+ else
+ {
+ if ( DownloadMod( mod ) )
+ {
+ downloadedMods++
+ }
+ else
+ {
+ DisplayModDownloadErrorDialog( mod.name )
+ return
+ }
+ }
+ }
+
+ // Mod not found, display error message
+ else
+ {
+ DialogData dialogData
+ dialogData.header = "#ERROR"
+ dialogData.message = Localize( "#MISSING_MOD", mod.name, mod.version )
+ dialogData.image = $"ui/menu/common/dialog_error"
- OpenDialog( dialogData )
+ AddDialogButton( dialogData, "#DISMISS" )
+ AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
+ AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" )
- return
+ OpenDialog( dialogData )
+ return
+ }
}
else
{
// this uses semver https://semver.org
- array<string> serverModVersion = split( NSGetServerRequiredModVersion( serverIndex, i ), "." )
- array<string> clientModVersion = split( NSGetModVersionByModName( NSGetServerRequiredModName( serverIndex, i ) ), "." )
+ array<string> serverModVersion = split( mod.name, "." )
+ array<string> clientModVersion = split( NSGetModVersionByModName( mod.name ), "." )
bool semverFail = false
// if server has invalid semver don't bother checking
@@ -1003,7 +1077,7 @@ void function OnServerSelected( var button )
{
DialogData dialogData
dialogData.header = "#ERROR"
- dialogData.message = "Server has mod \"" + NSGetServerRequiredModName( serverIndex, i ) + "\" v" + NSGetServerRequiredModVersion( serverIndex, i ) + " while we have v" + NSGetModVersionByModName( NSGetServerRequiredModName( serverIndex, i ) )
+ dialogData.message = Localize( "#WRONG_MOD_VERSION", mod.name, mod.version, NSGetModVersionByModName( mod.name ) )
dialogData.image = $"ui/menu/common/dialog_error"
#if PC_PROG
@@ -1020,24 +1094,25 @@ void function OnServerSelected( var button )
}
}
- if ( NSServerRequiresPassword( serverIndex ) )
+ if ( server.requiresPassword )
{
OnCloseServerBrowserMenu()
AdvanceMenu( GetMenu( "ConnectWithPasswordMenu" ) )
}
else
- thread ThreadedAuthAndConnectToServer()
+ {
+ TriggerConnectToServerCallbacks()
+ thread ThreadedAuthAndConnectToServer( "", downloadedMods != 0 )
+ }
}
-void function ThreadedAuthAndConnectToServer( string password = "" )
+void function ThreadedAuthAndConnectToServer( string password = "", bool modsChanged = false )
{
if ( NSIsAuthenticatingWithServer() )
return
- print( "trying to authenticate with server " + NSGetServerName( file.lastSelectedServer ) + " with password " + password )
-
- NSTryAuthWithServer( file.lastSelectedServer, password )
+ NSTryAuthWithServer( file.lastSelectedServer.index, password )
ToggleConnectingHUD( true )
@@ -1057,34 +1132,40 @@ void function ThreadedAuthAndConnectToServer( string password = "" )
}
file.cancelConnection = false
- NSSetLoading( true )
- NSUpdateServerInfo(
- NSGetServerID( file.lastSelectedServer ),
- NSGetServerName( file.lastSelectedServer ),
- password,
- NSGetServerPlayerCount( file.lastSelectedServer ),
- NSGetServerMaxPlayerCount( file.lastSelectedServer ),
- NSGetServerMap( file.lastSelectedServer ),
- Localize( GetMapDisplayName( NSGetServerMap( file.lastSelectedServer ) ) ),
- NSGetServerPlaylist( file.lastSelectedServer ),
- Localize( GetPlaylistDisplayName( NSGetServerPlaylist( file.lastSelectedServer ) ) )
- )
if ( NSWasAuthSuccessful() )
{
- bool modsChanged
-
- array<string> requiredMods
- for ( int i = 0; i < NSGetServerRequiredModsCount( file.lastSelectedServer ); i++ )
- requiredMods.append( NSGetServerRequiredModName( file.lastSelectedServer, i ) )
+ // disable all RequiredOnClient mods that are not required by the server and are currently enabled
+ foreach ( string modName in NSGetModNames() )
+ {
+ if ( NSIsModRequiredOnClient( modName ) && NSIsModEnabled( modName ) )
+ {
+ // find the mod name in the list of server required mods
+ bool found = false
+ foreach ( RequiredModInfo mod in file.lastSelectedServer.requiredMods )
+ {
+ if (mod.name == modName)
+ {
+ found = true
+ break
+ }
+ }
+ // if we didnt find the mod name, disable the mod
+ if (!found)
+ {
+ modsChanged = true
+ NSSetModEnabled( modName, false )
+ }
+ }
+ }
- // unload mods we don't need, load necessary ones and reload mods before connecting
- foreach ( string mod in NSGetModNames() )
+ // enable all RequiredOnClient mods that are required by the server and are currently disabled
+ foreach ( RequiredModInfo mod in file.lastSelectedServer.requiredMods )
{
- if ( NSIsModRequiredOnClient( mod ) )
+ if ( NSIsModRequiredOnClient( mod.name ) && !NSIsModEnabled( mod.name ))
{
- modsChanged = modsChanged || NSIsModEnabled( mod ) != requiredMods.contains( mod )
- NSSetModEnabled( mod, requiredMods.contains( mod ) )
+ modsChanged = true
+ NSSetModEnabled( mod.name, true )
}
}
@@ -1096,9 +1177,11 @@ void function ThreadedAuthAndConnectToServer( string password = "" )
}
else
{
+ string reason = NSGetAuthFailReason()
+
DialogData dialogData
dialogData.header = "#ERROR"
- dialogData.message = "Authentication Failed"
+ dialogData.message = reason
dialogData.image = $"ui/menu/common/dialog_error"
#if PC_PROG
@@ -1115,7 +1198,7 @@ void function ThreadedAuthAndConnectToServer( string password = "" )
//////////////////////////////////////
// Shadow realm
//////////////////////////////////////
-int function ServerSortLogic ( serverStruct a, serverStruct b )
+int function ServerSortLogic ( ServerInfo a, ServerInfo b )
{
var aTemp
var bTemp
@@ -1126,45 +1209,45 @@ int function ServerSortLogic ( serverStruct a, serverStruct b )
switch ( filterDirection.sortingBy )
{
case sortingBy.DEFAULT:
- aTemp = a.serverPlayers
- bTemp = b.serverPlayers
+ aTemp = a.playerCount
+ bTemp = b.playerCount
// `1000` is assumed to always be higher than `serverPlayersMax`
- if (aTemp + 1 < a.serverPlayersMax)
+ if (aTemp + 1 < a.maxPlayerCount)
aTemp = aTemp+2000
- if (bTemp + 1 < b.serverPlayersMax)
+ if (bTemp + 1 < b.maxPlayerCount)
bTemp = bTemp+2000
- if (aTemp + 1 == a.serverPlayersMax)
+ if (aTemp + 1 == a.maxPlayerCount)
aTemp = aTemp+1000
- if (bTemp + 1 == b.serverPlayersMax)
+ if (bTemp + 1 == b.maxPlayerCount)
bTemp = bTemp+1000
direction = filterDirection.serverName
break;
case sortingBy.NAME:
- aTemp = a.serverName.tolower()
- bTemp = b.serverName.tolower()
+ aTemp = a.name.tolower()
+ bTemp = b.name.tolower()
direction = filterDirection.serverName
break;
case sortingBy.PLAYERS:
- aTemp = a.serverPlayers
- bTemp = b.serverPlayers
+ aTemp = a.playerCount
+ bTemp = b.playerCount
direction = filterDirection.serverPlayers
break;
case sortingBy.MAP:
- aTemp = Localize( a.serverMap ).tolower()
- bTemp = Localize( b.serverMap ).tolower()
+ aTemp = Localize( a.map ).tolower()
+ bTemp = Localize( b.map ).tolower()
direction = filterDirection.serverMap
break;
case sortingBy.GAMEMODE:
- aTemp = Localize( a.serverGamemode ).tolower()
- bTemp = Localize( b.serverGamemode ).tolower()
+ aTemp = Localize( a.playlist ).tolower()
+ bTemp = Localize( b.playlist ).tolower()
direction = filterDirection.serverGamemode
break;
- case sortingBy.LATENCY:
- aTemp = a.serverLatency
- bTemp = b.serverLatency
- direction = filterDirection.serverLatency
+ case sortingBy.REGION:
+ aTemp = a.region
+ bTemp = b.region
+ direction = filterDirection.serverRegion
break;
default:
return 0
@@ -1185,7 +1268,7 @@ void function SortServerListByDefault_Activate ( var button )
{
filterDirection.sortingBy = sortingBy.DEFAULT
- file.serversArrayFiltered.sort( ServerSortLogic )
+ file.filteredServers.sort( ServerSortLogic )
filterDirection.serverName = !filterDirection.serverName
@@ -1197,7 +1280,7 @@ void function SortServerListByName_Activate ( var button )
{
filterDirection.sortingBy = sortingBy.NAME
- file.serversArrayFiltered.sort( ServerSortLogic )
+ file.filteredServers.sort( ServerSortLogic )
filterDirection.serverName = !filterDirection.serverName
@@ -1209,7 +1292,7 @@ void function SortServerListByPlayers_Activate( var button )
{
filterDirection.sortingBy = sortingBy.PLAYERS
- file.serversArrayFiltered.sort( ServerSortLogic )
+ file.filteredServers.sort( ServerSortLogic )
filterDirection.serverPlayers = !filterDirection.serverPlayers
@@ -1220,7 +1303,7 @@ void function SortServerListByMap_Activate( var button )
{
filterDirection.sortingBy = sortingBy.MAP
- file.serversArrayFiltered.sort( ServerSortLogic )
+ file.filteredServers.sort( ServerSortLogic )
filterDirection.serverMap = !filterDirection.serverMap
@@ -1231,20 +1314,50 @@ void function SortServerListByGamemode_Activate( var button )
{
filterDirection.sortingBy = sortingBy.GAMEMODE
- file.serversArrayFiltered.sort( ServerSortLogic )
+ file.filteredServers.sort( ServerSortLogic )
filterDirection.serverGamemode = !filterDirection.serverGamemode
UpdateShownPage()
}
-void function SortServerListByLatency_Activate( var button )
+void function SortServerListByRegion_Activate( var button )
{
- filterDirection.sortingBy = sortingBy.LATENCY
+ filterDirection.sortingBy = sortingBy.REGION
- file.serversArrayFiltered.sort( ServerSortLogic )
+ file.filteredServers.sort( ServerSortLogic )
- filterDirection.serverLatency = !filterDirection.serverLatency
+ filterDirection.serverRegion = !filterDirection.serverRegion
UpdateShownPage()
}
+
+//////////////////////////////////////
+// Callbacks
+//////////////////////////////////////
+
+void function AddConnectToServerCallback( void functionref( ServerInfo ) callback )
+{
+ if ( file.connectCallbacks.find( callback ) >= 0 )
+ throw "ConnectToServerCallback has been registered twice. Duplicate callbacks are not allowed."
+ file.connectCallbacks.append( callback )
+}
+
+void function RemoveConnectToServerCallback( void functionref( ServerInfo ) callback )
+{
+ file.connectCallbacks.fastremovebyvalue( callback )
+}
+
+void function TriggerConnectToServerCallbacks( ServerInfo ornull targetServer = null )
+{
+ ServerInfo server;
+ if (targetServer == null)
+ {
+ targetServer = file.lastSelectedServer
+ }
+
+ foreach( callback in file.connectCallbacks )
+ {
+ callback( expect ServerInfo( targetServer ) )
+ }
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_pilot_loadouts_shared.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_pilot_loadouts_shared.nut
new file mode 100644
index 00000000..f0139e04
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_pilot_loadouts_shared.nut
@@ -0,0 +1,300 @@
+
+global function UpdatePilotLoadoutPanel
+global function UpdatePilotLoadoutPanelBinds
+global function UpdatePilotLoadoutButtons
+global function UpdatePilotItemButton
+
+void function UpdatePilotLoadoutButtons( int selectedIndex, var[NUM_PERSISTENT_PILOT_LOADOUTS] buttons, bool focusSelected = true )
+{
+ entity player = GetUIPlayer()
+ if ( player == null )
+ return
+
+ int numLoadouts = GetAllCachedPilotLoadouts().len()
+
+ // HACK: num_pilot_loadouts is just used to disable certain loadouts for FNF
+ int numLoadoutsForPlaylist = GetCurrentPlaylistVarInt( "num_pilot_loadouts", 0 )
+ if ( numLoadoutsForPlaylist > 0 )
+ numLoadouts = numLoadoutsForPlaylist
+
+ foreach ( index, button in buttons )
+ {
+ PilotLoadoutDef loadout = GetCachedPilotLoadout( index )
+ RHud_SetText( button, GetPilotLoadoutName( loadout ) )
+ Hud_SetPanelAlpha( button, 0 )
+
+ bool isSelected = ( index == selectedIndex ) ? true : false
+ Hud_SetSelected( button, isSelected )
+
+ string pilotLoadoutRef = "pilot_loadout_" + ( index + 1 )
+ Hud_SetLocked( button, IsItemLocked( player, pilotLoadoutRef ) )
+
+ bool shouldShowNew = ButtonShouldShowNew( eItemTypes.FEATURE, pilotLoadoutRef )
+ if ( !shouldShowNew && (RefHasAnyNewSubitem( player, loadout.primary ) || RefHasAnyNewSubitem( player, loadout.secondary ) || RefHasAnyNewSubitem( player, loadout.weapon3 )) )
+ shouldShowNew = true
+
+ if ( IsItemLocked( player, pilotLoadoutRef ) )
+ shouldShowNew = false
+
+ Hud_SetNew( button, shouldShowNew )
+
+ RefreshButtonCost( button, pilotLoadoutRef )
+ }
+
+ if ( focusSelected )
+ Hud_SetFocused( buttons[ selectedIndex ] )
+}
+
+void function UpdatePilotLoadoutPanel( var loadoutPanel, PilotLoadoutDef loadout )
+{
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "TacticalName" ), Localize( GetItemName( loadout.special ) ) )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "PrimaryName" ), Localize( GetItemName( loadout.primary ) ) )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "SecondaryName" ), Localize( GetItemName( loadout.secondary ) ) )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "Weapon3Name" ), Localize( GetItemName( loadout.weapon3 ) ) )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "OrdnanceName" ), Localize( GetItemName( loadout.ordnance ) ) )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "Kit1Name" ), Localize( GetItemName( loadout.passive1 ) ) )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "Kit2Name" ), Localize( GetItemName( loadout.passive2 ) ) )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "ExecutionName" ), Localize( GetItemName( loadout.execution ) ) )
+
+ UpdatePilotLoadoutPanelBinds( loadoutPanel )
+
+ var menu = Hud_GetParent( loadoutPanel )
+ array<var> buttons = GetElementsByClassname( menu, "PilotLoadoutPanelButtonClass" )
+
+ /*if ( button )
+ {
+ // TEMP disabled since Hud_GetChild( menu, "ButtonTooltip" ) will fail
+ //if ( HandleLockedMenuItem( menu, button ) )
+ // return
+ }*/
+ bool isEdit
+ if ( Hud_GetHudName( loadoutPanel ) == "PilotLoadoutButtons" ) // Edit menu
+ isEdit = true
+ else // Select menu
+ isEdit = false
+
+ foreach ( button in buttons )
+ UpdatePilotItemButton( button, loadout, isEdit )
+
+ var renameEditBox = Hud_GetChild( loadoutPanel, "RenameEditBox" )
+
+ asset pilotAppearanceImage = loadout.camoIndex > 0 ? CamoSkin_GetImage( CamoSkins_GetByIndex( loadout.camoIndex ) ) : $"rui/menu/common/appearance_button_swatch"
+
+ asset primaryAppearanceImage
+ if ( loadout.primarySkinIndex == 0 ) // default skin
+ primaryAppearanceImage = $"rui/menu/common/appearance_button_swatch"
+ else if ( loadout.primarySkinIndex == 1 ) // camo
+ primaryAppearanceImage = CamoSkin_GetImage( CamoSkins_GetByIndex( loadout.primaryCamoIndex ) )
+ else // warpaint skin
+ primaryAppearanceImage = GetItemImageFromWeaponRefAndPersistenceValue( loadout.primary, loadout.primarySkinIndex, "primarySkinIndex" )
+
+ asset secondaryAppearanceImage
+ if ( loadout.secondarySkinIndex == 0 ) // default skin
+ secondaryAppearanceImage = $"rui/menu/common/appearance_button_swatch"
+ else if ( loadout.secondarySkinIndex == 1 ) // camo
+ secondaryAppearanceImage = CamoSkin_GetImage( CamoSkins_GetByIndex( loadout.secondaryCamoIndex ) )
+ else // warpaint skin
+ secondaryAppearanceImage = GetItemImageFromWeaponRefAndPersistenceValue( loadout.secondary, loadout.secondarySkinIndex, "secondarySkinIndex" )
+
+ asset weapon3AppearanceImage
+ if ( loadout.weapon3SkinIndex == 0 ) // default skin
+ weapon3AppearanceImage = $"rui/menu/common/appearance_button_swatch"
+ else if ( loadout.weapon3SkinIndex == 1 ) // camo
+ weapon3AppearanceImage = CamoSkin_GetImage( CamoSkins_GetByIndex( loadout.weapon3CamoIndex ) )
+ else // warpaint skin
+ weapon3AppearanceImage = GetItemImageFromWeaponRefAndPersistenceValue( loadout.weapon3, loadout.weapon3SkinIndex, "weapon3SkinIndex" )
+
+ RuiSetImage( Hud_GetRui( Hud_GetChild( loadoutPanel, "ButtonPilotCamo" ) ), "camoImage", pilotAppearanceImage )
+ RuiSetImage( Hud_GetRui( Hud_GetChild( loadoutPanel, "ButtonPrimarySkin" ) ), "camoImage", primaryAppearanceImage )
+ RuiSetImage( Hud_GetRui( Hud_GetChild( loadoutPanel, "ButtonSecondarySkin" ) ), "camoImage", secondaryAppearanceImage )
+ RuiSetImage( Hud_GetRui( Hud_GetChild( loadoutPanel, "ButtonWeapon3Skin" ) ), "camoImage", weapon3AppearanceImage )
+
+ array<var> nonItemElements
+ nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonPilotCamo" ) )
+ nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonGender" ) )
+ nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonPrimarySkin" ) )
+ nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonSecondarySkin" ) )
+ nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonWeapon3Skin" ) )
+ nonItemElements.append( renameEditBox )
+
+ foreach ( elem in nonItemElements )
+ {
+ if ( isEdit )
+ Hud_Show( elem )
+ else
+ Hud_Hide( elem )
+ }
+ Hud_SetEnabled( renameEditBox, isEdit )
+}
+
+void function UpdatePilotItemButton( var button, PilotLoadoutDef loadout, bool isEdit )
+{
+ string propertyName = Hud_GetScriptID( button )
+ string itemRef = GetPilotLoadoutValue( loadout, propertyName )
+ int itemType = GetItemTypeFromPilotLoadoutProperty( propertyName )
+ asset image
+
+ ItemDisplayData parentItem
+
+ if ( itemType == eItemTypes.PILOT_PRIMARY_ATTACHMENT || itemType == eItemTypes.PILOT_PRIMARY_MOD || itemType == eItemTypes.PILOT_SECONDARY_MOD || itemType == eItemTypes.PILOT_WEAPON_MOD3 )
+ {
+ string parentProperty = GetParentLoadoutProperty( "pilot", propertyName )
+ Assert( parentProperty == "primary" || parentProperty == "secondary" || parentProperty == "weapon3" )
+
+ if ( parentProperty == "primary" )
+ parentItem = GetItemDisplayData( loadout.primary )
+ else if ( parentProperty == "secondary" )
+ parentItem = GetItemDisplayData( loadout.secondary )
+ else
+ parentItem = GetItemDisplayData( loadout.weapon3 )
+
+ bool isHiddenAttachment = false
+
+ if ( itemType == eItemTypes.PILOT_PRIMARY_ATTACHMENT )
+ {
+ // Disable attachment option for "special" primary weapons
+ if ( "menuCategory" in parentItem.i && ( expect int( parentItem.i.menuCategory ) == ePrimaryWeaponCategory.SPECIAL || expect int( parentItem.i.menuCategory ) == ePrimaryWeaponCategory.HANDGUN ) )
+ {
+ isHiddenAttachment = true
+
+ Hud_SetWidth( button, 0 )
+ Hud_SetPos( button, 0, 0 ) // Clear sibling offset
+ }
+ else
+ {
+ int defaultButtonWidth = int( ContentScaledX( 72 ) )
+ int defaultOffsetX = int( ContentScaledX( 6 ) )
+
+ Hud_SetWidth( button, defaultButtonWidth )
+ Hud_SetPos( button, defaultOffsetX, 0 )
+ }
+ }
+
+ if ( !isHiddenAttachment )
+ image = GetImage( itemType, parentItem.ref, itemRef )
+ }
+ else
+ {
+ image = GetImage( itemType, itemRef )
+ }
+
+ if ( itemType == eItemTypes.PILOT_PRIMARY || itemType == eItemTypes.PILOT_SECONDARY )
+ {
+ //if ( isEdit )
+ //{
+ // RuiSetString( Hud_GetRui( button ), "subText", "" )
+ // RuiSetFloat( Hud_GetRui( button ), "numSegments", 0 )
+ // RuiSetFloat( Hud_GetRui( button ), "filledSegments", 0 )
+ //}
+ //else
+ {
+ int currentXP = WeaponGetXP( GetLocalClientPlayer(), itemRef )
+ int numPips = WeaponGetNumPipsForXP( itemRef, currentXP )
+ int filledPips = WeaponGetFilledPipsForXP( itemRef, currentXP )
+ RuiSetString( Hud_GetRui( button ), "subText", WeaponGetDisplayGenAndLevelForXP( itemRef, currentXP ) )
+ RuiSetFloat( Hud_GetRui( button ), "numSegments", float( numPips ) )
+ RuiSetFloat( Hud_GetRui( button ), "filledSegments", float( filledPips ) )
+ }
+ }
+
+ var rui = Hud_GetRui( button )
+
+ if ( image == $"" )
+ {
+ RuiSetBool( rui, "isVisible", false )
+ Hud_SetEnabled( button, false )
+ }
+ else
+ {
+ RuiSetBool( rui, "isVisible", true )
+ RuiSetImage( rui, "buttonImage", image )
+
+ Hud_SetEnabled( button, true )
+ }
+
+ bool isLocked = false
+ bool shouldShowNew = false
+
+ // For unlock and subitem checks below, treat weapon3 as secondary
+ if ( propertyName == "weapon3Mod1" )
+ propertyName = "secondaryMod1"
+ else if ( propertyName == "weapon3Mod2" )
+ propertyName = "secondaryMod2"
+ else if ( propertyName == "weapon3Mod3" )
+ propertyName = "secondaryMod3"
+
+ string propertyRef = propertyName.tolower()
+
+ if ( !IsSubItemType( itemType ) )
+ {
+ if ( IsUnlockValid( propertyRef ) && IsItemLocked( GetUIPlayer(), propertyRef ) )
+ {
+ RefreshButtonCost( button, propertyRef )
+ isLocked = true
+ }
+ shouldShowNew = ButtonShouldShowNew( itemType, itemRef )
+ }
+ else
+ {
+ if ( IsUnlockValid( propertyRef, parentItem.ref ) && IsSubItemLocked( GetUIPlayer(), propertyRef, parentItem.ref ) )
+ {
+ RefreshButtonCost( button, propertyRef )
+ isLocked = true
+ }
+ shouldShowNew = ButtonShouldShowNew( itemType, itemRef, parentItem.ref )
+ }
+
+ Hud_SetLocked( button, isLocked )
+
+ if ( !shouldShowNew && IsUnlockValid( propertyRef, parentItem.ref ) )
+ shouldShowNew = ButtonShouldShowNew( GetSubitemType( parentItem.ref, propertyRef ), propertyRef, parentItem.ref )
+ Hud_SetNew( button, shouldShowNew )
+
+#if HAS_THREAT_SCOPE_SLOT_LOCK
+ if ( propertyName == "primaryMod2" )
+ {
+ string attatchmentRef = GetPilotLoadoutValue( loadout, "primaryAttachment" )
+ if ( attatchmentRef == "threat_scope" )
+ {
+ Hud_SetLocked( button, true )
+ RefreshButtonCost( button, propertyRef, "", 0, 0 )
+ Hud_SetNew( button, false )
+ }
+ }
+#endif
+}
+
+void function UpdatePilotLoadoutPanelBinds( var loadoutPanel )
+{
+ if ( IsControllerModeActive() )
+ {
+ //SetLabelRuiText( Hud_GetChild( loadoutPanel, "PrimaryBind" ), "%weaponCycle%" )
+ //SetLabelRuiText( Hud_GetChild( loadoutPanel, "SecondaryBind" ), "%weaponCycle%" )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "Weapon3Bind" ), Localize( "#WEAPON3_HOLD_HINT" ) )
+ }
+ else
+ {
+ //SetLabelRuiText( Hud_GetChild( loadoutPanel, "PrimaryBind" ), "%weaponSelectPrimary0%" )
+ //SetLabelRuiText( Hud_GetChild( loadoutPanel, "SecondaryBind" ), "%weaponSelectPrimary1%" )
+ SetLabelRuiText( Hud_GetChild( loadoutPanel, "Weapon3Bind" ), Localize( "#WEAPON3_PRESS_HINT" ) )
+ }
+
+ //SetLabelRuiText( Hud_GetChild( loadoutPanel, "TacticalBind" ), Localize( "%offhand1%" ) )
+ //SetLabelRuiText( Hud_GetChild( loadoutPanel, "OrdnanceBind" ), Localize( "%offhand0%" ) )
+}
+
+asset function GetItemImageFromWeaponRefAndPersistenceValue(string weaponRef, int persistenceValue, string loadoutProperty)
+{
+ string skinRef = GetSkinRefFromWeaponRefAndPersistenceValue( weaponRef, persistenceValue )
+ if (!IsRefValid(skinRef))
+ {
+ if (uiGlobal.editingLoadoutIndex != -1)
+ {
+ printt( "Resetting invalid " + loadoutProperty + " for weapon " + weaponRef )
+ SetCachedLoadoutValue(GetUIPlayer(), "pilot", uiGlobal.editingLoadoutIndex, loadoutProperty, "0")
+ }
+ return $"rui/menu/common/appearance_button_swatch"
+ }
+
+ return GetItemImage( skinRef )
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_private_match.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_private_match.nut
index d7c7442f..e3c1f268 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_private_match.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_private_match.nut
@@ -247,10 +247,7 @@ void function OnSelectMatchSettings_Activate( var button )
if ( Hud_IsLocked( button ) )
return
- if ( !IsNorthstarServer() )
- AdvanceMenu( GetMenu( "MatchSettingsMenu" ) )
- else
- AdvanceMenu( GetMenu( "CustomMatchSettingsCategoryMenu" ) )
+ AdvanceMenu( GetMenu( "CustomMatchSettingsCategoryMenu" ) )
}
void function SetupComboButtons( var menu, var navUpButton, var navDownButton )
@@ -274,13 +271,6 @@ void function SetupComboButtons( var menu, var navUpButton, var navDownButton )
file.matchSettingsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_TITLE_MATCH_SETTINGS" )
Hud_AddEventHandler( file.matchSettingsButton, UIE_CLICK, OnSelectMatchSettings_Activate )
- if ( !IsNorthstarServer() )
- {
- 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" )
@@ -575,17 +565,12 @@ function UpdatePrivateMatchButtons()
Hud_SetLocked( file.selectMapButton, true )
Hud_SetLocked( file.selectModeButton, true )
Hud_SetLocked( file.matchSettingsButton, true )
-
- if ( !IsNorthstarServer() )
- Hud_SetLocked( file.inviteFriendsButton, true )
}
else
{
RHud_SetText( file.startMatchButton, "#START_MATCH" )
Hud_SetLocked( file.selectMapButton, false )
Hud_SetLocked( file.selectModeButton, false )
- if ( !IsNorthstarServer() )
- Hud_SetLocked( file.inviteFriendsButton, false )
string modeName = PrivateMatch_GetSelectedMode()
bool settingsLocked = IsFDMode( modeName )
@@ -648,7 +633,7 @@ function UpdateLobby()
{
float varOrigVal = float( GetCurrentPlaylistGamemodeByIndexVar( gamemodeIdx, varName, false ) )
float varOverrideVal = float( GetCurrentPlaylistGamemodeByIndexVar( gamemodeIdx, varName, true ) )
- if ( varOrigVal == varOverrideVal && !IsNorthstarServer() ) // stuff seems to break outside of northstar servers since we dont always use private_match playlist
+ if ( varOrigVal == varOverrideVal ) // stuff seems to break outside of northstar servers since we dont always use private_match playlist
continue
string label = Localize( MatchSettings_PlaylistVarLabels[varName] ) + ": "
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/ns_slider.nut b/Northstar.Client/mod/scripts/vscripts/ui/ns_slider.nut
new file mode 100644
index 00000000..33a79cdc
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/ns_slider.nut
@@ -0,0 +1,52 @@
+// ModSettings_Slider
+// since we are missing some utility functions (e.g. GetMax, GetMin, SetValue), this is basically a collection of workarounds.
+global struct MS_Slider
+{
+ var slider
+ float min = 0.0
+ float max = 1.0
+ float stepSize = 0.05
+}
+
+globalize_all_functions
+
+MS_Slider function MS_Slider_Setup( var slider, float min = 0.0, float max = 1.0, float startVal = 0.0, float stepSize = 0.05 )
+{
+ MS_Slider result
+ result.slider = slider
+ result.min = min
+ result.max = max
+ result.stepSize = stepSize
+ Hud_SliderControl_SetMin( slider, startVal )
+ Hud_SliderControl_SetMax( slider, startVal )
+ Hud_SliderControl_SetStepSize( slider, stepSize )
+ Hud_SliderControl_SetMin( slider, min )
+ Hud_SliderControl_SetMax( slider, max )
+ return result
+}
+
+void function MS_Slider_SetValue( MS_Slider slider, float val )
+{
+ Hud_SliderControl_SetMin( slider.slider, val )
+ Hud_SliderControl_SetMax( slider.slider, val )
+ Hud_SliderControl_SetMin( slider.slider, slider.min )
+ Hud_SliderControl_SetMax( slider.slider, slider.max )
+}
+
+void function MS_Slider_SetMin( MS_Slider slider, float min )
+{
+ slider.min = min
+ Hud_SliderControl_SetMin( slider.slider, min )
+}
+
+void function MS_Slider_SetMax( MS_Slider slider, float max )
+{
+ slider.max = max
+ Hud_SliderControl_SetMax( slider.slider, max )
+}
+
+void function MS_Slider_SetStepSize( MS_Slider slider, float stepSize )
+{
+ slider.stepSize = stepSize
+ Hud_SliderControl_SetStepSize( slider.slider, stepSize )
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/openinvites.nut b/Northstar.Client/mod/scripts/vscripts/ui/openinvites.nut
index 4b3d0f55..f91231b6 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/openinvites.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/openinvites.nut
@@ -202,7 +202,10 @@ void function UpdateOpenInvite_Thread()
void function UICodeCallback_OpenInviteUpdated()
{
- if ( file.openInviteVisible || IsNorthstarServer() )
+ // don't support on northstar
+ return
+
+ if ( file.openInviteVisible )
return
int currentPartySize = GetPartySize()
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut b/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut
index 95b7bdae..2f1bcf02 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut
@@ -81,8 +81,9 @@ void function InitMainMenuPanel()
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 )
+ // "Launch Multiplayer" button removed because we don't support vanilla yet :clueless:
+ //file.mpButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MULTIPLAYER_LAUNCH" )
+ //Hud_AddEventHandler( file.mpButton, UIE_CLICK, OnPlayMPButton_Activate )
file.fdButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_LAUNCH_NORTHSTAR" )
Hud_AddEventHandler( file.fdButton, UIE_CLICK, OnPlayFDButton_Activate )
Hud_SetLocked( file.fdButton, true )
@@ -101,6 +102,10 @@ void function InitMainMenuPanel()
var videoButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#VIDEO" )
Hud_AddEventHandler( videoButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "VideoMenu" ) ) )
#endif
+
+ // MOD SETTINGS
+ var modSettingsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MOD_SETTINGS" )
+ Hud_AddEventHandler( modSettingsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ModSettings" ) ) )
var spotlightLargeButton = Hud_GetChild( file.spotlightPanel, "SpotlightLarge" )
spotlightLargeButton.SetNavLeft( file.spButtons[0] )
@@ -165,7 +170,8 @@ void function OnShowMainMenuPanel()
#endif // PS4_PROG
UpdateSPButtons()
- thread UpdatePlayButton( file.mpButton )
+ // dont try and update the launch multiplayer button, because it doesn't exist
+ //thread UpdatePlayButton( file.mpButton )
thread UpdatePlayButton( file.fdButton )
thread MonitorTrialVersionChange()
@@ -455,7 +461,8 @@ void function UpdatePlayButton( var button )
message = ""
}
- ComboButton_SetText( file.mpButton, buttonText )
+ // dont try and update the launch multiplayer button, because it doesn't exist
+ //ComboButton_SetText( file.mpButton, buttonText )
ComboButton_SetText( file.fdButton, "#MENU_LAUNCH_NORTHSTAR" )
//Hud_SetEnabled( file.fdButton, false )
@@ -527,7 +534,6 @@ void function OnPlayFDButton_Activate( var button ) // repurposed for launching
{
if ( !Hud_IsLocked( button ) )
{
- SetConVarBool( "ns_is_modded_server", true )
SetConVarString( "communities_hostname", "" ) // disable communities due to crash exploits that are still possible through it
NSTryAuthWithLocalServer()
thread TryAuthWithLocalServer()
@@ -571,12 +577,12 @@ void function TryAuthWithLocalServer()
{
CloseAllDialogs()
- var reason = NSGetAuthFailReason()
+ string reason = NSGetAuthFailReason()
DialogData dialogData
dialogData.image = $"ui/menu/common/dialog_error"
dialogData.header = "#ERROR"
- dialogData.message = Localize("#NS_SERVERBROWSER_CONNECTIONFAILED") + "\nERROR: " + reason + "\n" + Localize("#" + reason)
+ dialogData.message = reason
AddDialogButton( dialogData, "#OK", null )
OpenDialog( dialogData )
@@ -597,7 +603,6 @@ void function OnPlayMPButton_Activate( var button )
{
Lobby_SetAutoFDOpen( false )
// Lobby_SetFDMode( false )
- SetConVarBool( "ns_is_modded_server", false )
thread file.mpButtonActivateFunc()
}
}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/ui_mouse_capture.nut b/Northstar.Client/mod/scripts/vscripts/ui/ui_mouse_capture.nut
new file mode 100644
index 00000000..fa5c9217
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/ui/ui_mouse_capture.nut
@@ -0,0 +1,60 @@
+untyped // untyped purely so I can index into a table with var
+
+global function AddMouseMovementCaptureHandler
+global function UICodeCallback_MouseMovementCapture
+
+struct
+{
+ // a table of capturePanels and menus, each of which contains an array of callbacks
+ table< var, array< void functionref( int deltaX, int deltaY ) > > mouseMovementCaptureCallbacks = {}
+} file
+
+// this function registers a callback (or "handler") function for a MouseMovementCapture menu panel
+// use this for scrollbars, sliders, etc.
+void function AddMouseMovementCaptureHandler( var capturePanelOrMenu, void functionref( int deltaX, int deltaY ) func )
+{
+ // if the capturePanel or menu already has an array in the table, we append to the array
+ // if not, we should create the array, [func] just turns func into an array
+ if ( capturePanelOrMenu in file.mouseMovementCaptureCallbacks )
+ file.mouseMovementCaptureCallbacks[capturePanelOrMenu].append( func )
+ else
+ file.mouseMovementCaptureCallbacks[capturePanelOrMenu] <- [func]
+}
+
+void function RunMouseMovementCallbacks( var capturePanelOrMenu, int deltaX, int deltaY )
+{
+ // check that the capturePanelOrMenu is in the table before trying anything stupid
+ if ( capturePanelOrMenu in file.mouseMovementCaptureCallbacks )
+ {
+ // iterate through the different callback functions
+ foreach ( void functionref( int deltaX, int deltaY ) callback in file.mouseMovementCaptureCallbacks[capturePanelOrMenu] )
+ {
+ // run the callback function
+ callback( deltaX, deltaY )
+ }
+ }
+}
+
+void function UICodeCallback_MouseMovementCapture( var capturePanel, int deltaX, int deltaY )
+{
+ // run callbacks for the capturePanel
+ RunMouseMovementCallbacks( capturePanel, deltaX, deltaY )
+
+ // get the current menu and run callbacks, this preserves backwards compatibility
+ RunMouseMovementCallbacks( GetActiveMenu(), deltaX, deltaY )
+
+ // everything below here originally existed in vanilla sh_menu_models.gnut and is meant to be used for like all of their rotation stuff
+ // its easier to move this here than to add a shared callback for all of the vanilla capture handlers (there are like >20)
+
+ // this const was moved instead of made global because it was literally only used in the code below
+ const MOUSE_ROTATE_MULTIPLIER = 25.0
+
+ float screenScaleXModifier = 1920.0 / GetScreenSize()[0] // 1920 is base screen width
+ float mouseXRotateDelta = deltaX * screenScaleXModifier * MOUSE_ROTATE_MULTIPLIER
+ //printt( "deltaX:", deltaX, "screenScaleModifier:", screenScaleModifier, "mouseRotateDelta:", mouseRotateDelta )
+
+ float screenScaleYModifier = 1080.0 / GetScreenSize()[1] // 1080 is base screen height
+ float mouseYRotationDelta = deltaY * screenScaleYModifier * MOUSE_ROTATE_MULTIPLIER
+
+ RunMenuClientFunction( "UpdateMouseRotateDelta", mouseXRotateDelta, mouseYRotationDelta )
+}
diff --git a/Northstar.Custom/keyvalues/playlists_v2.txt b/Northstar.Custom/keyvalues/playlists_v2.txt
index bd2a58fe..d60a24e5 100644
--- a/Northstar.Custom/keyvalues/playlists_v2.txt
+++ b/Northstar.Custom/keyvalues/playlists_v2.txt
@@ -30,6 +30,7 @@ playlists
max_teams 2
classic_mp 1
scorelimit 100 // score is a percentage so ofc need max to be 100%
+ timelimit 15
}
}
gg
@@ -46,8 +47,6 @@ playlists
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
@@ -357,7 +356,6 @@ playlists
mp_angel_city 1
mp_colony02 1
mp_glitch 1
- mp_lf_stacks 1
mp_relic02 1
mp_wargames 1
mp_rise 1
@@ -397,7 +395,6 @@ playlists
mp_colony02 1
mp_glitch 1
mp_lf_stacks 1
- mp_lf_stacks 1
mp_lf_deck 1
mp_lf_meadow 1
mp_lf_traffic 1
@@ -451,7 +448,6 @@ playlists
mp_colony02 1
mp_glitch 1
mp_lf_stacks 1
- mp_lf_stacks 1
mp_lf_deck 1
mp_lf_meadow 1
mp_lf_traffic 1
@@ -500,7 +496,6 @@ playlists
mp_colony02 1
mp_glitch 1
mp_lf_stacks 1
- mp_lf_stacks 1
mp_lf_deck 1
mp_lf_meadow 1
mp_lf_traffic 1
@@ -574,6 +569,7 @@ playlists
max_teams 2
classic_mp 1
scorelimit 100
+ timelimit 15
gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
}
@@ -581,23 +577,15 @@ playlists
{
fw
{
- maps
+ maps // only 7 maps respawn had done
{
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
}
}
}
@@ -635,35 +623,27 @@ playlists
{
maps
{
- mp_complex3 1
- mp_drydock 1
- mp_glitch 1
- mp_homestead 2
- mp_eden 1
mp_forwardbase_kodai 1
- mp_black_water_canal 1
- mp_glitch 1
- mp_angel_city 1
- mp_colony02 1
- mp_relic02 1
mp_grave 1
mp_homestead 1
- mp_drydock 1
- mp_glitch 1
mp_thaw 1
- mp_eden 2
mp_black_water_canal 1
- mp_glitch 1
- mp_relic02 1
- mp_wargames 1
- mp_rise 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
}
}
}
@@ -690,35 +670,27 @@ playlists
{
maps
{
- mp_complex3 1
- mp_drydock 1
- mp_glitch 1
- mp_homestead 2
- mp_eden 1
mp_forwardbase_kodai 1
- mp_black_water_canal 1
- mp_glitch 1
- mp_angel_city 1
- mp_colony02 1
- mp_relic02 1
mp_grave 1
mp_homestead 1
- mp_drydock 1
- mp_glitch 1
mp_thaw 1
- mp_eden 2
mp_black_water_canal 1
- mp_glitch 1
- mp_relic02 1
- mp_wargames 1
- mp_rise 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
}
}
}
@@ -756,7 +728,6 @@ playlists
mp_colony02 1
mp_glitch 1
mp_lf_stacks 1
- mp_lf_stacks 1
mp_lf_deck 1
mp_lf_meadow 1
mp_lf_traffic 1
@@ -804,7 +775,6 @@ playlists
mp_colony02 1
mp_glitch 1
mp_lf_stacks 1
- mp_lf_stacks 1
mp_lf_deck 1
mp_lf_meadow 1
mp_lf_traffic 1
@@ -838,18 +808,18 @@ playlists
{
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_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
diff --git a/Northstar.Custom/mod.json b/Northstar.Custom/mod.json
index 46b5ab7f..69432f49 100644
--- a/Northstar.Custom/mod.json
+++ b/Northstar.Custom/mod.json
@@ -1,7 +1,7 @@
{
"Name": "Northstar.Custom",
"Description": "Custom content for Northstar: extra weapons, gamemodes, etc.",
- "Version": "1.9.0",
+ "Version": "1.19.0",
"LoadPriority": 1,
"RequiredOnClient": true,
"ConVars": [
@@ -24,15 +24,17 @@
{
"Name": "ns_force_melee",
"DefaultValue": ""
+ },
+ {
+ "Name": "ns_show_event_models",
+ "DefaultValue": "1",
+ "Flags": "ARCHIVE_PLAYERPROFILE"
}
],
"Scripts": [
{
"Path": "sh_northstar_custom_precache.gnut",
- "RunOn": "( CLIENT || SERVER ) && MP",
- "ClientCallback": {
- "After": "NorthstarCustomPrecache"
- },
+ "RunOn": "SERVER && MP",
"ServerCallback": {
"After": "NorthstarCustomPrecache"
}
@@ -42,6 +44,10 @@
"RunOn": "( CLIENT || SERVER ) && MP"
},
{
+ "Path": "weapons/mp_titanweapon_arc_cannon.nut",
+ "RunOn": "( CLIENT || SERVER ) && MP"
+ },
+ {
"Path": "gamemodes/sh_gamemode_chamber.nut",
"RunOn": "( CLIENT || SERVER ) && MP",
"ClientCallback": {
@@ -96,6 +102,10 @@
"RunOn": "SERVER && MP"
},
{
+ "Path": "gamemodes/_gamemode_fw.nut",
+ "RunOn": "SERVER && MP"
+ },
+ {
"Path": "gamemodes/sh_gamemode_fw_custom.nut",
"RunOn": "( CLIENT || SERVER ) && MP",
"ClientCallback": {
@@ -110,6 +120,10 @@
"RunOn": "( CLIENT || SERVER ) && MP"
},
{
+ "Path": "gamemodes/cl_gamemode_fw_custom.nut",
+ "RunOn": "CLIENT && MP"
+ },
+ {
"Path": "gamemodes/cl_gamemode_fw.nut",
"RunOn": "CLIENT && MP"
},
@@ -407,6 +421,51 @@
"ServerCallback": {
"After": "CustomPilotCollision_InitPlaylistVars"
}
+ },
+ {
+ "Path": "sh_message_utils.gnut",
+ "RunOn": "( CLIENT || SERVER ) && MP",
+ "ClientCallback": {
+ "Before": "MessageUtils_ClientInit"
+ },
+ "ServerCallback": {
+ "Before": "MessageUtils_ServerInit"
+ }
+ },
+ {
+ "Path": "sh_northstar_http_requests.gnut",
+ "RunOn": "CLIENT || SERVER || UI"
+ },
+ {
+ "Path": "sh_northstar_safe_io.gnut",
+ "RunOn": "CLIENT || SERVER || UI"
+ },
+ {
+ "Path": "_testing.nut",
+ "RunOn": "CLIENT || SERVER || UI",
+ "ClientCallback": {
+ "Before": "Testing_Init"
+ },
+ "ServerCallback": {
+ "Before": "Testing_Init"
+ },
+ "UICallback": {
+ "Before": "Testing_Init"
+ }
+ },
+ {
+ "Path": "_event_models.gnut",
+ "RunOn": "SERVER && LOBBY",
+ "ServerCallback": {
+ "Before": "EventModelsInit"
+ }
+ },
+ {
+ "Path": "ui/ns_custom_mod_settings.gnut",
+ "RunOn": "UI",
+ "UICallback":{
+ "Before": "NSCustomModSettings"
+ }
}
],
diff --git a/Northstar.Custom/mod/cfg/server/cleanup_gamemode_fw.cfg b/Northstar.Custom/mod/cfg/server/cleanup_gamemode_fw.cfg
new file mode 100644
index 00000000..6961e56b
--- /dev/null
+++ b/Northstar.Custom/mod/cfg/server/cleanup_gamemode_fw.cfg
@@ -0,0 +1,3 @@
+// reset cvars that fortwar set
+cvar_reset sv_max_props_multiplayer
+cvar_reset sv_max_prop_data_dwords_multiplayer
diff --git a/Northstar.Custom/mod/cfg/server/setup_gamemode_fw.cfg b/Northstar.Custom/mod/cfg/server/setup_gamemode_fw.cfg
new file mode 100644
index 00000000..e98ebb1a
--- /dev/null
+++ b/Northstar.Custom/mod/cfg/server/setup_gamemode_fw.cfg
@@ -0,0 +1,4 @@
+// setup engine for fortwar
+// this has to run before server initialisation, so it can't be in gamemode script
+sv_max_props_multiplayer 1250000
+sv_max_prop_data_dwords_multiplayer 2500000
diff --git a/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vmt b/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vmt
new file mode 100644
index 00000000..22b81e9a
--- /dev/null
+++ b/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vmt
@@ -0,0 +1,18 @@
+"UnlitTexture"
+{
+ $basetexture "models/northstartree/lightsflicker"
+ $color "[1.5 1.5 1.5]"
+
+ Proxies
+ {
+
+ TextureScroll
+ {
+ texturescrollvar $basetexturetransform
+ texturescrollrate 0.33
+ texturescrollangle 45
+ }
+
+ }
+
+}
diff --git a/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vtf b/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vtf
new file mode 100644
index 00000000..227756be
--- /dev/null
+++ b/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vtf
Binary files differ
diff --git a/Northstar.Custom/mod/models/northstartree/winter_holiday_floor.mdl b/Northstar.Custom/mod/models/northstartree/winter_holiday_floor.mdl
new file mode 100644
index 00000000..aaf70363
--- /dev/null
+++ b/Northstar.Custom/mod/models/northstartree/winter_holiday_floor.mdl
Binary files differ
diff --git a/Northstar.Custom/mod/models/northstartree/winter_holiday_tree.mdl b/Northstar.Custom/mod/models/northstartree/winter_holiday_tree.mdl
new file mode 100644
index 00000000..4690475f
--- /dev/null
+++ b/Northstar.Custom/mod/models/northstartree/winter_holiday_tree.mdl
Binary files differ
diff --git a/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdl b/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdl
new file mode 100644
index 00000000..82b58bec
--- /dev/null
+++ b/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdl
Binary files differ
diff --git a/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdl b/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdl
new file mode 100644
index 00000000..9e660374
--- /dev/null
+++ b/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdl
Binary files differ
diff --git a/Northstar.Custom/mod/models/weapons/titan_arc_rifle/atpov_titan_arc_rifle.mdl b/Northstar.Custom/mod/models/weapons/titan_arc_rifle/atpov_titan_arc_rifle.mdl
new file mode 100644
index 00000000..ec920318
--- /dev/null
+++ b/Northstar.Custom/mod/models/weapons/titan_arc_rifle/atpov_titan_arc_rifle.mdl
Binary files differ
diff --git a/Northstar.Custom/mod/models/weapons/titan_arc_rifle/w_titan_arc_rifle.mdl b/Northstar.Custom/mod/models/weapons/titan_arc_rifle/w_titan_arc_rifle.mdl
new file mode 100644
index 00000000..498cf0e8
--- /dev/null
+++ b/Northstar.Custom/mod/models/weapons/titan_arc_rifle/w_titan_arc_rifle.mdl
Binary files differ
diff --git a/Northstar.Custom/mod/models/weapons/titan_triple_threat_og/atpov_titan_triple_threat_og.mdl b/Northstar.Custom/mod/models/weapons/titan_triple_threat_og/atpov_titan_triple_threat_og.mdl
index 7ea0d4da..f2dafaf1 100644
--- a/Northstar.Custom/mod/models/weapons/titan_triple_threat_og/atpov_titan_triple_threat_og.mdl
+++ b/Northstar.Custom/mod/models/weapons/titan_triple_threat_og/atpov_titan_triple_threat_og.mdl
Binary files differ
diff --git a/Northstar.Custom/mod/models/weapons/titan_triple_threat_og/w_titan_triple_threat_og.mdl b/Northstar.Custom/mod/models/weapons/titan_triple_threat_og/w_titan_triple_threat_og.mdl
index 1c338bcf..b955e1c0 100644
--- a/Northstar.Custom/mod/models/weapons/titan_triple_threat_og/w_titan_triple_threat_og.mdl
+++ b/Northstar.Custom/mod/models/weapons/titan_triple_threat_og/w_titan_triple_threat_og.mdl
Binary files differ
diff --git a/Northstar.Custom/mod/resource/northstar_custom_english.txt b/Northstar.Custom/mod/resource/northstar_custom_english.txt
index 794aa664..56f56e8d 100644
--- a/Northstar.Custom/mod/resource/northstar_custom_english.txt
+++ b/Northstar.Custom/mod/resource/northstar_custom_english.txt
Binary files differ
diff --git a/Northstar.Custom/mod/resource/northstar_custom_portuguese.txt b/Northstar.Custom/mod/resource/northstar_custom_portuguese.txt
index 0a7b19d2..85e915f6 100644
--- a/Northstar.Custom/mod/resource/northstar_custom_portuguese.txt
+++ b/Northstar.Custom/mod/resource/northstar_custom_portuguese.txt
Binary files differ
diff --git a/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut b/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut
index 7447fc59..5bc75db2 100644
--- a/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut
@@ -14,7 +14,10 @@ void function DropPodSpawn_Init()
void function CleanupSpawningDropPods()
{
foreach ( entity pod in file.droppods )
- pod.Destroy()
+ {
+ if( IsValid( pod ) )
+ pod.Destroy()
+ }
file.droppods.clear()
}
@@ -22,6 +25,7 @@ void function CleanupSpawningDropPods()
void function SpawnPlayersInDropPod( array< entity > players, vector targetOrigin, vector angles, float destructionTime = -1 )
{
entity pod = CreateDropPod( targetOrigin, angles )
+ pod.EndSignal( "OnDestroy" )
file.droppods.append( pod )
svGlobal.levelEnt.EndSignal( "CleanUpEntitiesForRoundEnd" )
@@ -35,9 +39,11 @@ void function SpawnPlayersInDropPod( array< entity > players, vector targetOrigi
foreach ( entity player in players )
{
+ if( !IsValid( player ) )
+ continue
if ( !IsAlive( player ) )
player.RespawnPlayer( null )
-
+
player.SetOrigin( pod.GetOrigin() )
player.SetAngles( pod.GetAngles() )
player.SetParent( pod )
@@ -49,8 +55,12 @@ void function SpawnPlayersInDropPod( array< entity > players, vector targetOrigi
// wait for this
LaunchAnimDropPod( pod, "pod_testpath", targetOrigin, angles )
+ if( !GamePlaying() )
+ return
foreach ( entity player in players )
{
+ if( !IsValid( player ) )
+ continue
player.ClearParent()
player.ClearViewEntity()
player.UnfreezeControlsOnServer()
@@ -61,8 +71,12 @@ void function SpawnPlayersInDropPod( array< entity > players, vector targetOrigi
WaitFrame()
vector doorPos = pod.GetAttachmentOrigin( pod.LookupAttachment( "hatch" ) )
+ if( !GamePlaying() )
+ return
foreach ( entity player in players )
{
+ if( !IsValid( player ) )
+ continue
vector viewAngles = doorPos - player.GetOrigin()
viewAngles.x = 3.0
diff --git a/Northstar.Custom/mod/scripts/vscripts/_event_models.gnut b/Northstar.Custom/mod/scripts/vscripts/_event_models.gnut
new file mode 100644
index 00000000..0802d769
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/_event_models.gnut
@@ -0,0 +1,21 @@
+global function EventModelsInit
+
+void function EventModelsInit()
+{
+ if( !GetConVarBool( "ns_show_event_models" ) )
+ return
+
+ table timeParts = GetUnixTimeParts()
+ int month = expect int( timeParts[ "month" ] )
+ int day = expect int( timeParts[ "day" ] )
+
+ // 18th December to 6th January
+ if( ( ( month == 12 ) && ( day >= 18 ) ) || ( ( month == 1 ) && ( day <= 6 ) ) )
+ {
+ PrecacheModel( $"models/northstartee/winter_holiday_tree.mdl" )
+ PrecacheModel( $"models/northstartree/winter_holiday_floor.mdl" )
+
+ CreatePropDynamic( $"models/northstartree/winter_holiday_tree.mdl", < -60, 740, 30 >, < 0, 0, 0 >, SOLID_VPHYSICS, 1000 )
+ CreatePropDynamic( $"models/northstartree/winter_holiday_floor.mdl", < -60, 740, 30 >, < 0, 0, 0 >, SOLID_VPHYSICS, 1000 )
+ }
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/_testing.nut b/Northstar.Custom/mod/scripts/vscripts/_testing.nut
new file mode 100644
index 00000000..15bcf18b
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/_testing.nut
@@ -0,0 +1,302 @@
+global function Testing_Init
+global function RunAllTests
+global function RunAllTests_SaveToFile
+global function RunTestsByCategory
+global function RunTestByCategoryAndName
+
+global function AddTest
+
+struct TestInfo
+{
+ string testName
+ var functionref() callback
+ // whether the test completed successfully
+ // if this is true, actualResult is valid
+ bool completed
+ // var not string because then i can just set it to an exception
+ // which print can then handle
+ var error
+ // whether the test is considered successful
+ var expectedResult
+ var actualResult
+ bool passed
+}
+
+struct {
+ table< string, array< TestInfo > > tests = {}
+} file
+
+void function Testing_Init()
+{
+ // tests for the testing functions :)
+ //AddTest( "Example Tests", "example succeeding test", ExampleTest_ReturnsTrue, true )
+ //AddTest( "Example Tests", "example failing test", ExampleTest_ReturnsFalse, true )
+ //AddTest( "Example Tests", "example erroring test", ExampleTest_ThrowsError, true )
+ //AddTest( "Example Tests", "example test with args", var function() {
+ // return ExampleTest_HasArgs_ReturnsNonVar( 2, 3 )
+ //}, 6 )
+}
+
+int function ExampleTest_HasArgs_ReturnsNonVar( int first, int second )
+{
+ return first * second
+}
+
+var function ExampleTest_ReturnsFalse()
+{
+ return false
+}
+
+var function ExampleTest_ReturnsTrue()
+{
+ return true
+}
+
+var function ExampleTest_ThrowsError()
+{
+ throw "Example exception"
+ return null
+}
+
+void function RunAllTests_SaveToFile()
+{
+ RunAllTests()
+
+ #if UI
+ string fileName = "ns-unit-tests-UI.json"
+ #elseif CLIENT
+ string fileName = "ns-unit-tests-CLIENT.json"
+ #elseif SERVER
+ string fileName = "ns-unit-tests-SERVER.json"
+ #endif
+
+ // cant encode structs so have to reconstruct a table manually from the structs
+ table out = {}
+ foreach ( category, tests in file.tests )
+ {
+ array categoryResults = []
+ foreach ( test in tests )
+ {
+ table testTable = {}
+ testTable[ "name" ] <- test.testName
+ testTable[ "completed" ] <- test.completed
+ testTable[ "passed" ] <- test.passed
+ if ( !test.completed )
+ testTable[ "error" ] <- test.error
+ else if ( !test.passed )
+ {
+ testTable[ "expectedResult" ] <- test.expectedResult
+ testTable[ "actualResult" ] <- test.actualResult
+ }
+
+ categoryResults.append( testTable )
+ }
+ out[ category ] <- categoryResults
+ }
+
+ NSSaveJSONFile( fileName, out )
+}
+
+void function RunAllTests()
+{
+ printt( "Running all tests!" )
+
+ foreach ( category, categoryTests in file.tests )
+ {
+ foreach ( test in categoryTests )
+ {
+ RunTest( test )
+ }
+ }
+
+ PrintAllTestResults()
+}
+
+void function RunTestsByCategory( string category )
+{
+ if ( !( category in file.tests ) )
+ {
+ printt( format( "Category '%s' has no tests registered", category ) )
+ return
+ }
+
+ foreach ( categoryTest in file.tests[ category ] )
+ {
+ RunTest( categoryTest )
+ }
+}
+
+void function RunTestByCategoryAndName( string category, string testName )
+{
+ // find test
+ if ( !( category in file.tests ) )
+ {
+ printt( format( "Category '%s' has no tests registered", category ) )
+ return
+ }
+
+ TestInfo ornull foundTest = null
+ foreach ( categoryTest in file.tests[ category ] )
+ {
+ if ( categoryTest.testName == testName )
+ {
+ foundTest = categoryTest
+ break
+ }
+ }
+
+ if ( !foundTest )
+ {
+ printt( format( "Category '%s' does not contain a test with name '%s'", category, testName ) )
+ return
+ }
+
+ expect TestInfo( foundTest )
+
+ printt( "Running test!" )
+ // run test
+ RunTest( foundTest )
+ // print result
+ PrintTestResult( foundTest )
+}
+
+void function RunTest( TestInfo test )
+{
+ test.completed = false
+ test.passed = false
+ test.actualResult = null
+ test.error = ""
+
+ try
+ {
+ test.actualResult = test.callback()
+ test.completed = true
+ test.passed = test.actualResult == test.expectedResult
+ }
+ catch ( exception )
+ {
+ test.completed = false
+ test.error = exception
+ }
+}
+
+void function PrintAllTestResults()
+{
+ int totalSucceeded = 0
+ int totalFailed = 0
+ int totalErrored = 0
+
+ foreach ( category, categoryTests in file.tests )
+ {
+ int categorySucceeded = 0
+ int categoryFailed = 0
+ int categoryErrored = 0
+
+ printt( format( "Results for category: '%s'", category ) )
+ foreach ( test in categoryTests )
+ {
+ if ( test.completed )
+ {
+ if ( test.passed )
+ {
+ printt( "\t", test.testName, "- Passed!" )
+ categorySucceeded++
+ }
+ else
+ {
+ printt( "\t", test.testName, "- Failed!" )
+ printt( "\t\tExpected:", test.expectedResult )
+ printt( "\t\tActual: ", test.actualResult )
+ categoryFailed++
+ }
+ }
+ else
+ {
+ printt( "\t", test.testName, "- Errored!" )
+ printt( "\t\tError:", test.error )
+ categoryErrored++
+ }
+ }
+
+ printt( "Succeeded:", categorySucceeded, "Failed:", categoryFailed, "Errored:", categoryErrored )
+
+ totalSucceeded += categorySucceeded
+ totalFailed += categoryFailed
+ totalErrored += categoryErrored
+ }
+
+ printt( "TOTAL SUCCEEDED:", totalSucceeded, "TOTAL FAILED:", totalFailed, "TOTAL ERRORED:", totalErrored )
+}
+
+void function PrintCategoryResults( string category )
+{
+ int categorySucceeded = 0
+ int categoryFailed = 0
+ int categoryErrored = 0
+
+ printt( format( "Results for category: '%s'", category ) )
+ foreach ( test in file.tests[ category ] )
+ {
+ if ( test.completed )
+ {
+ if ( test.passed )
+ {
+ printt( "\t", test.testName, "- Passed!" )
+ categorySucceeded++
+ }
+ else
+ {
+ printt( "\t", test.testName, "- Failed!" )
+ printt( "\t\tExpected:", test.expectedResult )
+ printt( "\t\tActual: ", test.actualResult )
+ categoryFailed++
+ }
+ }
+ else
+ {
+ printt( "\t", test.testName, "- Errored!" )
+ printt( "\t\tError:", test.error )
+ categoryErrored++
+ }
+ }
+
+ printt( "Succeeded:", categorySucceeded, "Failed:", categoryFailed, "Errored:", categoryErrored )
+}
+
+void function PrintTestResult( TestInfo test )
+{
+ string resultString = test.testName
+
+ if ( test.completed )
+ {
+ if ( test.passed )
+ resultString += " - Passed!"
+ else
+ {
+ resultString += " - Failed!"
+ resultString += "\n\tExpected: " + test.expectedResult
+ resultString += "\n\tActual: " + test.actualResult
+ }
+ }
+ else
+ {
+ resultString += " - Not completed!"
+ resultString += "\n\tError: " + test.error
+ }
+
+ printt( resultString )
+}
+
+void function AddTest( string testCategory, string testName, var functionref() testFunc, var expectedResult )
+{
+ TestInfo newTest
+ newTest.testName = testName
+ newTest.callback = testFunc
+ newTest.expectedResult = expectedResult
+
+ // create the test category if it doesn't exist
+ if ( !( testCategory in file.tests ) )
+ file.tests[ testCategory ] <- [ newTest ]
+ else
+ file.tests[ testCategory ].append( newTest )
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut
index ac9ffab3..37d4356f 100644
--- a/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut
@@ -188,7 +188,7 @@ BurnReward function BurnReward_GetRandom()
string ref = burn.allowedCards.getrandom().ref
#if SERVER || CLIENT
- if ( !EarnMeterMP_IsTitanEarnGametype() )
+ if ( Riff_TitanAvailability() == eTitanAvailability.Never )
ref = BurnMeter_GetNoTitansReplacement( ref )
if ( GetCurrentPlaylistVarInt( "featured_mode_all_ticks", 0 ) >= 1 )
@@ -211,7 +211,7 @@ string function GetSelectedBurnCardRef( entity player )
#endif
#if SERVER || CLIENT
- if ( !EarnMeterMP_IsTitanEarnGametype() )
+ if ( Riff_TitanAvailability() == eTitanAvailability.Never )
ref = BurnMeter_GetNoTitansReplacement( ref )
if ( GetCurrentPlaylistVarInt( "featured_mode_all_ticks", 0 ) >= 1 )
diff --git a/Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut b/Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut
index 16908362..3971d2be 100644
--- a/Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut
@@ -335,7 +335,11 @@ void function EarnMeter_Update()
break
entity soul = player.GetTitanSoul()
- entity core = player.GetOffhandWeapons()[3]
+ entity core = player.GetOffhandWeapon( OFFHAND_EQUIPMENT )
+
+ if ( !IsValid( core ) )
+ break
+
string coreName = core.GetWeaponClassName()
array<string> coreMods = core.GetMods()
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut
index 019bcc7d..409d5ec0 100644
--- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut
@@ -1,5 +1,6 @@
untyped
global function GamemodeFastball_Init
+global function FastballAddPanelSpawnsForLevel
struct {
// first panel is a, second is b, third is c
@@ -282,7 +283,7 @@ function FastballOnPanelHacked( panel, player )
// respawn dead players
foreach ( entity deadPlayer in GetPlayerArrayOfTeam( player.GetTeam() ) )
{
- if ( !IsAlive( deadPlayer ) && !IsPrivateMatchSpectator( player ) )
+ if ( !IsAlive( deadPlayer ) && !IsPrivateMatchSpectator( deadPlayer ) )
{
deadPlayer.SetOrigin( panel.s.startOrigin )
deadPlayer.RespawnPlayer( null )
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fw.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fw.nut
new file mode 100644
index 00000000..fa66c2f7
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fw.nut
@@ -0,0 +1,2391 @@
+untyped
+global function GamemodeFW_Init
+
+// spawn points
+global function RateSpawnpointsPilot_FW
+global function RateSpawnpointsTitan_FW
+//global function RateSpawnpoints_FW
+
+// for battery_port.gnut to work
+global function FW_ReplaceMegaTurret
+
+// fw specific titanfalls
+global function FW_IsPlayerInFriendlyTerritory
+global function FW_IsPlayerInEnemyTerritory
+
+// Callbacks for mods to reduce harvester damage of modded weapons
+global function FW_AddHarvesterDamageSourceModifier
+global function FW_RemoveHarvesterDamageSourceModifier
+
+// you need to deal this much damage to trigger "FortWarTowerDamage" score event
+const int FW_HARVESTER_DAMAGE_SEGMENT = 5250
+
+// basically needs to match "waves count - bosswaves count"
+const int FW_MAX_LEVELS = 3
+
+// to confirm it's a npc from camps..
+const string FW_NPC_SCRIPTNAME = "fw_npcsFromCamp"
+const int FW_AI_TEAM = TEAM_BOTH
+const float WAVE_STATE_TRANSITION_TIME = 5.0
+
+// from sh_gamemode_fw, if half of these npcs cleared in one camp, it gets escalate
+const int FW_GRUNT_COUNT = 36//32
+const int FW_SPECTRE_COUNT = 24
+const int FW_REAPER_COUNT = 2
+
+// max deployment each camp
+const int FW_GRUNT_MAX_DEPLOYED = 8
+const int FW_SPECTRE_MAX_DEPLOYED = 8
+const int FW_REAPER_MAX_DEPLOYED = 1
+
+// if other camps been cleaned many times, we levelDown
+const int FW_CAMP_IGNORE_NEEDED = 2
+
+// debounce for showing damaged infos
+const float FW_HARVESTER_DAMAGED_DEBOUNCE = 5.0
+const float FW_TURRET_DAMAGED_DEBOUNCE = 2.0
+
+global HarvesterStruct& fw_harvesterMlt
+global HarvesterStruct& fw_harvesterImc
+
+// these are not using respawn's remaining code( sh_gamemode_fw.nut )!
+
+// respawn already have a FW_TowerData struct! this struct is only for score events
+struct HarvesterDamageStruct
+{
+ float recentDamageTime
+ int storedDamage
+}
+
+struct TurretSiteStruct
+{
+ entity site
+ entity turret
+ entity minimapstate
+ string turretflagid
+}
+
+// respawn already have a FW_CampData, FW_WaveOrigin and FW_SpawnData struct!
+struct CampSiteStruct
+{
+ entity camp
+ entity info
+ entity tracker
+ array<entity> validDropPodSpawns
+ array<entity> validTitanSpawns
+ string campId // "A", "B", "C"
+ int npcsAlive
+ int ignoredSinceLastClean
+}
+
+struct CampSpawnStruct
+{
+ string spawnContent // what npcs to spawn
+ int maxSpawnCount // max spawn count on this camp
+ int countPerSpawn // how many npcs to deploy per spawn, for droppods most be 4
+ int killsToEscalate // how many kills needed to escalate
+}
+
+struct
+{
+ array<HarvesterStruct> harvesters
+
+ // Stores damage source IDs and the modifier applied to them when they damage a harvester
+ table< int, float > harvesterDamageSourceMods
+
+ // save camp's info_target, we spawn camps after game starts, or player's first life won't show up correct camp icons
+ array<entity> camps
+
+ array<entity> fwTerritories
+
+ array<TurretSiteStruct> turretsites
+
+ array<CampSiteStruct> fwCampSites
+
+ // respawn already have a FW_TowerData struct! this table is only for score events
+ table< entity, HarvesterDamageStruct > playerDamageHarvester // team, table< player, time >
+
+ // this is for saving territory's connecting time, try not to make faction dialogues play together
+ table< int, float > teamTerrLastConnectTime // team, time
+
+ // unused
+ array<entity> etitaninmlt
+ array<entity> etitaninimc
+
+ entity harvesterMlt_info
+ entity harvesterImc_info
+
+ table<int, CampSpawnStruct> fwNpcLevel // basically use to powerup certian camp, sync with alertLevel
+ table< string, table< string, int > > trackedCampNPCSpawns
+}file
+
+void function GamemodeFW_Init()
+{
+ // _battery_port.gnut needs this
+ RegisterSignal( "BatteryActivate" )
+
+ AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] )
+ AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] )
+
+ AddCallback_EntitiesDidLoad( LoadEntities )
+ AddCallback_GameStateEnter( eGameState.Prematch, OnFWGamePrematch )
+ AddCallback_GameStateEnter( eGameState.Playing, OnFWGamePlaying )
+
+ AddSpawnCallback( "item_powerup", FWAddPowerUpIcon )
+ AddSpawnCallback( "npc_turret_mega", OnFWTurretSpawned )
+
+ AddCallback_OnClientConnected( OnFWPlayerConnected )
+ AddCallback_PlayerClassChanged( OnFWPlayerClassChanged )
+ AddCallback_OnPlayerKilled( OnFWPlayerKilled )
+ AddCallback_OnPilotBecomesTitan( OnFWPilotBecomesTitan )
+ AddCallback_OnTitanBecomesPilot( OnFWTitanBecomesPilot )
+
+ ScoreEvent_SetupEarnMeterValuesForMixedModes()
+ SetRecalculateRespawnAsTitanStartPointCallback( FW_ForcedTitanStartPoint )
+ SetRecalculateTitanReplacementPointCallback( FW_ReCalculateTitanReplacementPoint )
+ SetRequestTitanAllowedCallback( FW_RequestTitanAllowed )
+}
+
+
+
+//////////////////////////
+///// HACK FUNCTIONS /////
+//////////////////////////
+
+const array<string> HACK_CLEANUP_MAPS =
+[
+ "mp_grave",
+ "mp_homestead",
+ "mp_complex3"
+]
+
+//if npcs outside the map try to fire( like in death animation ), it will cause a engine error
+
+// in mp_grave, npcs will sometimes stuck underground
+const float GRAVE_CHECK_HEIGHT = 1700 // the map's lowest ground is 1950+, npcs will stuck under -4000 or -400
+// in mp_homestead, npcs will sometimes stuck in the sky
+const float HOMESTEAD_CHECK_HIEGHT = 8000 // the map's highest part is 7868+, npcs will stuck above 13800+
+// in mp_complex3, npcs will sometimes stuck in the sky
+const float COMPLEX_CHECK_HEIGHT = 7000 // the map's highest part is 6716+, npcs will stuck above 9700+
+
+// do a hack
+void function HACK_ForceDestroyNPCs()
+{
+ thread HACK_ForceDestroyNPCs_Threaded()
+}
+
+void function HACK_ForceDestroyNPCs_Threaded()
+{
+ string mapName = GetMapName()
+ if( !( HACK_CLEANUP_MAPS.contains( mapName ) ) )
+ return
+
+ while( true )
+ {
+ if( mapName == "mp_grave" )
+ {
+ foreach( entity npc in GetNPCArray() )
+ {
+ if( npc.GetOrigin().z <= GRAVE_CHECK_HEIGHT )
+ {
+ npc.ClearParent()
+ npc.Destroy()
+ }
+ }
+ }
+ if( mapName == "mp_homestead" )
+ {
+ foreach( entity npc in GetNPCArray() )
+ {
+ // neither spawning from droppod nor hotdropping
+ if( !IsValid( npc.GetParent() ) && !npc.e.isHotDropping )
+ {
+ if( npc.GetOrigin().z >= HOMESTEAD_CHECK_HIEGHT )
+ {
+ npc.Destroy()
+ }
+ }
+ }
+ }
+ if( mapName == "mp_complex3" )
+ {
+ foreach( entity npc in GetNPCArray() )
+ {
+ // neither spawning from droppod nor hotdropping
+ if( !IsValid( npc.GetParent() ) && !npc.e.isHotDropping )
+ {
+ if( npc.GetOrigin().z >= COMPLEX_CHECK_HEIGHT )
+ {
+ npc.Destroy()
+ }
+ }
+ }
+ }
+ WaitFrame()
+ }
+}
+
+//////////////////////////////
+///// HACK FUNCTIONS END /////
+//////////////////////////////
+
+
+
+////////////////////////////////
+///// SPAWNPOINT FUNCTIONS /////
+////////////////////////////////
+
+void function RateSpawnpointsPilot_FW( int checkClass, array<entity> spawnpoints, int team, entity player )
+{
+ array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
+ RateSpawnpoints_FW( startSpawns, checkClass, spawnpoints, team, player )
+}
+
+void function RateSpawnpointsTitan_FW( int checkClass, array<entity> spawnpoints, int team, entity player )
+{
+ array<entity> startSpawns = SpawnPoints_GetTitanStart( team )
+ RateSpawnpoints_FW( startSpawns, checkClass, spawnpoints, team, player )
+}
+
+void function RateSpawnpoints_FW( array<entity> startSpawns, int checkClass, array<entity> spawnpoints, int team, entity player )
+{
+ if ( HasSwitchedSides() )
+ team = GetOtherTeam( team )
+
+ // average out startspawn positions
+ vector averageFriendlySpawns
+ foreach ( entity spawnpoint in startSpawns )
+ averageFriendlySpawns += spawnpoint.GetOrigin()
+
+ averageFriendlySpawns /= startSpawns.len()
+
+ entity friendlyTerritory
+ foreach ( entity territory in file.fwTerritories )
+ {
+ if ( team == territory.GetTeam() )
+ {
+ friendlyTerritory = territory
+ break
+ }
+ }
+
+ vector ratingPos
+ if ( IsValid( friendlyTerritory ) )
+ ratingPos = friendlyTerritory.GetOrigin()
+ else
+ ratingPos = averageFriendlySpawns
+
+ foreach ( entity spawnpoint in spawnpoints )
+ {
+ // idk about magic number here really
+ float rating = 1.0 - ( Distance2D( spawnpoint.GetOrigin(), ratingPos ) / 1000.0 )
+ spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating )
+ }
+}
+
+////////////////////////////////////
+///// SPAWNPOINT FUNCTIONS END /////
+////////////////////////////////////
+
+
+
+//////////////////////////////
+///// CALLBACK FUNCTIONS /////
+//////////////////////////////
+
+void function OnFWGamePrematch()
+{
+ InitFWScoreEvents()
+ FW_createHarvester()
+ InitFWCampSites()
+ InitCampSpawnerLevel()
+}
+
+void function OnFWGamePlaying()
+{
+ startFWHarvester()
+ FWAreaThreatLevelThink()
+ StartFWCampThink()
+ InitTurretSettings()
+ FWPlayerObjectiveState()
+
+ HACK_ForceDestroyNPCs()
+}
+
+void function OnFWPlayerConnected( entity player )
+{
+ InitFWPlayers( player )
+}
+
+void function OnFWPlayerClassChanged( entity player )
+{
+ // give player a friendly highlight
+ Highlight_SetFriendlyHighlight( player, "fw_friendly" )
+}
+
+void function OnFWPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ HandleFWPlayerKilledScoreEvent( victim, attacker )
+}
+
+void function OnFWPilotBecomesTitan( entity player, entity titan )
+{
+ // objective stuff
+ SetTitanObjective( player, titan )
+}
+
+void function OnFWTitanBecomesPilot( entity player, entity titan )
+{
+ // objective stuff
+ SetPilotObjective( player, titan )
+}
+
+//////////////////////////////////
+///// CALLBACK FUNCTIONS END /////
+//////////////////////////////////
+
+
+/////////////////////////////////
+///// SCORE EVENT FUNCTIONS /////
+/////////////////////////////////
+
+void function InitFWScoreEvents()
+{
+ // common scoreEvents
+ ScoreEvent_SetEarnMeterValues( "KillHeavyTurret", 0.0, 0.20 ) // can only adds to titan's in this mode
+
+ // fw special: save for later use of scoreEvents
+
+ // combat
+ ScoreEvent_SetEarnMeterValues( "FortWarAssault", 0.0, 0.05, 0.0 ) // titans don't earn
+ ScoreEvent_SetEarnMeterValues( "FortWarDefense", 0.0, 0.05, 0.0 ) // titans don't earn
+ ScoreEvent_SetEarnMeterValues( "FortWarPerimeterDefense", 0.0, 0.05 ) // unused
+ ScoreEvent_SetEarnMeterValues( "FortWarSiege", 0.0, 0.05 ) // unused
+ ScoreEvent_SetEarnMeterValues( "FortWarSnipe", 0.0, 0.05 ) // unused
+
+ // constructions
+ ScoreEvent_SetEarnMeterValues( "FortWarBaseConstruction", 0.0, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "FortWarForwardConstruction", 0.0, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "FortWarInvasiveConstruction", 0.0, 0.25 ) // unused
+ ScoreEvent_SetEarnMeterValues( "FortWarResourceDenial", 0.0, 0.05 ) // unused
+ ScoreEvent_SetEarnMeterValues( "FortWarSecuringGatheredResources", 0.0, 0.05 ) // unused
+
+ // tower
+ ScoreEvent_SetEarnMeterValues( "FortWarTowerDamage", 0.0, 0.10, 0.0 ) // using the const FW_HARVESTER_DAMAGE_SEGMENT, titans don't earn
+ ScoreEvent_SetEarnMeterValues( "FortWarTowerDefense", 0.0, 0.10, 0.0 ) // titans don't earn
+ ScoreEvent_SetEarnMeterValues( "FortWarShieldDestroyed", 0.0, 0.15 )
+
+ // turrets
+ ScoreEvent_SetEarnMeterValues( "FortWarTeamTurretControlBonus_One", 0.0, 0.15, 0.5 ) // give more meter if no turret left
+ ScoreEvent_SetEarnMeterValues( "FortWarTeamTurretControlBonus_Two", 0.0, 0.15, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "FortWarTeamTurretControlBonus_Three", 0.0, 0.10, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "FortWarTeamTurretControlBonus_Four", 0.0, 0.10, 0.5 ) // give less meter if controlled most turrets
+ ScoreEvent_SetEarnMeterValues( "FortWarTeamTurretControlBonus_Five", 0.0, 0.05, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "FortWarTeamTurretControlBonus_Six", 0.0, 0.05, 0.5 )
+}
+
+// consider this means victim recently damaged harvester
+const float TOWER_DEFENSE_REQURED_TIME = 10.0
+
+void function HandleFWPlayerKilledScoreEvent( entity victim, entity attacker )
+{
+ // this function only handles player's kills
+ if( !attacker.IsPlayer() )
+ return
+
+ // suicide don't get scores
+ if( attacker == victim )
+ return
+
+ int attackerTeam = attacker.GetTeam()
+ int victimTeam = victim.GetTeam()
+
+ string scoreEvent = ""
+ int secondaryScore = 0
+ entity attackerHarvester = FW_GetTeamHarvesterProp( attackerTeam )
+
+ if( FW_IsPlayerInEnemyTerritory( victim ) ) // victim is in enemy territory
+ {
+ scoreEvent = "FortWarDefense" // enemy earn score from defense
+ secondaryScore = POINTVALUE_FW_DEFENSE
+ }
+
+ if( FW_IsPlayerInFriendlyTerritory( victim ) ) // victim is in friendly territory
+ {
+ scoreEvent = "FortWarAssault" // enemy earn score from assault
+ secondaryScore = POINTVALUE_FW_ASSAULT
+ }
+
+ if( victim in file.playerDamageHarvester ) // victim has damaged the harvester this life
+ {
+ float damageTime = file.playerDamageHarvester[ victim ].recentDamageTime
+
+ // is victim recently damaged havester?
+ if( damageTime + TOWER_DEFENSE_REQURED_TIME >= Time() )
+ {
+ scoreEvent = "FortWarTowerDefense" // you defend the tower!
+ secondaryScore = POINTVALUE_FW_TOWER_DEFENSE
+ }
+
+ }
+
+ if( scoreEvent != "" )
+ {
+ AddPlayerScore( attacker, scoreEvent, victim )
+ attacker.AddToPlayerGameStat( PGS_DEFENSE_SCORE, secondaryScore )
+ }
+}
+
+/////////////////////////////////////
+///// SCORE EVENT FUNCTIONS END /////
+/////////////////////////////////////
+
+
+
+//////////////////////////////////////
+///// FACTION DIALOGUE FUNCTIONS /////
+//////////////////////////////////////
+
+const float FW_TERRYTORY_DIALOGUE_DEBOUNCE = 5.0
+
+// WORKING IN PROGRESS
+bool function TryFWTerritoryDialogue( entity territory, entity player )
+{
+ bool thisTimeIsTitan = player.IsTitan()
+ int terrTeam = territory.GetTeam()
+ int enemyTeam = GetOtherTeam( terrTeam )
+ bool sameTeam = terrTeam == player.GetTeam()
+ bool isInDebounce = file.teamTerrLastConnectTime[ terrTeam ] + FW_TERRYTORY_DIALOGUE_DEBOUNCE >= Time()
+
+ // the territory trigger will only save players and titans
+ array<entity> allEntsInside = GetAllEntitiesInTrigger( territory )
+ allEntsInside.removebyvalue( null ) // since we're using a fake trigger, need to check this
+ array<entity> friendliesInside // this means territory's friendly team
+ array<entity> enemiesInside // this means territory's enemy team
+ array<entity> enemyTitansInside
+ foreach( entity ent in allEntsInside )
+ {
+ if( !IsValid( ent ) ) // since we're using a fake trigger, need to check this
+ continue
+ if( ent.GetTeam() == terrTeam )
+ friendliesInside.append( ent )
+ }
+ foreach( entity ent in allEntsInside )
+ {
+ if( !IsValid( ent ) ) // since we're using a fake trigger, need to check this
+ continue
+ if( ent.GetTeam() != terrTeam )
+ enemiesInside.append( ent )
+ }
+ foreach( entity enemy in enemiesInside )
+ {
+ if( !IsValid( enemy ) ) // since we're using a fake trigger, need to check this
+ continue
+ if( enemy.IsTitan() )
+ enemyTitansInside.append( enemy )
+ }
+
+ print( "enemy in territory: " + string( enemiesInside.len() ) )
+ print( "friendly in territory: " + string( friendliesInside.len() ) )
+
+ print( "sameTeam: " + string( sameTeam ) )
+ print( "isInDebounce: " + string( isInDebounce ) )
+ print( "thisTimeIsTitan: " + string( thisTimeIsTitan ) )
+
+ if( enemiesInside.len() > 3 || friendliesInside.len() > 1 ) // already have some players triggered dialogue
+ return false
+
+ // notify player enemy's behaves
+ if( !sameTeam ) // player is not the same team as territory
+ {
+ // consider this means all enemies has left friendly territory, should use a debounce
+ if( enemiesInside.len() == 0 && !isInDebounce )
+ {
+ PlayFactionDialogueToTeam( "fortwar_terEnemyExpelled", terrTeam )
+ return true
+ }
+ // has more than 3 titans inside including new one, ignores debounce
+ else if( enemyTitansInside.len() >= 3 && thisTimeIsTitan )
+ {
+ PlayFactionDialogueToTeam( "fortwar_terPresentEnemyTitans", terrTeam )
+ return true
+ }
+ // only the player inside terrytory
+ else if( enemyTitansInside.len() == 1 )
+ {
+ // entered territory as titan, ignores debounce
+ if( thisTimeIsTitan )
+ {
+ PlayFactionDialogueToTeam( "fortwar_terEnteredEnemyPilot", terrTeam )
+ return true
+ }
+ // entered territory as pilot
+ else if( !isInDebounce )
+ {
+ PlayFactionDialogueToTeam( "fortwar_terEnteredEnemyPilot", terrTeam )
+ return true
+ }
+ }
+
+ // notify player friendly's behaves
+ // consider this means all friendlies has left enemy territory
+ if( friendliesInside.len() == 0 && !sameTeam && !isInDebounce )
+ {
+ PlayFactionDialogueToTeam( "fortwar_terFriendlyExpelled", terrTeam )
+ return true
+ }
+ }
+
+ return false
+}
+
+//////////////////////////////////////////
+///// FACTION DIALOGUE FUNCTIONS END /////
+//////////////////////////////////////////
+
+
+
+/////////////////////////////////////////
+///// GAMEMODE INITIALIZE FUNCTIONS /////
+/////////////////////////////////////////
+
+void function LoadEntities()
+{
+ // info_target
+ foreach ( entity info_target in GetEntArrayByClass_Expensive( "info_target" ) )
+ {
+ if( info_target.HasKey( "editorclass" ) )
+ {
+ switch( info_target.kv.editorclass )
+ {
+ case "info_fw_team_tower":
+ if ( info_target.GetTeam() == TEAM_IMC )
+ {
+ file.harvesterImc_info = info_target
+ //print("fw_tower tracker spawned")
+ }
+ if ( info_target.GetTeam() == TEAM_MILITIA )
+ {
+ file.harvesterMlt_info = info_target
+ //print("fw_tower tracker spawned")
+ }
+ break
+ case "info_fw_camp":
+ file.camps.append( info_target )
+ //InitCampTracker( info_target )
+ //print("fw_camp spawned")
+ break
+ case "info_fw_turret_site":
+ string idString = expect string(info_target.kv.turretId)
+ int id = int( info_target.kv.turretId )
+ //print("info_fw_turret_siteID : " + idString )
+
+ // set this for replace function to find
+ TurretSiteStruct turretsite
+ file.turretsites.append( turretsite )
+
+ turretsite.site = info_target
+
+ // create turret, spawn with no team and set it after game starts
+ entity turret = CreateNPC( "npc_turret_mega", TEAM_UNASSIGNED, info_target.GetOrigin(), info_target.GetAngles() )
+ SetSpawnOption_AISettings( turret, "npc_turret_mega_fortwar" )
+ DispatchSpawn( turret )
+
+ turretsite.turret = turret
+
+ // init turret settings
+ turret.s.minimapstate <- null // entity, for saving turret's minimap handler
+ turret.s.baseTurret <- false // bool, is this turret from base
+ turret.s.turretflagid <- "" // string, turret's id like "1", "2", "3"
+ turret.s.lastDamagedTime <- 0.0 // float, for showing turret underattack icons
+ turret.s.relatedBatteryPort <- null // entity, corssfile
+
+ // minimap icons holder
+ entity minimapstate = CreateEntity( "prop_script" )
+ minimapstate.SetValueForModelKey( info_target.GetModelName() ) // these info must have model to work
+ minimapstate.Hide() // hide the model! it will still work on minimaps
+ minimapstate.SetOrigin( info_target.GetOrigin() )
+ minimapstate.SetAngles( info_target.GetAngles() )
+ //SetTeam( minimapstate, info_target.GetTeam() ) // setTeam() for icons is done in TurretStateWatcher()
+ minimapstate.kv.solid = SOLID_VPHYSICS
+ DispatchSpawn( minimapstate )
+ // show on minimaps
+ minimapstate.Minimap_AlwaysShow( TEAM_IMC, null )
+ minimapstate.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ minimapstate.Minimap_SetCustomState( eMinimapObject_prop_script.FW_BUILDSITE_SHIELDED )
+
+ turretsite.minimapstate = minimapstate
+ turret.s.minimapstate = minimapstate
+
+ break
+ }
+ }
+ }
+
+ // script_ref
+ foreach ( entity script_ref in GetEntArrayByClass_Expensive( "script_ref" ) )
+ {
+ if( script_ref.HasKey( "editorclass" ) )
+ {
+ switch( script_ref.kv.editorclass )
+ {
+ case "info_fw_foundation_plate":
+ entity prop = CreatePropScript( script_ref.GetModelName(), script_ref.GetOrigin(), script_ref.GetAngles(), 6 )
+ break
+ case "info_fw_battery_port":
+ entity batteryPort = CreatePropScript( script_ref.GetModelName(), script_ref.GetOrigin(), script_ref.GetAngles(), 6 )
+ FW_InitBatteryPort(batteryPort)
+
+ break
+ }
+ }
+ }
+
+ // trigger_multiple
+ foreach ( entity trigger_multiple in GetEntArrayByClass_Expensive( "trigger_multiple" ) )
+ {
+ if( trigger_multiple.HasKey( "editorclass" ) )
+ {
+ switch( trigger_multiple.kv.editorclass )
+ {
+ case "trigger_fw_territory":
+ SetupFWTerritoryTrigger( trigger_multiple )
+ break
+ }
+ }
+ }
+
+ // maybe for tick_spawning reapers?
+ ValidateAndFinalizePendingStationaryPositions()
+}
+
+void function InitCampSpawnerLevel() // can edit this to make more spawns, alertLevel icons supports max to lv3( 0,1,2 )
+{
+ // lv1 spawns: grunts
+ CampSpawnStruct campSpawnLv1
+ campSpawnLv1.spawnContent = "npc_soldier"
+ campSpawnLv1.maxSpawnCount = FW_GRUNT_MAX_DEPLOYED
+ campSpawnLv1.countPerSpawn = 4 // how many npcs to deploy per spawn, for droppods most be 4
+ campSpawnLv1.killsToEscalate = FW_GRUNT_COUNT / 2
+
+ file.fwNpcLevel[0] <- campSpawnLv1
+
+ // lv2 spawns: spectres
+ CampSpawnStruct campSpawnLv2
+ campSpawnLv2.spawnContent = "npc_spectre"
+ campSpawnLv2.maxSpawnCount = FW_SPECTRE_MAX_DEPLOYED
+ campSpawnLv2.countPerSpawn = 4 // how many npcs to deploy per spawn, for droppods most be 4
+ campSpawnLv2.killsToEscalate = FW_SPECTRE_COUNT / 2
+
+ file.fwNpcLevel[1] <- campSpawnLv2
+
+ // lv3 spawns: reapers
+ CampSpawnStruct campSpawnLv3
+ campSpawnLv3.spawnContent = "npc_super_spectre"
+ campSpawnLv3.maxSpawnCount = FW_REAPER_MAX_DEPLOYED
+ campSpawnLv3.countPerSpawn = 1 // how many npcs to deploy per spawn
+ campSpawnLv3.killsToEscalate = FW_REAPER_COUNT / 2 // only 1 kill needed to spawn the boss?
+
+ file.fwNpcLevel[2] <- campSpawnLv3
+}
+
+/////////////////////////////////////////////
+///// GAMEMODE INITIALIZE FUNCTIONS END /////
+/////////////////////////////////////////////
+
+
+
+///////////////////////////////////////
+///// PLAYER INITIALIZE FUNCTIONS /////
+///////////////////////////////////////
+
+void function InitFWPlayers( entity player )
+{
+ HarvesterDamageStruct emptyStruct
+ file.playerDamageHarvester[ player ] <- emptyStruct
+
+ // objective stuff
+ player.s.notifiedTitanfall <- false
+
+ // notification stuff
+ player.s.lastTurretNotifyTime <- 0.0
+}
+
+///////////////////////////////////////////
+///// PLAYER INITIALIZE FUNCTIONS END /////
+///////////////////////////////////////////
+
+
+
+/////////////////////////////
+///// POWERUP FUNCTIONS /////
+/////////////////////////////
+
+void function FWAddPowerUpIcon( entity powerup )
+{
+ powerup.Minimap_SetAlignUpright( true )
+ powerup.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ powerup.Minimap_SetClampToEdge( false )
+ powerup.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ powerup.Minimap_AlwaysShow( TEAM_IMC, null )
+}
+
+/////////////////////////////////
+///// POWERUP FUNCTIONS END /////
+/////////////////////////////////
+
+
+
+/////////////////////////////
+///// AICAMPS FUNCTIONS /////
+/////////////////////////////
+
+void function InitFWCampSites()
+{
+ // init here
+ foreach( entity info_target in file.camps )
+ {
+ InitCampTracker( info_target )
+ }
+
+ // camps don't have a id, set them manually
+ foreach( int index, CampSiteStruct campsite in file.fwCampSites )
+ {
+ entity campInfo = campsite.camp
+ float radius = float( campInfo.kv.radius )
+
+ // get droppod spawns
+ foreach ( entity spawnpoint in SpawnPoints_GetDropPod() )
+ if ( Distance( campInfo.GetOrigin(), spawnpoint.GetOrigin() ) < radius )
+ campsite.validDropPodSpawns.append( spawnpoint )
+
+ // get titan spawns
+ foreach ( entity spawnpoint in SpawnPoints_GetTitan() )
+ if ( Distance( campInfo.GetOrigin(), spawnpoint.GetOrigin() ) < radius )
+ campsite.validTitanSpawns.append( spawnpoint )
+
+ if ( index == 0 )
+ {
+ campsite.campId = "A"
+ SetGlobalNetInt( "fwCampAlertA", 0 )
+ SetGlobalNetFloat( "fwCampStressA", 0.0 ) // start from empty
+ SetLocationTrackerID( campsite.tracker, 0 )
+ file.trackedCampNPCSpawns["A"] <- {}
+ continue
+ }
+ if ( index == 1 )
+ {
+ campsite.campId = "B"
+ SetGlobalNetInt( "fwCampAlertB", 0 )
+ SetGlobalNetFloat( "fwCampStressB", 0.0 ) // start from empty
+ SetLocationTrackerID( campsite.tracker, 1 )
+ file.trackedCampNPCSpawns["B"] <- {}
+ continue
+ }
+ if ( index == 2 )
+ {
+ campsite.campId = "C"
+ SetGlobalNetInt( "fwCampAlertC", 0 )
+ SetGlobalNetFloat( "fwCampStressC", 0.0 ) // start from empty
+ SetLocationTrackerID( campsite.tracker, 2 )
+ file.trackedCampNPCSpawns["C"] <- {}
+ continue
+ }
+ }
+}
+
+void function InitCampTracker( entity camp )
+{
+ //print("InitCampTracker")
+ CampSiteStruct campsite
+ campsite.camp = camp
+ file.fwCampSites.append( campsite )
+
+ entity placementHelper = CreateEntity( "info_placement_helper" )
+ placementHelper.SetOrigin( camp.GetOrigin() ) // tracker needs a owner to display
+ campsite.info = placementHelper
+ DispatchSpawn( placementHelper )
+
+ float radius = float( camp.kv.radius ) // radius to show up icon and spawn ais
+
+ entity tracker = GetAvailableCampLocationTracker()
+ tracker.SetOwner( placementHelper )
+ campsite.tracker = tracker
+ SetLocationTrackerRadius( tracker, radius )
+ DispatchSpawn( tracker )
+}
+
+void function StartFWCampThink()
+{
+ foreach( CampSiteStruct camp in file.fwCampSites )
+ {
+ //print( "has " + string( file.fwCampSites.len() ) + " camps in total" )
+ //print( "campId is " + camp.campId )
+ thread FWAiCampThink( camp )
+ }
+}
+
+// this is not using respawn's remaining code!
+void function FWAiCampThink( CampSiteStruct campsite )
+{
+ string campId = campsite.campId
+ string alertVarName = "fwCampAlert" + campId
+ string stressVarName = "fwCampStress" + campId
+
+
+ bool firstSpawn = true
+ while( GamePlayingOrSuddenDeath() )
+ {
+ wait WAVE_STATE_TRANSITION_TIME
+
+ int alertLevel = GetGlobalNetInt( alertVarName )
+ //print( "campsite" + campId + ".ignoredSinceLastClean: " + string( campsite.ignoredSinceLastClean ) )
+ if( campsite.ignoredSinceLastClean >= FW_CAMP_IGNORE_NEEDED && alertLevel > 0 ) // has been ignored many times, level > 0
+ alertLevel = 0 // reset level
+ else if( !firstSpawn ) // not the first spawn!
+ alertLevel += 1 // level up
+
+ if( alertLevel >= FW_MAX_LEVELS - 1 ) // reached max level?
+ alertLevel = FW_MAX_LEVELS - 1 // stay
+
+ // update netVars, don't know how client update these, sometimes they can't catch up
+ SetGlobalNetInt( alertVarName, alertLevel )
+ SetGlobalNetFloat( stressVarName, 1.0 ) // refill
+
+ // under attack, clean this
+ campsite.ignoredSinceLastClean = 0
+
+ CampSpawnStruct curSpawnStruct = file.fwNpcLevel[alertLevel]
+ string npcToSpawn = curSpawnStruct.spawnContent
+ int maxSpawnCount = curSpawnStruct.maxSpawnCount
+ int countPerSpawn = curSpawnStruct.countPerSpawn
+ int killsToEscalate = curSpawnStruct.killsToEscalate
+
+ // for this time's loop
+ file.trackedCampNPCSpawns[campId] = {}
+ int killsNeeded = killsToEscalate
+ int lastNpcLeft
+ while( true )
+ {
+ WaitFrame()
+
+ //print( alertVarName + " : " + string( GetGlobalNetInt( alertVarName ) ) )
+ //print( stressVarName + " : " + string( GetGlobalNetFloat( stressVarName ) ) )
+ //print( "campsite" + campId + ".ignoredSinceLastClean: " + string( campsite.ignoredSinceLastClean ) )
+
+ if( !( npcToSpawn in file.trackedCampNPCSpawns[campId] ) ) // init it
+ file.trackedCampNPCSpawns[campId][npcToSpawn] <- 0
+
+ int npcsLeft = file.trackedCampNPCSpawns[campId][npcToSpawn]
+ killsNeeded -= lastNpcLeft - npcsLeft
+
+ if( killsNeeded <= 0 ) // check if needs more kills
+ {
+ SetGlobalNetFloat( stressVarName, 0.0 ) // empty
+ AddIgnoredCountToOtherCamps( campsite )
+ break
+ }
+
+ // update stress bar
+ float campStressLeft = float( killsNeeded ) / float( killsToEscalate )
+ SetGlobalNetFloat( stressVarName, campStressLeft )
+ //print( "campStressLeft: " + string( campStressLeft ) )
+
+ if( maxSpawnCount - npcsLeft >= countPerSpawn && killsNeeded >= countPerSpawn ) // keep spawning
+ {
+ // spawn functions, for fw we only spawn one kind of enemy each time
+ // light units
+ if( npcToSpawn == "npc_soldier"
+ || npcToSpawn == "npc_spectre"
+ || npcToSpawn == "npc_stalker" )
+ thread FW_SpawnDroppodSquad( campsite, npcToSpawn )
+
+ // reapers
+ if( npcToSpawn == "npc_super_spectre" )
+ thread FW_SpawnReaper( campsite )
+
+ file.trackedCampNPCSpawns[campId][npcToSpawn] += countPerSpawn
+
+ // titans?
+ //else if( npcToSpawn == "npc_titan" )
+ //{
+ // file.trackedCampNPCSpawns[campId][npcToSpawn] += 4
+ //}
+ }
+
+ lastNpcLeft = file.trackedCampNPCSpawns[campId][npcToSpawn]
+ }
+
+ // first loop ends
+ firstSpawn = false
+ }
+}
+
+void function AddIgnoredCountToOtherCamps( CampSiteStruct senderCamp )
+{
+ foreach( CampSiteStruct camp in file.fwCampSites )
+ {
+ //print( "senderCampId is: " + senderCamp.campId )
+ //print( "curCampId is " + camp.campId )
+ if( camp.campId != senderCamp.campId ) // other camps
+ {
+ camp.ignoredSinceLastClean += 1
+ }
+ }
+}
+
+// functions from at
+void function FW_SpawnDroppodSquad( CampSiteStruct campsite, string aiType )
+{
+ entity spawnpoint
+ if ( campsite.validDropPodSpawns.len() == 0 )
+ spawnpoint = campsite.tracker // no spawnPoints valid, use camp itself to spawn
+ else
+ spawnpoint = campsite.validDropPodSpawns.getrandom()
+
+ // add variation to spawns
+ wait RandomFloat( 1.0 )
+
+ AiGameModes_SpawnDropPod( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), FW_AI_TEAM, aiType, void function( array<entity> guys ) : ( campsite, aiType )
+ {
+ FW_HandleSquadSpawn( guys, campsite, aiType )
+ })
+}
+
+void function FW_HandleSquadSpawn( array<entity> guys, CampSiteStruct campsite, string aiType )
+{
+ foreach ( entity guy in guys )
+ {
+ guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) // NPC_ALLOW_INVESTIGATE is not allowed
+ guy.SetScriptName( FW_NPC_SCRIPTNAME ) // well no need
+ // show on minimap to let players kill them
+ guy.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ guy.Minimap_AlwaysShow( TEAM_IMC, null )
+
+ // untrack them on death
+ thread FW_WaitToUntrackNPC( guy, campsite.campId, aiType )
+ }
+ // at least don't let them running around
+ thread FW_ForceAssaultInCamp( guys, campsite.camp )
+}
+
+void function FW_SpawnReaper( CampSiteStruct campsite )
+{
+ entity spawnpoint
+ if ( campsite.validDropPodSpawns.len() == 0 )
+ spawnpoint = campsite.tracker // no spawnPoints valid, use camp itself to spawn
+ else
+ spawnpoint = campsite.validDropPodSpawns.getrandom()
+
+ // add variation to spawns
+ wait RandomFloat( 1.0 )
+
+ AiGameModes_SpawnReaper( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), FW_AI_TEAM, "npc_super_spectre_aitdm",void function( entity reaper ) : ( campsite )
+ {
+ reaper.SetScriptName( FW_NPC_SCRIPTNAME ) // no neet rn
+ // show on minimap to let players kill them
+ reaper.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ reaper.Minimap_AlwaysShow( TEAM_IMC, null )
+
+ // at least don't let them running around
+ thread FW_ForceAssaultInCamp( [reaper], campsite.camp )
+ // untrack them on death
+ thread FW_WaitToUntrackNPC( reaper, campsite.campId, "npc_super_spectre" )
+ })
+}
+
+// maybe this will make them stay around the camp
+void function FW_ForceAssaultInCamp( array<entity> guys, entity camp )
+{
+ while( true )
+ {
+ bool oneGuyValid = false
+ foreach( entity guy in guys )
+ {
+ if( IsValid( guy ) )
+ {
+ guy.AssaultPoint( camp.GetOrigin() )
+ guy.AssaultSetGoalRadius( float( camp.kv.radius ) ) // the camp's radius
+ guy.AssaultSetFightRadius( 0 )
+ oneGuyValid = true
+ }
+ }
+ if( !oneGuyValid ) // no guys left
+ return
+
+ wait RandomFloatRange( 10, 15 ) // make randomness
+ }
+}
+
+void function FW_WaitToUntrackNPC( entity guy, string campId, string aiType )
+{
+ guy.WaitSignal( "OnDeath", "OnDestroy" )
+ if( aiType in file.trackedCampNPCSpawns[ campId ] ) // maybe escalated?
+ file.trackedCampNPCSpawns[ campId ][ aiType ]--
+}
+
+/////////////////////////////////
+///// AICAMPS FUNCTIONS END /////
+/////////////////////////////////
+
+
+
+///////////////////////////////
+///// TERRITORY FUNCTIONS /////
+///////////////////////////////
+
+void function SetupFWTerritoryTrigger( entity trigger )
+{
+ //print("trigger_fw_territory detected")
+ file.fwTerritories.append( trigger )
+ trigger.ConnectOutput( "OnStartTouch", EntityEnterFWTrig )
+ trigger.ConnectOutput( "OnEndTouch", EntityLeaveFWTrig )
+
+ // respawn didn't leave a key for trigger's team, let's set it manually.
+ if( Distance( trigger.GetOrigin(), file.harvesterMlt_info.GetOrigin() ) > Distance( trigger.GetOrigin(), file.harvesterImc_info.GetOrigin() ) )
+ SetTeam( trigger, TEAM_IMC )
+ else
+ SetTeam( trigger, TEAM_MILITIA )
+
+ // init
+ file.teamTerrLastConnectTime[ trigger.GetTeam() ] <- 0.0
+
+ thread FWTerritoryTriggerThink( trigger )
+}
+
+// since we're using a trigger_multiple, needs this to remove invalid keys
+void function FWTerritoryTriggerThink( entity trigger )
+{
+ trigger.EndSignal( "OnDestroy" )
+
+ while( true )
+ {
+ if( null in trigger.e.scriptTriggerData.entities )
+ delete trigger.e.scriptTriggerData.entities[ null ]
+ WaitFrame()
+ }
+}
+
+void function EntityEnterFWTrig( entity trigger, entity ent, entity caller, var value )
+{
+ if( !IsValid( ent ) ) // post-spawns
+ return
+ if( !ent.IsPlayer() && !ent.IsTitan() ) // no neet to add props and grunts i guess
+ return
+ // functions that trigger_multiple missing
+ if( IsValid( ent ) )
+ {
+ ScriptTriggerAddEntity( trigger, ent )
+ thread ScriptTriggerPlayerDisconnectThink( trigger, ent )
+ //TryFWTerritoryDialogue( trigger, ent ) // WIP
+ file.teamTerrLastConnectTime[ trigger.GetTeam() ] = Time()
+ }
+
+ if( !IsValid(ent) )
+ return
+ if ( ent.IsPlayer() ) // notifications for player
+ {
+ MessageToPlayer( ent, eEventNotifications.Clear ) // clean up last message
+ bool sameTeam = ent.GetTeam() == trigger.GetTeam()
+ if ( sameTeam )
+ {
+ Remote_CallFunction_NonReplay( ent , "ServerCallback_FW_NotifyEnterFriendlyArea" )
+ ent.SetPlayerNetInt( "indicatorId", 1 ) // 1 means "FRIENDLY TERRITORY"
+ }
+ else
+ {
+ Remote_CallFunction_NonReplay( ent , "ServerCallback_FW_NotifyEnterEnemyArea" )
+ ent.SetPlayerNetInt( "indicatorId", 2 ) // 2 means "ENEMY TERRITORY"
+ }
+ }
+}
+
+void function EntityLeaveFWTrig( entity trigger, entity ent, entity caller, var value )
+{
+ if( !IsValid( ent ) ) // post-spawns
+ return
+ if( !ent.IsPlayer() && !ent.IsTitan() ) // no neet to add props and grunts i guess
+ return
+ // functions that trigger_multiple missing
+ if( IsValid( ent ) )
+ {
+ if( ent in trigger.e.scriptTriggerData.entities ) // need to check this!
+ {
+ ScriptTriggerRemoveEntity( trigger, ent )
+ //TryFWTerritoryDialogue( trigger, ent ) // WIP
+ file.teamTerrLastConnectTime[ trigger.GetTeam() ] = Time()
+ }
+ }
+
+ if( !IsValid(ent) )
+ return
+ if ( ent.IsPlayer() ) // notifications for player
+ {
+ MessageToPlayer( ent, eEventNotifications.Clear ) // clean up
+ bool sameTeam = ent.GetTeam() == trigger.GetTeam()
+ if ( sameTeam )
+ Remote_CallFunction_NonReplay( ent , "ServerCallback_FW_NotifyExitFriendlyArea" )
+ else
+ Remote_CallFunction_NonReplay( ent , "ServerCallback_FW_NotifyExitEnemyArea" )
+ ent.SetPlayerNetInt( "indicatorId", 4 ) // 4 means "NO MAN'S LAND"
+ }
+}
+
+// globlized!
+bool function FW_IsPlayerInFriendlyTerritory( entity player )
+{
+ foreach( entity trigger in file.fwTerritories )
+ {
+ if( trigger.GetTeam() == player.GetTeam() ) // is it friendly one?
+ {
+ if( GetAllEntitiesInTrigger( trigger ).contains( player ) ) // is player inside?
+ return true
+ }
+ }
+ return false // can't find the player
+}
+
+// globlized!
+bool function FW_IsPlayerInEnemyTerritory( entity player )
+{
+ foreach( entity trigger in file.fwTerritories )
+ {
+ if( trigger.GetTeam() != player.GetTeam() ) // is it enemy one?
+ {
+ if( GetAllEntitiesInTrigger( trigger ).contains( player ) ) // is player inside?
+ return true
+ }
+ }
+ return false // can't find the player
+}
+
+///////////////////////////////////
+///// TERRITORY FUNCTIONS END /////
+///////////////////////////////////
+
+
+
+////////////////////////////////
+///// TITANSPAWN FUNCTIONS /////
+////////////////////////////////
+
+// territory trigger don't have a kv.radius, let's use a const
+// 2800 will pretty much get harvester's near titan startpoints
+const float FW_SPAWNPOINT_SEARCH_RADIUS = 2800.0
+
+
+Point function FW_ReCalculateTitanReplacementPoint( Point basePoint, entity player )
+{
+ int team = player.GetTeam()
+ // find team's harvester
+ entity teamHarvester = FW_GetTeamHarvesterProp( team )
+
+ if ( !IsValid( teamHarvester ) ) // team's havester has been destroyed!
+ return basePoint // return given value
+
+ if( Distance2D( basePoint.origin, teamHarvester.GetOrigin() ) <= FW_SPAWNPOINT_SEARCH_RADIUS ) // close enough!
+ return basePoint // this origin is good enough
+
+ // if not close enough to base, re-calculate
+ array<entity> fortWarPoints = FW_GetTitanSpawnPointsForTeam( team )
+ entity validPoint = GetClosest( fortWarPoints, basePoint.origin )
+ basePoint.origin = validPoint.GetOrigin()
+ return basePoint
+}
+
+bool function FW_RequestTitanAllowed( entity player, array< string > args )
+{
+ if( !FW_IsPlayerInFriendlyTerritory( player ) ) // is player in friendly base?
+ {
+ PlayFactionDialogueToPlayer( "tw_territoryNag", player ) // notify player
+ TryPlayTitanfallNegativeSoundToPlayer( player )
+ int objectiveID = 101 // which means "#FW_OBJECTIVE_TITANFALL"
+ Remote_CallFunction_NonReplay( player, "ServerCallback_FW_SetObjective", objectiveID )
+ return false
+ }
+ return true
+}
+
+bool function TryPlayTitanfallNegativeSoundToPlayer( entity player )
+{
+ if( !( "lastNegativeSound" in player.s ) )
+ player.s.lastNegativeSound <- 0.0 // float
+ if( player.s.lastNegativeSound + 1.0 > Time() ) // in sound cooldown
+ return false
+
+ // use a sound to notify player they can't titanfall here
+ EmitSoundOnEntityOnlyToPlayer( player, player, "titan_dryfire" )
+ player.s.lastNegativeSound = Time()
+
+ return true
+}
+
+array<entity> function FW_GetTitanSpawnPointsForTeam( int team )
+{
+ array<entity> validSpawnPoints
+ // find team's harvester
+ entity teamHarvester = FW_GetTeamHarvesterProp( team )
+
+ array<entity> allPoints
+ // same as _replacement_titans_drop.gnut does
+ allPoints.extend( GetEntArrayByClass_Expensive( "info_spawnpoint_titan" ) )
+ allPoints.extend( GetEntArrayByClass_Expensive( "info_spawnpoint_titan_start" ) )
+ allPoints.extend( GetEntArrayByClass_Expensive( "info_replacement_titan_spawn" ) )
+
+ // get valid points from all points
+ foreach( entity point in allPoints )
+ {
+ if( Distance2D( point.GetOrigin(), teamHarvester.GetOrigin() ) <= FW_SPAWNPOINT_SEARCH_RADIUS )
+ validSpawnPoints.append( point )
+ }
+
+ return validSpawnPoints
+}
+
+// some maps have reversed startpoints! we need a hack
+const array<string> TITAN_POINT_REVERSED_MAPS =
+[
+ "mp_grave"
+]
+
+// "Respawn as Titan" don't follow the rateSpawnPoints, fix it manually
+entity function FW_ForcedTitanStartPoint( entity player, entity basePoint )
+{
+ int team = player.GetTeam()
+ if ( TITAN_POINT_REVERSED_MAPS.contains( GetMapName() ) )
+ team = GetOtherTeam( player.GetTeam() )
+ array<entity> startPoints = SpawnPoints_GetTitanStart( team )
+ entity validPoint = startPoints[ RandomInt( startPoints.len() ) ] // choose a random( maybe not safe ) start point
+ return validPoint
+}
+
+////////////////////////////////////
+///// TITANSPAWN FUNCTIONS END /////
+////////////////////////////////////
+
+
+
+/////////////////////////////////
+///// THREATLEVEL FUNCTIONS /////
+/////////////////////////////////
+
+void function FWAreaThreatLevelThink()
+{
+ thread FWAreaThreatLevelThink_Threaded()
+}
+
+void function FWAreaThreatLevelThink_Threaded()
+{
+ entity imcTerritory
+ entity mltTerritory
+ foreach( entity territory in file.fwTerritories )
+ {
+ if( territory.GetTeam() == TEAM_IMC )
+ imcTerritory = territory
+ else
+ mltTerritory = territory
+ }
+
+ float lastWarningTime // for debounce
+ bool warnImcTitanApproach
+ bool warnMltTitanApproach
+ bool warnImcTitanInArea
+ bool warnMltTitanInArea
+
+ while( GamePlayingOrSuddenDeath() )
+ {
+ //print( " imc threat level is: " + string( GetGlobalNetInt( "imcTowerThreatLevel" ) ) )
+ //print( " mlt threat level is: " + string( GetGlobalNetInt( "milTowerThreatLevel" ) ) )
+ float imcLastDamage = fw_harvesterImc.lastDamage
+ float mltLastDamage = fw_harvesterMlt.lastDamage
+ bool imcShieldDown = fw_harvesterImc.harvesterShieldDown
+ bool mltShieldDown = fw_harvesterMlt.harvesterShieldDown
+
+ // imc threatLevel
+ if( imcLastDamage + FW_HARVESTER_DAMAGED_DEBOUNCE >= Time() && imcShieldDown )
+ SetGlobalNetInt( "imcTowerThreatLevel", 3 ) // 3 will show a "harvester being damaged" warning to player
+ else if( warnImcTitanInArea )
+ SetGlobalNetInt( "imcTowerThreatLevel", 2 ) // 2 will show a "titan in area" warning to player
+ else if( warnImcTitanApproach )
+ SetGlobalNetInt( "imcTowerThreatLevel", 1 ) // 1 will show a "titan approach" waning to player
+ else
+ SetGlobalNetInt( "imcTowerThreatLevel", 0 ) // 0 will hide all warnings
+
+ // militia threatLevel
+ if( mltLastDamage + FW_HARVESTER_DAMAGED_DEBOUNCE >= Time() && mltShieldDown )
+ SetGlobalNetInt( "milTowerThreatLevel", 3 ) // 3 will show a "harvester being damaged" warning to player
+ else if( warnMltTitanInArea )
+ SetGlobalNetInt( "milTowerThreatLevel", 2 ) // 2 will show a "titan in area" warning to player
+ else if( warnMltTitanApproach )
+ SetGlobalNetInt( "milTowerThreatLevel", 1 ) // 1 will show a "titan approach" waning to player
+ else
+ SetGlobalNetInt( "milTowerThreatLevel", 0 ) // 0 will hide all warnings
+
+
+ // clean it here
+ warnImcTitanInArea = false
+ warnMltTitanInArea = false
+ warnImcTitanApproach = false
+ warnMltTitanApproach = false
+
+ // get valid titans
+ array<entity> allTitans = GetNPCArrayByClass( "npc_titan" )
+ array<entity> allPlayers = GetPlayerArray()
+ foreach( entity player in allPlayers )
+ {
+ if( IsAlive( player ) && player.IsTitan() )
+ {
+ allTitans.append( player )
+ }
+ }
+
+ // check threats
+ array<entity> imcEntArray = GetAllEntitiesInTrigger( imcTerritory )
+ array<entity> mltEntArray = GetAllEntitiesInTrigger( mltTerritory )
+ imcEntArray.removebyvalue( null ) // since we're using a fake trigger, need to check this
+ mltEntArray.removebyvalue( null )
+ foreach( entity ent in imcEntArray )
+ {
+ //print( ent )
+ if( !IsValid( ent ) ) // since we're using a fake trigger, need to check this
+ continue
+ if( ent.IsPlayer() || ent.IsNPC() )
+ {
+ if( ent.IsTitan() && ent.GetTeam() != TEAM_IMC )
+ warnImcTitanInArea = true
+ }
+ }
+ foreach( entity ent in mltEntArray )
+ {
+ //print( ent )
+ if( !IsValid( ent ) ) // since we're using a fake trigger, need to check this
+ continue
+ if( ent.IsPlayer() || ent.IsNPC() )
+ {
+ if( ent.IsTitan() && ent.GetTeam() != TEAM_MILITIA )
+ warnMltTitanInArea = true
+ }
+ }
+
+ foreach( entity titan in allTitans )
+ {
+ if( !imcEntArray.contains( titan )
+ && !mltEntArray.contains( titan )
+ && titan.GetTeam() != TEAM_IMC
+ && !titan.e.isHotDropping )
+ warnImcTitanApproach = true // this titan must be in natural space
+
+ if( !mltEntArray.contains( titan )
+ && !imcEntArray.contains( titan )
+ && titan.GetTeam() != TEAM_MILITIA
+ && !titan.e.isHotDropping )
+ warnMltTitanApproach = true // this titan must be in natural space
+ }
+
+ WaitFrame()
+ }
+}
+
+/////////////////////////////////////
+///// THREATLEVEL FUNCTIONS END /////
+/////////////////////////////////////
+
+
+
+////////////////////////////
+///// TURRET FUNCTIONS /////
+////////////////////////////
+
+void function OnFWTurretSpawned( entity turret )
+{
+ turret.EnableTurret() // always enabled
+ SetDefaultMPEnemyHighlight( turret ) // for sonar highlights to work
+ AddEntityCallback_OnDamaged( turret, OnMegaTurretDamaged )
+ thread FWTurretHighlightThink( turret )
+}
+
+// this will clear turret's highlight upon their death, for notifying players to fix them
+void function FWTurretHighlightThink( entity turret )
+{
+ turret.EndSignal( "OnDestroy" )
+ WaitFrame() // wait a frame for other turret spawn options to set up
+ Highlight_SetFriendlyHighlight( turret, "fw_friendly" ) // initialize the highlight, they will show upon player's next respawn
+
+ turret.WaitSignal( "OnDeath" )
+ Highlight_ClearFriendlyHighlight( turret )
+}
+
+// for battery_port, replace the turret with new one
+entity function FW_ReplaceMegaTurret( entity perviousTurret )
+{
+ if( !IsValid( perviousTurret ) ) // previous turret not exist!
+ return
+
+ entity turret = CreateNPC( "npc_turret_mega", perviousTurret.GetTeam(), perviousTurret.GetOrigin(), perviousTurret.GetAngles() )
+ SetSpawnOption_AISettings( turret, "npc_turret_mega_fortwar" )
+ DispatchSpawn( turret )
+
+ // apply settings to new turret, must up on date
+ turret.s.baseTurret <- perviousTurret.s.baseTurret
+ turret.s.minimapstate <- perviousTurret.s.minimapstate
+ turret.s.turretflagid <- perviousTurret.s.turretflagid
+ turret.s.lastDamagedTime <- perviousTurret.s.lastDamagedTime
+ turret.s.relatedBatteryPort <- perviousTurret.s.relatedBatteryPort
+
+ int maxHealth = perviousTurret.GetMaxHealth()
+ int maxShield = perviousTurret.GetShieldHealthMax()
+ turret.SetMaxHealth( maxHealth )
+ turret.SetHealth( maxHealth )
+ turret.SetShieldHealth( maxShield )
+ turret.SetShieldHealthMax( maxShield )
+
+ // update turretSiteStruct
+ foreach( TurretSiteStruct turretsite in file.turretsites )
+ {
+ if( turretsite.turret == perviousTurret )
+ {
+ turretsite.turret = turret // only changed this
+ }
+ }
+
+ perviousTurret.Destroy() // destroy previous one
+
+ return turret
+}
+
+// avoid notifications overrides itself
+const float TURRET_NOTIFICATION_DEBOUNCE = 10.0
+
+void function OnMegaTurretDamaged( entity turret, var damageInfo )
+{
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+ int scriptType = DamageInfo_GetCustomDamageType( damageInfo )
+ int turretTeam = turret.GetTeam()
+
+ if ( !damageSourceID && !damageAmount && !attacker )
+ return
+
+ if( turret.GetShieldHealth() - damageAmount <= 0 && scriptType != damageTypes.rodeoBatteryRemoval ) // this shot breaks shield
+ {
+ if ( !attacker.IsTitan() && !IsSuperSpectre( attacker ) )
+ {
+ if( attacker.IsPlayer() && attacker.GetTeam() != turret.GetTeam() ) // good to have
+ {
+ // avoid notifications overrides itself
+ if( attacker.s.lastTurretNotifyTime + TURRET_NOTIFICATION_DEBOUNCE < Time() )
+ {
+ MessageToPlayer( attacker, eEventNotifications.Clear ) // clean up last message
+ MessageToPlayer( attacker, eEventNotifications.TurretTitanDamageOnly )
+ attacker.s.lastTurretNotifyTime = Time()
+ }
+ }
+ DamageInfo_SetDamage( damageInfo, turret.GetShieldHealth() ) // destroy shields
+ return
+ }
+ }
+
+ // successfully damaged turret
+ turret.s.lastDamagedTime = Time()
+
+ if ( damageSourceID == eDamageSourceId.mp_titanweapon_heat_shield ||
+ damageSourceID == eDamageSourceId.mp_titanweapon_meteor_thermite ||
+ damageSourceID == eDamageSourceId.mp_titanweapon_flame_wall ||
+ damageSourceID == eDamageSourceId.mp_titanability_slow_trap ||
+ damageSourceID == eDamageSourceId.mp_titancore_flame_wave_secondary
+ ) // scorch's thermite damages
+ DamageInfo_SetDamage( damageInfo, DamageInfo_GetDamage( damageInfo )/2 ) // nerf scorch
+
+ // faction dialogue
+ damageAmount = DamageInfo_GetDamage( damageInfo )
+ if( turret.GetHealth() - damageAmount <= 0 ) // turret killed this shot
+ {
+ if( GamePlayingOrSuddenDeath() )
+ PlayFactionDialogueToTeam( "fortwar_turretDestroyedFriendly", turretTeam )
+ }
+}
+
+void function InitTurretSettings()
+{
+ foreach( TurretSiteStruct turretSite in file.turretsites )
+ {
+ entity turret = turretSite.turret
+ entity minimapstate = turretSite.minimapstate
+ int teamNum = turretSite.site.GetTeam()
+ int id = int( string( turretSite.site.kv.turretId ) )
+ string idString = string( id + 1 )
+ int team = int( string( turretSite.site.kv.teamnumber ) )
+
+ int stateFlag = 1 // netural
+
+ // spawn with teamNumber?
+ if( team == TEAM_IMC || team == TEAM_MILITIA )
+ turret.s.baseTurret = true
+
+ //SetTeam( minimapstate, team ) // setTeam() for icons is done in TurretStateWatcher()
+ SetTeam( turret, team )
+
+ //print( "Try to set globatNetEnt: " + "turretSite" + idString )
+
+ turret.s.turretflagid = idString
+ turretSite.turretflagid = idString
+
+ thread TurretStateWatcher( turretSite )
+ }
+}
+
+// about networkvar "turretStateFlags" value
+// 1 means destoryed/netural
+// 2 means imc turret
+// 4 means mlt turret
+// 10 means shielded imc turret
+// 13 means shielded mlt turret
+// 16 means destoryed/netural being attacked
+// 18 means imc turret being attacked
+// 20 means mlt turret being attacked
+// 26 means shielded imc turret being attacked
+// 28 means shielded mlt turret being attacked
+
+// unsure:
+// 24 means destroyed imc turret being attacked?
+// 40 means destroyed imc turret?
+// 48 means destroyed mlt turret being attacked?
+
+const int TURRET_DESTROYED_FLAG = 1
+const int TURRET_NATURAL_FLAG = 1
+const int TURRET_IMC_FLAG = 2
+const int TURRET_MLT_FLAG = 4
+const int TURRET_SHIELDED_IMC_FLAG = 10
+const int TURRET_SHIELDED_MLT_FLAG = 13
+
+const int TURRET_UNDERATTACK_NATURAL_FLAG = 16
+const int TURRET_UNDERATTACK_IMC_FLAG = 18
+const int TURRET_UNDERATTACK_MLT_FLAG = 20
+// natural turret noramlly can't get shielded
+const int TURRET_SHIELDED_UNDERATTACK_IMC_FLAG = 26
+const int TURRET_SHIELDED_UNDERATTACK_MLT_FLAG = 28
+
+void function TurretStateWatcher( TurretSiteStruct turretSite )
+{
+ entity mapIcon = turretSite.minimapstate
+ entity turret = turretSite.turret
+ entity batteryPort = expect entity( turret.s.relatedBatteryPort )
+
+ int turretHealth = GetCurrentPlaylistVarInt( "fw_turret_health", FW_DEFAULT_TURRET_HEALTH )
+ int turretShield = GetCurrentPlaylistVarInt( "fw_turret_shield", FW_DEFAULT_TURRET_SHIELD )
+ turret.SetMaxHealth( turretHealth )
+ turret.SetHealth( turretHealth )
+ turret.SetShieldHealthMax( turretShield )
+
+ string idString = turretSite.turretflagid
+ string siteVarName = "turretSite" + idString
+ string stateVarName = "turretStateFlags" + idString
+
+ // battery overlay icons holder
+ entity overlayState = CreateEntity( "prop_script" )
+ overlayState.SetValueForModelKey( $"models/communication/flag_base.mdl" ) // requires a model to show overlays
+ overlayState.Hide() // this can still show players overlay icons
+ overlayState.SetOrigin( batteryPort.GetOrigin() ) // tracking batteryPort's positions
+ overlayState.SetAngles( batteryPort.GetAngles() )
+ overlayState.kv.solid = SOLID_VPHYSICS
+ DispatchSpawn( overlayState )
+
+ svGlobal.levelEnt.EndSignal( "CleanUpEntitiesForRoundEnd" ) // end dialogues is good
+ mapIcon.EndSignal( "OnDestroy" ) // mapIcon should be valid all time, tracking it
+ batteryPort.EndSignal( "OnDestroy" ) // also track this
+ overlayState.EndSignal( "OnDestroy" )
+
+ SetGlobalNetEnt( siteVarName, overlayState ) // tracking batteryPort's positions and team
+ SetGlobalNetInt( stateVarName, TURRET_NATURAL_FLAG ) // init for all turrets
+
+ int lastFrameTeam
+ bool lastFrameIsAlive
+
+ while( true )
+ {
+ WaitFrame() // start of the loop
+
+ turret = turretSite.turret // need to keep updating, for sometimes it being replaced
+
+ if( !IsValid( turret ) ) // replacing turret this frame
+ continue // skip the loop once
+
+ bool isBaseTurret = expect bool( turret.s.baseTurret )
+ int turretTeam = turret.GetTeam()
+ bool turretAlive = IsAlive( turret )
+
+ bool changedTeamThisFrame = lastFrameTeam != turretTeam // turret has changed team?
+ bool killedThisFrame = lastFrameIsAlive != turretAlive // turret has no health left?
+
+ if( !turretAlive ) // turret down, waiting to be repaired
+ {
+ if( !isBaseTurret ) // never reset base turret's team
+ {
+ SetTeam( turret, TEAM_UNASSIGNED )
+ SetTeam( mapIcon, TEAM_UNASSIGNED )
+ SetTeam( batteryPort, TEAM_UNASSIGNED )
+ SetTeam( overlayState, TEAM_UNASSIGNED )
+ batteryPort.SetUsableByGroup( "pilot" ) // show hints to any pilot
+ }
+ SetGlobalNetInt( stateVarName, TURRET_DESTROYED_FLAG )
+ continue
+ }
+
+ // wrong dialogue, it will say "The turret you requested is on the way"
+ //if( changedTeamThisFrame ) // has been hacked!
+ // PlayFactionDialogueToTeam( "fortwar_turretDeployFriendly", turretTeam )
+
+ int iconTeam = turretTeam == TEAM_BOTH ? TEAM_UNASSIGNED : turretTeam // specific check
+ SetTeam( mapIcon, iconTeam ) // update icon's team
+ SetTeam( batteryPort, turretTeam ) // update batteryPort's team
+ SetTeam( overlayState, iconTeam ) // update overlayEnt's team
+
+ if( turretTeam != TEAM_BOTH && turretTeam != TEAM_UNASSIGNED ) // not a natural turret nor dead
+ batteryPort.SetUsableByGroup( "friendlies pilot" ) // only show hint to friendlies
+
+ float lastDamagedTime = expect float( turret.s.lastDamagedTime )
+ int stateFlag = TURRET_NATURAL_FLAG
+
+ // imc states
+ if( iconTeam == TEAM_IMC )
+ {
+ if( lastDamagedTime + FW_TURRET_DAMAGED_DEBOUNCE >= Time() ) // recent underattack
+ {
+ if( turret.GetShieldHealth() > 0 ) // has shields
+ stateFlag = TURRET_SHIELDED_UNDERATTACK_IMC_FLAG
+ else
+ stateFlag = TURRET_UNDERATTACK_IMC_FLAG
+
+ // these dialogue have 30s debounce inside
+ if( isBaseTurret )
+ PlayFactionDialogueToTeam( "fortwar_baseTurretsUnderAttack", TEAM_IMC )
+ else
+ PlayFactionDialogueToTeam( "fortwar_awayTurretsUnderAttack", TEAM_IMC )
+ }
+ else if( turret.GetShieldHealth() > 0 ) // has shields left
+ stateFlag = TURRET_SHIELDED_IMC_FLAG
+ else
+ stateFlag = TURRET_IMC_FLAG
+ }
+
+ // mlt states
+ if( iconTeam == TEAM_MILITIA )
+ {
+ if( lastDamagedTime + FW_TURRET_DAMAGED_DEBOUNCE >= Time() ) // recent underattack
+ {
+ if( turret.GetShieldHealth() > 0 ) // has shields
+ stateFlag = TURRET_SHIELDED_UNDERATTACK_MLT_FLAG
+ else
+ stateFlag = TURRET_UNDERATTACK_MLT_FLAG
+
+ // these dialogue have 30s debounce inside
+ if( isBaseTurret )
+ PlayFactionDialogueToTeam( "fortwar_baseTurretsUnderAttack", TEAM_MILITIA )
+ else
+ PlayFactionDialogueToTeam( "fortwar_awayTurretsUnderAttack", TEAM_MILITIA )
+ }
+ else if( turret.GetShieldHealth() > 0 ) // has shields left
+ stateFlag = TURRET_SHIELDED_MLT_FLAG
+ else
+ stateFlag = TURRET_MLT_FLAG
+ }
+
+ // natural states
+ if( iconTeam == TEAM_UNASSIGNED )
+ {
+ if( lastDamagedTime + FW_TURRET_DAMAGED_DEBOUNCE >= Time() ) // recent underattack
+ stateFlag = TURRET_UNDERATTACK_NATURAL_FLAG
+ else
+ stateFlag = TURRET_NATURAL_FLAG
+ }
+
+ SetGlobalNetInt( stateVarName, stateFlag )
+
+ // update these
+ lastFrameTeam = turretTeam
+ lastFrameIsAlive = turretAlive
+
+ WaitFrame()
+ }
+}
+
+////////////////////////////////
+///// TURRET FUNCTIONS END /////
+////////////////////////////////
+
+
+
+///////////////////////////////
+///// HARVESTER FUNCTIONS /////
+///////////////////////////////
+
+void function startFWHarvester()
+{
+ thread HarvesterThink(fw_harvesterImc)
+ thread HarvesterAlarm(fw_harvesterImc)
+ thread UpdateHarvesterHealth( TEAM_IMC )
+
+ thread HarvesterThink(fw_harvesterMlt)
+ thread HarvesterAlarm(fw_harvesterMlt)
+ thread UpdateHarvesterHealth( TEAM_MILITIA )
+}
+
+entity function FW_GetTeamHarvesterProp( int team )
+{
+ if( team == TEAM_IMC )
+ return fw_harvesterImc.harvester
+ else if( team == TEAM_MILITIA )
+ return fw_harvesterMlt.harvester
+
+ unreachable // crash the game
+}
+
+void function FW_createHarvester()
+{
+ // imc havester spawn
+ fw_harvesterImc = SpawnHarvester( file.harvesterImc_info.GetOrigin(), file.harvesterImc_info.GetAngles(), GetCurrentPlaylistVarInt( "fw_harvester_health", FW_DEFAULT_HARVESTER_HEALTH ), GetCurrentPlaylistVarInt( "fw_harvester_shield", FW_DEFAULT_HARVESTER_SHIELD ), TEAM_IMC )
+ fw_harvesterImc.harvester.SetArmorType( ARMOR_TYPE_HEAVY )
+ fw_harvesterImc.harvester.Minimap_SetAlignUpright( true )
+ fw_harvesterImc.harvester.Minimap_AlwaysShow( TEAM_IMC, null )
+ fw_harvesterImc.harvester.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ fw_harvesterImc.harvester.Minimap_SetHeightTracking( true )
+ fw_harvesterImc.harvester.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ fw_harvesterImc.harvester.Minimap_SetCustomState( eMinimapObject_prop_script.FD_HARVESTER )
+ AddEntityCallback_OnFinalDamaged( fw_harvesterImc.harvester, OnHarvesterDamaged )
+ AddEntityCallback_OnPostDamaged( fw_harvesterImc.harvester, OnHarvesterPostDamaged )
+
+ // imc havester settings
+ // don't set this, or sonar pulse will try to find it and failed to set highlight
+ //fw_harvesterMlt.harvester.SetScriptName("fw_team_tower")
+ file.harvesters.append(fw_harvesterImc)
+ entity trackerImc = GetAvailableBaseLocationTracker()
+ trackerImc.SetOwner( fw_harvesterImc.harvester )
+ DispatchSpawn( trackerImc )
+ SetLocationTrackerRadius( trackerImc, 1 ) // whole map
+
+ // scores starts from 100, TeamScore means harvester health; TeamScore2 means shield bar
+ GameRules_SetTeamScore( TEAM_MILITIA , 100 )
+ GameRules_SetTeamScore2( TEAM_MILITIA , 100 )
+
+
+ // mlt havester spawn
+ fw_harvesterMlt = SpawnHarvester( file.harvesterMlt_info.GetOrigin(), file.harvesterMlt_info.GetAngles(), GetCurrentPlaylistVarInt( "fw_harvester_health", FW_DEFAULT_HARVESTER_HEALTH ), GetCurrentPlaylistVarInt( "fw_harvester_shield", FW_DEFAULT_HARVESTER_SHIELD ), TEAM_MILITIA )
+ fw_harvesterMlt.harvester.SetArmorType( ARMOR_TYPE_HEAVY )
+ fw_harvesterMlt.harvester.Minimap_SetAlignUpright( true )
+ fw_harvesterMlt.harvester.Minimap_AlwaysShow( TEAM_IMC, null )
+ fw_harvesterMlt.harvester.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ fw_harvesterMlt.harvester.Minimap_SetHeightTracking( true )
+ fw_harvesterMlt.harvester.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ fw_harvesterMlt.harvester.Minimap_SetCustomState( eMinimapObject_prop_script.FD_HARVESTER )
+ AddEntityCallback_OnFinalDamaged( fw_harvesterMlt.harvester, OnHarvesterDamaged )
+ AddEntityCallback_OnPostDamaged( fw_harvesterMlt.harvester, OnHarvesterPostDamaged )
+
+ // mlt havester settings
+ // don't set this, or sonar pulse will try to find it and failed to set highlight
+ //fw_harvesterImc.harvester.SetScriptName("fw_team_tower")
+ file.harvesters.append(fw_harvesterMlt)
+ entity trackerMlt = GetAvailableBaseLocationTracker()
+ trackerMlt.SetOwner( fw_harvesterMlt.harvester )
+ DispatchSpawn( trackerMlt )
+ SetLocationTrackerRadius( trackerMlt, 1 ) // whole map
+
+ // scores starts from 100, TeamScore means harvester health; TeamScore2 means shield bar
+ GameRules_SetTeamScore( TEAM_IMC , 100 )
+ GameRules_SetTeamScore2( TEAM_IMC , 100 )
+
+ InitHarvesterDamageMods()
+}
+
+void function FW_AddHarvesterDamageSourceModifier( int id, float mod )
+{
+ if ( !( id in file.harvesterDamageSourceMods ) )
+ file.harvesterDamageSourceMods[id] <- 1.0
+
+ file.harvesterDamageSourceMods[id] *= mod
+}
+
+void function FW_RemoveHarvesterDamageSourceModifier( int id, float mod )
+{
+ if ( !( id in file.harvesterDamageSourceMods ) )
+ return
+
+ file.harvesterDamageSourceMods[id] /= mod
+
+ if ( file.harvesterDamageSourceMods[id] == 1.0 )
+ delete file.harvesterDamageSourceMods[id]
+}
+
+void function InitHarvesterDamageMods()
+{
+ // Damage balancing
+ const float CORE_DAMAGE_FRAC = 0.67
+ const float NUKE_EJECT_DAMAGE_FRAC = 0.25
+ const float DOT_DAMAGE_FRAC = 0.5
+
+ // Core balancing
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titancore_laser_cannon, CORE_DAMAGE_FRAC )
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titancore_salvo_core, CORE_DAMAGE_FRAC )
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titanweapon_flightcore_rockets, CORE_DAMAGE_FRAC )
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titancore_shift_core, CORE_DAMAGE_FRAC )
+ // Flame Core is not included since its single target damage is low compared to the others
+
+ // Nuke eject balancing
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.damagedef_nuclear_core, NUKE_EJECT_DAMAGE_FRAC )
+
+ // Damage over time balancing
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titanweapon_dumbfire_rockets, DOT_DAMAGE_FRAC )
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titanweapon_meteor_thermite, DOT_DAMAGE_FRAC )
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titanweapon_flame_wall, DOT_DAMAGE_FRAC )
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titanability_slow_trap, DOT_DAMAGE_FRAC )
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titancore_flame_wave_secondary, DOT_DAMAGE_FRAC )
+ FW_AddHarvesterDamageSourceModifier( eDamageSourceId.mp_titanweapon_heat_shield, DOT_DAMAGE_FRAC )
+}
+
+// this function can't handle specific damageSourceID, such as plasma railgun, but is the best to scale both shield and health damage
+void function OnHarvesterDamaged( entity harvester, var damageInfo )
+{
+ if ( !IsValid( harvester ) )
+ return
+
+ // Entities (non-Players and non-NPCs) don't consider damaged entity lists, which makes ground attacks (e.g. Arc Wave) and thermite hit more than they should
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ if ( IsValid( inflictor ) && ( inflictor.e.onlyDamageEntitiesOnce || inflictor.e.onlyDamageEntitiesOncePerTick ) )
+ {
+ if ( inflictor.e.damagedEntities.contains( harvester ) )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+ else
+ {
+ inflictor.e.damagedEntities.append( harvester )
+ }
+ }
+
+ int friendlyTeam = harvester.GetTeam()
+ int enemyTeam = GetOtherTeam( friendlyTeam )
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ HarvesterStruct harvesterstruct // current harveter's struct
+ if( friendlyTeam == TEAM_MILITIA )
+ harvesterstruct = fw_harvesterMlt
+ if( friendlyTeam == TEAM_IMC )
+ harvesterstruct = fw_harvesterImc
+
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+ if((harvester.GetShieldHealth()-damageAmount)<0)
+ {
+ if( !harvesterstruct.harvesterShieldDown )
+ {
+ PlayFactionDialogueToTeam( "fortwar_baseShieldDownFriendly", friendlyTeam )
+ PlayFactionDialogueToTeam( "fortwar_baseShieldDownEnemy", enemyTeam )
+ harvesterstruct.harvesterShieldDown = true // prevent shield dialogues from repeating
+ }
+ }
+
+ // always reset harvester's recharge delay
+ harvesterstruct.lastDamage = Time()
+
+ // Should be moved to a final damage callback once those are added
+ if ( damageSourceID in file.harvesterDamageSourceMods )
+ DamageInfo_ScaleDamage( damageInfo, file.harvesterDamageSourceMods[ damageSourceID ] )
+}
+void function OnHarvesterPostDamaged( entity harvester, var damageInfo )
+{
+ if ( !IsValid( harvester ) )
+ return
+
+ int friendlyTeam = harvester.GetTeam()
+ int enemyTeam = GetOtherTeam( friendlyTeam )
+
+ GameRules_SetTeamScore( friendlyTeam , 1.0 * GetHealthFrac( harvester ) * 100 )
+
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ int scriptType = DamageInfo_GetCustomDamageType( damageInfo )
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+
+ if(damageAmount == 0)
+ return
+
+ if ( !damageSourceID && !damageAmount && !attacker ) // actually not dealing any damage?
+ return
+
+ // prevent player from sniping the harvester cross-map
+ if ( attacker.IsPlayer() && !FW_IsPlayerInEnemyTerritory( attacker ) )
+ {
+ Remote_CallFunction_NonReplay( attacker , "ServerCallback_FW_NotifyNeedsEnterEnemyArea" )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ DamageInfo_SetCustomDamageType( damageInfo, scriptType | DF_NO_INDICATOR ) // hide the hitmarker
+ return // these damage won't do anything to the harvester
+ }
+
+ HarvesterStruct harvesterstruct // current harveter's struct
+ if( friendlyTeam == TEAM_MILITIA )
+ harvesterstruct = fw_harvesterMlt
+ if( friendlyTeam == TEAM_IMC )
+ harvesterstruct = fw_harvesterImc
+
+
+ damageAmount = DamageInfo_GetDamage( damageInfo ) // get damageAmount again after all damage adjustments
+
+ if ( !attacker.IsTitan() )
+ {
+ if( attacker.IsPlayer() )
+ Remote_CallFunction_NonReplay( attacker , "ServerCallback_FW_NotifyTitanRequired" )
+ DamageInfo_SetDamage( damageInfo, harvester.GetShieldHealth() )
+ damageAmount = 0 // never damage haveter's prop
+ }
+
+ if( !harvesterstruct.harvesterShieldDown )
+ {
+ PlayFactionDialogueToTeam( "fortwar_baseShieldDownFriendly", friendlyTeam )
+ PlayFactionDialogueToTeam( "fortwar_baseShieldDownEnemy", enemyTeam )
+ harvesterstruct.harvesterShieldDown = true // prevent shield dialogues from repeating
+ }
+
+ harvesterstruct.harvesterDamageTaken = harvesterstruct.harvesterDamageTaken + damageAmount // track damage for wave recaps
+ float newHealth = harvester.GetHealth() - damageAmount
+ float oldhealthpercent = ( ( harvester.GetHealth().tofloat() / harvester.GetMaxHealth() ) * 100 )
+ float healthpercent = ( ( newHealth / harvester.GetMaxHealth() ) * 100 )
+
+ if (healthpercent <= 75 && oldhealthpercent > 75) // we don't want the dialogue to keep saying "Harvester is below 75% health" everytime they take additional damage
+ {
+ PlayFactionDialogueToTeam( "fortwar_baseDmgFriendly75", friendlyTeam )
+ PlayFactionDialogueToTeam( "fortwar_baseDmgEnemy75", enemyTeam )
+ }
+
+ if (healthpercent <= 50 && oldhealthpercent > 50)
+ {
+ PlayFactionDialogueToTeam( "fortwar_baseDmgFriendly50", friendlyTeam )
+ PlayFactionDialogueToTeam( "fortwar_baseDmgEnemy50", enemyTeam )
+ }
+
+ if (healthpercent <= 25 && oldhealthpercent > 25)
+ {
+ PlayFactionDialogueToTeam( "fortwar_baseDmgFriendly25", friendlyTeam )
+ PlayFactionDialogueToTeam( "fortwar_baseDmgEnemy25", enemyTeam )
+ }
+
+ if( newHealth <= 0 )
+ {
+ EmitSoundAtPosition(TEAM_UNASSIGNED,harvesterstruct.harvester.GetOrigin(),"coop_generator_destroyed")
+ newHealth = 0
+ harvesterstruct.rings.Destroy()
+ harvesterstruct.harvester.Dissolve( ENTITY_DISSOLVE_CORE, Vector( 0, 0, 0 ), 500 )
+ }
+
+ harvester.SetHealth( newHealth )
+ harvesterstruct.havesterWasDamaged = true
+
+ if ( attacker.IsPlayer() )
+ {
+ // dialogue for enemy attackers
+ if( !harvesterstruct.harvesterShieldDown )
+ PlayFactionDialogueToTeam( "fortwar_baseEnemyAllyAttacking", enemyTeam )
+
+ attacker.NotifyDidDamage( harvester, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamagePosition( damageInfo ), DamageInfo_GetCustomDamageType( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageFlags( damageInfo ), DamageInfo_GetHitGroup( damageInfo ), DamageInfo_GetWeapon( damageInfo ), DamageInfo_GetDistFromAttackOrigin( damageInfo ) )
+
+ // get newest damage for adding score!
+ int scoreDamage = int( DamageInfo_GetDamage( damageInfo ) )
+ // score events
+ attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, scoreDamage )
+
+ // add to player structs
+ file.playerDamageHarvester[ attacker ].recentDamageTime = Time()
+ file.playerDamageHarvester[ attacker ].storedDamage += scoreDamage
+
+ // enough to earn score?
+ if( file.playerDamageHarvester[ attacker ].storedDamage >= FW_HARVESTER_DAMAGE_SEGMENT )
+ {
+ AddPlayerScore( attacker, "FortWarTowerDamage", attacker )
+ attacker.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_FW_TOWER_DAMAGE )
+ file.playerDamageHarvester[ attacker ].storedDamage -= FW_HARVESTER_DAMAGE_SEGMENT // reset stored damage
+ }
+ }
+
+ // harvester down!
+ if ( harvester.GetHealth() == 0 )
+ {
+ // force deciding winner
+ SetWinner( enemyTeam )
+ //PlayFactionDialogueToTeam( "scoring_wonMercy", enemyTeam )
+ //PlayFactionDialogueToTeam( "fortwar_matchLoss", friendlyTeam )
+ GameRules_SetTeamScore2( friendlyTeam, 0 ) // force set score2 to 0( shield bar will empty )
+ GameRules_SetTeamScore( friendlyTeam, 0 ) // force set score to 0( health 0% )
+ }
+}
+
+void function HarvesterThink( HarvesterStruct fw_harvester )
+{
+ entity harvester = fw_harvester.harvester
+
+
+ EmitSoundOnEntity( harvester, "coop_generator_startup" )
+
+ float lastTime = Time()
+ wait 4
+ int lastShieldHealth = harvester.GetShieldHealth()
+ generateBeamFX( fw_harvester )
+ generateShieldFX( fw_harvester )
+
+ EmitSoundOnEntity( harvester, "coop_generator_ambient_healthy" )
+
+ bool isRegening = false // stops the regenning sound to keep stacking on top of each other
+
+ while ( IsAlive( harvester ) )
+ {
+ float currentTime = Time()
+ float deltaTime = currentTime -lastTime
+
+ if ( IsValid( fw_harvester.particleShield ) )
+ {
+ vector shieldColor = GetShieldTriLerpColor( 1.0 - ( harvester.GetShieldHealth().tofloat() / harvester.GetShieldHealthMax().tofloat() ) )
+ EffectSetControlPointVector( fw_harvester.particleShield, 1, shieldColor )
+ }
+
+ if( IsValid( fw_harvester.particleBeam ) )
+ {
+ vector beamColor = GetShieldTriLerpColor( 1.0 - ( harvester.GetHealth().tofloat() / harvester.GetMaxHealth().tofloat() ) )
+ EffectSetControlPointVector( fw_harvester.particleBeam, 1, beamColor )
+ }
+
+ if ( fw_harvester.harvester.GetShieldHealth() == 0 )
+ if( IsValid( fw_harvester.particleShield ) )
+ fw_harvester.particleShield.Destroy()
+
+ if ( ( ( currentTime-fw_harvester.lastDamage ) >= GetCurrentPlaylistVarFloat( "fw_harvester_regen_delay", FW_DEFAULT_HARVESTER_REGEN_DELAY ) ) && ( harvester.GetShieldHealth() < harvester.GetShieldHealthMax() ) )
+ {
+ if( !IsValid( fw_harvester.particleShield ) )
+ generateShieldFX( fw_harvester )
+
+ if( harvester.GetShieldHealth() == 0 )
+ EmitSoundOnEntity( harvester, "coop_generator_shieldrecharge_start" )
+
+ if (!isRegening)
+ {
+ EmitSoundOnEntity( harvester, "coop_generator_shieldrecharge_resume" )
+ fw_harvester.harvesterShieldDown = false
+ isRegening = true
+ }
+
+ float newShieldHealth = ( harvester.GetShieldHealthMax() / GetCurrentPlaylistVarFloat( "fw_harvester_regen_time", FW_DEFAULT_HARVESTER_REGEN_TIME ) * deltaTime ) + harvester.GetShieldHealth()
+
+ // shield full
+ if ( newShieldHealth >= harvester.GetShieldHealthMax() )
+ {
+ StopSoundOnEntity( harvester, "coop_generator_shieldrecharge_resume" )
+ harvester.SetShieldHealth( harvester.GetShieldHealthMax() )
+ EmitSoundOnEntity( harvester, "coop_generator_shieldrecharge_end" )
+ PlayFactionDialogueToTeam( "fortwar_baseShieldUpFriendly", harvester.GetTeam() )
+ isRegening = false
+ }
+ else
+ {
+ harvester.SetShieldHealth( newShieldHealth )
+ }
+ }
+ else if ( ( ( currentTime-fw_harvester.lastDamage ) < GENERATOR_SHIELD_REGEN_DELAY ) && ( harvester.GetShieldHealth() < harvester.GetShieldHealthMax() ) )
+ {
+ isRegening = false
+ }
+
+ if ( ( lastShieldHealth > 0 ) && ( harvester.GetShieldHealth() == 0 ) )
+ {
+ EmitSoundOnEntity( harvester, "TitanWar_Harvester_ShieldDown" ) // add this
+ EmitSoundOnEntity( harvester, "coop_generator_shielddown" )
+ }
+
+ lastShieldHealth = harvester.GetShieldHealth()
+ lastTime = currentTime
+ WaitFrame()
+ }
+}
+
+void function HarvesterAlarm( HarvesterStruct fw_harvester )
+{
+ while( IsAlive( fw_harvester.harvester ) )
+ {
+ if( fw_harvester.harvester.GetShieldHealth() == 0 )
+ {
+ wait EmitSoundOnEntity( fw_harvester.harvester, "coop_generator_underattack_alarm" )
+ }
+ else
+ {
+ WaitFrame()
+ }
+ }
+}
+
+void function UpdateHarvesterHealth( int team )
+{
+ entity harvester
+ if( team == TEAM_MILITIA )
+ harvester = fw_harvesterMlt.harvester
+ if( team == TEAM_IMC )
+ harvester = fw_harvesterImc.harvester
+
+ while( true )
+ {
+ if( IsValid(harvester) )
+ {
+ GameRules_SetTeamScore2( team, 1.0 * harvester.GetShieldHealth() / harvester.GetShieldHealthMax() * 100 )
+ WaitFrame()
+ }
+ else // harvester down
+ {
+ int winnerTeam = GetOtherTeam(team)
+ SetWinner( winnerTeam )
+ //PlayFactionDialogueToTeam( "scoring_wonMercy", winnerTeam )
+ //PlayFactionDialogueToTeam( "fortwar_matchLoss", team )
+ GameRules_SetTeamScore2( team, 0 ) // force set score2 to 0( shield bar will empty )
+ GameRules_SetTeamScore( team, 0 ) // force set score to 0( health 0% )
+ break
+ }
+ }
+}
+
+///////////////////////////////////
+///// HARVESTER FUNCTIONS END /////
+///////////////////////////////////
+
+
+
+//////////////////////////////////////
+///// PLAYER OBJECTIVE FUNCTIONS /////
+//////////////////////////////////////
+
+const int APPLY_BATTERY_TEXT_INDEX = 96 // notify player to use batteries on turrets
+const int EARN_TITAN_TEXT_INDEX = 100 // notify player to earn titans
+const int CALL_IN_TITAN_TEXT_INDEX = 101 // notify player to call in titans in territory
+const int EMBARK_TITAN_TEXT_INDEX = 102 // notify player to embark titans
+const int ATTACK_HARVESTER_TEXT_INDEX = 103 // notify player to attack harvester
+
+void function FWPlayerObjectiveState()
+{
+ thread FWPlayerObjectiveState_Threaded()
+}
+
+void function FWPlayerObjectiveState_Threaded()
+{
+ while( GamePlayingOrSuddenDeath() )
+ {
+ foreach( player in GetPlayerArray() )
+ {
+ entity petTitan = player.GetPetTitan()
+ entity titanSoul
+ if( IsValid( petTitan ) )
+ titanSoul = petTitan.GetTitanSoul()
+
+ if ( IsValid( GetBatteryOnBack( player ) ) )
+ player.SetPlayerNetInt( "gameInfoStatusText", APPLY_BATTERY_TEXT_INDEX )
+ else if ( IsTitanAvailable( player ) )
+ {
+ if( !player.s.notifiedTitanfall ) // first notification, also do a objective announcement
+ {
+ SetObjective( player, CALL_IN_TITAN_TEXT_INDEX )
+ player.s.notifiedTitanfall = true
+ }
+ else
+ player.SetPlayerNetInt( "gameInfoStatusText", CALL_IN_TITAN_TEXT_INDEX )
+ }
+ else if ( IsValid( petTitan ) )
+ player.SetPlayerNetInt( "gameInfoStatusText", EMBARK_TITAN_TEXT_INDEX )
+ else if ( IsAlive( player ) && !player.IsTitan() )
+ player.SetPlayerNetInt( "gameInfoStatusText", EARN_TITAN_TEXT_INDEX )
+ else if( !IsValid( titanSoul ) ) // titan died or player first embarked
+ player.s.notifiedTitanfall = false
+
+ if ( !IsAlive( player ) ) // don't show objetive for dying players
+ player.SetPlayerNetInt( "gameInfoStatusText", -1 )
+ }
+ WaitFrame()
+ }
+
+ // game entered other state, clean this
+ foreach( player in GetPlayerArray() )
+ {
+ player.SetPlayerNetInt( "gameInfoStatusText", -1 )
+ }
+}
+
+void function SetObjective( entity player, int stringid )
+{
+ Remote_CallFunction_NonReplay( player, "ServerCallback_FW_SetObjective", stringid )
+ player.SetPlayerNetInt( "gameInfoStatusText", stringid )
+}
+
+void function SetTitanObjective( entity player, entity titan )
+{
+ SetObjective( player, ATTACK_HARVESTER_TEXT_INDEX )
+}
+
+void function SetPilotObjective( entity player, entity titan )
+{
+ if( titan.GetTitanSoul().IsEjecting() ) // this time titan is ejecting
+ {
+ SetObjective( player, EARN_TITAN_TEXT_INDEX )
+ player.s.notifiedTitanfall = false
+ }
+ else
+ player.SetPlayerNetInt( "gameInfoStatusText", EMBARK_TITAN_TEXT_INDEX )
+}
+
+//////////////////////////////////////////
+///// PLAYER OBJECTIVE FUNCTIONS END /////
+//////////////////////////////////////////
+
+
+
+/////////////////////////////////
+///// BatteryPort Functions /////
+/////////////////////////////////
+
+void function FW_InitBatteryPort( entity batteryPort )
+{
+ batteryPort.kv.fadedist = 10000 // try not to fade
+ InitTurretBatteryPort( batteryPort )
+
+ batteryPort.s.relatedTurret <- null // entity, for saving batteryPort's nearest turret
+
+ entity turret = GetNearestMegaTurret( batteryPort ) // consider this is the port's related turret
+
+ bool isBaseTurret = expect bool( turret.s.baseTurret )
+ SetTeam( batteryPort, turret.GetTeam() )
+ batteryPort.s.relatedTurret = turret
+ batteryPort.s.isUsable <- FW_IsBatteryPortUsable
+ batteryPort.s.useBattery <- FW_UseBattery
+ if( isBaseTurret ) // this is a base turret!
+ {
+ batteryPort.s.hackAvaliable = false
+ batteryPort.SetUsableByGroup( "friendlies pilot" ) // only show hint to friendlies
+ } // it can never be hacked!
+
+ turret.s.relatedBatteryPort = batteryPort // do it here
+}
+
+function FW_IsBatteryPortUsable( batteryPortvar, playervar ) //actually bool function( entity, entity )
+{
+ entity batteryPort = expect entity( batteryPortvar )
+ entity player = expect entity( playervar )
+ entity turret = expect entity( batteryPort.s.relatedTurret )
+ if( !IsValid( turret ) ) // turret has been destroyed!
+ return false
+
+ // get turret's settings, decide behavior
+ bool validTeam = turret.GetTeam() == player.GetTeam() || turret.GetTeam() == TEAM_BOTH || turret.GetTeam() == TEAM_UNASSIGNED
+ bool isBaseTurret = expect bool( turret.s.baseTurret )
+ // is this port able to be hacked
+ bool portHackAvaliable = expect bool( batteryPort.s.hackAvaliable )
+
+ // player has a battery, team valid or able to hack && not a base turret
+ return ( PlayerHasBattery( player ) && ( validTeam || ( portHackAvaliable && !isBaseTurret ) ) )
+}
+
+function FW_UseBattery( batteryPortvar, playervar ) //actually void function( entity, entity )
+{
+ entity batteryPort = expect entity( batteryPortvar )
+ entity player = expect entity( playervar )
+ // change turret settings
+ entity turret = expect entity( batteryPort.s.relatedTurret ) // consider this is the port's related turret
+
+ int playerTeam = player.GetTeam()
+ bool turretReplaced = false
+ bool sameTeam = turret.GetTeam() == player.GetTeam()
+
+ if( !IsAlive( turret ) ) // turret has been killed!
+ {
+ turret = FW_ReplaceMegaTurret( turret )
+ if( !IsValid( turret ) ) // replace failed!
+ return
+ batteryPort.s.relatedTurret = turret
+ turretReplaced = true // if turret has been replaced, mostly reset team!
+ }
+
+ bool teamChanged = false
+ bool isBaseTurret = expect bool( turret.s.baseTurret )
+ if( ( !sameTeam || turretReplaced ) && !isBaseTurret ) // is there a need to change team?
+ {
+ SetTeam( turret, playerTeam )
+ teamChanged = true
+ }
+
+ // restore turret health
+ int newHealth = int ( min( turret.GetMaxHealth(), turret.GetHealth() + ( turret.GetMaxHealth() * GetCurrentPlaylistVarFloat( "fw_turret_fixed_health", TURRET_FIXED_HEALTH_PERCENTAGE ) ) ) )
+ if( turretReplaced || teamChanged ) // replaced/hacked turret will spawn with 50% health
+ newHealth = int ( turret.GetMaxHealth() * GetCurrentPlaylistVarFloat( "fw_turret_hacked_health", TURRET_HACKED_HEALTH_PERCENTAGE ) )
+ // restore turret shield
+ int newShield = int ( min( turret.GetShieldHealthMax(), turret.GetShieldHealth() + ( turret.GetShieldHealth() * GetCurrentPlaylistVarFloat( "fw_turret_fixed_shield", TURRET_FIXED_SHIELD_PERCENTAGE ) ) ) )
+ if( turretReplaced || teamChanged ) // replaced/hacked turret will spawn with 50% shield
+ newShield = int ( turret.GetShieldHealthMax() * GetCurrentPlaylistVarFloat( "fw_turret_hacked_shield", TURRET_HACKED_SHIELD_PERCENTAGE ) )
+ // only do team score event if turret's shields down, encourage players to hack more turrets
+ bool additionalScore = turret.GetShieldHealth() <= 0
+ // this can be too much powerful
+ turret.SetHealth( newHealth )
+ turret.SetShieldHealth( newShield )
+
+ // score event
+ string scoreEvent = "FortWarForwardConstruction"
+ int secondaryScore = POINTVALUE_FW_FORWARD_CONSTRUCTION
+ if( isBaseTurret ) // this is a base turret
+ {
+ scoreEvent = "FortWarBaseConstruction"
+ secondaryScore = POINTVALUE_FW_BASE_CONSTRUCTION
+ }
+ AddPlayerScore( player, scoreEvent, player ) // player themself gets more meter
+ player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, secondaryScore )
+
+ // only do team score event if turret's shields down
+ if( additionalScore )
+ {
+ // get turrets alive, for adding scores
+ string teamTurretCount = GetTeamAliveTurretCount_ReturnString( playerTeam )
+ foreach( entity friendly in GetPlayerArrayOfTeam( playerTeam ) )
+ AddPlayerScore( friendly, "FortWarTeamTurretControlBonus_" + teamTurretCount, friendly )
+
+ PlayFactionDialogueToTeam( "fortwar_turretShieldedByFriendlyPilot", playerTeam )
+ }
+
+}
+
+// get nearest turret, consider it belongs to the port
+entity function GetNearestMegaTurret( entity ent )
+{
+ array<entity> allTurrets = GetNPCArrayByClass( "npc_turret_mega" )
+ entity turret = GetClosest( allTurrets, ent.GetOrigin() )
+ return turret
+}
+
+// this will get english name of the count, since the "FortWarTeamTurretControlBonus_" score event uses it
+string function GetTeamAliveTurretCount_ReturnString( int team )
+{
+ int turretCount
+ foreach( entity turret in GetNPCArrayByClass( "npc_turret_mega" ) )
+ {
+ if( turret.GetTeam() == team && IsAlive( turret ) )
+ turretCount += 1
+ }
+
+ switch( turretCount )
+ {
+ case 1:
+ return "One"
+ case 2:
+ return "Two"
+ case 3:
+ return "Three"
+ case 4:
+ return "Four"
+ case 5:
+ return "Five"
+ case 6:
+ return "Six"
+ }
+
+ return ""
+}
+
+/////////////////////////////////////
+///// BatteryPort Functions End /////
+/////////////////////////////////////
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut
index 8f34541b..ad46b42e 100644
--- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut
@@ -18,14 +18,6 @@ void function GamemodeGG_Init()
AddCallback_GameStateEnter( eGameState.WinnerDetermined, OnWinnerDetermined )
AddCallback_GGEarnMeterFull( OnGGEarnMeterFilled )
-
- // set scorelimit if it's wrong, sort of a jank way to do it but best i've got rn
- try
- {
- if ( GetCurrentPlaylistVarInt( "scorelimit", GetGunGameWeapons().len() ) != GetGunGameWeapons().len() )
- SetPlaylistVarOverride( "scorelimit", GetGunGameWeapons().len().tostring() )
- }
- catch ( ex ) {}
}
void function OnPlayerDisconnected(entity player)
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_hidden.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_hidden.nut
index 4d52835b..6729ff97 100644
--- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_hidden.nut
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_hidden.nut
@@ -1,5 +1,9 @@
global function GamemodeHidden_Init
+struct {
+ bool isVisible = false
+ array<entity> hiddens
+} file
void function GamemodeHidden_Init()
{
@@ -20,8 +24,7 @@ void function GamemodeHidden_Init()
AddCallback_GameStateEnter( eGameState.Postmatch, RemoveHidden )
SetTimeoutWinnerDecisionFunc( TimeoutCheckSurvivors )
- thread PredatorMain()
-
+ RegisterSignal( "VisibleNotification" )
}
void function HiddenInitPlayer( entity player )
@@ -78,7 +81,10 @@ void function MakePlayerHidden(entity player)
SetTeam( player, TEAM_IMC )
player.SetPlayerGameStat( PGS_ASSAULT_SCORE, 0 ) // reset kills
+ file.hiddens.append( player )
RespawnHidden( player )
+ thread PredatorMain( player )
+ thread VisibleNotification( player )
Remote_CallFunction_NonReplay( player, "ServerCallback_YouAreHidden" )
}
@@ -153,35 +159,66 @@ void function RemoveHidden()
}
}
-void function PredatorMain()
+void function PredatorMain( entity player )
{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+ float playerVel
+
while (true)
{
WaitFrame()
if(!IsLobby())
{
- foreach (entity player in GetPlayerArray())
+ if ( !IsValid( player ) || !IsAlive( player ) || player.GetTeam() != TEAM_IMC )
+ continue
+
+ vector playerVelV = player.GetVelocity()
+ playerVel = sqrt( playerVelV.x * playerVelV.x + playerVelV.y * playerVelV.y + playerVelV.z * playerVelV.z )
+
+ if ( playerVel/300 < 1.3 )
{
- if (player == null || !IsValid(player) || !IsAlive(player) || player.GetTeam() != TEAM_IMC)
- continue
- vector playerVelV = player.GetVelocity()
- float playerVel
- playerVel = sqrt(playerVelV.x * playerVelV.x + playerVelV.y * playerVelV.y + playerVelV.z * playerVelV.z)
- float playerVelNormal = playerVel * 0.068544
- if (playerVel/300 < 1.3)
+ player.SetCloakFlicker( 0, 0 )
+ player.kv.VisibilityFlags = 0
+ wait 0.5
+ if ( file.isVisible )
{
- player.SetCloakFlicker(0, 0)
- player.kv.VisibilityFlags = 0
- }
- else
- {
- player.SetCloakFlicker(0.2 , 1 )
- player.kv.VisibilityFlags = 0
- float waittime = RandomFloat(0.5)
- wait waittime
- player.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ file.isVisible = false
+ player.Signal( "VisibleNotification" )
}
}
+ else
+ {
+ player.SetCloakFlicker( 0.2 , 1 )
+ player.kv.VisibilityFlags = 0
+ float waittime = RandomFloat( 0.5 )
+ wait waittime
+ player.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ file.isVisible = true
+ }
+ }
+ }
+}
+
+void function VisibleNotification( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+ while (IsAlive(player))
+ {
+ WaitFrame()
+ if (!file.isVisible)
+ {
+ NSDeleteStatusMessageOnPlayer( player, "visibleTitle" )
+ NSDeleteStatusMessageOnPlayer( player, "visibleDesc" )
+ continue
+ }
+ else
+ {
+ NSCreateStatusMessageOnPlayer( player, "You are visible!", "", "visibleTitle" )
+ NSCreateStatusMessageOnPlayer( player, "Note:", "Slow down to remain invisible!", "visibleDesc" )
+ player.WaitSignal( "VisibleNotification" )
+ continue
}
}
}
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut
index 35e034cc..02f0799a 100644
--- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut
@@ -29,7 +29,7 @@ void function GamemodeInfection_Init()
void function InfectionInitPlayer( entity player )
{
- if ( GetGameState() < eGameState.Playing )
+ if ( GetGameState() < eGameState.Playing || !file.hasHadFirstInfection ) // per Gecko's suggestion, make anyone joining before first infected to stay as survivor instead
SetTeam( player, INFECTION_TEAM_SURVIVOR )
else
InfectPlayer( player )
@@ -45,7 +45,16 @@ void function SelectFirstInfectedDelayed()
wait 10.0 + RandomFloat( 5.0 )
array<entity> players = GetPlayerArray()
- entity infected = players[ RandomInt( players.len() ) ]
+
+ // End game if server empty on selecting infected
+ if ( !players.len() )
+ {
+ printt( "Couldn't select first infected: player array was empty" )
+ SetWinner( INFECTION_TEAM_SURVIVOR )
+ return
+ }
+
+ entity infected = players.getrandom()
InfectPlayer( infected )
RespawnInfected( infected )
@@ -185,6 +194,8 @@ void function SetLastSurvivor( entity player )
Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_AnnounceLastSurvivor", player.GetEncodedEHandle() )
Highlight_SetEnemyHighlight( player, "enemy_sonar" )
+ StatusEffect_AddEndless( player, eStatusEffect.sonar_detected, 1.0 ) // sonar is better here so the player themselves see the SONAR DETECTED warning.
+
if ( SpawnPoints_GetTitan().len() > 0 )
thread CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) )
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tffa.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tffa.gnut
index 30aacad5..3b75e725 100644
--- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tffa.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tffa.gnut
@@ -63,6 +63,9 @@ void function PlayerWatchesTFFAIntroIntermissionCam( entity player )
wait TFFAIntroLength
+ if ( !IsValid( player ) ) // if player leaves during the intro sequence
+ return
+
RespawnAsTitan( player, false )
TryGameModeAnnouncement( player )
}
@@ -75,4 +78,4 @@ void function AddTeamScoreForPlayerKilled( entity victim, entity attacker, var d
// why isn't this PGS_SCORE? odd game
attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 )
}
-} \ No newline at end of file
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fw_custom.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fw_custom.nut
new file mode 100644
index 00000000..68a710e8
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fw_custom.nut
@@ -0,0 +1,13 @@
+// cl_gamemode_fw already exists in vanilla game file
+// this file is to register more network vars or remote functions
+global function ServerCallback_FW_NotifyNeedsEnterEnemyArea
+
+void function ServerCallback_FW_NotifyNeedsEnterEnemyArea()
+{
+ AnnouncementData announcement = Announcement_Create( "#FW_ENTER_ENEMY_AREA" )
+ Announcement_SetSoundAlias( announcement, "UI_InGame_LevelUp" )
+ Announcement_SetSubText( announcement, "#FW_TITAN_REQUIRED_SUB" )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut
index ca238d5d..c295d596 100644
--- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut
+++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut
@@ -5,8 +5,39 @@
global function SHCreateGamemodeFW_Init
+// object settings, changable through playlist vars
+// default havester settings
+global const int FW_DEFAULT_HARVESTER_HEALTH = 87500
+global const int FW_DEFAULT_HARVESTER_SHIELD = 17500
+global const float FW_DEFAULT_HARVESTER_REGEN_DELAY = 12.0
+global const float FW_DEFAULT_HARVESTER_REGEN_TIME = 10.0
+// default turret settings
+global const int FW_DEFAULT_TURRET_HEALTH = 12500
+global const int FW_DEFAULT_TURRET_SHIELD = 4000
+
+// fix a turret
+global const float TURRET_FIXED_HEALTH_PERCENTAGE = 0.33
+global const float TURRET_FIXED_SHIELD_PERCENTAGE = 1.0 // default is regen all shield
+// hack a turret
+global const float TURRET_HACKED_HEALTH_PERCENTAGE = 0.5
+global const float TURRET_HACKED_SHIELD_PERCENTAGE = 0.5
+
void function SHCreateGamemodeFW_Init()
{
+ // harvester playlistvar
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_harvester_health", FW_DEFAULT_HARVESTER_HEALTH.tostring() )
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_harvester_shield", FW_DEFAULT_HARVESTER_SHIELD.tostring() )
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_harvester_regen_delay", FW_DEFAULT_HARVESTER_REGEN_DELAY.tostring() )
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_harvester_regen_time", FW_DEFAULT_HARVESTER_REGEN_TIME.tostring() )
+ // turret playlistvar
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_turret_health", FW_DEFAULT_TURRET_HEALTH.tostring() )
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_turret_shield", FW_DEFAULT_TURRET_SHIELD.tostring() )
+ // battery port playlistvar
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_turret_fixed_health", TURRET_FIXED_HEALTH_PERCENTAGE.tostring() )
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_turret_fixed_shield", TURRET_FIXED_SHIELD_PERCENTAGE.tostring() )
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_turret_hacked_health", TURRET_HACKED_HEALTH_PERCENTAGE.tostring() )
+ AddPrivateMatchModeSettingArbitrary( "#PL_fw", "fw_turret_hacked_shield", TURRET_HACKED_SHIELD_PERCENTAGE.tostring() )
+
AddCallback_OnCustomGamemodesInit( CreateGamemodeFW )
AddCallback_OnRegisteringCustomNetworkVars( FWOnRegisteringNetworkVars )
}
@@ -19,16 +50,28 @@ void function CreateGamemodeFW()
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
+
+ // fw lines are unfortunately not registered to faction dialogue, maybe do it in gamemode script manually, current using it's modeName
+ GameMode_SetGameModeAnnouncement( FORT_WAR, "fortwar_modeName" )
- #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
+ // waiting to be synced with client
+ GameMode_AddScoreboardColumnData( FORT_WAR, "#SCOREBOARD_KILLS", PGS_KILLS, 2 )
+ GameMode_AddScoreboardColumnData( FORT_WAR, "#SCOREBOARD_SUPPORT_SCORE", PGS_DEFENSE_SCORE, 4 )
+ GameMode_AddScoreboardColumnData( FORT_WAR, "#SCOREBOARD_COOP_POINTS", PGS_ASSAULT_SCORE, 6 )
+
+ AddPrivateMatchMode( FORT_WAR )
+
+ #if SERVER
+ GameMode_AddServerInit( FORT_WAR, GamemodeFW_Init )
+ GameMode_SetPilotSpawnpointsRatingFunc( FORT_WAR, RateSpawnpointsPilot_FW )
+ GameMode_SetTitanSpawnpointsRatingFunc( FORT_WAR, RateSpawnpointsTitan_FW )
+ #elseif CLIENT
+ GameMode_AddClientInit( FORT_WAR, CLGamemodeFW_Init )
+ #endif
+ #if !UI
+ GameMode_SetScoreCompareFunc( FORT_WAR, CompareAssaultScore )
+ GameMode_AddSharedInit( FORT_WAR, SHGamemodeFW_Init )
+ #endif
}
void function FWOnRegisteringNetworkVars()
@@ -36,6 +79,8 @@ void function FWOnRegisteringNetworkVars()
if ( GAMETYPE != FORT_WAR )
return
+ Remote_RegisterFunction( "ServerCallback_FW_NotifyNeedsEnterEnemyArea" )
+
RegisterNetworkedVariable( "turretSite1", SNDC_GLOBAL, SNVT_ENTITY )
RegisterNetworkedVariable( "turretSite2", SNDC_GLOBAL, SNVT_ENTITY )
RegisterNetworkedVariable( "turretSite3", SNDC_GLOBAL, SNVT_ENTITY )
@@ -59,13 +104,13 @@ void function FWOnRegisteringNetworkVars()
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( "fwCampStressA", SNDC_GLOBAL, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
RegisterNetworkedVariable( "fwCampAlertB", SNDC_GLOBAL, SNVT_INT )
- RegisterNetworkedVariable( "fwCampStressB", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampStressB", SNDC_GLOBAL, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
RegisterNetworkedVariable( "fwCampAlertC", SNDC_GLOBAL, SNVT_INT )
- RegisterNetworkedVariable( "fwCampStressC", SNDC_GLOBAL, SNVT_INT )
+ RegisterNetworkedVariable( "fwCampStressC", SNDC_GLOBAL, SNVT_FLOAT_RANGE, 0.0, 0.0, 1.0 )
#if CLIENT
CLFortWar_RegisterNetworkFunctions()
#endif
-} \ No newline at end of file
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee.gnut b/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee.gnut
new file mode 100644
index 00000000..95ab3915
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/melee/sh_melee.gnut
@@ -0,0 +1,1226 @@
+untyped
+
+global function MeleeShared_Init
+
+global function CodeCallback_OnMeleePressed
+//global function CodeCallback_OnMeleeHeld
+//global function CodeCallback_OnMeleeReleased
+global function CodeCallback_IsValidMeleeExecutionTarget
+global function CodeCallback_IsValidMeleeAttackTarget
+global function CodeCallback_OnMeleeAttackAnimEvent
+global function AddSyncedMeleeServerCallback
+global function AddSyncedMeleeServerThink
+
+global function GetSyncedMeleeChooser
+global function CreateSyncedMeleeChooser
+global function PlayerTriesSyncedMelee
+global function FindBestSyncedMelee
+global function GetSyncedMeleeChooserForPlayerVsTarget
+global function AddSyncedMelee
+global function GetEyeOrigin
+global function SetObjectCanBeMeleed
+global function ObjectCanBeMeleed
+global function ShouldClampTargetVelocity
+global function ClampVerticalVelocity
+global function IsInExecutionMeleeState
+
+global function GetLungeTargetForPlayer
+global function Melee_IsAllowed
+global function IsAttackerRef
+global function AddCallback_IsValidMeleeExecutionTarget
+
+#if SERVER
+ global function Melee_Enable
+ global function Melee_Disable
+ global function SyncedMelee_Enable
+ global function SyncedMelee_Disable
+ global function InitMeleeAnimEventCallbacks
+ global function GetRefAnglesBetweenEnts
+ global function CreateMeleeScriptMoverBetweenEnts
+ global function ShouldHolsterWeaponForMelee
+ global function ShouldHolsterWeaponForSyncedMelee
+ global function NPCTriesSyncedMeleeVsPlayer
+#endif
+
+const SMOOTH_TIME = 0.2
+const INSTA_KILL_TIME_THRESHOLD = 0.35
+const BUG_REPRO_MOVEMELEE = 19114
+
+global struct SyncedMelee
+{
+ string ref
+ bool enabled = true
+ vector direction = < 1, 0, 0 >
+ float distance
+ float distanceSqr
+ string attackerAnimation1p
+ string attackerAnimation3p
+// void function AddAnimEvent( entity ent, string eventName, void functionref( entity ent ) func, var optionalVar = null )
+ array<AnimEventData> attacker3pAnimEvents
+ array<AnimEventData> target3pAnimEvents
+ string targetAnimation1p
+ string targetAnimation3p
+ string thirdPersonCameraAttachment
+ asset attachModel1p
+ string attachTag1p
+ float minDot = -1.0 // always happens
+ string animRefPos = "target"
+ bool canTargetNPCs = true
+ float percentDamageDealtPerHit = 1.0
+ bool usableByPlayers = true
+
+ float targetMinHealthRatio = 0.0 // target health ratio must be at least this high
+ float targetMaxHealthRatio = 1.0 // target health ratio must be at or below this
+ bool onlyIfLethal // only if the strike would be lethal
+ bool isAttackerRef = true
+
+}
+
+global struct SyncedMeleeChooser
+{
+ vector functionref( entity ) attackerOriginFunc
+ vector functionref( entity ) targetOriginFunc
+ array<SyncedMelee> syncedMelees
+ bool displayMeleePrompt = true
+}
+
+struct
+{
+ table<string, table<string,SyncedMeleeChooser> > syncedMeleeChoosers
+ table<SyncedMeleeChooser, array<void functionref( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity target )> > syncedMeleeServerCallbacks
+ table<SyncedMeleeChooser, bool functionref( SyncedMelee action, entity player, entity target ) > syncedMeleeServerThink
+ array<bool functionref(entity attacker, entity target)> isValidMeleeExecutionTargetCallBacks
+ string lastExecutionUsed = ""
+} file
+
+function MeleeShared_Init()
+{
+ FlagInit( "ForceSyncedMelee" )
+
+ level.HUMAN_VS_TITAN_MELEE <- 1
+ level.titan_attack_anim_event_count <- 0
+ level.titan_attack_push_button_count <- 0
+
+ MeleeHumanShared_Init()
+ MeleeTitanShared_Init()
+ MeleeSyncedHumanShared_Init()
+ MeleeSyncedTitanShared_Init()
+
+ #if SERVER
+ VerifySyncedMelee()
+ MeleeSyncedServer_Init()
+ #endif
+
+ RegisterSignal( "SyncedMeleeComplete" )
+ RegisterSignal( "OnSyncedMelee" )
+ RegisterSignal( "OnSyncedMeleeVictim" )
+ RegisterSignal( "OnSyncedMeleeAttacker" )
+}
+
+int function GetPlayerMeleeDamage( entity player )
+{
+ Assert( player.IsPlayer() )
+ foreach ( weapon in player.GetMainWeapons() )
+ {
+ switch ( weapon.GetWeaponInfoFileKeyField( "fire_mode" ) )
+ {
+ case "offhand_melee":
+ return expect int( weapon.GetWeaponInfoFileKeyField( "melee_damage" ) )
+ }
+ }
+
+ return 0
+}
+
+void function AddSyncedMeleeServerCallback( SyncedMeleeChooser chooser, void functionref( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity target ) func )
+{
+ if ( !( chooser in file.syncedMeleeServerCallbacks ) )
+ file.syncedMeleeServerCallbacks[ chooser ] <- []
+
+ file.syncedMeleeServerCallbacks[ chooser ].append( func )
+}
+
+void function AddSyncedMeleeServerThink( SyncedMeleeChooser chooser, bool functionref( SyncedMelee action, entity player, entity target ) func )
+{
+ file.syncedMeleeServerThink[ chooser ] <- func
+}
+
+
+function VerifySyncedMelee()
+{
+ foreach ( attackerChoosers in file.syncedMeleeChoosers )
+ {
+ foreach ( chooser in attackerChoosers )
+ {
+ //Assert( chooser in file.syncedMeleeServerCallbacks, "Need to add synced server melee callback for synced melee chooser" )
+ //Assert( file.syncedMeleeServerCallbacks[ chooser ].len() > 0, "Need to create a callback for chooser" )
+ Assert( chooser in file.syncedMeleeServerThink, "Need to add synced server melee callback for synced melee chooser" )
+ }
+ }
+}
+
+SyncedMeleeChooser function GetSyncedMeleeChooser( string attackerType, string victimType )
+{
+ return file.syncedMeleeChoosers[ attackerType ][ victimType ]
+}
+
+SyncedMeleeChooser function CreateSyncedMeleeChooser( string attackerType, string victimType )
+{
+ SyncedMeleeChooser chooser
+
+ chooser.attackerOriginFunc = GetEyeOrigin
+ chooser.targetOriginFunc = GetEyeOrigin
+
+ if ( !( attackerType in file.syncedMeleeChoosers ) )
+ file.syncedMeleeChoosers[ attackerType ] <- {}
+
+ Assert( !( victimType in file.syncedMeleeChoosers[ attackerType ] ), "Already has " + victimType )
+ file.syncedMeleeChoosers[ attackerType ][ victimType ] <- chooser
+ return chooser
+}
+
+vector function GetEyeOrigin( entity ent )
+{
+ return ent.EyePosition()
+}
+
+void function AddCallback_IsValidMeleeExecutionTarget( bool functionref( entity attacker, entity target ) callbackFunc )
+{
+ file.isValidMeleeExecutionTargetCallBacks.append( callbackFunc )
+}
+
+//Called after pressing the melee button to recheck for targets
+bool function CodeCallback_IsValidMeleeExecutionTarget( entity attacker, entity target )
+{
+ if ( attacker == target )
+ return false
+
+ if ( !ShouldPlayerExecuteTarget( attacker, target ) )
+ return false
+
+ if ( !attacker.IsOnGround() && attacker.IsHuman() )
+ return false
+
+ if ( !IsAlive( target ) )
+ return false
+
+ if ( target.IsInvulnerable() )
+ return false
+
+ if ( !CanBeMeleed( target ) )
+ return false
+
+ if ( target.IsNPC() && !target.CanBeMeleeExecuted() )
+ return false
+
+ // Disallow executing someone that is already in execution. That road leads to script errors and asserts.
+ if ( target.ContextAction_IsMeleeExecution() )
+ return false
+
+ if ( attacker.IsTitan() && target.IsTitan() )
+ {
+ // no melee execute for berserker
+ if ( PlayerHasPassive( attacker, ePassives.PAS_BERSERKER ) )
+ return false
+
+ if ( PlayerHasPassive( attacker, ePassives.PAS_SHIFT_CORE ) )
+ return false
+
+ if ( HasSoul( target ) && target.GetTitanSoul().IsEjecting() )
+ return false
+
+ if ( attacker.ContextAction_IsActive() )
+ return false
+
+ if ( target.ContextAction_IsActive() )
+ return false
+
+ if ( GetCurrentPlaylistVarInt( "vortex_blocks_melee", 0 ) == 1 )
+ {
+ vector traceStartPos = attacker.EyePosition()
+ vector traceEndPos = target.EyePosition()
+ VortexBulletHit ornull vortexHit = VortexBulletHitCheck( attacker, traceStartPos, traceEndPos )
+ if ( vortexHit != null )
+ {
+ return false
+ }
+ }
+ }
+
+ if ( !CheckVerticallyCloseEnough( attacker, target ) )
+ return false
+
+ //No necksnaps while wall running or mantling
+ if ( attacker.IsWallRunning() )
+ return false
+
+ if ( attacker.IsTraversing() )
+ return false
+
+ if ( target.IsPlayer() ) //Disallow execution on a bunch of player-only actions
+ {
+
+ if ( target.IsHuman() )
+ {
+ if ( target.IsWallRunning() )
+ return false
+
+ if ( target.IsTraversing() )
+ return false
+
+ if ( !target.IsOnGround() ) //disallow mid-air necksnaps. Can't really do that for Titan executions since dash puts them in mid air... will have visual glitches unfortunately.
+ return false
+
+ if ( target.IsCrouched() )
+ return false
+
+ if ( Rodeo_IsAttached( target ) )
+ return false
+ }
+ }
+
+ if ( target.IsPhaseShifted() )
+ return false
+
+ //Disallow executions on contextActions marked Busy. Note that this allows
+ //execution on melee and leeching context actions!
+ if ( target.ContextAction_IsBusy() )
+ return false
+
+ if ( target.IsNPC() ) //NPC only checks
+ {
+ if ( target.ContextAction_IsActive() )
+ return false
+
+ if ( !target.IsInterruptable() )
+ return false
+ }
+
+ if ( attacker.GetTeam() == target.GetTeam() )
+ return false
+
+#if SERVER
+ if ( "syncedMeleeAttacker" in target.s ) //Don't allow necksnap on a guy who'se already getting necksnapped
+ return false
+#endif // #if SERVER
+
+ SyncedMeleeChooser ornull actions = GetSyncedMeleeChooserForPlayerVsTarget( attacker, target )
+ if ( actions == null )
+ return false
+ expect SyncedMeleeChooser( actions )
+
+ SyncedMelee ornull action = FindBestSyncedMelee( attacker, target, actions )
+ if ( action == null )
+ return false
+
+ if ( !PlayerMelee_IsExecutionReachable( attacker, target, 0.3 ) )
+ return false
+
+ foreach ( callbackFunc in file.isValidMeleeExecutionTargetCallBacks )
+ {
+ if ( !callbackFunc( attacker, target ) )
+ {
+ return false
+ }
+ }
+
+ return true
+}
+
+bool function CodeCallback_IsValidMeleeAttackTarget( entity attacker, entity target )
+{
+ if ( attacker == target )
+ return false
+
+ if ( target.IsBreakableGlass() )
+ return true
+
+ if ( !CanBeMeleed( target ) )
+ return false
+
+ if ( attacker.GetTeam() == target.GetTeam() )
+ return false
+
+#if SERVER
+ if ( target.IsPlayer() )
+ {
+ //Make titans not able to melee the pilot who is doing the embark animation
+ if ( GetTitanBeingRodeoed( target ) == attacker )
+ return false
+ }
+#endif // #if SERVER
+
+ if ( target.IsPhaseShifted() )
+ return false
+
+ if ( target.GetParent() == attacker )
+ return false
+
+ #if SERVER //Awkward, needed because it's CBaseCombatCharacter on server and C_BaseCombatCharacter on client, and because we allow melee on non BaseCombatCharacters like props that don't have ContextActions defined
+ if ( target instanceof CBaseCombatCharacter && target.ContextAction_IsMeleeExecutionTarget() ) //Don't lunge towards a victim that is already being executed )
+ return false
+ #elseif CLIENT
+ if ( target instanceof C_BaseCombatCharacter && target.ContextAction_IsMeleeExecutionTarget() ) //Don't lunge towards a victim that is already being executed )
+ return false
+ #endif
+
+ entity meleeWeapon = attacker.GetMeleeWeapon()
+ if ( !IsValid( meleeWeapon ) )
+ return false;
+
+ if ( !meleeWeapon.GetMeleeCanHitHumanSized() && IsHumanSized( target ) )
+ return false;
+ if ( !meleeWeapon.GetMeleeCanHitTitans() && target.IsTitan() )
+ return false;
+
+ return true
+}
+
+void function CodeCallback_OnMeleePressed( entity player )
+{
+#if SERVER
+ print( "SERVER: " + player + " pressed melee\n" )
+#else
+ print( "CLIENT: " + player + " pressed melee\n" )
+#endif
+
+ if ( !Melee_IsAllowed( player ) )
+ {
+#if SERVER
+ print( "SERVER: Melee_IsAllowed() for " + player + " is false\n" )
+#else
+ print( "CLIENT: Melee_IsAllowed() for " + player + " is false\n" )
+#endif
+ return
+ }
+
+#if SERVER
+ if ( svGlobal.cloakBreaksOnMelee && IsCloaked( player ) )
+ player.SetCloakFlicker( 1.0, 2.0 )
+#endif // #if SERVER
+
+ if ( player.IsWeaponDisabled() )
+ {
+#if SERVER
+ print( "SERVER: IsWeaponDisabled() for " + player + " is true\n" )
+#else
+ print( "CLIENT: IsWeaponDisabled() for " + player + " is true\n" )
+#endif
+ return
+ }
+
+ if ( player.PlayerMelee_GetState() != PLAYER_MELEE_STATE_NONE )
+ {
+#if SERVER
+ print( "SERVER: PlayerMelee_GetState() for " + player + " is " + player.PlayerMelee_GetState() + "\n" )
+#else
+ print( "CLIENT: PlayerMelee_GetState() for " + player + " is " + player.PlayerMelee_GetState() + "\n" )
+#endif
+ return
+ }
+
+ if ( !IsAlive( player ) )
+ {
+#if SERVER
+ print( "SERVER: " + player + " is dead\n" )
+#else
+ print( "CLIENT: " + player + " is dead\n" )
+#endif
+ return
+ }
+
+ thread CodeCallback_OnMeleePressed_InternalThread( player )
+}
+
+void function CodeCallback_OnMeleePressed_InternalThread( entity player )
+{
+ if ( player.IsTitan() )
+ {
+ TitanUnsyncedMelee( player )
+ }
+ else if ( player.IsHuman() )
+ {
+ const float STUN_EFFECT_CUTOFF = 0.05
+ float movestunEffect = StatusEffect_Get( player, eStatusEffect.move_slow )
+ bool movestunBlocked = (movestunEffect > STUN_EFFECT_CUTOFF)
+
+ HumanUnsyncedMelee( player, movestunBlocked )
+ }
+}
+
+//void function CodeCallback_OnMeleeHeld( entity player )
+//{
+//}
+
+//void function CodeCallback_OnMeleeReleased( entity player )
+//{
+//}
+
+bool function ShouldHolsterWeaponForSyncedMelee( entity player )
+{
+ if ( player.GetPlayerSettings() == "titan_ogre_minigun" )
+ return false
+
+ return ShouldHolsterWeaponForMelee( player )
+}
+
+bool function ShouldHolsterWeaponForMelee( entity player )
+{
+ #if !SERVER
+ return true
+ #endif
+
+ if ( !player.IsTitan() )
+ return true
+
+ return Time() - player.s.startDashMeleeTime > 1.0 //Fix issue with gun being out when it shouldn't, according to Mackey...
+}
+
+#if SERVER
+bool function NPCTriesSyncedMeleeVsPlayer( entity npc, entity player )
+{
+ Assert( npc.IsNPC() )
+ Assert( player.IsPlayer() )
+ Assert( IsAlive( player ) )
+ Assert( player.IsPlayer() )
+ Assert( IsPilot( player ) )
+ if ( player.ContextAction_IsBusy() )
+ return false
+
+ //#if SERVER
+ //player.PlayerMelee_SetState( PLAYER_MELEE_STATE_HUMAN_EXECUTION )
+ //#else
+ //player.PlayerMelee_SetState( PLAYER_MELEE_STATE_HUMAN_EXECUTION_PREDICTED )
+ //#endif
+
+ return DoSyncedMelee( npc, player )
+}
+#endif
+
+
+
+bool function PlayerTriesSyncedMelee( entity player, entity target )
+{
+ if ( !target )
+ return false
+ if ( !IsAlive( target ) )
+ return false
+
+ if ( target.ContextAction_IsBusy() )
+ return false
+
+ if ( player.IsTitan() )
+ {
+#if SERVER
+ player.PlayerMelee_SetState( PLAYER_MELEE_STATE_TITAN_EXECUTION )
+#else
+ player.PlayerMelee_SetState( PLAYER_MELEE_STATE_TITAN_EXECUTION_PREDICTED )
+#endif
+ }
+ else
+ {
+#if SERVER
+ player.PlayerMelee_SetState( PLAYER_MELEE_STATE_HUMAN_EXECUTION )
+#else
+ player.PlayerMelee_SetState( PLAYER_MELEE_STATE_HUMAN_EXECUTION_PREDICTED )
+#endif
+ }
+
+ if ( !player.Lunge_IsActive() || !player.Lunge_IsGroundExecute() || !player.Lunge_IsLungingToEntity() || (player.Lunge_GetTargetEntity() != target) )
+ {
+#if SERVER
+ print( "SERVER: " + player + " is calling Lunge_SetTargetEntity() from PlayerTriesSyncedMelee()\n" )
+#else
+ print( "CLIENT: " + player + " is calling Lunge_SetTargetEntity() from PlayerTriesSyncedMelee()\n" )
+#endif
+ player.Lunge_SetTargetEntity( target, false )
+ }
+
+#if SERVER
+ OnThreadEnd(
+ function() : ( player, target )
+ {
+ if ( IsValid( player ) && player.IsPlayer() )
+ {
+ RemoveCinematicFlag( player, CE_FLAG_TITAN_3P_CAM )
+ RemoveCinematicFlag( player, CE_FLAG_EXECUTION )
+ }
+ if ( IsValid( target ) && target.IsPlayer() )
+ {
+ RemoveCinematicFlag( target, CE_FLAG_TITAN_3P_CAM )
+ RemoveCinematicFlag( target, CE_FLAG_EXECUTION )
+ }
+ }
+ )
+
+ if ( player.IsTitan() )
+ TransferDamageHistoryToTarget( target )
+ if ( player.IsPlayer() )
+ {
+ AddCinematicFlag( player, CE_FLAG_TITAN_3P_CAM )
+ AddCinematicFlag( player, CE_FLAG_EXECUTION )
+ }
+ if ( IsValid( target ) && target.IsPlayer() )
+ {
+ AddCinematicFlag( target, CE_FLAG_TITAN_3P_CAM )
+ AddCinematicFlag( player, CE_FLAG_EXECUTION )
+ }
+#endif
+
+ bool success = DoSyncedMelee( player, target )
+ if ( !success )
+ {
+ player.Lunge_ClearTarget()
+ }
+
+ return success
+}
+
+function TransferDamageHistoryToTarget( entity target )
+{
+ entity titanSoul = target.GetTitanSoul()
+ target.e.recentDamageHistory = titanSoul.e.recentDamageHistory
+}
+
+bool function DoSyncedMelee( entity player, entity target )
+{
+ SyncedMeleeChooser ornull actions = GetSyncedMeleeChooserForPlayerVsTarget( player, target )
+
+ Assert( actions != null, "No melee action for " + player + " vs " + target )
+ expect SyncedMeleeChooser( actions )
+
+#if SERVER
+ if ( player.IsPlayer() )
+ {
+ PlayerMelee_StartLagCompensateTargetForLunge( player, target )
+ }
+#endif // #if SERVER
+
+ SyncedMelee ornull action = FindBestSyncedMelee( player, target, actions )
+
+#if SERVER
+ if ( player.IsPlayer() )
+ {
+
+ player.ForceStand()
+ player.UnforceStand()
+ PlayerMelee_FinishLagCompensateTarget( player )
+ }
+#endif // #if SERVER
+
+ if ( action == null )
+ return false
+
+ expect SyncedMelee( action )
+
+
+
+ player.Signal( "OnSyncedMelee" )
+ target.Signal( "OnSyncedMelee" )
+ player.Signal( "OnSyncedMeleeAttacker" )
+ target.Signal( "OnSyncedMeleeVictim" )
+
+#if SERVER
+ player.p.lastExecutionUsed = action.ref
+
+ if ( actions in file.syncedMeleeServerCallbacks )
+ thread SyncedMeleeServerCallbacks( actions, action, player, target )
+ bool functionref( SyncedMelee action, entity player, entity target ) think = file.syncedMeleeServerThink[ actions ]
+ return think( action, player, target )
+#endif // #if SERVER
+
+ return true
+}
+
+void function SyncedMeleeServerCallbacks( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity target )
+{
+ // Added via AddSyncedMeleeServerCallback
+ foreach ( index, _ in file.syncedMeleeServerCallbacks[ actions ] )
+ {
+ void functionref( SyncedMeleeChooser actions, SyncedMelee action, entity player, entity target ) item = file.syncedMeleeServerCallbacks[ actions ][ index ]
+ item( actions, action, player, target )
+ }
+}
+
+/*
+void function CodeCallback_OnMeleeReleased( entity player )
+{
+}
+*/
+
+function TextDebug( string msg )
+{
+ wait 0.5
+ printt( msg )
+}
+
+bool function ShouldClampTargetVelocity( vector targetVelocity, vector pushBackVelocity, float clampRatio )
+{
+ float dot = DotProduct( targetVelocity, pushBackVelocity )
+ if ( dot < 0 )
+ return true
+
+ if ( dot <= 0 )
+ return false
+
+ float velRatio = LengthSqr( targetVelocity ) / LengthSqr( pushBackVelocity )
+
+ return velRatio < clampRatio
+}
+
+bool function CanBeMeleed( entity target )
+{
+ if ( target.IsPlayer() )
+ return true
+ if ( target.IsNPC() )
+ return true
+
+ if ( ObjectCanBeMeleed( target ) )
+ return true
+
+ return false
+}
+
+// IMPORTANT: Only used for non-player, non-living special cases like prop_dynamics we want to be able to melee (drones, etc)
+bool function ObjectCanBeMeleed( entity ent )
+{
+ if ( !( "canBeMeleed" in ent.s ) )
+ return false
+
+ return expect bool( ent.s.canBeMeleed )
+}
+
+// IMPORTANT: Only used for non-player, non-living special cases like prop_dynamics we want to be able to melee (drones, etc)
+function SetObjectCanBeMeleed( entity ent, bool value )
+{
+ Assert( !ent.IsPlayer(), ent + " should not be a player. This is for non-player, non-NPC entities.")
+ Assert( !ent.IsNPC(), ent + " should not be an NPC. This is for non-player, non-NPC entities.")
+
+ if ( !( "canBeMeleed" in ent.s ) )
+ ent.s.canBeMeleed <- false
+
+ ent.s.canBeMeleed = value
+}
+
+//function TitanExposionDeath( entity titan, entity attacker )
+//{
+// if ( !IsAlive( titan ) )
+// return
+//
+// ExplodeTitanBits( titan )
+// // and your pretty titan too!
+//
+// //TitanEjectExplosion
+// table deathTable = { scriptType = damageTypes.titanMelee, forceKill = true, damageType = DMG_MELEE_EXECUTION, damageSourceId = eDamageSourceId.titan_execution }
+// titan.TakeDamage( titan.GetMaxHealth() + 1, attacker, attacker, deathTable )
+//}
+
+#if SERVER
+vector function GetRefAnglesBetweenEnts( entity attacker, entity target )
+{
+ vector endOrigin = target.GetOrigin()
+ vector startOrigin = attacker.GetOrigin()
+ vector refVec = endOrigin - startOrigin
+ vector refAng = VectorToAngles( refVec )
+ if ( fabs( AngleNormalize( refAng.x ) ) > 35 ) //If pitch is too much, use angles from either attacker or target
+ {
+ if ( attacker.IsTitan() )
+ refAng = attacker.GetAngles() //Doing titan synced kill from front, so use attacker's origin
+ else
+ refAng = target.GetAngles() // Doing rear necksnap, so use target's angles
+ }
+ return refAng
+}
+
+entity function CreateMeleeScriptMoverBetweenEnts( entity attacker, entity target )
+{
+ vector refAng = GetRefAnglesBetweenEnts( attacker, target )
+
+ vector endOrigin = target.GetOrigin()
+ vector startOrigin = attacker.GetOrigin()
+ vector refVec = endOrigin - startOrigin
+ vector refPos = endOrigin - refVec * 0.5
+
+ entity ref = CreateOwnedScriptMover( attacker )
+ ref.SetOrigin( refPos )
+ ref.SetAngles( refAng )
+
+ return ref
+}
+#endif // SERVER
+
+void function AddSyncedMelee( SyncedMeleeChooser chooser, SyncedMelee melee )
+{
+ // sqr the distance
+ melee.distanceSqr = melee.distance * melee.distance
+
+ chooser.syncedMelees.append( melee )
+}
+
+SyncedMelee ornull function FindBestSyncedMelee( entity attacker, entity target, SyncedMeleeChooser actions )
+{
+ #if CLIENT
+ Assert( attacker == GetLocalViewPlayer() )
+ #endif // CLIENT
+
+ vector absTargetToPlayerDir
+ if ( attacker.IsPlayer() && attacker.Lunge_IsActive() && (attacker.Lunge_GetTargetEntity() == target) )
+ {
+ absTargetToPlayerDir = attacker.Lunge_GetStartPositionOffset()
+ absTargetToPlayerDir = Normalize( absTargetToPlayerDir )
+ }
+ else
+ {
+ vector attackerPos = actions.attackerOriginFunc( attacker ) // + ( attacker.GetVelocity() * SMOOTH_TIME )
+ vector targetPos = actions.targetOriginFunc( target )
+
+ if ( attackerPos == targetPos )
+ {
+ absTargetToPlayerDir = < 1, 0, 0 >
+ }
+ else
+ {
+ absTargetToPlayerDir = Normalize( attackerPos - targetPos )
+ }
+ }
+
+ vector angles = attacker.EyeAngles()
+ vector forward = AnglesToForward( angles )
+
+ vector relTargetToPlayerDir = CalcRelativeVector( < 0, target.EyeAngles().y, 0 >, absTargetToPlayerDir )
+
+ array<SyncedMelee> bestActions
+ float bestDot = -2.0
+ float distSqr = LengthSqr( actions.attackerOriginFunc( attacker ) - actions.targetOriginFunc( target ) )
+
+ bool npcTarget = target.IsNPC()
+ bool playerAttacker = attacker.IsPlayer()
+
+ int health = target.GetHealth()
+ float healthRatio = HealthRatio( target )
+ int meleeDamage
+ if ( attacker.IsNPC() )
+ {
+ meleeDamage = attacker.GetMeleeDamageMaxForTarget( target )
+ }
+ else if ( attacker.IsPlayer() )
+ {
+ meleeDamage = GetPlayerMeleeDamage( attacker )
+ }
+
+ SyncedMelee ornull returnVal = null
+
+#if MP
+ if ( IsPilot( attacker ) )
+ {
+ PilotLoadoutDef loadout = GetActivePilotLoadout( attacker )
+
+ foreach ( action in actions.syncedMelees )
+ {
+ if ( action.ref != loadout.execution )
+ continue
+
+ if ( npcTarget && !action.canTargetNPCs )
+ break
+
+ if ( playerAttacker && !action.usableByPlayers )
+ break
+
+ if ( healthRatio < action.targetMinHealthRatio )
+ break
+
+ if ( healthRatio > action.targetMaxHealthRatio )
+ break
+
+ if ( action.onlyIfLethal && health > meleeDamage )
+ break
+
+ if ( distSqr > action.distanceSqr )
+ break
+
+ float dot = relTargetToPlayerDir.Dot( action.direction )
+ if ( dot < action.minDot )
+ break
+
+#if SERVER
+ //Random Execution
+ if ( string( attacker.GetPersistentVar( "activePilotLoadout.execution" )) == "execution_random")
+ {
+ returnVal = PickRandomExecution(actions, attacker)
+ break
+ }
+#endif
+
+ returnVal = action
+ break
+ }
+ }
+ else
+ {
+#endif
+ foreach ( action in actions.syncedMelees )
+ {
+ if ( !action.enabled )
+ continue
+
+ if ( npcTarget && !action.canTargetNPCs )
+ continue
+
+ if ( playerAttacker && !action.usableByPlayers )
+ continue
+
+ if ( healthRatio < action.targetMinHealthRatio )
+ continue
+
+ if ( healthRatio > action.targetMaxHealthRatio )
+ continue
+
+ if ( action.onlyIfLethal && health > meleeDamage )
+ continue
+
+ if ( distSqr > action.distanceSqr )
+ continue
+
+ float dot = relTargetToPlayerDir.Dot( action.direction )
+
+ //printt( "Dot: " + dot )
+
+ if ( dot < action.minDot )
+ continue
+
+ if ( dot == bestDot )
+ {
+ bestActions.append( action )
+ continue
+ }
+
+ if ( dot > bestDot )
+ {
+ // found new best dot
+ bestActions.clear()
+ bestDot = dot
+ bestActions.append( action )
+ }
+ }
+
+ if ( bestActions.len() )
+ returnVal = bestActions.getrandom()
+#if MP
+ }
+#endif
+
+ return returnVal
+}
+
+string function GetAttackerSyncedMelee( entity ent )
+{
+ if ( ent.IsPlayer() )
+ {
+ // TODO: for MP, change this to be based on loadout choice
+ string bodyType = GetPlayerBodyType( ent )
+ if ( bodyType == "human" )
+ {
+ entity weapon = ent.GetActiveWeapon()
+ var weaponSyncedMelee
+
+ if ( IsValid( weapon ) )
+ weaponSyncedMelee = weapon.GetWeaponInfoFileKeyField( "synced_melee_action" )
+
+ if ( weaponSyncedMelee )
+ return string( weaponSyncedMelee )
+ }
+
+ return bodyType
+
+ }
+ else if ( IsProwler( ent ) )
+ {
+ return "prowler"
+ }
+ else if ( IsPilotElite( ent ) )
+ {
+ return "pilotelite"
+ }
+ else if ( IsSpectre( ent ) )
+ {
+ return "spectre"
+ }
+ else if ( ent.IsNPC() )
+ {
+ return ent.GetBodyType()
+ }
+ else if ( ent.IsTitan() )
+ {
+ return "titan"
+ }
+
+ unreachable
+}
+
+string function GetVictimSyncedMeleeTargetType( entity ent )
+{
+ string targetType
+
+ if ( ent.IsPlayer() && GetPlayerBodyType( ent ) == "human" )
+ {
+ targetType = "human"
+ }
+ else if ( IsProwler( ent ) )
+ {
+ targetType = "prowler"
+ }
+ else if ( IsPilotElite( ent ) )
+ {
+ targetType = "pilotelite"
+ }
+ else if ( ent.IsNPC() )
+ {
+ targetType = ent.GetBodyType()
+ }
+ else if ( ent.IsTitan() )
+ {
+ targetType = "titan"
+ }
+ else
+ {
+ Assert( 0, "Unknown ent type" )
+ }
+
+ return targetType
+}
+
+SyncedMeleeChooser ornull function GetSyncedMeleeChooserForPlayerVsTarget( entity attacker, entity target )
+{
+ string attackerType = GetAttackerSyncedMelee( attacker )
+ string targetType = GetVictimSyncedMeleeTargetType( target )
+
+ if ( !( attackerType in file.syncedMeleeChoosers ) )
+ return null
+
+ if ( !( targetType in file.syncedMeleeChoosers[ attackerType ] ) )
+ return null
+
+ return file.syncedMeleeChoosers[ attackerType ][ targetType ]
+}
+
+void function CodeCallback_OnMeleeAttackAnimEvent( entity player )
+{
+ Assert( IsValid( player ) )
+#if SERVER
+ print( "SERVER: " + player + " is calling CodeCallback_OnMeleeAttackAnimEvent()\n" )
+#else
+ print( "CLIENT: " + player + " is calling CodeCallback_OnMeleeAttackAnimEvent()\n" )
+#endif
+ if ( player.PlayerMelee_IsAttackActive() )
+ {
+ if ( player.IsTitan() )
+ TitanMeleeAttack( player )
+ else if ( player.IsHuman() )
+ HumanMeleeAttack( player )
+ }
+}
+
+bool function IsInExecutionMeleeState( entity player )
+{
+ local meleeState = player.PlayerMelee_GetState()
+ switch ( meleeState )
+ {
+ case PLAYER_MELEE_STATE_HUMAN_EXECUTION_PREDICTED:
+ case PLAYER_MELEE_STATE_HUMAN_EXECUTION:
+ case PLAYER_MELEE_STATE_TITAN_EXECUTION_PREDICTED:
+ case PLAYER_MELEE_STATE_TITAN_EXECUTION:
+ return true
+
+ default:
+ return false
+ }
+
+ unreachable
+}
+
+#if SERVER
+void function InitMeleeAnimEventCallbacks( entity player )
+{
+ AddAnimEvent( player, "screen_blackout", MeleeBlackoutScreen_AE )
+}
+
+void function MeleeBlackoutScreen_AE( entity player )
+{
+ ScreenFadeToBlack( player, 0.7, 1.2 )
+}
+#endif
+
+bool function ShouldPlayerExecuteTarget( entity player, entity target )
+{
+ if ( player.IsTitan() )
+ {
+ if ( !target.IsTitan() )
+ return false
+
+ if ( Flag( "ForceSyncedMelee" ) )
+ return true
+
+ if ( !GetDoomedState( target ) )
+ return false
+
+ entity soul = target.GetTitanSoul()
+ if ( soul != null )
+ {
+ if ( soul.GetShieldHealth() > 0 && GetCurrentPlaylistVarInt( "titan_shield_blocks_execution", 0 ) != 0 )
+ return false
+ }
+
+ if ( !SyncedMelee_IsAllowed( player ) )
+ return false
+
+ return true
+ }
+
+ if ( player.IsHuman() )
+ {
+ if ( !IsHumanSized( target ) )
+ return false
+
+#if SERVER
+ if ( Flag( "ForceSyncedMelee" ) )
+ return true
+#endif // #if SERVER
+
+ if ( !SyncedMelee_IsAllowed( player ) )
+ return false
+ }
+
+ return true
+}
+
+vector function ClampVerticalVelocity( vector targetVelocity, float maxVerticalVelocity )
+{
+ vector clampedVelocity = targetVelocity
+ if ( clampedVelocity.z > maxVerticalVelocity )
+ {
+ printt( "clampedVelocity.z: " + clampedVelocity.z +", maxVerticalVelocity:" + maxVerticalVelocity )
+ clampedVelocity = Vector( targetVelocity.x, targetVelocity.y, maxVerticalVelocity )
+ }
+
+ return clampedVelocity
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool function CheckVerticallyCloseEnough( entity attacker, entity target )
+{
+ vector attackerOrigin = attacker.GetOrigin()
+ vector targetOrigin = target.GetOrigin()
+
+ float verticalDistance = fabs( attackerOrigin.z - targetOrigin.z )
+ float halfHeight = 0
+
+ if ( attacker.IsTitan() )
+ halfHeight = 92.5
+ else if ( attacker.IsHuman() )
+ halfHeight = 30
+
+ Assert( halfHeight, "Attacker is neither Titan nor Human" )
+
+ //printt( "vertical distance: " + verticalDistance )
+ return verticalDistance < halfHeight
+}
+
+
+entity function GetLungeTargetForPlayer( entity player )
+{
+ // Titan melee does not lunge
+ if ( player.IsTitan() )
+ return null
+
+ if ( player.IsPhaseShifted() )
+ return null
+
+ entity lungeTarget = PlayerMelee_LungeConeTrace( player, SHARED_CB_IS_VALID_MELEE_ATTACK_TARGET )
+ return lungeTarget
+}
+
+#if SERVER
+void function Melee_Enable( entity player )
+{
+ player.SetPlayerNetBool( "playerAllowedToMelee", true )
+}
+
+void function Melee_Disable( entity player )
+{
+ player.SetPlayerNetBool( "playerAllowedToMelee", false )
+}
+
+void function SyncedMelee_Enable( entity player )
+{
+ player.SetPlayerNetBool( "playerAllowedToSyncedMelee", true )
+}
+
+void function SyncedMelee_Disable( entity player )
+{
+ player.SetPlayerNetBool( "playerAllowedToSyncedMelee", false )
+}
+#endif
+
+bool function Melee_IsAllowed( entity player )
+{
+ return player.GetPlayerNetBool( "playerAllowedToMelee" )
+}
+
+bool function SyncedMelee_IsAllowed( entity player )
+{
+ return player.GetPlayerNetBool( "playerAllowedToSyncedMelee" )
+}
+
+bool function IsAttackerRef( SyncedMelee ornull action, entity target )
+{
+ if ( action != null )
+ {
+ expect SyncedMelee( action )
+ if ( action.isAttackerRef )
+ {
+ return true
+ }
+ }
+
+ if ( !target )
+ return true
+
+ if ( !IsValid( target ) )
+ return true
+
+ if ( !target.IsPlayer() )
+ return true
+
+ return false
+}
+
+#if MP
+#if SERVER
+SyncedMelee ornull function PickRandomExecution( SyncedMeleeChooser actions, entity attacker )
+{
+ array<SyncedMelee> possibleExecutions = []
+
+ SyncedMelee neckSnap
+
+ foreach ( action in actions.syncedMelees )
+ {
+ if (action.ref == "execution_neck_snap")
+ neckSnap = action
+
+ if(!IsItemLocked( attacker, action.ref ) && action.ref != "execution_random" && action.ref != attacker.p.lastExecutionUsed)
+
+ possibleExecutions.append(action)
+ }
+
+ if (possibleExecutions.len() == 0)
+ return neckSnap
+
+ possibleExecutions.randomize()
+
+ return possibleExecutions[0]
+}
+#endif
+#endif \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut b/Northstar.Custom/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut
index ad433ae2..9aa86a43 100644
--- a/Northstar.Custom/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut
@@ -41,6 +41,9 @@ 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.
+// fort war needs these
+global function Rodeo_TakeBatteryAwayFromPilot
+
#if DEV
global function SetDebugRodeoPrint
global function GetDebugRodeoPrint
diff --git a/Northstar.Custom/mod/scripts/vscripts/sh_damage_types.nut b/Northstar.Custom/mod/scripts/vscripts/sh_damage_types.nut
new file mode 100644
index 00000000..bae0116e
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/sh_damage_types.nut
@@ -0,0 +1,778 @@
+global function DamageTypes_Init
+global function RegisterWeaponDamageSourceName
+global function GetObitFromDamageSourceID
+global function DamageSourceIDToString
+global function DamageSourceIDHasString
+
+#if SERVER
+global function RegisterWeaponDamageSource
+global function RegisterWeaponDamageSources
+#endif
+
+struct
+{
+ table<int,string> damageSourceIDToName
+ table<int,string> damageSourceIDToString
+
+ // For new, modded damageSourceIDs.
+ // Holds triplets of [id, enum_name, display name]. Stored with no separation for ease of string conversion.
+ array<string> customDamageSourceIDList
+} file
+
+// For sending custom damage source IDs to clients
+const int SOURCE_ID_MAX_MESSAGE_LENGTH = 200 // JFS - Used to break messages sent to client into chunks in case it would hit the limitation on command argument length
+const string MESSAGE_SPACE_PADDING = "\xA6" // The "broken pipe" character. Trash character used to replace spaces in display name to allow sending via commands (args are separated by spaces).
+
+global enum eDamageSourceId
+{
+ invalid = -1 // used in code
+
+ //---------------------------
+ // defined in damageDef.txt. This will go away ( you can use damagedef_nuclear_core instead of eDamageSourceId.[enum id] and get rid of it from here )
+ // once this list has only damagedef_*, then we can remove eDamageSourceId
+ code_reserved // may be merged with invalid -1 above
+ damagedef_unknown // must start at 1 and order must match what's in damageDefs.txt
+ damagedef_unknownBugIt
+ damagedef_suicide
+ damagedef_despawn
+ damagedef_titan_step
+ damagedef_crush
+ damagedef_nuclear_core
+ damagedef_titan_fall
+ damagedef_titan_hotdrop
+ damagedef_reaper_fall
+ damagedef_trip_wire
+ damagedef_reaper_groundslam
+ damagedef_reaper_nuke
+ damagedef_frag_drone_explode
+ damagedef_frag_drone_explode_FD
+ damagedef_frag_drone_throwable_PLAYER
+ damagedef_frag_drone_throwable_NPC
+ damagedef_stalker_powersupply_explosion_small
+ damagedef_stalker_powersupply_explosion_large
+ damagedef_stalker_powersupply_explosion_large_at
+ damagedef_shield_captain_arc_shield
+ damagedef_fd_explosive_barrel
+ damagedef_fd_tether_trap
+
+ //---------------------------
+
+ // Titan Weapons
+ mp_titanweapon_40mm
+ mp_titanweapon_arc_cannon
+ mp_titanweapon_arc_wave
+ mp_titanweapon_arc_ball
+ mp_titanweapon_arc_pylon
+ mp_titanweapon_emp_volley
+ mp_titanweapon_rocket_launcher
+ mp_titanweapon_rocketeer_missile
+ mp_titanweapon_rocketeer_rocketstream
+ mp_titanweapon_shoulder_rockets
+ mp_titanweapon_shoulder_grenade
+ mp_titanweapon_orbital_strike
+ mp_titanweapon_tether_shot
+ mp_titanweapon_homing_rockets
+ mp_titanweapon_dumbfire_rockets
+ mp_titanweapon_multi_cluster
+ mp_titanweapon_meteor
+ mp_titanweapon_meteor_thermite
+ mp_titanweapon_meteor_thermite_charged
+ mp_titanweapon_salvo_rockets
+ mp_titanweapon_tracker_rockets
+ mp_titanweapon_sniper
+ mp_titanweapon_triple_threat
+ mp_titanweapon_vortex_shield
+ mp_titanweapon_vortex_shield_ion
+ mp_titanweapon_xo16
+ mp_titanweapon_xo16_shorty
+ mp_titanweapon_xopistol
+ mp_titanweapon_at_mine
+ mp_titanweapon_leadwall
+ mp_titanweapon_jackhammer
+ mp_titanweapon_electric_fist
+ mp_titanweapon_cabertoss
+ mp_titanweapon_flame_wall
+ mp_titanweapon_flame_ring
+ mp_titanweapon_smash
+ mp_titanweapon_particle_accelerator
+ mp_titanweapon_sticky_40mm
+ mp_titanweapon_predator_cannon
+ mp_titanweapon_predator_cannon_siege
+ mp_titanability_laser_trip
+ mp_titanweapon_laser_lite
+ mp_titanweapon_stun_laser
+ mp_titanability_smoke
+ mp_titanability_arc_field
+ mp_titanweapon_arc_minefield
+ mp_titanability_hover
+ mp_titanability_cloak
+ mp_titanability_tether_trap
+
+ mp_titancore_amp_core
+ mp_titancore_emp
+ mp_titancore_flame_wave
+ mp_titancore_flame_wave_secondary
+ mp_titancore_laser_cannon
+ mp_titancore_nuke_core
+ mp_titancore_nuke_missile
+ mp_titanweapon_berserker
+ mp_titancore_shift_core
+ mp_titanweapon_flightcore_rockets
+ mp_titancore_salvo_core
+ mp_titancore_siege_mode
+
+ //SP weapons
+ mp_weapon_grenade_electric_smoke
+ proto_titanweapon_deathblossom
+
+ // Pilot Weapons
+ mp_weapon_hemlok
+ mp_weapon_lmg
+ mp_weapon_rspn101
+ mp_weapon_vinson
+ mp_weapon_lstar
+ mp_weapon_g2
+ mp_weapon_smart_pistol
+ mp_weapon_r97
+ mp_weapon_car
+ mp_weapon_hemlok_smg
+ mp_weapon_dmr
+ mp_weapon_wingman
+ mp_weapon_wingman_n
+ mp_weapon_semipistol
+ mp_weapon_autopistol
+ mp_weapon_mgl
+ mp_weapon_sniper
+ mp_weapon_shotgun
+ mp_weapon_mastiff
+ mp_weapon_frag_drone
+ mp_weapon_frag_grenade
+ mp_weapon_grenade_emp
+ mp_weapon_arc_blast
+ mp_weapon_thermite_grenade
+ mp_weapon_grenade_sonar
+ mp_weapon_grenade_gravity
+ mp_weapon_satchel
+ mp_weapon_nuke_satchel
+ mp_weapon_proximity_mine
+ mp_weapon_smr
+ mp_weapon_rocket_launcher
+ mp_weapon_arc_launcher
+ mp_weapon_defender
+ mp_weapon_dash_melee
+ mp_weapon_tether
+ mp_weapon_tripwire
+ mp_weapon_flak_rifle
+ mp_extreme_environment
+ mp_weapon_shotgun_pistol
+ mp_weapon_pulse_lmg
+ mp_weapon_sword
+ mp_weapon_softball
+ mp_weapon_shotgun_doublebarrel
+ mp_weapon_doubletake
+ mp_weapon_arc_rifle
+ mp_weapon_gibber_pistol
+ mp_weapon_alternator_smg
+ mp_weapon_esaw
+ mp_weapon_epg
+ mp_weapon_arena1
+ mp_weapon_arena2
+ mp_weapon_arena3
+ mp_weapon_rspn101_og
+
+ //
+ melee_pilot_emptyhanded
+ melee_pilot_arena
+ melee_pilot_sword
+ melee_titan_punch
+ melee_titan_punch_ion
+ melee_titan_punch_tone
+ melee_titan_punch_legion
+ melee_titan_punch_scorch
+ melee_titan_punch_northstar
+ melee_titan_punch_fighter
+ melee_titan_punch_vanguard
+ melee_titan_sword
+ melee_titan_sword_aoe
+
+ mp_weapon_engineer_turret
+
+ // Turret Weapons
+ mp_weapon_yh803
+ mp_weapon_yh803_bullet
+ mp_weapon_yh803_bullet_overcharged
+ mp_weapon_mega_turret
+ mp_weapon_mega_turret_aa
+ mp_turretweapon_rockets
+ mp_turretweapon_blaster
+ mp_turretweapon_plasma
+ mp_turretweapon_sentry
+
+ // AI only Weapons
+ mp_weapon_super_spectre
+ mp_weapon_dronebeam
+ mp_weapon_dronerocket
+ mp_weapon_droneplasma
+ mp_weapon_turretplasma
+ mp_weapon_turretrockets
+ mp_weapon_turretplasma_mega
+ mp_weapon_gunship_launcher
+ mp_weapon_gunship_turret
+ mp_weapon_gunship_missile
+
+ // Misc
+ rodeo
+ rodeo_forced_titan_eject //For awarding points when you force a pilot to eject via rodeo
+ rodeo_execution
+ human_melee
+ auto_titan_melee
+ berserker_melee
+ mind_crime
+ charge_ball
+ grunt_melee
+ spectre_melee
+ prowler_melee
+ super_spectre_melee
+ titan_execution
+ human_execution
+ eviscerate
+ wall_smash
+ ai_turret
+ team_switch
+ rocket
+ titan_explosion
+ flash_surge
+ molotov
+ sticky_time_bomb
+ vortex_grenade
+ droppod_impact
+ ai_turret_explosion
+ rodeo_trap
+ round_end
+ bubble_shield
+ evac_dropship_explosion
+ sticky_explosive
+ titan_grapple
+
+ // streaks
+ satellite_strike
+
+ // Environmental
+ fall
+ splat
+ crushed
+ burn
+ lasergrid
+ outOfBounds
+ indoor_inferno
+ submerged
+ switchback_trap
+ floor_is_lava
+ suicideSpectreAoE
+ titanEmpField
+ stuck
+ deadly_fog
+ exploding_barrel
+ electric_conduit
+ turbine
+ harvester_beam
+ toxic_sludge
+
+ mp_weapon_spectre_spawner
+
+ // development
+ weapon_cubemap
+
+ // Prototype
+ mp_weapon_zipline
+ mp_ability_ground_slam
+ sp_weapon_arc_tool
+ sp_weapon_proto_battery_charger_offhand
+ at_turret_override
+ rodeo_battery_removal
+ phase_shift
+ gamemode_bomb_detonation
+ nuclear_turret
+ proto_viewmodel_test
+ mp_titanweapon_heat_shield
+ mp_titanability_slow_trap
+ mp_titanability_gun_shield
+ mp_titanability_power_shot
+ mp_titanability_ammo_swap
+ mp_titanability_sonar_pulse
+ mp_titanability_rearm
+ mp_titancore_upgrade
+ mp_titanweapon_xo16_vanguard
+ mp_weapon_arc_trap
+ core_overload
+
+ bombardment
+ bleedout
+ //damageSourceId=eDamageSourceId.xxxxx
+ //fireteam
+ //marvin
+ //rocketstrike
+ //orbitallaser
+ //explosion
+}
+
+//When adding new mods, they need to be added below and to persistent_player_data_version_N.pdef in r1/cfg/server.
+//Then when updating that file, save a new one and increment N.
+
+global enum eModSourceId
+{
+ accelerator
+ afterburners
+ arc_triple_threat
+ aog
+ burn_mod_autopistol
+ burn_mod_car
+ burn_mod_defender
+ burn_mod_dmr
+ burn_mod_emp_grenade
+ burn_mod_frag_grenade
+ burn_mod_grenade_electric_smoke
+ burn_mod_grenade_gravity
+ burn_mod_thermite_grenade
+ burn_mod_g2
+ burn_mod_hemlok
+ burn_mod_lmg
+ burn_mod_mgl
+ burn_mod_r97
+ burn_mod_rspn101
+ burn_mod_satchel
+ burn_mod_semipistol
+ burn_mod_smart_pistol
+ burn_mod_smr
+ burn_mod_sniper
+ burn_mod_rocket_launcher
+ burn_mod_titan_40mm
+ burn_mod_titan_arc_cannon
+ burn_mod_titan_rocket_launcher
+ 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
+ enhanced_targeting
+ extended_ammo
+ fast_lock
+ fast_reload
+ guided_missile
+ hcog
+ holosight
+ instant_shot
+ iron_sights
+ overcharge
+ quick_shot
+ rapid_fire_missiles
+ scope_4x
+ scope_6x
+ scope_8x
+ scope_10x
+ scope_12x
+ burn_mod_shotgun
+ silencer
+ slammer
+ spread_increase_ttt
+ stabilizer
+ titanhammer
+ burn_mod_wingman
+ burn_mod_lstar
+ burn_mod_mastiff
+ burn_mod_vinson
+ ricochet
+ ar_trajectory
+ redline_sight
+ threat_scope
+ smart_lock
+ pro_screen
+ rocket_arena
+}
+
+//Attachments intentionally left off. This prevents them from displaying in kill cards.
+// modNameStrings should be defined when the mods are created, not in a separate table -Mackey
+global const modNameStrings = {
+ [ eModSourceId.accelerator ] = "#MOD_ACCELERATOR_NAME",
+ [ eModSourceId.afterburners ] = "#MOD_AFTERBURNERS_NAME",
+ [ eModSourceId.arc_triple_threat ] = "#MOD_ARC_TRIPLE_THREAT_NAME",
+ [ eModSourceId.burn_mod_autopistol ] = "#BC_AUTOPISTOL_M2",
+ [ eModSourceId.burn_mod_car ] = "#BC_CAR_M2",
+ [ eModSourceId.burn_mod_defender ] = "#BC_DEFENDER_M2",
+ [ eModSourceId.burn_mod_dmr ] = "#BC_DMR_M2",
+ [ eModSourceId.burn_mod_emp_grenade ] = "#BC_EMP_GRENADE_M2",
+ [ eModSourceId.burn_mod_frag_grenade ] = "#BC_FRAG_GRENADE_M2",
+ [ eModSourceId.burn_mod_grenade_electric_smoke ] = "#BC_GRENADE_ELECTRIC_SMOKE_M2",
+ [ eModSourceId.burn_mod_grenade_gravity ] = "#BC_GRENADE_ELECTRIC_SMOKE_M2",
+ [ eModSourceId.burn_mod_thermite_grenade ] = "#BC_GRENADE_ELECTRIC_SMOKE_M2",
+ [ eModSourceId.burn_mod_g2 ] = "#BC_G2_M2",
+ [ eModSourceId.burn_mod_hemlok ] = "#BC_HEMLOK_M2",
+ [ eModSourceId.burn_mod_lmg ] = "#BC_LMG_M2",
+ [ eModSourceId.burn_mod_mgl ] = "#BC_MGL_M2",
+ [ eModSourceId.burn_mod_r97 ] = "#BC_R97_M2",
+ [ eModSourceId.burn_mod_rspn101 ] = "#BC_RSPN101_M2",
+ [ eModSourceId.burn_mod_satchel ] = "#BC_SATCHEL_M2",
+ [ eModSourceId.burn_mod_semipistol ] = "#BC_SEMIPISTOL_M2",
+ [ eModSourceId.burn_mod_smr ] = "#BC_SMR_M2",
+ [ eModSourceId.burn_mod_smart_pistol ] = "#BC_SMART_PISTOL_M2",
+ [ eModSourceId.burn_mod_sniper ] = "#BC_SNIPER_M2",
+ [ eModSourceId.burn_mod_rocket_launcher ] = "#BC_ROCKET_LAUNCHER_M2",
+ [ eModSourceId.burn_mod_titan_40mm ] = "#BC_TITAN_40MM_M2",
+ [ eModSourceId.burn_mod_titan_arc_cannon ] = "#BC_TITAN_ARC_CANNON_M2",
+ [ eModSourceId.burn_mod_titan_rocket_launcher ] = "#BC_TITAN_ROCKET_LAUNCHER_M2",
+ [ eModSourceId.burn_mod_titan_sniper ] = "#BC_TITAN_SNIPER_M2",
+ [ eModSourceId.burn_mod_titan_triple_threat ] = "#BC_TITAN_TRIPLE_THREAT_M2",
+ [ eModSourceId.burn_mod_titan_xo16 ] = "#BC_TITAN_XO16_M2",
+ [ eModSourceId.burn_mod_titan_dumbfire_rockets ] = "#BC_TITAN_DUMBFIRE_MISSILE_M2",
+ [ eModSourceId.burn_mod_titan_homing_rockets ] = "#BC_TITAN_HOMING_ROCKETS_M2",
+ [ eModSourceId.burn_mod_titan_salvo_rockets ] = "#BC_TITAN_SALVO_ROCKETS_M2",
+ [ eModSourceId.burn_mod_titan_shoulder_rockets ] = "#BC_TITAN_SHOULDER_ROCKETS_M2",
+ [ eModSourceId.burn_mod_titan_vortex_shield ] = "#BC_TITAN_VORTEX_SHIELD_M2",
+ [ eModSourceId.burn_mod_titan_smoke ] = "#BC_TITAN_ELECTRIC_SMOKE_M2",
+ [ eModSourceId.burn_mod_titan_particle_wall ] = "#BC_TITAN_SHIELD_WALL_M2",
+ [ eModSourceId.burst ] = "#MOD_BURST_NAME",
+ [ eModSourceId.capacitor ] = "#MOD_CAPACITOR_NAME",
+ [ eModSourceId.enhanced_targeting ] = "#MOD_ENHANCED_TARGETING_NAME",
+ [ eModSourceId.extended_ammo ] = "#MOD_EXTENDED_MAG_NAME",
+ [ eModSourceId.fast_reload ] = "#MOD_FAST_RELOAD_NAME",
+ [ eModSourceId.instant_shot ] = "#MOD_INSTANT_SHOT_NAME",
+ [ eModSourceId.overcharge ] = "#MOD_OVERCHARGE_NAME",
+ [ eModSourceId.quick_shot ] = "#MOD_QUICK_SHOT_NAME",
+ [ eModSourceId.rapid_fire_missiles ] = "#MOD_RAPID_FIRE_MISSILES_NAME",
+ [ eModSourceId.burn_mod_shotgun ] = "#BC_SHOTGUN_M2",
+ [ eModSourceId.silencer ] = "#MOD_SILENCER_NAME",
+ [ eModSourceId.slammer ] = "#MOD_SLAMMER_NAME",
+ [ eModSourceId.spread_increase_ttt ] = "#MOD_SPREAD_INCREASE_TTT_NAME",
+ [ eModSourceId.stabilizer ] = "#MOD_STABILIZER_NAME",
+ [ eModSourceId.titanhammer ] = "#MOD_TITANHAMMER_NAME",
+ [ eModSourceId.burn_mod_wingman ] = "#BC_WINGMAN_M2",
+ [ eModSourceId.burn_mod_lstar ] = "#BC_LSTAR_M2",
+ [ eModSourceId.burn_mod_mastiff ] = "#BC_MASTIFF_M2",
+ [ eModSourceId.burn_mod_vinson ] = "#BC_VINSON_M2",
+ [ eModSourceId.ricochet ] = "Ricochet",
+ [ eModSourceId.ar_trajectory ] = "AR Trajectory",
+ [ eModSourceId.smart_lock ] = "Smart Lock",
+ [ eModSourceId.pro_screen ] = "Pro Screen",
+ [ eModSourceId.rocket_arena ] = "Rocket Arena",
+}
+
+void function DamageTypes_Init()
+{
+ #if SERVER
+ AddCallback_OnClientConnected( SendNewDamageSourceIDsConnected )
+ #else
+ AddServerToClientStringCommandCallback( "register_damage_source_ids", ReceiveNewDamageSourceIDs )
+ #endif
+
+ foreach ( name, number in eDamageSourceId )
+ {
+ file.damageSourceIDToString[ number ] <- name
+ }
+
+ PrecacheWeapon( "mp_weapon_rspn101" ) // used by npc_soldier ><
+
+#if DEV
+
+ int numDamageDefs = DamageDef_GetCount()
+ table damageSourceIdEnum = expect table( getconsttable().eDamageSourceId )
+ foreach ( name, id in damageSourceIdEnum )
+ {
+ expect int( id )
+ if ( id <= eDamageSourceId.code_reserved || id >= numDamageDefs )
+ continue
+
+ string damageDefName = DamageDef_GetName( id )
+ Assert( damageDefName == name, "damage def (" + id + ") name: '" + damageDefName + "' doesn't match damage source id '" + name + "'" )
+ }
+#endif
+
+ file.damageSourceIDToName =
+ {
+ //sp
+ [ eDamageSourceId.mp_weapon_grenade_electric_smoke ] = "#DEATH_ELECTRIC_SMOKE_SCREEN",
+ [ eDamageSourceId.proto_titanweapon_deathblossom ] = "#WPN_TITAN_ROCKET_LAUNCHER",
+
+ //mp
+ [ eDamageSourceId.mp_extreme_environment ] = "#DAMAGE_EXTREME_ENVIRONMENT",
+
+ [ eDamageSourceId.mp_weapon_engineer_turret ] = "#WPN_ENGINEER_TURRET",
+
+ [ eDamageSourceId.mp_weapon_yh803 ] = "#WPN_LIGHT_TURRET",
+ [ eDamageSourceId.mp_weapon_yh803_bullet ] = "#WPN_LIGHT_TURRET",
+ [ eDamageSourceId.mp_weapon_yh803_bullet_overcharged ] = "#WPN_LIGHT_TURRET",
+ [ eDamageSourceId.mp_weapon_mega_turret ] = "#WPN_MEGA_TURRET",
+ [ eDamageSourceId.mp_weapon_mega_turret_aa ] = "#WPN_MEGA_TURRET",
+ [ eDamageSourceId.mp_turretweapon_rockets ] = "#WPN_TURRET_ROCKETS",
+ [ eDamageSourceId.mp_weapon_super_spectre ] = "#WPN_SUPERSPECTRE_ROCKETS",
+ [ eDamageSourceId.mp_weapon_dronebeam ] = "#WPN_DRONERBEAM",
+ [ eDamageSourceId.mp_weapon_dronerocket ] = "#WPN_DRONEROCKET",
+ [ eDamageSourceId.mp_weapon_droneplasma ] = "#WPN_DRONEPLASMA",
+ [ eDamageSourceId.mp_weapon_turretplasma ] = "#WPN_TURRETPLASMA",
+ [ eDamageSourceId.mp_weapon_turretrockets ] = "#WPN_TURRETROCKETS",
+ [ eDamageSourceId.mp_weapon_turretplasma_mega ] = "#WPN_TURRETPLASMA_MEGA",
+ [ eDamageSourceId.mp_weapon_gunship_launcher ] = "#WPN_GUNSHIP_LAUNCHER",
+ [ eDamageSourceId.mp_weapon_gunship_turret ] = "#WPN_GUNSHIP_TURRET",
+ [ eDamageSourceId.mp_weapon_gunship_turret ] = "#WPN_GUNSHIP_MISSILE",
+
+ [ eDamageSourceId.mp_titanability_smoke ] = "#DEATH_ELECTRIC_SMOKE_SCREEN",
+ [ eDamageSourceId.mp_titanability_laser_trip ] = "#DEATH_LASER_TRIPWIRE",
+ [ eDamageSourceId.mp_titanability_slow_trap ] = "#DEATH_SLOW_TRAP",
+ [ eDamageSourceId.mp_titanability_tether_trap ] = "#DEATH_TETHER_TRAP",
+
+ [ eDamageSourceId.rodeo ] = "#DEATH_TITAN_RODEO",
+ [ eDamageSourceId.rodeo_forced_titan_eject ] = "#DEATH_TITAN_RODEO",
+ [ eDamageSourceId.rodeo_execution ] = "#DEATH_RODEO_EXECUTION",
+ [ eDamageSourceId.nuclear_turret ] = "#DEATH_NUCLEAR_TURRET",
+ [ eDamageSourceId.mp_titanweapon_flightcore_rockets ] = "#WPN_TITAN_FLIGHT_ROCKET",
+ [ eDamageSourceId.mp_titancore_amp_core ] = "#TITANCORE_AMP_CORE",
+ [ eDamageSourceId.mp_titancore_emp ] = "#TITANCORE_EMP",
+ [ eDamageSourceId.mp_titancore_siege_mode ] = "#TITANCORE_SIEGE_MODE",
+ [ eDamageSourceId.mp_titancore_flame_wave ] = "#TITANCORE_FLAME_WAVE",
+ [ eDamageSourceId.mp_titancore_flame_wave_secondary ] = "#GEAR_SCORCH_FLAMECORE",
+ [ eDamageSourceId.mp_titancore_nuke_core ] = "#TITANCORE_NUKE",
+ [ eDamageSourceId.mp_titancore_nuke_missile ] = "#TITANCORE_NUKE_MISSILE",
+ [ eDamageSourceId.mp_titancore_shift_core ] = "#TITANCORE_SWORD",
+ [ eDamageSourceId.berserker_melee ] = "#DEATH_BERSERKER_MELEE",
+ [ eDamageSourceId.human_melee ] = "#DEATH_HUMAN_MELEE",
+ [ eDamageSourceId.auto_titan_melee ] = "#DEATH_AUTO_TITAN_MELEE",
+
+ [ eDamageSourceId.prowler_melee ] = "#DEATH_PROWLER_MELEE",
+ [ eDamageSourceId.super_spectre_melee ] = "#DEATH_SUPER_SPECTRE",
+ [ eDamageSourceId.grunt_melee ] = "#DEATH_GRUNT_MELEE",
+ [ eDamageSourceId.spectre_melee ] = "#DEATH_SPECTRE_MELEE",
+ [ eDamageSourceId.eviscerate ] = "#DEATH_EVISCERATE",
+ [ eDamageSourceId.wall_smash ] = "#DEATH_WALL_SMASH",
+ [ eDamageSourceId.ai_turret ] = "#DEATH_TURRET",
+ [ eDamageSourceId.team_switch ] = "#DEATH_TEAM_CHANGE",
+ [ eDamageSourceId.rocket ] = "#DEATH_ROCKET",
+ [ eDamageSourceId.titan_explosion ] = "#DEATH_TITAN_EXPLOSION",
+ [ eDamageSourceId.evac_dropship_explosion ] = "#DEATH_EVAC_DROPSHIP_EXPLOSION",
+ [ eDamageSourceId.flash_surge ] = "#DEATH_FLASH_SURGE",
+ [ eDamageSourceId.molotov ] = "#DEATH_MOLOTOV",
+ [ eDamageSourceId.sticky_time_bomb ] = "#DEATH_STICKY_TIME_BOMB",
+ [ eDamageSourceId.vortex_grenade ] = "#DEATH_VORTEX_GRENADE",
+ [ eDamageSourceId.droppod_impact ] = "#DEATH_DROPPOD_CRUSH",
+ [ eDamageSourceId.ai_turret_explosion ] = "#DEATH_TURRET_EXPLOSION",
+ [ eDamageSourceId.rodeo_trap ] = "#DEATH_RODEO_TRAP",
+ [ eDamageSourceId.round_end ] = "#DEATH_ROUND_END",
+ [ eDamageSourceId.burn ] = "#DEATH_BURN",
+ [ eDamageSourceId.mind_crime ] = "Mind Crime",
+ [ eDamageSourceId.charge_ball ] = "Charge Ball",
+ [ eDamageSourceId.mp_titanweapon_rocketeer_missile ] = "Rocketeer Missile",
+ [ eDamageSourceId.core_overload ] = "#DEATH_CORE_OVERLOAD",
+ [ eDamageSourceId.mp_weapon_arc_trap ] = "#WPN_ARC_TRAP",
+
+
+ [ eDamageSourceId.mp_turretweapon_sentry ] = "#WPN_SENTRY_TURRET",
+ [ eDamageSourceId.mp_turretweapon_blaster ] = "#WPN_BLASTER_TURRET",
+ [ eDamageSourceId.mp_turretweapon_rockets ] = "#WPN_ROCKET_TURRET",
+ [ eDamageSourceId.mp_turretweapon_plasma ] = "#WPN_PLASMA_TURRET",
+
+ [ eDamageSourceId.bubble_shield ] = "#DEATH_BUBBLE_SHIELD",
+ [ eDamageSourceId.sticky_explosive ] = "#DEATH_STICKY_EXPLOSIVE",
+ [ eDamageSourceId.titan_grapple ] = "#DEATH_TITAN_GRAPPLE",
+
+ [ eDamageSourceId.satellite_strike ] = "#DEATH_SATELLITE_STRIKE",
+
+ [ eDamageSourceId.mp_titanweapon_meteor ] = "#WPN_TITAN_METEOR",
+ [ eDamageSourceId.mp_titanweapon_meteor_thermite ] = "#WPN_TITAN_METEOR",
+ [ eDamageSourceId.mp_titanweapon_meteor_thermite_charged ] = "Thermite Meteor",
+ [ eDamageSourceId.mp_titanweapon_flame_ring ] = "Flame Wreath",
+
+ // Instant death. Show no percentages on death recap.
+ [ eDamageSourceId.fall ] = "#DEATH_FALL",
+ //Todo: Rename eDamageSourceId.splat with a more appropriate name. This damage type was used for enviornmental damage, but it was for eject killing pilots if they were near a ceiling. I've changed the localized string to "Enviornment Damage", but this will cause confusion in the future.
+ [ eDamageSourceId.splat ] = "#DEATH_SPLAT",
+ [ eDamageSourceId.titan_execution ] = "#DEATH_TITAN_EXECUTION",
+ [ eDamageSourceId.human_execution ] = "#DEATH_HUMAN_EXECUTION",
+ [ eDamageSourceId.outOfBounds ] = "#DEATH_OUT_OF_BOUNDS",
+ [ eDamageSourceId.indoor_inferno ] = "#DEATH_INDOOR_INFERNO",
+ [ eDamageSourceId.submerged ] = "#DEATH_SUBMERGED",
+ [ eDamageSourceId.switchback_trap ] = "#DEATH_ELECTROCUTION", // Damages teammates and opposing team
+ [ eDamageSourceId.floor_is_lava ] = "#DEATH_ELECTROCUTION",
+ [ eDamageSourceId.suicideSpectreAoE ] = "#DEATH_SUICIDE_SPECTRE", // Used for distinguishing the initial spectre from allies.
+ [ eDamageSourceId.titanEmpField ] = "#DEATH_TITAN_EMP_FIELD",
+ [ eDamageSourceId.deadly_fog ] = "#DEATH_DEADLY_FOG",
+
+
+ // Prototype
+ [ eDamageSourceId.mp_weapon_zipline ] = "Zipline",
+ [ eDamageSourceId.mp_ability_ground_slam ] = "Ground Slam",
+ [ eDamageSourceId.sp_weapon_arc_tool ] = "#WPN_ARC_TOOL",
+ [ eDamageSourceId.sp_weapon_proto_battery_charger_offhand ] = "Battery Charger",
+ [ eDamageSourceId.at_turret_override ] = "AT Turret",
+ [ eDamageSourceId.phase_shift ] = "#WPN_SHIFTER",
+ [ eDamageSourceId.gamemode_bomb_detonation ] = "Bomb Detonation",
+ [ eDamageSourceId.bleedout ] = "#DEATH_BLEEDOUT",
+
+ [ eDamageSourceId.damagedef_unknownBugIt ] = "#DEATH_GENERIC_KILLED",
+ [ eDamageSourceId.damagedef_unknown ] = "#DEATH_GENERIC_KILLED",
+ [ eDamageSourceId.weapon_cubemap ] = "#DEATH_GENERIC_KILLED",
+ [ eDamageSourceId.stuck ] = "#DEATH_GENERIC_KILLED",
+ [ eDamageSourceId.rodeo_battery_removal ] = "#DEATH_RODEO_BATTERY_REMOVAL",
+
+ [ eDamageSourceId.melee_pilot_emptyhanded ] = "#DEATH_MELEE",
+ [ eDamageSourceId.melee_pilot_arena ] = "#DEATH_MELEE",
+ [ eDamageSourceId.melee_pilot_sword ] = "#DEATH_SWORD",
+ [ eDamageSourceId.melee_titan_punch ] = "#DEATH_TITAN_MELEE",
+ [ eDamageSourceId.melee_titan_punch_ion ] = "#DEATH_TITAN_MELEE",
+ [ eDamageSourceId.melee_titan_punch_tone ] = "#DEATH_TITAN_MELEE",
+ [ eDamageSourceId.melee_titan_punch_northstar ] = "#DEATH_TITAN_MELEE",
+ [ eDamageSourceId.melee_titan_punch_scorch ] = "#DEATH_TITAN_MELEE",
+ [ eDamageSourceId.melee_titan_punch_legion ] = "#DEATH_TITAN_MELEE",
+ [ eDamageSourceId.melee_titan_punch_fighter ] = "#DEATH_TITAN_MELEE",
+ [ eDamageSourceId.melee_titan_punch_vanguard ] = "#DEATH_TITAN_MELEE",
+ [ eDamageSourceId.melee_titan_sword ] = "#DEATH_TITAN_SWORD",
+ [ eDamageSourceId.melee_titan_sword_aoe ] = "#DEATH_TITAN_SWORD",
+ [ eDamageSourceId.mp_titanweapon_arc_cannon ] = "#WPN_TITAN_ARC_CANNON_SHORT",
+ [ eDamageSourceId.mp_weapon_shotgun_doublebarrel ] = "#WPN_SHOTGUN_DBLBARREL_SHORT"
+ }
+
+ #if DEV
+ //development, with retail versions incase a rare bug happens we dont want to show developer text
+ file.damageSourceIDToName[ eDamageSourceId.damagedef_unknownBugIt ] = "UNKNOWN! BUG IT!"
+ file.damageSourceIDToName[ eDamageSourceId.damagedef_unknown ] = "Unknown"
+ file.damageSourceIDToName[ eDamageSourceId.weapon_cubemap ] = "Cubemap"
+ //file.damageSourceIDToName[ eDamageSourceId.invalid ] = "INVALID (BUG IT!)"
+ file.damageSourceIDToName[ eDamageSourceId.stuck ] = "NPC got Stuck (Don't Bug it!)"
+ #endif
+}
+
+void function RegisterWeaponDamageSourceName( string weaponRef, string damageSourceName )
+{
+ int sourceID = eDamageSourceId[weaponRef]
+ file.damageSourceIDToName[ sourceID ] <- damageSourceName
+}
+
+bool function DamageSourceIDHasString( int index )
+{
+ return (index in file.damageSourceIDToString)
+}
+
+string function DamageSourceIDToString( int index )
+{
+ return file.damageSourceIDToString[ index ]
+}
+
+string function GetObitFromDamageSourceID( int damageSourceID )
+{
+ if ( damageSourceID > 0 && damageSourceID < DamageDef_GetCount() )
+ {
+ return DamageDef_GetObituary( damageSourceID )
+ }
+
+ if ( damageSourceID in file.damageSourceIDToName )
+ return file.damageSourceIDToName[ damageSourceID ]
+
+ table damageSourceIdEnum = expect table( getconsttable().eDamageSourceId )
+ foreach ( name, id in damageSourceIdEnum )
+ {
+ if ( id == damageSourceID )
+ return expect string( name )
+ }
+
+ return ""
+}
+
+#if SERVER
+void function RegisterWeaponDamageSource( string weaponRef, string damageSourceName )
+{
+ // Have to do this since squirrel table initialization only supports literals for string keys
+ table< string, string > temp
+ temp[ weaponRef ] <- damageSourceName
+ RegisterWeaponDamageSources( temp )
+}
+
+/* Values are expected to be in a table containing the enum variable name and the string name, e.g.
+ {"mp_titanweapon_sniper" : "Plasma Railgun", "mp_titanweapon_meteor" : "T203 Thermite Launcher"}
+ Only works properly if used after the match starts, e.g. called in "after" callbacks.
+*/
+void function RegisterWeaponDamageSources( table< string, string > newValueTable )
+{
+ int trgt = file.damageSourceIDToString.len() - 1 // -1 accounts for invalid.
+ int lastCustomSize = file.customDamageSourceIDList.len() // Used to only send new IDs to clients if any are added during runtime.
+
+ foreach ( newVal, stringVal in newValueTable )
+ {
+ // Don't replace existing enum values
+ while ( trgt in file.damageSourceIDToString )
+ trgt++
+
+ // Only move insertion point if insertion succeeded
+ if ( RegisterWeaponDamageSourceInternal( trgt, newVal, stringVal ) )
+ trgt++;
+ }
+
+ // Send IDs created during match runtime. IDs made on inits get sent through client connected callback.
+ foreach( player in GetPlayerArray() )
+ SendNewDamageSourceIDs( player, lastCustomSize )
+}
+#endif
+
+bool function RegisterWeaponDamageSourceInternal( int id, string newVal, string stringVal )
+{
+ table damageSourceID = expect table( getconsttable()[ "eDamageSourceId" ] )
+
+ // Fail invalid new source IDs (already exists or cannot be sent via string commands). Length condition has loose padding to account for ID string length.
+ if ( newVal in damageSourceID || newVal.len() + stringVal.len() > SOURCE_ID_MAX_MESSAGE_LENGTH - 15 || id in file.damageSourceIDToString )
+ return false
+
+ damageSourceID[ newVal ] <- id
+ file.damageSourceIDToString[ id ] <- newVal
+ file.damageSourceIDToName[ id ] <- stringVal
+ file.customDamageSourceIDList.extend( [ id.tostring(), newVal, StringReplace( stringVal, " ", MESSAGE_SPACE_PADDING, true ) ] )
+ return true
+}
+
+#if SERVER
+void function SendNewDamageSourceIDsConnected( entity player )
+{
+ SendNewDamageSourceIDs( player )
+}
+
+void function SendNewDamageSourceIDs( entity player, int index = 0 )
+{
+ while ( index < file.customDamageSourceIDList.len() )
+ {
+ int curSize = 0
+ int curIndex = index
+
+ // Figure out how many sources to send in this message chunk
+ while ( curIndex < file.customDamageSourceIDList.len() )
+ {
+ // Sources are inserted to the custom list in triplets, so we can trust these indices exist.
+ curSize += file.customDamageSourceIDList[ curIndex ].len()
+ curSize += file.customDamageSourceIDList[ curIndex + 1 ].len()
+ curSize += file.customDamageSourceIDList[ curIndex + 2 ].len()
+
+ // Stop before including strings in current message if it exceeds max message length.
+ // This will never stall on a singular source that exceeds the size since new sources are size limited.
+ if ( curSize > SOURCE_ID_MAX_MESSAGE_LENGTH )
+ break
+
+ curIndex += 3
+ }
+
+ // Create the string to pass to client
+ string message = ""
+ while ( index < curIndex )
+ message += file.customDamageSourceIDList[ index++ ] + " "
+
+ ServerToClientStringCommand( player, "register_damage_source_ids " + message )
+ }
+}
+#else
+void function ReceiveNewDamageSourceIDs( array<string> args )
+{
+ // IDs are inserted to the custom list in triplets, so we can trust these indices exist and the loop will end properly
+ for ( int i = 0; i < args.len(); i += 3 )
+ RegisterWeaponDamageSourceInternal( args[ i ].tointeger(), args[ i + 1 ], StringReplace( args[ i + 2 ], MESSAGE_SPACE_PADDING, " ", true ) )
+}
+#endif
diff --git a/Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut b/Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut
new file mode 100644
index 00000000..4cfdc6fb
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut
@@ -0,0 +1,505 @@
+#if SERVER
+global function MessageUtils_ServerInit
+
+global function NSCreatePollOnPlayer
+global function NSGetPlayerResponse
+
+global function NSSendLargeMessageToPlayer
+global function NSSendPopUpMessageToPlayer
+global function NSSendAnnouncementMessageToPlayer
+global function NSSendInfoMessageToPlayer
+
+global function NSCreateStatusMessageOnPlayer
+global function NSEditStatusMessageOnPlayer
+global function NSDeleteStatusMessageOnPlayer
+
+struct
+{
+ table<entity,int> playerPollResponses
+} server
+#endif // SERVER
+
+
+#if CLIENT
+global function MessageUtils_ClientInit
+
+vector ColorSelected = < 0.9, 0.8, 0.5 >
+vector ColorBase = < 0.9, 0.5, 0.1 >
+
+struct tempMessage
+{
+ string title
+ string description
+ float duration
+ string image
+ int priority
+ int style
+ vector color
+}
+
+
+// Nested structs look funny, but are pretty helpful when reading code so I'm keeping them :)
+struct
+{
+ struct
+ {
+ string header
+ array<string> options
+ float duration
+ bool pollActive
+ array<var> ruis
+ } poll
+
+ string id
+ tempMessage temp
+
+ array<tempMessage> largeMessageQueue
+ array<tempMessage> popupMessageQueue
+ array<tempMessage> announcementQueue
+ array<tempMessage> infoMessageQueue
+
+ // table<id,rui>
+ table<string,var> statusMessageList
+} client
+#endif // CLIENT
+
+
+const int STATUS_MESSAGES_MAX = 4
+
+
+enum eMessageType
+{
+ POLL,
+ LARGE,
+ POPUP,
+ ANNOUNCEMENT,
+ INFO,
+ CREATE_STATUS,
+ EDIT_STATUS,
+ DELETE_STATUS
+}
+
+enum eDataType
+{
+ POLL_HEADER,
+ POLL_OPTION,
+ POLL_DURATION,
+ POLL_SELECT,
+ TITLE,
+ DESC,
+ DURATION,
+ ASSET,
+ COLOR,
+ PRIORITY,
+ STYLE,
+ ID
+}
+
+#if SERVER
+void function MessageUtils_ServerInit()
+{
+ AddClientCommandCallback( "vote", ClientCommand_Vote )
+ AddClientCommandCallback( "poll_respond", ClientCommand_PollRespond )
+}
+
+bool function ClientCommand_Vote( entity player, array<string> args )
+{
+ if( args.len() == 0 )
+ return false
+
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.POLL_SELECT + " " + args[0] )
+ return true
+}
+
+bool function ClientCommand_PollRespond( entity player, array<string> args )
+{
+ if( args.len() == 0 )
+ return false
+
+ server.playerPollResponses[player] <- args[0].tointeger()
+ return true
+}
+
+void function NSCreateStatusMessageOnPlayer( entity player, string title, string description, string id )
+{
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.TITLE + " " + title )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.DESC + " " + description )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.ID + " " + id )
+
+ ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.CREATE_STATUS )
+}
+
+void function NSEditStatusMessageOnPlayer( entity player, string title, string description, string id )
+{
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.TITLE + " " + title )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.DESC + " " + description )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.ID + " " + id )
+
+ ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.EDIT_STATUS )
+}
+
+void function NSDeleteStatusMessageOnPlayer( entity player, string id )
+{
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.ID + " " + id )
+
+ ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.DELETE_STATUS )
+}
+
+void function NSCreatePollOnPlayer( entity player, string header, array<string> options, float duration )
+{
+ foreach ( string option in options )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.POLL_OPTION + " " + option )
+
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.POLL_DURATION + " " + duration )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.POLL_HEADER + " " + header )
+
+ server.playerPollResponses[player] <- -1 // Reset poll response table
+ ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.POLL )
+}
+
+int function NSGetPlayerResponse( entity player )
+{
+ if( !( player in server.playerPollResponses ) )
+ return -1
+
+ if( server.playerPollResponses[ player ] == -1 )
+ return -1
+
+ return server.playerPollResponses[ player ] - 1
+}
+
+void function NSSendLargeMessageToPlayer( entity player, string title, string description, float duration, string image )
+{
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.TITLE + " " + title )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.DESC + " " + description )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.DURATION + " " + duration )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.ASSET + " " + image )
+
+ ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.LARGE )
+}
+
+void function NSSendPopUpMessageToPlayer( entity player, string text )
+{
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.DESC + " " + text )
+
+ ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.POPUP )
+}
+
+void function NSSendAnnouncementMessageToPlayer( entity player, string title, string description, vector color, int priority, int style )
+{
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.TITLE + " " + title )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.DESC + " " + description )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.COLOR + " " + color.x + " " + color.y + " " + color.z )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.PRIORITY + " " + priority )
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.STYLE + " " + style )
+
+ ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.ANNOUNCEMENT )
+}
+
+void function NSSendInfoMessageToPlayer( entity player, string text )
+{
+ ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.DESC + " " + text )
+
+ ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.INFO )
+}
+
+#endif // SERVER
+
+#if CLIENT
+void function MessageUtils_ClientInit()
+{
+ // ServerHUDMessageRequest <eMessageType>
+ AddServerToClientStringCommandCallback( "ServerHUDMessageShow", ServerCallback_CreateServerHUDMessage )
+ // ServerHUDMessageRequest <eDataType> <Data>
+ AddServerToClientStringCommandCallback( "ServerHUDMessagePut", ServerCallback_UpdateServerHUDMessage )
+
+ thread LargeMessageHandler_Threaded()
+ thread PopUpMessageHandler_Threaded()
+ thread AnnouncementMessageHandler_Threaded()
+ thread InfoMessageHandler_Threaded()
+}
+
+string function CombineArgsIntoString( array<string> args )
+{
+ string result
+
+ // Ignore the first argument
+ for( int i = 1; i < args.len(); i++ )
+ result += Localize( args[i] ) + " "
+
+ return result
+}
+
+void function ServerCallback_UpdateServerHUDMessage ( array<string> args )
+{
+ switch ( args[0].tointeger() )
+ {
+ case eDataType.POLL_HEADER:
+ client.poll.header = CombineArgsIntoString( args )
+ break
+ case eDataType.POLL_OPTION:
+ client.poll.options.append( CombineArgsIntoString( args ) )
+ break
+ case eDataType.POLL_DURATION:
+ client.poll.duration = args[1].tofloat()
+ break
+ case eDataType.POLL_SELECT:
+ thread SelectPollOption_Threaded( args[1].tointeger() )
+ break
+ case eDataType.TITLE:
+ client.temp.title = CombineArgsIntoString( args )
+ break
+ case eDataType.DESC:
+ client.temp.description = CombineArgsIntoString( args )
+ break
+ case eDataType.DURATION:
+ client.temp.duration = args[1].tofloat()
+ break
+ case eDataType.ASSET:
+ client.temp.image = CombineArgsIntoString( args )
+ break
+ case eDataType.COLOR:
+ client.temp.color = Vector( args[1].tofloat(), args[2].tofloat(), args[3].tofloat())
+ break
+ case eDataType.PRIORITY:
+ client.temp.priority = args[1].tointeger()
+ break
+ case eDataType.STYLE:
+ client.temp.style = args[1].tointeger()
+ break
+ case eDataType.ID:
+ client.id = args[1]
+ break
+ }
+}
+
+void function ServerCallback_CreateServerHUDMessage ( array<string> args )
+{
+ switch ( args[0].tointeger() )
+ {
+ case eMessageType.POLL:
+ thread ShowPollMessage_Threaded()
+ break
+ case eMessageType.LARGE:
+ client.largeMessageQueue.append( client.temp )
+ break
+ case eMessageType.POPUP:
+ client.popupMessageQueue.append( client.temp )
+ break
+ case eMessageType.ANNOUNCEMENT:
+ client.announcementQueue.append( client.temp )
+ break
+ case eMessageType.INFO:
+ client.infoMessageQueue.append( client.temp )
+ break
+ case eMessageType.CREATE_STATUS:
+ CreateStatusMessage( client.id )
+ break
+ case eMessageType.EDIT_STATUS:
+ EditStatusMessage( client.id )
+ break
+ case eMessageType.DELETE_STATUS:
+ thread DeleteStatusMessage( client.id )
+ break
+ }
+}
+
+void function DeleteStatusMessage( string id )
+{
+ if ( id in client.statusMessageList )
+ {
+ var rui = client.statusMessageList[ id ]
+ RuiSetGameTime( rui, "startFadeOutTime", Time() )
+
+ // Remove it from table
+ delete client.statusMessageList[ id ]
+
+ // Wait for animation
+ wait 0.6
+
+ RuiDestroyIfAlive( rui )
+
+ int i = 0
+ foreach( _id, _rui in client.statusMessageList )
+ {
+ RuiSetInt( _rui, "listPos", i )
+ i++
+ }
+ }
+}
+
+void function EditStatusMessage( string id )
+{
+ if( id in client.statusMessageList )
+ {
+ var rui = client.statusMessageList[ id ]
+ RuiSetString( rui, "titleText", client.temp.title )
+ RuiSetString( rui, "itemText", client.temp.description )
+ }
+}
+
+void function CreateStatusMessage( string id )
+{
+ // Cap at 4 messages at a time
+ if( client.statusMessageList.len() == STATUS_MESSAGES_MAX )
+ return
+
+ var rui = CreatePermanentCockpitRui( $"ui/at_wave_intro.rpak" )
+ RuiSetInt( rui, "listPos", client.statusMessageList.len() )
+ RuiSetGameTime( rui, "startFadeInTime", Time() )
+ RuiSetString( rui, "titleText", client.temp.title )
+ RuiSetString( rui, "itemText", client.temp.description )
+ RuiSetFloat2( rui, "offset", < 0, -250, 0 > )
+
+ client.statusMessageList[ id ] <- rui
+}
+
+void function SelectPollOption_Threaded( int index )
+{
+ if ( index >= client.poll.ruis.len() || index <= 0 )
+ return
+
+ RuiSetFloat3( client.poll.ruis[ index ], "msgColor", ColorSelected )
+ EmitSoundOnEntity( GetLocalClientPlayer(), "menu_accept" )
+
+ float endTime = 1 + client.poll.duration
+ while( endTime > Time() && client.poll.pollActive )
+ WaitFrame()
+
+ GetLocalClientPlayer().ClientCommand( "poll_respond " + index )
+
+ foreach( var rui in client.poll.ruis )
+ RuiDestroyIfAlive( rui )
+
+ client.poll.ruis.clear()
+ client.poll.pollActive = false
+}
+
+void function ShowPollMessage_Threaded()
+{
+ if( client.poll.pollActive )
+ return
+
+ client.poll.pollActive = true
+
+ for( int i = 0; i < client.poll.options.len() + 1; i++ )
+ {
+ var rui = CreateCockpitRui( $"ui/cockpit_console_text_top_left.rpak" )
+ // This makes it fade and me no likey >:(
+ RuiSetFloat2( rui, "msgPos", < 0, 0.4 + i * 0.025, 0 > )
+ if( i == 0 )
+ {
+ RuiSetFloat3( rui, "msgColor", ColorSelected )
+ RuiSetString( rui, "msgText", client.poll.header )
+ }
+ else
+ {
+ RuiSetFloat3( rui, "msgColor", ColorBase )
+ RuiSetString( rui, "msgText", i + ". " + client.poll.options[i - 1] )
+ }
+
+ RuiSetFloat( rui, "msgFontSize", 30.0 )
+ RuiSetFloat( rui, "msgAlpha", 0.9 )
+ RuiSetFloat( rui, "thicken", 0.0 )
+
+ client.poll.ruis.append( rui )
+ }
+
+ client.poll.options.clear()
+
+ float endTime = Time() + client.poll.duration
+ while( endTime > Time() && client.poll.pollActive )
+ WaitFrame()
+
+
+ foreach( var rui in client.poll.ruis )
+ RuiDestroyIfAlive( rui )
+
+ client.poll.ruis.clear()
+ client.poll.pollActive = false
+}
+
+void function InfoMessageHandler_Threaded()
+{
+ while( true )
+ {
+ while( client.infoMessageQueue.len() == 0 )
+ WaitFrame()
+
+ var rui = CreatePermanentCockpitRui( $"ui/death_hint_mp.rpak" )
+ RuiSetString( rui, "hintText", client.infoMessageQueue[0].description )
+ RuiSetGameTime( rui, "startTime", Time() )
+ RuiSetFloat3( rui, "bgColor", < 0, 0, 0 > )
+ RuiSetFloat( rui, "bgAlpha", 0.5 )
+
+ wait 7
+
+ client.infoMessageQueue.remove( 0 )
+ RuiDestroyIfAlive( rui )
+ }
+}
+
+void function AnnouncementMessageHandler_Threaded()
+{
+ while( true )
+ {
+ while( client.announcementQueue.len() == 0 )
+ WaitFrame()
+
+ AnnouncementData announcement = Announcement_Create( client.announcementQueue[0].title )
+ Announcement_SetSubText( announcement, client.announcementQueue[0].description )
+ Announcement_SetTitleColor( announcement, client.announcementQueue[0].color )
+ Announcement_SetPurge( announcement, true )
+ Announcement_SetPriority( announcement, client.announcementQueue[0].priority )
+ Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
+ Announcement_SetStyle( announcement, client.announcementQueue[0].style )
+ AnnouncementFromClass( GetLocalViewPlayer(), announcement )
+
+ wait 5
+
+ client.announcementQueue.remove(0)
+ }
+}
+
+void function LargeMessageHandler_Threaded()
+{
+ while( true )
+ {
+ while( client.largeMessageQueue.len() == 0 )
+ WaitFrame()
+
+ var rui = CreatePermanentCockpitRui( $"ui/fd_tutorial_tip.rpak" )
+ RuiSetImage( rui, "backgroundImage", StringToAsset( strip( client.largeMessageQueue[0].image ) ) )
+ RuiSetString( rui, "titleText", client.largeMessageQueue[0].title )
+ RuiSetString( rui, "descriptionText", client.largeMessageQueue[0].description )
+ RuiSetGameTime( rui, "updateTime", Time() )
+ RuiSetFloat( rui, "duration", client.largeMessageQueue[0].duration )
+
+ wait client.largeMessageQueue[0].duration
+
+ client.largeMessageQueue.remove(0)
+ RuiDestroyIfAlive( rui )
+ }
+}
+
+void function PopUpMessageHandler_Threaded()
+{
+ while( true )
+ {
+ while( client.popupMessageQueue.len() == 0 )
+ WaitFrame()
+
+ var rui = CreateCockpitRui( $"ui/killdeath_info.rpak" )
+ RuiSetGameTime( rui, "startTime", Time() )
+ RuiSetFloat( rui, "duration", 20 ) // It has a weird end animation
+ RuiSetString( rui, "messageText", client.popupMessageQueue[0].description )
+ RuiSetBool( rui, "isBigText", true )
+
+ wait 2.4
+
+ client.popupMessageQueue.remove(0)
+ RuiDestroyIfAlive( rui )
+ }
+}
+
+#endif // CLIENT \ No newline at end of file
diff --git a/Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut b/Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut
index 848a4b86..b8d4b1ba 100644
--- a/Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/sh_northstar_custom_precache.gnut
@@ -1,13 +1,16 @@
-untyped
global function NorthstarCustomPrecache
void function NorthstarCustomPrecache()
{
PrecacheWeapon( "mp_weapon_peacekraber" )
PrecacheWeapon( "mp_titanweapon_triplethreat" )
+ PrecacheWeapon( "mp_titanweapon_arc_cannon" )
PrecacheWeapon( "melee_pilot_kunai" )
- // create kunai damage source so game won't crash when we hit smth with it
- // just using the default melee one, easier than making a new one
- getconsttable()[ "eDamageSourceId" ][ "melee_pilot_kunai" ] <- eDamageSourceId.melee_pilot_emptyhanded
+ RegisterWeaponDamageSources(
+ {
+ mp_weapon_peacekraber = "#WPN_PEACEKRABER",
+ melee_pilot_kunai = "#MELEE_KUNAI"
+ }
+ )
}
diff --git a/Northstar.Custom/mod/scripts/vscripts/sh_northstar_http_requests.gnut b/Northstar.Custom/mod/scripts/vscripts/sh_northstar_http_requests.gnut
new file mode 100644
index 00000000..8ff55eae
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/sh_northstar_http_requests.gnut
@@ -0,0 +1,235 @@
+globalize_all_functions
+
+global enum HttpRequestMethod
+{
+ GET = 0,
+ POST = 1
+ HEAD = 2,
+ PUT = 3,
+ DELETE = 4,
+ PATCH = 5,
+ OPTIONS = 6,
+}
+
+global struct HttpRequest
+{
+ /** Method used for this http request. */
+ int method
+ /** Base URL of this http request. */
+ string url
+ /** Headers used for this http request. Some may get overridden or ignored. */
+ table< string, array< string > > headers
+ /** Query parameters for this http request. */
+ table< string, array< string > > queryParameters
+ /** The content type of this http request. Defaults to application/json & UTF-8 charset. */
+ string contentType = "application/json; charset=utf-8"
+ /** The body of this http request. If set, will override queryParameters.*/
+ string body
+ /** The timeout for the http request in seconds. Must be between 1 and 60. */
+ int timeout = 60
+ /** If set, the override to use for the User-Agent header. */
+ string userAgent
+}
+
+global struct HttpRequestResponse
+{
+ /** The status code returned by the remote the call was made to. */
+ int statusCode
+ /** The body of the response. */
+ string body
+ /** The raw headers returned by the remote. */
+ string rawHeaders
+ /** A key -> values table of headers returned by the remote. */
+ table< string, array< string > > headers
+}
+
+global struct HttpRequestFailure
+{
+ /** The error code returned by native for this failure. */
+ int errorCode
+ /** The reason why this http request failed. */
+ string errorMessage
+}
+
+struct HttpRequestCallbacks
+{
+ /**
+ * The function to call if the HTTP request was a success.
+ * Passes in the response received from the remote.
+ */
+ void functionref( HttpRequestResponse ) onSuccess
+
+ /**
+ * The function to call if the HTTP request failed.
+ */
+ void functionref( HttpRequestFailure ) onFailure
+}
+
+table< int, HttpRequestCallbacks > pendingCallbacks
+
+/**
+ * Called from native when a HTTP request is successful.
+ * This is internal and shouldn't be used.
+ * Keep in mind that the success can be successful, but have a non-success status code.
+ * @param handle The handle of the request we got a response for.
+ * @param statusCode The status code returned in the response.
+ * @param body The body returned for GET requests.
+ * @param headers The headers that were returned in the response.
+ */
+void function NSHandleSuccessfulHttpRequest( int handle, int statusCode, string body, string headers )
+{
+ if ( !( handle in pendingCallbacks ) )
+ {
+ return
+ }
+
+ if ( pendingCallbacks[ handle ].onSuccess != null )
+ {
+ HttpRequestResponse response
+ response.statusCode = statusCode
+ response.body = body
+ response.rawHeaders = headers
+
+ // Parse the raw headers into key -> values
+ array<string> values = split( headers, "\n" )
+
+ foreach ( string header in values )
+ {
+ var index = header.find( ":" )
+ if ( index == null )
+ {
+ continue
+ }
+
+ expect int( index )
+
+ string name = strip( header.slice( 0, index ) )
+ string value = strip( header.slice( index + 1 ) )
+
+ if ( name in response.headers )
+ {
+ response.headers[ name ].append( value )
+ }
+ else
+ {
+ response.headers[ name ] <- [ value ]
+ }
+ }
+
+ pendingCallbacks[ handle ].onSuccess( response )
+ }
+
+ delete pendingCallbacks[ handle ]
+}
+
+/**
+ * Called from native when a HTTP request has failed.
+ * This is internal and shouldn't be used.
+ * @param handle The handle of the request that failed.
+ * @param errorCode The error code returned by curl.
+ * @param errorMessage The error message returned by curl.
+ */
+void function NSHandleFailedHttpRequest( int handle, int errorCode, string errorMessage )
+{
+ if ( handle in pendingCallbacks )
+ {
+ if ( pendingCallbacks[ handle ].onFailure != null )
+ {
+ HttpRequestFailure failure
+ failure.errorCode = errorCode
+ failure.errorMessage = errorMessage
+
+ pendingCallbacks[ handle ].onFailure( failure )
+ }
+
+ delete pendingCallbacks[ handle ]
+ }
+}
+
+/**
+ * Launch a HTTP request with the given request data.
+ * This function is async, and the provided callbacks will be called when it is completed.
+ * @param requestParameters The parameters to use for this request.
+ * @param onSuccess The callback to execute if the request is successful.
+ * @param onFailure The callback to execute if the request has failed.
+ * @returns Whether or not the request has been successfully started.
+ */
+bool function NSHttpRequest( HttpRequest requestParameters, void functionref( HttpRequestResponse ) onSuccess = null, void functionref( HttpRequestFailure ) onFailure = null )
+{
+ int handle = NS_InternalMakeHttpRequest( requestParameters.method, requestParameters.url, requestParameters.headers,
+ requestParameters.queryParameters, requestParameters.contentType, requestParameters.body, requestParameters.timeout, requestParameters.userAgent )
+
+ if ( handle != -1 && ( onSuccess != null || onFailure != null ) )
+ {
+ HttpRequestCallbacks callback
+ callback.onSuccess = onSuccess
+ callback.onFailure = onFailure
+
+ pendingCallbacks[ handle ] <- callback
+ }
+
+ return handle != -1
+}
+
+/**
+ * Launches an HTTP GET request at the specified URL with the given query parameters.
+ * This function is async, and the provided callbacks will be called when it is completed.
+ * @param url The url to make the http request for.
+ * @param queryParameters A table of key value parameters to insert in the url.
+ * @param onSuccess The callback to execute if the request is successful.
+ * @param onFailure The callback to execute if the request has failed.
+ * @returns Whether or not the request has been successfully started.
+ */
+bool function NSHttpGet( string url, table< string, array< string > > queryParameters = {}, void functionref( HttpRequestResponse ) onSuccess = null, void functionref( HttpRequestFailure ) onFailure = null )
+{
+ HttpRequest request
+ request.method = HttpRequestMethod.GET
+ request.url = url
+ request.queryParameters = queryParameters
+
+ return NSHttpRequest( request, onSuccess, onFailure )
+}
+
+/**
+ * Launches an HTTP POST request at the specified URL with the given query parameters.
+ * This function is async, and the provided callbacks will be called when it is completed.
+ * @param url The url to make the http request for.
+ * @param queryParameters A table of key value parameters to insert in the url.
+ * @param onSuccess The callback to execute if the request is successful.
+ * @param onFailure The callback to execute if the request has failed.
+ * @returns Whether or not the request has been successfully started.
+ */
+bool function NSHttpPostQuery( string url, table< string, array< string > > queryParameters, void functionref( HttpRequestResponse ) onSuccess = null, void functionref( HttpRequestFailure ) onFailure = null )
+{
+ HttpRequest request
+ request.method = HttpRequestMethod.POST
+ request.url = url
+ request.queryParameters = queryParameters
+
+ return NSHttpRequest( request, onSuccess, onFailure )
+}
+
+/**
+ * Launches an HTTP POST request at the specified URL with the given body.
+ * This function is async, and the provided callbacks will be called when it is completed.
+ * @param url The url to make the http request for.
+ * @param queryParameters A table of key value parameters to insert in the url.
+ * @param onSuccess The callback to execute if the request is successful.
+ * @param onFailure The callback to execute if the request has failed.
+ * @returns Whether or not the request has been successfully started.
+ */
+bool function NSHttpPostBody( string url, string body, void functionref( HttpRequestResponse ) onSuccess = null, void functionref( HttpRequestFailure ) onFailure = null )
+{
+ HttpRequest request
+ request.method = HttpRequestMethod.POST
+ request.url = url
+ request.body = body
+
+ return NSHttpRequest( request, onSuccess, onFailure )
+}
+
+/** Whether or not the given status code is considered successful. */
+bool function NSIsSuccessHttpCode( int statusCode )
+{
+ return statusCode >= 200 && statusCode <= 299
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/sh_northstar_safe_io.gnut b/Northstar.Custom/mod/scripts/vscripts/sh_northstar_safe_io.gnut
new file mode 100644
index 00000000..f7b31cc2
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/sh_northstar_safe_io.gnut
@@ -0,0 +1,83 @@
+globalize_all_functions
+
+table< int, void functionref( string ) > pendingCallbacks
+table< int, void functionref( table ) > pendingJSONCallbacks
+table< int, void functionref() > failedCallbacks
+
+
+void function NSLoadFile( string file, void functionref( string ) onSuccess, void functionref() onFailure = null )
+{
+ int handle = NS_InternalLoadFile( file )
+
+ pendingCallbacks[handle] <- onSuccess
+ if (onFailure != null)
+ failedCallbacks[handle] <- onFailure
+}
+
+void function NSLoadJSONFile( string file, void functionref( table ) onSuccess, void functionref() onFailure = null )
+{
+ int handle = NS_InternalLoadFile( file )
+
+ pendingJSONCallbacks[handle] <- onSuccess
+ if (onFailure != null)
+ failedCallbacks[handle] <- onFailure
+}
+
+void function NSHandleLoadResult( int handle, bool success, string result )
+{
+ bool hasFailedCallback = handle in failedCallbacks
+ bool isJSONRequest = handle in pendingJSONCallbacks
+ bool isValid = isJSONRequest || handle in pendingCallbacks
+
+ if (!isValid)
+ throw "Invalid IO callback handle"
+
+ if (success)
+ {
+ if (isJSONRequest)
+ {
+ try
+ {
+ table result = DecodeJSON(result, true)
+ pendingJSONCallbacks[handle](result)
+ }
+ catch (ex)
+ {
+ print(ex)
+ // parsing failed, setting 'success' to false, since we
+ // consider this a failure.
+ success = false
+ }
+ }
+ else
+ {
+ pendingCallbacks[handle](result)
+ }
+ }
+ // don't use 'else', json might fail parsing and set 'success' to false.
+ if (!success)
+ {
+ if (hasFailedCallback)
+ failedCallbacks[handle]()
+ else
+ {
+ if (isJSONRequest)
+ pendingJSONCallbacks[handle]({})
+ else
+ pendingCallbacks[handle]("")
+ }
+ }
+
+ if (isJSONRequest)
+ delete pendingJSONCallbacks[handle]
+ else
+ delete pendingCallbacks[handle]
+
+ if (hasFailedCallback)
+ delete failedCallbacks[handle]
+}
+
+array<string> function NSGetAllFiles( string path = "" )
+{
+ return NS_InternalGetAllFiles(path)
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut b/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut
index 92b4924b..814e4430 100644
--- a/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut
+++ b/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut
@@ -219,7 +219,7 @@ void function AddArmBadgeToTitan( entity soul )
void function AddArmBadgeToTitan_Internal( entity soul )
{
- soul.EndSignal( "OnDeath" )
+ soul.EndSignal( "OnDestroy" )
// wait until the end of the frame to allow the soul to become owned by a boss player
WaitEndFrame()
diff --git a/Northstar.Custom/mod/scripts/vscripts/ui/ns_custom_mod_settings.gnut b/Northstar.Custom/mod/scripts/vscripts/ui/ns_custom_mod_settings.gnut
new file mode 100644
index 00000000..5a7d80b7
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/ui/ns_custom_mod_settings.gnut
@@ -0,0 +1,8 @@
+global function NSCustomModSettings
+
+void function NSCustomModSettings()
+{
+ ModSettings_AddModTitle( "Northstar Custom" , 2 )
+ ModSettings_AddModCategory( "Event Models" )
+ ModSettings_AddEnumSetting( "ns_show_event_models", "Show Event Models", [ "#SETTING_OFF", "#SETTING_ON" ], 2 )
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut
new file mode 100644
index 00000000..defb1a56
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut
@@ -0,0 +1,1033 @@
+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 = IsValid( vortexWeapon ) ? vortexWeapon.GetWeaponClassName() : ""
+ if ( vortexWeapon && ( className == "mp_titanweapon_vortex_shield" || className == "mp_titanweapon_vortex_shield_ion" ) )
+ {
+ float amount = expect float ( chargeFrac ) * 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 )
+ }
+ }
+ 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, 1, 0.0, 1.0 ) * 10 // do more damage the more charged the weapon is.
+ VortexSphereDrainHealthForDamage( vortexHit.vortex, damage )
+ if ( IsValid( player ) && player.IsPlayer() )
+ player.NotifyDidDamage( vortexHit.vortex, 0, vortexHit.hitPos, 0, damage, DF_NO_HITBEEP, 0, null, 0 )
+ }
+ #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 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
+ int damageFarDistance = eWeaponVar.damage_far_distance
+ int damageNearDistance = eWeaponVar.damage_near_distance
+ 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
+ damageFarDistance = eWeaponVar.npc_damage_far_distance
+ damageNearDistance = eWeaponVar.npc_damage_near_distance
+ }
+
+ if ( IsValid( target ) && IsValid( zapInfo.player ) )
+ {
+ bool hasFastPacitor = false
+ bool noArcing = false
+
+ entity weapon = expect entity( zapInfo.weapon )
+ hasFastPacitor = weapon.GetWeaponInfoFileKeyField( "push_apart" ) != null && weapon.GetWeaponInfoFileKeyField( "push_apart" ) == 1
+ noArcing = weapon.GetWeaponInfoFileKeyField( "no_arcing" ) != null && weapon.GetWeaponInfoFileKeyField( "no_arcing" ) == 1
+ float critScale = weapon.GetWeaponSettingFloat( eWeaponVar.critical_hit_damage_scale )
+
+ if ( target.GetArmorType() == ARMOR_TYPE_HEAVY )
+ {
+ damageMin = weapon.GetWeaponSettingInt( damageFarValueTitanArmor )
+ damageMax = weapon.GetWeaponSettingInt( damageNearValueTitanArmor )
+ }
+ else
+ {
+ damageMin = weapon.GetWeaponSettingInt( damageFarValue )
+ damageMax = weapon.GetWeaponSettingInt( damageNearValue )
+
+ if ( target.IsNPC() )
+ {
+ damageMin *= 3 // more powerful against NPC humans so they die easy
+ damageMax *= 3
+ }
+ }
+
+
+ local chargeRatio = GetArcCannonChargeFraction( weapon )
+ if ( !weapon.GetWeaponSettingBool( eWeaponVar.charge_require_input ) )
+ {
+ // use distance for damage if the weapon auto-fires
+ float nearDist = weapon.GetWeaponSettingFloat( damageNearDistance )
+ float farDist = weapon.GetWeaponSettingFloat( damageFarDistance )
+
+ 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 ( weapon.HasMod( "splitter" ) )
+ damageFalloff = SPLITTER_DAMAGE_FALLOFF_SCALER
+ damageAmount *= pow( damageFalloff, chainNum - 1 )
+
+ local isMissile = ( target.GetClassName() == "rpg_missile" )
+ if ( !isMissile )
+ wait ARC_CANNON_FORK_DELAY
+ else
+ wait 0.05
+
+ if ( !IsValid( target ) || !IsValid( zapInfo.player ) )
+ return
+
+ 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,criticalHitScale = critScale } )
+ //vector dir = Normalize( beamEndPos - beamStartPos )
+ //vector velocity = dir * 600
+ //PushPlayerAway( target, velocity )
+ //PushPlayerAway( expect entity( zapInfo.player ), -velocity )
+
+ if ( IsValid( 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, entity weapon )
+{
+ array<entity> targets = GetScriptManagedEntArrayWithinCenter( level._arcCannonTargetsArrayID, team, origin, ARC_CANNON_TITAN_RANGE_CHAIN )
+
+ if ( ARC_CANNON_TARGETS_MISSILES && weapon.GetWeaponChargeFraction() == 1.0 )
+ 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, weapon )
+ 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
+}
diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/mp_titanweapon_arc_cannon.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_titanweapon_arc_cannon.nut
new file mode 100644
index 00000000..78879393
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_titanweapon_arc_cannon.nut
@@ -0,0 +1,226 @@
+untyped
+
+global function MpTitanweaponArcCannon_Init
+
+global function OnWeaponActivate_titanweapon_arc_cannon
+global function OnWeaponDeactivate_titanweapon_arc_cannon
+global function OnWeaponReload_titanweapon_arc_cannon
+global function OnWeaponOwnerChanged_titanweapon_arc_cannon
+global function OnWeaponChargeBegin_titanweapon_arc_cannon
+global function OnWeaponChargeEnd_titanweapon_arc_cannon
+global function OnWeaponPrimaryAttack_titanweapon_arc_cannon
+
+const FX_EMP_BODY_HUMAN = $"P_emp_body_human"
+const FX_EMP_BODY_TITAN = $"P_emp_body_titan"
+
+#if SERVER
+global function OnWeaponNpcPrimaryAttack_titanweapon_arc_cannon
+#endif // #if SERVER
+
+void function MpTitanweaponArcCannon_Init()
+{
+ ArcCannon_PrecacheFX()
+
+ #if SERVER
+ AddDamageCallbackSourceID( eDamageSourceId.mp_titanweapon_arc_cannon, ArcRifleOnDamage )
+ #endif
+}
+
+void function OnWeaponActivate_titanweapon_arc_cannon( entity weapon )
+{
+ entity weaponOwner = weapon.GetWeaponOwner()
+ thread DelayedArcCannonStart( weapon, weaponOwner )
+ if( !("weaponOwner" in weapon.s) )
+ weapon.s.weaponOwner <- weaponOwner
+}
+
+function DelayedArcCannonStart( entity weapon, entity weaponOwner )
+{
+ weapon.EndSignal( "WeaponDeactivateEvent" )
+
+ WaitFrame()
+
+ if ( IsValid( weapon ) && IsValid( weaponOwner ) && weapon == weaponOwner.GetActiveWeapon() )
+ {
+ if( weaponOwner.IsPlayer() )
+ {
+ entity modelEnt = weaponOwner.GetViewModelEntity()
+ if( IsValid( modelEnt ) && EntHasModelSet( modelEnt ) )
+ ArcCannon_Start( weapon )
+ }
+ else
+ {
+ ArcCannon_Start( weapon )
+ }
+ }
+}
+
+void function OnWeaponDeactivate_titanweapon_arc_cannon( entity weapon )
+{
+ ArcCannon_ChargeEnd( weapon, expect entity( weapon.s.weaponOwner ) )
+ ArcCannon_Stop( weapon )
+}
+
+void function OnWeaponReload_titanweapon_arc_cannon( entity weapon, int milestoneIndex )
+{
+ local reloadTime = weapon.GetWeaponInfoFileKeyField( "reload_time" )
+ thread ArcCannon_HideIdleEffect( weapon, reloadTime ) //constant seems to help it sync up better
+}
+
+void function OnWeaponOwnerChanged_titanweapon_arc_cannon( entity weapon, WeaponOwnerChangedParams changeParams )
+{
+ #if CLIENT
+ entity viewPlayer = GetLocalViewPlayer()
+ if ( changeParams.oldOwner != null && changeParams.oldOwner == viewPlayer )
+ {
+ ArcCannon_ChargeEnd( weapon, changeParams.oldOwner )
+ ArcCannon_Stop( weapon)
+ }
+
+ if ( changeParams.newOwner != null && changeParams.newOwner == viewPlayer )
+ thread ArcCannon_HideIdleEffect( weapon, 0.25 )
+ #else
+ if ( changeParams.oldOwner != null )
+ {
+ ArcCannon_ChargeEnd( weapon, changeParams.oldOwner )
+ ArcCannon_Stop( weapon )
+ }
+
+ if ( changeParams.newOwner != null )
+ thread ArcCannon_HideIdleEffect( weapon, 0.25 )
+ #endif
+}
+
+bool function OnWeaponChargeBegin_titanweapon_arc_cannon( entity weapon )
+{
+ local stub = "this is here to suppress the untyped message. This can go away when the .s. usage is removed from this file."
+ #if SERVER
+ //if ( weapon.HasMod( "fastpacitor_push_apart" ) )
+ // weapon.GetWeaponOwner().StunMovementBegin( weapon.GetWeaponSettingFloat( eWeaponVar.charge_time ) )
+ #endif
+
+ ArcCannon_ChargeBegin( weapon )
+
+ return true
+}
+
+void function OnWeaponChargeEnd_titanweapon_arc_cannon( entity weapon )
+{
+ ArcCannon_ChargeEnd( weapon, weapon )
+}
+
+var function OnWeaponPrimaryAttack_titanweapon_arc_cannon( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ if ( weapon.HasMod( "capacitor" ) && weapon.GetWeaponChargeFraction() < GetArcCannonChargeFraction( weapon ) )
+ return 0
+
+ if ( !attackParams.firstTimePredicted )
+ return
+
+ local fireRate = weapon.GetWeaponInfoFileKeyField( "fire_rate" )
+ thread ArcCannon_HideIdleEffect( weapon, (1 / fireRate) )
+
+ return FireArcCannon( weapon, attackParams )
+}
+
+#if SERVER
+var function OnWeaponNpcPrimaryAttack_titanweapon_arc_cannon( entity weapon, WeaponPrimaryAttackParams attackParams )
+{
+ local fireRate = weapon.GetWeaponInfoFileKeyField( "fire_rate" )
+ thread ArcCannon_HideIdleEffect( weapon, fireRate )
+
+ return FireArcCannon( weapon, attackParams )
+}
+
+void function ArcRifleOnDamage( entity ent, var damageInfo )
+{
+ vector pos = DamageInfo_GetDamagePosition( damageInfo )
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ EmitSoundOnEntity( ent, ARC_CANNON_TITAN_SCREEN_SFX )
+
+ if ( ent.IsPlayer() || ent.IsNPC() )
+ {
+ entity entToSlow = ent
+ entity soul = ent.GetTitanSoul()
+
+ if ( soul != null )
+ entToSlow = soul
+
+ StatusEffect_AddTimed( entToSlow, eStatusEffect.move_slow, 0.5, 2.0, 1.0 )
+ StatusEffect_AddTimed( entToSlow, eStatusEffect.dodge_speed_slow, 0.5, 2.0, 1.0 )
+ }
+
+ string tag = ""
+ asset effect
+
+ if ( ent.IsTitan() )
+ {
+ tag = "exp_torso_front"
+ effect = FX_EMP_BODY_TITAN
+ }
+ else if ( ChestFocusTarget( ent ) )
+ {
+ tag = "CHESTFOCUS"
+ effect = FX_EMP_BODY_HUMAN
+ }
+ else if ( IsAirDrone( ent ) )
+ {
+ tag = "HEADSHOT"
+ effect = FX_EMP_BODY_HUMAN
+ }
+ else if ( IsGunship( ent ) )
+ {
+ tag = "ORIGIN"
+ effect = FX_EMP_BODY_TITAN
+ }
+
+ if ( tag != "" )
+ {
+ float duration = 2.0
+ //thread EMP_FX( effect, ent, tag, duration )
+ }
+
+ if ( ent.IsTitan() )
+ {
+ if ( ent.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( ent, ent, "titan_energy_bulletimpact_3p_vs_1p" )
+ EmitSoundOnEntityExceptToPlayer( ent, ent, "titan_energy_bulletimpact_3p_vs_3p" )
+ }
+ else
+ {
+ EmitSoundOnEntity( ent, "titan_energy_bulletimpact_3p_vs_3p" )
+ }
+ }
+ else
+ {
+ if ( ent.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( ent, ent, "flesh_lavafog_deathzap_3p" )
+ EmitSoundOnEntityExceptToPlayer( ent, ent, "flesh_lavafog_deathzap_1p" )
+ }
+ else
+ {
+ EmitSoundOnEntity( ent, "flesh_lavafog_deathzap_1p" )
+ }
+ }
+
+}
+
+bool function ChestFocusTarget( entity ent )
+{
+ if ( IsSpectre( ent ) )
+ return true
+ if ( IsStalker( ent ) )
+ return true
+ if ( IsSuperSpectre( ent ) )
+ return true
+ if ( IsGrunt( ent ) )
+ return true
+ if ( IsPilot( ent ) )
+ return true
+
+ return false
+}
+#endif // #if SERVER
diff --git a/Northstar.Custom/mod/scripts/weapons/mp_titanweapon_arc_cannon.txt b/Northstar.Custom/mod/scripts/weapons/mp_titanweapon_arc_cannon.txt
new file mode 100644
index 00000000..2672dca9
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/weapons/mp_titanweapon_arc_cannon.txt
@@ -0,0 +1,349 @@
+WeaponData
+{
+ // General
+ "printname" "#WPN_TITAN_ARC_CANNON"
+ "shortprintname" "#WPN_TITAN_ARC_CANNON_SHORT"
+ "description" "#WPN_TITAN_ARC_CANNON_DESC"
+ "longdesc" "#WPN_TITAN_ARC_CANNON_LONGDESC"
+ "weaponClass" "titan"
+ "fire_mode" "semi-auto"
+ "pickup_hold_prompt" "Hold [USE] [WEAPONNAME]"
+ "pickup_press_prompt" "[USE] [WEAPONNAME]"
+ "minimap_reveal_distance" "32000"
+
+ // Menu Stats
+ "stat_damage" "85"
+ "stat_range" "35"
+ "stat_accuracy" "80"
+ "stat_rof" "20"
+
+ // Models
+ "viewmodel" "models/weapons/titan_arc_rifle/atpov_titan_arc_rifle.mdl"
+ "playermodel" "models/weapons/titan_arc_rifle/w_titan_arc_rifle.mdl"
+ "anim_prefix" "ar2"
+
+
+ "OnWeaponActivate" "OnWeaponActivate_titanweapon_arc_cannon"
+ "OnWeaponDeactivate" "OnWeaponDeactivate_titanweapon_arc_cannon"
+ "OnWeaponReload" "OnWeaponReload_titanweapon_arc_cannon"
+ "OnWeaponOwnerChanged" "OnWeaponOwnerChanged_titanweapon_arc_cannon"
+ "OnWeaponChargeBegin" "OnWeaponChargeBegin_titanweapon_arc_cannon"
+ "OnWeaponChargeEnd" "OnWeaponChargeEnd_titanweapon_arc_cannon"
+ "OnWeaponPrimaryAttack" "OnWeaponPrimaryAttack_titanweapon_arc_cannon"
+ "OnWeaponNpcPrimaryAttack" "OnWeaponNpcPrimaryAttack_titanweapon_arc_cannon"
+// "OnWeaponCooldown" "OnWeaponCooldown_titanweapon_particle_accelerator"
+
+
+
+ // Effects
+ //"tracer_effect" "weapon_tracers_xo16"
+ //Impact Table used for visuals at the top of arc_cannon.nut
+ "tracer_effect" "P_wpn_arcball_beam"
+ "tracer_effect_first_person" "P_wpn_arcball_beam"
+ "impact_effect_table" "exp_arc_cannon"
+ "adjust_to_gun_barrel" "1"
+ "fx_muzzle_flash_view" "wpn_arc_cannon_electricity_fp"
+ "fx_muzzle_flash_world" "wpn_arc_cannon_electricity"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+
+ // Damage - When Used by Players
+ "damage_type" "bullet"
+ "damage_near_distance" "200"
+ "damage_far_distance" "2500"
+ "damage_near_value" "220"
+ "damage_far_value" "170"
+ "damage_near_value_titanarmor" "1800"
+ "damage_far_value_titanarmor" "100"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_distance" "200"
+ "npc_damage_far_distance" "2500"
+ "npc_damage_near_value" "220"
+ "npc_damage_far_value" "170"
+ "npc_damage_near_value_titanarmor" "1800"
+ "npc_damage_far_value_titanarmor" "100"
+
+ "critical_hit" "0"
+ "critical_hit_damage_scale" "1.5"
+
+ // Ammo
+ "ammo_min_to_fire" "1"
+ "ammo_no_remove_from_stockpile" "1"
+
+ // Behavior
+ "fire_rate" "1"
+// "rechamber_time" "0.25" //"1.30"
+ "cooldown_time" "0.6"
+ "reloadempty_time" "6.03"
+ "reloadempty_time_late1" "4.7"
+ "reloadempty_time_late2" "3.5"
+ "reloadempty_time_late3" "2.5"
+ "reloadempty_time_late4" "1.43"
+ "reloadempty_time_late5" "0.56"
+ "zoom_time_in" "0.1"
+ "zoom_time_out" "0.1"
+ "zoom_fov" "33"
+ "reload_time" "3.5"
+ "reloadempty_time" "3.5"
+ "holster_time" ".45"
+ "deploy_time" ".85"
+ "lower_time" ".1"
+ "raise_time" ".4"
+ "charge_time" "3.7"
+ "charge_cooldown_time" "1.0"
+ "charge_end_forces_fire" "0"
+ "allow_empty_fire" "1"
+ "reload_enabled" "0"
+ "allow_empty_click" "1"
+ "empty_reload_only" "0"
+ "trigger_snipercam" "1"
+ "allow_headshots" "0"
+ "bypass_semiauto_hold_protection" "1"
+ "vortex_drain" ".15"
+ "charge_effect_1p" "wpn_arc_cannon_charge_fp"
+ "charge_effect_3p" "wpn_arc_cannon_charge"
+ "charge_effect_attachment" "muzzle_flash"
+
+
+
+ // Spread
+ "spread_stand_hip" "10"
+ "spread_npc" "2"
+
+ "ammo_suck_behavior" "primary_weapons"
+
+ // View Kick
+ "viewkick_spring" "titan_arc"
+
+ "viewkick_pitch_base" "-1"
+ "viewkick_pitch_random" "0.5"
+ "viewkick_pitch_softScale" "1"
+ "viewkick_pitch_hardScale" "0"
+
+ "viewkick_yaw_base" "0"
+ "viewkick_yaw_random" "0.5"
+ "viewkick_yaw_softScale" "1"
+ "viewkick_yaw_hardScale" "0"
+
+ "viewkick_roll_base" "0.0"
+ "viewkick_roll_randomMin" "0.3"
+ "viewkick_roll_randomMax" "0.45"
+ "viewkick_roll_softScale" "0.2"
+ "viewkick_roll_hardScale" "1.5"
+
+ "viewkick_hipfire_weaponFraction" "0.5"
+ "viewkick_hipfire_weaponFraction_vmScale" "0.75"
+ "viewkick_ads_weaponFraction" "0.6"
+ "viewkick_ads_weaponFraction_vmScale" "0.2"
+
+
+ // Bob
+ "bob_cycle_time" "0.7"
+ "bob_vert_dist" "0.5"
+ "bob_horz_dist" "1"
+ "bob_max_speed" "150"
+ "bob_pitch" "1"
+ "bob_yaw" "1"
+ "bob_roll" "-0.75"
+
+ // View Drift
+
+ // Rumble
+ "fire_rumble" "titan_arc_cannon"
+
+ // 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" "-1"
+ "sway_max_pitch" "3"
+ "sway_max_yaw" "3.5"
+ "sway_max_roll" "2"
+ "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" "-1"
+ "sway_move_right_translate_y" "1"
+ "sway_move_right_translate_z" "-0.5"
+ "sway_move_right_rotate_roll" "2"
+ "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"
+
+ // NPC
+ "proficiency_poor_spreadscale" "5.0"
+ "proficiency_poor_bias" "1.0"
+ "proficiency_average_spreadscale" "4.0"
+ "proficiency_average_bias" "1.0"
+ "proficiency_good_spreadscale" "3.0"
+ "proficiency_good_bias" "1.0"
+ "proficiency_very_good_spreadscale" "2.3"
+ "proficiency_very_good_bias" "1.0"
+ "proficiency_perfect_spreadscale" "1.7"
+ "proficiency_perfect_bias" "1.0"
+
+ "npc_min_range" "0"
+ "npc_max_range" "2500"
+ "npc_min_range_secondary" "0"
+ "npc_max_range_secondary" "2500"
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "rest_time_between_bursts_min" "2.5"
+ "rest_time_between_bursts_max" "3.0"
+
+ // WeaponED Unhandled Key/Values and custom script Key/Values
+ "sound_dryfire" "titan_dryfire"
+ "viewdrift_hipfire_stand_scale_pitch" "0.1"
+ "viewdrift_hipfire_crouch_scale_pitch" "0.1"
+ "viewdrift_hipfire_air_scale_pitch" "0.1"
+ "viewdrift_hipfire_stand_scale_yaw" "0.075"
+ "viewdrift_hipfire_crouch_scale_yaw" "0.075"
+ "viewdrift_hipfire_air_scale_yaw" "0.075"
+ "viewdrift_hipfire_speed_pitch" "0.6"
+ "viewdrift_hipfire_speed_yaw" "1.22"
+ "viewdrift_ads_stand_scale_pitch" "0.05"
+ "viewdrift_ads_crouch_scale_pitch" "0.05"
+ "viewdrift_ads_air_scale_pitch" "0.05"
+ "viewdrift_ads_stand_scale_yaw" "0.037"
+ "viewdrift_ads_crouch_scale_yaw" "0.037"
+ "viewdrift_ads_air_scale_yaw" "0.037"
+ "viewdrift_ads_speed_pitch" "0.6"
+ "viewdrift_ads_speed_yaw" "1.22"
+ "npc_reload_enabled" "0"
+ "npc_vortex_block" "1"
+
+ // Crosshair
+ "red_crosshair_range" "2500"
+
+ Mods
+ {
+ overcharge
+ {
+ //overcharge
+ }
+ capacitor
+ {
+ "charge_time" "2.5" //for reference was 3 in 10/15 evening playtest
+ "charge_cooldown_time" "1.0"
+ "charge_cooldown_delay" "0.0"
+ //"crosshair_index" "1"
+ "spread_stand_hip" "15"
+ "damage_far_distance" "2700"
+ "damage_near_value_titanarmor" "2000"
+ }
+ splitter
+ {
+ "damage_near_value_titanarmor" "1900"
+ "damage_far_value_titanarmor" "100"
+ }
+ burn_mod_titan_arc_cannon
+ {
+ //"crosshair_index" "2"
+ "tracer_effect" "wpn_arc_cannon_beam_mod"
+ "tracer_effect_first_person" "wpn_arc_cannon_beam_mod"
+ "damage_near_value" "*1.1"
+ "damage_far_value" "*1.1"
+ "damage_near_value_titanarmor" "*1.1"
+ "damage_far_value_titanarmor" "*1.1"
+ "is_burn_mod" "1"
+ }
+ }
+
+ active_crosshair_count "2"
+// rui_crosshair_index "1"
+ "ui1_enable" "1"
+ "ui1_draw_cloaked" "0"
+ UiData1
+ {
+ "ui" "ui/crosshair_charge_rifle"
+ "mesh" "models/weapons/attachments/alternator_rui_upper"
+ Args
+ {
+ adjustedSpread weapon_spread
+ adsFrac player_zoomFrac
+ isSprinting player_is_sprinting
+ isReloading weapon_is_reloading
+ readyFrac progress_ready_to_fire_frac
+ teamColor crosshair_team_color
+ isAmped weapon_is_amped
+ chargeFrac player_chargeFrac
+ crosshairMovementX crosshair_movement_x
+ crosshairMovementY crosshair_movement_y
+ }
+ }
+ RUI_CrosshairData
+ {
+ DefaultArgs
+ {
+ adjustedSpread weapon_spread
+ adsFrac player_zoomFrac
+ isSprinting player_is_sprinting
+ isReloading weapon_is_reloading
+ readyFrac progress_ready_to_fire_frac
+ teamColor crosshair_team_color
+ isAmped weapon_is_amped
+ chargeFrac player_chargeFrac
+ crosshairMovementX crosshair_movement_x
+ crosshairMovementY crosshair_movement_y
+ }
+
+ Crosshair_1
+ {
+ "ui" "ui/crosshair_charge_rifle"
+// "ui" "ui/alternator_reticle"
+ "base_spread" "10.0"
+ Args
+ {
+ isFiring weapon_is_firing
+ }
+ Element0
+ {
+ "fade_while_sprinting" "1"
+ "fade_while_reloading" "1"
+ "stationary" "1"
+ "default_color" "246 134 40 255"
+ "type" "static"
+ "material" $"vgui/hud/arc_cannon_charge/arc_cannon_charge"
+ "size_x" "80"
+ "size_y" "80"
+ "scale_ads" "1.5"
+ }
+ Element1
+ {
+ "fade_while_sprinting" "1"
+ "fade_while_reloading" "1"
+ "stationary" "1"
+ "default_color" "246 134 40 255"
+ "type" "static"
+ "material" "vgui/hud/arc_cannon_charge/arc_cannon_shadow_horizontal"
+ "size_x" "80"
+ "size_y" "80"
+ "scale_ads" "1.5"
+ }
+ }
+
+ Crosshair_2
+ {
+ "ui" "ui/crosshair_circle2"
+ "base_spread" "0.0"
+ Args
+ {
+ isFiring weapon_is_firing
+ }
+ }
+ }
+}
diff --git a/Northstar.Custom/mod/scripts/weapons/mp_titanweapon_triplethreat.txt b/Northstar.Custom/mod/scripts/weapons/mp_titanweapon_triplethreat.txt
index a1337d9f..09aac6ea 100644
--- a/Northstar.Custom/mod/scripts/weapons/mp_titanweapon_triplethreat.txt
+++ b/Northstar.Custom/mod/scripts/weapons/mp_titanweapon_triplethreat.txt
@@ -102,6 +102,8 @@ WeaponData
// Damage - When Used by Players
"damage_type" "burn"
+ "show_grenade_indicator" "1"
+
"crosshair" "crosshair_t"
"explosion_damage" "350" // 150
"explosion_damage_heavy_armor" "550" // 150
diff --git a/Northstar.Custom/mod/scripts/weapons/mp_weapon_shotgun_doublebarrel.txt b/Northstar.Custom/mod/scripts/weapons/mp_weapon_shotgun_doublebarrel.txt
new file mode 100644
index 00000000..2bc8b094
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/weapons/mp_weapon_shotgun_doublebarrel.txt
@@ -0,0 +1,405 @@
+WeaponData
+{
+ // General
+ "printname" "#WPN_SHOTGUN_DBLBARREL"
+ "shortprintname" "#WPN_SHOTGUN_DBLBARREL_SHORT"
+ "description" "#WPN_SHOTGUN_DBLBARREL_DESC"
+ "longdesc" "#WPN_SHOTGUN_DBLBARREL_LONGDESC"
+ "menu_icon" "rui/weapon_icons/mp_weapon_shotgun_doublebarrel"
+ "hud_icon" "rui/weapon_icons/mp_weapon_shotgun_doublebarrel"
+ "viewmodel_offset_hip" "2 -2 -2"
+ "weaponClass" "human"
+ "weaponSubClass" "shotgun"
+ "body_type" "light"
+ "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"
+
+ // Models
+ "viewmodel" "models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdl"
+ "playermodel" "models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdl"
+
+ "OnWeaponPrimaryAttack" "OnWeaponPrimaryAttack_weapon_shotgun"
+ "OnWeaponNpcPrimaryAttack" "OnWeaponNpcPrimaryAttack_weapon_shotgun"
+
+
+ "viewmodel_offset_ads" "0 0 0"
+ "dof_zoom_nearDepthStart" "2.000"
+ "dof_zoom_nearDepthEnd" "4.750"
+ "dof_nearDepthStart" "3.683"
+ "dof_nearDepthEnd" "5.300"
+
+ // Menu
+ "menu_category" "shotgun"
+ "menu_anim_class" "large"
+ "stat_damage" "50"
+ "stat_range" "70"
+ "stat_accuracy" "65"
+ "stat_rof" "30"
+
+ "impulse_force" "800"
+
+ "impact_effect_table" "inc_bullet"
+
+ // Spread
+ "spread_stand_hip" "8.5"
+ "spread_stand_hip_run" "8.5"
+ "spread_stand_hip_sprint" "8.5"
+ "spread_crouch_hip" "8.5"
+ "spread_air_hip" "8.5"
+ "spread_stand_ads" "8.5"
+ "spread_crouch_ads" "8.5"
+ "spread_air_ads" "8.5"
+ "spread_wallrunning" "8.5"
+ "spread_wallhanging" "8.5"
+
+ // Damage - When Used by Players
+ "damage_type" "bullet"
+ "damage_near_distance" "300"
+ "damage_far_distance" "700"
+ "damage_near_value" "220"
+ "damage_far_value" "25"
+ "damage_near_value_titanarmor" "130"
+ "damage_far_value_titanarmor" "10"
+
+ "damage_rodeo" "700"
+ "damage_falloff_type" "inverse"
+ "damage_inverse_distance" "130"
+ "damage_falloff_type" "inverse"
+ "damage_inverse_distance" "100"
+ "damage_flags" "DF_SHOTGUN | DF_BULLET | DF_KNOCK_BACK | DF_DISMEMBERMENT"
+
+ "damage_headshot_scale" "1.25"
+
+ "red_crosshair_range" "750"
+
+ // Damage - When Used by NPCs
+ "npc_damage_near_value" "25"
+ "npc_damage_far_value" "13"
+ "npc_damage_near_value_titanarmor" "40"
+ "npc_damage_far_value_titanarmor" "0"
+
+ "enable_highlight_networking_on_creation" "1"
+
+ "damage_heavyarmor_nontitan_scale" "0.35"
+
+
+ // Ammo
+ "ammo_stockpile_max" "40"
+ "ammo_default_total" "40"
+ "ammo_clip_size" "2"
+ "ammo_no_remove_from_stockpile" "1"
+ "ammo_min_to_fire" "1"
+
+ "reload_time" "1.85"
+ "reload_time_late1" "1.15"
+ "reloadempty_time" "1.85"
+ "reloadempty_time_late1" "1.15"
+
+
+ // Effects
+ "tracer_effect" "weapon_tracers_shotgun"
+ "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"
+
+ "sound_dryfire" "shotgun_dryfire"
+ "sound_pickup" "wpn_pickup_Rifle_1P"
+ "fire_sound_1_player_1p" "Weapon_EVA8_AutoFire_1P"
+ "fire_sound_1_player_3p" "Weapon_EVA8_AutoFire_3P"
+ "fire_sound_1_npc" "Weapon_EVA8_AutoFire_NPC"
+ "sound_zoom_in" "Weapon_EVA8_ADS_In"
+ "sound_zoom_out" "Weapon_EVA8_ADS_Out"
+
+ "low_ammo_sound_name_1" "EVA8_LowAmmo_Shot1"
+ "low_ammo_sound_name_2" "EVA8_LowAmmo_Shot2"
+ "low_ammo_sound_name_3" "EVA8_LowAmmo_Shot3"
+
+ "fx_shell_eject_view" "wpn_shelleject_shotshell_FP"
+ "fx_shell_eject_world" "wpn_shelleject_shotshell"
+ "fx_shell_eject_attach" "shell"
+
+ "fx_muzzle_flash_view" "mflash_shotgun_fp_FULL"
+ "fx_muzzle_flash_world" "mflash_shotgun_FULL"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+
+
+
+ "critical_hit_damage_scale" "1"
+ "critical_hit" "1"
+
+ dof_zoom_focusArea_horizontal 0.036
+ dof_zoom_focusArea_top 0.070
+ dof_zoom_focusArea_bottom -0.023
+
+
+ "titanarmor_critical_hit_required" "1"
+
+
+ // Behavior
+ "fire_rate" "2.75"
+ "zoom_time_in" "0.25"
+ "zoom_time_out" "0.2"
+ "zoom_fov" "55"
+ "holster_time" "0.5"
+ "deploy_time" "0.66"
+ "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" "1"
+ "primary_fire_does_not_block_sprint" "0"
+ "ads_move_speed_scale" "0.75"
+ "aimassist_disable_hipfire" "0"
+ "aimassist_disable_ads" "0"
+ "aimassist_disable_hipfire_titansonly" "1"
+ "aimassist_disable_ads_titansonly" "1"
+ "headshot_distance" "500"
+
+
+ "sprint_fractional_anims" "0"
+
+
+
+ "ammo_suck_behavior" "primary_weapons"
+
+ // View Kick
+ "viewkick_spring" "shotgun"
+
+ "viewkick_pitch_base" "-1.75"
+ "viewkick_pitch_random" "0.75"
+ "viewkick_pitch_softScale" "0.3"
+ "viewkick_pitch_hardScale" "1.5"
+
+ "viewkick_yaw_base" "-0.65"
+ "viewkick_yaw_random" "0.38"
+ "viewkick_yaw_softScale" "0.38"
+ "viewkick_yaw_hardScale" "1.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" "0.35"
+ "viewkick_ads_weaponFraction_vmScale" "0.25"
+
+ "viewkick_perm_pitch_base" "-0.5"
+ "viewkick_perm_pitch_random" "1.1"
+ "viewkick_perm_pitch_random_innerexclude" "0.5"
+ "viewkick_perm_yaw_base" "0.0"
+ "viewkick_perm_yaw_random" "1.5"
+ "viewkick_perm_yaw_random_innerexclude" "0.5"
+
+ //
+ "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"
+
+ // 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_shotgun"
+
+ // 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.045"
+ "sway_max_yaw_zoomed" "0.045"
+ "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"
+
+ // NPC
+ "proficiency_poor_spreadscale" "7.0"
+ "proficiency_average_spreadscale" "5.0"
+ "proficiency_good_spreadscale" "4.5"
+ "proficiency_very_good_spreadscale" "3.7"
+
+ "npc_min_engage_range" "0"
+ "npc_max_engage_range" "800"
+ "npc_min_engage_range_heavy_armor" "500"
+ "npc_max_engage_range_heavy_armor" "800"
+ "npc_min_range" "0"
+ "npc_max_range" "800"
+
+ "npc_min_burst" "1"
+ "npc_max_burst" "1"
+ "npc_rest_time_between_bursts_min" "0.5"
+ "npc_rest_time_between_bursts_max" "0.7"
+
+ // 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.25"
+ "deploycatch_time" "1.33"
+ "sprintcycle_time" ".55"
+
+
+ "clip_bodygroup" "twinbshotgun_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"
+ Mods
+ {
+ iron_sights
+ {
+ }
+ pas_run_and_gun
+ {
+ "primary_fire_does_not_block_sprint" "1"
+ "crosshair_force_sprint_fade_disabled" "1"
+ }
+ 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_shotgun
+ {
+ "is_burn_mod" "1"
+ "fx_muzzle_flash_view" "P_wpn_muz_shotgun_amp_FP"
+ "fx_muzzle_flash_world" "P_wpn_muz_shotgun_amp"
+ "fx_muzzle_flash_attach" "muzzle_flash"
+ "tracer_effect" "P_wpn_tracer_shotgun_BC"
+
+ "damage_near_value" "250"
+ "damage_far_value" "20"
+ "damage_near_value_titanarmor" "400"
+ "damage_far_value_titanarmor" "20"
+ }
+ }
+
+
+ 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_shotgun"
+ "base_spread" "-4.0"
+ Args
+ {
+ isFiring weapon_is_firing
+ }
+ }
+ }
+}
diff --git a/Northstar.Custom/paks/bt.rpak b/Northstar.Custom/paks/bt.rpak
new file mode 100644
index 00000000..7a4b9e31
--- /dev/null
+++ b/Northstar.Custom/paks/bt.rpak
Binary files differ
diff --git a/Northstar.Custom/paks/bt.starpak b/Northstar.Custom/paks/bt.starpak
new file mode 100644
index 00000000..70549d51
--- /dev/null
+++ b/Northstar.Custom/paks/bt.starpak
Binary files differ
diff --git a/Northstar.Custom/paks/giftwrap.rpak b/Northstar.Custom/paks/giftwrap.rpak
new file mode 100644
index 00000000..7b9200b3
--- /dev/null
+++ b/Northstar.Custom/paks/giftwrap.rpak
Binary files differ
diff --git a/Northstar.Custom/paks/giftwrap.starpak b/Northstar.Custom/paks/giftwrap.starpak
new file mode 100644
index 00000000..46ea6d8d
--- /dev/null
+++ b/Northstar.Custom/paks/giftwrap.starpak
Binary files differ
diff --git a/Northstar.Custom/paks/leaves.rpak b/Northstar.Custom/paks/leaves.rpak
new file mode 100644
index 00000000..b17346dd
--- /dev/null
+++ b/Northstar.Custom/paks/leaves.rpak
Binary files differ
diff --git a/Northstar.Custom/paks/leaves.starpak b/Northstar.Custom/paks/leaves.starpak
new file mode 100644
index 00000000..b37aa523
--- /dev/null
+++ b/Northstar.Custom/paks/leaves.starpak
Binary files differ
diff --git a/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.rpak b/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.rpak
new file mode 100644
index 00000000..440f16ae
--- /dev/null
+++ b/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.rpak
Binary files differ
diff --git a/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.starpak b/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.starpak
new file mode 100644
index 00000000..93e59c89
--- /dev/null
+++ b/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.starpak
Binary files differ
diff --git a/Northstar.Custom/paks/rpak.json b/Northstar.Custom/paks/rpak.json
new file mode 100644
index 00000000..522c558b
--- /dev/null
+++ b/Northstar.Custom/paks/rpak.json
@@ -0,0 +1,10 @@
+{
+ "Postload": {
+ "mp_weapon_shotgun_doublebarrel.rpak": "common.rpak",
+ "leaves.rpak": "common.rpak",
+ "tree_stump.rpak": "common.rpak",
+ "bt.rpak": "common.rpak",
+ "giftwrap.rpak": "common.rpak",
+ "snow.rpak": "common.rpak"
+ }
+}
diff --git a/Northstar.Custom/paks/snow.rpak b/Northstar.Custom/paks/snow.rpak
new file mode 100644
index 00000000..4756b6c7
--- /dev/null
+++ b/Northstar.Custom/paks/snow.rpak
Binary files differ
diff --git a/Northstar.Custom/paks/snow.starpak b/Northstar.Custom/paks/snow.starpak
new file mode 100644
index 00000000..7f3dbf19
--- /dev/null
+++ b/Northstar.Custom/paks/snow.starpak
Binary files differ
diff --git a/Northstar.Custom/paks/tree_stump.rpak b/Northstar.Custom/paks/tree_stump.rpak
new file mode 100644
index 00000000..3cdf1866
--- /dev/null
+++ b/Northstar.Custom/paks/tree_stump.rpak
Binary files differ
diff --git a/Northstar.Custom/paks/tree_stump.starpak b/Northstar.Custom/paks/tree_stump.starpak
new file mode 100644
index 00000000..b233176e
--- /dev/null
+++ b/Northstar.Custom/paks/tree_stump.starpak
Binary files differ
diff --git a/Northstar.Custom/vpk/client_mp_northstar_common.bsp.pak000_000.vpk b/Northstar.Custom/vpk/client_mp_northstar_common.bsp.pak000_000.vpk
index 036dd8ab..dd8fe7d0 100644
--- a/Northstar.Custom/vpk/client_mp_northstar_common.bsp.pak000_000.vpk
+++ b/Northstar.Custom/vpk/client_mp_northstar_common.bsp.pak000_000.vpk
Binary files differ
diff --git a/Northstar.Custom/vpk/englishclient_mp_northstar_common.bsp.pak000_dir.vpk b/Northstar.Custom/vpk/englishclient_mp_northstar_common.bsp.pak000_dir.vpk
index 4c36af3e..1aff9ac3 100644
--- a/Northstar.Custom/vpk/englishclient_mp_northstar_common.bsp.pak000_dir.vpk
+++ b/Northstar.Custom/vpk/englishclient_mp_northstar_common.bsp.pak000_dir.vpk
Binary files differ
diff --git a/Northstar.CustomServers/keyvalues/playlists_v2.txt b/Northstar.CustomServers/keyvalues/playlists_v2.txt
new file mode 100644
index 00000000..c49fb0e9
--- /dev/null
+++ b/Northstar.CustomServers/keyvalues/playlists_v2.txt
@@ -0,0 +1,13 @@
+playlists
+{
+ Gamemodes
+ {
+ defaults
+ {
+ vars
+ {
+ player_force_respawn 5
+ }
+ }
+ }
+}
diff --git a/Northstar.CustomServers/mod.json b/Northstar.CustomServers/mod.json
index 14e1ff63..fa51f4d4 100644
--- a/Northstar.CustomServers/mod.json
+++ b/Northstar.CustomServers/mod.json
@@ -1,26 +1,17 @@
{
"Name": "Northstar.CustomServers",
"Description": "Attempts to recreate the behaviour of vanilla Titanfall 2 servers, as well as changing some scripts to allow better support for mods",
- "Version": "1.9.0",
+ "Version": "1.19.0",
"LoadPriority": 0,
"ConVars": [
{
- "Name": "ns_lobby_type",
- "DefaultValue": "0"
- },
- {
- "Name": "ns_is_modded_server",
- "DefaultValue": "1",
- "Flags": 8192
- },
- {
"Name": "ns_should_return_to_lobby",
"DefaultValue": "1"
},
{
"Name": "ns_allow_spectators",
"DefaultValue": "0",
- "Flags": 8192
+ "Flags": "REPLICATED"
},
{
"Name": "ns_private_match_last_mode",
@@ -53,15 +44,17 @@
{
"Name": "ns_allow_kill_commands",
"DefaultValue": "0"
+ },
+ {
+ "Name": "ns_progression_enabled",
+ "DefaultValue": "0",
+ "Flags": "ARCHIVE_PLAYERPROFILE"
}
],
"Scripts": [
{
"Path": "_custom_codecallbacks.gnut",
- "RunOn": "SERVER",
- "ServerCallback": {
- "Before": "NSSetupChathooksServer"
- }
+ "RunOn": "SERVER"
},
{
"Path": "_northstar_cheatcommands.nut",
@@ -104,10 +97,7 @@
},
{
"Path": "sh_server_to_client_stringcommands.gnut",
- "RunOn": "CLIENT || SERVER",
- "ClientCallback": {
- "After": "ServerToClientStringCommands_Init"
- }
+ "RunOn": "CLIENT || SERVER"
},
{
"Path": "gamemodes/_gamemode_fra.nut",
@@ -149,6 +139,26 @@
"ServerCallback": {
"Before": "RespawnProtection_Init"
}
+ },
+ {
+ "Path": "/ai/_ai_turret_sentry.gnut",
+ "RunOn": "SERVER && MP",
+ "ServerCallback": {
+ "After": "AiTurretSentry_Init"
+ }
+ },
+ {
+ "Path": "sh_progression.nut",
+ "RunOn": "UI || SERVER || CLIENT",
+ "ServerCallback": {
+ "Before": "Progression_Init"
+ },
+ "ClientCallback": {
+ "Before": "Progression_Init"
+ },
+ "UICallback": {
+ "Before": "Progression_Init"
+ }
}
]
}
diff --git a/Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg b/Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg
index d92ca3ec..bd422783 100644
--- a/Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg
+++ b/Northstar.CustomServers/mod/cfg/autoexec_ns_server.cfg
@@ -6,7 +6,6 @@ ns_report_sp_server_to_masterserver 0 // whether this server should report itsel
ns_auth_allow_insecure 0 // keep this to 0 unless you want to allow people to join without masterserver auth/persistence
ns_erase_auth_info 1 // keep this to 1 unless you're testing and crashing alot, so you don't have to go through the northstar lobby to reauth
-ns_player_auth_port 8081 // this can be whatever, make sure it's portforwarded over tcp
ns_masterserver_hostname "https://northstar.tf" // masterserver hostname
everything_unlocked 1 // unlock everything
@@ -22,4 +21,7 @@ sv_updaterate_mp 20 // default updaterate: 20 tick
sv_minupdaterate 20 // unsure if this actually works, but if it does, should set minimum client updaterate
sv_max_snapshots_multiplayer 300 // this needs to be updaterate * 15, or clients will dc in killreplay
net_data_block_enabled 0 // not really sure on this, have heard datablock could have security issues? doesn't seem to have any adverse effects being disabled
-host_skip_client_dll_crc 1 // allow people to run modded client dlls, this is mainly so people running pilot visor colour mods can keep those, since they use a client.dll edit \ No newline at end of file
+host_skip_client_dll_crc 1 // allow people to run modded client dlls, this is mainly so people running pilot visor colour mods can keep those, since they use a client.dll edit
+
+announcementVersion 1
+announcement #PROGRESSION_ANNOUNCEMENT_BODY \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/maps/mp_complex3_script.ent b/Northstar.CustomServers/mod/maps/mp_complex3_script.ent
new file mode 100644
index 00000000..df26fd80
--- /dev/null
+++ b/Northstar.CustomServers/mod/maps/mp_complex3_script.ent
@@ -0,0 +1,13234 @@
+ENTITIES01
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "0 -12.363 0"
+"origin" "-3966.17 -2563 748"
+"targetname" "info_player_start_7"
+"classname" "info_player_start"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_at" "0"
+"origin" "-6248 -1288 520"
+"link_to_guid_0" "3148974f"
+"link_guid" "e2596db6"
+"hardpointGroup" "p"
+"gamemode_tday" "1"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"classname" "info_hardpoint"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_at" "0"
+"origin" "-3688 -2288 720"
+"link_to_guid_0" "5bdcb1bf"
+"link_guid" "9ef44891"
+"hardpointGroup" "p"
+"gamemode_tday" "1"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"classname" "info_hardpoint"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_at" "0"
+"origin" "-896 -2880 520"
+"link_to_guid_0" "a6716741"
+"link_guid" "f137536f"
+"hardpointGroup" "p"
+"gamemode_tday" "1"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"classname" "info_hardpoint"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_at" "0"
+"origin" "-3624 -4216 640"
+"link_to_guid_0" "c9c2695"
+"link_guid" "24221b55"
+"hardpointGroup" "p"
+"gamemode_tday" "1"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"classname" "info_hardpoint"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_at" "0"
+"origin" "-3752 -96 560"
+"link_to_guid_0" "17679895"
+"link_guid" "bf771060"
+"hardpointGroup" "p"
+"gamemode_tday" "1"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"classname" "info_hardpoint"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 69.653 0"
+"origin" "-8484.94 -2690.44 603.359"
+"targetname" "prop_static_83"
+"spawnflags" "4"
+"solid" "6"
+"model" "models/containers/gas_tank.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 74.349 0"
+"origin" "-8474.32 -2693.28 603.359"
+"targetname" "prop_static_83"
+"spawnflags" "4"
+"solid" "6"
+"model" "models/containers/gas_tank.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -15 0"
+"origin" "-8442.56 -2694.55 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 35.353 0"
+"origin" "-8428.26 -2695.27 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 140.285 0"
+"origin" "-8435.99 -2693.2 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 162.106 0"
+"origin" "-8448.62 -2693.96 585.359"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -95.139 0"
+"origin" "-8445.15 -2704.21 585.359"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -15 0"
+"origin" "-8432.59 -2707.57 585.359"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -13.747 0"
+"origin" "-8477.69 -2690.31 651.359"
+"targetname" "func_model_225"
+"spawnflags" "4"
+"model" "models/containers/styrofoam_box_short_dirty.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -15 0"
+"origin" "-8472.39 -2693.8 585.359"
+"targetname" "func_model_242"
+"model" "models/containers/styrofoam_box_dirty.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -30.172 0"
+"origin" "-8446.78 -2698.6 651.359"
+"targetname" "func_model_225"
+"spawnflags" "4"
+"model" "models/containers/styrofoam_box_short_dirty.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -15 0"
+"origin" "-8461.88 -2689.37 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 35.353 0"
+"origin" "-8447.58 -2690.1 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 140.285 0"
+"origin" "-8455.31 -2688.02 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -15 0"
+"origin" "-8485.06 -2683.16 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 35.353 0"
+"origin" "-8470.76 -2683.88 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 140.285 0"
+"origin" "-8478.49 -2681.81 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -15 0"
+"origin" "-8434.97 -2704.86 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -15 0"
+"origin" "-8465.88 -2696.58 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 140.285 0"
+"origin" "-8455.45 -2696.27 627.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 35.353 0"
+"origin" "-8472.83 -2691.61 627.359"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -13.747 0"
+"origin" "-8439.05 -2700.67 603.359"
+"targetname" "func_model_225"
+"spawnflags" "4"
+"model" "models/containers/styrofoam_box_short_dirty.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "-2.2039e-006 140.285 1.12504e-006"
+"origin" "-8454.93 -2694.34 603.639"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "2.4724e-006 -15 0"
+"origin" "-8452.22 -2691.96 603.639"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 89.4372 0"
+"origin" "-8463.43 -2695.17 603.359"
+"targetname" "prop_static_83"
+"spawnflags" "4"
+"solid" "6"
+"model" "models/containers/gas_tank.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 119.849 0"
+"origin" "-8397.44 -2748.86 585.86"
+"targetname" "func_model_287"
+"model" "models/containers/crate_blue_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -60.1509 0"
+"origin" "-8380.52 -2778.35 585.86"
+"targetname" "func_model_287"
+"model" "models/containers/crate_blue_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 116.955 0"
+"origin" "-8384.52 -2763.35 628.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -151.236 0"
+"origin" "-8370.71 -2785.4 628.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -151.236 0"
+"origin" "-8384.12 -2778.11 628.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 120.523 0"
+"origin" "-8397.78 -2735.22 652.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 121.152 0"
+"origin" "-8408.19 -2741.19 652.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 121.152 0"
+"origin" "-8401.72 -2752.46 652.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 120.523 0"
+"origin" "-8391.31 -2746.49 652.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 120.523 0"
+"origin" "-8384.34 -2758.63 652.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 94.0521 0"
+"origin" "-8381.52 -2776.61 652.36"
+"targetname" "func_model_224"
+"spawnflags" "4"
+"model" "models/hardware/paint_container_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -19.5849 0"
+"origin" "-8398.65 -2766.85 580.86"
+"targetname" "func_model_297"
+"model" "models/containers/styrofoam_lid_dirty.mdl"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 119.849 0"
+"origin" "-8382.2 -2776.43 604.36"
+"targetname" "prop_static_3"
+"spawnflags" "4"
+"solid" "6"
+"model" "models/containers/box_mini_plastic.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"forcetoenablemotion" "10"
+"fadedist" "1200"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "-0.6507 -21.8879 -90"
+"origin" "-8346.46 -2848.61 607.86"
+"targetname" "prop_static_83"
+"spawnflags" "4"
+"solid" "6"
+"model" "models/containers/gas_tank.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -150.802 0"
+"origin" "-8357.11 -2814.72 604.36"
+"targetname" "prop_static_83"
+"spawnflags" "4"
+"solid" "6"
+"model" "models/containers/gas_tank.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -155.498 0"
+"origin" "-8351.13 -2825.12 604.36"
+"targetname" "prop_static_83"
+"spawnflags" "4"
+"solid" "6"
+"model" "models/containers/gas_tank.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 31.4211 0"
+"origin" "-8336.32 -2856.97 604.36"
+"targetname" "prop_static_83"
+"spawnflags" "4"
+"solid" "6"
+"model" "models/containers/gas_tank.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "0.8"
+"angles" "0 119.849 180"
+"origin" "-8357.91 -2816.33 662.235"
+"targetname" "func_model_287"
+"model" "models/containers/crate_blue_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 122.844 0"
+"origin" "-8359.79 -2822.08 627.36"
+"targetname" "func_model_225"
+"spawnflags" "4"
+"model" "models/containers/styrofoam_box_short_dirty.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 130.405 0"
+"origin" "-8357.34 -2822.34 586.36"
+"targetname" "prop_static_3"
+"solid" "6"
+"model" "models/hardware/paint_container_metal.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -83.9714 0"
+"origin" "-8336.39 -2847.8 586.36"
+"targetname" "prop_static_3"
+"solid" "6"
+"model" "models/hardware/paint_container_metal.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -150.151 0"
+"origin" "-8361.71 -2808.71 586.36"
+"targetname" "prop_static_3"
+"solid" "6"
+"model" "models/hardware/paint_container_metal.mdl"
+"luxelsize" "16"
+"lightingMethod" "0"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -69.2394 0"
+"origin" "-8342.32 -2848.36 627.36"
+"targetname" "func_model_225"
+"spawnflags" "4"
+"model" "models/containers/styrofoam_box_short_dirty.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 28.2831 0"
+"origin" "-8322.86 -2875.99 586.36"
+"targetname" "func_model_895"
+"spawnflags" "4"
+"model" "models/containers/crate_orange_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -61.7169 0"
+"origin" "-8313.16 -2888.88 586.36"
+"targetname" "func_model_895"
+"spawnflags" "4"
+"model" "models/containers/crate_orange_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -151.717 0"
+"origin" "-8304.44 -2908.08 586.36"
+"targetname" "func_model_895"
+"spawnflags" "4"
+"model" "models/containers/crate_orange_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -137.524 0"
+"origin" "-8296.97 -2921.09 586.36"
+"targetname" "func_model_895"
+"spawnflags" "4"
+"model" "models/containers/crate_orange_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 31.9871 0"
+"origin" "-8321.86 -2877.73 604.36"
+"targetname" "func_model_895"
+"spawnflags" "4"
+"model" "models/containers/crate_orange_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 27.1071 0"
+"origin" "-8314.89 -2889.87 604.36"
+"targetname" "func_model_895"
+"spawnflags" "4"
+"model" "models/containers/crate_orange_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -80.9149 0"
+"origin" "-8303.94 -2908.95 604.36"
+"targetname" "func_model_895"
+"spawnflags" "4"
+"model" "models/containers/crate_orange_plastic.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 114.489 0"
+"origin" "-8317.92 -2884.58 652.36"
+"targetname" "func_model_225"
+"spawnflags" "4"
+"model" "models/containers/styrofoam_box_short_dirty.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -59.5809 0"
+"origin" "-8297.61 -2917.99 628.36"
+"targetname" "func_model_225"
+"spawnflags" "4"
+"model" "models/containers/styrofoam_box_short_dirty.mdl"
+"forcetoenablemotion" "10"
+"fadedist" "1500"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 20.8021 0"
+"origin" "-8300.88 -2922.38 604.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -82.5729 0"
+"origin" "-8294.08 -2926.26 604.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -99.5309 0"
+"origin" "-8315.2 -2901.47 604.36"
+"targetname" "func_model_240"
+"spawnflags" "4"
+"model" "models/hardware/paint_spraycan_closed.mdl"
+"forcetoenablemotion" "5"
+"fadedist" "1000"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 119.849 0"
+"origin" "-8302.32 -2913.79 652.36"
+"targetname" "func_model_218"
+"model" "models/containers/styrofoam_box_long_dirty.mdl"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "4.26799e-006 -145.253 6.43766e-008"
+"origin" "-4448.12 -4514.19 666.181"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "4.26799e-006 64.3103 6.43766e-008"
+"origin" "-6668.7 -636.537 545.181"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "-2.53273e-006 -107.57 -1.67088e-006"
+"origin" "-6664.16 -937.235 545.181"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "4.26799e-006 32.1242 6.43766e-008"
+"origin" "-5892.35 775.76 872.583"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "4.26799e-006 32.1242 6.43766e-008"
+"origin" "-5615.16 -881.698 600.583"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 32.8045 2.59711e-006"
+"origin" "-8307.97 -1570.87 872.582"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 149.842 0"
+"origin" "-8655.45 -2144.56 586"
+"targetname" "func_static_1"
+"physicsmode" "1"
+"model" "models/containers/barrel.mdl"
+"forcetoenablemotion" "0"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -135 0"
+"origin" "-8631.4 -2171.43 586"
+"targetname" "func_static_1"
+"physicsmode" "1"
+"model" "models/containers/barrel.mdl"
+"forcetoenablemotion" "0"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -78.0148 0"
+"origin" "-8600.29 -2114.86 586"
+"targetname" "func_static_1"
+"physicsmode" "1"
+"model" "models/containers/barrel.mdl"
+"forcetoenablemotion" "0"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -135 0"
+"origin" "-8638.48 -2161.53 656"
+"targetname" "func_static_1"
+"solid" "6"
+"model" "models/containers/box_large_plastic.mdl"
+"forcetoenablemotion" "1"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -135 0"
+"origin" "-8596.76 -2119.81 656"
+"targetname" "func_static_1"
+"solid" "6"
+"model" "models/containers/box_large_plastic.mdl"
+"forcetoenablemotion" "1"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -45 0"
+"origin" "-8516.15 -2300.83 586"
+"targetname" "func_static_1"
+"solid" "6"
+"model" "models/containers/box_large_plastic.mdl"
+"forcetoenablemotion" "1"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -45 0"
+"origin" "-8570.59 -2246.38 586"
+"targetname" "func_static_1"
+"solid" "6"
+"model" "models/containers/box_large_plastic.mdl"
+"forcetoenablemotion" "1"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "0 -45 0"
+"origin" "-8594.63 -2222.34 586"
+"targetname" "func_static_1"
+"solid" "6"
+"model" "models/containers/box_large_plastic.mdl"
+"forcetoenablemotion" "1"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "1.40406e-006 59.3433 -2.16305e-006"
+"origin" "-7348.92 -477.003 596.582"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "0.98919"
+"angles" "89.9558 0 -36.2377"
+"origin" "-7993.88 -1240.76 576.988"
+"targetname" "prop_physics_1"
+"spawnflags" "4"
+"model" "models/containers/can_red_soda.mdl"
+"collide_titan" "0"
+"collide_sight" "0"
+"collide_human" "0"
+"collide_bullet" "0"
+"collide_ai" "0"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "0.98919"
+"angles" "89.9558 0 -126.238"
+"origin" "-7996 -1194.28 577.489"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "0.876748"
+"angles" "89.9558 0 -134.246"
+"origin" "-7463.83 -1274.32 588.877"
+"targetname" "prop_physics_1"
+"spawnflags" "4"
+"model" "models/containers/can_red_soda.mdl"
+"collide_titan" "0"
+"collide_sight" "0"
+"collide_human" "0"
+"collide_bullet" "0"
+"collide_ai" "0"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "0.876748"
+"angles" "89.9558 0 135.754"
+"origin" "-7504.36 -1281.92 589.321"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1.19801"
+"angles" "89.9558 0 -128.72"
+"origin" "-6799.69 -346.635 581.197"
+"targetname" "prop_physics_1"
+"spawnflags" "4"
+"model" "models/containers/can_red_soda.mdl"
+"collide_titan" "0"
+"collide_sight" "0"
+"collide_human" "0"
+"collide_bullet" "0"
+"collide_ai" "0"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1.19801"
+"angles" "89.9558 0 141.28"
+"origin" "-6855.82 -351.639 581.804"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "0.892666"
+"angles" "89.9558 0 94.2158"
+"origin" "-7214.96 -435.646 576.893"
+"targetname" "prop_physics_1"
+"spawnflags" "4"
+"model" "models/containers/can_red_soda.mdl"
+"collide_titan" "0"
+"collide_sight" "0"
+"collide_human" "0"
+"collide_bullet" "0"
+"collide_ai" "0"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "0.892666"
+"angles" "89.9558 0 4.2156"
+"origin" "-7181.8 -461.404 577.346"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "89.9558 0 -70.039"
+"origin" "-6909.78 -873.816 524.998"
+"targetname" "prop_physics_1"
+"spawnflags" "4"
+"model" "models/containers/can_red_soda.mdl"
+"collide_titan" "0"
+"collide_sight" "0"
+"collide_human" "0"
+"collide_bullet" "0"
+"collide_ai" "0"
+"classname" "prop_physics"
+}
+{
+"spawnflags" "1"
+"skin" "0"
+"shadowcastdist" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"pressuredelay" "0"
+"physicsmode" "0"
+"physdamagescale" "0.1"
+"PerformanceMode" "0"
+"nodamageforces" "0"
+"minhealthdmg" "0"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"massScale" "0"
+"inertiaScale" "1.0"
+"forcetoenablemotion" "0"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"Damagetype" "0"
+"damagetoenablemotion" "0"
+"allowfunnel" "1"
+"scale" "1"
+"angles" "89.9558 0 -160.039"
+"origin" "-6937.7 -835.964 525.505"
+"targetname" "func_static_1428"
+"model" "models/containers/can_blue_soda.mdl"
+"classname" "prop_physics"
+}
+{
+"editorclass" "trigger_fw_territory"
+"origin" "-6879.19 -581.853 796"
+"wait" "0"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "1"
+"StartDisabled" "0"
+"spawnflags" "64"
+"triggerFilterUseNew" "1"
+"link_guid" "25060c9d8d9"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 -0 -0 -223.44238"
+"*trigger_brush_0_plane_1" "1 0 0 2033.6353"
+"*trigger_brush_0_plane_2" "0 -1 0 -114.55118"
+"*trigger_brush_0_plane_3" "0 1 0 2275.4695"
+"*trigger_brush_0_plane_4" "0 0 -1 296"
+"*trigger_brush_0_plane_5" "0 0 1 296"
+"*trigger_brush_0_plane_6" "0.70710683 0.70710671 0 2026.9972"
+"*trigger_brush_0_plane_7" "0.99972111 -0.023614977 0 2013.3975"
+"*trigger_brush_0_plane_8" "-0.70710683 -0.70710677 -0 -1506.9976"
+"*trigger_brush_0_plane_9" "-0.70710701 0.70710653 0 1191.0017"
+"*trigger_brush_0_plane_10" "0.70710689 0.70710677 0 2026.9973"
+"*trigger_brush_0_plane_11" "0.99972117 -0.023614977 0 2013.3977"
+"*trigger_brush_0_plane_12" "-0.70710683 -0.70710683 0 -1506.9976"
+"*trigger_brush_0_plane_13" "-0.70710707 0.70710659 0 1191.0018"
+"*trigger_brush_0_plane_14" "0.92833394 0.37174723 0 2197.5474"
+"*trigger_brush_0_plane_15" "0.37174726 -0.92833406 0 643.34802"
+"*trigger_brush_1_plane_0" "-1 -0 -0 -591.1377"
+"*trigger_brush_1_plane_1" "1 0 0 2361.7329"
+"*trigger_brush_1_plane_2" "-0 -1 -0 -889.54016"
+"*trigger_brush_1_plane_3" "0 1 0 2660.1355"
+"*trigger_brush_1_plane_4" "0 0 -1 296"
+"*trigger_brush_1_plane_5" "0 0 1 296"
+"*trigger_brush_1_plane_6" "-0.70710683 -0.70710671 -0 -2026.9973"
+"*trigger_brush_1_plane_7" "0.70710683 -0.70710671 0 768.99762"
+"*trigger_brush_1_plane_8" "0.70710677 0.70710677 0 2570.9971"
+"*trigger_brush_1_plane_9" "-0.70710677 0.70710677 0 1191.0024"
+"*trigger_brush_1_plane_10" "0.70710689 -0.70710671 0 768.99786"
+"*trigger_brush_1_plane_11" "0.70710683 0.70710683 0 2570.9976"
+"*trigger_brush_1_plane_12" "-0.70710665 0.70710689 0 1191.0029"
+"*trigger_brush_2_plane_0" "-1 -0 -0 1043.6934"
+"*trigger_brush_2_plane_1" "1 -0 -0 -446.89502"
+"*trigger_brush_2_plane_2" "0 -1 0 2660.1357"
+"*trigger_brush_2_plane_3" "0 1 0 -2094.4504"
+"*trigger_brush_2_plane_4" "0 0 -1 296"
+"*trigger_brush_2_plane_5" "0 0 1 296"
+"*trigger_brush_2_plane_6" "-0.26079348 -0.96539462 -0 2684.6282"
+"*trigger_brush_2_plane_7" "0.9750666 0.22191244 -0 -1026.0696"
+"*trigger_brush_2_plane_8" "0.35897923 0.93334556 0 -2293.9731"
+"*trigger_brush_2_plane_9" "-0.97132921 0.23773833 0 419.68182"
+"*trigger_brush_2_plane_10" "-0.26079351 -0.96539462 -0 2684.6282"
+"*trigger_brush_2_plane_11" "0.9750666 0.22191243 0 -1026.0696"
+"*trigger_brush_2_plane_12" "-0.97132933 0.23773836 0 419.68188"
+"*trigger_brush_2_plane_13" "-0.86105388 -0.50851375 0 2169.4092"
+"*trigger_brush_2_plane_14" "0.69279945 -0.72113037 -0 1608.696"
+"*trigger_brush_2_plane_15" "0.75594574 0.65463442 0 -1881.3237"
+"*trigger_brush_2_plane_16" "-0.46336862 0.88616568 0 -1418.2866"
+"*trigger_brush_3_plane_0" "-1 0 0 2361.7397"
+"*trigger_brush_3_plane_1" "1 0 0 1920.4985"
+"*trigger_brush_3_plane_2" "0 -1 0 2250.0139"
+"*trigger_brush_3_plane_3" "0 1 0 1907.7739"
+"*trigger_brush_3_plane_4" "0 0 -1 296"
+"*trigger_brush_3_plane_5" "0 0 1 296"
+"*trigger_brush_3_plane_6" "-0.35897923 -0.93334556 -0 2293.9731"
+"*trigger_brush_3_plane_7" "0.70710677 0.70710677 -0 1506.9974"
+"*trigger_brush_3_plane_8" "0.70710677 -0.70710683 0 1208.9976"
+"*trigger_brush_3_plane_9" "-0.70710689 -0.70710665 0 2149.0024"
+"*trigger_brush_3_plane_10" "-0.70710683 0.70710677 0 1191.0022"
+"*trigger_brush_3_plane_11" "0.70710683 0.70710683 0 1506.9976"
+"*trigger_brush_3_plane_12" "0.70710671 -0.70710683 0 1208.9976"
+"*trigger_brush_3_plane_13" "-0.70710695 -0.70710671 0 2149.0027"
+"*trigger_brush_3_plane_14" "-0.70710689 0.70710677 0 1191.0023"
+"*trigger_brush_3_plane_15" "0.2075914 -0.97821563 0 2088.8511"
+"*trigger_bounds_mins" "-2361.7397 -2660.1357 -296"
+"*trigger_bounds_maxs" "2361.7329 2660.1355 296"
+}
+{
+"model" "*1"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-7273.76 -262.241 830"
+"startDisconnected" "0"
+"link_guid" "250c09f3be6"
+"classname" "func_brush"
+}
+{
+"editorclass" "info_fw_team_tower"
+"teamnumber" "3"
+"spawnflags" "0"
+"model" "models/props/generator_coop/generator_coop_medium.mdl"
+"scale" "1"
+"angles" "0 45.1013 0"
+"origin" "-7274.49 -264.029 640"
+"radius" "1"
+"link_to_guid_1" "250c09f3be6"
+"link_to_guid_0" "25060c9d8d9"
+"link_guid" "250f840be54"
+"classname" "info_target"
+}
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "16.3574 55.7916 -1.02973e-015"
+"origin" "-9592.83 -5363.1 1083.69"
+"targetname" "spec_cam2"
+"target" "escape_node2"
+"classname" "info_target"
+}
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "0 32.194 0"
+"origin" "-9424.58 -4026.32 688"
+"targetname" "escape_node2"
+"classname" "info_target"
+}
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-640 1856 704"
+"targetname" "escape_node3"
+"classname" "info_target"
+}
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "0.0015841 -78.187 0.31054"
+"origin" "-1510.63 2845.77 1260.03"
+"targetname" "spec_cam3"
+"target" "escape_node3"
+"classname" "info_target"
+}
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "0 129.018 0"
+"origin" "-1449.94 395.183 680"
+"targetname" "escape_node1"
+"classname" "info_target"
+}
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "0 -50.606 0"
+"origin" "-2105.68 1100.57 1024"
+"targetname" "spec_cam1"
+"target" "escape_node1"
+"classname" "info_target"
+}
+{
+"origin" "-8108 -3596 784"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "nonphaseshift"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "3"
+"triggerFilterUseNew" "1"
+"targetname" "trigger_hardpoint_A1"
+"classname" "trigger_capture_point"
+"*trigger_brush_0_plane_0" "-1 0 0 948"
+"*trigger_brush_0_plane_1" "1 0 0 948"
+"*trigger_brush_0_plane_2" "0 -1 0 764"
+"*trigger_brush_0_plane_3" "0 1 0 764"
+"*trigger_brush_0_plane_4" "-0 0 -1 304"
+"*trigger_brush_0_plane_5" "-0 -0 1 304"
+"*trigger_brush_0_plane_6" "-0.86514002 -0.50153041 -0 757.96295"
+"*trigger_brush_0_plane_7" "0.95562351 -0.29459071 0 817.55389"
+"*trigger_brush_0_plane_8" "0.26159054 0.96517897 0 537.5415"
+"*trigger_brush_0_plane_9" "-0.25997347 -0.96561575 -0 576.54688"
+"*trigger_brush_0_plane_10" "-0.96106929 0.27630743 0 945.35583"
+"*trigger_brush_0_plane_11" "-0.86513996 -0.50153041 0 757.96295"
+"*trigger_brush_0_plane_12" "-0.2599735 -0.96561587 -0 576.54694"
+"*trigger_brush_0_plane_13" "-0.96106935 0.27630743 0 945.3559"
+"*trigger_brush_0_plane_14" "0.48327103 -0.87547076 0 968.48773"
+"*trigger_brush_0_plane_15" "0.87587523 0.48253766 0 975.091"
+"*trigger_brush_0_plane_16" "-0.49087024 0.87123269 0 1040.6466"
+"*trigger_bounds_mins" "-948 -764 -304"
+"*trigger_bounds_maxs" "948 763.99994 304"
+}
+{
+"origin" "-1056 184 832"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "nonphaseshift"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "3"
+"triggerFilterUseNew" "1"
+"targetname" "trigger_hardpoint_C1"
+"classname" "trigger_capture_point"
+"*trigger_brush_0_plane_0" "-1 -0 -0 704"
+"*trigger_brush_0_plane_1" "1 -0 -0 704"
+"*trigger_brush_0_plane_2" "-0 -1 -0 824"
+"*trigger_brush_0_plane_3" "0 1 0 824"
+"*trigger_brush_0_plane_4" "0 0 -1 384"
+"*trigger_brush_0_plane_5" "0 0 1 384"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 1080.4592"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 0 1080.4592"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 1080.4592"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 1080.4592"
+"*trigger_bounds_mins" "-704 -824 -384"
+"*trigger_bounds_maxs" "704 824 384"
+}
+{
+"origin" "-4552 -1864 768"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "nonphaseshift"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "3"
+"triggerFilterUseNew" "1"
+"targetname" "trigger_hardpoint_B1"
+"classname" "trigger_capture_point"
+"*trigger_brush_0_plane_0" "-1 -0 -0 376"
+"*trigger_brush_0_plane_1" "1 0 0 376"
+"*trigger_brush_0_plane_2" "-0 -1 -0 520"
+"*trigger_brush_0_plane_3" "0 1 0 520"
+"*trigger_brush_0_plane_4" "-0 0 -1 96"
+"*trigger_brush_0_plane_5" "-0 -0 1 96"
+"*trigger_brush_0_plane_6" "0 -0.0033803894 -0.99999428 94.241646"
+"*trigger_brush_0_plane_7" "-0.70710677 0.70710677 0 633.56763"
+"*trigger_brush_0_plane_8" "-0.70710677 -0.70710677 -0 633.56763"
+"*trigger_brush_0_plane_9" "0.70710677 -0.70710677 0 633.56763"
+"*trigger_brush_0_plane_10" "0.70710677 0.70710677 0 633.56763"
+"*trigger_bounds_mins" "-376 -520 -96"
+"*trigger_bounds_maxs" "376 520 96"
+}
+{
+"editorclass" "info_bomb_mode_pilot_defuse_point"
+"spawnflags" "0"
+"origin" "-6258.77 -506.916 588"
+"teamnumber" "3"
+"classname" "info_target"
+}
+{
+"editorclass" "info_bomb_mode_pilot_defuse_point"
+"spawnflags" "0"
+"origin" "-16 -2528 528"
+"teamnumber" "2"
+"classname" "info_target"
+}
+{
+"editorclass" "info_bomb_mode_bomb"
+"spawnflags" "0"
+"origin" "-3733.81 -73.9383 553.108"
+"classname" "info_target"
+}
+{
+"editorclass" "info_bomb_mode_bomb"
+"spawnflags" "0"
+"origin" "-4263.48 -4642.26 652.991"
+"classname" "info_target"
+}
+{
+"editorclass" "info_bomb_mode_base"
+"spawnflags" "0"
+"origin" "-759.944 -2094.34 519.999"
+"teamnumber" "2"
+"classname" "info_target"
+}
+{
+"editorclass" "info_bomb_mode_base"
+"spawnflags" "0"
+"origin" "-6937.51 -1231.18 523.996"
+"teamnumber" "3"
+"classname" "info_target"
+}
+{
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-11520 10880 10536"
+"script_name" "menu_camera_target"
+"classname" "info_target_clientside"
+}
+{
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-11520 11136 10496"
+"script_name" "menu_scene_ref"
+"classname" "info_target_clientside"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"origin" "-6576 -6864 -104"
+"targetname" "at_hardpoint_blackbox"
+"link_to_guid_0" "27528a1afab"
+"link_guid" "2758215f435"
+"hardpointGroup" "p"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"gamemode_at" "1"
+"classname" "info_hardpoint"
+}
+{
+"origin" "-6576 -6864 -40"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"triggerFilterUseNew" "1"
+"link_guid" "27528a1afab"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 128"
+"*trigger_brush_0_plane_1" "1 -0 -0 128"
+"*trigger_brush_0_plane_2" "-0 -1 -0 128"
+"*trigger_brush_0_plane_3" "0 1 0 128"
+"*trigger_brush_0_plane_4" "0 0 -1 128"
+"*trigger_brush_0_plane_5" "-0 -0 1 128"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 181.01933"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 0 181.01933"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 181.01933"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 181.01933"
+"*trigger_bounds_mins" "-128 -128 -128"
+"*trigger_bounds_maxs" "128 128 128"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"origin" "-6256 -6864 -104"
+"targetname" "at_hardpoint_blackbox1"
+"link_to_guid_0" "2759b6c10ad"
+"link_guid" "275ca0c34a7"
+"hardpointGroup" "p"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"gamemode_at" "1"
+"classname" "info_hardpoint"
+}
+{
+"origin" "-6256 -6864 -40"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"triggerFilterUseNew" "1"
+"link_guid" "2759b6c10ad"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 128"
+"*trigger_brush_0_plane_1" "1 -0 -0 128"
+"*trigger_brush_0_plane_2" "-0 -1 -0 128"
+"*trigger_brush_0_plane_3" "0 1 0 128"
+"*trigger_brush_0_plane_4" "0 0 -1 128"
+"*trigger_brush_0_plane_5" "-0 -0 1 128"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 181.01933"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 0 181.01933"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 181.01933"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 181.01933"
+"*trigger_bounds_mins" "-128 -128 -128"
+"*trigger_bounds_maxs" "128 128 128"
+}
+{
+"origin" "-6256 -7184 -40"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"triggerFilterUseNew" "1"
+"link_guid" "275da28a532"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 128"
+"*trigger_brush_0_plane_1" "1 -0 -0 128"
+"*trigger_brush_0_plane_2" "-0 -1 -0 128"
+"*trigger_brush_0_plane_3" "0 1 0 128"
+"*trigger_brush_0_plane_4" "0 0 -1 128"
+"*trigger_brush_0_plane_5" "-0 -0 1 128"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 181.01933"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 0 181.01933"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 181.01933"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 181.01933"
+"*trigger_bounds_mins" "-128 -128 -128"
+"*trigger_bounds_maxs" "128 128 128"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"origin" "-6256 -7184 -104"
+"targetname" "at_hardpoint_blackbox2"
+"link_to_guid_0" "275da28a532"
+"link_guid" "275edb69c4d"
+"hardpointGroup" "p"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"gamemode_at" "1"
+"classname" "info_hardpoint"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"origin" "-6576 -7184 -104"
+"targetname" "at_hardpoint_blackbox3"
+"link_to_guid_0" "275cd46eda5"
+"link_guid" "2751884002b"
+"hardpointGroup" "p"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"gamemode_at" "1"
+"classname" "info_hardpoint"
+}
+{
+"origin" "-6576 -7184 -40"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"triggerFilterUseNew" "1"
+"link_guid" "275cd46eda5"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 128"
+"*trigger_brush_0_plane_1" "1 -0 -0 128"
+"*trigger_brush_0_plane_2" "-0 -1 -0 128"
+"*trigger_brush_0_plane_3" "0 1 0 128"
+"*trigger_brush_0_plane_4" "0 0 -1 128"
+"*trigger_brush_0_plane_5" "-0 -0 1 128"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 181.01933"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 0 181.01933"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 181.01933"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 181.01933"
+"*trigger_bounds_mins" "-128 -128 -128"
+"*trigger_bounds_maxs" "128 128 128"
+}
+{
+"origin" "-5840 -6864 -40"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"triggerFilterUseNew" "1"
+"link_guid" "275e36af485"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 -0 -0 192"
+"*trigger_brush_0_plane_1" "1 -0 0 192"
+"*trigger_brush_0_plane_2" "-0 -1 -0 192"
+"*trigger_brush_0_plane_3" "0 1 0 192"
+"*trigger_brush_0_plane_4" "0 0 -1 128"
+"*trigger_brush_0_plane_5" "-0 -0 1 128"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 271.52899"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 -0 271.52899"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 271.52899"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 271.52899"
+"*trigger_bounds_mins" "-192 -192 -128"
+"*trigger_bounds_maxs" "192 192 128"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"origin" "-5840 -6864 -104"
+"targetname" "at_hardpoint_blackbox4"
+"link_to_guid_0" "275e36af485"
+"link_guid" "275fa2f4f04"
+"hardpointGroup" "s"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"gamemode_at" "1"
+"classname" "info_hardpoint"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"origin" "-5392 -6864 -104"
+"targetname" "at_hardpoint_blackbox5"
+"link_to_guid_0" "275ddde673b"
+"link_guid" "2753df97a78"
+"hardpointGroup" "s"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"gamemode_at" "1"
+"classname" "info_hardpoint"
+}
+{
+"origin" "-5392 -6864 -40"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"triggerFilterUseNew" "1"
+"link_guid" "275ddde673b"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 -0 -0 192"
+"*trigger_brush_0_plane_1" "1 -0 0 192"
+"*trigger_brush_0_plane_2" "-0 -1 -0 192"
+"*trigger_brush_0_plane_3" "0 1 0 192"
+"*trigger_brush_0_plane_4" "0 0 -1 128"
+"*trigger_brush_0_plane_5" "-0 -0 1 128"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 271.52899"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 -0 271.52899"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 271.52899"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 271.52899"
+"*trigger_bounds_mins" "-192 -192 -128"
+"*trigger_bounds_maxs" "192 192 128"
+}
+{
+"origin" "-5392 -7312 -40"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"triggerFilterUseNew" "1"
+"link_guid" "275ac23c51f"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 -0 -0 192"
+"*trigger_brush_0_plane_1" "1 -0 0 192"
+"*trigger_brush_0_plane_2" "-0 -1 -0 192"
+"*trigger_brush_0_plane_3" "0 1 0 192"
+"*trigger_brush_0_plane_4" "0 0 -1 128"
+"*trigger_brush_0_plane_5" "-0 -0 1 128"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 271.52899"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 -0 271.52899"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 271.52899"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 271.52899"
+"*trigger_bounds_mins" "-192 -192 -128"
+"*trigger_bounds_maxs" "192 192 128"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"origin" "-5392 -7312 -104"
+"targetname" "at_hardpoint_blackbox6"
+"link_to_guid_0" "275ac23c51f"
+"link_guid" "275cbcacf82"
+"hardpointGroup" "s"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"gamemode_at" "1"
+"classname" "info_hardpoint"
+}
+{
+"model" "models/communication/terminal_com_station_tall.mdl"
+"hardpointName" "random"
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"origin" "-5840 -7312 -104"
+"targetname" "at_hardpoint_blackbox7"
+"link_to_guid_0" "2759e570221"
+"link_guid" "27590ba1b72"
+"hardpointGroup" "s"
+"gamemode_sur" "0"
+"gamemode_lh" "0"
+"gamemode_cp" "0"
+"gamemode_at" "1"
+"classname" "info_hardpoint"
+}
+{
+"origin" "-5840 -7312 -40"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"triggerFilterUseNew" "1"
+"link_guid" "2759e570221"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 -0 -0 192"
+"*trigger_brush_0_plane_1" "1 -0 0 192"
+"*trigger_brush_0_plane_2" "-0 -1 -0 192"
+"*trigger_brush_0_plane_3" "0 1 0 192"
+"*trigger_brush_0_plane_4" "0 0 -1 128"
+"*trigger_brush_0_plane_5" "-0 -0 1 128"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 271.52899"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 -0 271.52899"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 271.52899"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 271.52899"
+"*trigger_bounds_mins" "-192 -192 -128"
+"*trigger_bounds_maxs" "192 192 128"
+}
+{
+"origin" "-5009.8 -4210.14 671.526"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_1_easy" "1"
+"damagemodel" "0"
+"damagecap" "20"
+"triggerFilterUseNew" "1"
+"mobility_2_normal" "0"
+"damageSourceName" "burn"
+"damage" "10"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 21.77832"
+"*trigger_brush_0_plane_1" "1 0 0 21.771973"
+"*trigger_brush_0_plane_2" "0 -1 0 20.042969"
+"*trigger_brush_0_plane_3" "0 1 0 20.052246"
+"*trigger_brush_0_plane_4" "0.00016665296 6.251569e-005 -1 18.119028"
+"*trigger_brush_0_plane_5" "-0.00016378085 -5.835561e-005 1 18.118982"
+"*trigger_brush_0_plane_6" "0.17627637 -0.98434073 -4.0189021e-005 19.384438"
+"*trigger_brush_0_plane_7" "0.98433661 0.17629911 0.00015777189 20.173597"
+"*trigger_brush_0_plane_8" "-0.17627142 0.98434162 2.4551438e-005 19.05201"
+"*trigger_brush_0_plane_9" "-0.98956627 0.14407825 -0.00013696289 20.649738"
+"*trigger_brush_0_plane_10" "0.82067442 -0.57139605 9.4199539e-005 21.93189"
+"*trigger_brush_0_plane_11" "-0.57138246 -0.8206839 -0.0001399785 17.561274"
+"*trigger_brush_0_plane_12" "-0.82069337 0.57136887 -0.00010190317 20.345854"
+"*trigger_brush_0_plane_13" "0.57139683 0.82067388 0.00014767883 18.677172"
+"*trigger_brush_0_plane_14" "0.17627637 -0.98434073 0 19.385166"
+"*trigger_brush_0_plane_15" "0.17627141 -0.98434162 0 19.383736"
+"*trigger_brush_0_plane_16" "0.98433489 0.17630903 0 20.170713"
+"*trigger_brush_0_plane_17" "0.98433667 0.17629911 -0 20.176456"
+"*trigger_brush_0_plane_18" "-0.17626645 0.98434252 0 19.052406"
+"*trigger_brush_0_plane_19" "-0.17627141 0.98434162 0 19.051565"
+"*trigger_brush_0_plane_20" "-0.98956412 0.14409372 0 20.647114"
+"*trigger_brush_0_plane_21" "-0.98956639 0.14407825 0 20.652224"
+"*trigger_brush_0_plane_22" "0.8206687 -0.57140434 0 21.930241"
+"*trigger_brush_0_plane_23" "0.82067454 -0.57139611 0 21.933601"
+"*trigger_brush_0_plane_24" "-0.57138246 -0.82068402 -0 17.563808"
+"*trigger_brush_0_plane_25" "-0.82070088 0.57135814 0 20.343922"
+"*trigger_brush_0_plane_26" "-0.82069343 0.57136881 0 20.3477"
+"*trigger_brush_0_plane_27" "0.57139134 0.82067776 -0 18.679781"
+"*trigger_brush_0_plane_28" "0.57140237 0.82067001 0 18.674438"
+"*trigger_brush_0_plane_29" "-0.91751391 -0.39770368 -0.00016278407 22.460146"
+"*trigger_bounds_mins" "-21.77832 -20.042969 -18.123047"
+"*trigger_bounds_maxs" "21.771973 20.052345 18.12262"
+}
+{
+"origin" "-4979.47 -4239.79 669.121"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_1_easy" "1"
+"damagemodel" "0"
+"damagecap" "20"
+"triggerFilterUseNew" "1"
+"mobility_2_normal" "0"
+"damageSourceName" "burn"
+"damage" "10"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 22.675781"
+"*trigger_brush_0_plane_1" "1 0 0 22.670898"
+"*trigger_brush_0_plane_2" "0 -1 0 21.022461"
+"*trigger_brush_0_plane_3" "0 1 0 21.017578"
+"*trigger_brush_0_plane_4" "0 0 -1 19.994141"
+"*trigger_brush_0_plane_5" "0 0 1 19.994568"
+"*trigger_brush_0_plane_6" "-0.79023558 0.61255193 -0.017547544 19.368935"
+"*trigger_brush_0_plane_7" "-0.61217505 -0.7877965 0.067958973 19.915857"
+"*trigger_brush_0_plane_8" "0.79024911 -0.61253446 0.017546039 19.839951"
+"*trigger_brush_0_plane_9" "0.83065367 0.55366367 -0.058915287 21.821468"
+"*trigger_brush_0_plane_10" "0.0277991 0.064444266 0.99753404 18.568161"
+"*trigger_brush_0_plane_11" "-0.12590383 0.99019819 -0.060462583 18.096422"
+"*trigger_brush_0_plane_12" "0.9916538 0.12390357 -0.035645492 21.509848"
+"*trigger_brush_0_plane_13" "-0.027795117 -0.064435109 -0.99753475 18.398573"
+"*trigger_brush_0_plane_14" "0.12591469 -0.99019766 0.060449135 18.870047"
+"*trigger_brush_0_plane_15" "-0.99164891 -0.12394232 0.035647802 21.617529"
+"*trigger_brush_0_plane_16" "-0.11031668 0 -0.99389642 20.323952"
+"*trigger_brush_0_plane_17" "0 0.085939594 0.99630034 19.237625"
+"*trigger_brush_0_plane_18" "-0.78962427 0.61359054 0 19.692518"
+"*trigger_brush_0_plane_19" "-0.99961174 0 0.027862126 22.167356"
+"*trigger_brush_0_plane_20" "0 0.028639561 0.99958974 19.452505"
+"*trigger_brush_0_plane_21" "-0.02220404 0 0.99975342 20.130083"
+"*trigger_brush_0_plane_22" "0 -0.99792051 0.064456455 19.89064"
+"*trigger_brush_0_plane_23" "-0.61264962 -0.79035473 0 21.120331"
+"*trigger_brush_0_plane_24" "0 -0.085925035 -0.99630165 19.084589"
+"*trigger_brush_0_plane_25" "0.78964102 -0.61356914 0 20.160496"
+"*trigger_brush_0_plane_26" "0.11033292 0 0.9938947 20.542135"
+"*trigger_brush_0_plane_27" "0.99961174 0 -0.027862221 22.156351"
+"*trigger_brush_0_plane_28" "0 -0.045882083 -0.99894691 19.118061"
+"*trigger_brush_0_plane_29" "0.068414442 0 -0.99765694 20.797762"
+"*trigger_brush_0_plane_30" "0.83084786 0.5564996 0 22.878237"
+"*trigger_brush_0_plane_31" "0 0.99791962 -0.064470463 19.885517"
+"*trigger_brush_0_plane_32" "-0 0.060945805 0.99814111 19.166637"
+"*trigger_brush_0_plane_33" "-0.1239908 0.99228328 0 19.186661"
+"*trigger_brush_0_plane_34" "0.99201429 0.12612571 -0 22.159206"
+"*trigger_brush_0_plane_35" "0.035918012 -0 0.99935478 19.754478"
+"*trigger_brush_0_plane_36" "-0.035910171 0 -0.99935496 19.534838"
+"*trigger_brush_0_plane_37" "0 -0.060938403 -0.99814153 18.994009"
+"*trigger_brush_0_plane_38" "-0.99200934 -0.12616478 -0 22.260799"
+"*trigger_brush_0_plane_39" "0.12399895 -0.99228233 0 19.948387"
+"*trigger_brush_0_plane_40" "0.41424116 0.90745842 -0.070168488 23.463116"
+"*trigger_bounds_mins" "-22.675793 -21.022488 -19.994152"
+"*trigger_bounds_maxs" "22.67091 21.017591 19.994577"
+}
+{
+"origin" "-6630.51 -3874.89 545.789"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_1_easy" "1"
+"damagemodel" "0"
+"damagecap" "20"
+"triggerFilterUseNew" "1"
+"mobility_2_normal" "0"
+"damageSourceName" "burn"
+"damage" "10"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 23.161621"
+"*trigger_brush_0_plane_1" "1 0 0 23.152832"
+"*trigger_brush_0_plane_2" "0 -1 0 18.999268"
+"*trigger_brush_0_plane_3" "0 1 0 18.994873"
+"*trigger_brush_0_plane_4" "0 0 -1 20.728516"
+"*trigger_brush_0_plane_5" "0 0 1 20.728699"
+"*trigger_brush_0_plane_6" "0.072347663 -0.99576795 -0.056674022 18.008764"
+"*trigger_brush_0_plane_7" "0.97601706 0.082389019 -0.20150122 19.375139"
+"*trigger_brush_0_plane_8" "-0.07234402 0.99576789 0.05668062 17.779463"
+"*trigger_brush_0_plane_9" "-0.94880831 0.23674069 0.20908532 18.678394"
+"*trigger_brush_0_plane_10" "0.20531863 -0.040751129 0.97784638 17.118582"
+"*trigger_brush_0_plane_11" "-0.63899124 -0.76236641 0.10240906 15.904586"
+"*trigger_brush_0_plane_12" "-0.74129868 0.64586955 0.1825617 18.554268"
+"*trigger_brush_0_plane_13" "-0.20530531 0.040738355 -0.97784972 16.622938"
+"*trigger_brush_0_plane_14" "0.63898653 0.76237053 -0.10240788 17.836845"
+"*trigger_brush_0_plane_15" "0.74129188 -0.6458807 -0.18254977 20.810663"
+"*trigger_brush_0_plane_16" "-0.20217523 -0 -0.97934932 17.345173"
+"*trigger_brush_0_plane_17" "0 -0.99913335 -0.041623548 18.290937"
+"*trigger_brush_0_plane_18" "0.084106304 -0.99645674 0 18.969072"
+"*trigger_brush_0_plane_19" "0.97865915 0 -0.20549038 20.138351"
+"*trigger_brush_0_plane_20" "0.99737042 0.072472855 0 22.431414"
+"*trigger_brush_0_plane_21" "0 0.05682518 -0.99838412 20.255196"
+"*trigger_brush_0_plane_22" "-0.084103674 0.99645692 0 18.711605"
+"*trigger_brush_0_plane_23" "0 0.99913311 0.041630547 18.28121"
+"*trigger_brush_0_plane_24" "0.20219001 -0 0.97934633 17.831245"
+"*trigger_brush_0_plane_25" "-0.97865975 0 0.20548709 20.146931"
+"*trigger_brush_0_plane_26" "-0.9707675 0.24002215 0 21.741335"
+"*trigger_brush_0_plane_27" "0 -0.27201349 0.96229345 21.163248"
+"*trigger_brush_0_plane_28" "-0 0.13312577 0.99109912 21.450081"
+"*trigger_brush_0_plane_29" "0.15823317 0 0.98740178 18.252949"
+"*trigger_brush_0_plane_30" "0.23913115 0 0.97098726 18.046215"
+"*trigger_brush_0_plane_31" "0.65689623 0.75398088 -0 19.52286"
+"*trigger_brush_0_plane_32" "0.7663759 -0.6423924 0 23.598701"
+"*trigger_brush_0_plane_33" "-0.23910885 -0 -0.97099274 17.447922"
+"*trigger_brush_0_plane_34" "-0.65690094 -0.75397676 0 17.549532"
+"*trigger_brush_0_plane_35" "-0.76638836 0.64237756 0 21.289915"
+"*trigger_brush_0_plane_36" "-0 -0.13313271 -0.99109828 21.574442"
+"*trigger_brush_0_plane_37" "-0.15824135 0 -0.98740047 17.899864"
+"*trigger_brush_0_plane_38" "0 0.27198976 -0.96230012 21.282295"
+"*trigger_brush_0_plane_39" "-0.93328655 -0.30895549 0.18309207 20.327454"
+"*trigger_bounds_mins" "-23.161669 -18.999268 -20.728502"
+"*trigger_bounds_maxs" "23.152775 18.994848 20.728708"
+}
+{
+"origin" "-6752.38 -3850.29 591.335"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_1_easy" "1"
+"damagemodel" "0"
+"damagecap" "20"
+"triggerFilterUseNew" "1"
+"mobility_2_normal" "0"
+"damageSourceName" "burn"
+"damage" "10"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 29.717285"
+"*trigger_brush_0_plane_1" "1 0 0 29.714844"
+"*trigger_brush_0_plane_2" "0 -1 0 27.994873"
+"*trigger_brush_0_plane_3" "0 1 0 27.990967"
+"*trigger_brush_0_plane_4" "0 0 -1 28.347107"
+"*trigger_brush_0_plane_5" "0 0 1 28.34668"
+"*trigger_brush_0_plane_6" "-0.35651442 0.88672775 0.29429817 22.655819"
+"*trigger_brush_0_plane_7" "-0.90868795 -0.40231937 0.11146893 22.658901"
+"*trigger_brush_0_plane_8" "0.35651633 -0.88672686 -0.29429838 22.658344"
+"*trigger_brush_0_plane_9" "0.97479039 0.10128918 -0.19880684 25.473263"
+"*trigger_brush_0_plane_10" "0.21724674 -0.22769055 0.9491896 21.361078"
+"*trigger_brush_0_plane_11" "0.39044589 0.91150326 0.129282 21.358908"
+"*trigger_brush_0_plane_12" "0.89463139 -0.34251106 -0.28691611 24.921045"
+"*trigger_brush_0_plane_13" "-0.21725486 0.22769006 -0.94918787 21.361015"
+"*trigger_brush_0_plane_14" "-0.3904449 -0.9115029 -0.1292876 21.363583"
+"*trigger_brush_0_plane_15" "-0.89462113 0.3425425 0.2869105 24.921858"
+"*trigger_brush_0_plane_16" "-0.40486702 0.9143756 0 27.965538"
+"*trigger_brush_0_plane_17" "0 0.97241479 0.23325837 23.848343"
+"*trigger_brush_0_plane_18" "0.12175279 -0 0.99256045 26.3246"
+"*trigger_brush_0_plane_19" "-0.92782265 -0.37302157 0 24.995735"
+"*trigger_brush_0_plane_20" "-0.97479403 0 0.22310717 25.834534"
+"*trigger_brush_0_plane_21" "0 -0.31499946 0.94909179 26.044333"
+"*trigger_brush_0_plane_22" "-0.12176256 -0 -0.99255919 26.325142"
+"*trigger_brush_0_plane_23" "0 -0.97241485 -0.23325837 23.8522"
+"*trigger_brush_0_plane_24" "0.40485916 -0.91437912 0 27.967974"
+"*trigger_brush_0_plane_25" "0.97479415 0 -0.22310589 25.83219"
+"*trigger_brush_0_plane_26" "0.99862283 0.052462563 0 29.311333"
+"*trigger_brush_0_plane_27" "0 0.2442729 -0.96970654 26.390877"
+"*trigger_brush_0_plane_28" "-0.35755318 -0.93389279 0 24.051094"
+"*trigger_brush_0_plane_29" "0 -0.14042859 0.99009079 26.638908"
+"*trigger_brush_0_plane_30" "0.30539426 0 0.95222604 25.900644"
+"*trigger_brush_0_plane_31" "0.91922188 -0.39373985 0 30.03582"
+"*trigger_brush_0_plane_32" "0.35755575 0.93389171 -0 24.04635"
+"*trigger_brush_0_plane_33" "0 0.14043257 -0.99009031 26.638762"
+"*trigger_brush_0_plane_34" "-0.3053968 0 -0.95222515 25.901608"
+"*trigger_brush_0_plane_35" "-0.91921216 0.39376283 0 30.036396"
+"*trigger_brush_0_plane_36" "0.80246222 0.59530187 -0.040865496 27.527145"
+"*trigger_bounds_mins" "-29.717148 -27.994802 -28.347107"
+"*trigger_bounds_maxs" "29.714777 27.990847 28.34668"
+}
+{
+"origin" "-6863.43 -3565.73 649.505"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_1_easy" "1"
+"damagemodel" "0"
+"damagecap" "20"
+"triggerFilterUseNew" "1"
+"mobility_2_normal" "0"
+"damageSourceName" "burn"
+"damage" "10"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 22.392578"
+"*trigger_brush_0_plane_1" "1 0 0 22.38623"
+"*trigger_brush_0_plane_2" "0 -1 0 23.695801"
+"*trigger_brush_0_plane_3" "0 1 0 23.701904"
+"*trigger_brush_0_plane_4" "0 0 -1 22.756958"
+"*trigger_brush_0_plane_5" "0 0 1 22.756592"
+"*trigger_brush_0_plane_6" "-0.79363966 0.59335631 0.13440391 19.363192"
+"*trigger_brush_0_plane_7" "-0.60828322 -0.76986647 -0.19312474 19.924692"
+"*trigger_brush_0_plane_8" "0.79365343 -0.59333885 -0.13439952 19.84547"
+"*trigger_brush_0_plane_9" "0.82803267 0.54273474 0.14071582 21.814674"
+"*trigger_brush_0_plane_10" "-0.011122293 -0.23502477 0.97192574 18.448565"
+"*trigger_brush_0_plane_11" "-0.13108115 0.96393937 0.23160015 18.086199"
+"*trigger_brush_0_plane_12" "0.99130905 0.12482663 0.041529257 21.507544"
+"*trigger_brush_0_plane_13" "0.011131484 0.23503047 -0.97192425 18.517982"
+"*trigger_brush_0_plane_14" "0.13107763 -0.96394128 -0.23159409 18.880201"
+"*trigger_brush_0_plane_15" "-0.99130905 -0.12482665 -0.04152919 21.619696"
+"*trigger_brush_0_plane_16" "-0.78463531 0.61995769 0 21.717289"
+"*trigger_brush_0_plane_17" "-0.3026042 0 0.9531163 24.282679"
+"*trigger_brush_0_plane_18" "0 0.22091037 -0.97529411 18.879345"
+"*trigger_brush_0_plane_19" "-0.16696684 -0 -0.98596251 23.528826"
+"*trigger_brush_0_plane_20" "-0.59878683 -0.80090845 -0 23.1381"
+"*trigger_brush_0_plane_21" "-0 -0.97198671 -0.23503631 19.936106"
+"*trigger_brush_0_plane_22" "0.78464919 -0.61994004 0 22.185463"
+"*trigger_brush_0_plane_23" "0.30263686 0 -0.95310599 24.525101"
+"*trigger_brush_0_plane_24" "0.31883568 -0 0.94781005 25.59812"
+"*trigger_brush_0_plane_25" "-0 0.97198439 0.23504555 19.941891"
+"*trigger_brush_0_plane_26" "0.82108754 0.57080221 -0 24.242928"
+"*trigger_brush_0_plane_27" "-0.99087983 -0.13474874 0 22.387548"
+"*trigger_brush_0_plane_28" "-0.041856475 0 0.99912369 22.207701"
+"*trigger_brush_0_plane_29" "0.12492958 -0.99216563 0 22.642241"
+"*trigger_brush_0_plane_30" "-0.12493286 0.99216515 0 21.886036"
+"*trigger_brush_0_plane_31" "0.99087977 0.13474873 -0 22.278454"
+"*trigger_brush_0_plane_32" "0.04186533 0 -0.99912328 22.463369"
+"*trigger_brush_0_plane_33" "0.40965781 0.88560075 0.21884184 23.453144"
+"*trigger_bounds_mins" "-22.392578 -23.695826 -22.756931"
+"*trigger_bounds_maxs" "22.38623 23.701887 22.756578"
+}
+{
+"origin" "-5083.35 -3813.92 658.865"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_1_easy" "1"
+"damagemodel" "0"
+"damagecap" "20"
+"triggerFilterUseNew" "1"
+"mobility_2_normal" "0"
+"damageSourceName" "burn"
+"damage" "10"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 22.353027"
+"*trigger_brush_0_plane_1" "1 0 0 22.356445"
+"*trigger_brush_0_plane_2" "0 -1 0 20.129639"
+"*trigger_brush_0_plane_3" "0 1 0 20.134277"
+"*trigger_brush_0_plane_4" "0 0 -1 20.002502"
+"*trigger_brush_0_plane_5" "0 0 1 20.002197"
+"*trigger_brush_0_plane_6" "0.092027478 -0.99034268 -0.10369346 18.011898"
+"*trigger_brush_0_plane_7" "0.98560303 0.075766191 0.15114939 19.300398"
+"*trigger_brush_0_plane_8" "-0.092014894 0.99034363 0.10369541 17.776217"
+"*trigger_brush_0_plane_9" "-0.96412814 0.24129748 -0.11060037 18.7479"
+"*trigger_brush_0_plane_10" "-0.14183062 -0.11611505 0.98305714 16.76276"
+"*trigger_brush_0_plane_11" "-0.63186896 -0.75383621 -0.18020141 15.959561"
+"*trigger_brush_0_plane_12" "-0.76200849 0.64669693 -0.03355841 18.604645"
+"*trigger_brush_0_plane_13" "0.14184251 0.11611181 -0.98305583 16.9785"
+"*trigger_brush_0_plane_14" "0.63186169 0.75384116 0.18020649 17.781702"
+"*trigger_brush_0_plane_15" "0.76199979 -0.6467073 0.033555906 20.760326"
+"*trigger_brush_0_plane_16" "0 -0.99309695 -0.11729673 18.357685"
+"*trigger_brush_0_plane_17" "0.07664188 -0.99705875 0 19.670906"
+"*trigger_brush_0_plane_18" "0.15159363 0 -0.9884429 18.960403"
+"*trigger_brush_0_plane_19" "0.9897514 -0 0.14280061 20.011522"
+"*trigger_brush_0_plane_20" "0 -0.10413853 0.9945628 19.340891"
+"*trigger_brush_0_plane_21" "0.99570972 0.092532285 -0 21.656448"
+"*trigger_brush_0_plane_22" "-0.076628812 0.9970597 0 19.459202"
+"*trigger_brush_0_plane_23" "0 0.99309695 0.11729673 18.384413"
+"*trigger_brush_0_plane_24" "-0.15158319 0 0.98844451 18.718756"
+"*trigger_brush_0_plane_25" "-0.98975176 0 -0.14279857 20.243675"
+"*trigger_brush_0_plane_26" "0 0.14999454 -0.9886868 19.526537"
+"*trigger_brush_0_plane_27" "-0.97394067 0.22680272 0 20.504448"
+"*trigger_brush_0_plane_28" "-0.6470865 -0.7624166 0 18.720362"
+"*trigger_brush_0_plane_29" "-0.27426073 0 0.96165532 19.786783"
+"*trigger_brush_0_plane_30" "-0.76640385 0.6423589 0 19.165703"
+"*trigger_brush_0_plane_31" "0 0.051804371 0.99865729 20.25021"
+"*trigger_brush_0_plane_32" "-0.043983713 0 0.9990322 19.274397"
+"*trigger_brush_0_plane_33" "0 -0.23249318 0.97259808 20.278576"
+"*trigger_brush_0_plane_34" "-0 -0.05182166 -0.99865639 20.061886"
+"*trigger_brush_0_plane_35" "0.043999169 0 -0.99903154 19.21018"
+"*trigger_brush_0_plane_36" "0 0.23250006 -0.97259635 20.0965"
+"*trigger_brush_0_plane_37" "0.27426839 0 -0.96165317 20.379969"
+"*trigger_brush_0_plane_38" "0.76639473 -0.64236981 0 21.327457"
+"*trigger_brush_0_plane_39" "0.64707154 0.76242924 0 20.551546"
+"*trigger_brush_0_plane_40" "-0.93809688 -0.30126056 -0.17092778 20.400368"
+"*trigger_bounds_mins" "-22.353048 -20.129639 -20.002504"
+"*trigger_bounds_maxs" "22.356396 20.134279 20.002209"
+}
+{
+"SuppressAnimSounds" "0"
+"StartDisabled" "0"
+"spawnflags" "0"
+"solid" "6"
+"skin" "0"
+"SetBodyGroup" "0"
+"rendermode" "0"
+"renderfx" "0"
+"rendercolor" "255 255 255"
+"renderamt" "255"
+"RandomAnimation" "0"
+"pressuredelay" "0"
+"PerformanceMode" "0"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"mingpulevel" "0"
+"mincpulevel" "0"
+"MinAnimTime" "5"
+"maxgpulevel" "0"
+"maxcpulevel" "0"
+"MaxAnimTime" "10"
+"HoldAnimation" "0"
+"gamemode_tdm" "1"
+"gamemode_sur" "1"
+"gamemode_lts" "1"
+"gamemode_lh" "1"
+"gamemode_fd" "1"
+"gamemode_ctf" "1"
+"gamemode_cp" "1"
+"fadedist" "-1"
+"ExplodeRadius" "0"
+"ExplodeDamage" "0"
+"drawinfastreflection" "0"
+"disableX360" "0"
+"disableshadows" "0"
+"disablereceiveshadows" "0"
+"DisableBoneFollowers" "0"
+"collide_titan" "1"
+"collide_ai" "1"
+"ClientSide" "0"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-784 7760 2304"
+"script_name" "rings_pristine"
+"model" "models/props/timeshift_rings_animated.mdl"
+"classname" "prop_dynamic"
+}
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-1270.75 -1694.72 647.999"
+"targetname" "intermission"
+"classname" "info_target"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -44.724 0"
+"origin" "-5903.3 1066.05 724.634"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -44.724 0"
+"origin" "-5943.3 1026.05 724.634"
+"classname" "info_node_cover_stand"
+}
+{
+"origin" "-1692.96 -828.384 1487"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"triggerFilterUseNew" "1"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamBeast" "1"
+"targetname" "trigger_nessy"
+"classname" "trigger_once"
+"*trigger_brush_0_plane_0" "-1 0 0 22.049438"
+"*trigger_brush_0_plane_1" "1 0 0 22.049194"
+"*trigger_brush_0_plane_2" "0 -1 0 22.049194"
+"*trigger_brush_0_plane_3" "0 1 0 22.049255"
+"*trigger_brush_0_plane_4" "6.4687615e-006 -4.0451205e-006 -1 16"
+"*trigger_brush_0_plane_5" "-6.4687406e-006 4.0451073e-006 1 16"
+"*trigger_brush_0_plane_6" "0.84787196 -0.53020108 8.4913063e-006 15.999903"
+"*trigger_brush_0_plane_7" "-0.53020018 -0.8478725 -8.1072972e-007 16.000036"
+"*trigger_brush_0_plane_8" "-0.84787196 0.53020108 -8.4913063e-006 16.000143"
+"*trigger_brush_0_plane_9" "0.53020018 0.8478725 8.1072972e-007 15.99996"
+"*trigger_brush_0_plane_10" "0.84787196 -0.53020108 0 15.999767"
+"*trigger_brush_0_plane_11" "-0.53020018 -0.8478725 -0 16.00005"
+"*trigger_brush_0_plane_12" "-0.84787196 0.53020108 0 16.000006"
+"*trigger_brush_0_plane_13" "0.53020018 0.8478725 -0 15.99997"
+"*trigger_brush_0_plane_14" "0.22462773 -0.97444457 5.4309844e-006 22.627359"
+"*trigger_brush_0_plane_15" "0.97444463 0.22462772 6.5775362e-006 22.627329"
+"*trigger_brush_0_plane_16" "-0.97444463 -0.22462772 -6.5775362e-006 22.627554"
+"*trigger_brush_0_plane_17" "-0.22462773 0.97444457 -5.4309844e-006 22.627476"
+"*trigger_bounds_mins" "-22.049438 -22.049194 -16.000122"
+"*trigger_bounds_maxs" "22.049194 22.049255 16.000122"
+}
+{
+"origin" "-3680 -4240 1320"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 -0 -0 -256"
+"*trigger_brush_0_plane_1" "1 -0 -0 1504"
+"*trigger_brush_0_plane_2" "-0 -1 -0 1712"
+"*trigger_brush_0_plane_3" "0 1 0 -152"
+"*trigger_brush_0_plane_4" "0 -0 -1 80"
+"*trigger_brush_0_plane_5" "0 0 1 80"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 -288.49957"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 0 1029.5475"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 2274.0552"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 956.0083"
+"*trigger_brush_1_plane_0" "-1 -0 -0 2096"
+"*trigger_brush_1_plane_1" "1 0 0 256"
+"*trigger_brush_1_plane_2" "-0 -1 -0 1712"
+"*trigger_brush_1_plane_3" "0 1 0 -1544"
+"*trigger_brush_1_plane_4" "0 -0 -1 80"
+"*trigger_brush_1_plane_5" "0 0 1 80"
+"*trigger_brush_1_plane_6" "-0.70710677 0.70710677 0 390.323"
+"*trigger_brush_1_plane_7" "-0.70710677 -0.70710677 0 2692.6626"
+"*trigger_brush_1_plane_8" "0.70710677 -0.70710677 0 1391.5861"
+"*trigger_brush_1_plane_9" "0.70710677 0.70710677 0 -910.75348"
+"*trigger_brush_2_plane_0" "-1 0 0 -1504"
+"*trigger_brush_2_plane_1" "1 -0 0 2096"
+"*trigger_brush_2_plane_2" "-0 -1 -0 1712"
+"*trigger_brush_2_plane_3" "-0 1 -0 624"
+"*trigger_brush_2_plane_4" "0 -0 -1 80"
+"*trigger_brush_2_plane_5" "0 0 1 80"
+"*trigger_brush_2_plane_6" "-0.70710677 0.70710677 0 -622.25391"
+"*trigger_brush_2_plane_7" "-0.70710677 -0.70710677 0 147.07825"
+"*trigger_brush_2_plane_8" "0.70710677 -0.70710677 0 2692.6626"
+"*trigger_brush_2_plane_9" "0.70710677 0.70710677 0 1923.3304"
+"*trigger_brush_3_plane_0" "-1 -0 -0 -256"
+"*trigger_brush_3_plane_1" "1 -0 -0 1504"
+"*trigger_brush_3_plane_2" "0 -1 0 20"
+"*trigger_brush_3_plane_3" "0 1 0 1712"
+"*trigger_brush_3_plane_4" "0 -0 -1 80"
+"*trigger_brush_3_plane_5" "0 0 1 80"
+"*trigger_brush_3_plane_6" "-0.70710677 0.70710677 0 1029.5475"
+"*trigger_brush_3_plane_7" "-0.70710677 -0.70710677 0 -166.8772"
+"*trigger_brush_3_plane_8" "0.70710677 -0.70710677 0 1077.6306"
+"*trigger_brush_3_plane_9" "0.70710677 0.70710677 0 2274.0552"
+"*trigger_brush_4_plane_0" "-1 -0 -0 -256"
+"*trigger_brush_4_plane_1" "1 -0 -0 1504"
+"*trigger_brush_4_plane_2" "-0 -1 -0 152"
+"*trigger_brush_4_plane_3" "-0 1 -0 -20"
+"*trigger_brush_4_plane_4" "0 -0 -1 80"
+"*trigger_brush_4_plane_5" "0 0 1 80"
+"*trigger_brush_4_plane_6" "-0.7288481 0.68467546 0 -290.65579"
+"*trigger_brush_4_plane_7" "0.70710677 -0.70710677 -0 1170.9688"
+"*trigger_brush_4_plane_8" "0.70710677 0.70710677 -0 1049.3464"
+"*trigger_brush_4_plane_9" "-0.91778958 -0.39706707 0 -174.59995"
+"*trigger_brush_4_plane_10" "-0.3970671 0.91778958 0 -169.24129"
+"*trigger_bounds_mins" "-2096 -1712 -80"
+"*trigger_bounds_maxs" "2096 1712 80"
+}
+{
+"editorclass" "trigger_mp_spawn_zone"
+"origin" "-8259.93 -3898.29 768"
+"link_to_guid_0" "8d5c25db"
+"link_guid" "20bff3cf"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 1676.9395"
+"*trigger_brush_0_plane_1" "1 0 0 1676.9297"
+"*trigger_brush_0_plane_2" "0 -1 0 1431.5415"
+"*trigger_brush_0_plane_3" "0 1 0 1431.54"
+"*trigger_brush_0_plane_4" "0 0 -1 384"
+"*trigger_brush_0_plane_5" "0 0 1 384"
+"*trigger_brush_0_plane_6" "0.86856514 -0.49557504 0 1230.6445"
+"*trigger_brush_0_plane_7" "0.32101053 0.94707561 -0 969.97974"
+"*trigger_brush_0_plane_8" "-0.97844875 0.20648976 0 1471.5179"
+"*trigger_brush_0_plane_9" "-0.25945646 -0.96575481 -0 1226.8235"
+"*trigger_brush_0_plane_10" "0.8685652 -0.4955751 0 1230.6445"
+"*trigger_brush_0_plane_11" "0.32101053 0.94707572 -0 969.97986"
+"*trigger_brush_0_plane_12" "-0.97844887 0.20648979 0 1471.5182"
+"*trigger_brush_0_plane_13" "0.38473445 -0.92302728 0 1552.2231"
+"*trigger_brush_0_plane_14" "0.93492395 0.35484812 0 1729.538"
+"*trigger_brush_0_plane_15" "-0.49514958 0.86880779 0 1838.814"
+"*trigger_brush_0_plane_16" "-0.85243279 -0.5228368 0 1858.1025"
+"*trigger_bounds_mins" "-1676.9395 -1431.5415 -384"
+"*trigger_bounds_maxs" "1676.9297 1431.54 384"
+}
+{
+"editorclass" "trigger_mp_spawn_zone"
+"origin" "-1096 -1064 836"
+"link_to_guid_0" "7a915f75"
+"link_guid" "376be52c"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 -0 792"
+"*trigger_brush_0_plane_1" "1 0 0 792"
+"*trigger_brush_0_plane_2" "-0 -1 -0 728"
+"*trigger_brush_0_plane_3" "0 1 0 728"
+"*trigger_brush_0_plane_4" "-0 0 -1 316"
+"*trigger_brush_0_plane_5" "0 0 1 316"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 1074.8022"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 -0 1074.8022"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 1074.8022"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 1074.8022"
+"*trigger_bounds_mins" "-792 -728 -316"
+"*trigger_bounds_maxs" "792 728 316"
+}
+{
+"editorclass" "trigger_mp_spawn_zone"
+"origin" "-3344 -32 832"
+"link_guid" "c73b1ba0"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 -528"
+"*trigger_brush_0_plane_1" "1 -0 0 1456"
+"*trigger_brush_0_plane_2" "-0 -1 -0 1120"
+"*trigger_brush_0_plane_3" "0 1 0 96"
+"*trigger_brush_0_plane_4" "-0 0 -1 320"
+"*trigger_brush_0_plane_5" "0 0 1 320"
+"*trigger_brush_0_plane_6" "0.61887223 0.7854917 0 402.17172"
+"*trigger_brush_0_plane_7" "0.61887223 0.78549176 -0 402.17175"
+"*trigger_brush_0_plane_8" "-0.43653622 0.89968669 0 -144.1212"
+"*trigger_brush_0_plane_9" "-0.70710677 -0.70710677 0 418.60721"
+"*trigger_brush_0_plane_10" "0.70710677 -0.70710677 0 1821.5071"
+"*trigger_brush_0_plane_11" "0.89968669 0.43653628 0 1032.6772"
+"*trigger_brush_1_plane_0" "-1 0 -0 1456"
+"*trigger_brush_1_plane_1" "1 -0 -0 528"
+"*trigger_brush_1_plane_2" "-0 -1 -0 1120"
+"*trigger_brush_1_plane_3" "-0 1 0 1120"
+"*trigger_brush_1_plane_4" "-0 0 -1 320"
+"*trigger_brush_1_plane_5" "0 0 1 320"
+"*trigger_brush_1_plane_6" "-0.70710677 0.70710677 0 1323.7039"
+"*trigger_brush_1_plane_7" "-0.70710683 0.70710683 0 1323.7039"
+"*trigger_brush_1_plane_8" "-0.70710677 -0.70710677 -0 1821.5071"
+"*trigger_brush_1_plane_9" "0.70710677 -0.70710677 -0 1165.312"
+"*trigger_brush_1_plane_10" "0.70710677 0.70710677 0 1165.312"
+"*trigger_bounds_mins" "-1456 -1120 -320"
+"*trigger_bounds_maxs" "1456 1120 320"
+}
+{
+"editorclass" "trigger_mp_spawn_zone"
+"origin" "-7140.16 -1396.14 772"
+"link_guid" "e620fcf0"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 2224.2539"
+"*trigger_brush_0_plane_1" "1 0 0 2224.2637"
+"*trigger_brush_0_plane_2" "0 -1 0 2217.8926"
+"*trigger_brush_0_plane_3" "0 1 0 2217.8877"
+"*trigger_brush_0_plane_4" "0 -0 -1 380"
+"*trigger_brush_0_plane_5" "0 0 1 380"
+"*trigger_brush_0_plane_6" "0.72732341 -0.68629485 0 1703.6879"
+"*trigger_brush_0_plane_7" "0.68394107 0.72953725 -0 1429.9221"
+"*trigger_brush_0_plane_8" "-0.70123887 -0.71292639 -0 1406.1461"
+"*trigger_brush_0_plane_9" "-0.71656835 0.69751686 0 1744.0974"
+"*trigger_brush_0_plane_10" "0.72732347 -0.68629491 0 1703.688"
+"*trigger_brush_0_plane_11" "0.68394113 0.72953725 -0 1429.9222"
+"*trigger_brush_0_plane_12" "-0.71656841 0.69751692 0 1744.0975"
+"*trigger_bounds_mins" "-2224.2539 -2217.8926 -380"
+"*trigger_bounds_maxs" "2224.2637 2217.8877 380"
+}
+{
+"editorclass" "trigger_mp_spawn_zone"
+"origin" "-1312 -3120 836"
+"link_to_guid_0" "e620fcf0"
+"link_guid" "8d5c25db"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 -0 -0 1712"
+"*trigger_brush_0_plane_1" "1 0 -0 1712"
+"*trigger_brush_0_plane_2" "-0 -1 -0 1328"
+"*trigger_brush_0_plane_3" "0 1 0 1328"
+"*trigger_brush_0_plane_4" "-0 0 -1 316"
+"*trigger_brush_0_plane_5" "0 0 1 316"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 2149.6045"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 0 2149.6045"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 2149.6045"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 2149.6045"
+"*trigger_bounds_mins" "-1712 -1328 -316"
+"*trigger_bounds_maxs" "1712 1328 316"
+}
+{
+"editorclass" "trigger_mp_spawn_zone"
+"origin" "-5934.88 798.942 772"
+"link_guid" "7a915f75"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 1485.8599"
+"*trigger_brush_0_plane_1" "1 0 0 1485.8623"
+"*trigger_brush_0_plane_2" "0 -1 0 1490.5747"
+"*trigger_brush_0_plane_3" "0 1 0 1490.5756"
+"*trigger_brush_0_plane_4" "0 -0 -1 380"
+"*trigger_brush_0_plane_5" "0 0 1 380"
+"*trigger_brush_0_plane_6" "-0.68394113 -0.72953719 -0 989.97607"
+"*trigger_brush_0_plane_7" "0.70748007 0.70673329 0 1003.9444"
+"*trigger_brush_0_plane_8" "0.72732341 -0.68629485 0 1126.6088"
+"*trigger_brush_0_plane_9" "-0.71656853 0.69751674 0 1089.8322"
+"*trigger_brush_0_plane_10" "-0.68394113 -0.72953725 -0 989.97614"
+"*trigger_brush_0_plane_11" "0.72732347 -0.68629491 0 1126.6089"
+"*trigger_bounds_mins" "-1485.8599 -1490.5747 -380"
+"*trigger_bounds_maxs" "1485.8622 1490.5756 380"
+}
+{
+"editorclass" "trigger_mp_spawn_zone"
+"origin" "-4848 -4232 836"
+"link_to_guid_0" "c73b1ba0"
+"link_guid" "891ea354"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 -0 1824"
+"*trigger_brush_0_plane_1" "1 0 0 1824"
+"*trigger_brush_0_plane_2" "0 -1 0 1240"
+"*trigger_brush_0_plane_3" "0 1 0 1240"
+"*trigger_brush_0_plane_4" "-0 0 -1 316"
+"*trigger_brush_0_plane_5" "0 0 1 316"
+"*trigger_brush_0_plane_6" "0.70710677 0.70710677 -0 1861.105"
+"*trigger_brush_0_plane_7" "0.70710683 0.70710683 0 1861.1052"
+"*trigger_brush_0_plane_8" "-0.70710677 0.70710677 0 2166.5752"
+"*trigger_brush_0_plane_9" "-0.70710677 -0.70710677 0 2166.5752"
+"*trigger_brush_0_plane_10" "0.70710677 -0.70710677 0 2166.5752"
+"*trigger_bounds_mins" "-1824 -1240 -316"
+"*trigger_bounds_maxs" "1824 1240 316"
+}
+{
+"spawnflags" "0"
+"scale" "1"
+"angles" "-5 69 3.06941e-016"
+"origin" "-2120 -2960 -6692"
+"targetname" "spacenode"
+"collide_titan" "0"
+"classname" "info_target"
+}
+{
+"origin" "-1504 160 -516"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"targetname" "trigger_hurt_1"
+"damagecap" "90000"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 13184"
+"*trigger_brush_0_plane_1" "1 0 0 13184"
+"*trigger_brush_0_plane_2" "-0 -1 -0 15648"
+"*trigger_brush_0_plane_3" "0 1 0 15648"
+"*trigger_brush_0_plane_4" "0 0 -1 764"
+"*trigger_brush_0_plane_5" "0 0 1 764"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 20387.303"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 0 20387.303"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 20387.303"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 20387.303"
+"*trigger_bounds_mins" "-13184 -15648 -764"
+"*trigger_bounds_maxs" "13184 15648 764"
+}
+{
+"editorclass" "info_attrition_camp"
+"origin" "-716 728 560"
+"radius" "2000"
+"phase_9" "0"
+"phase_8" "0"
+"phase_7" "0"
+"phase_6" "0"
+"phase_5" "0"
+"phase_4" "0"
+"phase_3" "1"
+"phase_2" "1"
+"phase_1" "0"
+"height" "500"
+"gamemode_fw" "0"
+"classname" "info_target"
+}
+{
+"editorclass" "info_attrition_camp"
+"origin" "-8544 -3920 580.002"
+"radius" "1700"
+"phase_9" "0"
+"phase_8" "0"
+"phase_7" "0"
+"phase_6" "0"
+"phase_5" "0"
+"phase_4" "0"
+"phase_3" "1"
+"phase_2" "1"
+"phase_1" "0"
+"height" "500"
+"gamemode_fw" "0"
+"classname" "info_target"
+}
+{
+"editorclass" "info_attrition_camp"
+"origin" "-3888 -2308 725.73"
+"radius" "1800"
+"phase_9" "0"
+"phase_8" "0"
+"phase_7" "0"
+"phase_6" "0"
+"phase_5" "0"
+"phase_4" "0"
+"phase_3" "1"
+"phase_2" "0"
+"phase_1" "1"
+"height" "500"
+"gamemode_fw" "0"
+"classname" "info_target"
+}
+{
+"editorclass" "info_attrition_bank"
+"radius" "256"
+"model" "models/Robots/mobile_hardpoint/mobile_hardpoint.mdl"
+"height" "256"
+"scale" "1"
+"angles" "-2.44662 -0.1162 -6.31668"
+"origin" "-3728.92 -249.347 570.834"
+"classname" "info_target"
+}
+{
+"editorclass" "info_attrition_bank"
+"radius" "256"
+"model" "models/Robots/mobile_hardpoint/mobile_hardpoint.mdl"
+"height" "256"
+"scale" "1"
+"angles" "-0.0219147 45 -0.000732113"
+"origin" "-4634.46 -4318.01 643.997"
+"classname" "info_target"
+}
+{
+"editorclass" "script_power_up_other"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "3.31448 90.4263 0.0792013"
+"origin" "-3792.41 -260.96 570.401"
+"powerUpType" "mp_loot_titan_build_credit"
+"classname" "script_ref"
+}
+{
+"editorclass" "script_power_up_other"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "-5.19204e-005 90.4217 5.11617e-005"
+"origin" "-4133.64 -4605.58 644.989"
+"powerUpType" "mp_loot_titan_build_credit"
+"classname" "script_ref"
+}
+{
+"editorclass" "script_power_up_other"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0.0002205 90.4217 -0.000274384"
+"origin" "-9398.19 -4025.82 578.673"
+"powerUpType" "mp_loot_titan_build_credit"
+"classname" "script_ref"
+}
+{
+"editorclass" "script_power_up_other"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0.00735007 136.844 -0.00725995"
+"origin" "-1248.99 839.02 752.045"
+"powerUpType" "mp_loot_titan_build_credit"
+"classname" "script_ref"
+}
+{
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"gamemode_sur" "1"
+"gamemode_lh" "1"
+"gamemode_cp" "1"
+"gamemode_at" "0"
+"scale" "1"
+"angles" "0 167.628 0"
+"origin" "-8154.63 -3522 580.361"
+"link_guid" "dfc2c4f3"
+"triggerTarget" "trigger_hardpoint_A1"
+"targetname" "info_hardpoint_1"
+"target" "assault_hardpoint_far_A*"
+"nearTarget" "assault_hardpoint_near_A*"
+"model" "models/Robots/mobile_hardpoint/mobile_hardpoint.mdl"
+"hardpointName" "titangarage"
+"hardpointGroup" "A"
+"classname" "info_hardpoint"
+}
+{
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"gamemode_sur" "1"
+"gamemode_lh" "1"
+"gamemode_cp" "1"
+"gamemode_at" "0"
+"scale" "1"
+"angles" "0 -138.254 0"
+"origin" "-4333.02 -2155.06 695.002"
+"link_guid" "7749b115"
+"triggerTarget" "trigger_hardpoint_B1"
+"targetname" "info_hardpoint_3"
+"target" "assault_hardpoint_far_B*"
+"nearTarget" "assault_hardpoint_near_B*"
+"model" "models/Robots/mobile_hardpoint/mobile_hardpoint.mdl"
+"hardpointName" "dogwhistle"
+"hardpointGroup" "B"
+"classname" "info_hardpoint"
+}
+{
+"hardpointFrontlineYaw" "0"
+"hardpointFrontlineOverride" "0"
+"gamemode_tday" "0"
+"gamemode_sur" "1"
+"gamemode_lh" "1"
+"gamemode_cp" "1"
+"gamemode_at" "0"
+"scale" "1"
+"angles" "0 -114.836 0"
+"origin" "-1390.34 2.709 520.001"
+"link_guid" "2a84a088"
+"triggerTarget" "trigger_hardpoint_C1"
+"targetname" "info_hardpoint_2"
+"target" "assault_hardpoint_far_C*"
+"nearTarget" "assault_hardpoint_near_C*"
+"model" "models/Robots/mobile_hardpoint/mobile_hardpoint.mdl"
+"hardpointName" "market"
+"hardpointGroup" "C"
+"classname" "info_hardpoint"
+}
+{
+"editorclass" "info_bomb_mode_bomb"
+"spawnflags" "0"
+"origin" "-4470.68 -2688.15 724.001"
+"classname" "info_target"
+}
+{
+"editorclass" "script_power_up_other"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0 90 2.53193e-006"
+"origin" "-4612.01 -2544.57 716.025"
+"powerUpType" "mp_loot_titan_build_credit"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_lts_bomb_site"
+"teamnumber" "2"
+"spawnflags" "0"
+"model" "models/props/generator_coop/generator_coop_medium.mdl"
+"origin" "-3798.73 -258.059 587.091"
+"bombSiteLocation" "0"
+"classname" "info_target"
+}
+{
+"editorclass" "info_lts_bomb_site"
+"teamnumber" "2"
+"spawnflags" "0"
+"model" "models/props/generator_coop/generator_coop_medium.mdl"
+"origin" "-2614.51 -3454.96 644"
+"bombSiteLocation" "1"
+"classname" "info_target"
+}
+{
+"origin" "-6248 -1288 616"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"link_guid" "3148974f"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 316.78223"
+"*trigger_brush_0_plane_1" "1 0 0 316.78223"
+"*trigger_brush_0_plane_2" "0 -1 0 316.78247"
+"*trigger_brush_0_plane_3" "0 1 0 316.78241"
+"*trigger_brush_0_plane_4" "-0 0 -1 96"
+"*trigger_brush_0_plane_5" "-0 -0 1 96"
+"*trigger_brush_0_plane_6" "-0.7049914 -0.70921588 -0 224.00005"
+"*trigger_brush_0_plane_7" "-0.70921636 0.70499092 0 223.99983"
+"*trigger_brush_0_plane_8" "0.70499128 0.70921594 0 224"
+"*trigger_brush_0_plane_9" "0.70921642 -0.7049908 0 223.99986"
+"*trigger_brush_0_plane_10" "-0.70921636 0.70499086 0 223.99983"
+"*trigger_brush_0_plane_11" "0.70921648 -0.70499086 0 223.99988"
+"*trigger_bounds_mins" "-316.78223 -316.78247 -96"
+"*trigger_bounds_maxs" "316.78223 316.78241 96"
+}
+{
+"origin" "-3688 -2288 816"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"link_guid" "5bdcb1bf"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 316.78223"
+"*trigger_brush_0_plane_1" "1 0 0 316.78223"
+"*trigger_brush_0_plane_2" "0 -1 0 316.78247"
+"*trigger_brush_0_plane_3" "0 1 0 316.78241"
+"*trigger_brush_0_plane_4" "-0 0 -1 96"
+"*trigger_brush_0_plane_5" "-0 -0 1 96"
+"*trigger_brush_0_plane_6" "-0.7049908 -0.70921642 -0 224.00003"
+"*trigger_brush_0_plane_7" "-0.70921642 0.7049908 0 224.00003"
+"*trigger_brush_0_plane_8" "0.7049908 0.70921642 0 224.00003"
+"*trigger_brush_0_plane_9" "0.70921642 -0.7049908 0 223.99986"
+"*trigger_brush_0_plane_10" "-0.70499086 -0.70921648 -0 224.00005"
+"*trigger_brush_0_plane_11" "-0.70921648 0.70499086 0 224.00005"
+"*trigger_brush_0_plane_12" "0.70499086 0.70921648 -0 224.00005"
+"*trigger_brush_0_plane_13" "0.70921648 -0.70499086 0 223.99988"
+"*trigger_bounds_mins" "-316.78247 -316.78235 -96"
+"*trigger_bounds_maxs" "316.78235 316.78247 96"
+}
+{
+"origin" "-896 -2880 616"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"link_guid" "a6716741"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 225.83789"
+"*trigger_brush_0_plane_1" "1 0 0 225.83783"
+"*trigger_brush_0_plane_2" "0 -1 0 225.83789"
+"*trigger_brush_0_plane_3" "0 1 0 225.83789"
+"*trigger_brush_0_plane_4" "-0 0 -1 96"
+"*trigger_brush_0_plane_5" "-0 -0 1 96"
+"*trigger_brush_0_plane_6" "-0.99996608 -0.0082386546 -0 224.00003"
+"*trigger_brush_0_plane_7" "-0.0082386546 0.99996608 0 224.00005"
+"*trigger_brush_0_plane_8" "0.99996608 0.0082385186 0 224"
+"*trigger_brush_0_plane_9" "0.0082386564 -0.99996608 0 224.00003"
+"*trigger_brush_0_plane_10" "-0.99996614 -0.0082386555 0 224.00005"
+"*trigger_brush_0_plane_11" "-0.0082386555 0.99996614 0 224.00005"
+"*trigger_brush_0_plane_12" "0.99996614 0.0082385195 0 224.00002"
+"*trigger_brush_0_plane_13" "0.0082386564 -0.99996614 0 224.00006"
+"*trigger_brush_0_plane_14" "-0.71290839 0.70125717 0 316.78387"
+"*trigger_brush_0_plane_15" "-0.70125717 -0.71290839 0 316.78387"
+"*trigger_brush_0_plane_16" "0.70125723 0.71290833 0 316.78387"
+"*trigger_brush_0_plane_17" "0.71290827 -0.70125717 0 316.78381"
+"*trigger_bounds_mins" "-225.83789 -225.83789 -96"
+"*trigger_bounds_maxs" "225.83783 225.83789 96"
+}
+{
+"origin" "-3624 -4216 736"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"link_guid" "c9c2695"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 316.78223"
+"*trigger_brush_0_plane_1" "1 0 0 316.78223"
+"*trigger_brush_0_plane_2" "0 -1 0 316.78247"
+"*trigger_brush_0_plane_3" "0 1 0 316.78241"
+"*trigger_brush_0_plane_4" "-0 0 -1 96"
+"*trigger_brush_0_plane_5" "-0 -0 1 96"
+"*trigger_brush_0_plane_6" "-0.70880991 -0.70539957 -0 223.99986"
+"*trigger_brush_0_plane_7" "-0.70539927 0.70881015 0 224.00011"
+"*trigger_brush_0_plane_8" "0.70881015 0.70539927 0 223.99994"
+"*trigger_brush_0_plane_9" "0.70539904 -0.70881045 0 224.00003"
+"*trigger_brush_0_plane_10" "0.70881021 0.70539927 0 223.99997"
+"*trigger_bounds_mins" "-316.78296 -316.78271 -96"
+"*trigger_bounds_maxs" "316.78296 316.78296 96"
+}
+{
+"origin" "-3752 -96 656"
+"wait" "1"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "0"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "none"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4097"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"link_guid" "17679895"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 0 0 316.78223"
+"*trigger_brush_0_plane_1" "1 0 0 316.78223"
+"*trigger_brush_0_plane_2" "0 -1 0 316.78247"
+"*trigger_brush_0_plane_3" "0 1 0 316.78241"
+"*trigger_brush_0_plane_4" "-0 0 -1 96"
+"*trigger_brush_0_plane_5" "-0 -0 1 96"
+"*trigger_brush_0_plane_6" "-0.70880991 -0.70539957 -0 223.99986"
+"*trigger_brush_0_plane_7" "-0.70539945 0.70880997 0 224.00002"
+"*trigger_brush_0_plane_8" "0.70880991 0.70539957 0 224"
+"*trigger_brush_0_plane_9" "0.70539951 -0.70880997 0 224.00003"
+"*trigger_brush_0_plane_10" "-0.70539945 0.70881003 0 224.00003"
+"*trigger_brush_0_plane_11" "0.70539945 -0.70880997 0 224.00003"
+"*trigger_bounds_mins" "-316.78284 -316.78284 -96"
+"*trigger_bounds_maxs" "316.78296 316.7829 96"
+}
+{
+"spawnflags" "0"
+"origin" "-3824 -72 640"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-3824 88 624"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-3664 -72 656"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-3824 -248 624"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-4000 -72 736"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-4610.47 -4408.71 676.989"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-4522.47 -4504.71 676.989"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-4306.47 -4712.71 676.989"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-4138.47 -4712.71 676.989"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-3930.47 -4680.71 676.989"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-3754.47 -4520.71 676.989"
+"classname" "info_node"
+}
+{
+"spawnflags" "0"
+"origin" "-3418.47 -4184.71 676.989"
+"classname" "info_node"
+}
+{
+"editorclass" "info_fw_turret_site"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0 135 0"
+"origin" "-6522 -1025 533"
+"link_to_guid_1" "cd6e529e"
+"link_to_guid_0" "1e132096"
+"link_guid" "1e3bbebe"
+"turretId" "7"
+"teamnumber" "3"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_turret_site"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "-0.0980319 -171.596 -0.00220506"
+"origin" "-6239.68 -4216.72 521.118"
+"link_to_guid_1" "ee670072"
+"link_to_guid_0" "4b6be066"
+"link_guid" "237cf48f"
+"turretId" "0"
+"teamnumber" "4"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_turret_site"
+"teamnumber" "4"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3818.28 -319.059 613.368"
+"link_to_guid_1" "c86bd441"
+"link_to_guid_0" "6668019f"
+"link_guid" "c9f7aad1"
+"turretId" "2"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_turret_site"
+"teamnumber" "4"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0 90 -8.60438e-006"
+"origin" "-4384 -2680 756"
+"link_to_guid_1" "3b7f5395"
+"link_to_guid_0" "2365c1c9"
+"link_guid" "394a1d3c"
+"turretId" "1"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_team_tower"
+"spawnflags" "0"
+"model" "models/props/generator_coop/generator_coop_medium.mdl"
+"scale" "1"
+"angles" "-2.48038e-006 90 0"
+"origin" "-454.381 -2886.73 520.827"
+"link_to_guid_1" "63693a5c"
+"link_to_guid_0" "128fc74f"
+"link_guid" "a2589c18"
+"teamnumber" "2"
+"radius" "1"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_turret_site"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "3.35337e-006 90 0"
+"origin" "-760 -3872 521"
+"link_to_guid_1" "feb9084e"
+"link_to_guid_0" "bcd0f203"
+"link_guid" "868d19d2"
+"turretId" "3"
+"teamnumber" "2"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_camp"
+"spawnflags" "0"
+"model" "models/vehicle/escape_pod/escape_pod.mdl"
+"height" "256"
+"origin" "-1248 448 656"
+"link_to_guid_8" "ffdb91c1"
+"link_to_guid_7" "fc571b81"
+"link_to_guid_6" "f983fcd0"
+"link_to_guid_5" "de85a29e"
+"link_to_guid_4" "ccdc3db0"
+"link_to_guid_3" "bdc38ac8"
+"link_to_guid_2" "563b6eff"
+"link_to_guid_1" "4359bf3d"
+"link_to_guid_0" "2f988812"
+"link_guid" "a9394e76"
+"radius" "900"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_camp"
+"spawnflags" "0"
+"model" "models/vehicle/escape_pod/escape_pod.mdl"
+"height" "256"
+"origin" "-4368 -2136 700"
+"link_to_guid_11" "f57064db"
+"link_to_guid_10" "df5a7c37"
+"link_to_guid_9" "a1a2ede5"
+"link_to_guid_8" "9c0be084"
+"link_to_guid_7" "7c9125e7"
+"link_to_guid_6" "436573d6"
+"link_to_guid_5" "36aeef28"
+"link_to_guid_4" "2639b847"
+"link_to_guid_3" "234799c3"
+"link_to_guid_2" "1b61f1e1"
+"link_to_guid_1" "194727c5"
+"link_to_guid_0" "5496c0f"
+"link_guid" "df7be002"
+"radius" "800"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_camp"
+"spawnflags" "0"
+"model" "models/vehicle/escape_pod/escape_pod.mdl"
+"height" "256"
+"origin" "-8064 -3544 667.01"
+"link_to_guid_8" "d982b5e0"
+"link_to_guid_7" "d57a2bb3"
+"link_to_guid_6" "d284dc2c"
+"link_to_guid_5" "a7db3193"
+"link_to_guid_4" "9503c7f8"
+"link_to_guid_3" "88df464c"
+"link_to_guid_2" "3adb10c9"
+"link_to_guid_1" "388cbaa5"
+"link_to_guid_0" "2266d6e2"
+"link_guid" "539c61dc"
+"radius" "900"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_turret_site"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0 65.8114 0"
+"origin" "-7869.5 -1013.15 681"
+"link_to_guid_1" "9b60b1b0"
+"link_to_guid_0" "7491589"
+"link_guid" "7070a0e2"
+"turretId" "6"
+"teamnumber" "3"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_turret_site"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0 135 0"
+"origin" "-6649 362 585"
+"link_to_guid_1" "9b6a7479"
+"link_to_guid_0" "6c2dd43f"
+"link_guid" "ef8ee88b"
+"turretId" "8"
+"teamnumber" "3"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_turret_site"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0.603044 90 -1.33634"
+"origin" "-949.577 -2882.28 533.149"
+"link_to_guid_1" "ce6e0c03"
+"link_to_guid_0" "436209f5"
+"link_guid" "f4757f6f"
+"turretId" "4"
+"teamnumber" "2"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_turret_site"
+"spawnflags" "0"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "-1.1511e-005 90 -5.58721e-005"
+"origin" "-957.242 -1445.11 520.999"
+"link_to_guid_1" "3566e2c0"
+"link_to_guid_0" "263c3de7"
+"link_guid" "6c185b79"
+"turretId" "5"
+"teamnumber" "2"
+"classname" "info_target"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 90 -1.52373e-006"
+"origin" "-4384 -2534 725.88"
+"link_guid" "3b7f5395"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4422 -2642 716"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4422 -2718 716"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4346 -2642 716"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4346 -2718 716"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4384 -2534 716"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4422 -2718 726"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4422 -2642 726"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4346 -2642 726"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4346 -2718 726"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4346 -2718 736"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4346 -2642 736"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4422 -2718 736"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4422 -2642 736"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4346 -2642 746"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4422 -2718 746"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4346 -2718 746"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4422 -2642 746"
+"classname" "script_ref"
+}
+{
+"model" "*2"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-4384 -2626 886"
+"link_guid" "2365c1c9"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3726 -65 530"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -281 564"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -357 564"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -357 564"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -281 564"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 90 -1.52373e-006"
+"origin" "-3726 -65 549.88"
+"link_guid" "6668019f"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3726 -65 540"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -357 574"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -357 574"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -281 574"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -281 574"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -357 584"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -357 584"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -281 584"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -281 584"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -281 594"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -281 594"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -357 594"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -357 594"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -281 604"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -281 604"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3856 -357 604"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-3780 -357 604"
+"classname" "script_ref"
+}
+{
+"model" "*3"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-3790.5 -210 722"
+"link_guid" "c86bd441"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 97.0737 0"
+"origin" "-6197.48 -4250.06 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 97.0737 0"
+"origin" "-6282.25 -4183.92 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 45.0117 -1.52373e-006"
+"origin" "-6081.35 -4053.12 545.88"
+"link_guid" "ee670072"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 97.0737 0"
+"origin" "-6272.91 -4259.4 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45.0117 0"
+"origin" "-6081.35 -4053.12 536"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 97.0737 0"
+"origin" "-6206.85 -4174.57 512"
+"classname" "script_ref"
+}
+{
+"model" "*4"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-6177.5 -4151 678"
+"link_guid" "4b6be066"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45.0117 0"
+"origin" "-6081.35 -4053.12 526"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-722 -3834 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-798 -3834 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-722 -3910 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-608 -3870 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-798 -3910 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 90 -1.52373e-006"
+"origin" "-608 -3870 521.88"
+"link_guid" "feb9084e"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-912 -2920 522"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-912 -2844 522"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-988 -2844 522"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-988 -2920 522"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-834 -2880 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 90 -1.52373e-006"
+"origin" "-834 -2880 521.88"
+"link_guid" "ce6e0c03"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-919 -1623 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-995 -1483 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 90 -1.52373e-006"
+"origin" "-919 -1623 521.88"
+"link_guid" "263c3de7"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-995 -1407 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-919 -1407 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-919 -1483 512"
+"classname" "script_ref"
+}
+{
+"model" "*5"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-702.5 -3872 686"
+"link_guid" "bcd0f203"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"model" "*6"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-911.5 -2880 686"
+"link_guid" "436209f5"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"model" "*7"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-955.5 -1515 686"
+"link_guid" "3566e2c0"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6702.86 362.291 576"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6649.12 416.031 576"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 45 -1.52373e-006"
+"origin" "-6766.12 247.122 585.88"
+"link_guid" "6c2dd43f"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6649.11 308.55 576"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6595.37 362.29 576"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6766.12 247.122 576"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6468.37 -1024.71 524"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 45 -1.52373e-006"
+"origin" "-6799.12 -875.88 533.88"
+"link_guid" "cd6e529e"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6522.11 -1078.45 524"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6522.12 -970.969 524"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6799.12 -875.88 524"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6575.86 -1024.71 524"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6522.11 -1078.45 514"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6575.86 -1024.71 514"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7819.05 -993.456 632"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7888.55 -962.627 632"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 11.1616 0"
+"origin" "-7927.69 -782.723 576"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7919.35 -1032.1 632"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7849.92 -1062.91 632"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_battery_port"
+"spawnflags" "0"
+"model" "models/props/battery_port/battery_port_animated.mdl"
+"scale" "1"
+"angles" "3.10056e-006 11.1616 -1.52373e-006"
+"origin" "-7927.69 -782.723 585.88"
+"link_guid" "7491589"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7819.05 -993.456 642"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7849.92 -1062.91 642"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7919.35 -1032.1 642"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7888.55 -962.627 642"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7819.05 -993.456 652"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7919.35 -1032.1 652"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7849.92 -1062.91 652"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7888.55 -962.627 652"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7849.92 -1062.91 662"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7888.55 -962.627 662"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7919.35 -1032.1 662"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7819.05 -993.456 662"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7819.05 -993.456 672"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7919.35 -1032.1 672"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7888.55 -962.627 672"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 66.0615 0"
+"origin" "-7849.92 -1062.91 672"
+"classname" "script_ref"
+}
+{
+"model" "*8"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-6680.75 331.983 750"
+"link_guid" "9b6a7479"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"model" "*9"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-6633.75 -977.05 686"
+"link_guid" "1e132096"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"model" "*10"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-7869.28 -925.873 774"
+"link_guid" "9b60b1b0"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"model" "*11"
+"triangle_collision" "1"
+"editorclass" "func_brush_navmesh_separator"
+"origin" "-452 -2886 732"
+"link_guid" "63693a5c"
+"startDisconnected" "0"
+"classname" "func_brush"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-912 -2920 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-988 -2920 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-988 -2844 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-912 -2844 512"
+"classname" "script_ref"
+}
+{
+"editorclass" "info_fw_foundation_plate"
+"spawnflags" "0"
+"model" "models/industrial/grate_mod.mdl"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-6468.37 -1024.71 514"
+"classname" "script_ref"
+}
+{
+"editorclass" "trigger_fw_territory"
+"origin" "-728 -2828 885"
+"wait" "0"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "0"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "0"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "1"
+"StartDisabled" "0"
+"spawnflags" "64"
+"link_guid" "128fc74f"
+"triggerFilterUseNew" "1"
+"classname" "trigger_multiple"
+"*trigger_brush_0_plane_0" "-1 -0 -0 904"
+"*trigger_brush_0_plane_1" "1 0 0 1336"
+"*trigger_brush_0_plane_2" "-0 -1 -0 1588"
+"*trigger_brush_0_plane_3" "-0 1 0 996"
+"*trigger_brush_0_plane_4" "0 0 -1 395"
+"*trigger_brush_0_plane_5" "0 0 1 395"
+"*trigger_brush_0_plane_6" "-0.70710677 0.70710677 0 1343.5029"
+"*trigger_brush_0_plane_7" "-0.70710677 -0.70710677 -0 1762.1101"
+"*trigger_brush_0_plane_8" "0.70710677 -0.70710677 0 2067.5801"
+"*trigger_brush_0_plane_9" "0.70710677 0.70710677 0 1648.9729"
+"*trigger_brush_1_plane_0" "-1 -0 -0 1336"
+"*trigger_brush_1_plane_1" "1 0 0 1336"
+"*trigger_brush_1_plane_2" "0 -1 -0 -996"
+"*trigger_brush_1_plane_3" "-0 1 -0 1588"
+"*trigger_brush_1_plane_4" "0 0 -1 395"
+"*trigger_brush_1_plane_5" "0 0 1 395"
+"*trigger_brush_1_plane_6" "-0.70710677 0.70710677 -0 2067.5801"
+"*trigger_brush_1_plane_7" "-0.70710677 -0.70710677 -0 240.41632"
+"*trigger_brush_1_plane_8" "0.70710677 -0.70710677 0 240.41632"
+"*trigger_brush_1_plane_9" "0.70710677 0.70710677 0 2067.5801"
+"*trigger_bounds_mins" "-1336 -1588 -395"
+"*trigger_bounds_maxs" "1336 1588 395"
+}
+{
+"model" "*12"
+"triangle_collision" "1"
+"editorclass" "func_brush_fw_territory_border"
+"origin" "-1051 -2171 728"
+"VisibilityFlags" "2"
+"useNonLocalWorldLights" "0"
+"startdisabled" "0"
+"solidity" "1"
+"solidbsp" "0"
+"gamemode_tdm" "0"
+"gamemode_lh" "0"
+"gamemode_fw" "1"
+"gamemode_ffa" "0"
+"gamemode_ctf" "0"
+"gamemode_cp" "0"
+"gamemode_at" "0"
+"drawinfastreflection" "0"
+"disableshadows" "0"
+"bakedSunFraction" "-1.0"
+"teamnumber" "2"
+"classname" "func_brush"
+}
+{
+"model" "*13"
+"triangle_collision" "1"
+"editorclass" "func_brush_fw_territory_border"
+"origin" "-6613 -1533.5 674"
+"useNonLocalWorldLights" "0"
+"teamnumber" "3"
+"startdisabled" "0"
+"solidity" "1"
+"solidbsp" "0"
+"gamemode_tdm" "0"
+"gamemode_lh" "0"
+"gamemode_fw" "1"
+"gamemode_ffa" "0"
+"gamemode_ctf" "0"
+"gamemode_cp" "0"
+"gamemode_at" "0"
+"drawinfastreflection" "0"
+"disableshadows" "0"
+"bakedSunFraction" "-1.0"
+"VisibilityFlags" "4"
+"classname" "func_brush"
+}
+{
+"model" "*14"
+"triangle_collision" "1"
+"editorclass" "func_brush_fw_territory_border"
+"origin" "-1051 -2170.5 728"
+"useNonLocalWorldLights" "0"
+"startdisabled" "0"
+"solidity" "1"
+"solidbsp" "0"
+"gamemode_tdm" "0"
+"gamemode_lh" "0"
+"gamemode_fw" "1"
+"gamemode_ffa" "0"
+"gamemode_ctf" "0"
+"gamemode_cp" "0"
+"gamemode_at" "0"
+"drawinfastreflection" "0"
+"disableshadows" "0"
+"bakedSunFraction" "-1.0"
+"teamnumber" "2"
+"VisibilityFlags" "4"
+"classname" "func_brush"
+}
+{
+"model" "*15"
+"triangle_collision" "1"
+"editorclass" "func_brush_fw_territory_border"
+"origin" "-6614 -1533.5 674"
+"VisibilityFlags" "2"
+"useNonLocalWorldLights" "0"
+"teamnumber" "3"
+"startdisabled" "0"
+"solidity" "1"
+"solidbsp" "0"
+"gamemode_tdm" "0"
+"gamemode_lh" "0"
+"gamemode_fw" "1"
+"gamemode_ffa" "0"
+"gamemode_ctf" "0"
+"gamemode_cp" "0"
+"gamemode_at" "0"
+"drawinfastreflection" "0"
+"disableshadows" "0"
+"bakedSunFraction" "-1.0"
+"classname" "func_brush"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -70.647 0"
+"origin" "-1398.96 873.896 574.188"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4448 -1480 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4448 -1536 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4448 -1592 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4600 -1568 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4712 -1568 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4656 -1568 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4768 -1568 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4604 -1368 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4220 -1782 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4540 -1784 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4792 -1724 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4844 -1784 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4844 -2152 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4792 -2100 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4488 -2100 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-4436 -2152 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4648 -1924 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 174.242 0"
+"origin" "-3516.01 -2612.4 736"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -134.404 0"
+"origin" "-4005.21 -2549.17 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -134.404 0"
+"origin" "-3969.21 -2585.17 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-3363.85 -2041.39 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-3364.24 -2092.28 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-2396.24 -2092.28 656"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-2395.85 -2041.39 656"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-3612.05 -2210.29 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 45 0"
+"origin" "-3647.46 -2175.76 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4258 -2710 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4258 -2660 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-5034 -2714 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-5034 -2664 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-4758 -2662 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-4758 -2712 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 124.91 0"
+"origin" "-5305.57 -2280.73 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 124.91 0"
+"origin" "-5346.53 -2309.39 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -55.09 0"
+"origin" "-5463.47 -2054.61 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -55.09 0"
+"origin" "-5504.43 -2083.27 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4822 -2766 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-4970 -2610 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-4200 -2766 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 137.849 0"
+"origin" "-3978.23 -2674.54 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 135.697 0"
+"origin" "-3527.69 -2204.18 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -55.171 0"
+"origin" "-5292.38 -1858.66 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 49.7768 0"
+"origin" "-4953.45 -3057.54 720"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -45.163 0"
+"origin" "-6104.11 -1467.31 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -45.163 0"
+"origin" "-6068.72 -1432.03 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -45.163 0"
+"origin" "-5880.11 -1255.31 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -45.163 0"
+"origin" "-6282.72 -1650.03 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 45.2765 0"
+"origin" "-6525.59 -1797.7 592"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 45.2765 0"
+"origin" "-6485.59 -1837.7 592"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 44.837 0"
+"origin" "-6592.69 -1784.11 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 44.837 0"
+"origin" "-6656.69 -1716.11 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -135.163 0"
+"origin" "-6819.31 -1879.89 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -135.163 0"
+"origin" "-6675.31 -2023.89 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 44.837 0"
+"origin" "-6616.69 -1416.11 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -135.163 0"
+"origin" "-7161.31 -2245.89 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -135.163 0"
+"origin" "-7383.31 -2023.89 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 44.837 0"
+"origin" "-7372.69 -1516.11 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.724 0"
+"origin" "-5710.41 -974.3 592"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -135.163 0"
+"origin" "-5745.31 -889.89 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.724 0"
+"origin" "-5836.41 -898.3 592"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.724 0"
+"origin" "-6372.41 -690.3 592"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.724 0"
+"origin" "-6420.41 -650.3 592"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.724 0"
+"origin" "-6460.41 -610.3 592"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -135.163 0"
+"origin" "-6305.31 -725.89 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -135.163 0"
+"origin" "-6481.31 -555.89 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 44.837 0"
+"origin" "-6090.69 -350.11 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 44.837 0"
+"origin" "-5666.69 33.89 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -135.163 0"
+"origin" "-6045.31 -305.89 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -20.865 0"
+"origin" "-5194.72 232.223 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 159.135 0"
+"origin" "-5075.28 189.78 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4882 184 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-4882 -306 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -25.868 0"
+"origin" "-5272.05 6.83351 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -134.691 0"
+"origin" "-6613.74 55.0701 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -134.691 0"
+"origin" "-6895.74 339.07 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -45.061 0"
+"origin" "-5494.43 573.319 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -25.868 0"
+"origin" "-5784.05 154.834 592"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -44.691 0"
+"origin" "-7145.07 -729.74 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 75.477 0"
+"origin" "-7936.67 -3117.23 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 67.9382 0"
+"origin" "-8116.76 -3943.04 612.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -22.062 0"
+"origin" "-8131.04 -3881.24 612.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 74.9241 0"
+"origin" "-8443.99 -4197.42 612.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 75.0976 0"
+"origin" "-8591.52 -2940.22 596.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -14.449 0"
+"origin" "-8625.64 -2878.83 596.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -14.902 0"
+"origin" "-8646.22 -3144.48 588.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -14.902 0"
+"origin" "-8186.22 -3268.48 588.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -14.902 0"
+"origin" "-7738.22 -3388.48 588.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -14.411 0"
+"origin" "-7861.12 -3839.65 612.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 75.098 0"
+"origin" "-7821.52 -3916.22 588.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 44.4931 0"
+"origin" "-8843.46 -4036.85 588.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 11.4796 0"
+"origin" "-9137.74 -3522.5 612.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -23.152 0"
+"origin" "-6910.97 -3776.88 612.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 24.3059 0"
+"origin" "-7612.21 -4206.92 612.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -135.244 0"
+"origin" "-5389.49 -3299.08 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -135.244 0"
+"origin" "-5428.86 -3259.31 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.26 0"
+"origin" "-5311.26 -3375.59 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.26 0"
+"origin" "-5271.21 -3414.68 720"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 45.6407 0"
+"origin" "-5198.86 -3069.67 720"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -106.038 0"
+"origin" "-2273.51 -2344.4 656"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -70.647 0"
+"origin" "-2784.97 -2332.36 656"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-2606.61 -1979.85 656"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -90 0"
+"origin" "-2451.72 -1980.24 656"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -2.2529 0"
+"origin" "-8502.15 -4125.32 612.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -83.909 0"
+"origin" "-1470.54 649.606 574.188"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -70.647 0"
+"origin" "-1646.96 681.896 574.188"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -47.145 0"
+"origin" "-1909.06 268.227 574.188"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -89.666 0"
+"origin" "-1188.89 -5.46055 534.188"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -89.666 0"
+"origin" "-1128.89 -5.4606 534.188"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -45.361 0"
+"origin" "-1702.89 389.442 574.188"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -50.123 0"
+"origin" "-1915.84 473.333 574.185"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -89.666 0"
+"origin" "-664 608 608"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -89.666 0"
+"origin" "-432 332 616"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -89.666 0"
+"origin" "-432 1192 592"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -106.195 0"
+"origin" "-640.812 -190.785 591.967"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -73.007 0"
+"origin" "-1373.04 -230.614 536.005"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -89.666 0"
+"origin" "-1300.89 -605.461 534.188"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -106.195 0"
+"origin" "-612.868 1338.66 586.157"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -82.54 0"
+"origin" "-1021.68 1162.37 574.188"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -121.825 0"
+"origin" "-1467.78 -599.512 534.188"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -143.868 0"
+"origin" "-167.517 1018.96 595.376"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 75.8469 0"
+"origin" "-7529.91 -2887.76 592"
+"classname" "info_node_cover_stand"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-4581.92 -1857.53 755.998"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-3202.13 -2679.22 772"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-4996.1 848.459 640"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "3.20383 -2583.9 584"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-4126.89 -3645.28 708.992"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-8254.52 -4383.02 668.36"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-8468.17 -2480.01 644.36"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-7833.18 -1925.19 644.125"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-8326.67 -1573.78 644"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-5844.11 -121.301 644"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-6128.37 -720.44 644"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-6707.37 -1812.42 643.999"
+"classname" "info_node_safe_hint"
+}
+{
+"spawnflags" "0"
+"nodeFOV" "360"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "102"
+"origin" "-5257.23 -3233.01 775.998"
+"classname" "info_node_safe_hint"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-707.71 -2674.11 543.997"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-704 -3212 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -178.666 0"
+"origin" "-617.141 -2875.04 543.998"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-460 -3100 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -125.081 0"
+"origin" "-833.472 -2284.29 543.998"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 90 0"
+"origin" "-832.73 -3601.05 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -1.40334e-014 0"
+"origin" "-1399.71 -2731.27 671.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -6.4689 0"
+"origin" "-1399.62 -2638.84 671.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-1480 -3028 672"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-1720 -3100 676"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-1720 -2660 676"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-1992 -2828 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-1992 -2936 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-2180 -2984 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-2016 -2576 676"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 56.2721 0"
+"origin" "-1949.45 -3509.17 676"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-2812.8 -2664.66 740"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 25.6162 0"
+"origin" "-2812.01 -3011.4 740"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -32.531 0"
+"origin" "-2828.86 -3205.67 740"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -6.2179 0"
+"origin" "-3032 -3416.73 740"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 0.0768 0"
+"origin" "-3104.63 -3485.69 740"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-2861.49 -2863.76 740"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 180 0"
+"origin" "-2704 -2396 676"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.33 0"
+"origin" "-2761.52 -3471.39 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -134.33 0"
+"origin" "-2684.26 -3546.82 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -134.639 0"
+"origin" "-3202.47 -3376.52 740"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -134.859 0"
+"origin" "-3200.63 -4278.03 676.992"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -134.639 0"
+"origin" "-3550.71 -3621.72 676.992"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 155.979 0"
+"origin" "-3463.88 -4643.98 804.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 133.053 0"
+"origin" "-3741.32 -4907.18 805"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 166.354 0"
+"origin" "-3675.16 -4826.35 805"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 114.473 0"
+"origin" "-3983 -4975.73 805"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 88.2126 0"
+"origin" "-4256.72 -4967.88 805"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 46.6407 0"
+"origin" "-4512.85 -4891.3 805"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 41.8151 0"
+"origin" "-4563.67 -4841.39 805.001"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 42.579 0"
+"origin" "-4859.07 -4548.38 805"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -120.962 0"
+"origin" "-3986.54 -3549.94 676.992"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -113.45 0"
+"origin" "-4188.01 -3705.29 676.991"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 66.5706 0"
+"origin" "-4238.87 -3829.87 676.992"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 62.3857 0"
+"origin" "-4364.49 -4336.36 677.088"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 44.8382 0"
+"origin" "-4309.25 -4380.65 677.117"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 37.7505 0"
+"origin" "-4071.07 -4137.88 676.992"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 139.865 0"
+"origin" "-4222.27 -4375.36 677.088"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 167.789 0"
+"origin" "-3955.13 -4117.21 677.088"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 80.8422 0"
+"origin" "-4411.09 -4892.87 805"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 89.7471 0"
+"origin" "-4326.31 -4894.97 805"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 70.1256 0"
+"origin" "-3910.69 -4912.43 805"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 45.67 0"
+"origin" "-2818.48 -3682.61 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 45.67 0"
+"origin" "-2885.74 -3615.18 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 36.5559 0"
+"origin" "-2609.52 -3780.17 676"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 31.9057 0"
+"origin" "-3696.58 -3757.97 676.992"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -23.621 0"
+"origin" "-7441.27 -898.424 612.001"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -23.621 0"
+"origin" "-8041.74 -1434.98 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 37.0224 0"
+"origin" "-7080.78 161.468 615.988"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 53.6677 0"
+"origin" "-6786.92 -124.946 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -33.427 0"
+"origin" "-6075.92 526.058 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -35.245 0"
+"origin" "-6677.06 -1246.48 547.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 138.638 0"
+"origin" "-2417.65 -1009.07 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 141.758 0"
+"origin" "-2332.31 -564.011 544.001"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 160.837 0"
+"origin" "-2624.02 -519.857 544.001"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 169.628 0"
+"origin" "-2689.16 -113.007 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 133.483 0"
+"origin" "-2829.19 -642.056 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 149.3 0"
+"origin" "-1979.57 -1001.11 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 134.155 0"
+"origin" "-2184.13 -999.64 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 149.3 0"
+"origin" "-1686.79 -1189.54 551.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 171.47 0"
+"origin" "-1357.28 -1042.66 543.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -179.666 0"
+"origin" "-1235.46 -715.11 534.188"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -34.978 0"
+"origin" "-2730.88 -458.32 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -35.71 0"
+"origin" "-2775.54 -520.54 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -9.6773 0"
+"origin" "-2266.83 -844.802 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 3.901 0"
+"origin" "-2043.38 -1151.14 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -64.026 0"
+"origin" "-2482.02 -350.092 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -30.214 0"
+"origin" "-2714.15 -751.932 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -45.244 0"
+"origin" "-5714.85 -3386.19 631.998"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -45.244 0"
+"origin" "-5778.85 -3454.19 631.998"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -45.162 0"
+"origin" "-5143.75 -3664.94 683.684"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -28.251 0"
+"origin" "-5595.25 -3691.11 631.998"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 28.2911 0"
+"origin" "-6336.37 -3733.64 583.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 43.454 0"
+"origin" "-6046.74 -4089.17 567.998"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 45.5625 0"
+"origin" "-6442.85 -4307.03 596.727"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 43.454 0"
+"origin" "-6411.68 -3979.16 544.059"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -135.616 0"
+"origin" "-5636.21 -3055.44 743.998"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -134.748 0"
+"origin" "-5911.06 -3640.23 615.998"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -158.587 0"
+"origin" "-5575.21 -3991.75 616.151"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -135.047 0"
+"origin" "-5652.33 -3892.95 616.263"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -109.894 0"
+"origin" "-6004.29 -3569.17 615.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 47.6708 0"
+"origin" "-6118.64 -4004.42 567.998"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 47.6708 0"
+"origin" "-5953.49 -4363.76 551.998"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 166.432 0"
+"origin" "-5926.31 -4057.69 583.998"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -142.99 0"
+"origin" "-6006.69 -3895.67 591.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 44.8896 0"
+"origin" "-6334.8 -4509.46 596.59"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 169.812 0"
+"origin" "-4504.9 -4068.47 676.998"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 134.202 0"
+"origin" "-4950.18 -4283.5 691.021"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 141.776 0"
+"origin" "-4755.07 -3956.47 706.986"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 136.692 0"
+"origin" "-4788.98 -4236.91 677.36"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 142.275 0"
+"origin" "-4860.91 -4054.94 689.753"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 145.937 0"
+"origin" "-4658.05 -4180.44 677.005"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 135.456 0"
+"origin" "-4556.96 -4489.46 676.997"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 141.59 0"
+"origin" "-4368.02 -4659.16 677.095"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 50.3557 0"
+"origin" "-3067.86 -4140.96 676.992"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 31.9057 0"
+"origin" "-3220.18 -3619.63 676.991"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 128.27 0"
+"origin" "-6730.49 -4096.46 596.637"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 144.639 0"
+"origin" "-7016.04 -3992.89 615.352"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 144.639 0"
+"origin" "-6970.05 -3936.68 613.751"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 137.753 0"
+"origin" "-6828.88 -3835.72 613.356"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 127.562 0"
+"origin" "-6906.02 -4206.28 621.319"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -30.354 0"
+"origin" "-7229.86 -3833.26 628.919"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -30.354 0"
+"origin" "-7189.66 -3781.25 615.588"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 1.4328 0"
+"origin" "-7652.91 -3868.36 613.737"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 169.997 0"
+"origin" "-3390.69 -409.783 628.126"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 145.444 0"
+"origin" "-3539.24 -628.953 653.85"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 170.226 0"
+"origin" "-3422.48 163.181 678.375"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 153.366 0"
+"origin" "-3977.6 -507.186 620.654"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -87.59 0"
+"origin" "-4051.64 -1031.06 642.338"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -106.967 0"
+"origin" "-3654.05 -1080.95 691.825"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 12.1599 0"
+"origin" "-4318.36 -436.306 653.563"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 36.0196 0"
+"origin" "-4284.9 -513.31 662.829"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -11.207 0"
+"origin" "-4220.42 -156.665 650.563"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -1.414 0"
+"origin" "-4181.35 -321.315 624.302"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -1.414 0"
+"origin" "-4146.35 640.5 599.621"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -1.414 0"
+"origin" "-4251.01 554.427 604.121"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 178.584 0"
+"origin" "-3355.36 -264.48 591.022"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 178.584 0"
+"origin" "-3434.78 494.689 570.717"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 178.584 0"
+"origin" "-3370.48 572.305 569.7"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 169.186 0"
+"origin" "-3338.88 371.23 595.424"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -87.59 0"
+"origin" "-3826.48 103.86 556.936"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -94.751 0"
+"origin" "-3612.14 -8.40236 573.338"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -100.359 0"
+"origin" "-3683.58 -455.71 633.426"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -93.066 0"
+"origin" "-3761.73 -451.058 623.982"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -94.751 0"
+"origin" "-3613.3 411.581 580.228"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -119.661 0"
+"origin" "-4356.36 485.86 609.257"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -67.568 0"
+"origin" "-3133.43 306.76 585.95"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -41.623 0"
+"origin" "-3233.32 119.493 660.132"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -52.608 0"
+"origin" "-3168.23 169.574 666.278"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -3.2097 0"
+"origin" "-3645.72 -387.85 633.759"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -15.827 0"
+"origin" "-5718.78 266.283 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 149.723 0"
+"origin" "-6117.19 -1016.82 548"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 134.837 0"
+"origin" "-6511.07 -1400.55 548"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 149.723 0"
+"origin" "-6988.63 -863.72 556"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -121.753 0"
+"origin" "-7241.91 -1372.73 556"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -134.726 0"
+"origin" "-6574.62 -1243.04 547.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -59.767 0"
+"origin" "-4954.64 413.425 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -147.04 0"
+"origin" "-5435.04 961.184 608"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -152.864 0"
+"origin" "-5958.78 512.058 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -137.45 0"
+"origin" "-6242.19 758.578 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -116.477 0"
+"origin" "-7831.51 -202.67 628"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 52.7583 0"
+"origin" "-7270.91 -2360.21 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -85.297 0"
+"origin" "-4082.07 -369.953 635.191"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 36.0196 0"
+"origin" "-4220.19 -580.09 662.354"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -62.22 0"
+"origin" "-4760.92 -245.885 605.061"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -106.967 0"
+"origin" "-3491.43 -947.177 682.578"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -88.642 0"
+"origin" "-3806.81 -915.8 630.794"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -88.642 0"
+"origin" "-3941.86 -835.906 621.134"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 65.2752 0"
+"origin" "-4196.32 -1418.68 747.066"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 91.358 0"
+"origin" "-3929.93 -1441.03 756.15"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 91.358 0"
+"origin" "-3775.94 -1462.05 754.149"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 91.358 0"
+"origin" "-3842.78 -1503.14 752.756"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -45.827 0"
+"origin" "-6790.89 -742.16 556"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -45.827 0"
+"origin" "-6766.08 -864.268 556"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -45.827 0"
+"origin" "-6587.41 -782.693 556.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -6.8311 0"
+"origin" "-7206.05 -1215.3 556.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -45.827 0"
+"origin" "-6327.11 -1407.17 547.996"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -45.827 0"
+"origin" "-6088.45 -1240.85 547.997"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 177.117 0"
+"origin" "-6528.74 -915.604 555.999"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 134.344 0"
+"origin" "-6842.1 -1983.31 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -172.091 0"
+"origin" "-2720.74 -3872.36 676"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -104.654 0"
+"origin" "-7818.54 -2545.53 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -106.009 0"
+"origin" "-7399.23 -2652.41 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -104.654 0"
+"origin" "-7682.55 -2236.73 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -103.491 0"
+"origin" "-7539.35 -2731.02 612.361"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -106.182 0"
+"origin" "-7556.15 -2515.44 612"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -103.272 0"
+"origin" "-7677.14 -2555.86 608"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 47.2903 0"
+"origin" "-7227.17 -1653.07 620"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 68.2386 0"
+"origin" "-7138.14 -1745.69 620"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -35.745 0"
+"origin" "-7295.84 -854.727 611.999"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 163.129 0"
+"origin" "-1079.54 -1393.29 543.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 164.256 0"
+"origin" "-644.283 -1287.47 551.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 163.129 0"
+"origin" "-608.017 -1548.47 543.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 121.938 0"
+"origin" "-1044.76 -1919.15 551.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -96.65 0"
+"origin" "-837.209 -1825.28 543.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -96.65 0"
+"origin" "-903.926 -1817.26 544"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 88.1533 0"
+"origin" "-912.089 -1918.1 543.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 109.373 0"
+"origin" "-773.872 -1925.72 543.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -96.65 0"
+"origin" "-652.95 -1876.11 544.306"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 146.327 0"
+"origin" "-671.199 -1642.57 543.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 138.57 0"
+"origin" "-562.81 -1962.17 545.867"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 158.674 0"
+"origin" "-498.6 -2227.35 551.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 90.7323 0"
+"origin" "-620.521 -2460.53 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 90.6667 0"
+"origin" "-560.453 -2459.98 545.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 90.7323 0"
+"origin" "-894.677 -2483.51 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 90.6667 0"
+"origin" "-1112.58 -2730.83 551.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 90.6667 0"
+"origin" "-664.477 -3024.61 544"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -89.333 0"
+"origin" "-741.66 -2149.92 544.001"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -92.033 0"
+"origin" "-1111.41 -2322.08 552"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -44.455 0"
+"origin" "-1293.77 -1707.54 551.999"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -45.719 0"
+"origin" "-1251.96 -1672.37 551.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -44.385 0"
+"origin" "-1455.92 -1275.27 551.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -45.719 0"
+"origin" "-1489.37 -964.045 544"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -44.385 0"
+"origin" "-1578.43 -999.334 544"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -90.518 0"
+"origin" "-1084.1 -953.878 546.075"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -96.65 0"
+"origin" "-732.146 -1010.05 552"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -1.2453 0"
+"origin" "-1537.71 -2839.76 676"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 3.1461 0"
+"origin" "-1517.71 -2924.2 676"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 139.352 0"
+"origin" "-2670.74 -602.656 543.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 149.245 0"
+"origin" "-1335.03 -1348.38 551.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 163.141 0"
+"origin" "-1032.31 -1085.11 543.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 74.4128 0"
+"origin" "-7853.43 -3034.08 612.36"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 74.4128 0"
+"origin" "-7751.29 -3071.42 612.36"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 167.629 0"
+"origin" "-5558.09 239.798 608"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 166.842 0"
+"origin" "-5227.33 360.85 613.635"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 135.208 0"
+"origin" "-5036.83 -214.626 613.635"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -150.455 0"
+"origin" "-5086.52 870.636 608"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -72.328 0"
+"origin" "-7478.87 -1322.08 620"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -49.23 0"
+"origin" "-7623.01 -1502.26 620"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -65.552 0"
+"origin" "-7408.93 -1568.43 619.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 129.387 0"
+"origin" "-7336.82 -1684.9 620.003"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 98.3354 0"
+"origin" "-7571.35 -1622.74 620"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 42.8562 0"
+"origin" "-7656.57 -851.33 608.001"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 50.9043 0"
+"origin" "-7387.3 -708.065 610.848"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -43.957 0"
+"origin" "-7396.13 -598.89 607.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -135.93 0"
+"origin" "-7294.09 -599.115 608.001"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -91.508 0"
+"origin" "-7590.79 -518.696 608"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -44.973 0"
+"origin" "-8232.15 -1676.94 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -44.973 0"
+"origin" "-8297.91 -2135.64 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -44.973 0"
+"origin" "-7915.97 -2454.28 612"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -45.665 0"
+"origin" "-7837.14 -2371.42 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -57.871 0"
+"origin" "-7613.43 -2143.64 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -44.634 0"
+"origin" "-8221.14 -1858.24 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -44.634 0"
+"origin" "-8328.48 -1787.78 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -44.634 0"
+"origin" "-8105.31 -1568.36 612"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 135.07 0"
+"origin" "-7298.37 -1518.83 620"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 98.8941 0"
+"origin" "-7408.47 -2129.94 620"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 75.1534 0"
+"origin" "-8007.74 -1160.66 608"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 44.2621 0"
+"origin" "-7462.92 -338.168 608"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 62.0431 0"
+"origin" "-7068.96 -645.363 611.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 60.7599 0"
+"origin" "-7127.36 -397.457 608.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 60.7599 0"
+"origin" "-6927.11 -247.45 607.997"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 48.9231 0"
+"origin" "-7026.65 -5.52116 608.269"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 44.1453 0"
+"origin" "-6898.84 -14.349 608.002"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 15.7763 0"
+"origin" "-7300.53 28.8221 615.989"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 15.7763 0"
+"origin" "-7312.08 -81.1711 615.989"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -142.622 0"
+"origin" "-6235.65 488.89 608"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -142.622 0"
+"origin" "-6285.76 583.541 608"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -93.005 0"
+"origin" "-7723.87 -317.694 620"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -110.578 0"
+"origin" "-7905.4 -806.484 608"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -110.545 0"
+"origin" "-8213.91 -1169.36 615.987"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -45.24 0"
+"origin" "-7779.74 -1457.22 620.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -118.793 0"
+"origin" "-7262.46 -2112.76 620"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 134.921 0"
+"origin" "-7069.4 -2059.8 619.999"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 134.921 0"
+"origin" "-6965.36 -2039.65 619.999"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 134.921 0"
+"origin" "-7067.73 -2181.29 620"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 134.438 0"
+"origin" "-7030.15 -1103.6 548.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 136.86 0"
+"origin" "-6958.79 -1023.52 548.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -122.915 0"
+"origin" "-6945.45 -1438.47 556.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -122.915 0"
+"origin" "-7076.89 -1356.67 547.999"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -27.741 0"
+"origin" "-6712.3 -1092.64 548.002"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -134.322 0"
+"origin" "-5838.37 -3486.14 623.998"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -134.322 0"
+"origin" "-5496.42 -3821.66 631.422"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -140.821 0"
+"origin" "-5635.04 -3386.63 639.998"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 44.8896 0"
+"origin" "-6389.73 -4358.61 596.68"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 45.8775 0"
+"origin" "-6190.79 -4504.95 596.59"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 39.1821 0"
+"origin" "-6138.37 -4365.84 543.997"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 49.869 0"
+"origin" "-6376.99 -4116.28 544.057"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 27.2854 0"
+"origin" "-6635.28 -4149.75 596.612"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 65.6465 0"
+"origin" "-5782.65 -4230.55 583.998"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 26.5723 0"
+"origin" "-6314.25 -3798.62 575.997"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 127.22 0"
+"origin" "-3975.97 -999.631 640.261"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 92.9475 0"
+"origin" "-3872.5 -954.094 635.842"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 39.1837 0"
+"origin" "-3746.29 -970.234 648.015"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 46.5053 0"
+"origin" "-3724.16 -1060.16 683.97"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 161.034 0"
+"origin" "-4931.75 -34.5351 612"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 167.601 0"
+"origin" "-4781.9 31.9081 609.512"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -88.88 0"
+"origin" "-3746.59 -1326.22 758.167"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -88.88 0"
+"origin" "-3862.17 -1403.46 755.403"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -88.88 0"
+"origin" "-4003.82 -1376.79 756.963"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -88.88 0"
+"origin" "-3818.67 -1310.57 736.729"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -88.88 0"
+"origin" "-3934.48 -1329.37 736.936"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 37.525 0"
+"origin" "-3261.78 -3825.89 676.991"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 42.1465 0"
+"origin" "-3004.11 -3943.44 676.99"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 29.8149 0"
+"origin" "-3785.85 -3979.67 676.99"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 146.092 0"
+"origin" "-7418.79 -3618.09 613.878"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 172.826 0"
+"origin" "-7497.34 -3881.1 613.738"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 164.11 0"
+"origin" "-7547.81 -3444.96 613.877"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 157.092 0"
+"origin" "-7382.1 -3382.33 613.951"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 171.98 0"
+"origin" "-7485.88 -4015.78 613.548"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 157.092 0"
+"origin" "-7068.7 -3627.8 630.557"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 135.409 0"
+"origin" "-7242 -3626.56 613.868"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 172.826 0"
+"origin" "-8034.04 -3907.92 612.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -16.281 0"
+"origin" "-8775.36 -3606.64 612.36"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -21.388 0"
+"origin" "-8728.06 -3723.08 612.36"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -16.725 0"
+"origin" "-8815.03 -3338.83 612.36"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -16.725 0"
+"origin" "-8472.9 -3035.34 612.361"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -16.725 0"
+"origin" "-8565.71 -3076.37 612.361"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -14.902 0"
+"origin" "-8536.68 -3642.07 612.36"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 150.096 0"
+"origin" "-6212.22 -1136.77 547.999"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 150.096 0"
+"origin" "-6376.93 -1153.07 548.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 150.096 0"
+"origin" "-6414.51 -1276.82 548.001"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 150.096 0"
+"origin" "-6223.32 -1303.52 547.999"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 169.832 0"
+"origin" "-5966.13 -1125.8 547.999"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -38.38 0"
+"origin" "-5580.96 453.289 612"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -11.798 0"
+"origin" "-5505.55 126.512 608"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -23.195 0"
+"origin" "-5691.8 369.818 608"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 179.146 0"
+"origin" "-5036.52 317.736 612"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 168.352 0"
+"origin" "-5023.53 106.622 611.999"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 139.991 0"
+"origin" "-4948.58 -138.127 612"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 175.641 0"
+"origin" "-4429.96 -6.77726 602.128"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -174.096 0"
+"origin" "-4064.05 -2378.34 745.168"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 0 0"
+"origin" "-5046.06 -2380.52 745.707"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 133.798 0"
+"origin" "-2495.17 -3816.94 676"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 177.129 0"
+"origin" "-2066.1 -3675.86 676"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 179.93 0"
+"origin" "-2192.01 -2920.02 676"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 88.3595 0"
+"origin" "-2666.73 -3245.09 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -99.291 0"
+"origin" "-2636.56 -2930.83 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -99.69 0"
+"origin" "-2271.33 -2856.08 676"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -99.291 0"
+"origin" "-2536.56 -2538.83 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 93.3102 0"
+"origin" "-2538.32 -2696.92 676"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 121.451 0"
+"origin" "-2235.09 -2639.19 675.999"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "98"
+"scale" "1"
+"angles" "0 -4.3496 0"
+"origin" "-4401.73 -4281.28 677.088"
+"classname" "info_node_cover_left"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "80"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "99"
+"scale" "1"
+"angles" "0 -19.849 0"
+"origin" "-4269.17 -4009.47 676.992"
+"classname" "info_node_cover_right"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 12.1031 0"
+"origin" "-4586.41 -4582 677.095"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 12.1031 0"
+"origin" "-4728.61 -4679.2 805"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -17.189 0"
+"origin" "-4572.98 -4366.2 676.992"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 45.1009 0"
+"origin" "-3993.2 -4718.89 676.992"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 45.1009 0"
+"origin" "-3937.2 -4774.89 676.992"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -138.479 0"
+"origin" "-3158.38 -3881.2 676.992"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "100"
+"scale" "1"
+"angles" "0 -138.479 0"
+"origin" "-3102.38 -3937.2 676.992"
+"classname" "info_node_cover_stand"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 46.1752 0"
+"origin" "-3862.58 -4359.17 676.991"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 -133.625 0"
+"origin" "-3489.45 -4008.82 676.991"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 135.596 0"
+"origin" "-3206.01 -4144.01 676.991"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 135.596 0"
+"origin" "-3781.91 -4714.67 676.994"
+"classname" "info_node_cover_crouch"
+}
+{
+"TargetNode" "-1"
+"spawnflags" "0"
+"nodeFOV" "120"
+"MinimumState" "1"
+"MaximumState" "3"
+"IgnoreFacing" "2"
+"hinttype" "101"
+"scale" "1"
+"angles" "0 135.596 0"
+"origin" "-3669.82 -4608.87 676.989"
+"classname" "info_node_cover_crouch"
+}
+{
+"editorclass" "script_power_up_other"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0 90 2.53193e-006"
+"origin" "-5243 -3233 712"
+"powerUpType" "mp_loot_titan_build_credit_lts"
+"classname" "script_ref"
+}
+{
+"editorclass" "script_power_up_other"
+"model" "models/communication/flag_base_red.mdl"
+"scale" "1"
+"angles" "0 90 2.53193e-006"
+"origin" "-4492.01 -1536.57 692.025"
+"powerUpType" "mp_loot_titan_build_credit_lts"
+"classname" "script_ref"
+}
+{
+"origin" "-7697.6 -3277.22 2118"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 455.62012"
+"*trigger_brush_0_plane_1" "1 0 0 455.61328"
+"*trigger_brush_0_plane_2" "0 -1 0 513.23438"
+"*trigger_brush_0_plane_3" "0 1 0 513.2417"
+"*trigger_brush_0_plane_4" "0 -0 -1 182"
+"*trigger_brush_0_plane_5" "-0 -0 1 182"
+"*trigger_brush_0_plane_6" "-0.98972714 0.14296904 0 393.67426"
+"*trigger_brush_0_plane_7" "0.14312822 0.98970413 0 461.63812"
+"*trigger_brush_0_plane_8" "0.98972714 -0.14296904 0 393.66644"
+"*trigger_brush_0_plane_9" "-0.14312822 -0.98970413 -0 461.63187"
+"*trigger_brush_0_plane_10" "-0.98972714 0.14296903 0 393.67426"
+"*trigger_brush_0_plane_11" "0.14312823 0.98970425 -0 461.63818"
+"*trigger_brush_0_plane_12" "0.98972714 -0.14296903 0 393.66644"
+"*trigger_brush_0_plane_13" "-0.14312823 -0.98970425 -0 461.6319"
+"*trigger_brush_0_plane_14" "-0.59868395 0.80098528 0 604.84576"
+"*trigger_brush_0_plane_15" "-0.80098528 -0.59868395 0 604.74408"
+"*trigger_brush_0_plane_16" "0.80098528 0.59868395 0 604.74304"
+"*trigger_brush_0_plane_17" "0.59868395 -0.80098528 0 604.83582"
+"*trigger_bounds_mins" "-455.62012 -513.23438 -182"
+"*trigger_bounds_maxs" "455.61328 513.2417 182"
+}
+{
+"origin" "-10901 -6447.86 1272"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 3209.627"
+"*trigger_brush_0_plane_1" "1 0 0 3209.6821"
+"*trigger_brush_0_plane_2" "0 -1 0 1600.1504"
+"*trigger_brush_0_plane_3" "0 1 0 1600.1563"
+"*trigger_brush_0_plane_4" "0 0 -1 2132"
+"*trigger_brush_0_plane_5" "-0 0 1 2132"
+"*trigger_brush_0_plane_6" "-0.93357444 0.35838348 0 3260.9895"
+"*trigger_brush_0_plane_7" "0.35853338 0.93351692 0 461.64783"
+"*trigger_brush_0_plane_8" "0.93357444 -0.35838348 0 3261.0391"
+"*trigger_brush_0_plane_9" "-0.35853338 -0.93351692 -0 461.62238"
+"*trigger_brush_0_plane_10" "-0.93357456 0.35838351 0 3260.99"
+"*trigger_brush_0_plane_11" "0.93357456 -0.35838351 0 3261.0396"
+"*trigger_brush_0_plane_12" "-0.40664804 0.91358483 0 2632.5132"
+"*trigger_brush_0_plane_13" "-0.91358477 -0.40664807 0 2632.0728"
+"*trigger_brush_0_plane_14" "0.91358477 0.40664807 0 2632.1255"
+"*trigger_brush_0_plane_15" "0.40664804 -0.91358483 0 2632.5305"
+"*trigger_bounds_mins" "-3209.627 -1600.1503 -2132"
+"*trigger_bounds_maxs" "3209.6821 1600.1563 2132"
+}
+{
+"origin" "-12224.3 -2684.64 1302"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 1919.71"
+"*trigger_brush_0_plane_1" "1 0 0 1919.6924"
+"*trigger_brush_0_plane_2" "0 -1 0 2792.1941"
+"*trigger_brush_0_plane_3" "0 1 0 2792.1877"
+"*trigger_brush_0_plane_4" "0 0 -1 2162"
+"*trigger_brush_0_plane_5" "0 -0 1 2162"
+"*trigger_brush_0_plane_6" "0.35838318 0.93357456 0 2582.3491"
+"*trigger_brush_0_plane_7" "0.93351686 -0.35853344 0 1064.5466"
+"*trigger_brush_0_plane_8" "-0.3583833 -0.93357456 -0 2582.3618"
+"*trigger_brush_0_plane_9" "-0.93351692 0.35853329 0 1064.5614"
+"*trigger_brush_0_plane_10" "0.93351698 -0.35853344 0 1064.5468"
+"*trigger_brush_0_plane_11" "-0.35838333 -0.93357462 -0 2582.3618"
+"*trigger_brush_0_plane_12" "-0.93351704 0.35853329 0 1064.5614"
+"*trigger_brush_0_plane_13" "0.91358483 0.40664819 0 2578.9524"
+"*trigger_brush_0_plane_14" "-0.40664828 0.91358477 0 2578.5479"
+"*trigger_brush_0_plane_15" "0.4066481 -0.91358483 0 2578.5464"
+"*trigger_brush_0_plane_16" "-0.91358471 -0.40664825 0 2578.9709"
+"*trigger_bounds_mins" "-1919.7101 -2792.1941 -2162"
+"*trigger_bounds_maxs" "1919.6924 2792.1877 2162"
+}
+{
+"origin" "-9678.93 1486.6 1272"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 1984.2383"
+"*trigger_brush_0_plane_1" "1 0 0 1984.2451"
+"*trigger_brush_0_plane_2" "0 -1 0 2092.8115"
+"*trigger_brush_0_plane_3" "0 1 0 2092.8159"
+"*trigger_brush_0_plane_4" "0 0 -1 2132"
+"*trigger_brush_0_plane_5" "-0 0 1 2132"
+"*trigger_brush_0_plane_6" "0.66694921 0.74510318 0 2141.3713"
+"*trigger_brush_0_plane_7" "0.74499589 -0.66706908 0 745.94049"
+"*trigger_brush_0_plane_8" "-0.66694921 -0.74510318 -0 2141.3635"
+"*trigger_brush_0_plane_9" "-0.74499589 0.66706902 0 745.93823"
+"*trigger_brush_0_plane_10" "-0.66694921 -0.74510324 -0 2141.3635"
+"*trigger_brush_0_plane_11" "-0.74499595 0.66706908 0 745.93829"
+"*trigger_bounds_mins" "-1984.2383 -2092.8115 -2132"
+"*trigger_bounds_maxs" "1984.2452 2092.8157 2132"
+}
+{
+"origin" "-6096.88 -6585.13 3646"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 186.99512"
+"*trigger_brush_0_plane_1" "1 0 0 187.00488"
+"*trigger_brush_0_plane_2" "0 -1 0 195.37012"
+"*trigger_brush_0_plane_3" "0 1 0 195.37988"
+"*trigger_brush_0_plane_4" "0 0 -1 1534"
+"*trigger_brush_0_plane_5" "-0 0 1 1534"
+"*trigger_brush_0_plane_6" "-0.93360478 0.35830453 0 140.7215"
+"*trigger_brush_0_plane_7" "0.35880053 0.93341428 0 155.19298"
+"*trigger_brush_0_plane_8" "0.93355304 -0.35843927 0 140.74951"
+"*trigger_brush_0_plane_9" "-0.35841355 -0.93356293 -0 155.23872"
+"*trigger_brush_0_plane_10" "-0.93360478 0.35830456 0 140.7215"
+"*trigger_brush_0_plane_11" "0.9335531 -0.35843927 0 140.74953"
+"*trigger_brush_0_plane_12" "-0.35841355 -0.93356299 -0 155.23874"
+"*trigger_brush_0_plane_13" "-0.40655595 0.91362584 0 209.29874"
+"*trigger_brush_0_plane_14" "-0.91354162 -0.40674537 0 209.26328"
+"*trigger_brush_0_plane_15" "0.91365522 0.40649006 0 209.22247"
+"*trigger_brush_0_plane_16" "0.40667942 -0.91357088 0 209.2924"
+"*trigger_bounds_mins" "-186.99512 -195.37012 -1534"
+"*trigger_bounds_maxs" "187.00488 195.37988 1534"
+}
+{
+"origin" "-5132.81 -6293.13 3646"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 96.314941"
+"*trigger_brush_0_plane_1" "1 0 0 96.310059"
+"*trigger_brush_0_plane_2" "0 -1 0 95.370117"
+"*trigger_brush_0_plane_3" "0 1 0 95.379883"
+"*trigger_brush_0_plane_4" "0 0 -1 1534"
+"*trigger_brush_0_plane_5" "-0 0 1 1534"
+"*trigger_brush_0_plane_6" "-0.93378407 0.35783696 0 75.044144"
+"*trigger_brush_0_plane_7" "0.3588675 0.93338853 0 73.302734"
+"*trigger_brush_0_plane_8" "0.93338954 -0.35886484 0 75.00016"
+"*trigger_brush_0_plane_9" "-0.35842046 -0.93356025 -0 73.376137"
+"*trigger_brush_0_plane_10" "0.3588675 0.93338859 -0 73.302734"
+"*trigger_brush_0_plane_11" "0.9333896 -0.35886484 0 75.000168"
+"*trigger_brush_0_plane_12" "-0.35842046 -0.93356031 -0 73.376129"
+"*trigger_brush_0_plane_13" "-0.40675199 0.91353858 0 104.95502"
+"*trigger_brush_0_plane_14" "-0.91344124 -0.40697074 0 104.91621"
+"*trigger_brush_0_plane_15" "0.91376239 0.40624902 0 104.86583"
+"*trigger_brush_0_plane_16" "0.40646777 -0.91366512 0 104.89292"
+"*trigger_bounds_mins" "-96.314941 -95.370117 -1534"
+"*trigger_bounds_maxs" "96.310059 95.379883 1534"
+}
+{
+"origin" "-5816.81 -7577.13 3646"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 96.314941"
+"*trigger_brush_0_plane_1" "1 0 0 96.310059"
+"*trigger_brush_0_plane_2" "0 -1 0 95.370117"
+"*trigger_brush_0_plane_3" "0 1 0 95.379883"
+"*trigger_brush_0_plane_4" "0 0 -1 1534"
+"*trigger_brush_0_plane_5" "-0 0 1 1534"
+"*trigger_brush_0_plane_6" "-0.93378407 0.35783696 0 75.044144"
+"*trigger_brush_0_plane_7" "0.3588675 0.93338853 0 73.302734"
+"*trigger_brush_0_plane_8" "0.93338954 -0.35886484 0 75.00016"
+"*trigger_brush_0_plane_9" "-0.35842046 -0.93356025 -0 73.376137"
+"*trigger_brush_0_plane_10" "0.3588675 0.93338859 -0 73.302734"
+"*trigger_brush_0_plane_11" "0.9333896 -0.35886484 0 75.000168"
+"*trigger_brush_0_plane_12" "-0.35842046 -0.93356031 -0 73.376129"
+"*trigger_brush_0_plane_13" "-0.40675199 0.91353858 0 104.95502"
+"*trigger_brush_0_plane_14" "-0.91344124 -0.40697074 0 104.91621"
+"*trigger_brush_0_plane_15" "0.91376239 0.40624902 0 104.86583"
+"*trigger_brush_0_plane_16" "0.40646777 -0.91366512 0 104.89292"
+"*trigger_bounds_mins" "-96.314941 -95.370117 -1534"
+"*trigger_bounds_maxs" "96.310059 95.379883 1534"
+}
+{
+"origin" "-7132.81 -7741.13 3646"
+"triggerNearbyRadius" "0"
+"triggerFilterTeamOther" "1"
+"triggerFilterTeamNeutral" "1"
+"triggerFilterTeamMilitia" "1"
+"triggerFilterTeamIMC" "1"
+"triggerFilterTeamBeast" "1"
+"triggerFilterPlayer" "all"
+"triggerFilterPhaseShift" "any"
+"triggerFilterNpcOwnedByPlayer" "any"
+"triggerFilterNpcFlip" "0"
+"triggerFilterNpc" "all"
+"triggerFilterNonCharacter" "0"
+"StartDisabled" "0"
+"spawnflags" "4099"
+"nodmgforce" "1"
+"mobility_3_hard" "1"
+"mobility_2_normal" "1"
+"mobility_1_easy" "1"
+"damageSourceName" "fall"
+"damagemodel" "0"
+"damagecap" "20"
+"damage" "10000"
+"triggerFilterUseNew" "1"
+"classname" "trigger_hurt"
+"*trigger_brush_0_plane_0" "-1 0 0 96.314941"
+"*trigger_brush_0_plane_1" "1 0 0 96.310059"
+"*trigger_brush_0_plane_2" "0 -1 0 95.370117"
+"*trigger_brush_0_plane_3" "0 1 0 95.379883"
+"*trigger_brush_0_plane_4" "0 0 -1 1534"
+"*trigger_brush_0_plane_5" "-0 0 1 1534"
+"*trigger_brush_0_plane_6" "-0.93378407 0.35783696 0 75.044144"
+"*trigger_brush_0_plane_7" "0.3588675 0.93338853 0 73.302734"
+"*trigger_brush_0_plane_8" "0.93338954 -0.35886484 0 75.00016"
+"*trigger_brush_0_plane_9" "-0.35842046 -0.93356025 -0 73.376137"
+"*trigger_brush_0_plane_10" "0.3588675 0.93338859 -0 73.302734"
+"*trigger_brush_0_plane_11" "0.9333896 -0.35886484 0 75.000168"
+"*trigger_brush_0_plane_12" "-0.35842046 -0.93356031 -0 73.376129"
+"*trigger_brush_0_plane_13" "-0.40675199 0.91353858 0 104.95502"
+"*trigger_brush_0_plane_14" "-0.91344124 -0.40697074 0 104.91621"
+"*trigger_brush_0_plane_15" "0.91376239 0.40624902 0 104.86583"
+"*trigger_brush_0_plane_16" "0.40646777 -0.91366512 0 104.89292"
+"*trigger_bounds_mins" "-96.314941 -95.370117 -1534"
+"*trigger_bounds_maxs" "96.310059 95.379883 1534"
+}
+
diff --git a/Northstar.CustomServers/mod/scripts/datatable/battle_chatter.csv b/Northstar.CustomServers/mod/scripts/datatable/battle_chatter.csv
new file mode 100644
index 00000000..74d4c1d9
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/battle_chatter.csv
@@ -0,0 +1,24 @@
+conversationname,priority,debounce
+"bc_pReload",500,45.000000
+"bc_pHardcover",500,10.000000
+"bc_pPulse",500,10.000000
+"bc_pGrapple",500,10.000000
+"bc_pHolo",500,10.000000
+"bc_pCloak",500,10.000000
+"bc_pAmp",500,10.000000
+"bc_pStim",500,10.000000
+"bc_pPhase",500,10.000000
+"bc_pFrag",500,10.000000
+"bc_pSmoke",500,10.000000
+"bc_pArc",500,10.000000
+"bc_pGrav",500,10.000000
+"bc_pSatchel",500,10.000000
+"bc_pFirestar",500,10.000000
+"bc_pGravStar",500,10.000000
+"bc_pAmpedWall",500,10.000000
+"bc_fKilledEnemy",500,10.000000
+"bc_pPulseBladeSpotEnemy",500,10.000000
+"bc_fNearEnemyDmg",500,10.000000
+"bc_fCongratsKill",500,10.000000
+"bc_pBatteryOffer",500,5.000000
+"bc_pIntroChat",500,5.000000 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/battle_chatter_voices.csv b/Northstar.CustomServers/mod/scripts/datatable/battle_chatter_voices.csv
new file mode 100644
index 00000000..32450513
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/battle_chatter_voices.csv
@@ -0,0 +1,19 @@
+isMale,isAndroid,ref
+1,0,"M1"
+1,0,"M2"
+1,0,"M3"
+1,0,"M4"
+1,0,"M5"
+1,0,"M6"
+1,0,"M7"
+1,0,"M8"
+1,0,"M9"
+0,0,"F1"
+0,0,"F2"
+0,0,"F3"
+0,0,"F4"
+0,0,"F5"
+1,1,"MA1"
+1,1,"MA2"
+0,1,"FA1"
+0,1,"FA2" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/burn_meter_rewards.csv b/Northstar.CustomServers/mod/scripts/datatable/burn_meter_rewards.csv
new file mode 100644
index 00000000..41e8376d
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/burn_meter_rewards.csv
@@ -0,0 +1,21 @@
+itemRef,selectable,name,description,image,model,skinIndex,activationCost,rewardAvailableFor,weaponName,extraWeaponMod,activationText,cost
+"burnmeter_maphack",1,"#BURNMETER_MAP_HACK","#BURNMETER_MAP_HACK_DESC","rui/menu/boosts/boost_map_hack","models/weapons_r2/burn_card/burn_card.mdl",9,0.700000,"PILOT_AND_TITAN","mp_ability_burncardweapon","burnmeter_maphack","#BURNMETER_MAP_HACK_DESC",125
+"burnmeter_amped_weapons",1,"#BURNMETER_AMPED_WEAPONS","#BURNMETER_AMPED_WEAPONS_DESC","rui/menu/boosts/boost_amped_weapons","models/weapons_r2/burn_card/burn_card.mdl",0,0.800000,"PILOT_AND_TITAN","mp_ability_burncardweapon","burnmeter_amped_weapons","#BURNMETER_AMPED_WEAPONS_DESC",125
+"burnmeter_ticks",1,"#BURNMETER_TICKS","#BURNMETER_TICKS_DESC","rui/menu/boosts/boost_ticks","models/weapons_r2/burn_card/burn_card.mdl",5,0.650000,"PILOT_ONLY","mp_weapon_frag_drone","","#WPN_FRAG_DRONE_LONGDESC",125
+"burnmeter_random_foil",1,"#BURNMETER_RANDOM_FOIL","#BURNMETER_RANDOM_FOIL_DESC","rui/menu/boosts/boost_random","models/weapons_r2/burn_card/burn_card.mdl",11,0.500000,"PILOT_AND_TITAN","mp_ability_burncardweapon","burnmeter_random_foil","#BURNMETER_RANDOM_FOIL_DESC",125
+"burnmeter_ap_turret_weapon",1,"#BURNMETER_AP_TURRETWEAPON","#BURNMETER_AP_TURRETWEAPON_DESC","rui/menu/boosts/boost_antipersonnel_sentry","models/weapons_r2/burn_card/burn_card.mdl",6,0.720000,"PILOT_ONLY","mp_ability_turretweapon","burnmeter_ap_turret_weapon","#BURNMETER_AP_TURRETWEAPON_DESC_BOOST_ACTIVATION_TEXT",125
+"burnmeter_phase_rewind",1,"#WPN_REWIND","#WPN_REWIND_LONGDESC","rui/menu/boosts/boost_phase_rewind","models/weapons_r2/burn_card/burn_card.mdl",8,0.250000,"PILOT_ONLY","mp_ability_burncardweapon","burnmeter_phase_rewind","#WPN_REWIND_LONGDESC",125
+"burnmeter_at_turret_weapon",1,"#BURNMETER_AT_TURRETWEAPON","#BURNMETER_AT_TURRETWEAPON_DESC","rui/menu/boosts/boost_antititan_sentry","models/weapons_r2/burn_card/burn_card.mdl",7,0.350000,"PILOT_ONLY","mp_ability_turretweapon","burnmeter_at_turret_weapon","#BURNMETER_AT_TURRETWEAPON_DESC_BOOST_ACTIVATION_TEXT",125
+"burnmeter_holopilot_nova",1,"#WPN_HOLOPILOT_NOVA","#WPN_HOLOPILOT_NOVA_DESC_BOOST_ACTIVATION_TEXT","rui/menu/boosts/boost_holo_pilots","models/weapons_r2/burn_card/burn_card.mdl",10,0.400000,"PILOT_ONLY","mp_ability_holopilot_nova","","#WPN_HOLOPILOT_NOVA_DESC",125
+"burnmeter_emergency_battery",1,"#BURNMETER_EMERGENCY_BATTERY","#BURNMETER_EMERGENCY_BATTERY_DESC","rui/menu/boosts/boost_battery","models/weapons_r2/burn_card/burn_card.mdl",1,0.800000,"PILOT_ONLY","mp_ability_burncardweapon","burnmeter_emergency_battery","#BURNMETER_AT_BATTERY_BOOST_ACTIVATION_TEXT",125
+"burnmeter_smart_pistol",1,"#WPN_SMART_PISTOL","#WPN_SMART_PISTOL_LONGDESC","rui/menu/boosts/boost_smart_pistol","models/weapons_r2/burn_card/burn_card.mdl",2,0.600000,"PILOT_ONLY","mp_ability_burncardweapon","burnmeter_smart_pistol","#WPN_SMART_PISTOL_BOOST_ACTIVATION_TEXT",125
+"burnmeter_radar_jammer",1,"#BURNMETER_RADAR_JAMMER","#BURNMETER_RADAR_JAMMER_DESC","rui/menu/boosts/boost_radar_jammer","models/weapons_r2/burn_card/burn_card.mdl",3,0.400000,"PILOT_ONLY","mp_ability_burncardweapon","burnmeter_radar_jammer","#BURNMETER_RADAR_JAMMER_DESC",125
+"burnmeter_hard_cover",1,"#WPN_HARD_COVER","#WPN_HARD_COVER_DESC","rui/menu/boosts/boost_shield","models/weapons_r2/burn_card/burn_card.mdl",4,0.200000,"PILOT_ONLY","mp_weapon_hard_cover","","#WPN_HARD_COVER_DESC",125
+"burnmeter_nuke_titan",0,"#WPN_NUKE_TITAN","#WPN_NUKE_TITAN_DESC","rui/menu/boosts/boost_nuke","models/weapons_r2/burn_card/burn_card.mdl",9,0.200000,"SPECIAL_PLAYERS_ONLY","mp_ability_burncardweapon","burnmeter_nuke_titan","#WPN_NUKE_TITAN_DESC",125
+"burnmeter_harvester_shield",0,"#BURNMETER_HARVESTER_SHIELD","#BURNMETER_HARVESTER_SHIELD_DESC","rui/menu/boosts/boost_harvester","models/weapons_r2/burn_card/burn_card.mdl",9,0.200000,"SPECIAL_PLAYERS_ONLY","mp_ability_burncardweapon","burnmeter_harvester_shield","#BURNMETER_HARVESTER_SHIELD_DESC",125
+"burnmeter_arc_trap",0,"#WPN_ARC_TRAP","#WPN_ARC_TRAP_DESC","rui/menu/boosts/boost_arc_trap","models/weapons_r2/burn_card/burn_card.mdl",9,0.200000,"PILOT_ONLY","mp_weapon_arc_trap","","#WPN_ARC_TRAP_DESC",125
+"burnmeter_ap_turret_weapon_infinite",0,"#BURNMETER_AP_TURRETWEAPON_INF","#BURNMETER_AP_TURRETWEAPON_INF_DESC","rui/menu/boosts/boost_antipersonnel_sentry","models/weapons_r2/burn_card/burn_card.mdl",6,0.720000,"PILOT_ONLY","mp_ability_turretweapon","burnmeter_ap_turret_weapon_inf","#BURNMETER_AP_TURRETWEAPON_INF_DESC",125
+"burnmeter_at_turret_weapon_infinite",0,"#BURNMETER_AT_TURRETWEAPON_INF","#BURNMETER_AT_TURRETWEAPON_INF_DESC","rui/menu/boosts/boost_antititan_sentry","models/weapons_r2/burn_card/burn_card.mdl",7,0.350000,"PILOT_ONLY","mp_ability_turretweapon","burnmeter_at_turret_weapon_inf","#BURNMETER_AT_TURRETWEAPON_INF_DESC",125
+"burnmeter_rodeo_grenade",0,"#BURNMETER_SUPER_RODEO","#BURNMETER_SUPER_RODEO_DESC","rui/menu/boosts/boost_core_grenade","models/weapons_r2/burn_card/burn_card.mdl",9,0.350000,"PILOT_ONLY","mp_ability_burncardweapon","burnmeter_rodeo_grenade","#BURNMETER_SUPER_RODEO_DESC",125
+"burnmeter_instant_battery",0,"#BURNMETER_INSTANT_BATTERY","#BURNMETER_INSTANT_BATTERY_DESC","rui/menu/boosts/boost_battery","models/weapons_r2/burn_card/burn_card.mdl",1,0.800000,"PILOT_ONLY","mp_ability_burncardweapon","burnmeter_emergency_battery","#BURNMETER_INSTANT_BATTERY_DESC",125
+"burnmeter_amped_weapons_permanent",0,"#BURNMETER_AMPED_WEAPONS_PERMANENT","#BURNMETER_AMPED_WEAPONS_PERMANENT_DESC","rui/menu/boosts/boost_amped_weapons","models/weapons_r2/burn_card/burn_card.mdl",0,0.800000,"PILOT_AND_TITAN","mp_ability_burncardweapon","burnmeter_amped_weapons","#BURNMETER_AMPED_WEAPONS_PERMANENT_DESC",125 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/burn_meter_store.csv b/Northstar.CustomServers/mod/scripts/datatable/burn_meter_store.csv
new file mode 100644
index 00000000..2f05bfc8
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/burn_meter_store.csv
@@ -0,0 +1,21 @@
+itemRef,modes,cost,extraDesc,autoActivate,storeIcon,lockedStoreIcon,extraDescFail
+"burnmeter_maphack","",0,"",0,"rui/menu/boosts/boost_icon_map_hack","rui/menu/boosts/boost_icon_map_hack",""
+"burnmeter_amped_weapons","",100,"#BOOST_STORE_INSTANT",1,"rui/menu/boosts/boost_icon_amped","rui/menu/boosts/boost_icon_amped","#BOOST_STORE_AMPED_FAIL"
+"burnmeter_ticks","",50,"",0,"rui/menu/boosts/boost_icon_tick","rui/menu/boosts/boost_icon_tick",""
+"burnmeter_random_foil","",0,"",0,"rui/menu/boosts/boost_icon_random","rui/menu/boosts/boost_icon_random",""
+"burnmeter_ap_turret_weapon","",0,"",0,"rui/menu/boosts/boost_icon_personel_sentry","rui/menu/boosts/boost_icon_personel_sentry",""
+"burnmeter_phase_rewind","",0,"",0,"rui/menu/boosts/boost_icon_phase_rewind","rui/menu/boosts/boost_icon_phase_rewind",""
+"burnmeter_at_turret_weapon","",0,"",0,"rui/menu/boosts/boost_icon_titan_sentry","rui/menu/boosts/boost_icon_titan_sentry",""
+"burnmeter_holopilot_nova","",0,"",0,"rui/menu/boosts/boost_icon_holopilot","rui/menu/boosts/boost_icon_holopilot",""
+"burnmeter_emergency_battery","",0,"",0,"rui/menu/boosts/boost_icon_battery","rui/menu/boosts/boost_icon_battery",""
+"burnmeter_smart_pistol","",0,"",1,"rui/menu/boosts/boost_icon_smart_pistol","rui/menu/boosts/boost_icon_smart_pistol",""
+"burnmeter_radar_jammer","",0,"",0,"rui/menu/boosts/boost_icon_radar_jam","rui/menu/boosts/boost_icon_radar_jam",""
+"burnmeter_hard_cover","",0,"",0,"rui/menu/boosts/boost_icon_shield","rui/menu/boosts/boost_icon_shield",""
+"burnmeter_nuke_titan","",0,"",0,"rui/menu/boosts/boost_icon_nuke","rui/menu/boosts/boost_icon_nuke",""
+"burnmeter_harvester_shield","fd",1200,"",1,"rui/menu/boosts/boost_icon_harvester_shield","rui/menu/boosts/locked/boost_icon_harvester_shield","#BOOST_STORE_HARVESTER_SHIELD_FAIL"
+"burnmeter_arc_trap","fd",650,"",0,"rui/menu/boosts/boost_icon_arc_trap","rui/menu/boosts/locked/boost_icon_arc_trap","#BOOST_STORE_ARC_TRAP_FAIL"
+"burnmeter_at_turret_weapon_infinite","",1200,"#BOOST_STORE_TURRET_LIMIT",0,"rui/menu/boosts/boost_icon_titan_sentry","rui/menu/boosts/boost_icon_titan_sentry",""
+"burnmeter_ap_turret_weapon_infinite","fd",1200,"#BOOST_STORE_TURRET_LIMIT",0,"rui/menu/boosts/boost_icon_personel_sentry","rui/menu/boosts/locked/boost_icon_personel_sentry","#BOOST_STORE_TURRET_FAIL"
+"burnmeter_rodeo_grenade","fd",500,"",1,"rui/menu/boosts/boost_icon_core_overload","rui/menu/boosts/locked/boost_icon_core_overload","#BOOST_STORE_RODEO_GRENADE_FAIL"
+"burnmeter_instant_battery","fd",400,"#BOOST_STORE_INSTANT",1,"rui/menu/boosts/boost_icon_battery_amped","rui/menu/boosts/locked/boost_icon_battery_amped","#BOOST_STORE_BATTERY_FAIL"
+"burnmeter_amped_weapons_permanent","fd",100,"#BOOST_STORE_INSTANT",1,"rui/menu/boosts/boost_icon_amped","rui/menu/boosts/locked/boost_icon_amped","#BOOST_STORE_AMPED_FAIL" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/caller_ids_mp.csv b/Northstar.CustomServers/mod/scripts/datatable/caller_ids_mp.csv
new file mode 100644
index 00000000..dcb3f5ab
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/caller_ids_mp.csv
@@ -0,0 +1,2 @@
+title,image
+"#FACTION_LEADER_NAME_MARVIN","rui/hud/caller_ids/caller_id_36" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/calling_cards.csv b/Northstar.CustomServers/mod/scripts/datatable/calling_cards.csv
new file mode 100644
index 00000000..998e644e
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/calling_cards.csv
@@ -0,0 +1,491 @@
+itemRef,name,layoutType,image,cost
+"callsign_01_col","#BANNER_CALLSIGN1",0,"rui/callsigns/callsign_01_col",0
+"callsign_02_col","#BANNER_CALLSIGN2",0,"rui/callsigns/callsign_02_col",100
+"callsign_03_col","#BANNER_CALLSIGN3",0,"rui/callsigns/callsign_03_col",0
+"callsign_04_col","#BANNER_CALLSIGN4",0,"rui/callsigns/callsign_04_col",0
+"callsign_05_col","#BANNER_CALLSIGN5",0,"rui/callsigns/callsign_05_col",0
+"callsign_06_col","#BANNER_CALLSIGN6",0,"rui/callsigns/callsign_06_col",0
+"callsign_07_col","#BANNER_CALLSIGN7",0,"rui/callsigns/callsign_07_col",0
+"callsign_08_col","#BANNER_CALLSIGN8",0,"rui/callsigns/callsign_08_col",100
+"callsign_09_col","#BANNER_CALLSIGN9",0,"rui/callsigns/callsign_09_col",0
+"callsign_10_col","#BANNER_CALLSIGN10",0,"rui/callsigns/callsign_10_col",100
+"callsign_11_col","#BANNER_CALLSIGN11",0,"rui/callsigns/callsign_11_col",0
+"callsign_12_col","#BANNER_CALLSIGN12",0,"rui/callsigns/callsign_12_col",100
+"callsign_13_col","#BANNER_CALLSIGN13",0,"rui/callsigns/callsign_13_col",0
+"callsign_14_col","#BANNER_CALLSIGN14",0,"rui/callsigns/callsign_14_col",0
+"callsign_15_col","#BANNER_CALLSIGN15",0,"rui/callsigns/callsign_15_col",0
+"callsign_16_col","#BANNER_CALLSIGN16",0,"rui/callsigns/callsign_16_col",0
+"callsign_17_col","#BANNER_CALLSIGN17",0,"rui/callsigns/callsign_17_col",100
+"callsign_18_col","#BANNER_CALLSIGN18",0,"rui/callsigns/callsign_18_col",0
+"callsign_19_col","#BANNER_CALLSIGN19",0,"rui/callsigns/callsign_19_col",0
+"callsign_20_col","#BANNER_CALLSIGN20",0,"rui/callsigns/callsign_20_col",100
+"callsign_21_col","#BANNER_CALLSIGN21",0,"rui/callsigns/callsign_21_col",0
+"callsign_22_col","#BANNER_CALLSIGN22",0,"rui/callsigns/callsign_22_col",0
+"callsign_23_col","#BANNER_CALLSIGN23",0,"rui/callsigns/callsign_23_col",0
+"callsign_24_col","#BANNER_CALLSIGN24",0,"rui/callsigns/callsign_24_col",0
+"callsign_25_col","#BANNER_CALLSIGN25",0,"rui/callsigns/callsign_25_col",100
+"callsign_26_col","#BANNER_CALLSIGN26",0,"rui/callsigns/callsign_26_col",0
+"callsign_27_col","#BANNER_CALLSIGN27",0,"rui/callsigns/callsign_27_col",0
+"callsign_28_col","#BANNER_CALLSIGN28",0,"rui/callsigns/callsign_28_col",100
+"callsign_29_col","#BANNER_CALLSIGN29",0,"rui/callsigns/callsign_29_col",0
+"callsign_30_col","#BANNER_CALLSIGN30",0,"rui/callsigns/callsign_30_col",0
+"callsign_31_col","#BANNER_CALLSIGN31",0,"rui/callsigns/callsign_31_col",0
+"callsign_32_col","#BANNER_CALLSIGN32",0,"rui/callsigns/callsign_32_col",100
+"callsign_34_col","#BANNER_CALLSIGN34",0,"rui/callsigns/callsign_34_col",0
+"callsign_35_col","#BANNER_CALLSIGN35",0,"rui/callsigns/callsign_35_col",0
+"callsign_36_col","#BANNER_CALLSIGN36",0,"rui/callsigns/callsign_36_col",0
+"callsign_37_col","#BANNER_CALLSIGN37",0,"rui/callsigns/callsign_37_col",0
+"callsign_39_col","#BANNER_CALLSIGN39",0,"rui/callsigns/callsign_39_col",0
+"callsign_40_col","#BANNER_CALLSIGN40",0,"rui/callsigns/callsign_40_col",0
+"callsign_41_col","#BANNER_CALLSIGN41",0,"rui/callsigns/callsign_41_col",0
+"callsign_42_col","#BANNER_CALLSIGN42",0,"rui/callsigns/callsign_42_col",0
+"callsign_43_col","#BANNER_CALLSIGN43",0,"rui/callsigns/callsign_43_col",100
+"callsign_44_col","#BANNER_CALLSIGN44",0,"rui/callsigns/callsign_44_col",100
+"callsign_45_col","#BANNER_CALLSIGN45",0,"rui/callsigns/callsign_45_col",0
+"callsign_46_col","#BANNER_CALLSIGN46",0,"rui/callsigns/callsign_46_col",0
+"callsign_47_col","#BANNER_CALLSIGN47",0,"rui/callsigns/callsign_47_col",0
+"callsign_48_col","#BANNER_CALLSIGN48",0,"rui/callsigns/callsign_48_col",0
+"callsign_49_col","#BANNER_CALLSIGN49",0,"rui/callsigns/callsign_49_col",100
+"callsign_50_col","#BANNER_CALLSIGN50",0,"rui/callsigns/callsign_50_col",100
+"callsign_51_col","#BANNER_CALLSIGN51",0,"rui/callsigns/callsign_51_col",0
+"callsign_52_col","#BANNER_CALLSIGN52",0,"rui/callsigns/callsign_52_col",100
+"callsign_53_col","#BANNER_CALLSIGN53",0,"rui/callsigns/callsign_53_col",0
+"callsign_54_col","#BANNER_CALLSIGN54",0,"rui/callsigns/callsign_54_col",100
+"callsign_55_col","#BANNER_CALLSIGN55",0,"rui/callsigns/callsign_55_col",0
+"callsign_56_col","#BANNER_CALLSIGN56",0,"rui/callsigns/callsign_56_col",100
+"callsign_57_col","#BANNER_CALLSIGN57",0,"rui/callsigns/callsign_57_col",0
+"callsign_58_col","#BANNER_CALLSIGN58",0,"rui/callsigns/callsign_58_col",100
+"callsign_59_col","#BANNER_CALLSIGN59",0,"rui/callsigns/callsign_59_col",0
+"callsign_60_col","#BANNER_CALLSIGN60",0,"rui/callsigns/callsign_60_col",100
+"callsign_61_col","#BANNER_CALLSIGN61",0,"rui/callsigns/callsign_61_col",100
+"callsign_62_col","#BANNER_CALLSIGN62",0,"rui/callsigns/callsign_62_col",100
+"callsign_63_col","#BANNER_CALLSIGN63",0,"rui/callsigns/callsign_63_col",100
+"callsign_64_col","#BANNER_CALLSIGN64",0,"rui/callsigns/callsign_64_col",100
+"callsign_66_col","#BANNER_CALLSIGN66",0,"rui/callsigns/callsign_66_col",0
+"callsign_67_col","#BANNER_CALLSIGN67",0,"rui/callsigns/callsign_67_col",0
+"callsign_68_col","#BANNER_CALLSIGN68",0,"rui/callsigns/callsign_68_col",0
+"callsign_69_col","#BANNER_CALLSIGN69",0,"rui/callsigns/callsign_69_col",0
+"callsign_70_col","#BANNER_CALLSIGN70",0,"rui/callsigns/callsign_70_col",0
+"callsign_71_col","#BANNER_CALLSIGN71",0,"rui/callsigns/callsign_71_col",100
+"callsign_72_col","#BANNER_CALLSIGN72",0,"rui/callsigns/callsign_72_col",100
+"callsign_73_col","#BANNER_CALLSIGN73",0,"rui/callsigns/callsign_73_col",100
+"callsign_74_col","#BANNER_CALLSIGN74",0,"rui/callsigns/callsign_74_col",100
+"callsign_75_col","#BANNER_CALLSIGN75",0,"rui/callsigns/callsign_75_col",0
+"callsign_76_col","#BANNER_CALLSIGN76",0,"rui/callsigns/callsign_76_col",0
+"callsign_77_col","#BANNER_CALLSIGN77",0,"rui/callsigns/callsign_77_col",100
+"callsign_78_col","#BANNER_CALLSIGN78",0,"rui/callsigns/callsign_78_col",0
+"callsign_79_col","#BANNER_CALLSIGN79",0,"rui/callsigns/callsign_79_col",0
+"callsign_80_col","#BANNER_CALLSIGN80",0,"rui/callsigns/callsign_80_col",100
+"callsign_81_col","#BANNER_CALLSIGN81",0,"rui/callsigns/callsign_81_col",100
+"callsign_82_col","#BANNER_CALLSIGN82",0,"rui/callsigns/callsign_82_col",100
+"callsign_83_col","#BANNER_CALLSIGN83",0,"rui/callsigns/callsign_83_col",100
+"callsign_84_col","#BANNER_CALLSIGN84",0,"rui/callsigns/callsign_84_col",100
+"callsign_85_col","#BANNER_CALLSIGN85",0,"rui/callsigns/callsign_85_col",100
+"callsign_86_col","#BANNER_CALLSIGN86",0,"rui/callsigns/callsign_86_col",100
+"callsign_87_col","#BANNER_CALLSIGN87",0,"rui/callsigns/callsign_87_col",100
+"callsign_88_col","#BANNER_CALLSIGN88",0,"rui/callsigns/callsign_88_col",100
+"callsign_89_col","#BANNER_CALLSIGN89",0,"rui/callsigns/callsign_89_col",100
+"callsign_90_col","#BANNER_CALLSIGN90",0,"rui/callsigns/callsign_90_col",100
+"callsign_91_col","#BANNER_CALLSIGN91",0,"rui/callsigns/callsign_91_col",100
+"callsign_92_col","#BANNER_CALLSIGN92",0,"rui/callsigns/callsign_92_col",0
+"callsign_93_col","#BANNER_CALLSIGN93",0,"rui/callsigns/callsign_93_col",100
+"callsign_94_col","#BANNER_CALLSIGN94",0,"rui/callsigns/callsign_94_col",0
+"callsign_95_col","#BANNER_CALLSIGN95",0,"rui/callsigns/callsign_95_col",100
+"callsign_96_col","#BANNER_CALLSIGN96",0,"rui/callsigns/callsign_96_col",0
+"callsign_97_col","#BANNER_CALLSIGN97",0,"rui/callsigns/callsign_97_col",0
+"callsign_98_col","#BANNER_CALLSIGN98",0,"rui/callsigns/callsign_98_col",100
+"callsign_99_col","#BANNER_CALLSIGN99",0,"rui/callsigns/callsign_99_col",0
+"callsign_100_col","#BANNER_CALLSIGN100",0,"rui/callsigns/callsign_100_col",0
+"callsign_101_col","#BANNER_CALLSIGN101",0,"rui/callsigns/callsign_101_col",100
+"callsign_102_col","#BANNER_CALLSIGN102",0,"rui/callsigns/callsign_102_col",100
+"callsign_103_col","#BANNER_CALLSIGN103",0,"rui/callsigns/callsign_103_col",0
+"callsign_104_col","#BANNER_CALLSIGN104",0,"rui/callsigns/callsign_104_col",0
+"callsign_02_col_prism","#BANNER_PRISM_CALLSIGN2",1,"rui/callsigns/callsign_02_col",250
+"callsign_08_col_prism","#BANNER_PRISM_CALLSIGN8",1,"rui/callsigns/callsign_08_col",250
+"callsign_10_col_prism","#BANNER_PRISM_CALLSIGN10",1,"rui/callsigns/callsign_10_col",250
+"callsign_12_col_prism","#BANNER_PRISM_CALLSIGN12",1,"rui/callsigns/callsign_12_col",250
+"callsign_17_col_prism","#BANNER_PRISM_CALLSIGN17",1,"rui/callsigns/callsign_17_col",250
+"callsign_20_col_prism","#BANNER_PRISM_CALLSIGN20",1,"rui/callsigns/callsign_20_col",250
+"callsign_25_col_prism","#BANNER_PRISM_CALLSIGN25",1,"rui/callsigns/callsign_25_col",250
+"callsign_28_col_prism","#BANNER_PRISM_CALLSIGN28",1,"rui/callsigns/callsign_28_col",250
+"callsign_32_col_prism","#BANNER_PRISM_CALLSIGN32",1,"rui/callsigns/callsign_32_col",250
+"callsign_43_col_prism","#BANNER_PRISM_CALLSIGN43",1,"rui/callsigns/callsign_43_col",250
+"callsign_44_col_prism","#BANNER_PRISM_CALLSIGN44",1,"rui/callsigns/callsign_44_col",250
+"callsign_49_col_prism","#BANNER_PRISM_CALLSIGN49",1,"rui/callsigns/callsign_49_col",250
+"callsign_50_col_prism","#BANNER_PRISM_CALLSIGN50",1,"rui/callsigns/callsign_50_col",250
+"callsign_52_col_prism","#BANNER_PRISM_CALLSIGN52",1,"rui/callsigns/callsign_52_col",250
+"callsign_54_col_prism","#BANNER_PRISM_CALLSIGN54",1,"rui/callsigns/callsign_54_col",250
+"callsign_56_col_prism","#BANNER_PRISM_CALLSIGN56",1,"rui/callsigns/callsign_56_col",250
+"callsign_58_col_prism","#BANNER_PRISM_CALLSIGN58",1,"rui/callsigns/callsign_58_col",250
+"callsign_60_col_prism","#BANNER_PRISM_CALLSIGN60",1,"rui/callsigns/callsign_60_col",250
+"callsign_61_col_prism","#BANNER_PRISM_CALLSIGN61",1,"rui/callsigns/callsign_61_col",250
+"callsign_62_col_prism","#BANNER_PRISM_CALLSIGN62",1,"rui/callsigns/callsign_62_col",250
+"callsign_63_col_prism","#BANNER_PRISM_CALLSIGN63",1,"rui/callsigns/callsign_63_col",250
+"callsign_64_col_prism","#BANNER_PRISM_CALLSIGN64",1,"rui/callsigns/callsign_64_col",250
+"callsign_71_col_prism","#BANNER_PRISM_CALLSIGN71",1,"rui/callsigns/callsign_71_col",250
+"callsign_72_col_prism","#BANNER_PRISM_CALLSIGN72",1,"rui/callsigns/callsign_72_col",250
+"callsign_73_col_prism","#BANNER_PRISM_CALLSIGN73",1,"rui/callsigns/callsign_73_col",250
+"callsign_74_col_prism","#BANNER_PRISM_CALLSIGN74",1,"rui/callsigns/callsign_74_col",250
+"callsign_77_col_prism","#BANNER_PRISM_CALLSIGN77",1,"rui/callsigns/callsign_77_col",250
+"callsign_80_col_prism","#BANNER_PRISM_CALLSIGN80",1,"rui/callsigns/callsign_80_col",250
+"callsign_81_col_prism","#BANNER_PRISM_CALLSIGN81",1,"rui/callsigns/callsign_81_col",250
+"callsign_82_col_prism","#BANNER_PRISM_CALLSIGN82",1,"rui/callsigns/callsign_82_col",250
+"callsign_83_col_prism","#BANNER_PRISM_CALLSIGN83",1,"rui/callsigns/callsign_83_col",250
+"callsign_84_col_prism","#BANNER_PRISM_CALLSIGN84",1,"rui/callsigns/callsign_84_col",250
+"callsign_85_col_prism","#BANNER_PRISM_CALLSIGN85",1,"rui/callsigns/callsign_85_col",250
+"callsign_86_col_prism","#BANNER_PRISM_CALLSIGN86",1,"rui/callsigns/callsign_86_col",250
+"callsign_87_col_prism","#BANNER_PRISM_CALLSIGN87",1,"rui/callsigns/callsign_87_col",250
+"callsign_88_col_prism","#BANNER_PRISM_CALLSIGN88",1,"rui/callsigns/callsign_88_col",250
+"callsign_89_col_prism","#BANNER_PRISM_CALLSIGN89",1,"rui/callsigns/callsign_89_col",250
+"callsign_90_col_prism","#BANNER_PRISM_CALLSIGN90",1,"rui/callsigns/callsign_90_col",250
+"callsign_91_col_prism","#BANNER_PRISM_CALLSIGN91",1,"rui/callsigns/callsign_91_col",250
+"callsign_93_col_prism","#BANNER_PRISM_CALLSIGN93",1,"rui/callsigns/callsign_93_col",250
+"callsign_95_col_prism","#BANNER_PRISM_CALLSIGN95",1,"rui/callsigns/callsign_95_col",250
+"callsign_98_col_prism","#BANNER_PRISM_CALLSIGN98",1,"rui/callsigns/callsign_98_col",250
+"callsign_101_col_prism","#BANNER_PRISM_CALLSIGN101",1,"rui/callsigns/callsign_101_col",250
+"callsign_102_col_prism","#BANNER_PRISM_CALLSIGN102",1,"rui/callsigns/callsign_102_col",250
+"callsign_16_col_gold","#BANNER_GOLD_CALLSIGN16",2,"rui/callsigns/callsign_16_col",0
+"callsign_01_col_gold","#BANNER_GOLD_CALLSIGN1",2,"rui/callsigns/callsign_01_col",0
+"callsign_03_col_gold","#BANNER_GOLD_CALLSIGN3",2,"rui/callsigns/callsign_03_col",0
+"callsign_04_col_gold","#BANNER_GOLD_CALLSIGN4",2,"rui/callsigns/callsign_04_col",0
+"callsign_05_col_gold","#BANNER_GOLD_CALLSIGN5",2,"rui/callsigns/callsign_05_col",0
+"callsign_06_col_gold","#BANNER_GOLD_CALLSIGN6",2,"rui/callsigns/callsign_06_col",0
+"callsign_07_col_gold","#BANNER_GOLD_CALLSIGN7",2,"rui/callsigns/callsign_07_col",0
+"callsign_09_col_gold","#BANNER_GOLD_CALLSIGN9",2,"rui/callsigns/callsign_09_col",0
+"callsign_11_col_gold","#BANNER_GOLD_CALLSIGN11",2,"rui/callsigns/callsign_11_col",0
+"callsign_13_col_gold","#BANNER_GOLD_CALLSIGN13",2,"rui/callsigns/callsign_13_col",0
+"callsign_14_col_gold","#BANNER_GOLD_CALLSIGN14",2,"rui/callsigns/callsign_14_col",0
+"callsign_15_col_gold","#BANNER_GOLD_CALLSIGN15",2,"rui/callsigns/callsign_15_col",0
+"callsign_18_col_gold","#BANNER_GOLD_CALLSIGN18",2,"rui/callsigns/callsign_18_col",0
+"callsign_19_col_gold","#BANNER_GOLD_CALLSIGN19",2,"rui/callsigns/callsign_19_col",0
+"callsign_21_col_gold","#BANNER_GOLD_CALLSIGN21",2,"rui/callsigns/callsign_21_col",0
+"callsign_22_col_gold","#BANNER_GOLD_CALLSIGN22",2,"rui/callsigns/callsign_22_col",0
+"callsign_23_col_gold","#BANNER_GOLD_CALLSIGN23",2,"rui/callsigns/callsign_23_col",0
+"callsign_24_col_gold","#BANNER_GOLD_CALLSIGN24",2,"rui/callsigns/callsign_24_col",0
+"callsign_26_col_gold","#BANNER_GOLD_CALLSIGN26",2,"rui/callsigns/callsign_26_col",0
+"callsign_27_col_gold","#BANNER_GOLD_CALLSIGN27",2,"rui/callsigns/callsign_27_col",0
+"callsign_29_col_gold","#BANNER_GOLD_CALLSIGN29",2,"rui/callsigns/callsign_29_col",0
+"callsign_30_col_gold","#BANNER_GOLD_CALLSIGN30",2,"rui/callsigns/callsign_30_col",0
+"callsign_31_col_gold","#BANNER_GOLD_CALLSIGN31",2,"rui/callsigns/callsign_31_col",0
+"callsign_33_col_gold","#BANNER_GOLD_CALLSIGN33",2,"rui/callsigns/callsign_33_col",0
+"callsign_34_col_gold","#BANNER_GOLD_CALLSIGN34",2,"rui/callsigns/callsign_34_col",0
+"callsign_35_col_gold","#BANNER_GOLD_CALLSIGN35",2,"rui/callsigns/callsign_35_col",0
+"callsign_36_col_gold","#BANNER_GOLD_CALLSIGN36",2,"rui/callsigns/callsign_36_col",0
+"callsign_37_col_gold","#BANNER_GOLD_CALLSIGN37",2,"rui/callsigns/callsign_37_col",0
+"callsign_38_col_gold","#BANNER_GOLD_CALLSIGN38",2,"rui/callsigns/callsign_38_col",0
+"callsign_39_col_gold","#BANNER_GOLD_CALLSIGN39",2,"rui/callsigns/callsign_39_col",0
+"callsign_40_col_gold","#BANNER_GOLD_CALLSIGN40",2,"rui/callsigns/callsign_40_col",0
+"callsign_41_col_gold","#BANNER_GOLD_CALLSIGN41",2,"rui/callsigns/callsign_41_col",0
+"callsign_42_col_gold","#BANNER_GOLD_CALLSIGN42",2,"rui/callsigns/callsign_42_col",0
+"callsign_45_col_gold","#BANNER_GOLD_CALLSIGN45",2,"rui/callsigns/callsign_45_col",0
+"callsign_46_col_gold","#BANNER_GOLD_CALLSIGN46",2,"rui/callsigns/callsign_46_col",0
+"callsign_47_col_gold","#BANNER_GOLD_CALLSIGN47",2,"rui/callsigns/callsign_47_col",0
+"callsign_48_col_gold","#BANNER_GOLD_CALLSIGN48",2,"rui/callsigns/callsign_48_col",0
+"callsign_51_col_gold","#BANNER_GOLD_CALLSIGN51",2,"rui/callsigns/callsign_51_col",0
+"callsign_53_col_gold","#BANNER_GOLD_CALLSIGN53",2,"rui/callsigns/callsign_53_col",0
+"callsign_55_col_gold","#BANNER_GOLD_CALLSIGN55",2,"rui/callsigns/callsign_55_col",0
+"callsign_57_col_gold","#BANNER_GOLD_CALLSIGN57",2,"rui/callsigns/callsign_57_col",0
+"callsign_59_col_gold","#BANNER_GOLD_CALLSIGN59",2,"rui/callsigns/callsign_59_col",0
+"callsign_65_col_gold","#BANNER_GOLD_CALLSIGN65",2,"rui/callsigns/callsign_65_col",0
+"callsign_66_col_gold","#BANNER_GOLD_CALLSIGN66",2,"rui/callsigns/callsign_66_col",0
+"callsign_67_col_gold","#BANNER_GOLD_CALLSIGN67",2,"rui/callsigns/callsign_67_col",0
+"callsign_68_col_gold","#BANNER_GOLD_CALLSIGN68",2,"rui/callsigns/callsign_68_col",0
+"callsign_69_col_gold","#BANNER_GOLD_CALLSIGN69",2,"rui/callsigns/callsign_69_col",0
+"callsign_70_col_gold","#BANNER_GOLD_CALLSIGN70",2,"rui/callsigns/callsign_70_col",0
+"callsign_71_col_gold","#BANNER_GOLD_CALLSIGN71",2,"rui/callsigns/callsign_71_col",0
+"callsign_75_col_gold","#BANNER_GOLD_CALLSIGN75",2,"rui/callsigns/callsign_75_col",0
+"callsign_76_col_gold","#BANNER_GOLD_CALLSIGN76",2,"rui/callsigns/callsign_76_col",0
+"callsign_78_col_gold","#BANNER_GOLD_CALLSIGN78",2,"rui/callsigns/callsign_78_col",0
+"callsign_79_col_gold","#BANNER_GOLD_CALLSIGN79",2,"rui/callsigns/callsign_79_col",0
+"callsign_92_col_gold","#BANNER_GOLD_CALLSIGN92",2,"rui/callsigns/callsign_92_col",0
+"callsign_94_col_gold","#BANNER_GOLD_CALLSIGN94",2,"rui/callsigns/callsign_94_col",0
+"callsign_96_col_gold","#BANNER_GOLD_CALLSIGN96",2,"rui/callsigns/callsign_96_col",0
+"callsign_97_col_gold","#BANNER_GOLD_CALLSIGN97",2,"rui/callsigns/callsign_97_col",0
+"callsign_99_col_gold","#BANNER_GOLD_CALLSIGN99",2,"rui/callsigns/callsign_99_col",0
+"callsign_100_col_gold","#BANNER_GOLD_CALLSIGN100",2,"rui/callsigns/callsign_100_col",0
+"callsign_103_col_gold","#BANNER_GOLD_CALLSIGN103",2,"rui/callsigns/callsign_103_col",0
+"callsign_104_col_gold","#BANNER_GOLD_CALLSIGN104",2,"rui/callsigns/callsign_104_col",0
+"callsign_105_col_gold","#BANNER_GOLD_CALLSIGN105",2,"rui/callsigns/callsign_105_col",0
+"callsign_16_col_fire","#BANNER_FIRE_CALLSIGN16",3,"rui/callsigns/callsign_16_col",0
+"callsign_01_col_fire","#BANNER_FIRE_CALLSIGN1",3,"rui/callsigns/callsign_01_col",0
+"callsign_03_col_fire","#BANNER_FIRE_CALLSIGN3",3,"rui/callsigns/callsign_03_col",0
+"callsign_04_col_fire","#BANNER_FIRE_CALLSIGN4",3,"rui/callsigns/callsign_04_col",0
+"callsign_05_col_fire","#BANNER_FIRE_CALLSIGN5",3,"rui/callsigns/callsign_05_col",0
+"callsign_06_col_fire","#BANNER_FIRE_CALLSIGN6",3,"rui/callsigns/callsign_06_col",0
+"callsign_07_col_fire","#BANNER_FIRE_CALLSIGN7",3,"rui/callsigns/callsign_07_col",0
+"callsign_09_col_fire","#BANNER_FIRE_CALLSIGN9",3,"rui/callsigns/callsign_09_col",0
+"callsign_11_col_fire","#BANNER_FIRE_CALLSIGN11",3,"rui/callsigns/callsign_11_col",0
+"callsign_13_col_fire","#BANNER_FIRE_CALLSIGN13",3,"rui/callsigns/callsign_13_col",0
+"callsign_14_col_fire","#BANNER_FIRE_CALLSIGN14",3,"rui/callsigns/callsign_14_col",0
+"callsign_15_col_fire","#BANNER_FIRE_CALLSIGN15",3,"rui/callsigns/callsign_15_col",0
+"callsign_18_col_fire","#BANNER_FIRE_CALLSIGN18",3,"rui/callsigns/callsign_18_col",0
+"callsign_19_col_fire","#BANNER_FIRE_CALLSIGN19",3,"rui/callsigns/callsign_19_col",0
+"callsign_21_col_fire","#BANNER_FIRE_CALLSIGN21",3,"rui/callsigns/callsign_21_col",0
+"callsign_22_col_fire","#BANNER_FIRE_CALLSIGN22",3,"rui/callsigns/callsign_22_col",0
+"callsign_23_col_fire","#BANNER_FIRE_CALLSIGN23",3,"rui/callsigns/callsign_23_col",0
+"callsign_24_col_fire","#BANNER_FIRE_CALLSIGN24",3,"rui/callsigns/callsign_24_col",0
+"callsign_26_col_fire","#BANNER_FIRE_CALLSIGN26",3,"rui/callsigns/callsign_26_col",0
+"callsign_27_col_fire","#BANNER_FIRE_CALLSIGN27",3,"rui/callsigns/callsign_27_col",0
+"callsign_29_col_fire","#BANNER_FIRE_CALLSIGN29",3,"rui/callsigns/callsign_29_col",0
+"callsign_30_col_fire","#BANNER_FIRE_CALLSIGN30",3,"rui/callsigns/callsign_30_col",0
+"callsign_31_col_fire","#BANNER_FIRE_CALLSIGN31",3,"rui/callsigns/callsign_31_col",0
+"callsign_34_col_fire","#BANNER_FIRE_CALLSIGN34",3,"rui/callsigns/callsign_34_col",0
+"callsign_35_col_fire","#BANNER_FIRE_CALLSIGN35",3,"rui/callsigns/callsign_35_col",0
+"callsign_36_col_fire","#BANNER_FIRE_CALLSIGN36",3,"rui/callsigns/callsign_36_col",0
+"callsign_37_col_fire","#BANNER_FIRE_CALLSIGN37",3,"rui/callsigns/callsign_37_col",0
+"callsign_39_col_fire","#BANNER_FIRE_CALLSIGN39",3,"rui/callsigns/callsign_39_col",0
+"callsign_40_col_fire","#BANNER_FIRE_CALLSIGN40",3,"rui/callsigns/callsign_40_col",0
+"callsign_41_col_fire","#BANNER_FIRE_CALLSIGN41",3,"rui/callsigns/callsign_41_col",0
+"callsign_42_col_fire","#BANNER_FIRE_CALLSIGN42",3,"rui/callsigns/callsign_42_col",0
+"callsign_45_col_fire","#BANNER_FIRE_CALLSIGN45",3,"rui/callsigns/callsign_45_col",0
+"callsign_46_col_fire","#BANNER_FIRE_CALLSIGN46",3,"rui/callsigns/callsign_46_col",0
+"callsign_47_col_fire","#BANNER_FIRE_CALLSIGN47",3,"rui/callsigns/callsign_47_col",0
+"callsign_48_col_fire","#BANNER_FIRE_CALLSIGN48",3,"rui/callsigns/callsign_48_col",0
+"callsign_51_col_fire","#BANNER_FIRE_CALLSIGN51",3,"rui/callsigns/callsign_51_col",0
+"callsign_53_col_fire","#BANNER_FIRE_CALLSIGN53",3,"rui/callsigns/callsign_53_col",0
+"callsign_55_col_fire","#BANNER_FIRE_CALLSIGN55",3,"rui/callsigns/callsign_55_col",0
+"callsign_57_col_fire","#BANNER_FIRE_CALLSIGN57",3,"rui/callsigns/callsign_57_col",0
+"callsign_59_col_fire","#BANNER_FIRE_CALLSIGN59",3,"rui/callsigns/callsign_59_col",0
+"callsign_66_col_fire","#BANNER_FIRE_CALLSIGN66",3,"rui/callsigns/callsign_66_col",0
+"callsign_67_col_fire","#BANNER_FIRE_CALLSIGN67",3,"rui/callsigns/callsign_67_col",0
+"callsign_68_col_fire","#BANNER_FIRE_CALLSIGN68",3,"rui/callsigns/callsign_68_col",0
+"callsign_69_col_fire","#BANNER_FIRE_CALLSIGN69",3,"rui/callsigns/callsign_69_col",0
+"callsign_70_col_fire","#BANNER_FIRE_CALLSIGN70",3,"rui/callsigns/callsign_70_col",0
+"callsign_75_col_fire","#BANNER_FIRE_CALLSIGN75",3,"rui/callsigns/callsign_75_col",0
+"callsign_76_col_fire","#BANNER_FIRE_CALLSIGN76",3,"rui/callsigns/callsign_76_col",0
+"callsign_78_col_fire","#BANNER_FIRE_CALLSIGN78",3,"rui/callsigns/callsign_78_col",0
+"callsign_79_col_fire","#BANNER_FIRE_CALLSIGN79",3,"rui/callsigns/callsign_79_col",0
+"callsign_92_col_fire","#BANNER_FIRE_CALLSIGN92",3,"rui/callsigns/callsign_92_col",0
+"callsign_94_col_fire","#BANNER_FIRE_CALLSIGN94",3,"rui/callsigns/callsign_94_col",0
+"callsign_96_col_fire","#BANNER_FIRE_CALLSIGN96",3,"rui/callsigns/callsign_96_col",0
+"callsign_97_col_fire","#BANNER_FIRE_CALLSIGN97",3,"rui/callsigns/callsign_97_col",0
+"callsign_99_col_fire","#BANNER_FIRE_CALLSIGN99",3,"rui/callsigns/callsign_99_col",0
+"callsign_100_col_fire","#BANNER_FIRE_CALLSIGN100",3,"rui/callsigns/callsign_100_col",0
+"callsign_103_col_fire","#BANNER_FIRE_CALLSIGN103",3,"rui/callsigns/callsign_103_col",0
+"callsign_104_col_fire","#BANNER_FIRE_CALLSIGN104",3,"rui/callsigns/callsign_104_col",0
+"callsign_106_col","#BANNER_CALLSIGN106",0,"rui/callsigns/callsign_106_col",0
+"callsign_107_col","#BANNER_CALLSIGN107",0,"rui/callsigns/callsign_107_col",0
+"callsign_108_col","#BANNER_CALLSIGN108",0,"rui/callsigns/callsign_108_col",0
+"callsign_109_col","#BANNER_CALLSIGN109",0,"rui/callsigns/callsign_109_col",0
+"callsign_110_col","#BANNER_CALLSIGN110",0,"rui/callsigns/callsign_110_col",0
+"callsign_111_col","#BANNER_CALLSIGN111",0,"rui/callsigns/callsign_111_col",0
+"callsign_112_col","#BANNER_CALLSIGN112",0,"rui/callsigns/callsign_112_col",0
+"callsign_113_col","#BANNER_CALLSIGN113",0,"rui/callsigns/callsign_113_col",0
+"callsign_114_col","#BANNER_CALLSIGN114",0,"rui/callsigns/callsign_114_col",0
+"callsign_115_col","#BANNER_CALLSIGN115",0,"rui/callsigns/callsign_115_col",0
+"callsign_116_col","#BANNER_CALLSIGN116",0,"rui/callsigns/callsign_116_col",0
+"callsign_117_col","#BANNER_CALLSIGN117",0,"rui/callsigns/callsign_117_col",0
+"callsign_118_col","#BANNER_CALLSIGN118",0,"rui/callsigns/callsign_118_col",0
+"callsign_119_col","#BANNER_CALLSIGN119",0,"rui/callsigns/callsign_119_col",0
+"callsign_120_col","#BANNER_CALLSIGN120",0,"rui/callsigns/callsign_120_col",0
+"callsign_121_col","#BANNER_CALLSIGN121",0,"rui/callsigns/callsign_121_col",0
+"callsign_122_col","#BANNER_CALLSIGN122",0,"rui/callsigns/callsign_122_col",0
+"callsign_123_col","#BANNER_CALLSIGN123",0,"rui/callsigns/callsign_123_col",0
+"callsign_124_col","#BANNER_CALLSIGN124",0,"rui/callsigns/callsign_124_col",0
+"callsign_125_col","#BANNER_CALLSIGN125",0,"rui/callsigns/callsign_125_col",0
+"callsign_139_col","#BANNER_CALLSIGN139",0,"rui/callsigns/callsign_139_col",0
+"callsign_139_col_fire","#BANNER_FIRE_CALLSIGN139",3,"rui/callsigns/callsign_139_col",0
+"callsign_139_col_gold","#BANNER_GOLD_CALLSIGN139",2,"rui/callsigns/callsign_139_col",0
+"callsign_126_col","#BANNER_CALLSIGN126",0,"rui/callsigns/callsign_126_col",0
+"callsign_127_col","#BANNER_CALLSIGN127",0,"rui/callsigns/callsign_127_col",0
+"callsign_128_col","#BANNER_CALLSIGN128",0,"rui/callsigns/callsign_128_col",0
+"callsign_129_col","#BANNER_CALLSIGN129",0,"rui/callsigns/callsign_129_col",0
+"callsign_130_col","#BANNER_CALLSIGN130",0,"rui/callsigns/callsign_130_col",0
+"callsign_131_col","#BANNER_CALLSIGN131",0,"rui/callsigns/callsign_131_col",0
+"callsign_132_col","#BANNER_CALLSIGN132",0,"rui/callsigns/callsign_132_col",0
+"callsign_133_col","#BANNER_CALLSIGN133",0,"rui/callsigns/callsign_133_col",0
+"callsign_134_col","#BANNER_CALLSIGN134",0,"rui/callsigns/callsign_134_col",0
+"callsign_135_col","#BANNER_CALLSIGN135",0,"rui/callsigns/callsign_135_col",0
+"callsign_136_col","#BANNER_CALLSIGN136",0,"rui/callsigns/callsign_136_col",0
+"callsign_137_col","#BANNER_CALLSIGN137",0,"rui/callsigns/callsign_137_col",0
+"callsign_138_col","#BANNER_CALLSIGN138",0,"rui/callsigns/callsign_138_col",0
+"callsign_125_col_fire","#BANNER_FIRE_CALLSIGN125",3,"rui/callsigns/callsign_125_col",0
+"callsign_126_col_fire","#BANNER_FIRE_CALLSIGN126",3,"rui/callsigns/callsign_126_col",0
+"callsign_127_col_fire","#BANNER_FIRE_CALLSIGN127",3,"rui/callsigns/callsign_127_col",0
+"callsign_128_col_fire","#BANNER_FIRE_CALLSIGN128",3,"rui/callsigns/callsign_128_col",0
+"callsign_129_col_fire","#BANNER_FIRE_CALLSIGN129",3,"rui/callsigns/callsign_129_col",0
+"callsign_130_col_fire","#BANNER_FIRE_CALLSIGN130",3,"rui/callsigns/callsign_130_col",0
+"callsign_131_col_fire","#BANNER_FIRE_CALLSIGN131",3,"rui/callsigns/callsign_131_col",0
+"callsign_132_col_fire","#BANNER_FIRE_CALLSIGN132",3,"rui/callsigns/callsign_132_col",0
+"callsign_133_col_fire","#BANNER_FIRE_CALLSIGN133",3,"rui/callsigns/callsign_133_col",0
+"callsign_134_col_fire","#BANNER_FIRE_CALLSIGN134",3,"rui/callsigns/callsign_134_col",0
+"callsign_135_col_fire","#BANNER_FIRE_CALLSIGN135",3,"rui/callsigns/callsign_135_col",0
+"callsign_136_col_fire","#BANNER_FIRE_CALLSIGN136",3,"rui/callsigns/callsign_136_col",0
+"callsign_137_col_fire","#BANNER_FIRE_CALLSIGN137",3,"rui/callsigns/callsign_137_col",0
+"callsign_138_col_fire","#BANNER_FIRE_CALLSIGN138",3,"rui/callsigns/callsign_138_col",0
+"callsign_125_col_gold","#BANNER_GOLD_CALLSIGN125",2,"rui/callsigns/callsign_125_col",0
+"callsign_126_col_gold","#BANNER_GOLD_CALLSIGN126",2,"rui/callsigns/callsign_126_col",0
+"callsign_127_col_gold","#BANNER_GOLD_CALLSIGN127",2,"rui/callsigns/callsign_127_col",0
+"callsign_128_col_gold","#BANNER_GOLD_CALLSIGN128",2,"rui/callsigns/callsign_128_col",0
+"callsign_129_col_gold","#BANNER_GOLD_CALLSIGN129",2,"rui/callsigns/callsign_129_col",0
+"callsign_130_col_gold","#BANNER_GOLD_CALLSIGN130",2,"rui/callsigns/callsign_130_col",0
+"callsign_131_col_gold","#BANNER_GOLD_CALLSIGN131",2,"rui/callsigns/callsign_131_col",0
+"callsign_132_col_gold","#BANNER_GOLD_CALLSIGN132",2,"rui/callsigns/callsign_132_col",0
+"callsign_133_col_gold","#BANNER_GOLD_CALLSIGN133",2,"rui/callsigns/callsign_133_col",0
+"callsign_134_col_gold","#BANNER_GOLD_CALLSIGN134",2,"rui/callsigns/callsign_134_col",0
+"callsign_135_col_gold","#BANNER_GOLD_CALLSIGN135",2,"rui/callsigns/callsign_135_col",0
+"callsign_136_col_gold","#BANNER_GOLD_CALLSIGN136",2,"rui/callsigns/callsign_136_col",0
+"callsign_137_col_gold","#BANNER_GOLD_CALLSIGN137",2,"rui/callsigns/callsign_137_col",0
+"callsign_138_col_gold","#BANNER_GOLD_CALLSIGN138",2,"rui/callsigns/callsign_138_col",0
+"callsign_125_col_prism","#BANNER_PRISM_CALLSIGN125",1,"rui/callsigns/callsign_125_col",0
+"callsign_126_col_prism","#BANNER_PRISM_CALLSIGN126",1,"rui/callsigns/callsign_126_col",1500
+"callsign_127_col_prism","#BANNER_PRISM_CALLSIGN127",1,"rui/callsigns/callsign_127_col",1500
+"callsign_128_col_prism","#BANNER_PRISM_CALLSIGN128",1,"rui/callsigns/callsign_128_col",0
+"callsign_129_col_prism","#BANNER_PRISM_CALLSIGN129",1,"rui/callsigns/callsign_129_col",0
+"callsign_130_col_prism","#BANNER_PRISM_CALLSIGN130",1,"rui/callsigns/callsign_130_col",1500
+"callsign_131_col_prism","#BANNER_PRISM_CALLSIGN131",1,"rui/callsigns/callsign_131_col",1500
+"callsign_132_col_prism","#BANNER_PRISM_CALLSIGN132",1,"rui/callsigns/callsign_132_col",0
+"callsign_133_col_prism","#BANNER_PRISM_CALLSIGN133",1,"rui/callsigns/callsign_133_col",0
+"callsign_134_col_prism","#BANNER_PRISM_CALLSIGN134",1,"rui/callsigns/callsign_134_col",0
+"callsign_135_col_prism","#BANNER_PRISM_CALLSIGN135",1,"rui/callsigns/callsign_135_col",0
+"callsign_136_col_prism","#BANNER_PRISM_CALLSIGN136",1,"rui/callsigns/callsign_136_col",0
+"callsign_137_col_prism","#BANNER_PRISM_CALLSIGN137",1,"rui/callsigns/callsign_137_col",0
+"callsign_138_col_prism","#BANNER_PRISM_CALLSIGN138",1,"rui/callsigns/callsign_138_col",0
+"callsign_140_col","#BANNER_CALLSIGN140",0,"rui/callsigns/callsign_140_col",0
+"callsign_141_col","#BANNER_CALLSIGN141",0,"rui/callsigns/callsign_141_col",0
+"callsign_142_col","#BANNER_CALLSIGN142",0,"rui/callsigns/callsign_142_col",0
+"callsign_140_col_fire","#BANNER_FIRE_CALLSIGN140",3,"rui/callsigns/callsign_140_col",0
+"callsign_141_col_fire","#BANNER_FIRE_CALLSIGN141",3,"rui/callsigns/callsign_141_col",0
+"callsign_142_col_fire","#BANNER_FIRE_CALLSIGN142",3,"rui/callsigns/callsign_142_col",0
+"callsign_140_col_gold","#BANNER_GOLD_CALLSIGN140",2,"rui/callsigns/callsign_140_col",0
+"callsign_141_col_gold","#BANNER_GOLD_CALLSIGN141",2,"rui/callsigns/callsign_141_col",0
+"callsign_142_col_gold","#BANNER_GOLD_CALLSIGN142",2,"rui/callsigns/callsign_142_col",0
+"callsign_143_col","#BANNER_CALLSIGN143",0,"rui/callsigns/callsign_143_col",0
+"callsign_144_col","#BANNER_CALLSIGN144",0,"rui/callsigns/callsign_144_col",0
+"callsign_145_col","#BANNER_CALLSIGN145",0,"rui/callsigns/callsign_145_col",0
+"callsign_146_col","#BANNER_CALLSIGN146",0,"rui/callsigns/callsign_146_col",0
+"callsign_147_col","#BANNER_CALLSIGN147",0,"rui/callsigns/callsign_147_col",0
+"callsign_148_col","#BANNER_CALLSIGN148",0,"rui/callsigns/callsign_148_col",0
+"callsign_149_col","#BANNER_CALLSIGN149",0,"rui/callsigns/callsign_149_col",0
+"callsign_150_col","#BANNER_CALLSIGN150",0,"rui/callsigns/callsign_150_col",0
+"callsign_151_col","#BANNER_CALLSIGN151",0,"rui/callsigns/callsign_151_col",0
+"callsign_152_col","#BANNER_CALLSIGN152",0,"rui/callsigns/callsign_152_col",0
+"callsign_153_col","#BANNER_CALLSIGN153",0,"rui/callsigns/callsign_153_col",0
+"callsign_154_col","#BANNER_CALLSIGN154",0,"rui/callsigns/callsign_154_col",0
+"callsign_155_col","#BANNER_CALLSIGN155",0,"rui/callsigns/callsign_155_col",0
+"callsign_156_col","#BANNER_CALLSIGN156",0,"rui/callsigns/callsign_156_col",0
+"callsign_157_col","#BANNER_CALLSIGN157",0,"rui/callsigns/callsign_157_col",0
+"callsign_158_col","#BANNER_CALLSIGN158",0,"rui/callsigns/callsign_158_col",0
+"callsign_159_col","#BANNER_CALLSIGN159",0,"rui/callsigns/callsign_159_col",0
+"callsign_160_col","#BANNER_CALLSIGN160",0,"rui/callsigns/callsign_160_col",0
+"callsign_161_col","#BANNER_CALLSIGN161",0,"rui/callsigns/callsign_161_col",0
+"callsign_162_col","#BANNER_CALLSIGN162",0,"rui/callsigns/callsign_162_col",0
+"callsign_163_col","#BANNER_CALLSIGN163",0,"rui/callsigns/callsign_163_col",0
+"callsign_164_col","#BANNER_CALLSIGN164",0,"rui/callsigns/callsign_164_col",0
+"callsign_163_col_prism","#BANNER_PRISM_CALLSIGN163",1,"rui/callsigns/callsign_163_col",0
+"callsign_164_col_prism","#BANNER_PRISM_CALLSIGN164",1,"rui/callsigns/callsign_164_col",0
+"callsign_163_col_gold","#BANNER_GOLD_CALLSIGN163",2,"rui/callsigns/callsign_163_col",0
+"callsign_164_col_gold","#BANNER_GOLD_CALLSIGN164",2,"rui/callsigns/callsign_164_col",0
+"callsign_163_col_fire","#BANNER_FIRE_CALLSIGN163",3,"rui/callsigns/callsign_163_col",0
+"callsign_164_col_fire","#BANNER_FIRE_CALLSIGN164",3,"rui/callsigns/callsign_164_col",0
+"callsign_regen_10_col","#BANNER_REGEN_10",0,"rui/callsigns/callsign_regen_10_col",0
+"callsign_regen_20_col","#BANNER_REGEN_20",0,"rui/callsigns/callsign_regen_20_col",0
+"callsign_regen_30_col","#BANNER_REGEN_30",0,"rui/callsigns/callsign_regen_30_col",0
+"callsign_regen_40_col","#BANNER_REGEN_40",0,"rui/callsigns/callsign_regen_40_col",0
+"callsign_regen_50_col","#BANNER_REGEN_50",0,"rui/callsigns/callsign_regen_50_col",0
+"callsign_regen_60_col","#BANNER_REGEN_60",0,"rui/callsigns/callsign_regen_60_col",0
+"callsign_regen_70_col","#BANNER_REGEN_70",0,"rui/callsigns/callsign_regen_70_col",0
+"callsign_regen_80_col","#BANNER_REGEN_80",0,"rui/callsigns/callsign_regen_80_col",0
+"callsign_regen_90_col","#BANNER_REGEN_90",0,"rui/callsigns/callsign_regen_90_col",0
+"callsign_regen_100_col","#BANNER_REGEN_100",0,"rui/callsigns/callsign_regen_100_col",0
+"callsign_regen_10_col_prism","#BANNER_PRISM_REGEN_10",1,"rui/callsigns/callsign_regen_10_col",0
+"callsign_regen_20_col_prism","#BANNER_PRISM_REGEN_20",1,"rui/callsigns/callsign_regen_20_col",0
+"callsign_regen_30_col_prism","#BANNER_PRISM_REGEN_30",1,"rui/callsigns/callsign_regen_30_col",0
+"callsign_regen_40_col_prism","#BANNER_PRISM_REGEN_40",1,"rui/callsigns/callsign_regen_40_col",0
+"callsign_regen_50_col_prism","#BANNER_PRISM_REGEN_50",1,"rui/callsigns/callsign_regen_50_col",0
+"callsign_regen_60_col_prism","#BANNER_PRISM_REGEN_60",1,"rui/callsigns/callsign_regen_60_col",0
+"callsign_regen_70_col_prism","#BANNER_PRISM_REGEN_70",1,"rui/callsigns/callsign_regen_70_col",0
+"callsign_regen_80_col_prism","#BANNER_PRISM_REGEN_80",1,"rui/callsigns/callsign_regen_80_col",0
+"callsign_regen_90_col_prism","#BANNER_PRISM_REGEN_90",1,"rui/callsigns/callsign_regen_90_col",0
+"callsign_regen_100_col_prism","#BANNER_PRISM_REGEN_100",1,"rui/callsigns/callsign_regen_100_col",0
+"callsign_165_col","#BANNER_CALLSIGN165",0,"rui/callsigns/callsign_165_col",0
+"callsign_166_col","#BANNER_CALLSIGN166",0,"rui/callsigns/callsign_166_col",0
+"callsign_167_col","#BANNER_CALLSIGN167",0,"rui/callsigns/callsign_167_col",0
+"callsign_168_col","#BANNER_CALLSIGN168",0,"rui/callsigns/callsign_168_col",0
+"callsign_169_col","#BANNER_CALLSIGN169",0,"rui/callsigns/callsign_169_col",0
+"callsign_170_col","#BANNER_CALLSIGN170",0,"rui/callsigns/callsign_170_col",0
+"callsign_171_col","#BANNER_CALLSIGN171",0,"rui/callsigns/callsign_171_col",0
+"callsign_172_col","#BANNER_CALLSIGN172",0,"rui/callsigns/callsign_172_col",0
+"callsign_173_col","#BANNER_CALLSIGN173",0,"rui/callsigns/callsign_173_col",0
+"callsign_174_col","#BANNER_CALLSIGN174",0,"rui/callsigns/callsign_174_col",0
+"callsign_175_col","#BANNER_CALLSIGN175",0,"rui/callsigns/callsign_175_col",0
+"callsign_176_col","#BANNER_CALLSIGN176",0,"rui/callsigns/callsign_176_col",0
+"callsign_177_col","#BANNER_CALLSIGN177",0,"rui/callsigns/callsign_177_col",0
+"callsign_178_col","#BANNER_CALLSIGN178",0,"rui/callsigns/callsign_178_col",0
+"callsign_179_col","#BANNER_CALLSIGN179",0,"rui/callsigns/callsign_179_col",0
+"callsign_180_col","#BANNER_CALLSIGN180",0,"rui/callsigns/callsign_180_col",0
+"callsign_181_col","#BANNER_CALLSIGN181",0,"rui/callsigns/callsign_181_col",0
+"callsign_182_col","#BANNER_CALLSIGN182",0,"rui/callsigns/callsign_182_col",0
+"callsign_183_col","#BANNER_CALLSIGN183",0,"rui/callsigns/callsign_183_col",0
+"callsign_184_col","#BANNER_CALLSIGN184",0,"rui/callsigns/callsign_184_col",0
+"callsign_185_col","#BANNER_CALLSIGN185",0,"rui/callsigns/callsign_185_col",0
+"callsign_165_col_fire","#BANNER_FIRE_CALLSIGN165",3,"rui/callsigns/callsign_165_col",0
+"callsign_165_col_gold","#BANNER_GOLD_CALLSIGN165",2,"rui/callsigns/callsign_165_col",0
+"callsign_24_col_prism","#BANNER_PRISM_CALLSIGN24",1,"rui/callsigns/callsign_24_col",0
+"callsign_47_col_prism","#BANNER_PRISM_CALLSIGN47",1,"rui/callsigns/callsign_47_col",0
+"callsign_36_col_prism","#BANNER_PRISM_CALLSIGN36",1,"rui/callsigns/callsign_36_col",0
+"callsign_45_col_prism","#BANNER_PRISM_CALLSIGN45",1,"rui/callsigns/callsign_45_col",0
+"callsign_68_col_prism","#BANNER_PRISM_CALLSIGN68",1,"rui/callsigns/callsign_68_col",0
+"callsign_26_col_prism","#BANNER_PRISM_CALLSIGN26",1,"rui/callsigns/callsign_26_col",0
+"callsign_165_col_prism","#BANNER_PRISM_CALLSIGN165",1,"rui/callsigns/callsign_165_col",0
+"callsign_fd_ion_dynamic","#BANNER_CALLSIGN_FD_ION",0,"rui/callsigns/callsign_fd_ion_dynamic",0
+"callsign_fd_tone_dynamic","#BANNER_CALLSIGN_FD_TONE",0,"rui/callsigns/callsign_fd_tone_dynamic",0
+"callsign_fd_scorch_dynamic","#BANNER_CALLSIGN_FD_SCORCH",0,"rui/callsigns/callsign_fd_scorch_dynamic",0
+"callsign_fd_legion_dynamic","#BANNER_CALLSIGN_FD_LEGION",0,"rui/callsigns/callsign_fd_legion_dynamic",0
+"callsign_fd_ronin_dynamic","#BANNER_CALLSIGN_FD_RONIN",0,"rui/callsigns/callsign_fd_ronin_dynamic",0
+"callsign_fd_northstar_dynamic","#BANNER_CALLSIGN_FD_NORTHSTAR",0,"rui/callsigns/callsign_fd_northstar_dynamic",0
+"callsign_fd_monarch_dynamic","#BANNER_CALLSIGN_FD_MONARCH",0,"rui/callsigns/callsign_fd_monarch_dynamic",0
+"callsign_fd_ion_hard","#BANNER_CALLSIGN_FD_ION",1,"rui/callsigns/callsign_fd_ion_hard",0
+"callsign_fd_ion_master","#BANNER_CALLSIGN_FD_ION",1,"rui/callsigns/callsign_fd_ion_master",0
+"callsign_fd_ion_insane","#BANNER_CALLSIGN_FD_ION",2,"rui/callsigns/callsign_fd_ion_insane",0
+"callsign_fd_tone_hard","#BANNER_CALLSIGN_FD_TONE",1,"rui/callsigns/callsign_fd_tone_hard",0
+"callsign_fd_tone_master","#BANNER_CALLSIGN_FD_TONE",1,"rui/callsigns/callsign_fd_tone_master",0
+"callsign_fd_tone_insane","#BANNER_CALLSIGN_FD_TONE",2,"rui/callsigns/callsign_fd_tone_insane",0
+"callsign_fd_scorch_hard","#BANNER_CALLSIGN_FD_SCORCH",1,"rui/callsigns/callsign_fd_scorch_hard",0
+"callsign_fd_scorch_master","#BANNER_CALLSIGN_FD_SCORCH",1,"rui/callsigns/callsign_fd_scorch_master",0
+"callsign_fd_scorch_insane","#BANNER_CALLSIGN_FD_SCORCH",2,"rui/callsigns/callsign_fd_scorch_insane",0
+"callsign_fd_legion_hard","#BANNER_CALLSIGN_FD_LEGION",1,"rui/callsigns/callsign_fd_legion_hard",0
+"callsign_fd_legion_master","#BANNER_CALLSIGN_FD_LEGION",1,"rui/callsigns/callsign_fd_legion_master",0
+"callsign_fd_legion_insane","#BANNER_CALLSIGN_FD_LEGION",2,"rui/callsigns/callsign_fd_legion_insane",0
+"callsign_fd_ronin_hard","#BANNER_CALLSIGN_FD_RONIN",1,"rui/callsigns/callsign_fd_ronin_hard",0
+"callsign_fd_ronin_master","#BANNER_CALLSIGN_FD_RONIN",1,"rui/callsigns/callsign_fd_ronin_master",0
+"callsign_fd_ronin_insane","#BANNER_CALLSIGN_FD_RONIN",2,"rui/callsigns/callsign_fd_ronin_insane",0
+"callsign_fd_northstar_hard","#BANNER_CALLSIGN_FD_NORTHSTAR",1,"rui/callsigns/callsign_fd_northstar_hard",0
+"callsign_fd_northstar_master","#BANNER_CALLSIGN_FD_NORTHSTAR",1,"rui/callsigns/callsign_fd_northstar_master",0
+"callsign_fd_northstar_insane","#BANNER_CALLSIGN_FD_NORTHSTAR",2,"rui/callsigns/callsign_fd_northstar_insane",0
+"callsign_fd_monarch_hard","#BANNER_CALLSIGN_FD_MONARCH",1,"rui/callsigns/callsign_fd_monarch_hard",0
+"callsign_fd_monarch_master","#BANNER_CALLSIGN_FD_MONARCH",1,"rui/callsigns/callsign_fd_monarch_master",0
+"callsign_fd_monarch_insane","#BANNER_CALLSIGN_FD_MONARCH",2,"rui/callsigns/callsign_fd_monarch_insane",0
+"callsign_tt_gameover","#BANNER_CALLSIGN_TT_GAMEOVER",0,"rui/callsigns/callsign_tt_gameover",1500
+"callsign_tt_gameover_prism","#BANNER_PRISM_TT_GAMEOVER",1,"rui/callsigns/callsign_tt_gameover",3000
+"callsign_tt_guardtheflag","#BANNER_CALLSIGN_TT_GUARDTHEFLAG",0,"rui/callsigns/callsign_tt_guardtheflag",1500
+"callsign_tt_guardtheflag_prism","#BANNER_PRISM_TT_GUARDTHEFLAG",1,"rui/callsigns/callsign_tt_guardtheflag",3000
+"callsign_tt_megamarvin","#BANNER_CALLSIGN_TT_MEGAMARVIN",0,"rui/callsigns/callsign_tt_megamarvin",1500
+"callsign_tt_megamarvin_prism","#BANNER_PRISM_TT_MEGAMARVIN",1,"rui/callsigns/callsign_tt_megamarvin",3000
+"callsign_tt_nessievault","#BANNER_CALLSIGN_TT_NESSIEVAULT",0,"rui/callsigns/callsign_tt_nessievault",1500
+"callsign_tt_nessievault_prism","#BANNER_PRISM_TT_NESSIEVAULT",1,"rui/callsigns/callsign_tt_nessievault",3000
+"callsign_tt_nsbt","#BANNER_CALLSIGN_TT_NSBT",0,"rui/callsigns/callsign_tt_nsbt",1500
+"callsign_tt_nsbt_prism","#BANNER_PRISM_TT_NSBT",1,"rui/callsigns/callsign_tt_nsbt",3000
+"callsign_tt_protocol2","#BANNER_CALLSIGN_TT_PROTOCOL2",0,"rui/callsigns/callsign_tt_protocol2",1500
+"callsign_tt_protocol2_prism","#BANNER_PRISM_TT_PROTOCOL2",1,"rui/callsigns/callsign_tt_protocol2",3000
+"callsign_tt_rekt","#BANNER_CALLSIGN_TT_REKT",0,"rui/callsigns/callsign_tt_rekt",1500
+"callsign_tt_rekt_prism","#BANNER_PRISM_TT_REKT",1,"rui/callsigns/callsign_tt_rekt",3000
+"callsign_tt_titantoons","#BANNER_CALLSIGN_TT_TITANTOONS",0,"rui/callsigns/callsign_tt_titantoons",1500
+"callsign_tt_titantoons_prism","#BANNER_PRISM_TT_TITANTOONS",1,"rui/callsigns/callsign_tt_titantoons",3000
+"callsign_eat_ion","#BANNER_EAT_ION",0,"rui/callsigns/callsign_eat_ion",1500
+"callsign_eat_legion","#BANNER_EAT_LEGION",0,"rui/callsigns/callsign_eat_legion",1500
+"callsign_eat_northstar","#BANNER_EAT_NORTHSTAR",0,"rui/callsigns/callsign_eat_northstar",1500
+"callsign_eat_ronin","#BANNER_EAT_RONIN",0,"rui/callsigns/callsign_eat_ronin",1500
+"callsign_eat_scorch","#BANNER_EAT_SCORCH",0,"rui/callsigns/callsign_eat_scorch",1500
+"callsign_eat_tone","#BANNER_EAT_TONE",0,"rui/callsigns/callsign_eat_tone",1500
+"callsign_goodboy","#BANNER_GOODBOY",0,"rui/callsigns/callsign_goodboy",0
+"callsign_contest_01","#BANNER_CONTEST_01",0,"rui/callsigns/callsign_contest_01",0
+"callsign_contest_02","#BANNER_CONTEST_02",0,"rui/callsigns/callsign_contest_02",0
+"callsign_contest_03","#BANNER_CONTEST_03",0,"rui/callsigns/callsign_contest_03",0
+"callsign_contest_04","#BANNER_CONTEST_04",0,"rui/callsigns/callsign_contest_04",0
+"callsign_contest_05","#BANNER_CONTEST_05",0,"rui/callsigns/callsign_contest_05",0
+"callsign_contest_06","#BANNER_CONTEST_06",0,"rui/callsigns/callsign_contest_06",0
+"callsign_contest_07","#BANNER_CONTEST_07",0,"rui/callsigns/callsign_contest_07",0
+"callsign_contest_08","#BANNER_CONTEST_08",0,"rui/callsigns/callsign_contest_08",0
+"callsign_contest_09","#BANNER_CONTEST_09",0,"rui/callsigns/callsign_contest_09",0
+"callsign_contest_10","#BANNER_CONTEST_10",0,"rui/callsigns/callsign_contest_10",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/callsign_icons.csv b/Northstar.CustomServers/mod/scripts/datatable/callsign_icons.csv
new file mode 100644
index 00000000..61840c68
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/callsign_icons.csv
@@ -0,0 +1,193 @@
+itemRef,name,image,smallImage,cost
+"gc_icon_5star","#PATCH_GC_ICON_5STAR","rui/gencard_icons/gc_icon_5star","rui/gencard_icons/gc_icon_5star_small",50
+"gc_icon_8ball","#PATCH_8BALL","rui/gencard_icons/gc_icon_8ball","rui/gencard_icons/gc_icon_8ball_small",0
+"gc_icon_ace","#PATCH_ACE","rui/gencard_icons/gc_icon_ace","rui/gencard_icons/gc_icon_ace_small",0
+"gc_icon_angryface","#PATCH_ANGRYFACE","rui/gencard_icons/gc_icon_angryface","rui/gencard_icons/gc_icon_angryface_small",50
+"gc_icon_apostrophe","#PATCH_APOSTROPHE","rui/gencard_icons/gc_icon_apostrophe","rui/gencard_icons/gc_icon_apostrophe_small",0
+"gc_icon_atom","#PATCH_ATOM","rui/gencard_icons/gc_icon_atom","rui/gencard_icons/gc_icon_atom_small",0
+"gc_icon_balloon","#PATCH_BALLOON","rui/gencard_icons/gc_icon_balloon","rui/gencard_icons/gc_icon_balloon_small",0
+"gc_icon_bear","#PATCH_BEAR","rui/gencard_icons/gc_icon_bear","rui/gencard_icons/gc_icon_bear_small",50
+"gc_icon_bird","#PATCH_BIRD","rui/gencard_icons/gc_icon_bird","rui/gencard_icons/gc_icon_bird_small",50
+"gc_icon_bomb_01","#PATCH_BOMB_01","rui/gencard_icons/gc_icon_bomb_01","rui/gencard_icons/gc_icon_bomb_01_small",0
+"gc_icon_bomb_02","#PATCH_BOMB_02","rui/gencard_icons/gc_icon_bomb_02","rui/gencard_icons/gc_icon_bomb_02_small",0
+"gc_icon_bullet","#PATCH_BULLET","rui/gencard_icons/gc_icon_bullet","rui/gencard_icons/gc_icon_bullet_small",0
+"gc_icon_bullseye","#PATCH_BULLSEYE","rui/gencard_icons/gc_icon_bullseye","rui/gencard_icons/gc_icon_bullseye_small",0
+"gc_icon_cateye","#PATCH_CATEYE","rui/gencard_icons/gc_icon_cateye","rui/gencard_icons/gc_icon_cateye_small",0
+"gc_icon_chicken","#PATCH_CHICKEN","rui/gencard_icons/gc_icon_chicken","rui/gencard_icons/gc_icon_chicken_small",50
+"gc_icon_clawmark","#PATCH_CLAWMARK","rui/gencard_icons/gc_icon_clawmark","rui/gencard_icons/gc_icon_clawmark_small",0
+"gc_icon_club","#PATCH_CLUB","rui/gencard_icons/gc_icon_club","rui/gencard_icons/gc_icon_club_small",50
+"gc_icon_comet","#PATCH_COMET","rui/gencard_icons/gc_icon_comet","rui/gencard_icons/gc_icon_comet_small",0
+"gc_icon_cow","#PATCH_COW","rui/gencard_icons/gc_icon_cow","rui/gencard_icons/gc_icon_cow_small",50
+"gc_icon_cowboy_hat","#PATCH_COWBOY_HAT","rui/gencard_icons/gc_icon_cowboy_hat","rui/gencard_icons/gc_icon_cowboy_hat_small",0
+"gc_icon_crosshair","#PATCH_CROSSHAIR","rui/gencard_icons/gc_icon_crosshair","rui/gencard_icons/gc_icon_crosshair_small",0
+"gc_icon_cupcake","#PATCH_CUPCAKE","rui/gencard_icons/gc_icon_cupcake","rui/gencard_icons/gc_icon_cupcake_small",50
+"gc_icon_diamond","#PATCH_DIAMOND","rui/gencard_icons/gc_icon_diamond","rui/gencard_icons/gc_icon_diamond_small",0
+"gc_icon_dice","#PATCH_DICE","rui/gencard_icons/gc_icon_dice","rui/gencard_icons/gc_icon_dice_small",0
+"gc_icon_dollarsign","#PATCH_DOLLARSIGN","rui/gencard_icons/gc_icon_dollarsign","rui/gencard_icons/gc_icon_dollarsign_small",0
+"gc_icon_doublerainbow","#PATCH_DOUBLERAINBOW","rui/gencard_icons/gc_icon_doublerainbow","rui/gencard_icons/gc_icon_doublerainbow_small",50
+"gc_icon_fingerprint","#PATCH_FINGERPRINT","rui/gencard_icons/gc_icon_fingerprint","rui/gencard_icons/gc_icon_fingerprint_small",50
+"gc_icon_fireball","#PATCH_FIREBALL","rui/gencard_icons/gc_icon_fireball","rui/gencard_icons/gc_icon_fireball_small",0
+"gc_icon_frag","#PATCH_FRAG","rui/gencard_icons/gc_icon_frag","rui/gencard_icons/gc_icon_frag_small",0
+"gc_icon_gear","#PATCH_GEAR","rui/gencard_icons/gc_icon_gear","rui/gencard_icons/gc_icon_gear_small",0
+"gc_icon_ghostface","#PATCH_GHOSTFACE","rui/gencard_icons/gc_icon_ghostface","rui/gencard_icons/gc_icon_ghostface_small",50
+"gc_icon_hamburger","#PATCH_HAMBURGER","rui/gencard_icons/gc_icon_hamburger","rui/gencard_icons/gc_icon_hamburger_small",50
+"gc_icon_handprint","#PATCH_HANDPRINT","rui/gencard_icons/gc_icon_handprint","rui/gencard_icons/gc_icon_handprint_small",50
+"gc_icon_happyface","#PATCH_HAPPYFACE","rui/gencard_icons/gc_icon_happyface","rui/gencard_icons/gc_icon_happyface_small",0
+"gc_icon_heart","#PATCH_HEART","rui/gencard_icons/gc_icon_heart","rui/gencard_icons/gc_icon_heart_small",0
+"gc_icon_hvt","#PATCH_HVT","rui/gencard_icons/gc_icon_hvt","rui/gencard_icons/gc_icon_hvt_small",50
+"gc_icon_jollyrgr","#PATCH_JOLLYRGR","rui/gencard_icons/gc_icon_jollyrgr","rui/gencard_icons/gc_icon_jollyrgr_small",50
+"gc_icon_lightning","#PATCH_LIGHTNING","rui/gencard_icons/gc_icon_lightning","rui/gencard_icons/gc_icon_lightning_small",0
+"gc_icon_lol","#PATCH_LOL","rui/gencard_icons/gc_icon_lol","rui/gencard_icons/gc_icon_lol_small",50
+"gc_icon_mad_hat","#PATCH_MAD_HAT","rui/gencard_icons/gc_icon_mad_hat","rui/gencard_icons/gc_icon_mad_hat_small",50
+"gc_icon_medic","#PATCH_MEDIC","rui/gencard_icons/gc_icon_medic","rui/gencard_icons/gc_icon_medic_small",0
+"gc_icon_moon","#PATCH_MOON","rui/gencard_icons/gc_icon_moon","rui/gencard_icons/gc_icon_moon_small",0
+"gc_icon_omg","#PATCH_OMG","rui/gencard_icons/gc_icon_omg","rui/gencard_icons/gc_icon_omg_small",50
+"gc_icon_paw","#PATCH_PAW","rui/gencard_icons/gc_icon_paw","rui/gencard_icons/gc_icon_paw_small",0
+"gc_icon_pizza","#PATCH_PIZZA","rui/gencard_icons/gc_icon_pizza","rui/gencard_icons/gc_icon_pizza_small",50
+"gc_icon_pro","#PATCH_PRO","rui/gencard_icons/gc_icon_pro","rui/gencard_icons/gc_icon_pro_small",0
+"gc_icon_question","#PATCH_QUESTION","rui/gencard_icons/gc_icon_question","rui/gencard_icons/gc_icon_question_small",50
+"gc_icon_rainbow","#PATCH_RAINBOW","rui/gencard_icons/gc_icon_rainbow","rui/gencard_icons/gc_icon_rainbow_small",50
+"gc_icon_raincloud","#PATCH_RAINCLOUD","rui/gencard_icons/gc_icon_raincloud","rui/gencard_icons/gc_icon_raincloud_small",0
+"gc_icon_rocket","#PATCH_ROCKET","rui/gencard_icons/gc_icon_rocket","rui/gencard_icons/gc_icon_rocket_small",0
+"gc_icon_saturn","#PATCH_SATURN","rui/gencard_icons/gc_icon_saturn","rui/gencard_icons/gc_icon_saturn_small",50
+"gc_icon_skull","#PATCH_SKULL","rui/gencard_icons/gc_icon_skull","rui/gencard_icons/gc_icon_skull_small",50
+"gc_icon_spade","#PATCH_SPADE","rui/gencard_icons/gc_icon_spade","rui/gencard_icons/gc_icon_spade_small",0
+"gc_icon_star","#PATCH_STAR","rui/gencard_icons/gc_icon_star","rui/gencard_icons/gc_icon_star_small",0
+"gc_icon_sword","#PATCH_SWORD","rui/gencard_icons/gc_icon_sword","rui/gencard_icons/gc_icon_sword_small",0
+"gc_icon_teabage","#PATCH_TEABAGE","rui/gencard_icons/gc_icon_teabage","rui/gencard_icons/gc_icon_teabage_small",50
+"gc_icon_titanfall","#PATCH_TITANFALL","rui/gencard_icons/gc_icon_titanfall","rui/gencard_icons/gc_icon_titanfall_small",0
+"gc_icon_vip","#PATCH_VIP","rui/gencard_icons/gc_icon_vip","rui/gencard_icons/gc_icon_vip_small",0
+"gc_icon_witch_hat","#PATCH_WITCH_HAT","rui/gencard_icons/gc_icon_witch_hat","rui/gencard_icons/gc_icon_witch_hat_small",50
+"gc_icon_wizard_hat","#PATCH_WIZARD_HAT","rui/gencard_icons/gc_icon_wizard_hat","rui/gencard_icons/gc_icon_wizard_hat_small",50
+"gc_icon_wtf","#PATCH_WTF","rui/gencard_icons/gc_icon_wtf","rui/gencard_icons/gc_icon_wtf_small",50
+"gc_icon_yuckface","#PATCH_YUCKFACE","rui/gencard_icons/gc_icon_yuckface","rui/gencard_icons/gc_icon_yuckface_small",0
+"gc_icon_respawn","#PATCH_RESPAWN","rui/gencard_icons/gc_icon_respawn","rui/gencard_icons/gc_icon_respawn_small",50
+"gc_icon_fair_warning","#PATCH_FAIR_WARNING","rui/gencard_icons/gc_icon_fair_warning","rui/gencard_icons/gc_icon_fair_warning_small",50
+"gc_icon_hawk","#PATCH_HAWK","rui/gencard_icons/gc_icon_hawk","rui/gencard_icons/gc_icon_hawk_small",0
+"gc_icon_ram","#PATCH_RAM","rui/gencard_icons/gc_icon_ram","rui/gencard_icons/gc_icon_ram_small",50
+"gc_icon_stab","#PATCH_STAB","rui/gencard_icons/gc_icon_stab","rui/gencard_icons/gc_icon_stab_small",50
+"gc_icon_bigcat","#PATCH_BIGCAT","rui/gencard_icons/gc_icon_bigcat","rui/gencard_icons/gc_icon_bigcat_small",50
+"gc_icon_wraith","#PATCH_WRAITH","rui/gencard_icons/gc_icon_wraith","rui/gencard_icons/gc_icon_wraith_small",50
+"gc_icon_stinger","#PATCH_STINGER","rui/gencard_icons/gc_icon_stinger","rui/gencard_icons/gc_icon_stinger_small",0
+"gc_icon_heartless","#PATCH_HEARTLESS","rui/gencard_icons/gc_icon_heartless","rui/gencard_icons/gc_icon_heartless_small",50
+"gc_icon_earthworm","#PATCH_EARTHWORM","rui/gencard_icons/gc_icon_earthworm","rui/gencard_icons/gc_icon_earthworm_small",50
+"gc_icon_prowler","#PATCH_PROWLER","rui/gencard_icons/gc_icon_prowler","rui/gencard_icons/gc_icon_prowler_small",0
+"gc_icon_ordnance","#PATCH_ORDNANCE","rui/gencard_icons/gc_icon_ordnance","rui/gencard_icons/gc_icon_ordnance_small",50
+"gc_icon_radar","#PATCH_RADAR","rui/gencard_icons/gc_icon_radar","rui/gencard_icons/gc_icon_radar_small",0
+"gc_icon_ramskull","#PATCH_RAMSKULL","rui/gencard_icons/gc_icon_ramskull","rui/gencard_icons/gc_icon_ramskull_small",0
+"gc_icon_hawkmoth","#PATCH_HAWKMOTH","rui/gencard_icons/gc_icon_hawkmoth","rui/gencard_icons/gc_icon_hawkmoth_small",50
+"gc_icon_fox","#PATCH_FOX","rui/gencard_icons/gc_icon_fox","rui/gencard_icons/gc_icon_fox_small",0
+"gc_icon_marksman","#PATCH_MARKSMAN","rui/gencard_icons/gc_icon_marksman","rui/gencard_icons/gc_icon_marksman_small",50
+"gc_icon_bunnyskull","#PATCH_BUNNYSKULL","rui/gencard_icons/gc_icon_bunnyskull","rui/gencard_icons/gc_icon_bunnyskull_small",50
+"gc_icon_falcon","#PATCH_FALCON","rui/gencard_icons/gc_icon_falcon","rui/gencard_icons/gc_icon_falcon_small",50
+"gc_icon_assault","#PATCH_ASSAULT","rui/gencard_icons/gc_icon_assault","rui/gencard_icons/gc_icon_assault_small",0
+"gc_icon_flying_skull","#PATCH_FLYING_SKULL","rui/gencard_icons/gc_icon_flying_skull","rui/gencard_icons/gc_icon_flying_skull_small",50
+"gc_icon_hammer","#PATCH_HAMMER","rui/gencard_icons/gc_icon_hammer","rui/gencard_icons/gc_icon_hammer_small",50
+"gc_icon_dragonfly","#PATCH_DRAGONFLY","rui/gencard_icons/gc_icon_dragonfly","rui/gencard_icons/gc_icon_dragonfly_small",0
+"gc_icon_striketwice","#PATCH_STRIKETWICE","rui/gencard_icons/gc_icon_striketwice","rui/gencard_icons/gc_icon_striketwice_small",0
+"gc_icon_waves","#PATCH_WAVES","rui/gencard_icons/gc_icon_waves","rui/gencard_icons/gc_icon_waves_small",0
+"gc_icon_fin","#PATCH_FIN","rui/gencard_icons/gc_icon_fin","rui/gencard_icons/gc_icon_fin_small",0
+"gc_icon_bee","#PATCH_BEE","rui/gencard_icons/gc_icon_bee","rui/gencard_icons/gc_icon_bee_small",50
+"gc_icon_wasp","#PATCH_WASP","rui/gencard_icons/gc_icon_wasp","rui/gencard_icons/gc_icon_wasp_small",0
+"gc_icon_bat","#PATCH_BAT","rui/gencard_icons/gc_icon_bat","rui/gencard_icons/gc_icon_bat_small",0
+"gc_icon_dataknife","#PATCH_DATAKNIFE","rui/gencard_icons/gc_icon_dataknife","rui/gencard_icons/gc_icon_dataknife_small",50
+"gc_icon_knife","#PATCH_KNIFE","rui/gencard_icons/gc_icon_knife","rui/gencard_icons/gc_icon_knife_small",50
+"gc_icon_widow","#PATCH_WIDOW","rui/gencard_icons/gc_icon_widow","rui/gencard_icons/gc_icon_widow_small",50
+"gc_icon_snake","#PATCH_SNAKE","rui/gencard_icons/gc_icon_snake","rui/gencard_icons/gc_icon_snake_small",50
+"gc_icon_scorpion","#PATCH_SCORPION","rui/gencard_icons/gc_icon_scorpion","rui/gencard_icons/gc_icon_scorpion_small",50
+"gc_icon_dragon","#PATCH_DRAGON","rui/gencard_icons/gc_icon_dragon","rui/gencard_icons/gc_icon_dragon_small",50
+"gc_icon_sgt_major","#PATCH_SGT_MAJOR","rui/gencard_icons/gc_icon_sgt_major","rui/gencard_icons/gc_icon_sgt_major_small",0
+"gc_icon_senior_sgt_e6","#PATCH_SENIOR_SGT_E6","rui/gencard_icons/gc_icon_senior_sgt_e6","rui/gencard_icons/gc_icon_senior_sgt_e6_small",50
+"gc_icon_senior_sgt","#PATCH_SENIOR_SGT","rui/gencard_icons/gc_icon_senior_sgt","rui/gencard_icons/gc_icon_senior_sgt_small",50
+"gc_icon_sgt","#PATCH_SGT","rui/gencard_icons/gc_icon_sgt","rui/gencard_icons/gc_icon_sgt_small",50
+"gc_icon_corporal","#PATCH_CORPORAL","rui/gencard_icons/gc_icon_corporal","rui/gencard_icons/gc_icon_corporal_small",50
+"gc_icon_pvt","#PATCH_PVT","rui/gencard_icons/gc_icon_private","rui/gencard_icons/gc_icon_private_small",50
+"gc_icon_gen0","#PATCH_GEN0","rui/gencard_icons/gc_icon_gen0","rui/gencard_icons/gc_icon_gen0_small",0
+"gc_icon_gen1","#PATCH_GEN1","rui/gencard_icons/gc_icon_gen1","rui/gencard_icons/gc_icon_gen1_small",0
+"gc_icon_gen2","#PATCH_GEN2","rui/gencard_icons/gc_icon_gen2","rui/gencard_icons/gc_icon_gen2_small",0
+"gc_icon_gen3","#PATCH_GEN3","rui/gencard_icons/gc_icon_gen3","rui/gencard_icons/gc_icon_gen3_small",0
+"gc_icon_gen4","#PATCH_GEN4","rui/gencard_icons/gc_icon_gen4","rui/gencard_icons/gc_icon_gen4_small",0
+"gc_icon_gen5","#PATCH_GEN5","rui/gencard_icons/gc_icon_gen5","rui/gencard_icons/gc_icon_gen5_small",0
+"gc_icon_gen6","#PATCH_GEN6","rui/gencard_icons/gc_icon_gen6","rui/gencard_icons/gc_icon_gen6_small",0
+"gc_icon_gen7","#PATCH_GEN7","rui/gencard_icons/gc_icon_gen7","rui/gencard_icons/gc_icon_gen7_small",0
+"gc_icon_gen8","#PATCH_GEN8","rui/gencard_icons/gc_icon_gen8","rui/gencard_icons/gc_icon_gen8_small",0
+"gc_icon_gen9","#PATCH_GEN9","rui/gencard_icons/gc_icon_gen9","rui/gencard_icons/gc_icon_gen9_small",0
+"gc_icon_respawn_dev","#PATCH_DEV","rui/gencard_icons/gc_icon_respawn_dev","rui/gencard_icons/gc_icon_respawn_dev_small",0
+"gc_icon_64","#PATCH_64","rui/gencard_icons/dlc1/gc_icon_64","rui/gencard_icons/dlc1/gc_icon_64_small",0
+"gc_icon_aces","#PATCH_ACES","rui/gencard_icons/dlc1/gc_icon_aces","rui/gencard_icons/dlc1/gc_icon_aces_small",0
+"gc_icon_alien","#PATCH_ALIEN","rui/gencard_icons/dlc1/gc_icon_alien","rui/gencard_icons/dlc1/gc_icon_alien_small",0
+"gc_icon_apex","#PATCH_APEX","rui/gencard_icons/dlc1/gc_icon_apex","rui/gencard_icons/dlc1/gc_icon_apex_small",0
+"gc_icon_ares","#PATCH_ARES","rui/gencard_icons/dlc1/gc_icon_ares","rui/gencard_icons/dlc1/gc_icon_ares_small",0
+"gc_icon_controller","#PATCH_CONTROLLER","rui/gencard_icons/dlc1/gc_icon_controller","rui/gencard_icons/dlc1/gc_icon_controller_small",0
+"gc_icon_drone","#PATCH_DRONE","rui/gencard_icons/dlc1/gc_icon_drone","rui/gencard_icons/dlc1/gc_icon_drone_small",0
+"gc_icon_heartbreaker","#PATCH_HEARTBREAKER","rui/gencard_icons/dlc1/gc_icon_heartbreaker","rui/gencard_icons/dlc1/gc_icon_heartbreaker_small",0
+"gc_icon_hexes","#PATCH_HEXES","rui/gencard_icons/dlc1/gc_icon_hexes","rui/gencard_icons/dlc1/gc_icon_hexes_small",0
+"gc_icon_kodai","#PATCH_KODAI","rui/gencard_icons/dlc1/gc_icon_kodai","rui/gencard_icons/dlc1/gc_icon_kodai_small",0
+"gc_icon_lastimosa","#PATCH_LASTIMOSA","rui/gencard_icons/dlc1/gc_icon_lastimosa","rui/gencard_icons/dlc1/gc_icon_lastimosa_small",0
+"gc_icon_lawai","#PATCH_LAWAI","rui/gencard_icons/dlc1/gc_icon_lawai","rui/gencard_icons/dlc1/gc_icon_lawai_small",0
+"gc_icon_mcor","#PATCH_MCOR","rui/gencard_icons/dlc1/gc_icon_mcor","rui/gencard_icons/dlc1/gc_icon_mcor_small",0
+"gc_icon_phoenix","#PATCH_PHOENIX","rui/gencard_icons/dlc1/gc_icon_phoenix","rui/gencard_icons/dlc1/gc_icon_phoenix_small",0
+"gc_icon_pilot","#PATCH_PILOT","rui/gencard_icons/dlc1/gc_icon_pilot","rui/gencard_icons/dlc1/gc_icon_pilot_small",0
+"gc_icon_robot","#PATCH_ROBOT","rui/gencard_icons/dlc1/gc_icon_robot","rui/gencard_icons/dlc1/gc_icon_robot_small",0
+"gc_icon_sentry","#PATCH_SENTRY","rui/gencard_icons/dlc1/gc_icon_sentry","rui/gencard_icons/dlc1/gc_icon_sentry_small",0
+"gc_icon_super_spectre","#PATCH_SUPER_SPECTRE","rui/gencard_icons/dlc1/gc_icon_super_spectre","rui/gencard_icons/dlc1/gc_icon_super_spectre_small",0
+"gc_icon_vinson","#PATCH_VINSON","rui/gencard_icons/dlc1/gc_icon_vinson","rui/gencard_icons/dlc1/gc_icon_vinson_small",0
+"gc_icon_wonyeon","#PATCH_WONYEON","rui/gencard_icons/dlc1/gc_icon_wonyeon","rui/gencard_icons/dlc1/gc_icon_wonyeon_small",0
+"gc_icon_b3_wing","#PATCH_B3_WING","rui/gencard_icons/gc_icon_b3_wing","rui/gencard_icons/gc_icon_b3_wing_small",0
+"gc_icon_balance","#PATCH_BALANCE","rui/gencard_icons/dlc3/gc_icon_balance","rui/gencard_icons/dlc3/gc_icon_balance_small",0
+"gc_icon_boot","#PATCH_BOOT","rui/gencard_icons/dlc3/gc_icon_boot","rui/gencard_icons/dlc3/gc_icon_boot_small",0
+"gc_icon_bt_eye","#PATCH_BT_EYE","rui/gencard_icons/dlc3/gc_icon_bt_eye","rui/gencard_icons/dlc3/gc_icon_bt_eye_small",0
+"gc_icon_peace","#PATCH_PEACE","rui/gencard_icons/dlc3/gc_icon_peace","rui/gencard_icons/dlc3/gc_icon_peace_small",0
+"gc_icon_pilot2","#PATCH_PILOT2","rui/gencard_icons/dlc3/gc_icon_pilot","rui/gencard_icons/dlc3/gc_icon_pilot_small",0
+"gc_icon_srs","#PATCH_SRS","rui/gencard_icons/dlc3/gc_icon_srs","rui/gencard_icons/dlc3/gc_icon_srs_small",0
+"gc_icon_starline","#PATCH_STARLINE","rui/gencard_icons/dlc3/gc_icon_starline","rui/gencard_icons/dlc3/gc_icon_starline_small",0
+"gc_icon_thumbdown","#PATCH_THUMBDOWN","rui/gencard_icons/dlc3/gc_icon_thumbdown","rui/gencard_icons/dlc3/gc_icon_thumbdown_small",0
+"gc_icon_thumbup","#PATCH_THUMBUP","rui/gencard_icons/dlc3/gc_icon_thumbup","rui/gencard_icons/dlc3/gc_icon_thumbup_small",0
+"gc_icon_vanguard","#PATCH_VANGUARD","rui/gencard_icons/dlc3/gc_icon_vanguard","rui/gencard_icons/dlc3/gc_icon_vanguard_small",0
+"gc_icon_deuce","#PATCH_DEUCE","rui/gencard_icons/dlc2/gc_icon_deuce","rui/gencard_icons/dlc2/gc_icon_deuce_small",0
+"gc_icon_down","#PATCH_DOWN","rui/gencard_icons/dlc2/gc_icon_down","rui/gencard_icons/dlc2/gc_icon_down_small",50
+"gc_icon_joy","#PATCH_JOY","rui/gencard_icons/dlc2/gc_icon_joy","rui/gencard_icons/dlc2/gc_icon_joy_small",50
+"gc_icon_mushroom","#PATCH_MUSHROOM","rui/gencard_icons/dlc2/gc_icon_mushroom","rui/gencard_icons/dlc2/gc_icon_mushroom_small",0
+"gc_icon_prowlerhead","#PATCH_PROWLERHEAD","rui/gencard_icons/dlc2/gc_icon_prowlerhead","rui/gencard_icons/dlc2/gc_icon_prowlerhead_small",0
+"gc_icon_scythe","#PATCH_SCYTHE","rui/gencard_icons/dlc2/gc_icon_scythe","rui/gencard_icons/dlc2/gc_icon_scythe_small",0
+"gc_icon_shuriken","#PATCH_SHURIKEN","rui/gencard_icons/dlc2/gc_icon_shuriken","rui/gencard_icons/dlc2/gc_icon_shuriken_small",0
+"gc_icon_squid","#PATCH_SQUID","rui/gencard_icons/dlc2/gc_icon_squid","rui/gencard_icons/dlc2/gc_icon_squid_small",0
+"gc_icon_threebullets","#PATCH_THREEBULLETS","rui/gencard_icons/dlc2/gc_icon_threebullets","rui/gencard_icons/dlc2/gc_icon_threebullets_small",0
+"gc_icon_tick","#PATCH_TICK","rui/gencard_icons/dlc2/gc_icon_tick","rui/gencard_icons/dlc2/gc_icon_tick_small",0
+"gc_icon_buzzsaw","#PATCH_BUZZSAW","rui/gencard_icons/dlc3/gc_icon_buzzsaw","rui/gencard_icons/dlc3/gc_icon_buzzsaw_small",0
+"gc_icon_crossed_lighting","#PATCH_CROSSED_LIGHTING","rui/gencard_icons/dlc3/gc_icon_crossed_lighting","rui/gencard_icons/dlc3/gc_icon_crossed_lighting_small",0
+"gc_icon_flying_bullet","#PATCH_FLYING_BULLET","rui/gencard_icons/dlc3/gc_icon_flying_bullet","rui/gencard_icons/dlc3/gc_icon_flying_bullet_small",0
+"gc_icon_hammer2","#PATCH_HAMMER2","rui/gencard_icons/dlc3/gc_icon_hammer","rui/gencard_icons/dlc3/gc_icon_hammer_small",0
+"gc_icon_keyboard","#PATCH_KEYBOARD","rui/gencard_icons/dlc3/gc_icon_keyboard","rui/gencard_icons/dlc3/gc_icon_keyboard_small",0
+"gc_icon_lightbulb","#PATCH_LIGHTBULB","rui/gencard_icons/dlc3/gc_icon_lightbulb","rui/gencard_icons/dlc3/gc_icon_lightbulb_small",0
+"gc_icon_narwhal","#PATCH_NARWHAL","rui/gencard_icons/dlc3/gc_icon_narwhal","rui/gencard_icons/dlc3/gc_icon_narwhal_small",0
+"gc_icon_robot_eye","#PATCH_ROBOT_EYE","rui/gencard_icons/dlc3/gc_icon_robot_eye","rui/gencard_icons/dlc3/gc_icon_robot_eye_small",0
+"gc_icon_taco","#PATCH_TACO","rui/gencard_icons/dlc3/gc_icon_taco","rui/gencard_icons/dlc3/gc_icon_taco_small",0
+"gc_icon_treble","#PATCH_TREBLE","rui/gencard_icons/dlc3/gc_icon_treble","rui/gencard_icons/dlc3/gc_icon_treble_small",0
+"gc_icon_monarch","#PATCH_MONARCH","rui/gencard_icons/dlc4/gc_icon_monarch","rui/gencard_icons/dlc4/gc_icon_monarch_small",0
+"gc_icon_mrvn","#PATCH_MRVN","rui/gencard_icons/dlc4/gc_icon_mrvn","rui/gencard_icons/dlc4/gc_icon_mrvn_small",0
+"gc_icon_blank","#PATCH_BLANK","rui/gencard_icons/gc_icon_blank","rui/gencard_icons/gc_icon_blank_small",0
+"gc_icon_monarch_dlc5","#PATCH_MONARCH_DLC5","rui/gencard_icons/dlc5/gc_icon_monarch","rui/gencard_icons/dlc5/gc_icon_monarch_small",0
+"gc_icon_militia","#PATCH_MILITIA","rui/gencard_icons/dlc5/gc_icon_militia","rui/gencard_icons/dlc5/gc_icon_militia_small",0
+"gc_icon_militia_alt","#PATCH_MILITIA_ALT","rui/gencard_icons/dlc5/gc_icon_militia_alt","rui/gencard_icons/dlc5/gc_icon_militia_alt_small",0
+"gc_icon_imc","#PATCH_IMC","rui/gencard_icons/dlc5/gc_icon_imc","rui/gencard_icons/dlc5/gc_icon_imc_small",0
+"gc_icon_hammond","#PATCH_HAMMOND","rui/gencard_icons/dlc5/gc_icon_hammond","rui/gencard_icons/dlc5/gc_icon_hammond_small",0
+"gc_icon_tri_chevron","#PATCH_TRI_CHEVRON","rui/gencard_icons/dlc5/gc_icon_tri_chevron","rui/gencard_icons/dlc5/gc_icon_tri_chevron_small",0
+"gc_icon_pilot_circle","#PATCH_PILOT_CIRCLE","rui/gencard_icons/dlc5/gc_icon_pilot_circle","rui/gencard_icons/dlc5/gc_icon_pilot_circle_small",0
+"gc_icon_x","#PATCH_X","rui/gencard_icons/dlc5/gc_icon_x","rui/gencard_icons/dlc5/gc_icon_x_small",0
+"gc_icon_nessie","#PATCH_NESSIE","rui/gencard_icons/dlc5/gc_icon_nessie","rui/gencard_icons/dlc5/gc_icon_nessie_small",0
+"gc_icon_spicy","#PATCH_SPICY","rui/gencard_icons/dlc5/gc_icon_spicy","rui/gencard_icons/dlc5/gc_icon_spicy_small",0
+"gc_icon_crown","#PATCH_CROWN","rui/gencard_icons/dlc5/gc_icon_crown","rui/gencard_icons/dlc5/gc_icon_crown_small",0
+"gc_icon_pawn","#PATCH_PAWN","rui/gencard_icons/dlc5/gc_icon_pawn","rui/gencard_icons/dlc5/gc_icon_pawn_small",0
+"gc_icon_excite","#PATCH_EXCITE","rui/gencard_icons/dlc5/gc_icon_excite","rui/gencard_icons/dlc5/gc_icon_excite_small",0
+"gc_icon_duck","#PATCH_DUCK","rui/gencard_icons/dlc5/gc_icon_duck","rui/gencard_icons/dlc5/gc_icon_duck_small",0
+"gc_icon_sock","#PATCH_SOCK","rui/gencard_icons/dlc5/gc_icon_sock","rui/gencard_icons/dlc5/gc_icon_sock_small",0
+"gc_icon_rabbit","#PATCH_RABBIT","rui/gencard_icons/dlc5/gc_icon_rabbit","rui/gencard_icons/dlc5/gc_icon_rabbit_small",0
+"gc_icon_peanut","#PATCH_PEANUT","rui/gencard_icons/dlc5/gc_icon_peanut","rui/gencard_icons/dlc5/gc_icon_peanut_small",0
+"gc_icon_clock","#PATCH_CLOCK","rui/gencard_icons/dlc5/gc_icon_clock","rui/gencard_icons/dlc5/gc_icon_clock_small",0
+"gc_icon_shamrock","#PATCH_SHAMROCK","rui/gencard_icons/dlc5/gc_icon_shamrock","rui/gencard_icons/dlc5/gc_icon_shamrock_small",0
+"gc_icon_trident","#PATCH_TRIDENT","rui/gencard_icons/dlc5/gc_icon_trident","rui/gencard_icons/dlc5/gc_icon_trident_small",0
+"gc_icon_fd_normal","#PATCH_FD_NORMAL","rui/gencard_icons/fd/gc_icon_fd_normal","rui/gencard_icons/fd/gc_icon_fd_normal_small",0
+"gc_icon_fd_hard","#PATCH_FD_HARD","rui/gencard_icons/fd/gc_icon_fd_hard","rui/gencard_icons/fd/gc_icon_fd_hard_small",0
+"gc_icon_fd_master","#PATCH_FD_MASTER","rui/gencard_icons/fd/gc_icon_fd_master","rui/gencard_icons/fd/gc_icon_fd_master_small",0
+"gc_icon_fd_insane","#PATCH_FD_INSANE","rui/gencard_icons/fd/gc_icon_fd_insane","rui/gencard_icons/fd/gc_icon_fd_insane_small",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/camo_skins.csv b/Northstar.CustomServers/mod/scripts/datatable/camo_skins.csv
new file mode 100644
index 00000000..2773f4b6
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/camo_skins.csv
@@ -0,0 +1,161 @@
+itemRef,pilotRef,titanRef,type,image,name,description,pilotCost,titanCost,pilotWeaponCost,titanWeaponCost,category
+"camo_skin00","pilot_camo_skin00","titan_camo_skin00","CAMO","rui/menu/common/no_art","#CAMO_DEFAULT","#DEFAULT_DESC",0,0,0,0,1
+"camo_skin01","pilot_camo_skin01","titan_camo_skin01","CAMO","models/camo_skins/camo_skin01_col","#CAMO_SKIN_01","#CAMO_SKIN_01_DESC",0,0,0,0,1
+"camo_skin02","pilot_camo_skin02","titan_camo_skin02","CAMO","models/camo_skins/camo_skin02_col","#CAMO_SKIN_02","#CAMO_SKIN_02_DESC",0,0,0,0,1
+"camo_skin03","pilot_camo_skin03","titan_camo_skin03","CAMO","models/camo_skins/camo_skin03_col","#CAMO_SKIN_03","#CAMO_SKIN_03_DESC",0,0,0,0,1
+"camo_skin04","pilot_camo_skin04","titan_camo_skin04","CAMO","models/camo_skins/camo_skin04_col","#CAMO_SKIN_04","#CAMO_SKIN_04_DESC",150,100,75,50,3
+"camo_skin05","pilot_camo_skin05","titan_camo_skin05","CAMO","models/camo_skins/camo_skin05_col","#CAMO_SKIN_05","#CAMO_SKIN_05_DESC",150,100,75,50,3
+"camo_skin06","pilot_camo_skin06","titan_camo_skin06","CAMO","models/camo_skins/camo_skin06_col","#CAMO_SKIN_06","#CAMO_SKIN_06_DESC",150,100,75,50,4
+"camo_skin07","pilot_camo_skin07","titan_camo_skin07","CAMO","models/camo_skins/camo_skin07_col","#CAMO_SKIN_07","#CAMO_SKIN_07_DESC",0,0,0,0,2
+"camo_skin08","pilot_camo_skin08","titan_camo_skin08","CAMO","models/camo_skins/camo_skin08_col","#CAMO_SKIN_08","#CAMO_SKIN_08_DESC",150,100,75,50,4
+"camo_skin09","pilot_camo_skin09","titan_camo_skin09","CAMO","models/camo_skins/camo_skin09_col","#CAMO_SKIN_09","#CAMO_SKIN_09_DESC",0,0,0,0,1
+"camo_skin10","pilot_camo_skin10","titan_camo_skin10","CAMO","models/camo_skins/camo_skin10_col","#CAMO_SKIN_10","#CAMO_SKIN_10_DESC",0,0,0,0,1
+"camo_skin11","pilot_camo_skin11","titan_camo_skin11","CAMO","models/camo_skins/camo_skin11_col","#CAMO_SKIN_11","#CAMO_SKIN_11_DESC",150,100,75,50,4
+"camo_skin12","pilot_camo_skin12","titan_camo_skin12","CAMO","models/camo_skins/camo_skin12_col","#CAMO_SKIN_12","#CAMO_SKIN_12_DESC",150,100,75,50,4
+"camo_skin13","pilot_camo_skin13","titan_camo_skin13","CAMO","models/camo_skins/camo_skin13_col","#CAMO_SKIN_13","#CAMO_SKIN_13_DESC",0,0,0,0,2
+"camo_skin14","pilot_camo_skin14","titan_camo_skin14","CAMO","models/camo_skins/camo_skin14_col","#CAMO_SKIN_14","#CAMO_SKIN_14_DESC",0,0,0,0,1
+"camo_skin15","pilot_camo_skin15","titan_camo_skin15","CAMO","models/camo_skins/camo_skin15_col","#CAMO_SKIN_15","#CAMO_SKIN_15_DESC",0,0,0,0,1
+"camo_skin16","pilot_camo_skin16","titan_camo_skin16","CAMO","models/camo_skins/camo_skin16_col","#CAMO_SKIN_16","#CAMO_SKIN_16_DESC",0,0,0,0,1
+"camo_skin17","pilot_camo_skin17","titan_camo_skin17","CAMO","models/camo_skins/camo_skin17_col","#CAMO_SKIN_17","#CAMO_SKIN_17_DESC",0,0,0,0,1
+"camo_skin18","pilot_camo_skin18","titan_camo_skin18","CAMO","models/camo_skins/camo_skin18_col","#CAMO_SKIN_18","#CAMO_SKIN_18_DESC",0,0,0,0,1
+"camo_skin19","pilot_camo_skin19","titan_camo_skin19","CAMO","models/camo_skins/camo_skin19_col","#CAMO_SKIN_19","#CAMO_SKIN_19_DESC",0,0,0,0,1
+"camo_skin20","pilot_camo_skin20","titan_camo_skin20","CAMO","models/camo_skins/camo_skin20_col","#CAMO_SKIN_20","#CAMO_SKIN_20_DESC",150,100,75,50,3
+"camo_skin21","pilot_camo_skin21","titan_camo_skin21","CAMO","models/camo_skins/camo_skin21_col","#CAMO_SKIN_21","#CAMO_SKIN_21_DESC",150,100,75,50,3
+"camo_skin22","pilot_camo_skin22","titan_camo_skin22","CAMO","models/camo_skins/camo_skin22_col","#CAMO_SKIN_22","#CAMO_SKIN_22_DESC",150,100,75,50,3
+"camo_skin23","pilot_camo_skin23","titan_camo_skin23","CAMO","models/camo_skins/camo_skin23_col","#CAMO_SKIN_23","#CAMO_SKIN_23_DESC",150,100,75,50,3
+"camo_skin24","pilot_camo_skin24","titan_camo_skin24","CAMO","models/camo_skins/camo_skin24_col","#CAMO_SKIN_24","#CAMO_SKIN_24_DESC",0,0,0,0,1
+"camo_skin25","pilot_camo_skin25","titan_camo_skin25","CAMO","models/camo_skins/camo_skin25_col","#CAMO_SKIN_25","#CAMO_SKIN_25_DESC",0,0,0,0,1
+"camo_skin26","pilot_camo_skin26","titan_camo_skin26","CAMO","models/camo_skins/camo_skin26_col","#CAMO_SKIN_26","#CAMO_SKIN_26_DESC",0,0,0,0,1
+"camo_skin27","pilot_camo_skin27","titan_camo_skin27","CAMO","models/camo_skins/camo_skin27_col","#CAMO_SKIN_27","#CAMO_SKIN_27_DESC",0,0,0,0,1
+"camo_skin28","pilot_camo_skin28","titan_camo_skin28","CAMO","models/camo_skins/camo_skin28_col","#CAMO_SKIN_28","#CAMO_SKIN_28_DESC",150,100,75,50,4
+"camo_skin29","pilot_camo_skin29","titan_camo_skin29","CAMO","models/camo_skins/camo_skin29_col","#CAMO_SKIN_29","#CAMO_SKIN_29_DESC",150,100,75,50,4
+"camo_skin30","pilot_camo_skin30","titan_camo_skin30","CAMO","models/camo_skins/camo_skin30_col","#CAMO_SKIN_30","#CAMO_SKIN_30_DESC",0,0,0,0,1
+"camo_skin31","pilot_camo_skin31","titan_camo_skin31","CAMO","models/camo_skins/camo_skin31_col","#CAMO_SKIN_31","#CAMO_SKIN_31_DESC",0,0,0,0,1
+"camo_skin32","pilot_camo_skin32","titan_camo_skin32","CAMO","models/camo_skins/camo_skin32_col","#CAMO_SKIN_32","#CAMO_SKIN_32_DESC",150,100,75,50,4
+"camo_skin33","pilot_camo_skin33","titan_camo_skin33","CAMO","models/camo_skins/camo_skin33_col","#CAMO_SKIN_33","#CAMO_SKIN_33_DESC",150,100,75,50,4
+"camo_skin34","pilot_camo_skin34","titan_camo_skin34","CAMO","models/camo_skins/camo_skin34_col","#CAMO_SKIN_34","#CAMO_SKIN_34_DESC",150,100,75,50,4
+"camo_skin35","pilot_camo_skin35","titan_camo_skin35","CAMO","models/camo_skins/camo_skin35_col","#CAMO_SKIN_35","#CAMO_SKIN_35_DESC",150,100,75,50,3
+"camo_skin36","pilot_camo_skin36","titan_camo_skin36","CAMO","models/camo_skins/camo_skin36_col","#CAMO_SKIN_36","#CAMO_SKIN_36_DESC",150,100,75,50,3
+"camo_skin37","pilot_camo_skin37","titan_camo_skin37","CAMO","models/camo_skins/camo_skin37_col","#CAMO_SKIN_37","#CAMO_SKIN_37_DESC",150,100,75,50,4
+"camo_skin38","pilot_camo_skin38","titan_camo_skin38","CAMO","models/camo_skins/camo_skin38_col","#CAMO_SKIN_38","#CAMO_SKIN_38_DESC",150,100,75,50,4
+"camo_skin39","pilot_camo_skin39","titan_camo_skin39","CAMO","models/camo_skins/camo_skin39_col","#CAMO_SKIN_39","#CAMO_SKIN_39_DESC",150,100,75,50,4
+"camo_skin40","pilot_camo_skin40","titan_camo_skin40","CAMO","models/camo_skins/camo_skin40_col","#CAMO_SKIN_40","#CAMO_SKIN_40_DESC",150,100,75,50,3
+"camo_skin41","pilot_camo_skin41","titan_camo_skin41","CAMO","models/camo_skins/camo_skin41_col","#CAMO_SKIN_41","#CAMO_SKIN_41_DESC",150,100,75,50,3
+"camo_skin42","pilot_camo_skin42","titan_camo_skin42","CAMO","models/camo_skins/camo_skin42_col","#CAMO_SKIN_42","#CAMO_SKIN_42_DESC",150,100,75,50,3
+"camo_skin43","pilot_camo_skin43","titan_camo_skin43","CAMO","models/camo_skins/camo_skin43_col","#CAMO_SKIN_43","#CAMO_SKIN_43_DESC",150,100,75,50,3
+"camo_skin44","pilot_camo_skin44","titan_camo_skin44","CAMO","models/camo_skins/camo_skin44_col","#CAMO_SKIN_44","#CAMO_SKIN_44_DESC",150,100,75,50,3
+"camo_skin45","pilot_camo_skin45","titan_camo_skin45","CAMO","models/camo_skins/camo_skin45_col","#CAMO_SKIN_45","#CAMO_SKIN_45_DESC",150,100,75,50,3
+"camo_skin46","pilot_camo_skin46","titan_camo_skin46","CAMO","models/camo_skins/camo_skin46_col","#CAMO_SKIN_46","#CAMO_SKIN_46_DESC",150,100,75,50,3
+"camo_skin47","pilot_camo_skin47","titan_camo_skin47","CAMO","models/camo_skins/camo_skin47_col","#CAMO_SKIN_47","#CAMO_SKIN_47_DESC",150,100,75,50,4
+"camo_skin48","pilot_camo_skin48","titan_camo_skin48","CAMO","models/camo_skins/camo_skin48_col","#CAMO_SKIN_48","#CAMO_SKIN_48_DESC",150,100,75,50,4
+"camo_skin49","pilot_camo_skin49","titan_camo_skin49","CAMO","models/camo_skins/camo_skin49_col","#CAMO_SKIN_49","#CAMO_SKIN_49_DESC",150,100,75,50,4
+"camo_skin50","pilot_camo_skin50","titan_camo_skin50","CAMO","models/camo_skins/camo_skin50_col","#CAMO_SKIN_50","#CAMO_SKIN_50_DESC",150,100,75,50,4
+"camo_skin51","pilot_camo_skin51","titan_camo_skin51","CAMO","models/camo_skins/camo_skin51_col","#CAMO_SKIN_51","#CAMO_SKIN_51_DESC",150,100,75,50,4
+"camo_skin52","pilot_camo_skin52","titan_camo_skin52","CAMO","models/camo_skins/camo_skin52_col","#CAMO_SKIN_52","#CAMO_SKIN_52_DESC",150,100,75,50,4
+"camo_skin53","pilot_camo_skin53","titan_camo_skin53","CAMO","models/camo_skins/camo_skin53_col","#CAMO_SKIN_53","#CAMO_SKIN_53_DESC",150,100,75,50,4
+"camo_skin54","pilot_camo_skin54","titan_camo_skin54","CAMO","models/camo_skins/camo_skin54_col","#CAMO_SKIN_54","#CAMO_SKIN_54_DESC",150,100,75,50,4
+"camo_skin55","pilot_camo_skin55","titan_camo_skin55","CAMO","models/camo_skins/camo_skin55_col","#CAMO_SKIN_55","#CAMO_SKIN_55_DESC",0,0,0,0,2
+"camo_skin56","pilot_camo_skin56","titan_camo_skin56","CAMO","models/camo_skins/camo_skin56_col","#CAMO_SKIN_56","#CAMO_SKIN_56_DESC",0,0,0,0,2
+"camo_skin57","pilot_camo_skin57","titan_camo_skin57","CAMO","models/camo_skins/camo_skin57_col","#CAMO_SKIN_57","#CAMO_SKIN_57_DESC",0,0,0,0,2
+"camo_skin58","pilot_camo_skin58","titan_camo_skin58","CAMO","models/camo_skins/camo_skin58_col","#CAMO_SKIN_58","#CAMO_SKIN_58_DESC",0,0,0,0,2
+"camo_skin59","pilot_camo_skin59","titan_camo_skin59","CAMO","models/camo_skins/camo_skin59_col","#CAMO_SKIN_59","#CAMO_SKIN_59_DESC",0,0,0,0,2
+"camo_skin60","pilot_camo_skin60","titan_camo_skin60","CAMO","models/camo_skins/camo_skin60_col","#CAMO_SKIN_60","#CAMO_SKIN_60_DESC",0,0,0,0,2
+"camo_skin61","pilot_camo_skin61","titan_camo_skin61","CAMO","models/camo_skins/camo_skin61_col","#CAMO_SKIN_61","#CAMO_SKIN_61_DESC",0,0,0,0,2
+"camo_skin62","pilot_camo_skin62","titan_camo_skin62","CAMO","models/camo_skins/camo_skin62_col","#CAMO_SKIN_62","#CAMO_SKIN_62_DESC",0,0,0,0,2
+"camo_skin63","pilot_camo_skin63","titan_camo_skin63","CAMO","models/camo_skins/camo_skin63_col","#CAMO_SKIN_63","#CAMO_SKIN_63_DESC",0,0,0,0,2
+"camo_skin64","pilot_camo_skin64","titan_camo_skin64","CAMO","models/camo_skins/camo_skin64_col","#CAMO_SKIN_64","#CAMO_SKIN_64_DESC",0,0,0,0,2
+"camo_skin65","pilot_camo_skin65","titan_camo_skin65","CAMO","models/camo_skins/camo_skin65_col","#CAMO_SKIN_65","#CAMO_SKIN_65_DESC",0,0,0,0,2
+"camo_skin66","pilot_camo_skin66","titan_camo_skin66","CAMO","models/camo_skins/camo_skin66_col","#CAMO_SKIN_66","#CAMO_SKIN_66_DESC",0,0,0,0,2
+"camo_skin67","pilot_camo_skin67","titan_camo_skin67","CAMO","models/camo_skins/camo_skin67_col","#CAMO_SKIN_67","#CAMO_SKIN_67_DESC",0,0,0,0,2
+"camo_skin68","pilot_camo_skin68","titan_camo_skin68","CAMO","models/camo_skins/camo_skin68_col","#CAMO_SKIN_68","#CAMO_SKIN_68_DESC",150,100,75,50,4
+"camo_skin69","pilot_camo_skin69","titan_camo_skin69","CAMO","models/camo_skins/camo_skin69_col","#CAMO_SKIN_69","#CAMO_SKIN_69_DESC",150,100,75,50,4
+"camo_skin70","pilot_camo_skin70","titan_camo_skin70","CAMO","models/camo_skins/camo_skin70_col","#CAMO_SKIN_70","#CAMO_SKIN_70_DESC",150,100,75,50,4
+"camo_skin71","pilot_camo_skin71","titan_camo_skin71","CAMO","models/camo_skins/camo_skin71_col","#CAMO_SKIN_71","#CAMO_SKIN_71_DESC",150,100,75,50,4
+"camo_skin72","pilot_camo_skin72","titan_camo_skin72","CAMO","models/camo_skins/camo_skin72_col","#CAMO_SKIN_72","#CAMO_SKIN_72_DESC",150,100,75,50,4
+"camo_skin73","pilot_camo_skin73","titan_camo_skin73","CAMO","models/camo_skins/camo_skin73_col","#CAMO_SKIN_73","#CAMO_SKIN_73_DESC",150,100,75,50,4
+"camo_skin74","pilot_camo_skin74","titan_camo_skin74","CAMO","models/camo_skins/camo_skin74_col","#CAMO_SKIN_74","#CAMO_SKIN_74_DESC",150,100,75,50,4
+"camo_skin75","pilot_camo_skin75","titan_camo_skin75","CAMO","models/camo_skins/camo_skin75_col","#CAMO_SKIN_75","#CAMO_SKIN_75_DESC",150,100,75,50,4
+"camo_skin76","pilot_camo_skin76","titan_camo_skin76","CAMO","models/camo_skins/camo_skin76_col","#CAMO_SKIN_76","#CAMO_SKIN_76_DESC",150,100,75,50,3
+"camo_skin77","pilot_camo_skin77","titan_camo_skin77","CAMO","models/camo_skins/camo_skin77_col","#CAMO_SKIN_77","#CAMO_SKIN_77_DESC",150,100,75,50,3
+"camo_skin78","pilot_camo_skin78","titan_camo_skin78","CAMO","models/camo_skins/camo_skin78_col","#CAMO_SKIN_78","#CAMO_SKIN_78_DESC",150,100,75,50,3
+"camo_skin79","pilot_camo_skin79","titan_camo_skin79","CAMO","models/camo_skins/camo_skin79_col","#CAMO_SKIN_79","#CAMO_SKIN_79_DESC",150,100,75,50,3
+"camo_skin80","pilot_camo_skin80","titan_camo_skin80","CAMO","models/camo_skins/camo_skin80_col","#CAMO_SKIN_80","#CAMO_SKIN_80_DESC",150,100,75,50,3
+"camo_skin81","pilot_camo_skin81","titan_camo_skin81","CAMO","models/camo_skins/camo_skin81_col","#CAMO_SKIN_81","#CAMO_SKIN_81_DESC",0,0,0,0,1
+"camo_skin82","pilot_camo_skin82","titan_camo_skin82","CAMO","models/camo_skins/camo_skin82_col","#CAMO_SKIN_82","#CAMO_SKIN_82_DESC",0,0,0,0,1
+"camo_skin83","pilot_camo_skin83","titan_camo_skin83","CAMO","models/camo_skins/camo_skin83_col","#CAMO_SKIN_83","#CAMO_SKIN_83_DESC",0,0,0,0,1
+"camo_skin84","pilot_camo_skin84","titan_camo_skin84","CAMO","models/camo_skins/camo_skin84_col","#CAMO_SKIN_84","#CAMO_SKIN_84_DESC",150,100,75,50,4
+"camo_skin85","pilot_camo_skin85","titan_camo_skin85","CAMO","models/camo_skins/camo_skin85_col","#CAMO_SKIN_85","#CAMO_SKIN_85_DESC",0,0,0,0,2
+"camo_skin86","pilot_camo_skin86","titan_camo_skin86","CAMO","models/camo_skins/camo_skin86_col","#CAMO_SKIN_86","#CAMO_SKIN_86_DESC",150,100,75,50,4
+"camo_skin87","pilot_camo_skin87","titan_camo_skin87","CAMO","models/camo_skins/camo_skin87_col","#CAMO_SKIN_87","#CAMO_SKIN_87_DESC",0,0,0,0,2
+"camo_skin88","pilot_camo_skin88","titan_camo_skin88","CAMO","models/camo_skins/camo_skin88_col","#CAMO_SKIN_88","#CAMO_SKIN_88_DESC",150,100,75,50,4
+"camo_skin89","pilot_camo_skin89","titan_camo_skin89","CAMO","models/camo_skins/camo_skin89_col","#CAMO_SKIN_89","#CAMO_SKIN_89_DESC",150,100,75,50,4
+"camo_skin90","pilot_camo_skin90","titan_camo_skin90","CAMO","models/camo_skins/camo_skin90_col","#CAMO_SKIN_90","#CAMO_SKIN_90_DESC",150,100,75,50,4
+"camo_skin91","pilot_camo_skin91","titan_camo_skin91","CAMO","models/camo_skins/camo_skin91_col","#CAMO_SKIN_91","#CAMO_SKIN_91_DESC",0,0,0,0,2
+"camo_skin92","pilot_camo_skin92","titan_camo_skin92","CAMO","models/camo_skins/camo_skin92_col","#CAMO_SKIN_92","#CAMO_SKIN_92_DESC",0,0,0,0,1
+"camo_skin93","pilot_camo_skin93","titan_camo_skin93","CAMO","models/camo_skins/camo_skin93_col","#CAMO_SKIN_93","#CAMO_SKIN_93_DESC",0,0,0,0,2
+"camo_skin94","pilot_camo_skin94","titan_camo_skin94","CAMO","models/camo_skins/camo_skin94_col","#CAMO_SKIN_94","#CAMO_SKIN_94_DESC",0,0,0,0,2
+"camo_skin95","pilot_camo_skin95","titan_camo_skin95","CAMO","models/camo_skins/camo_skin95_col","#CAMO_SKIN_95","#CAMO_SKIN_95_DESC",150,100,75,50,4
+"camo_skin96","pilot_camo_skin96","titan_camo_skin96","CAMO","models/camo_skins/camo_skin96_col","#CAMO_SKIN_96","#CAMO_SKIN_96_DESC",150,100,75,50,4
+"camo_skin97","pilot_camo_skin97","titan_camo_skin97","CAMO","models/camo_skins/camo_skin97_col","#CAMO_SKIN_97","#CAMO_SKIN_97_DESC",0,0,0,0,1
+"camo_skin98","pilot_camo_skin98","titan_camo_skin98","CAMO","models/camo_skins/camo_skin98_col","#CAMO_SKIN_98","#CAMO_SKIN_98_DESC",150,100,75,50,4
+"camo_skin99","pilot_camo_skin99","titan_camo_skin99","CAMO","models/camo_skins/camo_skin99_col","#CAMO_SKIN_99","#CAMO_SKIN_99_DESC",150,100,75,50,4
+"camo_skin101","pilot_camo_skin101","titan_camo_skin101","CAMO","models/camo_skins/camo_skin101_col","#CAMO_SKIN_101","#CAMO_SKIN_101_DESC",0,0,0,0,5
+"camo_skin102","pilot_camo_skin102","titan_camo_skin102","CAMO","models/camo_skins/camo_skin102_col","#CAMO_SKIN_102","#CAMO_SKIN_102_DESC",0,0,0,0,5
+"camo_skin103","pilot_camo_skin103","titan_camo_skin103","CAMO","models/camo_skins/camo_skin103_col","#CAMO_SKIN_103","#CAMO_SKIN_103_DESC",0,0,0,0,5
+"camo_skin104","pilot_camo_skin104","titan_camo_skin104","CAMO","models/camo_skins/camo_skin104_col","#CAMO_SKIN_104","#CAMO_SKIN_104_DESC",0,0,0,0,5
+"camo_skin105","pilot_camo_skin105","titan_camo_skin105","CAMO","models/camo_skins/camo_skin105_col","#CAMO_SKIN_105","#CAMO_SKIN_105_DESC",0,0,0,0,5
+"camo_skin106","pilot_camo_skin106","titan_camo_skin106","CAMO","models/camo_skins/camo_skin106_col","#CAMO_SKIN_106","#CAMO_SKIN_106_DESC",0,0,0,0,5
+"camo_skin107","pilot_camo_skin107","titan_camo_skin107","CAMO","models/camo_skins/camo_skin107_col","#CAMO_SKIN_107","#CAMO_SKIN_107_DESC",0,0,0,0,5
+"camo_skin108","pilot_camo_skin108","titan_camo_skin108","CAMO","models/camo_skins/camo_skin108_col","#CAMO_SKIN_108","#CAMO_SKIN_108_DESC",0,0,0,0,5
+"camo_skin109","pilot_camo_skin109","titan_camo_skin109","CAMO","models/camo_skins/camo_skin109_col","#CAMO_SKIN_109","#CAMO_SKIN_109_DESC",0,0,0,0,5
+"camo_skin110","pilot_camo_skin110","titan_camo_skin110","CAMO","models/camo_skins/camo_skin110_col","#CAMO_SKIN_110","#CAMO_SKIN_110_DESC",0,0,0,0,5
+"camo_skin111","pilot_camo_skin111","titan_camo_skin111","CAMO","models/camo_skins/camo_skin111_col","#CAMO_SKIN_111","#CAMO_SKIN_111_DESC",0,0,0,0,5
+"camo_skin112","pilot_camo_skin112","titan_camo_skin112","CAMO","models/camo_skins/camo_skin112_col","#CAMO_SKIN_112","#CAMO_SKIN_112_DESC",0,0,0,0,5
+"camo_skin113","pilot_camo_skin113","titan_camo_skin113","CAMO","models/camo_skins/camo_skin113_col","#CAMO_SKIN_113","#CAMO_SKIN_113_DESC",0,0,0,0,5
+"camo_skin114","pilot_camo_skin114","titan_camo_skin114","CAMO","models/camo_skins/camo_skin114_col","#CAMO_SKIN_114","#CAMO_SKIN_114_DESC",0,0,0,0,5
+"camo_skin115","pilot_camo_skin115","titan_camo_skin115","CAMO","models/camo_skins/camo_skin115_col","#CAMO_SKIN_115","#CAMO_SKIN_115_DESC",0,0,0,0,5
+"camo_skin116","pilot_camo_skin116","titan_camo_skin116","CAMO","models/camo_skins/camo_skin116_col","#CAMO_SKIN_116","#CAMO_SKIN_116_DESC",0,0,0,0,5
+"camo_skin117","pilot_camo_skin117","titan_camo_skin117","CAMO","models/camo_skins/camo_skin117_col","#CAMO_SKIN_117","#CAMO_SKIN_117_DESC",0,0,0,0,5
+"camo_skin118","pilot_camo_skin118","titan_camo_skin118","CAMO","models/camo_skins/camo_skin118_col","#CAMO_SKIN_118","#CAMO_SKIN_118_DESC",0,0,0,0,5
+"camo_skin119","pilot_camo_skin119","titan_camo_skin119","CAMO","models/camo_skins/camo_skin119_col","#CAMO_SKIN_119","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin120","pilot_camo_skin120","titan_camo_skin120","CAMO","models/camo_skins/camo_skin120_col","#CAMO_SKIN_120","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin121","pilot_camo_skin121","titan_camo_skin121","CAMO","models/camo_skins/camo_skin121_col","#CAMO_SKIN_121","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin122","pilot_camo_skin122","titan_camo_skin122","CAMO","models/camo_skins/camo_skin122_col","#CAMO_SKIN_122","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin123","pilot_camo_skin123","titan_camo_skin123","CAMO","models/camo_skins/camo_skin123_col","#CAMO_SKIN_123","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin124","pilot_camo_skin124","titan_camo_skin124","CAMO","models/camo_skins/camo_skin124_col","#CAMO_SKIN_124","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin125","pilot_camo_skin125","titan_camo_skin125","CAMO","models/camo_skins/camo_skin125_col","#CAMO_SKIN_125","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin126","pilot_camo_skin126","titan_camo_skin126","CAMO","models/camo_skins/camo_skin126_col","#CAMO_SKIN_126","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin127","pilot_camo_skin127","titan_camo_skin127","CAMO","models/camo_skins/camo_skin127_col","#CAMO_SKIN_127","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin128","pilot_camo_skin128","titan_camo_skin128","CAMO","models/camo_skins/camo_skin128_col","#CAMO_SKIN_128","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin129","pilot_camo_skin129","titan_camo_skin129","CAMO","models/camo_skins/camo_skin129_col","#CAMO_SKIN_129","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin130","pilot_camo_skin130","titan_camo_skin130","CAMO","models/camo_skins/camo_skin130_col","#CAMO_SKIN_130","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin131","pilot_camo_skin131","titan_camo_skin131","CAMO","models/camo_skins/camo_skin131_col","#CAMO_SKIN_131","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin132","pilot_camo_skin132","titan_camo_skin132","CAMO","models/camo_skins/camo_skin132_col","#CAMO_SKIN_132","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin133","pilot_camo_skin133","titan_camo_skin133","CAMO","models/camo_skins/camo_skin133_col","#CAMO_SKIN_133","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin134","pilot_camo_skin134","titan_camo_skin134","CAMO","models/camo_skins/camo_skin134_col","#CAMO_SKIN_134","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin135","pilot_camo_skin135","titan_camo_skin135","CAMO","models/camo_skins/camo_skin135_col","#CAMO_SKIN_135","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin136","pilot_camo_skin136","titan_camo_skin136","CAMO","models/camo_skins/camo_skin136_col","#CAMO_SKIN_136","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin137","pilot_camo_skin137","titan_camo_skin137","CAMO","models/camo_skins/camo_skin137_col","#CAMO_SKIN_137","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin138","pilot_camo_skin138","titan_camo_skin138","CAMO","models/camo_skins/camo_skin138_col","#CAMO_SKIN_138","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin139","pilot_camo_skin139","titan_camo_skin139","CAMO","models/camo_skins/camo_skin139_col","#CAMO_SKIN_139","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin140","pilot_camo_skin140","titan_camo_skin140","CAMO","models/camo_skins/camo_skin140_col","#CAMO_SKIN_140","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin141","pilot_camo_skin141","titan_camo_skin141","CAMO","models/camo_skins/camo_skin141_col","#CAMO_SKIN_141","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin142","pilot_camo_skin142","titan_camo_skin142","CAMO","models/camo_skins/camo_skin142_col","#CAMO_SKIN_142","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin143","pilot_camo_skin143","titan_camo_skin143","CAMO","models/camo_skins/camo_skin143_col","#CAMO_SKIN_143","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin144","pilot_camo_skin144","titan_camo_skin144","CAMO","models/camo_skins/camo_skin144_col","#CAMO_SKIN_144","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin145","pilot_camo_skin145","titan_camo_skin145","CAMO","models/camo_skins/camo_skin145_col","#CAMO_SKIN_145","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin146","pilot_camo_skin146","titan_camo_skin146","CAMO","models/camo_skins/camo_skin146_col","#CAMO_SKIN_146","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin147","pilot_camo_skin147","titan_camo_skin147","CAMO","models/camo_skins/camo_skin147_col","#CAMO_SKIN_147","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin148","pilot_camo_skin148","titan_camo_skin148","CAMO","models/camo_skins/camo_skin148_col","#CAMO_SKIN_148","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin149","pilot_camo_skin149","titan_camo_skin149","CAMO","models/camo_skins/camo_skin149_col","#CAMO_SKIN_149","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin150","pilot_camo_skin150","titan_camo_skin150","CAMO","models/camo_skins/camo_skin150_col","#CAMO_SKIN_150","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin151","pilot_camo_skin151","titan_camo_skin151","CAMO","models/camo_skins/camo_skin151_col","#CAMO_SKIN_151","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin152","pilot_camo_skin152","titan_camo_skin152","CAMO","models/camo_skins/camo_skin152_col","#CAMO_SKIN_152","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin153","pilot_camo_skin153","titan_camo_skin153","CAMO","models/camo_skins/camo_skin153_col","#CAMO_SKIN_153","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin154","pilot_camo_skin154","titan_camo_skin154","CAMO","models/camo_skins/camo_skin154_col","#CAMO_SKIN_154","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin155","pilot_camo_skin155","titan_camo_skin155","CAMO","models/camo_skins/camo_skin155_col","#CAMO_SKIN_155","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin156","pilot_camo_skin156","titan_camo_skin156","CAMO","models/camo_skins/camo_skin156_col","#CAMO_SKIN_156","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin157","pilot_camo_skin157","titan_camo_skin157","CAMO","models/camo_skins/camo_skin157_col","#CAMO_SKIN_157","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin158","pilot_camo_skin158","titan_camo_skin158","CAMO","models/camo_skins/camo_skin158_col","#CAMO_SKIN_158","#CAMO_SKIN_120_DESC",0,0,0,0,5
+"camo_skin159","pilot_camo_skin159","titan_camo_skin159","CAMO","models/camo_skins/camo_skin159_col","#CAMO_SKIN_159","#CAMO_SKIN_119_DESC",0,0,0,0,5
+"camo_skin160","pilot_camo_skin160","titan_camo_skin160","CAMO","models/camo_skins/camo_skin160_col","#CAMO_SKIN_160","#CAMO_SKIN_120_DESC",0,0,0,0,5 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/community_entries.csv b/Northstar.CustomServers/mod/scripts/datatable/community_entries.csv
new file mode 100644
index 00000000..0616f131
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/community_entries.csv
@@ -0,0 +1,22 @@
+category,key,locString
+"categories","gaming","#COMMUNITY_CATEGORY_GAMING"
+"categories","lifestyle","#COMMUNITY_CATEGORY_LIFESTYLE"
+"categories","geography","#COMMUNITY_CATEGORY_GEOGRAPHY"
+"categories","tech","#COMMUNITY_CATEGORY_TECH"
+"categories","other","#COMMUNITY_CATEGORY_OTHER"
+"regions","North America","#COMMUNITY_REGION_NORTHAMERICA"
+"regions","Europe","#COMMUNITY_REGION_EUROPE"
+"regions","South America","#COMMUNITY_REGION_SOUTHAMERICA"
+"regions","Asia","#COMMUNITY_REGION_ASIA"
+"regions","Oceania","#COMMUNITY_REGION_AUSTRALIA"
+"languages","English","#COMMUNITY_LANGUAGE_ENGLISH"
+"languages","French","#COMMUNITY_LANGUAGE_FRENCH"
+"languages","Italian","#COMMUNITY_LANGUAGE_ITALIAN"
+"languages","German","#COMMUNITY_LANGUAGE_GERMAN"
+"languages","Spanish","#COMMUNITY_LANGUAGE_SPANISH"
+"languages","MSpanish","#COMMUNITY_LANGUAGE_MSPANISH"
+"languages","Japanese","#COMMUNITY_LANGUAGE_JAPANESE"
+"languages","TChinese","#COMMUNITY_LANGUAGE_TCHINESE"
+"languages","Russian","#COMMUNITY_LANGUAGE_RUSSIAN"
+"languages","Portuguese","#COMMUNITY_LANGUAGE_PORTUGUESE"
+"languages","Polish","#COMMUNITY_LANGUAGE_POLISH" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/death_hints_mp.csv b/Northstar.CustomServers/mod/scripts/datatable/death_hints_mp.csv
new file mode 100644
index 00000000..4388ab0f
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/death_hints_mp.csv
@@ -0,0 +1,381 @@
+source,className,locString,mapName,gameMode
+"titan_class","ion","#DEATH_HINT_ION_001","",""
+"titan_class","ion","#DEATH_HINT_ION_002","",""
+"titan_class","ion","#DEATH_HINT_ION_003","",""
+"titan_class","ion","#DEATH_HINT_ION_004","",""
+"titan_class","ion","#DEATH_HINT_ION_005","",""
+"titan_class","ion","#DEATH_HINT_ION_006","",""
+"titan_class","legion","#DEATH_HINT_LEGION_001","",""
+"titan_class","legion","#DEATH_HINT_LEGION_002","",""
+"titan_class","legion","#DEATH_HINT_LEGION_003","",""
+"titan_class","legion","#DEATH_HINT_LEGION_004","",""
+"titan_class","legion","#DEATH_HINT_LEGION_005","",""
+"titan_class","legion","#DEATH_HINT_LEGION_006","",""
+"titan_class","northstar","#DEATH_HINT_NORTHSTAR_001","",""
+"titan_class","northstar","#DEATH_HINT_NORTHSTAR_002","",""
+"titan_class","northstar","#DEATH_HINT_NORTHSTAR_003","",""
+"titan_class","northstar","#DEATH_HINT_NORTHSTAR_004","",""
+"titan_class","northstar","#DEATH_HINT_NORTHSTAR_005","",""
+"titan_class","northstar","#DEATH_HINT_NORTHSTAR_006","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_001","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_002","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_003","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_004","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_005","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_006","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_007","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_008","",""
+"titan_class","ronin","#DEATH_HINT_RONIN_009","",""
+"titan_class","scorch","#DEATH_HINT_SCORCH_001","",""
+"titan_class","scorch","#DEATH_HINT_SCORCH_002","",""
+"titan_class","scorch","#DEATH_HINT_SCORCH_003","",""
+"titan_class","scorch","#DEATH_HINT_SCORCH_004","",""
+"titan_class","scorch","#DEATH_HINT_SCORCH_005","",""
+"titan_class","scorch","#DEATH_HINT_SCORCH_006","",""
+"titan_class","tone","#DEATH_HINT_TONE_001","",""
+"titan_class","tone","#DEATH_HINT_TONE_002","",""
+"titan_class","tone","#DEATH_HINT_TONE_003","",""
+"titan_class","tone","#DEATH_HINT_TONE_004","",""
+"titan_class","tone","#DEATH_HINT_TONE_005","",""
+"titan_class","tone","#DEATH_HINT_TONE_006","",""
+"weapon","mp_ability_cloak","#DEATH_HINT_CLOAK_001","",""
+"weapon","mp_ability_cloak","#DEATH_HINT_CLOAK_002","",""
+"weapon","mp_ability_cloak","#DEATH_HINT_CLOAK_003","",""
+"weapon","mp_ability_grapple","#DEATH_HINT_GRAPPLE_001","",""
+"weapon","mp_ability_heal","#DEATH_HINT_STIM_001","",""
+"weapon","mp_ability_heal","#DEATH_HINT_STIM_002","",""
+"weapon","mp_ability_heal","#DEATH_HINT_STIM_003","",""
+"weapon","mp_ability_holopilot","#DEATH_HINT_HOLOPILOT_001","",""
+"weapon","mp_ability_holopilot","#DEATH_HINT_HOLOPILOT_002","",""
+"weapon","mp_ability_holopilot","#DEATH_HINT_HOLOPILOT_003","",""
+"weapon","mp_ability_holopilot","#DEATH_HINT_HOLOPILOT_004","",""
+"weapon","mp_ability_holopilot","#DEATH_HINT_HOLOPILOT_005","",""
+"weapon","mp_ability_holopilot","#DEATH_HINT_HOLOPILOT_006","",""
+"weapon","mp_ability_phase_rewind","#DEATH_HINT_PHASE_REWIND_001","",""
+"weapon","mp_ability_shifter","#DEATH_HINT_PHASESHIFT_001","",""
+"weapon","mp_ability_shifter","#DEATH_HINT_PHASESHIFT_002","",""
+"weapon","mp_ability_shifter","#DEATH_HINT_PHASESHIFT_003","",""
+"weapon","mp_ability_shifter","#DEATH_HINT_PHASESHIFT_004","",""
+"weapon","mp_ability_shifter","#DEATH_HINT_PHASESHIFT_005","",""
+"weapon","mp_ability_shifter","#DEATH_HINT_PHASESHIFT_006","",""
+"weapon","mp_ability_shifter","#DEATH_HINT_PHASESHIFT_007","",""
+"weapon","mp_ability_shifter","#DEATH_HINT_PHASESHIFT_008","",""
+"weapon","mp_ability_sonar","#DEATH_HINT_SONAR_001","",""
+"weapon","mp_titanability_ammo_swap","#DEATH_HINT_AMMO_SWAP_001_MP","",""
+"weapon","mp_titanability_basic_block","#DEATH_HINT_SWORD_BLOCK_001_MP","",""
+"weapon","mp_titanability_gun_shield","#DEATH_HINT_GUN_SHIELD_001_MP","",""
+"weapon","mp_titanability_hover","#DEATH_HINT_TITAN_HOVER_001","",""
+"weapon","mp_titanability_laser_trip","#DEATH_HINT_LASER_TRIPWIRE_001","",""
+"weapon","mp_titanability_particle_wall","#DEATH_HINT_PARTICLE_WALL_001_MP","",""
+"weapon","mp_titanability_particle_wall","#DEATH_HINT_PARTICLE_WALL_002_MP","",""
+"weapon","mp_titanability_particle_wall","#DEATH_HINT_PARTICLE_WALL_003_MP","",""
+"weapon","mp_titanability_phase_dash","#DEATH_HINT_PHASE_DASH_001_MP","",""
+"weapon","mp_titanability_phase_dash","#DEATH_HINT_PHASE_DASH_002_MP","",""
+"weapon","mp_titanability_power_shot","#DEATH_HINT_POWER_SHOT_001_MP","",""
+"weapon","mp_titanability_power_shot","#DEATH_HINT_POWER_SHOT_002_MP","",""
+"weapon","mp_titanability_slow_trap","#DEATH_HINT_INCENDIARY_TRAP_001","",""
+"weapon","mp_titanability_tether_trap","#DEATH_HINT_TETHER_TRAP_001_MP","",""
+"weapon","mp_titancore_flame_wave","#DEATH_HINT_FLAME_CORE_001_MP","",""
+"weapon","mp_titancore_flight_core","#DEATH_HINT_FLIGHT_CORE_001_MP","",""
+"weapon","mp_titancore_laser_cannon","#DEATH_HINT_LASER_CANNON_001_MP","",""
+"weapon","mp_titancore_salvo_core","#DEATH_HINT_SALVO_CORE_001_MP","",""
+"weapon","mp_titancore_shift_core","#DEATH_HINT_SHIFT_CORE_001","",""
+"weapon","mp_titancore_siege_mode","#DEATH_HINT_SMART_CORE_001_MP","",""
+"weapon","mp_titanweapon_arc_wave","#DEATH_HINT_ARC_WAVE_001","",""
+"weapon","mp_titanweapon_flame_wall","#DEATH_HINT_FIREWALL_001_MP","",""
+"weapon","mp_titanweapon_heat_shield","#DEATH_HINT_HEAT_SHIELD_001_MP","",""
+"weapon","mp_titanweapon_laser_lite","#DEATH_HINT_LASER_SHOT_001_MP","",""
+"weapon","mp_titanweapon_leadwall","#DEATH_HINT_LEADWALL_001","",""
+"weapon","mp_titanweapon_leadwall","#DEATH_HINT_LEADWALL_002","",""
+"weapon","mp_titanweapon_leadwall","#DEATH_HINT_LEADWALL_003","",""
+"weapon","mp_titanweapon_leadwall","#DEATH_HINT_LEADWALL_004","",""
+"weapon","mp_titanweapon_meteor","#DEATH_HINT_THERMITE_LAUNCHER_001","",""
+"weapon","mp_titanweapon_meteor","#DEATH_HINT_THERMITE_LAUNCHER_002","",""
+"weapon","mp_titanweapon_meteor","#DEATH_HINT_THERMITE_LAUNCHER_003","",""
+"weapon","mp_titanweapon_meteor","#DEATH_HINT_THERMITE_LAUNCHER_004","",""
+"weapon","mp_titanweapon_meteor","#DEATH_HINT_THERMITE_LAUNCHER_005","",""
+"weapon","mp_titanweapon_particle_accelerator","#DEATH_HINT_SPLITTER_RIFLE_001","",""
+"weapon","mp_titanweapon_particle_accelerator","#DEATH_HINT_SPLITTER_RIFLE_002","",""
+"weapon","mp_titanweapon_particle_accelerator","#DEATH_HINT_SPLITTER_RIFLE_003","",""
+"weapon","mp_titanweapon_particle_accelerator","#DEATH_HINT_SPLITTER_RIFLE_004","",""
+"weapon","mp_titanweapon_predator_cannon","#DEATH_HINT_PREDATOR_CANNON_001","",""
+"weapon","mp_titanweapon_predator_cannon","#DEATH_HINT_PREDATOR_CANNON_002","",""
+"weapon","mp_titanweapon_predator_cannon","#DEATH_HINT_PREDATOR_CANNON_003","",""
+"weapon","mp_titanweapon_predator_cannon","#DEATH_HINT_PREDATOR_CANNON_004","",""
+"weapon","mp_titanweapon_predator_cannon","#DEATH_HINT_PREDATOR_CANNON_005","",""
+"weapon","mp_titanweapon_sniper","#DEATH_HINT_PLASMA_RAILGUN_001","",""
+"weapon","mp_titanweapon_sniper","#DEATH_HINT_PLASMA_RAILGUN_002","",""
+"weapon","mp_titanweapon_sniper","#DEATH_HINT_PLASMA_RAILGUN_003","",""
+"weapon","mp_titanweapon_sniper","#DEATH_HINT_PLASMA_RAILGUN_004","",""
+"weapon","mp_titanweapon_sniper","#DEATH_HINT_PLASMA_RAILGUN_005","",""
+"weapon","mp_titanweapon_sticky_40mm","#DEATH_HINT_40MM_TRACKER_001","",""
+"weapon","mp_titanweapon_sticky_40mm","#DEATH_HINT_40MM_TRACKER_002","",""
+"weapon","mp_titanweapon_sticky_40mm","#DEATH_HINT_40MM_TRACKER_003","",""
+"weapon","mp_titanweapon_sticky_40mm","#DEATH_HINT_40MM_TRACKER_004","",""
+"weapon","mp_titanweapon_sticky_40mm","#DEATH_HINT_40MM_TRACKER_005","",""
+"weapon","mp_titanweapon_sticky_40mm","#DEATH_HINT_40MM_TRACKER_006","",""
+"weapon","mp_titanweapon_tracker_rockets","#DEATH_HINT_TRACKING_ROCKETS_001_MP","",""
+"weapon","mp_titanweapon_tracker_rockets","#DEATH_HINT_TRACKING_ROCKETS_002_MP","",""
+"weapon","mp_titanweapon_tracker_rockets","#DEATH_HINT_TRACKING_ROCKETS_003","",""
+"weapon","mp_titanweapon_vortex_shield","#DEATH_HINT_VORTEX_SHIELD_001","",""
+"weapon","mp_titanweapon_vortex_shield","#DEATH_HINT_VORTEX_SHIELD_002_MP","",""
+"weapon","mp_weapon_alternator_smg","#DEATH_HINT_ALTERNATOR_001","",""
+"weapon","mp_weapon_alternator_smg","#DEATH_HINT_ALTERNATOR_002","",""
+"weapon","mp_weapon_arc_launcher","#DEATH_HINT_ARCLAUNCHER_001","",""
+"weapon","mp_weapon_arc_launcher","#DEATH_HINT_ARCLAUNCHER_002","",""
+"weapon","mp_weapon_arc_launcher","#DEATH_HINT_ARCLAUNCHER_003","",""
+"weapon","mp_weapon_arc_launcher","#DEATH_HINT_ARCLAUNCHER_004","",""
+"weapon","mp_weapon_autopistol","#DEATH_HINT_RE45_001","",""
+"weapon","mp_weapon_autopistol","#DEATH_HINT_RE45_002","",""
+"weapon","mp_weapon_autopistol","#DEATH_HINT_RE45_003","",""
+"weapon","mp_weapon_car","#DEATH_HINT_CAR_001","",""
+"weapon","mp_weapon_car","#DEATH_HINT_CAR_002","",""
+"weapon","mp_weapon_defender","#DEATH_HINT_CHARGERIFLE_001","",""
+"weapon","mp_weapon_defender","#DEATH_HINT_CHARGERIFLE_002","",""
+"weapon","mp_weapon_defender","#DEATH_HINT_CHARGERIFLE_003","",""
+"weapon","mp_weapon_defender","#DEATH_HINT_CHARGERIFLE_004","",""
+"weapon","mp_weapon_defender","#DEATH_HINT_CHARGERIFLE_005","",""
+"weapon","mp_weapon_defender","#DEATH_HINT_CHARGERIFLE_006","",""
+"weapon","mp_weapon_deployable_cover","#DEATH_HINT_AWALL_001","",""
+"weapon","mp_weapon_deployable_cover","#DEATH_HINT_AWALL_002","",""
+"weapon","mp_weapon_deployable_cover","#DEATH_HINT_AWALL_003","",""
+"weapon","mp_weapon_deployable_cover","#DEATH_HINT_AWALL_004","",""
+"weapon","mp_weapon_deployable_cover","#DEATH_HINT_AWALL_005","",""
+"weapon","mp_weapon_deployable_cover","#DEATH_HINT_AWALL_006","",""
+"weapon","mp_weapon_dmr","#DEATH_HINT_DMR_001","",""
+"weapon","mp_weapon_dmr","#DEATH_HINT_DMR_002","",""
+"weapon","mp_weapon_dmr","#DEATH_HINT_DMR_003","",""
+"weapon","mp_weapon_dmr","#DEATH_HINT_DMR_004","",""
+"weapon","mp_weapon_dmr","#DEATH_HINT_DMR_005","",""
+"weapon","mp_weapon_dmr","#DEATH_HINT_DMR_006","",""
+"weapon","mp_weapon_doubletake","#DEATH_HINT_DOUBLETAKE_001","",""
+"weapon","mp_weapon_doubletake","#DEATH_HINT_DOUBLETAKE_002","",""
+"weapon","mp_weapon_doubletake","#DEATH_HINT_DOUBLETAKE_003","",""
+"weapon","mp_weapon_doubletake","#DEATH_HINT_DOUBLETAKE_004","",""
+"weapon","mp_weapon_doubletake","#DEATH_HINT_DOUBLETAKE_005","",""
+"weapon","mp_weapon_doubletake","#DEATH_HINT_DOUBLETAKE_006","",""
+"weapon","mp_weapon_doubletake","#DEATH_HINT_DOUBLETAKE_007","",""
+"weapon","mp_weapon_epg","#DEATH_HINT_EPG1_001","",""
+"weapon","mp_weapon_epg","#DEATH_HINT_EPG1_002","",""
+"weapon","mp_weapon_epg","#DEATH_HINT_EPG1_003","",""
+"weapon","mp_weapon_epg","#DEATH_HINT_EPG1_004","",""
+"weapon","mp_weapon_epg","#DEATH_HINT_EPG1_005","",""
+"weapon","mp_weapon_epg","#DEATH_HINT_EPG1_006","",""
+"weapon","mp_weapon_epg","#DEATH_HINT_EPG1_007","",""
+"weapon","mp_weapon_epg","#DEATH_HINT_EPG1_008","",""
+"weapon","mp_weapon_esaw","#DEATH_HINT_DEVOTION_001","",""
+"weapon","mp_weapon_esaw","#DEATH_HINT_DEVOTION_002","",""
+"weapon","mp_weapon_esaw","#DEATH_HINT_DEVOTION_003","",""
+"weapon","mp_weapon_esaw","#DEATH_HINT_DEVOTION_004","",""
+"weapon","mp_weapon_frag_drone","#DEATH_HINT_FRAG_DRONE_001","",""
+"weapon","mp_weapon_frag_grenade","#DEATH_HINT_GRENADE_FRAG_001","",""
+"weapon","mp_weapon_frag_grenade","#DEATH_HINT_GRENADE_FRAG_002","",""
+"weapon","mp_weapon_frag_grenade","#DEATH_HINT_GRENADE_FRAG_003","",""
+"weapon","mp_weapon_frag_grenade","#DEATH_HINT_GRENADE_FRAG_004","",""
+"weapon","mp_weapon_frag_grenade","#DEATH_HINT_GRENADE_FRAG_005","",""
+"weapon","mp_weapon_frag_grenade","#DEATH_HINT_GRENADE_FRAG_006","",""
+"weapon","mp_weapon_frag_grenade","#DEATH_HINT_GRENADE_FRAG_007","",""
+"weapon","mp_weapon_g2","#DEATH_HINT_G2_001","",""
+"weapon","mp_weapon_g3","#DEATH_HINT_G2_002","",""
+"weapon","mp_weapon_g4","#DEATH_HINT_G2_003","",""
+"weapon","mp_weapon_g5","#DEATH_HINT_G2_004","",""
+"weapon","mp_weapon_grenade_electric_smoke","#DEATH_HINT_GRENADE_ELECTRIC_SMOKE_001","",""
+"weapon","mp_weapon_grenade_electric_smoke","#DEATH_HINT_GRENADE_ELECTRIC_SMOKE_002","",""
+"weapon","mp_weapon_grenade_electric_smoke","#DEATH_HINT_GRENADE_ELECTRIC_SMOKE_003","",""
+"weapon","mp_weapon_grenade_electric_smoke","#DEATH_HINT_GRENADE_ELECTRIC_SMOKE_004","",""
+"weapon","mp_weapon_grenade_emp","#DEATH_HINT_GRENADE_EMP_001","",""
+"weapon","mp_weapon_grenade_emp","#DEATH_HINT_GRENADE_EMP_002","",""
+"weapon","mp_weapon_grenade_emp","#DEATH_HINT_GRENADE_EMP_003","",""
+"weapon","mp_weapon_grenade_emp","#DEATH_HINT_GRENADE_EMP_004","",""
+"weapon","mp_weapon_grenade_emp","#DEATH_HINT_GRENADE_EMP_005","",""
+"weapon","mp_weapon_grenade_gravity","#DEATH_HINT_GRENADE_GRAVITY_001","",""
+"weapon","mp_weapon_grenade_gravity","#DEATH_HINT_GRENADE_GRAVITY_002","",""
+"weapon","mp_weapon_grenade_gravity","#DEATH_HINT_GRENADE_GRAVITY_003","",""
+"weapon","mp_weapon_grenade_gravity","#DEATH_HINT_GRENADE_GRAVITY_004","",""
+"weapon","mp_weapon_grenade_gravity","#DEATH_HINT_GRENADE_GRAVITY_005","",""
+"weapon","mp_weapon_grenade_sonar","#DEATH_HINT_GRENADE_SONAR_001","",""
+"weapon","mp_weapon_grenade_sonar","#DEATH_HINT_GRENADE_SONAR_002","",""
+"weapon","mp_weapon_grenade_sonar","#DEATH_HINT_GRENADE_SONAR_003","",""
+"weapon","mp_weapon_grenade_sonar","#DEATH_HINT_GRENADE_SONAR_004","",""
+"weapon","mp_weapon_hemlok","#DEATH_HINT_HEMLOK_001","",""
+"weapon","mp_weapon_hemlok","#DEATH_HINT_HEMLOK_002","",""
+"weapon","mp_weapon_hemlok_smg","#DEATH_HINT_VOLT_001","",""
+"weapon","mp_weapon_hemlok_smg","#DEATH_HINT_VOLT_002","",""
+"weapon","mp_weapon_lmg","#DEATH_HINT_SPITFIRE_001","",""
+"weapon","mp_weapon_lmg","#DEATH_HINT_SPITFIRE_002","",""
+"weapon","mp_weapon_lmg","#DEATH_HINT_SPITFIRE_003","",""
+"weapon","mp_weapon_lmg","#DEATH_HINT_SPITFIRE_004","",""
+"weapon","mp_weapon_lmg","#DEATH_HINT_SPITFIRE_005","",""
+"weapon","mp_weapon_lmg","#DEATH_HINT_SPITFIRE_006","",""
+"weapon","mp_weapon_lmg","#DEATH_HINT_SPITFIRE_007","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_001","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_002","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_003","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_004","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_005","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_006","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_007","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_008","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_009","",""
+"weapon","mp_weapon_lstar","#DEATH_HINT_LSTAR_010","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_001","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_002","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_003","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_004","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_005","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_006","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_007","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_008","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_009","",""
+"weapon","mp_weapon_mastiff","#DEATH_HINT_MASTIFF_010","",""
+"weapon","mp_weapon_mgl","#DEATH_HINT_MGL_001","",""
+"weapon","mp_weapon_mgl","#DEATH_HINT_MGL_002","",""
+"weapon","mp_weapon_mgl","#DEATH_HINT_MGL_003","",""
+"weapon","mp_weapon_mgl","#DEATH_HINT_MGL_004","",""
+"weapon","mp_weapon_mgl","#DEATH_HINT_MGL_005","",""
+"weapon","mp_weapon_mgl","#DEATH_HINT_MGL_006","",""
+"weapon","mp_weapon_mgl","#DEATH_HINT_MGL_007","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_001","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_002","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_003","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_004","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_005","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_006","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_007","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_008","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_009","",""
+"weapon","mp_weapon_pulse_lmg","#DEATH_HINT_COLDWAR_010","",""
+"weapon","mp_weapon_r97","#DEATH_HINT_97_001","",""
+"weapon","mp_weapon_r97","#DEATH_HINT_97_002","",""
+"weapon","mp_weapon_r97","#DEATH_HINT_97_003","",""
+"weapon","mp_weapon_r97","#DEATH_HINT_97_004","",""
+"weapon","mp_weapon_rocket_launcher","#DEATH_HINT_ARCHER_001","",""
+"weapon","mp_weapon_rocket_launcher","#DEATH_HINT_ARCHER_002","",""
+"weapon","mp_weapon_rocket_launcher","#DEATH_HINT_ARCHER_003","",""
+"weapon","mp_weapon_rocket_launcher","#DEATH_HINT_ARCHER_004","",""
+"weapon","mp_weapon_rocket_launcher","#DEATH_HINT_ARCHER_005","",""
+"weapon","mp_weapon_rspn101","#DEATH_HINT_102_001","",""
+"weapon","mp_weapon_rspn102","#DEATH_HINT_102_002","",""
+"weapon","mp_weapon_satchel","#DEATH_HINT_SATCHEL_001","",""
+"weapon","mp_weapon_satchel","#DEATH_HINT_SATCHEL_002","",""
+"weapon","mp_weapon_satchel","#DEATH_HINT_SATCHEL_003","",""
+"weapon","mp_weapon_semipistol","#DEATH_HINT_P2011_001","",""
+"weapon","mp_weapon_semipistol","#DEATH_HINT_P2011_002","",""
+"weapon","mp_weapon_semipistol","#DEATH_HINT_P2011_003","",""
+"weapon","mp_weapon_shotgun","#DEATH_HINT_EVA8_001","",""
+"weapon","mp_weapon_shotgun","#DEATH_HINT_EVA8_002","",""
+"weapon","mp_weapon_shotgun","#DEATH_HINT_EVA8_003","",""
+"weapon","mp_weapon_shotgun","#DEATH_HINT_EVA8_004","",""
+"weapon","mp_weapon_shotgun","#DEATH_HINT_EVA8_005","",""
+"weapon","mp_weapon_shotgun","#DEATH_HINT_EVA8_006","",""
+"weapon","mp_weapon_shotgun","#DEATH_HINT_EVA8_007","",""
+"weapon","mp_weapon_shotgun_pistol","#DEATH_HINT_MOZAMBIQUE_001","",""
+"weapon","mp_weapon_shotgun_pistol","#DEATH_HINT_MOZAMBIQUE_002","",""
+"weapon","mp_weapon_shotgun_pistol","#DEATH_HINT_MOZAMBIQUE_003","",""
+"weapon","mp_weapon_smart_pistol","#DEATH_HINT_SMARTPISTOL_001","",""
+"weapon","mp_weapon_smart_pistol","#DEATH_HINT_SMARTPISTOL_002","",""
+"weapon","mp_weapon_smart_pistol","#DEATH_HINT_SMARTPISTOL_003","",""
+"weapon","mp_weapon_smart_pistol","#DEATH_HINT_SMARTPISTOL_004","",""
+"weapon","mp_weapon_smart_pistol","#DEATH_HINT_SMARTPISTOL_005","",""
+"weapon","mp_weapon_smart_pistol","#DEATH_HINT_SMARTPISTOL_006","",""
+"weapon","mp_weapon_smr","#DEATH_HINT_SMR_001","",""
+"weapon","mp_weapon_smr","#DEATH_HINT_SMR_002","",""
+"weapon","mp_weapon_smr","#DEATH_HINT_SMR_003","",""
+"weapon","mp_weapon_smr","#DEATH_HINT_SMR_004","",""
+"weapon","mp_weapon_smr","#DEATH_HINT_SMR_005","",""
+"weapon","mp_weapon_smr","#DEATH_HINT_SMR_006","",""
+"weapon","mp_weapon_smr","#DEATH_HINT_SMR_007","",""
+"weapon","mp_weapon_smr","#DEATH_HINT_SMR_008","",""
+"weapon","mp_weapon_sniper","#DEATH_HINT_KRABER_001","",""
+"weapon","mp_weapon_sniper","#DEATH_HINT_KRABER_002","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_001","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_002","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_003","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_004","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_005","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_006","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_007","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_008","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_009","",""
+"weapon","mp_weapon_softball","#DEATH_HINT_SOFTBALL_010","",""
+"weapon","mp_weapon_thermite_grenade","#DEATH_HINT_GRENADE_THERMITE_001","",""
+"weapon","mp_weapon_thermite_grenade","#DEATH_HINT_GRENADE_THERMITE_002","",""
+"weapon","mp_weapon_thermite_grenade","#DEATH_HINT_GRENADE_THERMITE_003","",""
+"weapon","mp_weapon_thermite_grenade","#DEATH_HINT_GRENADE_THERMITE_004","",""
+"weapon","mp_weapon_vinson","#DEATH_HINT_VINSON_001","",""
+"weapon","mp_weapon_vinson","#DEATH_HINT_VINSON_002","",""
+"weapon","mp_weapon_vinson","#DEATH_HINT_VINSON_003","",""
+"weapon","mp_weapon_wingman","#DEATH_HINT_WINGMAN_001","",""
+"weapon","mp_weapon_wingman","#DEATH_HINT_WINGMAN_002","",""
+"weapon","mp_weapon_wingman","#DEATH_HINT_WINGMAN_003","",""
+"weapon","mp_weapon_wingman","#DEATH_HINT_WINGMAN_004","",""
+"weapon","mp_weapon_wingman_n","#DEATH_HINT_WINGMAN_N_001","",""
+"weapon","pas_power_cell","#DEATH_HINT_POWER_CELL_001","",""
+"weapon","pas_fast_health_regen","#DEATH_HINT_FAST_REGEN_001","",""
+"weapon","pas_ordnance_pack","#DEATH_HINT_ORDNANCE_EXPERT_001","",""
+"weapon","pas_ordnance_pack","#DEATH_HINT_ORDNANCE_EXPERT_002","",""
+"weapon","pas_ordnance_pack","#DEATH_HINT_ORDNANCE_EXPERT_003","",""
+"weapon","pas_ordnance_pack","#DEATH_HINT_ORDNANCE_EXPERT_004","",""
+"weapon","pas_fast_embark","#DEATH_HINT_PHASE_EMBARK_001","",""
+"weapon","pas_fast_embark","#DEATH_HINT_PHASE_EMBARK_002","",""
+"weapon","pas_fast_embark","#DEATH_HINT_PHASE_EMBARK_003","",""
+"weapon","pas_enemy_death_icons","#DEATH_HINT_KILL_REPORT_001","",""
+"weapon","pas_enemy_death_icons","#DEATH_HINT_KILL_REPORT_002","",""
+"weapon","pas_enemy_death_icons","#DEATH_HINT_KILL_REPORT_003","",""
+"weapon","pas_wallhang","#DEATH_HINT_WALLHANG_001","",""
+"weapon","pas_wallhang","#DEATH_HINT_WALLHANG_002","",""
+"weapon","pas_ads_hover","#DEATH_HINT_HOVER_001","",""
+"weapon","pas_ads_hover","#DEATH_HINT_HOVER_002","",""
+"weapon","pas_ads_hover","#DEATH_HINT_HOVER_003","",""
+"weapon","pas_ads_hover","#DEATH_HINT_HOVER_004","",""
+"weapon","pas_ads_hover","#DEATH_HINT_HOVER_005","",""
+"weapon","pas_stealth_movement","#DEATH_HINT_LOW_PROFILE_001","",""
+"weapon","pas_enhanced_titan_ai","#DEATH_HINT_ASSAULT_CHIP_001","",""
+"weapon","pas_enhanced_titan_ai","#DEATH_HINT_ASSAULT_CHIP_002","",""
+"weapon","pas_auto_eject","#DEATH_HINT_AUTO_EJECT_001","",""
+"weapon","pas_auto_eject","#DEATH_HINT_AUTO_EJECT_002","",""
+"weapon","pas_auto_eject","#DEATH_HINT_AUTO_EJECT_003","",""
+"weapon","pas_auto_eject","#DEATH_HINT_AUTO_EJECT_004","",""
+"weapon","pas_mobility_dash_capacity","#DEATH_HINT_TURBO_ENGINE_001","",""
+"weapon","pas_mobility_dash_capacity","#DEATH_HINT_TURBO_ENGINE_002","",""
+"weapon","pas_mobility_dash_capacity","#DEATH_HINT_TURBO_ENGINE_003","",""
+"weapon","pas_hyper_core","#DEATH_HINT_OVERCORE_001","",""
+"weapon","pas_build_up_nuclear_core","#DEATH_HINT_NUKE_EJECT_001","",""
+"weapon","pas_build_up_nuclear_core","#DEATH_HINT_NUKE_EJECT_002","",""
+"weapon","pas_anti_rodeo","#DEATH_HINT_COUNTER_READY_001","",""
+"weapon","pas_anti_rodeo","#DEATH_HINT_COUNTER_READY_002","",""
+"weapon","pas_warpfall","#DEATH_HINT_WARPFALL_001","",""
+"weapon","pas_bubbleshield","#DEATH_HINT_DOME_SHIELD_001","",""
+"weapon","pas_bubbleshield","#DEATH_HINT_DOME_SHIELD_002","",""
+"npc_title","#NPC_TITAN_AUTO_NUKE","#HINT_FD_TITAN_AUTO_NUKE","","fd"
+"npc_title","#NPC_TITAN_AUTO_NUKE","#HINT_FD_TITAN_AUTO_NUKE2","","fd"
+"npc_title","#NPC_TITAN_ARC","#HINT_FD_TITAN_ARC","","fd"
+"npc_title","#NPC_TITAN_ARC","#HINT_FD_TITAN_ARC2","","fd"
+"npc_title","#NPC_TITAN_MORTAR","#HINT_FD_TITAN_MORTAR","","fd"
+"npc_title","#NPC_TITAN_MORTAR","#HINT_FD_TITAN_MORTAR2","","fd"
+"npc_title","#NPC_SOLDIER","#HINT_FD_SOLDIER","","fd"
+"npc_title","#NPC_SPECTRE","#HINT_FD_SPECTRE","","fd"
+"npc_title","#NPC_SPECTRE_MORTAR","#HINT_FD_SPECTRE_MORTAR","","fd"
+"npc_title","#NPC_SPECTRE_MORTAR","#HINT_FD_SPECTRE_MORTAR2","","fd"
+"npc_title","#NPC_STALKER","#HINT_FD_STALKER","","fd"
+"npc_title","#NPC_SUPER_SPECTRE","#HINT_FD_SUPER_SPECTRE","","fd"
+"npc_title","#NPC_SUPER_SPECTRE","#HINT_FD_SUPER_SPECTRE2","","fd"
+"npc_title","#NPC_SPECTRE_SUICIDE","#HINT_FD_SPECTRE_SUICIDE","","fd"
+"npc_title","#NPC_DRONE_PLASMA","#HINT_FD_DRONE_PLASMA","","fd"
+"npc_title","#NPC_DRONE_CLOAKED","#HINT_FD_DRONE_CLOAKED","","fd"
+"npc_title","#NPC_TITAN_STRYDER_LEADWALL","#HINT_FD_TITAN_STRYDER_LEADWALL","","fd"
+"npc_title","#NPC_TITAN_STRYDER_SNIPER","#HINT_FD_TITAN_STRYDER_SNIPER","","fd"
+"npc_title","#NPC_TITAN_OGRE_METEOR","#HINT_FD_TITAN_OGRE_METEOR","","fd"
+"npc_title","#NPC_TITAN_OGRE_MINIGUN","#HINT_FD_TITAN_OGRE_MINIGUN","","fd"
+"npc_title","#NPC_TITAN_ATLAS_TRACKER","#HINT_FD_TITAN_ATLAS_TRACKER","","fd"
+"npc_title","#NPC_TITAN_ATLAS_STICKYBOMB","#HINT_FD_TITAN_ATLAS_STICKYBOMB","","fd"
+"npc_title","#NPC_TITAN_ATLAS_VANGUARD","#HINT_FD_TITAN_ATLAS_VANGUARD","","fd"
+"npc_title","#NPC_TITAN_SNIPER_FD","#HINT_FD_TITAN_STRYDER_SNIPER","","fd"
+"npc_title","#NPC_TITAN_SNIPER_FD","#HINT_FD_TITAN_STRYDER_SNIPER2","","fd"
+"npc_title","#NPC_TITAN_SNIPER_FD","#HINT_FD_TITAN_STRYDER_SNIPER3","","fd"
+"titan_class","vanguard","#DEATH_HINT_VANGUARD_001","",""
+"titan_class","vanguard","#DEATH_HINT_VANGUARD_002","",""
+"titan_class","vanguard","#DEATH_HINT_VANGUARD_003","",""
+"titan_class","vanguard","#DEATH_HINT_VANGUARD_004","",""
+"titan_class","vanguard","#DEATH_HINT_VANGUARD_005","",""
+"titan_class","vanguard","#DEATH_HINT_VANGUARD_006","","" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/default_pilot_loadouts.csv b/Northstar.CustomServers/mod/scripts/datatable/default_pilot_loadouts.csv
new file mode 100644
index 00000000..e0fbbcd9
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/default_pilot_loadouts.csv
@@ -0,0 +1,11 @@
+name,suit,race,primary,primaryAttachment,primaryMod1,primaryMod2,primaryMod3,secondary,secondaryMod1,secondaryMod2,secondaryMod3,weapon3,weapon3Mod1,weapon3Mod2,weapon3Mod3,ordnance,passive1,passive2
+"#DEFAULT_PILOT_1","grapple","race_human_male","mp_weapon_rspn101","","","","","mp_weapon_defender","","","","mp_weapon_autopistol","","","","mp_weapon_frag_grenade","pas_power_cell","pas_enemy_death_icons"
+"#DEFAULT_PILOT_2","medium","race_human_female","mp_weapon_car","","","","","mp_weapon_mgl","","","","mp_weapon_autopistol","","","","mp_weapon_grenade_emp","pas_fast_health_regen","pas_wallhang"
+"#DEFAULT_PILOT_3","grapple","race_human_female","mp_weapon_lmg","","","","","mp_weapon_defender","","","","mp_weapon_autopistol","","","","mp_weapon_thermite_grenade","pas_fast_health_regen","pas_enemy_death_icons"
+"#DEFAULT_PILOT_4","medium","race_human_male","mp_weapon_shotgun","","","","","mp_weapon_defender","","","","mp_weapon_autopistol","","","","mp_weapon_frag_grenade","pas_power_cell","pas_wallhang"
+"#DEFAULT_PILOT_5","geist","race_human_female","mp_weapon_sniper","","","","","mp_weapon_defender","","","","mp_weapon_autopistol","","","","mp_weapon_grenade_emp","pas_power_cell","pas_enemy_death_icons"
+"#DEFAULT_PILOT_6","grapple","race_human_female","mp_weapon_smr","","","","","mp_weapon_defender","","","","mp_weapon_semipistol","","","","mp_weapon_thermite_grenade","pas_fast_health_regen","pas_wallhang"
+"#DEFAULT_PILOT_7","medium","race_human_female","mp_weapon_rspn101","","","","","mp_weapon_mgl","","","","mp_weapon_autopistol","","","","mp_weapon_grenade_emp","pas_power_cell","pas_enemy_death_icons"
+"#DEFAULT_PILOT_8","grapple","race_human_male","mp_weapon_car","","","","","mp_weapon_defender","","","","mp_weapon_autopistol","","","","mp_weapon_thermite_grenade","pas_fast_health_regen","pas_wallhang"
+"#DEFAULT_PILOT_9","grapple","race_human_male","mp_weapon_sniper","","","","","mp_weapon_defender","","","","mp_weapon_semipistol","","","","mp_weapon_frag_grenade","pas_power_cell","pas_enemy_death_icons"
+"#DEFAULT_PILOT_10","geist","race_human_male","mp_weapon_smr","","","","","mp_weapon_defender","","","","mp_weapon_autopistol","","","","mp_weapon_grenade_emp","pas_fast_health_regen","pas_wallhang" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/default_titan_loadouts.csv b/Northstar.CustomServers/mod/scripts/datatable/default_titan_loadouts.csv
new file mode 100644
index 00000000..b19ef636
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/default_titan_loadouts.csv
@@ -0,0 +1,8 @@
+name,primeName,setFile,titanRef,primaryMod,special,antirodeo,passive1,passive2,passive3,passive4,passive5,passive6,voice
+"#DEFAULT_TITAN_5","#DEFAULT_TITAN_5_PRIME","titan_atlas_stickybomb","ion","","mp_titanweapon_vortex_shield_ion","mp_titanability_laser_trip","pas_enhanced_titan_ai","pas_ion_weapon","pas_bubbleshield","pas_vanguard_core1","pas_vanguard_core4","pas_vanguard_core7","titanos_ion"
+"#DEFAULT_TITAN_7","#DEFAULT_TITAN_7_PRIME","titan_ogre_meteor","scorch","","mp_titanweapon_heat_shield","mp_titanability_slow_trap","pas_enhanced_titan_ai","pas_scorch_weapon","pas_bubbleshield","pas_vanguard_core1","pas_vanguard_core4","pas_vanguard_core7","titanos_scorch"
+"#DEFAULT_TITAN_3","#DEFAULT_TITAN_3_PRIME","titan_stryder_sniper","northstar","","mp_titanability_tether_trap","mp_titanability_hover","pas_enhanced_titan_ai","pas_northstar_weapon","pas_bubbleshield","pas_vanguard_core1","pas_vanguard_core4","pas_vanguard_core7","titanos_northstar"
+"#DEFAULT_TITAN_2","#DEFAULT_TITAN_2_PRIME","titan_stryder_leadwall","ronin","","mp_titanability_basic_block","mp_titanability_phase_dash","pas_enhanced_titan_ai","pas_ronin_weapon","pas_bubbleshield","pas_vanguard_core1","pas_vanguard_core4","pas_vanguard_core7","titanos_ronin"
+"#DEFAULT_TITAN_4","#DEFAULT_TITAN_4_PRIME","titan_atlas_tracker","tone","","mp_titanability_particle_wall","mp_titanability_sonar_pulse","pas_enhanced_titan_ai","pas_tone_weapon","pas_bubbleshield","pas_vanguard_core1","pas_vanguard_core4","pas_vanguard_core7","titanos_tone"
+"#DEFAULT_TITAN_8","#DEFAULT_TITAN_8_PRIME","titan_ogre_minigun","legion","","mp_titanability_gun_shield","mp_titanability_ammo_swap","pas_enhanced_titan_ai","pas_legion_weapon","pas_bubbleshield","pas_vanguard_core1","pas_vanguard_core4","pas_vanguard_core7","titanos_legion"
+"#DEFAULT_TITAN_10","#DEFAULT_TITAN_10_PRIME","titan_atlas_vanguard","vanguard","","mp_titanweapon_stun_laser","mp_titanability_rearm","pas_enhanced_titan_ai","pas_vanguard_shield","pas_bubbleshield","pas_vanguard_core1","pas_vanguard_core4","pas_vanguard_core7","titanos_vanguard" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/earn_meter_mp.csv b/Northstar.CustomServers/mod/scripts/datatable/earn_meter_mp.csv
new file mode 100644
index 00000000..1c766181
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/earn_meter_mp.csv
@@ -0,0 +1,31 @@
+itemRef,earnType,buildingImage,readyImage,nameText
+"titan_ronin","GOAL","rui/hud/earn_meter/ajax_building","rui/hud/earn_meter/titan_ready","#RONIN"
+"titan","GOAL","rui/hud/earn_meter/ajax_building","rui/hud/earn_meter/titan_ready","#TITAN"
+"ion","GOAL","rui/hud/earn_meter/ajax_building","rui/hud/earn_meter/titan_ready","#ION"
+"tone","GOAL","rui/hud/earn_meter/ajax_building","rui/hud/earn_meter/titan_ready","#TONE"
+"scorch","GOAL","rui/hud/earn_meter/ogre_building","rui/hud/earn_meter/titan_ready","#SCORCH"
+"legion","GOAL","rui/hud/earn_meter/ogre_building","rui/hud/earn_meter/titan_ready","#LEGION"
+"ronin","GOAL","rui/hud/earn_meter/stryder_building","rui/hud/earn_meter/titan_ready","#RONIN"
+"northstar","GOAL","rui/hud/earn_meter/stryder_building","rui/hud/earn_meter/titan_ready","#NORTHSTAR"
+"vanguard","GOAL","rui/hud/earn_meter/ajax_building","rui/hud/earn_meter/titan_ready","#VANGUARD"
+"core_electric_smoke","REWARD","rui/menu/boosts/boost_icon_electric_smoke","rui/menu/boosts/boost_icon_electric_smoke","#WPN_TITAN_ELECTRIC_SMOKE"
+"burnmeter_maphack","REWARD","rui/menu/boosts/boost_icon_map_hack","rui/menu/boosts/boost_icon_map_hack","#BURNMETER_MAP_HACK"
+"burnmeter_amped_weapons","REWARD","rui/menu/boosts/boost_icon_amped","rui/menu/boosts/boost_icon_amped","#BURNMETER_AMPED_WEAPONS"
+"burnmeter_ticks","REWARD","rui/menu/boosts/boost_icon_tick","rui/menu/boosts/boost_icon_tick","#BURNMETER_TICKS"
+"burnmeter_emergency_titan","REWARD","ui/temp","ui/temp","#BURNMETER_EMERGENCY_TITAN"
+"burnmeter_random_foil","REWARD","rui/menu/boosts/boost_icon_random","rui/menu/boosts/boost_icon_random","#BURNMETER_RANDOM_FOIL"
+"burnmeter_double_agent","REWARD","rui/menu/boosts/burncard_icon","rui/menu/boosts/burncard_icon","#BURNMETER_DOUBLE_AGENT"
+"burnmeter_phase_rewind","REWARD","rui/menu/boosts/boost_icon_phase_rewind","rui/menu/boosts/boost_icon_phase_rewind","#WPN_REWIND"
+"burnmeter_at_turret_weapon","REWARD","rui/menu/boosts/boost_icon_titan_sentry","rui/menu/boosts/boost_icon_titan_sentry","#BURNMETER_AT_TURRETWEAPON"
+"burnmeter_ap_turret_weapon","REWARD","rui/menu/boosts/boost_icon_personel_sentry","rui/menu/boosts/boost_icon_personel_sentry","#BURNMETER_AP_TURRETWEAPON"
+"burnmeter_holopilot_nova","REWARD","rui/menu/boosts/boost_icon_holopilot","rui/menu/boosts/boost_icon_holopilot","#WPN_HOLOPILOT_NOVA"
+"burnmeter_emergency_battery","REWARD","rui/menu/boosts/boost_icon_battery","rui/menu/boosts/boost_icon_battery","#BURNMETER_EMERGENCY_BATTERY"
+"burnmeter_smart_pistol","REWARD","rui/menu/boosts/boost_icon_smart_pistol","rui/menu/boosts/boost_icon_smart_pistol","#WPN_SMART_PISTOL"
+"burnmeter_radar_jammer","REWARD","rui/menu/boosts/boost_icon_radar_jam","rui/menu/boosts/boost_icon_radar_jam","#BURNMETER_RADAR_JAMMER"
+"burnmeter_hard_cover","REWARD","rui/menu/boosts/boost_icon_shield","rui/menu/boosts/boost_icon_shield","#WPN_HARD_COVER"
+"burnmeter_nuke_titan","REWARD","rui/menu/boosts/boost_icon_nuke","rui/menu/boosts/boost_icon_nuke","#WPN_NUKE_TITAN"
+"burnmeter_harvester_shield","REWARD","rui/menu/boosts/boost_icon_harvester_shield","rui/menu/boosts/boost_icon_harvester_shield","#BURNMETER_HARVESTER_SHIELD"
+"burnmeter_arc_trap","REWARD","rui/menu/boosts/boost_icon_arc_trap","rui/menu/boosts/boost_icon_arc_trap","#WPN_ARC_TRAP"
+"burnmeter_at_turret_weapon_infinite","REWARD","rui/menu/boosts/boost_icon_titan_sentry","rui/menu/boosts/boost_icon_titan_sentry","#BURNMETER_AT_TURRETWEAPON"
+"burnmeter_ap_turret_weapon_infinite","REWARD","rui/menu/boosts/boost_icon_personel_sentry","rui/menu/boosts/boost_icon_personel_sentry","#BURNMETER_AP_TURRETWEAPON"
+"burnmeter_rodeo_grenade","REWARD","rui/menu/boosts/boost_icon_core_overload","rui/menu/boosts/boost_icon_core_overload","#BURNMETER_SUPER_RODEO" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/faction_dialogue.csv b/Northstar.CustomServers/mod/scripts/datatable/faction_dialogue.csv
new file mode 100644
index 00000000..8e9673d1
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/faction_dialogue.csv
@@ -0,0 +1,273 @@
+conversationname,priority,debounce,disabledForFaction,inheritedDebounceConversations
+"scoring_won",3000,10.000000,"",""
+"scoring_lost",3000,10.000000,"",""
+"scoring_tied",3000,10.000000,"",""
+"scoring_wonClose",3000,10.000000,"",""
+"scoring_lostClose",3000,10.000000,"",""
+"scoring_winning",3000,10.000000,"faction_marvin",""
+"scoring_losing",3000,10.000000,"faction_marvin",""
+"scoring_winningClose",3000,10.000000,"faction_marvin",""
+"scoring_losingClose",3000,10.000000,"faction_marvin",""
+"scoring_winningLarge",3000,10.000000,"faction_marvin",""
+"scoring_losingLarge",3000,10.000000,"faction_marvin",""
+"scoring_lostMercy",3000,10.000000,"",""
+"scoring_wonMercy",3000,10.000000,"",""
+"fortwar_matchLoss",3000,10.000000,"",""
+"scoring_flavor",1500,10.000000,"",""
+"scoring_gotMailAlert",1500,10.000000,"",""
+"amphp_modeName",1500,10.000000,"",""
+"mtitan_modeName",1500,10.000000,"",""
+"ffa_modeName",2500,10.000000,"",""
+"freea_modeName",2500,10.000000,"",""
+"at_modeName",2500,10.000000,"",""
+"hp_modeName",2500,10.000000,"",""
+"cp_modeName",2500,10.000000,"",""
+"phunt_modeName",2500,10.000000,"",""
+"fortwar_modeName",2500,10.000000,"",""
+"tw_modeName",2500,10.000000,"",""
+"ctf_modeName",2500,10.000000,"",""
+"bh_modeName",2500,10.000000,"",""
+"front_modeName",2500,10.000000,"",""
+"frontdef_modeName",2500,10.000000,"",""
+"frontatk_modeName",2500,10.000000,"",""
+"lts_modeName",2500,10.000000,"",""
+"mfd_modeName",2500,10.000000,"",""
+"tmfd_modeName",2500,10.000000,"",""
+"extract_modeName",2500,10.000000,"",""
+"mw_modeName",2500,10.000000,"",""
+"pvp_modeName",2500,10.000000,"",""
+"raid_modeName",2500,10.000000,"",""
+"aslt_modeName",2500,10.000000,"",""
+"gnrc_modeDesc",2500,10.000000,"",""
+"ffa_modeDesc",2500,10.000000,"",""
+"ctf_modeDesc",2500,10.000000,"",""
+"lts_modeDesc",2500,10.000000,"",""
+"mfd_modeDesc",2500,10.000000,"",""
+"ltsd_modeDesc",2500,10.000000,"",""
+"ltsa_modeDesc",2500,10.000000,"",""
+"hp_modeDesc",2500,10.000000,"",""
+"mtitan_modeDesc",2500,10.000000,"",""
+"pvp_modeDesc",2500,10.000000,"",""
+"phunt_modeDesc",2500,10.000000,"",""
+"freea_modeDesc",2500,10.000000,"",""
+"grnc_modeDesc",2500,10.000000,"",""
+"mp_titanReady",1200,30.000000,"",""
+"mp_titanSoon",1200,45.000000,"faction_marvin",""
+"mp_titanInbound",1200,10.000000,"",""
+"mp_titanEmergency",1200,10.000000,"faction_marvin",""
+"mp_evacGo",1500,10.000000,"faction_marvin",""
+"mp_evacStop",1500,10.000000,"faction_marvin",""
+"mp_evacGoNag",1500,10.000000,"faction_marvin",""
+"mp_evacStopNag",1500,10.000000,"faction_marvin",""
+"mp_halftime",1500,10.000000,"",""
+"mp_sideSwitching",1500,10.000000,"",""
+"bh_modeDesc",1500,10.000000,"",""
+"bh_incoming",1500,10.000000,"",""
+"bh_collect",1500,10.000000,"faction_marvin",""
+"bh_decrypt",1500,10.000000,"faction_marvin",""
+"bh_collectSuccess",1500,10.000000,"faction_marvin",""
+"bh_collectFail",1500,10.000000,"faction_marvin",""
+"bh_mvp",1500,10.000000,"faction_marvin",""
+"bh_clearedA",1500,10.000000,"",""
+"bh_clearedB",1500,10.000000,"",""
+"bh_clearedC",1500,10.000000,"",""
+"bh_newWave",1500,10.000000,"",""
+"bh_bountyClaimedByEnemy",1500,10.000000,"",""
+"bh_bountyClaimedByFriendly",1500,10.000000,"",""
+"bh_playerKilledBounty",1500,10.000000,"faction_marvin",""
+"lts_atk60",1500,10.000000,"faction_marvin",""
+"lts_atk30",1500,10.000000,"faction_marvin",""
+"lts_def60",1500,10.000000,"faction_marvin",""
+"lts_def30",1500,10.000000,"faction_marvin",""
+"lts_bombDown",1500,10.000000,"faction_marvin",""
+"lts_bombDownAtk",1500,10.000000,"faction_marvin",""
+"lts_bombDownDef",1500,10.000000,"faction_marvin",""
+"lts_bombPickup",1500,10.000000,"faction_marvin",""
+"lts_bombPlanted",1500,10.000000,"faction_marvin",""
+"lts_bombDefusedAtk",1500,10.000000,"faction_marvin",""
+"lts_bombDefusedDef",1500,10.000000,"faction_marvin",""
+"lts_bombPlantedAtk",1500,10.000000,"faction_marvin",""
+"lts_bombPlantedDef",1500,10.000000,"faction_marvin",""
+"fortwar_turretDeployFriendly",1500,10.000000,"faction_marvin",""
+"fortwar_turretDestroyedFriendly",1500,10.000000,"faction_marvin",""
+"fortwar_baseShieldDownFriendly",1500,10.000000,"faction_marvin",""
+"fortwar_baseShieldDownEnemy",1500,10.000000,"faction_marvin",""
+"fortwar_baseShieldUpFriendly",1500,10.000000,"faction_marvin",""
+"fortwar_baseDmgFriendly",1500,10.000000,"faction_marvin",""
+"fortwar_baseDmgFriendly75",1500,10.000000,"faction_marvin",""
+"fortwar_baseDmgFriendly50",1500,10.000000,"faction_marvin",""
+"fortwar_baseDmgFriendly25",1500,10.000000,"faction_marvin",""
+"fortwar_baseDmgEnemy75",1500,10.000000,"faction_marvin",""
+"fortwar_baseDmgEnemy50",1500,10.000000,"faction_marvin",""
+"fortwar_baseDmgEnemy25",1500,10.000000,"faction_marvin",""
+"fortwar_baseEnemyAllyAttacking",1500,30.000000,"faction_marvin",""
+"fortwar_awayTurretsUnderAttack",1450,30.000000,"faction_marvin",""
+"fortwar_baseTurretsUnderAttack",1470,30.000000,"faction_marvin",""
+"fortwar_turretShieldedByFriendlyPilot",1400,10.000000,"faction_marvin",""
+"tw_territoryNag",1500,10.000000,"faction_marvin",""
+"fortwar_terEnemyExpelled",1500,60.000000,"faction_marvin",""
+"fortwar_terFriendlyExpelled",1450,60.000000,"faction_marvin",""
+"fortwar_terEnteredEnemyPilot",1400,60.000000,"faction_marvin",""
+"fortwar_terEnteredEnemyTitan",1450,60.000000,"faction_marvin",""
+"fortwar_terPresentEnemyTitans",1470,60.000000,"faction_marvin",""
+"fortwar_terEnteredEnemyForce",1500,60.000000,"faction_marvin",""
+"amphp_friendlyCappingA",1500,30.000000,"",""
+"amphp_friendlyCappingB",1500,30.000000,"",""
+"amphp_friendlyCappingC",1500,30.000000,"",""
+"amphp_friendlyCappedA",1500,10.000000,"faction_marvin",""
+"amphp_friendlyCappedB",1500,10.000000,"faction_marvin",""
+"amphp_friendlyCappedC",1500,10.000000,"faction_marvin",""
+"amphp_friendlyAmpedA",1500,10.000000,"faction_marvin",""
+"amphp_friendlyAmpedB",1500,10.000000,"faction_marvin",""
+"amphp_friendlyAmpedC",1500,10.000000,"faction_marvin",""
+"amphp_enemyCappedA",1500,10.000000,"",""
+"amphp_enemyCappedB",1500,10.000000,"",""
+"amphp_enemyCappedC",1500,10.000000,"",""
+"amphp_enemyAmpedA",1500,10.000000,"",""
+"amphp_enemyAmpedB",1500,10.000000,"",""
+"amphp_enemyAmpedC",1500,10.000000,"",""
+"amphp_friendlyCapAll",2000,10.000000,"",""
+"amphp_enemyCapAll",2000,10.000000,"",""
+"amphp_youAmpedA",1500,10.000000,"",""
+"amphp_youAmpedB",1500,10.000000,"",""
+"amphp_youAmpedC",1500,10.000000,"",""
+"ctf_flagPickupFriendly",1500,10.000000,"",""
+"ctf_flagPickupYou",1500,10.000000,"",""
+"ctf_flagReturnedFriendly",1500,10.000000,"",""
+"ctf_flagReturnedEnemy",1500,10.000000,"",""
+"ctf_notifyWin1more",1500,10.000000,"faction_marvin",""
+"ctf_notifyLose1more",1500,10.000000,"faction_marvin",""
+"kc_pilotkillLegion",1400,0.100000,"faction_marvin",""
+"kc_pilotkillScorch",1400,0.100000,"faction_marvin",""
+"kc_pilotkillTone",1400,0.100000,"faction_marvin",""
+"kc_pilotkillIon",1400,0.100000,"faction_marvin",""
+"kc_pilotkillRonin",1400,0.100000,"faction_marvin",""
+"kc_pilotkillNorthstar",1400,0.100000,"faction_marvin",""
+"kc_bullseye",1350,0.100000,"faction_marvin",""
+"kc_rodeo",1350,10.000000,"faction_marvin",""
+"kc_rakerodeoguy",1400,10.000000,"faction_marvin",""
+"kc_pilotkilltitan",1400,0.100000,"faction_marvin",""
+"kc_hitandrun",1350,0.100000,"faction_marvin",""
+"kc_firstblood",2100,0.100000,"faction_marvin",""
+"kc_megakill",2100,0.100000,"",""
+"kc_triplekill",2100,0.100000,"",""
+"kc_doublekill",1350,0.100000,"faction_marvin",""
+"kc_iced",1350,0.100000,"faction_marvin",""
+"kc_rampage",1350,0.100000,"faction_marvin",""
+"kc_killingspree",1350,0.100000,"faction_marvin",""
+"kc_dominating",1350,0.100000,"faction_marvin",""
+"kc_retribution",1350,0.100000,"faction_marvin",""
+"kc_comeback",1350,0.100000,"faction_marvin",""
+"mfd_youAreMarked",1500,0.100000,"",""
+"mfd_youKilledMark",1500,0.100000,"",""
+"mfd_markDownEnemy",1500,0.100000,"",""
+"mfd_markDownFriendly",1500,0.100000,"",""
+"mfd_targetsMarkedLong",1500,0.100000,"",""
+"mfd_targetsMarkedShort",1500,0.100000,"",""
+"mfd_youOutlastedEnemy",1500,0.100000,"",""
+"mfd_markCountdown",1500,0.100000,"",""
+"lts_playerLastTitanOnTeam",1500,0.100000,"",""
+"fd_modeDesc",2500,10.000000,"",""
+"fd_firstWaveStartPrefix",2500,10.000000,"",""
+"fd_newWaveStartPrefix",2500,10.000000,"",""
+"fd_finalWaveStartPrefix",2500,10.000000,"",""
+"fd_waveVictory",3000,10.000000,"",""
+"fd_waveRestart",3000,10.000000,"",""
+"fd_waveRedoTwo",3000,10.000000,"",""
+"fd_waveRedoFinal",3000,10.000000,"",""
+"fd_titanReadyNag",1500,10.000000,"",""
+"fd_minimapTip",1500,10.000000,"",""
+"fd_waveNoTitanDrops",1500,10.000000,"",""
+"fd_waveTypeInfantry",1500,10.000000,"",""
+"fd_waveTypeCloakDrone",1500,10.000000,"",""
+"fd_waveTypeTitanReg",1500,10.000000,"",""
+"fd_waveTypeTitanMortar",1500,10.000000,"",""
+"fd_waveTypeTitanNuke",1500,10.000000,"",""
+"fd_waveTypeTitanArc",1500,10.000000,"",""
+"fd_waveComboNukeMortar",1500,10.000000,"",""
+"fd_waveComboArcMortar",1500,10.000000,"",""
+"fd_waveComboArcNuke",1500,10.000000,"",""
+"fd_waveComboNukeCloak",1500,10.000000,"",""
+"fd_waveComboNukeTrain",1500,10.000000,"",""
+"fd_waveComboMultiMix",1500,10.000000,"",""
+"fd_waveTypeReapers",1500,10.000000,"",""
+"fd_waveTypeTicks",1500,10.000000,"",""
+"fd_waveTypeStalkers",1500,10.000000,"",""
+"fd_waveTypeMortarSpectre",1500,10.000000,"",""
+"fd_waveTypeReaperTicks",1500,10.000000,"",""
+"fd_waveTypeEliteTitan",1500,10.000000,"",""
+"fd_waveTypeFlyers",1500,10.000000,"",""
+"fd_baseDeath",3000,10.000000,"",""
+"fd_waveRecapLowHealth",25000,10.000000,"",""
+"fd_waveRecapNearPerfect",2500,10.000000,"",""
+"fd_waveRecapPerfect",2500,10.000000,"",""
+"fd_bigWaveInc",1500,10.000000,"",""
+"fd_finalWaveStartGeneric",1500,10.000000,"",""
+"fd_baseHealthRecharge",2500,10.000000,"",""
+"fd_matchVictory",3000,10.000000,"",""
+"fd_matchDefeat",3000,10.000000,"",""
+"fd_waveCleanup",1500,10.000000,"",""
+"fd_singlePilotDown",2000,10.000000,"",""
+"fd_multiPilotDown",2000,10.000000,"",""
+"fd_onlyPlayerIsAlive",2000,10.000000,"",""
+"fd_pilotRespawn",2000,10.000000,"",""
+"fd_nukeTitanNearBase",1750,10.000000,"",""
+"fd_waveCleanup5",1500,20.000000,"",""
+"fd_waveCleanup4",1500,20.000000,"",""
+"fd_waveCleanup3",1500,20.000000,"",""
+"fd_waveCleanup2",1500,20.000000,"",""
+"fd_waveCleanup1",1500,20.000000,"",""
+"fd_baseShieldTakingDmg",1900,30.000000,"",""
+"fd_baseShieldLow",1910,10.000000,"","fd_baseShieldTakingDmg"
+"fd_baseShieldDown",1930,10.000000,"",""
+"fd_baseShieldUp",1810,8.000000,"","fd_baseShieldTakingDmg fd_baseShieldRecharging"
+"fd_baseShieldRecharging",1800,30.000000,"","fd_baseShieldTakingDmg"
+"fd_baseShieldRechargingShort",1800,30.000000,"",""
+"fd_baseBatteryNagLow",1920,10.000000,"",""
+"fd_baseBatteryNagDown",2000,10.000000,"",""
+"fd_baseShieldLowHolding",2000,10.000000,"",""
+"fd_waveNoTitans",1500,10.000000,"",""
+"fd_incTitansNukeClump",1500,10.000000,"",""
+"fd_incTitansMortarClump",1500,10.000000,"",""
+"fd_incArcTitanClump",1500,10.000000,"",""
+"fd_incCloakDroneClump",1500,10.000000,"",""
+"fd_incReaperClump",1500,10.000000,"",""
+"fd_nagKillTitansMortar",2020,45.000000,"",""
+"fd_nagKillInfantry",1500,45.000000,"",""
+"fd_nagKillStalkers",1000,45.000000,"",""
+"fd_nagKillMortarSpectres",2020,45.000000,"",""
+"fd_nagKillTitanEMP",1500,45.000000,"",""
+"fd_nagTitanArcAtBase",1500,10.000000,"",""
+"fd_baseHealth75",2000,15.000000,"",""
+"fd_baseHealth50",2010,15.000000,"","fd_baseHealth75"
+"fd_baseHealth25",2020,15.000000,"","fd_baseHealth75 fd_baseHealth50"
+"fd_baseLowHealth",2030,15.000000,"","fd_baseHealth75 fd_baseHealth50 fd_baseHealth25"
+"fd_baseHealth50nag",2010,10.000000,"",""
+"fd_baseHealth25nag",2020,10.000000,"",""
+"fd_playerCashNagSurplus",1500,10.000000,"",""
+"fd_playerCashNagReg",1500,10.000000,"",""
+"fd_wavePayoutAddtnl",1500,10.000000,"",""
+"fd_wavePayoutFirst",1500,10.000000,"",""
+"fd_introEasy",1500,10.000000,"",""
+"fd_introMedium",1500,10.000000,"",""
+"fd_introHard",1500,10.000000,"",""
+"fd_turretOffline",1500,10.000000,"",""
+"fd_turretOnline",1500,10.000000,"",""
+"turretKillsLow",1500,10.000000,"",""
+"turretKillsMedium",1500,10.000000,"",""
+"turretKillsHigh",1500,10.000000,"",""
+"turretKillsMega",1500,10.000000,"",""
+"fd_boughtSentryTurret",1500,10.000000,"",""
+"fd_boughtArcTrap",1500,10.000000,"",""
+"fd_boughtCoreOverload",1500,10.000000,"",""
+"fd_playerNeedsToReadyUp",1500,10.000000,"",""
+"fd_boughtAmpedWeapons",1500,10.000000,"",""
+"fd_boughtHarvesterShield",1500,10.000000,"",""
+"fd_boughtBattery",1500,10.000000,"",""
+"matchRecapNoDeaths",1500,10.000000,"",""
+"matchRecapNoPlayerDeath",1500,10.000000,"",""
+"matchRecapPlayerMVP",1500,10.000000,"",""
+"fd_soonNukeTitans",1500,10.000000,"",""
+"fd_soonArcTitans",1500,10.000000,"",""
+"fd_soonMortarTitans",1500,10.000000,"",""
+"fd_stalkerExploNag",1000,300.000000,"","" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/faction_leaders.csv b/Northstar.CustomServers/mod/scripts/datatable/faction_leaders.csv
new file mode 100644
index 00000000..22fb3e45
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/faction_leaders.csv
@@ -0,0 +1,8 @@
+persistenceRef,factionDialoguePrefix,logo,image,name,description,factionName,usesWaveform,modelName,skinIndex,menuIdleAnim,propModelName,propAttachment,cost
+"faction_marauder","mcor_sarah","rui/faction/faction_logo_marauder","rui/faction/faction_button_marauder","#FACTION_LEADER_NAME_SARAH","#FACTION_LEADER_DESC_SARAH","#FACTION_MARAUDER",0,"models/humans/heroes/mlt_hero_sarah.mdl",0,"Sarah_menu_pose","models/Weapons/p2011/w_p2011.mdl","KNIFE",100
+"faction_apex","imc_blisk","rui/faction/faction_logo_apex","rui/faction/faction_button_apex","#FACTION_LEADER_NAME_BLISK","#FACTION_LEADER_DESC_BLISK","#FACTION_APEX",0,"models/humans/heroes/imc_hero_blisk.mdl",0,"Blisk_menu_pose","models/Weapons/combat_knife/w_combat_knife.mdl","KNIFE",100
+"faction_vinson","imc_ash","rui/faction/faction_logo_vinson","rui/faction/faction_button_vinson","#FACTION_LEADER_NAME_ASH","#FACTION_LEADER_DESC_ASH","#FACTION_VINSON",0,"models/humans/heroes/imc_hero_ash.mdl",0,"Ash_menu_pose","","",100
+"faction_aces","mcor_barker","rui/faction/faction_logo_aces","rui/faction/faction_button_aces","#FACTION_LEADER_NAME_BARKER","#FACTION_LEADER_DESC_BARKER","#FACTION_ACES",0,"models/humans/heroes/mlt_hero_barker.mdl",0,"Barker_menu_pose","models/props/flask/prop_flask_animated.mdl","PROPGUN",100
+"faction_64","mcor_gates","rui/faction/faction_logo_64","rui/faction/faction_button_64","#FACTION_LEADER_NAME_GATES","#FACTION_LEADER_DESC_GATES","#FACTION_64",0,"models/humans/pilots/sp_medium_geist_f.mdl",0,"Gates_menu_pose","models/Weapons/p2011/w_p2011.mdl","KNIFE",100
+"faction_ares","imc_marder","rui/faction/faction_logo_ares","rui/faction/faction_button_ares","#FACTION_LEADER_NAME_MARDER","#FACTION_LEADER_DESC_MARDER","#FACTION_ARES",0,"models/humans/heroes/imc_hero_marder.mdl",0,"Marder_menu_pose","","",100
+"faction_marvin","mcor_marvin","rui/faction/faction_logo_mrvn","rui/faction/faction_button_mrvn","#FACTION_LEADER_NAME_MARVIN","#FACTION_LEADER_DESC_MARVIN","#FACTION_MARVIN",1,"models/Robots/marvin/marvin.mdl",1,"commander_MP_flyin_marvin_idle","","",100 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/faction_leaders_dropship_anims.csv b/Northstar.CustomServers/mod/scripts/datatable/faction_leaders_dropship_anims.csv
new file mode 100644
index 00000000..0d759d93
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/faction_leaders_dropship_anims.csv
@@ -0,0 +1,23 @@
+persistenceRef,dropshipAnimName,isEasterEgg
+"faction_marauder","commander_MP_flyin_sarah",0
+"faction_marauder","commander_MP_flyin_sarah_alt",0
+"faction_marauder","commander_MP_flyin_sarah_silent",0
+"faction_apex","commander_MP_flyin_blisk_betta",0
+"faction_apex","commander_MP_flyin_blisk_born",0
+"faction_apex","commander_MP_flyin_blisk_silent",0
+"faction_vinson","commander_MP_flyin_ash_focus",0
+"faction_vinson","commander_MP_flyin_ash_grateful",0
+"faction_vinson","commander_MP_flyin_ash_silent",0
+"faction_aces","commander_MP_flyin_barker",0
+"faction_aces","commander_MP_flyin_barker_victory",0
+"faction_aces","commander_MP_flyin_barker_conductor",0
+"faction_64","commander_MP_flyin_gates_family",0
+"faction_64","commander_MP_flyin_gates_sixfour",0
+"faction_64","commander_MP_flyin_gates_silent",0
+"faction_ares","commander_MP_flyin_marder",0
+"faction_ares","commander_MP_flyin_marder_alt1",0
+"faction_ares","commander_MP_flyin_marder_alt2",0
+"faction_marvin","commander_MP_flyin_marvin_salute",0
+"faction_marvin","commander_MP_flyin_marvin_highfive",0
+"faction_marvin","commander_MP_flyin_marvin_greeter",0
+"faction_marvin","commander_MP_flyin_marvin_freestyle",1 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/fd_awards.csv b/Northstar.CustomServers/mod/scripts/datatable/fd_awards.csv
new file mode 100644
index 00000000..01c6254b
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/fd_awards.csv
@@ -0,0 +1,15 @@
+ref,priority,displayString,subText,awardDisplayString,displayStyle,image,validityCheck,validityCheckValue,comparisonCheck,needsToBeBest
+"harvesterHeals",5,"#FD_AWARD_0","#FD_AWARD_SUBTEXT_0","#FD_AWARD_VALUE_DISPLAY_BLANK","FD_DISPLAY_STYLE_NUMBER","rui/medals/shielded_harvester","greater_than",1.000000,"highest",1
+"mortarUnitsKilled",3,"#FD_AWARD_1","#FD_AWARD_SUBTEXT_1","#FD_AWARD_VALUE_DISPLAY_BLANK","FD_DISPLAY_STYLE_NUMBER","rui/medals/mortar_units","greater_than",1.000000,"highest",1
+"moneySpent",4,"#FD_AWARD_2","#FD_AWARD_SUBTEXT_2","#FD_AWARD_VALUE_DISPLAY_MONEY","FD_DISPLAY_STYLE_NUMBER","rui/medals/cash_spent","greater_than",2000.000000,"highest",1
+"coresUsed",8,"#FD_AWARD_3","#FD_AWARD_SUBTEXT_3","#FD_AWARD_VALUE_DISPLAY_BLANK","FD_DISPLAY_STYLE_NUMBER","rui/medals/titan_core","greater_than",1.000000,"highest",1
+"longestTitanLife",9,"#FD_AWARD_4","#FD_AWARD_SUBTEXT_4","#FD_AWARD_VALUE_DISPLAY_SECONDS","FD_DISPLAY_STYLE_TIME","rui/medals/least_lost","greater_than",100.000000,"highest",1
+"turretsRepaired",7,"#FD_AWARD_5","#FD_AWARD_SUBTEXT_5","#FD_AWARD_VALUE_DISPLAY_BLANK","FD_DISPLAY_STYLE_NUMBER","rui/medals/turrets_healed","greater_than",5.000000,"highest",1
+"moneyShared",6,"#FD_AWARD_6","#FD_AWARD_SUBTEXT_6","#FD_AWARD_VALUE_DISPLAY_MONEY","FD_DISPLAY_STYLE_NUMBER","rui/medals/cash_shared","greater_than",100.000000,"highest",1
+"damageDealt",1,"#FD_AWARD_7","#FD_AWARD_SUBTEXT_7","#FD_AWARD_VALUE_DISPLAY_POINTS","FD_DISPLAY_STYLE_NUMBER","rui/medals/most_damage","greater_than",-1.000000,"highest",0
+"timeNearHarvester",2,"#FD_AWARD_8","#FD_AWARD_SUBTEXT_8","#FD_AWARD_VALUE_DISPLAY_SECONDS","FD_DISPLAY_STYLE_TIME","rui/medals/harvester_protect","greater_than",200.000000,"highest",1
+"longestLife",0,"#FD_AWARD_9","#FD_AWARD_SUBTEXT_9","#FD_AWARD_VALUE_DISPLAY_SECONDS","FD_DISPLAY_STYLE_TIME","rui/medals/long_life","greater_than",200.000000,"highest",1
+"heals",11,"#FD_AWARD_10","#FD_AWARD_SUBTEXT_10","#FD_AWARD_VALUE_DISPLAY_POINTS","FD_DISPLAY_STYLE_NUMBER","rui/medals/heal","greater_than",5000.000000,"highest",1
+"turretKills",10,"#FD_AWARD_11","#FD_AWARD_SUBTEXT_11","#FD_AWARD_VALUE_DISPLAY_BLANK","FD_DISPLAY_STYLE_NUMBER","rui/medals/turret_damage","greater_than",10.000000,"highest",1
+"mvp",13,"#FD_AWARD_12","#FD_AWARD_SUBTEXT_12","#FD_AWARD_VALUE_DISPLAY_BLANK","FD_DISPLAY_STYLE_NUMBER","rui/medals/mvp","greater_than",2.000000,"highest",1
+"titanKills",12,"#FD_AWARD_13","#FD_AWARD_SUBTEXT_13","#FD_AWARD_VALUE_DISPLAY_BLANK","FD_DISPLAY_STYLE_NUMBER","rui/medals/titans_killed","greater_than",30.000000,"highest",1 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/features_mp.csv b/Northstar.CustomServers/mod/scripts/datatable/features_mp.csv
new file mode 100644
index 00000000..8f1b39b3
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/features_mp.csv
@@ -0,0 +1,21 @@
+featureRef,featureName,featureDesc,featureIcon,specificType,cost
+"communities","#FEATURE_COMMUNITIES","","rui/hud/common/feature_icon","#ITEM_TYPE_FEATURE",0
+"happy_hour","#FEATURE_HAPPY_HOUR","","rui/hud/common/feature_icon","#ITEM_TYPE_FEATURE",0
+"pilot_loadout_1","#DEFAULT_PILOT_1","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",15
+"pilot_loadout_2","#DEFAULT_PILOT_2","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",15
+"pilot_loadout_3","#DEFAULT_PILOT_3","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",15
+"pilot_loadout_4","#DEFAULT_PILOT_4","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",15
+"pilot_loadout_5","#DEFAULT_PILOT_5","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",15
+"pilot_loadout_6","#DEFAULT_PILOT_6","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",15
+"pilot_loadout_7","#DEFAULT_PILOT_7","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",100
+"pilot_loadout_8","#DEFAULT_PILOT_8","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",100
+"pilot_loadout_9","#DEFAULT_PILOT_9","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",100
+"pilot_loadout_10","#DEFAULT_PILOT_10","","rui/hud/common/feature_icon","#ITEM_TYPE_LOADOUT",100
+"coliseum_ticket","#ITEM_COLISEUM","#ITEM_COLISEUM_DESC","rui/menu/common/ticket_icon","#ITEM_TYPE_PLAYLIST",10
+"double_xp","#DOUBLE_XP","#DOUBLE_XP_DESC","rui/menu/common/dbl_xp_icon","#ITEM_TYPE_DOUBLEXP",0
+"credit_award","#CREDIT_AWARD","#CREDIT_AWARD_DESC","rui/menu/common/credit_symbol_large_color","#ITEM_TYPE_CREDITS",0
+"credit_award_5x","#CREDIT_AWARD_5X","","rui/menu/common/credit_symbol_large_color","#ITEM_TYPE_CREDITS",0
+"advocate_gift","#RANDOM","","rui\menu\common\unlock_random","#UNLOCK_RANDOM",0
+"mp_angel_city","#MP_ANGEL_CITY","","rui/hud/common/feature_icon","#UNLOCK_RANDOM",0
+"random","#RANDOM","","rui\menu\common\unlock_random","#UNLOCK_RANDOM",0
+"classic_music","#CLASSIC_MUSIC","","rui/hud/common/feature_icon","#UNLOCK_CLASSIC_MUSIC",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/flightpath_assets.csv b/Northstar.CustomServers/mod/scripts/datatable/flightpath_assets.csv
new file mode 100644
index 00000000..d14566f1
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/flightpath_assets.csv
@@ -0,0 +1,8 @@
+name,mp_model,sp_model
+"fp_dropship_model","models/vehicle/goblin_dropship/goblin_dropship.mdl","models/vehicle/goblin_dropship/goblin_dropship.mdl"
+"fp_dropship_hero_model","models/vehicle/goblin_dropship/goblin_dropship_hero.mdl","models/vehicle/goblin_dropship/goblin_dropship_hero.mdl"
+"fp_crow_model","models/vehicle/crow_dropship/crow_dropship.mdl","models/vehicle/crow_dropship/crow_dropship.mdl"
+"fp_crow_hero_model","models/vehicle/crow_dropship/crow_dropship_hero.mdl","models/vehicle/crow_dropship/crow_dropship_hero.mdl"
+"fp_titan_model","models/titans/medium/titan_medium_ajax.mdl","models/titans/medium/sp_titan_medium_ajax.mdl"
+"fp_straton_model","models/vehicle/straton/straton_imc_gunship_01.mdl","models/vehicle/straton/straton_imc_gunship_01.mdl"
+"fp_hornet_model","models/vehicle/hornet/hornet_fighter.mdl","models/vehicle/hornet/hornet_fighter.mdl" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/grunt_chatter_mp.csv b/Northstar.CustomServers/mod/scripts/datatable/grunt_chatter_mp.csv
new file mode 100644
index 00000000..c3c15f19
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/grunt_chatter_mp.csv
@@ -0,0 +1,47 @@
+conversationname,alias,priority,debounce
+"bc_allygruntdown","bc_allygruntdown",250,20.000000
+"bc_enemytitandown","bc_enemytitandown",250,20.000000
+"bc_enemytitanspotcall","bc_enemytitanspotcall",250,20.000000
+"bc_engageenemycloakedpilot","bc_engageenemycloakedpilot",250,20.000000
+"bc_engagepilotenemy","bc_engagepilotenemy",250,20.000000
+"bc_grenadecall","bc_grenadecall",250,20.000000
+"bc_gruntkillstitan","bc_gruntkillstitan",250,20.000000
+"bc_killenemypilot","bc_killenemypilot",250,20.000000
+"bc_spotenemypilot","bc_spotenemypilot",250,20.000000
+"bc_squaddeplete","bc_squaddeplete",250,20.000000
+"bc_reactGrenadeArc","bc_reactGrenadeArc",250,20.000000
+"bc_reactGrenadeThermite","bc_reactGrenadeThermite",250,20.000000
+"bc_reactGrenadeGravity","bc_reactGrenadeGravity",250,20.000000
+"bc_reactGrenadeElecSmoke","bc_reactGrenadeElecSmoke",250,20.000000
+"bc_grenadeOutCall","bc_grenadeOutCall",250,20.000000
+"bc_fleePlayerTitanCall","bc_spotclosetitancall_01",250,20.000000
+"bc_fleePlayerTitanCall","bc_fleePlayerTitanCall",250,20.000000
+"bc_reactTickSpawnFriendly","bc_reactTickSpawnFriendly_01",250,20.000000
+"bc_reactTickSpawnFriendly","bc_reactTickSpawnFriendly_02",250,20.000000
+"bc_reactTickSpawnFriendly","bc_reactTickSpawnFriendly_03",250,20.000000
+"bc_reactTitanfallFriendlyArrives","bc_reactTitanfallFriendlyArrives_01",250,40.000000
+"bc_reactTitanfallFriendlyArrives","bc_titancheer_01",250,40.000000
+"bc_reactEnemySpotted","bc_engagepilotenemy_01",250,20.000000
+"bc_reactEnemySpotted","bc_engagepilotenemy_02",250,20.000000
+"bc_reactEnemySpotted","bc_engagepilotenemy_06",250,20.000000
+"bc_reactEnemyReaper","diag_sp_ReaperTown_BM102_15_01_mcor_grunt3",250,20.000000
+"bc_reactEnemyReaper","diag_sp_ReaperTown_BM102_16_01_mcor_grunt2",250,20.000000
+"bc_reactReaperFriendlyArrives","bc_reactReaperFriendlyArrives_01",250,40.000000
+"bc_reactFriendlyPilot","diag_sp_ReaperTown_BM103_01a_01_mcor_grunt2",250,180.000000
+"TEMP_bc_reactFriendlyPilot","diag_sp_corkscrew_SE131_02_01_mcor_grunt1",250,180.000000
+"bc_reactFriendlyPilot","diag_sp_corkscrew_SE131_19_01_mcor_grunt1",250,180.000000
+"bc_generalCombat","diag_sp_corkscrew_SE131_21_01_mcor_grunt1",250,20.000000
+"bc_generalCombat","diag_sp_corkscrew_SE131_12_01_mcor_grunt1",250,20.000000
+"bc_generalCombat","diag_sp_intro_WD104_29_01_mcor_grunt6",250,20.000000
+"bc_generalCombat","diag_sp_intro_WD104_24_01_mcor_grunt1",250,20.000000
+"bc_generalCombat","diag_sp_intro_WD103_02_01_mcor_grunt2",250,20.000000
+"bc_generalCombat","diag_sp_intro_WD104_25_01_mcor_grunt2",250,20.000000
+"bc_generalCombat","diag_sp_intro_WD104_26_01_mcor_grunt3",250,20.000000
+"bc_generalCombat","diag_sp_intro_WD104_27_01_mcor_grunt4",250,20.000000
+"bc_generalCombat","diag_sp_intro_WD104_28_01_mcor_grunt5",250,20.000000
+"TEMP_bc_generalNonCombat","diag_sp_ReaperTown_BM102_29a_01_imc_gcaptain",250,100.000000
+"bc_generalNonCombat","diag_sp_ReaperTown_BM103_02a_01_mcor_grunt1",250,100.000000
+"bc_generalCombatTitan","diag_sp_sewerArena_SE151_09_01_imc_grunt1",250,20.000000
+"bc_generalCombatTitan","diag_sp_sewerArena_SE152_01_01_imc_grunt1",250,20.000000
+"bc_generalCombatTitan","diag_sp_intro_WD103_07_01_mcor_grunt1",250,20.000000
+"bc_generalCombatTitan","diag_sp_bigCharge_TD111_06_01_mcor_pilot3",250,20.000000 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/non_loadout_weapons.csv b/Northstar.CustomServers/mod/scripts/datatable/non_loadout_weapons.csv
new file mode 100644
index 00000000..8816fd8f
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/non_loadout_weapons.csv
@@ -0,0 +1,29 @@
+weapon
+"melee_titan_punch"
+"melee_titan_punch_fighter"
+"melee_titan_punch_ion"
+"melee_titan_punch_tone"
+"melee_titan_punch_northstar"
+"melee_titan_punch_scorch"
+"melee_titan_punch_legion"
+"melee_titan_sword"
+"melee_titan_sword_aoe"
+"mp_titanweapon_flightcore_rockets"
+"mp_titanweapon_orbital_strike"
+"mp_titanweapon_predator_cannon_siege"
+"mp_weapon_spectre_spawner"
+"proto_viewmodel_test"
+"mp_turretweapon_blaster"
+"mp_turretweapon_plasma"
+"mp_turretweapon_sentry"
+"mp_ability_arc_blast"
+"mp_ability_burncardweapon"
+"mp_ability_holopilot_nova"
+"mp_weapon_smart_pistol"
+"mp_weapon_hard_cover"
+"melee_titan_punch_vanguard"
+"mp_titanweapon_rocketeer_rocketstream"
+"mp_titanweapon_shoulder_rockets"
+"mp_ability_swordblock"
+"mp_titanability_nuke_eject"
+"mp_weapon_arc_trap" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pain_death_sounds.csv b/Northstar.CustomServers/mod/scripts/datatable/pain_death_sounds.csv
new file mode 100644
index 00000000..00d5e595
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pain_death_sounds.csv
@@ -0,0 +1,53 @@
+event,priority,blocksNextPriority,method,bodyType,alias_1p_victim_only,alias_3p_except_victim,alias_3p_attacker_only,alias_3p_except_attacker,spmp
+"death",105,0,"SE_GIB","NPC_GRUNT","","death.pinkmist","","","spmp"
+"death",105,0,"SE_GIB","NPC_MARVIN","","","","","spmp"
+"death",105,0,"SE_GIB","NPC_PROWLER","","","","","spmp"
+"death",105,0,"SE_GIB","NPC_SPECTRE","","","","","spmp"
+"death",105,0,"SE_GIB","NPC_STALKER","","","","","spmp"
+"death",110,1,"SE_HEADSHOT_BULLET","NPC_GRUNT","","","","Flesh.Light.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_BULLET","NPC_MARVIN","","","","Android.Light.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_BULLET","NPC_SPECTRE","","","","Android.Light.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_BULLET","NPC_STALKER","","","","Android.Light.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_SHOTGUN","NPC_GRUNT","","","","Flesh.Shotgun.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_SHOTGUN","NPC_MARVIN","","","","Android.Heavy.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_SHOTGUN","NPC_SPECTRE","","","","Android.Heavy.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_SHOTGUN","NPC_STALKER","","","","Android.Heavy.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_TITAN","NPC_GRUNT","","","","Flesh.Heavy.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_TITAN","NPC_MARVIN","","","","Android.Heavy.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_TITAN","NPC_SPECTRE","","","","Android.Heavy.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",110,1,"SE_HEADSHOT_TITAN","NPC_STALKER","","","","Android.Heavy.BulletImpact_Headshot_3P_vs_3P","spmp"
+"death",120,1,"SE_NECK_SNAP","NPC_GRUNT","","diag_efforts_DeathNeck_gl_grunt_3p","","","spmp"
+"death",140,1,"SE_DISSOLVE","NPC_GRUNT","","diag_efforts_DeathBurn_gl_grunt_3p","","","spmp"
+"death",160,1,"SE_ELECTRICAL","NPC_GRUNT","","diag_efforts_DeathElec_gl_grunt_3p","","","spmp"
+"death",180,1,"SE_EXPLOSION","NPC_GRUNT","","diag_efforts_DeathExplo_gl_grunt_3p","","","spmp"
+"death",200,1,"SE_FALL","NPC_GRUNT","","diag_efforts_DeathFall_gl_grunt_3p","","","spmp"
+"death",220,1,"SE_PROWLER","NPC_GRUNT","","diag_efforts_DeathProwler_gl_grunt_3p","","","spmp"
+"death",290,1,"SE_TITAN_STEP","NPC_GRUNT","","titan_grunt_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","NPC_MARVIN","","titan_spectre_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","NPC_SPECTRE","","titan_spectre_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","NPC_STALKER","","titan_spectre_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","NPC_PROWLER","","titan_grunt_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_HUMAN_MALE","","titan_grunt_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_HUMAN_MALE","","titan_grunt_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_ANDROID_MALE","","titan_spectre_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_HUMAN_FEMALE","","titan_grunt_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_ANDROID_FEMALE","","titan_spectre_squish","","","spmp"
+"death",290,1,"SE_TITAN_STEP","NPC_GRUNT","","diag_efforts_DeathCrush_gl_grunt_3p","","","spmp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_HUMAN_MALE","diag_efforts_DeathCrush_sp_cooper_3p_vs_1p","diag_efforts_DeathCrush_mp_maleHuman_3p","","","sp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_HUMAN_MALE","diag_efforts_DeathCrush_mp_maleHuman_3p_vs_1p","diag_efforts_DeathCrush_mp_maleHuman_3p","","","mp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_ANDROID_MALE","diag_efforts_DeathCrush_mp_maleRobot_3p_vs_1p","diag_efforts_DeathCrush_mp_maleRobot_3p","","","mp"
+"death",290,1,"SE_TITAN_STEP","PLAYER_HUMAN_FEMALE","diag_efforts_DeathCrush_mp_femaleHuman_3p_vs_1p","diag_efforts_DeathCrush_mp_femaleHuman_3p","","","mp"
+"death",400,1,"SE_ANY","NPC_MARVIN","","marvin_death","","","spmp"
+"death",400,1,"SE_TITAN_STEP","PLAYER_ANDROID_FEMALE","diag_efforts_DeathCrush_mp_femaleRobot_3p_vs_1p","diag_efforts_DeathCrush_mp_femaleRobot_3p","","","mp"
+"death",500,1,"SE_ANY","NPC_GRUNT","","diag_efforts_DeathQuick_gl_grunt_3p","","","spmp"
+"death",500,1,"SE_ANY","TITAN","titan_death_explode","titan_death_explode","","","spmp"
+"pain",300,1,"SE_ELECTRICAL","NPC_GRUNT","","diag_efforts_hitByArc_gl_grunt_3p","","","spmp"
+"pain",300,1,"SE_THERMITE_GRENADE","NPC_GRUNT","","diag_efforts_burns_gl_grunt_3p","","","spmp"
+"pain",400,0,"SE_PROWLER","NPC_GRUNT","","diag_efforts_hitByProwler_gl_grunt_3p","","","spmp"
+"pain",400,0,"SE_SMOKE","NPC_GRUNT","","diag_efforts_cough_gl_grunt_3p","","","spmp"
+"pain",400,0,"SE_ANY","NPC_GRUNT","","diag_efforts_hits_gl_grunt_3p","","","spmp"
+"pain",400,0,"SE_ANY","PLAYER_ANDROID_FEMALE","diag_efforts_hits_mp_femaleRobot_3p_vs_1p","","","","mp"
+"pain",400,0,"SE_ANY","PLAYER_ANDROID_MALE","diag_efforts_hits_mp_maleRobot_3p_vs_1p","","","","mp"
+"pain",400,0,"SE_ANY","PLAYER_HUMAN_FEMALE","diag_efforts_hits_mp_femaleHuman_3p_vs_1p","","","","mp"
+"pain",400,0,"SE_ANY","PLAYER_HUMAN_MALE","diag_efforts_hits_mp_maleHuman_3p_vs_1p","","","","mp"
+"pain",400,0,"SE_ANY","PLAYER_HUMAN_MALE","diag_efforts_hits_sp_cooper_3p_vs_1p","","","","sp" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pilot_abilities.csv b/Northstar.CustomServers/mod/scripts/datatable/pilot_abilities.csv
new file mode 100644
index 00000000..e9949479
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pilot_abilities.csv
@@ -0,0 +1,16 @@
+itemRef,type,damageSource,hidden,cost
+"mp_ability_grapple","PILOT_SPECIAL",0,0,180
+"mp_ability_heal","PILOT_SPECIAL",0,0,180
+"mp_ability_holopilot","PILOT_SPECIAL",0,0,180
+"mp_weapon_grenade_sonar","PILOT_SPECIAL",1,0,180
+"mp_ability_shifter","PILOT_SPECIAL",0,0,180
+"mp_weapon_deployable_cover","PILOT_SPECIAL",0,0,180
+"mp_ability_cloak","PILOT_SPECIAL",0,0,180
+"mp_weapon_frag_grenade","PILOT_ORDNANCE",1,0,220
+"mp_weapon_grenade_emp","PILOT_ORDNANCE",1,0,220
+"mp_weapon_grenade_gravity","PILOT_ORDNANCE",1,0,220
+"mp_weapon_grenade_electric_smoke","PILOT_ORDNANCE",1,0,220
+"mp_weapon_thermite_grenade","PILOT_ORDNANCE",1,0,220
+"mp_weapon_satchel","PILOT_ORDNANCE",1,0,220
+"melee_pilot_sword","NOT_LOADOUT",1,1,220
+"mp_ability_shifter_super","PILOT_SPECIAL",0,1,220 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pilot_executions.csv b/Northstar.CustomServers/mod/scripts/datatable/pilot_executions.csv
new file mode 100644
index 00000000..faae9db7
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pilot_executions.csv
@@ -0,0 +1,14 @@
+ref,name,description,image,hidden,cost
+"execution_neck_snap","#PILOT_EXECUTION_NECK_SNAP","#PILOT_EXECUTION_NECK_SNAP_DESC","rui/pilot_loadout/execution/execution_neck_snap",0,0
+"execution_face_stab","#PILOT_EXECUTION_FACE_STAB","#PILOT_EXECUTION_FACE_STAB_DESC","rui/pilot_loadout/execution/execution_face_stab",0,300
+"execution_backshot","#PILOT_EXECUTION_BACK_SHOT","#PILOT_EXECUTION_BACK_SHOT_DESC","rui/pilot_loadout/execution/execution_backshot",0,300
+"execution_combo","#PILOT_EXECUTION_COMBO","#PILOT_EXECUTION_COMBO_DESC","rui/pilot_loadout/execution/execution_combo",0,300
+"execution_knockout","#PILOT_EXECUTION_KNOCKOUT","#PILOT_EXECUTION_KNOCKOUT_DESC","rui/pilot_loadout/execution/execution_knockout",0,300
+"execution_telefrag","#PILOT_EXECUTION_TELEFRAG","#PILOT_EXECUTION_TELEFRAG_DESC","rui/pilot_loadout/execution/execution_inner_pieces",0,0
+"execution_stim","#PILOT_EXECUTION_STIM","#PILOT_EXECUTION_STIM_DESC","rui/pilot_loadout/execution/execution_straight_blast",0,0
+"execution_grapple","#PILOT_EXECUTION_GRAPPLE","#PILOT_EXECUTION_GRAPPLE_DESC","rui/pilot_loadout/execution/execution_grapple",0,0
+"execution_pulseblade","#PILOT_EXECUTION_PULSEBLADE","#PILOT_EXECUTION_PULSEBLADE_DESC","rui/pilot_loadout/execution/execution_pulseblade",0,0
+"execution_random","#PILOT_EXECUTION_RANDOM","#PILOT_EXECUTION_RANDOM_DESC","rui/pilot_loadout/execution/execution_random",0,0
+"execution_cloak","#PILOT_EXECUTION_CLOAK","#PILOT_EXECUTION_CLOAK_DESC","rui/pilot_loadout/execution/execution_now_you_see_me",0,0
+"execution_holopilot","#PILOT_EXECUTION_HOLOPILOT","#PILOT_EXECUTION_HOLOPILOT_DESC","rui/pilot_loadout/execution/execution_holopilot",0,0
+"execution_ampedwall","#PILOT_EXECUTION_AMPEDWALL","#PILOT_EXECUTION_AMPEDWALL_DESC","rui/pilot_loadout/execution/execution_amped_wall",0,0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pilot_passives.csv b/Northstar.CustomServers/mod/scripts/datatable/pilot_passives.csv
new file mode 100644
index 00000000..a13a98f2
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pilot_passives.csv
@@ -0,0 +1,10 @@
+passive,type,name,description,image,hidden,cost
+"pas_enemy_death_icons","PILOT_PASSIVE2","#GEAR_ENEMY_DEATH_ICONS","#GEAR_ENEMY_DEATH_ICONS_DESC","rui/pilot_loadout/kit/kill_report_menu",0,25
+"pas_ordnance_pack","PILOT_PASSIVE1","#GEAR_EXPLOSIVES_PACK","#GEAR_EXPLOSIVES_PACK_DESC","rui/pilot_loadout/kit/ordnance_expert_menu",0,125
+"pas_power_cell","PILOT_PASSIVE1","#GEAR_POWER_CELL","#GEAR_POWER_CELL_DESC","rui/pilot_loadout/kit/power_cell_menu",0,25
+"pas_fast_embark","PILOT_PASSIVE1","#GEAR_FAST_EMBARK","#GEAR_FAST_EMBARK_DESC","rui/pilot_loadout/kit/phase_embark_menu",0,125
+"pas_ads_hover","PILOT_PASSIVE2","#GEAR_ADS_HOVER","#GEAR_ADS_HOVER_DESC","rui/pilot_loadout/kit/hover_menu",0,225
+"pas_wallhang","PILOT_PASSIVE2","#GEAR_WALLHANG","#GEAR_WALLHANG_DESC","rui/pilot_loadout/kit/wall_hang_menu",0,25
+"pas_fast_health_regen","PILOT_PASSIVE1","#GEAR_FAST_HEALTH_REGEN","#GEAR_FAST_HEALTH_REGEN_DESC","rui/pilot_loadout/kit/quick_regen_menu",0,25
+"pas_stealth_movement","PILOT_PASSIVE2","#GEAR_STEALTH_KIT","#GEAR_STEALTH_KIT_DESC","rui/pilot_loadout/kit/stealth_movement_menu",0,225
+"pas_at_hunter","PILOT_PASSIVE2","#GEAR_AT_HUNTER_KIT","#GEAR_AT_HUNTER_KIT_DESC","rui/pilot_loadout/kit/titan_hunter_menu",0,225 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pilot_properties.csv b/Northstar.CustomServers/mod/scripts/datatable/pilot_properties.csv
new file mode 100644
index 00000000..6e40e332
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pilot_properties.csv
@@ -0,0 +1,8 @@
+type,image,tactical,defaultPassive1,defaultPassive2,melee,cost
+"nomad","rui/pilot_loadout/suit/nomad","mp_ability_heal","pas_power_cell","pas_enemy_death_icons","melee_pilot_emptyhanded",180
+"light","rui/pilot_loadout/suit/light","mp_ability_shifter","pas_fast_health_regen","pas_wallhang","melee_pilot_emptyhanded",180
+"geist","rui/pilot_loadout/suit/geist","mp_ability_cloak","pas_power_cell","pas_enemy_death_icons","melee_pilot_emptyhanded",180
+"medium","rui/pilot_loadout/suit/medium","mp_weapon_grenade_sonar","pas_fast_health_regen","pas_wallhang","melee_pilot_emptyhanded",180
+"grapple","rui/pilot_loadout/suit/grapple","mp_ability_grapple","pas_power_cell","pas_wallhang","melee_pilot_emptyhanded",180
+"heavy","rui/pilot_loadout/suit/heavy","mp_weapon_deployable_cover","pas_fast_health_regen","pas_enemy_death_icons","melee_pilot_emptyhanded",180
+"stalker","rui/pilot_loadout/suit/stalker","mp_ability_holopilot","pas_power_cell","pas_enemy_death_icons","melee_pilot_emptyhanded",180 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_features.csv b/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_features.csv
new file mode 100644
index 00000000..2226eb79
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_features.csv
@@ -0,0 +1,5 @@
+featureRef,featureName,featureDesc,featureIcon,cost
+"primarymod2","#EXTRA_MOD","","rui/hud/common/feature_icon",0
+"secondarymod2","#EXTRA_MOD","","rui/hud/common/feature_icon",0
+"primarymod3","#MOD_PRO_SCREEN_NAME","","rui/hud/common/feature_icon",0
+"secondarymod3","#MOD_PRO_SCREEN_NAME","","rui/hud/common/feature_icon",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_mods.csv b/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_mods.csv
new file mode 100644
index 00000000..b59acb18
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_mods.csv
@@ -0,0 +1,250 @@
+mod,weapon,statDamage,statAccuracy,statRange,statFireRate,statClipSize,hidden
+"extended_ammo","mp_weapon_r97",0,0,0,0,10,0
+"extended_ammo","mp_weapon_shotgun",0,0,0,0,3,0
+"extended_ammo","mp_weapon_mastiff",0,0,0,0,3,0
+"extended_ammo","mp_weapon_car",0,0,0,0,10,0
+"extended_ammo","mp_weapon_rspn101",0,0,0,0,6,0
+"extended_ammo","mp_weapon_rspn101_og",0,0,0,0,6,0
+"extended_ammo","mp_weapon_hemlok",0,0,0,0,6,0
+"extended_ammo","mp_weapon_g2",0,0,0,0,4,0
+"extended_ammo","mp_weapon_vinson",0,0,0,0,6,0
+"extended_ammo","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"extended_ammo","mp_weapon_alternator_smg",0,0,0,0,0,0
+"extended_ammo","mp_weapon_wingman",0,0,0,0,0,0
+"extended_ammo","mp_weapon_wingman_n",0,0,0,0,0,0
+"extended_ammo","mp_weapon_shotgun_pistol",0,0,0,0,0,0
+"extended_ammo","mp_weapon_sniper",0,0,0,0,0,0
+"extended_ammo","mp_weapon_defender",0,0,0,0,0,0
+"extended_ammo","mp_weapon_dmr",0,0,0,0,0,0
+"extended_ammo","mp_weapon_doubletake",0,0,0,0,0,0
+"extended_ammo","mp_weapon_softball",0,0,0,0,0,0
+"extended_ammo","mp_weapon_epg",0,0,0,0,0,0
+"extended_ammo","mp_weapon_pulse_lmg",0,0,0,0,0,0
+"extended_ammo","mp_weapon_smr",0,0,0,0,0,0
+"extended_ammo","mp_weapon_rocket_launcher",0,0,0,0,0,0
+"extended_ammo","mp_weapon_mgl",0,0,0,0,0,0
+"extended_ammo","mp_weapon_arc_launcher",0,0,0,0,0,0
+"extended_ammo","mp_weapon_semipistol",0,0,0,0,0,0
+"extended_ammo","mp_weapon_autopistol",0,0,0,0,0,0
+"extended_ammo","mp_weapon_lmg",0,0,0,0,0,0
+"extended_ammo","mp_weapon_lstar",0,0,0,0,0,0
+"extended_ammo","mp_weapon_esaw",0,0,0,0,0,0
+"silencer","mp_weapon_wingman",0,0,0,0,0,0
+"silencer","mp_weapon_shotgun_pistol",0,0,0,0,0,0
+"silencer","mp_weapon_autopistol",0,0,0,0,0,0
+"silencer","mp_weapon_semipistol",0,0,0,0,0,0
+"hcog","mp_weapon_rspn101",0,0,0,0,0,0
+"hcog","mp_weapon_rspn101_og",0,0,0,0,0,0
+"hcog","mp_weapon_hemlok",0,0,0,0,0,0
+"hcog","mp_weapon_g2",0,0,0,0,0,0
+"hcog","mp_weapon_vinson",0,0,0,0,0,0
+"hcog","mp_weapon_alternator_smg",0,0,0,0,0,0
+"redline_sight","mp_weapon_r97",0,0,0,0,0,0
+"redline_sight","mp_weapon_car",0,0,0,0,0,0
+"redline_sight","mp_weapon_rspn101",0,0,0,0,0,0
+"redline_sight","mp_weapon_rspn101_og",0,0,0,0,0,0
+"redline_sight","mp_weapon_hemlok",0,0,0,0,0,0
+"redline_sight","mp_weapon_g2",0,0,0,0,0,0
+"redline_sight","mp_weapon_lmg",0,0,0,0,0,0
+"redline_sight","mp_weapon_vinson",0,0,0,0,0,0
+"redline_sight","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"redline_sight","mp_weapon_alternator_smg",0,0,0,0,0,0
+"redline_sight","mp_weapon_lstar",0,0,0,0,0,0
+"redline_sight","mp_weapon_esaw",0,0,0,0,0,0
+"redline_sight","mp_weapon_shotgun",0,0,0,0,0,0
+"redline_sight","mp_weapon_mastiff",0,0,0,0,0,0
+"aog","mp_weapon_lstar",0,0,0,0,0,0
+"aog","mp_weapon_lmg",0,0,0,0,0,0
+"aog","mp_weapon_esaw",0,0,0,0,0,0
+"holosight","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"holosight","mp_weapon_car",0,0,0,0,0,0
+"holosight","mp_weapon_r97",0,0,0,0,0,0
+"holosight","mp_weapon_shotgun",0,0,0,0,0,0
+"holosight","mp_weapon_mastiff",0,0,0,0,0,0
+"scope_4x","mp_weapon_dmr",0,0,0,0,0,0
+"scope_4x","mp_weapon_sniper",0,0,0,0,0,0
+"scope_4x","mp_weapon_doubletake",0,0,0,0,0,0
+"ricochet","mp_weapon_sniper",0,0,0,0,0,0
+"ricochet","mp_weapon_doubletake",0,0,0,0,0,0
+"ricochet","mp_weapon_wingman_n",0,0,0,0,0,0
+"threat_scope","mp_weapon_rspn101",0,0,0,0,0,0
+"threat_scope","mp_weapon_rspn101_og",0,0,0,0,0,0
+"threat_scope","mp_weapon_vinson",0,0,0,0,0,0
+"threat_scope","mp_weapon_hemlok",0,0,0,0,0,0
+"threat_scope","mp_weapon_g2",0,0,0,0,0,0
+"threat_scope","mp_weapon_car",0,0,0,0,0,0
+"threat_scope","mp_weapon_r97",0,0,0,0,0,0
+"threat_scope","mp_weapon_alternator_smg",0,0,0,0,0,0
+"threat_scope","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"threat_scope","mp_weapon_sniper",0,0,0,0,0,0
+"threat_scope","mp_weapon_dmr",0,0,0,0,0,0
+"threat_scope","mp_weapon_doubletake",0,0,0,0,0,0
+"threat_scope","mp_weapon_lmg",0,0,0,0,0,0
+"threat_scope","mp_weapon_lstar",0,0,0,0,0,0
+"threat_scope","mp_weapon_esaw",0,0,0,0,0,0
+"threat_scope","mp_weapon_shotgun",0,0,0,0,0,0
+"threat_scope","mp_weapon_mastiff",0,0,0,0,0,0
+"quick_charge","mp_weapon_defender",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_rspn101",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_rspn101_og",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_vinson",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_hemlok",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_g2",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_car",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_r97",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_alternator_smg",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_lmg",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_lstar",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_esaw",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_sniper",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_dmr",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_doubletake",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_wingman",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_wingman_n",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_shotgun_pistol",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_autopistol",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_semipistol",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_shotgun",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_mastiff",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_softball",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_epg",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_pulse_lmg",0,0,0,0,0,0
+"tactical_cdr_on_kill","mp_weapon_smr",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_rspn101",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_rspn101_og",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_vinson",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_hemlok",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_g2",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_car",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_r97",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_alternator_smg",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_lmg",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_lstar",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_esaw",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_sniper",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_dmr",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_doubletake",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_wingman",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_wingman_n",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_shotgun_pistol",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_autopistol",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_semipistol",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_shotgun",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_mastiff",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_softball",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_epg",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_pulse_lmg",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_smr",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_defender",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_rocket_launcher",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_mgl",0,0,0,0,0,0
+"pas_fast_ads","mp_weapon_arc_launcher",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_rspn101",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_rspn101_og",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_vinson",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_hemlok",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_g2",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_car",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_r97",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_alternator_smg",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_lmg",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_lstar",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_esaw",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_sniper",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_dmr",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_doubletake",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_shotgun",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_mastiff",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_softball",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_epg",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_pulse_lmg",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_smr",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_defender",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_rocket_launcher",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_mgl",0,0,0,0,0,0
+"pas_fast_swap","mp_weapon_arc_launcher",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_rspn101",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_rspn101_og",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_vinson",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_hemlok",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_g2",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_car",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_r97",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_alternator_smg",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_lmg",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_lstar",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_esaw",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_sniper",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_dmr",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_doubletake",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_wingman",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_wingman_n",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_shotgun_pistol",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_autopistol",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_semipistol",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_shotgun",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_mastiff",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_softball",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_epg",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_pulse_lmg",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_smr",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_rocket_launcher",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_mgl",0,0,0,0,0,0
+"pas_fast_reload","mp_weapon_arc_launcher",0,0,0,0,0,0
+"pro_screen","mp_weapon_r97",0,0,0,0,0,0
+"pro_screen","mp_weapon_shotgun",0,0,0,0,0,0
+"pro_screen","mp_weapon_mastiff",0,0,0,0,0,0
+"pro_screen","mp_weapon_car",0,0,0,0,0,0
+"pro_screen","mp_weapon_rspn101",0,0,0,0,0,0
+"pro_screen","mp_weapon_rspn101_og",0,0,0,0,0,0
+"pro_screen","mp_weapon_hemlok",0,0,0,0,0,0
+"pro_screen","mp_weapon_g2",0,0,0,0,0,0
+"pro_screen","mp_weapon_vinson",0,0,0,0,0,0
+"pro_screen","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"pro_screen","mp_weapon_alternator_smg",0,0,0,0,0,0
+"pro_screen","mp_weapon_wingman",0,0,0,0,0,0
+"pro_screen","mp_weapon_wingman_n",0,0,0,0,0,0
+"pro_screen","mp_weapon_shotgun_pistol",0,0,0,0,0,0
+"pro_screen","mp_weapon_sniper",0,0,0,0,0,0
+"pro_screen","mp_weapon_defender",0,0,0,0,0,0
+"pro_screen","mp_weapon_dmr",0,0,0,0,0,0
+"pro_screen","mp_weapon_doubletake",0,0,0,0,0,0
+"pro_screen","mp_weapon_softball",0,0,0,0,0,0
+"pro_screen","mp_weapon_epg",0,0,0,0,0,0
+"pro_screen","mp_weapon_pulse_lmg",0,0,0,0,0,0
+"pro_screen","mp_weapon_smr",0,0,0,0,0,0
+"pro_screen","mp_weapon_rocket_launcher",0,0,0,0,0,0
+"pro_screen","mp_weapon_mgl",0,0,0,0,0,0
+"pro_screen","mp_weapon_arc_launcher",0,0,0,0,0,0
+"pro_screen","mp_weapon_semipistol",0,0,0,0,0,0
+"pro_screen","mp_weapon_autopistol",0,0,0,0,0,0
+"pro_screen","mp_weapon_lmg",0,0,0,0,0,0
+"pro_screen","mp_weapon_lstar",0,0,0,0,0,0
+"pro_screen","mp_weapon_esaw",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_r97",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_shotgun",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_mastiff",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_car",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_rspn101",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_rspn101_og",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_hemlok",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_g2",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_vinson",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_hemlok_smg",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_alternator_smg",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_wingman",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_wingman_n",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_shotgun_pistol",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_softball",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_epg",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_pulse_lmg",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_smr",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_semipistol",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_autopistol",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_lmg",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_lstar",0,0,0,0,0,0
+"pas_run_and_gun","mp_weapon_esaw",0,0,0,0,0,0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_mods_common.csv b/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_mods_common.csv
new file mode 100644
index 00000000..4718c126
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pilot_weapon_mods_common.csv
@@ -0,0 +1,27 @@
+mod,type,name,shortName,description,image,cost,costSniper,costPistol,costAT
+"iron_sights","attachment","#FACTORY_ISSUE_NAME","","#FACTORY_ISSUE_SIGHT_DESC","r2_ui/menus/loadout_icons/attachments/iron_sights",0,0,0,0
+"holosight","attachment","#MOD_HOLOSIGHT_NAME","","#MOD_HOLOSIGHT_DESC","r2_ui/menus/loadout_icons/attachments/holosight",0,0,0,0
+"hcog","attachment","#MOD_HCOG_NAME","HCOG","#MOD_HCOG_DESC","r2_ui/menus/loadout_icons/attachments/hcog_ranger",0,0,0,0
+"aog","attachment","#MOD_AOG_NAME","AOG","#MOD_AOG_DESC","r2_ui/menus/loadout_icons/attachments/aog",0,0,0,0
+"scope_4x","attachment","#MOD_SCOPE_4X_NAME","","#MOD_SCOPE_4X_DESC","r2_ui/menus/loadout_icons/attachments/variable_zoom",0,0,0,0
+"scope_6x","attachment","4x Zoom Scope","4x Zoom","#MOD_SCOPE_4X_DESC","ui/menu/items/attachment_icons/scope_6x",0,0,0,0
+"redline_sight","attachment","#MOD_ZOOM_SIGHT_NAME","#MOD_ZOOM_SIGHT_NAME","#MOD_ZOOM_SIGHT_DESC","r2_ui/menus/loadout_icons/attachments/hcog",0,0,0,0
+"threat_scope","attachment","#MOD_THREAT_SIGHT_NAME","T-Scope","#MOD_THREAT_SIGHT_DESC","r2_ui/menus/loadout_icons/attachments/threat_scope",0,0,0,0
+"extended_ammo","mod","#MOD_EXTENDED_AMMO_NAME","Ext. Mag","#MOD_EXTENDED_AMMO_DESC","rui/pilot_loadout/mods/extended_ammo",0,0,0,0
+"silencer","mod","#MOD_SILENCER_NAME","Suppressor","#MOD_SILENCER_DESC","r2_ui/menus/loadout_icons/attachments/suppressor",0,0,0,0
+"stabilizer","mod","Stabilizer","Stabilizer","Reduces sway when looking down scope","ui/menu/items/mod_icons/stabilizer",0,0,0,0
+"slammer","mod","Slammer","Slammer","Increases rodeo damage","ui/menu/items/mod_icons/slammer",0,0,0,0
+"enhanced_targeting","mod","Enhanced Targeting","E. Targeting","Enables multiple lock-on capability","ui/menu/items/mod_icons/enhanced_targeting",0,0,0,0
+"single_lock","mod","Single Lock","Single Lock","Single Lock on Pilots","ui/menu/items/mod_icons/enhanced_targeting",0,0,0,0
+"ricochet","mod","#MOD_RICOCHET_NAME","Ricochet","#MOD_RICOCHET_DESC","rui/pilot_loadout/mods/ricochet",0,0,0,0
+"ar_trajectory","mod","Ar Trajectory","Ar Trajectory","Displays estimated trajectory","rui/pilot_loadout/mods/ar_trajectory",0,0,0,0
+"alt_spread","mod","Alt Spread","Alt Spread","Alternate spread pattern","ui/temp",0,0,0,0
+"delayed_shot","mod","Soft Launch","Soft Launch","Delayed ignition with fast rockets","ui/temp",0,0,0,0
+"quick_charge","mod","#MOD_CHARGE_HACK_NAME","Charge Hack","#MOD_CHARGE_HACK_DESC","rui/pilot_loadout/mods/charge_hack",0,0,0,0
+"pas_run_and_gun","mod","#MOD_GUNRUNNER_NAME","Gunrunner","#MOD_GUNRUNNER_DESC","rui/pilot_loadout/mods/gunrunner",0,0,0,0
+"tactical_cdr_on_kill","mod","#MOD_TACTIKILL_NAME","Tactikill","#MOD_TACTIKILL_DESC","rui/pilot_loadout/mods/tactikill",0,0,0,0
+"pas_fast_ads","mod","#MOD_GUN_READY_NAME","Fast ADS","#MOD_GUN_READY_DESC","rui/pilot_loadout/mods/gun_ready",0,0,0,0
+"pas_fast_swap","mod","#MOD_SPEED_TRANSITION_NAME","Fast Swap","#MOD_SPEED_TRANSITION_DESC","rui/pilot_loadout/mods/speed_transition",0,0,0,0
+"pas_fast_reload","mod","#GEAR_QUICK_RELOAD","#GEAR_QUICK_RELOAD","#GEAR_QUICK_RELOAD_DESC","rui/pilot_loadout/kit/speed_loader",0,0,0,0
+"pro_screen","mod3","#MOD_PRO_SCREEN_NAME","Pro Screen","#MOD_PRO_SCREEN_DESC","rui/pilot_loadout/mods/pro_screen",0,0,0,0
+"rocket_arena","mod","#MOD_PRO_SCREEN_NAME","Pro Screen","#MOD_PRO_SCREEN_DESC","rui/pilot_loadout/mods/pro_screen",0,0,0,0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/pilot_weapons.csv b/Northstar.CustomServers/mod/scripts/datatable/pilot_weapons.csv
new file mode 100644
index 00000000..d8bbfb0a
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/pilot_weapons.csv
@@ -0,0 +1,33 @@
+itemRef,type,defaultAttachment,defaultMod1,defaultMod2,defaultMod3,hidden,xpPerLevelType,cost
+"mp_weapon_r97","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_alternator_smg","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_car","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_hemlok_smg","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_lmg","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_lstar","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_esaw","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_rspn101","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_vinson","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_hemlok","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_g2","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_shotgun","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_mastiff","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_dmr","PILOT_PRIMARY","","","","",0,"sniper",100
+"mp_weapon_doubletake","PILOT_PRIMARY","","","","",0,"sniper",100
+"mp_weapon_sniper","PILOT_PRIMARY","","","","",0,"sniper",100
+"mp_weapon_epg","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_smr","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_pulse_lmg","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_softball","PILOT_PRIMARY","","","","",0,"default",100
+"mp_weapon_autopistol","PILOT_SECONDARY","","","","",0,"pistol",50
+"mp_weapon_semipistol","PILOT_SECONDARY","","","","",0,"pistol",50
+"mp_weapon_wingman","PILOT_SECONDARY","","","","",0,"pistol",50
+"mp_weapon_shotgun_pistol","PILOT_PRIMARY","","","","",0,"default",50
+"mp_weapon_rocket_launcher","PILOT_SECONDARY","","","","",0,"antititan",50
+"mp_weapon_arc_launcher","PILOT_SECONDARY","","","","",0,"antititan",50
+"mp_weapon_defender","PILOT_SECONDARY","","","","",0,"antititan",50
+"mp_weapon_mgl","PILOT_SECONDARY","","","","",0,"antititan",50
+"melee_pilot_emptyhanded","PILOT_MELEE","","","","",0,"default",100
+"melee_pilot_arena","PILOT_MELEE","","","","",0,"default",100
+"mp_weapon_wingman_n","PILOT_PRIMARY","","","","",0,"default",50
+"mp_weapon_rspn101_og","PILOT_PRIMARY","","","","",0,"default",100 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/playlist_items.csv b/Northstar.CustomServers/mod/scripts/datatable/playlist_items.csv
new file mode 100644
index 00000000..f485fe70
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/playlist_items.csv
@@ -0,0 +1,27 @@
+playlist,name,image,thumbnail,cost
+"at","#PL_attrition","rui/menu/gametype_select/bounty_hunt","rui/menu/gametype_select/playlist_bounty_hunt",5
+"aitdm","#PL_aitdm","rui/menu/gametype_select/attrition","rui/menu/gametype_select/playlist_attrition",5
+"cp","#PL_hardpoint","rui/menu/gametype_select/hardpoint","rui/menu/gametype_select/playlist_amped_hardpoint",5
+"ctf","#PL_capture_the_flag","rui/menu/gametype_select/capture_the_flag","rui/menu/gametype_select/playlist_ctf",5
+"lts","#PL_last_titan_standing","rui/menu/gametype_select/last_titan_standing","rui/menu/gametype_select/playlist_lts",5
+"tdm","#PL_pilot_hunter","rui/menu/gametype_select/pilot_hunter","rui/menu/gametype_select/playlist_skirmish",5
+"varietypack","Variety Pack","rui/menu/gametype_select/variety_pack","rui/menu/gametype_select/playlist_gen_weapon",5
+"coliseum","#PL_coliseum","rui/menu/gametype_select/colosseum","rui/menu/gametype_select/playlist_coliseum",0
+"fnf","Friday Night Fights","rui/menu/gametype_select/friday_night_fights","rui/menu/gametype_select/playlist_gen_star",5
+"ffa","#PL_ffa","rui/menu/gametype_select/free_for_all","rui/menu/gametype_select/playlist_free_for_all",5
+"ps","#PL_pilot_skirmish","rui/menu/gametype_select/pilot_v_pilot","rui/menu/gametype_select/playlist_pilot_vs_pilot",5
+"default","Default","rui/menu/gametype_select/bounty_hunt","rui/menu/gametype_select/playlist_gen_star",5
+"fw","#PL_titan_war","rui/menu/gametype_select/titan_war","rui/menu/gametype_select/playlist_gen_arrow",5
+"private_match","#PL_private_match","rui/menu/gametype_select/private_match","rui/menu/gametype_select/playlist_gen_star",0
+"lf","#PL_speedball","rui/menu/gametype_select/hardcore_pilot_hunter","rui/menu/gametype_select/playlist_live_fire",5
+"angel_city_247","#PL_angel_city","rui/menu/gametype_select/angel_city","rui/menu/gametype_select/playlist_gen_star",5
+"hunted","#PL_hunted","rui/menu/gametype_select/variety_pack","rui/menu/gametype_select/playlist_apex",5
+"mfd","#PL_mfd","rui/menu/gametype_select/pilot_hunter","rui/menu/gametype_select/playlist_marked_for_death",5
+"speedball","#PL_speedball","rui/menu/gametype_select/hardcore_pilot_hunter","rui/menu/gametype_select/playlist_live_fire",0
+"fd_easy","#PL_fd_easy","rui/menu/gametype_select/last_titan_standing","rui/menu/gametype_select/playlist_fd_easy",0
+"fd","#PL_fd_normal","rui/menu/gametype_select/last_titan_standing","rui/menu/gametype_select/playlist_fd_normal",0
+"fd_normal","#PL_fd_normal","rui/menu/gametype_select/last_titan_standing","rui/menu/gametype_select/playlist_fd_normal",0
+"fd_hard","#PL_fd_hard","rui/menu/gametype_select/last_titan_standing","rui/menu/gametype_select/playlist_fd_hard",0
+"fd_master","#PL_fd_master","rui/menu/gametype_select/last_titan_standing","rui/menu/gametype_select/playlist_fd_master",0
+"fd_insane","#PL_fd_insane","rui/menu/gametype_select/last_titan_standing","rui/menu/gametype_select/playlist_fd_insane",0
+"ttdm","#PL_titan_brawl","rui/menu/gametype_select/last_titan_standing","rui/menu/gametype_select/playlist_titan_brawl",5 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/score_events.csv b/Northstar.CustomServers/mod/scripts/datatable/score_events.csv
new file mode 100644
index 00000000..d71d3ac9
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/score_events.csv
@@ -0,0 +1,221 @@
+name,splashText,medalText,pointValue,burnPointValue,pointType,xpValue,xpValueTitan,xpValueWeapon,xpValueFaction,xpType,displayType,medalIcon,conversation,scaleScoreForAutoTitan
+"AttritionAirDroneKilled","#SCORE_EVENT_AT_AIR_DRONE_KILLED","#SCORE_EVENT_AT_AIR_DRONE_KILLED","ATTRITION_SCORE_AIR_DRONE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionBossKilled","#SCORE_EVENT_AT_BOSS_KILLED","#SCORE_EVENT_AT_BOSS_KILLED","ATTRITION_SCORE_BOSS","","ASSAULT","0","0","0","0","","CALLINGCARD MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionBountySurvived","#SCORE_EVENT_AT_BOUNTY_SURVIVED","#SCORE_EVENT_AT_BOUNTY_SURVIVED","ATTRITION_SCORE_BOUNTY_SURVIVAL","","","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/victory","",0
+"AttritionGruntKilled","#SCORE_EVENT_AT_GRUNT_KILLED","#SCORE_EVENT_AT_GRUNT_KILLED","ATTRITION_SCORE_GRUNT","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"AttritionMegaTurretKilled","#SCORE_EVENT_AT_MEGA_TURRET_KILLED","#SCORE_EVENT_AT_MEGA_TURRET_KILLED","ATTRITION_SCORE_MEGATURRET","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionSentryTurretKilled","#SCORE_EVENT_AT_SENTRY_TURRET_KILLED","#SCORE_EVENT_AT_SENTRY_TURRET_KILLED","ATTRITION_SCORE_SENTRYTURRET","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionPilotKilled","#SCORE_EVENT_AT_PILOT_KILLED","#SCORE_EVENT_AT_PILOT_KILLED","ATTRITION_SCORE_PILOT","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"AttritionProwlerKilled","#SCORE_EVENT_AT_PROWLER_KILLED","#SCORE_EVENT_AT_PROWLER_KILLED","ATTRITION_SCORE_PROWLER","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"AttritionSpectreKilled","#SCORE_EVENT_AT_SPECTRE_KILLED","#SCORE_EVENT_AT_SPECTRE_KILLED","ATTRITION_SCORE_SPECTRE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionStalkerKilled","#SCORE_EVENT_AT_STALKER_KILLED","#SCORE_EVENT_AT_STALKER_KILLED","ATTRITION_SCORE_STALKER","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionSuperSpectreKilled","#SCORE_EVENT_AT_SUPER_SPECTRE_KILLED","#SCORE_EVENT_AT_SUPER_SPECTRE_KILLED","ATTRITION_SCORE_SUPER_SPECTRE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionTitanDoomed","#SCORE_EVENT_AT_TITAN_DOOMED","#SCORE_EVENT_AT_TITAN_DOOMED","ATTRITION_SCORE_TITAN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionTitanKilled","#SCORE_EVENT_AT_TITAN_DOOMED","#SCORE_EVENT_AT_TITAN_DOOMED","ATTRITION_SCORE_TITAN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"AttritionCashedBonus","#SCORE_EVENT_AT_CASHED_BONUS","#SCORE_EVENT_AT_CASHED_BONUS","ATTRITION_SCORE_BONUS","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"AttritionBonusStolen","#SCORE_EVENT_AT_BONUS_STOLEN","#SCORE_EVENT_AT_BONUS_STOLEN","ATTRITION_SCORE_BONUS_STOLEN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"BombCarry","#SCORE_EVENT_BOMB_CARRY_POINTS","#SCORE_EVENT_BOMB_CARRY_POINTS","GAMEMODE_BOMB_SCORE_CARRY","","DISTANCE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/bomb","",0
+"BombPlant","#SCORE_EVENT_BOMB_PLANT_POINTS","#SCORE_EVENT_BOMB_PLANT_POINTS","GAMEMODE_BOMB_SCORE_PLANT","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/bomb","",0
+"BombDefuse","#SCORE_EVENT_BOMB_DEFUSE_POINTS","#SCORE_EVENT_BOMB_DEFUSE_POINTS","GAMEMODE_BOMB_SCORE_DEFUSE","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/bomb","",0
+"BombExplode","#SCORE_EVENT_BOMB_EXPLODE_POINTS","#SCORE_EVENT_BOMB_EXPLODE_POINTS","GAMEMODE_BOMB_SCORE_EXPLODE","","DETONATION","0","0","0","0","","MEDAL GAMEMODE","rui/medals/bomb","",0
+"ChallengeCompleted","#SCORE_EVENT_CHALLENGE_COMPLETED","#SCORE_EVENT_CHALLENGE_COMPLETED","0","","","0","0","0","0","","MEDAL","rui/medals/victory","",0
+"ChallengePVPKillCount","#SCORE_EVENT_CHALLENGE_PVP_KILLS","#SCORE_EVENT_CHALLENGE_PVP_KILLS","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeATAssault","#SCORE_EVENT_CHALLENGE_AT_ASSAULT","#SCORE_EVENT_CHALLENGE_AT_ASSAULT","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeCPAssault","#SCORE_EVENT_CHALLENGE_CP_ASSAULT","#SCORE_EVENT_CHALLENGE_CP_ASSAULT","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeCPDefense","#SCORE_EVENT_CHALLENGE_CP_DEFENSE","#SCORE_EVENT_CHALLENGE_CP_DEFENSE","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeCTFCapAssist","#SCORE_EVENT_ASSIST_WITH_FLAG_CAP","#SCORE_EVENT_ASSIST_WITH_FLAG_CAP","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeCTFRetAssist","#SCORE_EVENT_ASSIST_WITH_FLAG_RET","#SCORE_EVENT_ASSIST_WITH_FLAG_RET","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeFW","#SCORE_EVENT_USE_N_BATTERIES","#SCORE_EVENT_USE_N_BATTERIES","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeFFA","#SCORE_EVENT_KILL_N_PILOTS","#SCORE_EVENT_KILL_N_PILOTS","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeTDM","#SCORE_EVENT_KILL_N_IN_A_ROW","#SCORE_EVENT_KILL_N_IN_A_ROW","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeTTDM","#SCORE_EVENT_CHALLENGE_COMPLETED","#SCORE_EVENT_CHALLENGE_COMPLETED","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeLTS","#SCORE_EVENT_KILL_N_TITANS","#SCORE_EVENT_KILL_N_TITANS","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeSPEEDBALL","#SCORE_EVENT_KILL_N_PILOTS","#SCORE_EVENT_KILL_N_PILOTS","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeMFD","#SCORE_EVENT_CHALLENGE_COMPLETED","#SCORE_EVENT_CHALLENGE_COMPLETED","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"ChallengeFD","#SCORE_EVENT_CHALLENGE_FD","#SCORE_EVENT_CHALLENGE_FD","","","","1","0","0","0","SCORE_MILESTONE","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"Comeback","#SCORE_EVENT_COMEBACK","#SCORE_EVENT_COMEBACK","POINTVALUE_COMEBACK","","","0","0","0","0","","MEDAL","rui/medals/kill","kc_comeback",0
+"ControlPanelHeavyTurretActivate","#SCORE_EVENT_HEAVY_TURRET_ACTIVATED","#SCORE_EVENT_HEAVY_TURRET_ACTIVATED","POINTVALUE_CONTROL_PANEL_ACTIVATE","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"ControlPanelLightTurretActivate","#SCORE_EVENT_LIGHT_TURRETS_ACTIVATED","#SCORE_EVENT_LIGHT_TURRETS_ACTIVATED","POINTVALUE_CONTROL_PANEL_ACTIVATE_LIGHT","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"ControlPointCapture","#SCORE_EVENT_HARDPOINT_CAPTURED","#SCORE_EVENT_HARDPOINT_CAPTURED","POINTVALUE_HARDPOINT_CAPTURE","","ASSAULT","0","0","0","0","","CALLINGCARD MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"ControlPointCaptureAssist","#SCORE_EVENT_HARDPOINT_CAPTURE_ASSIST","#SCORE_EVENT_HARDPOINT_CAPTURE_ASSIST","POINTVALUE_HARDPOINT_CAPTURE_ASSIST","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"ControlPointHold","#SCORE_EVENT_HARDPOINT_HOLD","#SCORE_EVENT_HARDPOINT_HOLD","POINTVALUE_HARDPOINT_HOLD","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"ControlPointTake","#SCORE_EVENT_HARDPOINT_TAKE","#SCORE_EVENT_HARDPOINT_TAKE","POINTVALUE_HARDPOINT_HOLD","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"ControlPointAmped","#SCORE_EVENT_HARDPOINT_AMPED","#SCORE_EVENT_HARDPOINT_AMPED","POINTVALUE_HARDPOINT_AMPED","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"ControlPointAmpedHold","#SCORE_EVENT_HARDPOINT_AMPED_HOLD","#SCORE_EVENT_HARDPOINT_AMPED_HOLD","POINTVALUE_HARDPOINT_AMPED_HOLD","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"ControlPointNeutralize","#SCORE_EVENT_HARDPOINT_NEUTRALIZED","#SCORE_EVENT_HARDPOINT_NEUTRALIZED","POINTVALUE_HARDPOINT_NEUTRALIZE","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"ControlPointNeutralizeAssist","#SCORE_EVENT_HARDPOINT_NEUTRALIZE_ASSIST","#SCORE_EVENT_HARDPOINT_NEUTRALIZE_ASSIST","POINTVALUE_HARDPOINT_NEUTRALIZE_ASSIST","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"Damage","#SCORE_EVENT_DAMAGE_GENERIC","#SCORE_EVENT_DAMAGE_GENERIC","0","","","0","0","0","0","","CENTER","rui/medals/kill","",0
+"DamageTitan","#SCORE_EVENT_DAMAGE_TITAN","#SCORE_EVENT_DAMAGE_TITAN","0","","","0","0","0","0","","CENTER","rui/medals/kill","",0
+"StealMeter","#SCORE_EVENT_STEAL_METER","#SCORE_EVENT_STEAL_METER","0","","","0","0","0","0","","CENTER","rui/medals/kill","",0
+"Destored_Proximity_Mine","#SCORE_EVENT_DESTROYED_PROXIMITY_CHARGE","#SCORE_EVENT_DESTROYED_PROXIMITY_CHARGE","POINTVALUE_DESTROYED_PROXIMITY_MINE","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"Destroyed_Satchel","#SCORE_EVENT_DESTROYED_SATCHEL_CHARGE","#SCORE_EVENT_DESTROYED_SATCHEL_CHARGE","POINTVALUE_DESTROYED_SATCHEL","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"Dominating","#SCORE_EVENT_DOMINATING","#SCORE_EVENT_DOMINATING","POINTVALUE_DOMINATING","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","kc_dominating",0
+"DoomAutoTitan","#MEDAL_DOOMED_TITAN","#MEDAL_DOOMED_TITAN","100","BURN_METER_SMALL_POINT_VALUE","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"DoomTitan","#MEDAL_DOOMED_TITAN","#MEDAL_DOOMED_TITAN","100","BURN_METER_SMALL_POINT_VALUE","","0","0","0","0","","BIG MEDAL","rui/medals/kill_robot","",0
+"DoubleKill","#SCORE_EVENT_DOUBLE_KILL","#SCORE_EVENT_DOUBLE_KILL","POINTVALUE_DOUBLEKILL","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","kc_doublekill",0
+"EliminatePilot","#SCORE_EVENT_ELIMINATED_PILOT","#MEDAL_ELIMINATED_PILOT","POINTVALUE_ELIMINATE_PILOT","BURN_METER_SMALL_POINT_VALUE","","0","0","1","0","KILL","BIG MEDAL CENTER","rui/medals/kill","",0
+"EliminateTitan","#MEDAL_ELIMINATED_TITAN","#MEDAL_ELIMINATED_TITAN","1","BURN_METER_SMALL_POINT_VALUE","","0","0","1","0","","CALLINGCARD MEDAL","rui/medals/kill_robot","",0
+"EliminateAutoTitan","#MEDAL_ELIMINATED_TITAN","#MEDAL_ELIMINATED_TITAN","1","BURN_METER_SMALL_POINT_VALUE","","0","0","1","0","","CALLINGCARD MEDAL","rui/medals/kill_robot","",0
+"FirstStrike","#SCORE_EVENT_FIRST_STRIKE","#SCORE_EVENT_FIRST_STRIKE","POINTVALUE_FIRST_STRIKE","","","0","0","0","0","","CALLINGCARD MEDAL","rui/medals/first_strike","kc_firstblood",0
+"FirstTitanfall","#SCORE_EVENT_FIRST_TITANFALL","#SCORE_EVENT_FIRST_TITANFALL","POINTVALUE_FIRST_TITANFALL","","","0","1","0","0","TITAN_FALL","CALLINGCARD MEDAL","rui/medals/titanfall","",0
+"FishInBarrel","#SCORE_EVENT_FISH_IN_A_BARREL","#SCORE_EVENT_FISH_IN_A_BARREL","POINTVALUE_FISHINBARREL","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"FlagCapture","#SCORE_EVENT_FLAG_CAPTURE","#SCORE_EVENT_FLAG_CAPTURE","POINTVALUE_FLAG_CAPTURE","","","0","0","0","0","","CALLINGCARD MEDAL GAMEMODE","rui/medals/ctf","",0
+"FlagTaken","#SCORE_EVENT_FLAG_TAKEN","#SCORE_EVENT_FLAG_TAKEN","POINTVALUE_FLAG_TAKEN","","","0","0","0","0","","CALLINGCARD MEDAL GAMEMODE","rui/medals/ctf","",0
+"FlagCaptureAssist","#SCORE_EVENT_FLAG_CAPTURE_ASSIST","#SCORE_EVENT_FLAG_CAPTURE_ASSIST","POINTVALUE_FLAG_CAPTURE_ASSIST","","","0","0","0","0","","MEDAL","rui/medals/ctf","",0
+"FlagCarrierKill","#SCORE_EVENT_KILLED_FLAG_CARRIER","#SCORE_EVENT_KILLED_FLAG_CARRIER","POINTVALUE_FLAG_CARRIER_KILL","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/ctf","",0
+"FlagReturn","#SCORE_EVENT_FLAG_RETURN","#SCORE_EVENT_FLAG_RETURN","POINTVALUE_FLAG_RETURN","","","0","0","0","0","","CALLINGCARD MEDAL GAMEMODE","rui/medals/ctf","",0
+"FlyerKill","#SCORE_EVENT_KILLED_FLYER","#MEDAL_KILLED_FLYER","POINTVALUE_KILL_FLYER","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"GetToChopper","#SCORE_EVENT_GET_TO_THE_CHOPPER","#SCORE_EVENT_GET_TO_THE_CHOPPER","POINTVALUE_GET_TO_CHOPPER","","","0","0","0","0","","MEDAL","rui/medals/extract","",0
+"GiveRide","#SCORE_EVENT_GIVE_A_FRIEND_A_LIFT","#SCORE_EVENT_GIVE_A_FRIEND_A_LIFT","POINTVALUE_FRIEND_RIDE","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"HappyHourBonus","#SCORE_EVENT_HAPPY_HOUR_BONUS","#SCORE_EVENT_HAPPY_HOUR_BONUS","","","","5","0","0","0","HAPPY_HOUR","MEDAL CHALLENGE GAMEMODE","rui/medals/victory","",0
+"HardpointAssault","#SCORE_EVENT_HARDPOINT_ASSAULT","#SCORE_EVENT_HARDPOINT_ASSAULT","POINTVALUE_HARDPOINT_ASSAULT","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"HardpointDefense","#SCORE_EVENT_HARDPOINT_DEFENSE","#SCORE_EVENT_HARDPOINT_DEFENSE","POINTVALUE_HARDPOINT_DEFENSE","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"HardpointPerimeterDefense","#SCORE_EVENT_HARDPOINT_PERIMETER_DEFENSE","#SCORE_EVENT_HARDPOINT_PERIMETER_DEFENSE","POINTVALUE_HARDPOINT_PERIMETER_DEFENSE","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"HardpointSiege","#SCORE_EVENT_HARDPOINT_SIEGE","#SCORE_EVENT_HARDPOINT_SIEGE","POINTVALUE_HARDPOINT_SIEGE","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"HardpointSnipe","#SCORE_EVENT_HARDPOINT_RANGED_SUPPORT","#SCORE_EVENT_HARDPOINT_RANGED_SUPPORT","POINTVALUE_HARDPOINT_SNIPE","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"Headshot","#MEDAL_HEADSHOT","#MEDAL_HEADSHOT","POINTVALUE_HEADSHOT","","","0","0","0","0","","MEDAL","rui/medals/headshot","",0
+"HitchRide","#SCORE_EVENT_HITCH_A_RIDE","#SCORE_EVENT_HITCH_A_RIDE","POINTVALUE_RODEOD_FRIEND","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"HotZoneExtract","#SCORE_EVENT_HOT_ZONE_EXTRACT","#SCORE_EVENT_HOT_ZONE_EXTRACT","POINTVALUE_HOTZONE_EXTRACT","","","1","0","0","1","EVAC","MEDAL","rui/medals/extract","",0
+"KillAutoTitan","#MEDAL_KILLED_TITAN","#MEDAL_KILLED_TITAN","POINTVALUE_KILL_TITAN","BURN_METER_SMALL_POINT_VALUE","","0","0","1","0","","CALLINGCARD MEDAL","rui/medals/kill_robot","",0
+"KillDrone","#SCORE_EVENT_KILLED_DRONE","#MEDAL_KILLED_DRONE","POINTVALUE_KILL_DRONE","","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"KillDropship","#SCORE_EVENT_EVAC_DENIED","#SCORE_EVENT_EVAC_DENIED","POINTVALUE_EVAC_DENIED","","","0","0","0","0","","MEDAL","rui/medals/extract","",0
+"KilledEscapee","#SCORE_EVENT_KILLED_EVACUATING_ENEMY","#MEDAL_KILLED_EVACUATING_ENEMY","POINTVALUE_KILLED_ESCAPEE","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","",0
+"KilledMVP","#SCORE_EVENT_KILLED_MVP","#MEDAL_KILLED_MVP","POINTVALUE_KILLED_MVP","","","0","0","0","0","","MEDAL","rui/medals/kill","",0
+"KillGrunt","#SCORE_EVENT_KILLED_MILITIA","#SCORE_EVENT_KILLED_MILITIA","POINTVALUE_KILL_FIRETEAM_AI","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/kill","",0
+"KillHeavyTurret","#SCORE_EVENT_KILLED_HEAVY_TURRET","#SCORE_EVENT_KILLED_HEAVY_TURRET","POINTVALUE_KILL_HEAVY_TURRET","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"KillingSpree","#SCORE_EVENT_KILLING_SPREE","#SCORE_EVENT_KILLING_SPREE","POINTVALUE_KILLINGSPREE","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","kc_killingspree",0
+"KillLightTurret","#SCORE_EVENT_KILLED_LIGHT_TURRET","#SCORE_EVENT_KILLED_LIGHT_TURRET","POINTVALUE_KILL_LIGHT_TURRET","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"KillPilot","#SCORE_EVENT_KILLED_PILOT","#MEDAL_KILLED_PILOT","1","BURN_METER_SMALL_POINT_VALUE","","0","0","1","0","KILL","BIG MEDAL CENTER","rui/medals/kill","",0
+"KillProwler","#SCORE_EVENT_KILLED_PROWLER","#MEDAL_KILLED_PROWLER","POINTVALUE_KILL_PROWLER","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"KillRescueShip","#SCORE_EVENT_EVAC_DENIED","#SCORE_EVENT_EVAC_DENIED","POINTVALUE_EVAC_DENIED","","","0","0","0","0","","MEDAL","rui/medals/extract","",0
+"KillSpectre","#SCORE_EVENT_KILLED_SPECTRE","#MEDAL_KILLED_SPECTRE","POINTVALUE_KILL_SPECTRE","","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"KillStalker","#SCORE_EVENT_KILLED_STALKER","#MEDAL_KILLED_STALKER","POINTVALUE_KILL_STALKER","","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"KillHackedSpectre","#SCORE_EVENT_KILLED_HACKED_SPECTRE","#MEDAL_KILLED_SPECTRE","POINTVALUE_KILL_SPECTRE","","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"KillSuperSpectre","#SCORE_EVENT_KILLED_SUPER_SPECTRE","#MEDAL_KILLED_SUPER_SPECTRE","POINTVALUE_KILL_SUPER_SPECTRE","","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"KillTitan","#MEDAL_KILLED_TITAN","#MEDAL_KILLED_TITAN","POINTVALUE_KILL_TITAN","BURN_METER_SMALL_POINT_VALUE","","0","0","1","0","KILL","CALLINGCARD MEDAL CENTER","rui/medals/kill_robot","",0
+"TitanKillTitan","#MEDAL_KILLED_TITAN","#MEDAL_KILLED_TITAN","POINTVALUE_KILL_TITAN","BURN_METER_SMALL_POINT_VALUE","","0","1","1","0","KILL","CALLINGCARD MEDAL CENTER","rui/medals/kill_robot","",0
+"LeechDrone","#SCORE_EVENT_LEECHED_DRONE","#SCORE_EVENT_LEECHED_DRONE","POINTVALUE_LEECH_DRONE","","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"LeechSpectre","#SCORE_EVENT_LEECHED_SPECTRE","#SCORE_EVENT_LEECHED_SPECTRE","POINTVALUE_LEECH_SPECTRE","","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"LeechSuperSpectre","#SCORE_EVENT_LEECHED_SUPER_SPECTRE","#SCORE_EVENT_LEECHED_SUPER_SPECTRE","POINTVALUE_LEECH_SUPER_SPECTRE","","","0","0","0","0","","MEDAL","rui/medals/kill_robot","",0
+"MarkedEscort","#SCORE_EVENT_PROTECTED_MARKED_TARGET","#SCORE_EVENT_PROTECTED_MARKED_TARGET","POINTVALUE_MARKED_ESCORT","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/kill","",0
+"MarkedKilledMarked","#SCORE_EVENT_MARKED_KILLED_MARKED","#SCORE_EVENT_MARKED_KILLED_MARKED","POINTVALUE_MARKED_KILLED_MARKED","","","0","0","0","0","","MEDAL GAMEMODE","rui/hud/gametype_icons/mfd/mfd_enemy","",0
+"MarkedOutlastedEnemyMarked","#SCORE_EVENT_MARKED_OUTLASTED_ENEMY_MARK","#SCORE_EVENT_MARKED_OUTLASTED_ENEMY_MARK","POINTVALUE_MARKED_OUTLASTED_ENEMY_MARKED","","","0","0","0","0","","MEDAL GAMEMODE","rui/hud/gametype_icons/mfd/mfd_friendly","",0
+"MarkedSurvival","#SCORE_EVENT_MARKED_SURVIVAL","#SCORE_EVENT_MARKED_SURVIVAL","POINTVALUE_MARKED_SURVIVAL","","","0","0","0","0","","MEDAL GAMEMODE","rui/hud/gametype_icons/mfd/mfd_friendly","",0
+"MarkedTargetKilled","#SCORE_EVENT_KILLED_MARKED_TARGET","#SCORE_EVENT_KILLED_MARKED_TARGET","POINTVALUE_MARKED_TARGET_KILLED","","","0","0","0","0","","MEDAL GAMEMODE","rui/hud/gametype_icons/mfd/mfd_enemy","",0
+"MatchComplete","#SCORE_EVENT_COMPLETION","#SCORE_EVENT_COMPLETION","POINTVALUE_MATCH_COMPLETION","","","1","0","0","0","MATCH_COMPLETED","MEDAL","rui/medals/victory","",0
+"MatchVictory","#SCORE_EVENT_VICTORY","#SCORE_EVENT_VICTORY","POINTVALUE_MATCH_VICTORY","","","1","0","0","1","MATCH_VICTORY","MEDAL GAMEMODE","rui/medals/victory","",0
+"Mayhem","#SCORE_EVENT_MAYHEM","#SCORE_EVENT_MAYHEM","POINTVALUE_MAYHEM","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","",0
+"MegaKill","#SCORE_EVENT_MEGA_KILL","#SCORE_EVENT_MEGA_KILL","POINTVALUE_MEGAKILL","","","0","0","0","0","","CALLINGCARD MEDAL","rui/medals/kill_multiple","kc_megakill",0
+"Nemesis","#SCORE_EVENT_NEMESIS","#SCORE_EVENT_NEMESIS","POINTVALUE_NEMESIS","","","0","0","0","0","","MEDAL","rui/medals/kill","kc_retribution",0
+"NewPlayerBonus","#SCORE_EVENT_NEW_PLAYER_BONUS","#SCORE_EVENT_NEW_PLAYER_BONUS","POINTVALUE_MATCH_VICTORY","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/victory","",0
+"NPCHeadshot","#SCORE_EVENT_HEADSHOT","#SCORE_EVENT_HEADSHOT","POINTVALUE_NPC_HEADSHOT","","","0","0","0","0","","MEDAL","rui/medals/headshot","",0
+"Onslaught","#SCORE_EVENT_ONSLAUGHT","#SCORE_EVENT_ONSLAUGHT","POINTVALUE_ONSLAUGHT","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","",0
+"PilotAmmoPickup","Picked Up Ammo","Picked Up Ammo","50","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",1
+"PilotAssist","#MEDAL_ASSIST_PILOT","#MEDAL_ASSIST_PILOT","POINTVALUE_ASSIST","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/kill","",0
+"PilotBatteryApplied","#SCORE_EVENT_BATTERY_APPLY","#SCORE_EVENT_BATTERY_APPLY","50","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",1
+"PilotBatteryPickup","#SCORE_EVENT_BATTERY_PICKUP","#SCORE_EVENT_BATTERY_PICKUP","50","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",1
+"PilotBatteryStolen","#SCORE_EVENT_BATTERY_STEAL","#SCORE_EVENT_BATTERY_STEAL","50","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",1
+"QuickRevenge","#SCORE_EVENT_QUICK_REVENGE","#SCORE_EVENT_QUICK_REVENGE","POINTVALUE_REVENGE_QUICK","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","",0
+"Rampage","#SCORE_EVENT_RAMPAGE","#SCORE_EVENT_RAMPAGE","POINTVALUE_RAMPAGE","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","kc_rampage",0
+"Revenge","#SCORE_EVENT_REVENGE","#SCORE_EVENT_REVENGE","POINTVALUE_REVENGE","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","",0
+"RodeoEnemyTitan","#SCORE_EVENT_RODEOED_ENEMY_TITAN","#SCORE_EVENT_RODEOED_ENEMY_TITAN","POINTVALUE_RODEOD","","","0","0","0","0","","MEDAL","rui/medals/medal_prototype","",0
+"RoundComplete","#SCORE_EVENT_ROUND_COMPLETION","#SCORE_EVENT_ROUND_COMPLETION","POINTVALUE_ROUND_COMPLETION","","","0","0","0","0","","MEDAL","rui/medals/victory","",0
+"RoundVictory","#SCORE_EVENT_ROUND_WIN","#SCORE_EVENT_ROUND_WIN","POINTVALUE_ROUND_WIN","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/victory","",0
+"SelfBonusKilledAll","#SCORE_EVENT_SOLO_KILLED_ALL_COMBATANTS","#SCORE_EVENT_SOLO_KILLED_ALL_COMBATANTS","POINTVALUE_FULL_TEAM_KILL_SOLO","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","",0
+"Showstopper","#SCORE_EVENT_SHOWSTOPPER","#SCORE_EVENT_SHOWSTOPPER","POINTVALUE_SHOWSTOPPER","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","kc_iced",0
+"SoleSurvivor","#SCORE_EVENT_SOLE_SURVIVOR","#SCORE_EVENT_SOLE_SURVIVOR","POINTVALUE_SOLE_SURVIVOR","","","0","0","0","0","","MEDAL","rui/medals/extract","",0
+"SpotAssist","#SCORE_EVENT_SPOT_ASSIST","#SCORE_EVENT_SPOT_ASSIST","POINTVALUE_ASSIST","","","0","0","0","0","","MEDAL","rui/medals/headshot","",0
+"TeamBonusFullEvac","#SCORE_EVENT_TEAM_BONUS_FULL_TEAM_EVAC","#SCORE_EVENT_TEAM_BONUS_FULL_TEAM_EVAC","POINTVALUE_FULL_TEAM_EVAC","","","0","0","0","0","","MEDAL","rui/medals/extract","",0
+"TeamBonusKilledAll","#SCORE_EVENT_TEAM_BONUS_KILLED_ALL_COMBATANTS","#SCORE_EVENT_TEAM_BONUS_KILLED_ALL_COMBATANTS","POINTVALUE_FULL_TEAM_KILL","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","",0
+"TitanAssist","#MEDAL_ASSIST_TITAN","#MEDAL_ASSIST_TITAN","POINTVALUE_ASSIST_TITAN","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"TitanCoreEarned","#SCORE_EVENT_CORE_EARNED","#SCORE_EVENT_CORE_EARNED","POINTVALUE_CALLED_IN_TITAN","","","0","1","0","0","TITAN_CORE_EARNED","CALLINGCARD MEDAL","rui/medals/titanfall","",0
+"Titanfall","#SCORE_EVENT_TITANFALL","#SCORE_EVENT_TITANFALL","POINTVALUE_CALLED_IN_TITAN","","","0","1","0","0","TITAN_FALL","CALLINGCARD MEDAL","rui/medals/titanfall","",0
+"TripleKill","#SCORE_EVENT_TRIPLE_KILL","#SCORE_EVENT_TRIPLE_KILL","POINTVALUE_TRIPLEKILL","","","0","0","0","0","","MEDAL","rui/medals/kill_multiple","kc_triplekill",0
+"VictoryKill","#SCORE_EVENT_VICTORY_KILL","#SCORE_EVENT_VICTORY_KILL","POINTVALUE_VICTORYKILL","BURN_METER_SMALL_POINT_VALUE","","0","0","0","0","","CALLINGCARD MEDAL GAMEMODE","rui/medals/kill","",0
+"FactionLevelUp","","","","","","1","0","0","0","FACTION_LEVELED","","","",0
+"TitanLevelUp","","","","","","1","0","0","0","TITAN_LEVELED","","","",0
+"WeaponLevelUp","","","","","","1","0","0","0","WEAPON_LEVELED","","","",0
+"FortWarAssault","#SCORE_EVENT_FW_ASSAULT","#SCORE_EVENT_FW_ASSAULT","POINTVALUE_FW_ASSAULT","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarDefense","#SCORE_EVENT_FW_DEFENSE","#SCORE_EVENT_FW_DEFENSE","POINTVALUE_FW_DEFENSE","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarPerimeterDefense","#SCORE_EVENT_FW_PERIMETER_DEFENSE","#SCORE_EVENT_FW_PERIMETER_DEFENSE","POINTVALUE_FW_PERIMETER_DEFENSE","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarSiege","#SCORE_EVENT_FW_SIEGE","#SCORE_EVENT_FW_SIEGE","POINTVALUE_FW_SIEGE","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarSnipe","#SCORE_EVENT_FW_RANGED_SUPPORT","#SCORE_EVENT_FW_RANGED_SUPPORT","POINTVALUE_FW_SNIPE","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarBaseConstruction","#SCORE_EVENT_FW_BASE_CONSTRUCTION","#SCORE_EVENT_FW_BASE_CONSTRUCTION","POINTVALUE_FW_BASE_CONSTRUCTION","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarForwardConstruction","#SCORE_EVENT_FW_FORWARD_CONSTRUCTION","#SCORE_EVENT_FW_FORWARD_CONSTRUCTION","POINTVALUE_FW_FORWARD_CONSTRUCTION","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarInvasiveConstruction","#SCORE_EVENT_FW_INVASIVE_CONSTRUCTION","#SCORE_EVENT_FW_INVASIVE_CONSTRUCTION","POINTVALUE_FW_INVASIVE_CONSTRUCTION","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarShieldConstruction","#SCORE_EVENT_FW_SHIELD_CONSTRUCTION","#SCORE_EVENT_FW_SHIELD_CONSTRUCTION","POINTVALUE_FW_SHIELD_CONSTRUCTION","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarResourceDenial","#SCORE_EVENT_FW_RESOURCE_DENIAL","#SCORE_EVENT_FW_RESOURCE_DENIAL","POINTVALUE_FW_RESOURCE_DENIAL","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarTowerDamage","#SCORE_EVENT_FW_TOWER_DAMAGE","#SCORE_EVENT_FW_TOWER_DAMAGE","POINTVALUE_FW_TOWER_DAMAGE","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarTowerDefense","#SCORE_EVENT_FW_TOWER_DEFENSE","#SCORE_EVENT_FW_TOWER_DEFENSE","POINTVALUE_FW_TOWER_DEFENSE","","DEFENSE","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarTeamTurretControlBonus_One","#SCORE_EVENT_FW_TURRET_CONTROL_ONE","#SCORE_EVENT_FW_TURRET_CONTROL_ONE","POINTVALUE_FW_TEAM_TURRET_CONTROL","","DEFENSE","0","0","0","0","","MEDAL","rui/medals/hardpoint","",0
+"FortWarTeamTurretControlBonus_Two","#SCORE_EVENT_FW_TURRET_CONTROL_TWO","#SCORE_EVENT_FW_TURRET_CONTROL_TWO","POINTVALUE_FW_TEAM_TURRET_CONTROL","","DEFENSE","0","0","0","0","","MEDAL","rui/medals/hardpoint","",0
+"FortWarTeamTurretControlBonus_Three","#SCORE_EVENT_FW_TURRET_CONTROL_THREE","#SCORE_EVENT_FW_TURRET_CONTROL_THREE","POINTVALUE_FW_TEAM_TURRET_CONTROL","","DEFENSE","0","0","0","0","","MEDAL","rui/medals/hardpoint","",0
+"FortWarTeamTurretControlBonus_Four","#SCORE_EVENT_FW_TURRET_CONTROL_FOUR","#SCORE_EVENT_FW_TURRET_CONTROL_FOUR","POINTVALUE_FW_TEAM_TURRET_CONTROL","","DEFENSE","0","0","0","0","","MEDAL","rui/medals/hardpoint","",0
+"FortWarTeamTurretControlBonus_Five","#SCORE_EVENT_FW_TURRET_CONTROL_FIVE","#SCORE_EVENT_FW_TURRET_CONTROL_FIVE","POINTVALUE_FW_TEAM_TURRET_CONTROL","","DEFENSE","0","0","0","0","","MEDAL","rui/medals/hardpoint","",0
+"FortWarTeamTurretControlBonus_Six","#SCORE_EVENT_FW_TURRET_CONTROL_SIX","#SCORE_EVENT_FW_TURRET_CONTROL_SIX","POINTVALUE_FW_TEAM_TURRET_CONTROL","","DEFENSE","0","0","0","0","","MEDAL","rui/medals/hardpoint","",0
+"FortWarSecuringGatheredResources","#SCORE_EVENT_FW_SECURING_RESOURCES","#SCORE_EVENT_FW_SECURING_RESOURCES","POINTVALUE_FW_SECURING_RESOURCES","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"FortWarShieldDestroyed","#SCORE_EVENT_FW_DESTROYED_TURRET_SHIELD","#SCORE_EVENT_FW_DESTROYED_TURRET_SHIELD","POINTVALUE_FW_DESTROY_TURRET_SHIELD","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/hardpoint","",0
+"HuntedEliminatePilot","#SCORE_EVENT_HUNTED_HUNTER_KILLED","#SCORE_EVENT_HUNTED_HUNTER_KILLED","POINTVALUE_HUNTED_ELIMINATE_HUNTER","","DEFENSE","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"HuntedEliminateGrunt","#SCORE_EVENT_HUNTED_GRUNT_KILLED","#SCORE_EVENT_HUNTED_GRUNT_KILLED","POINTVALUE_HUNTED_ELIMINATE_GRUNT","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"HuntedEliminateSquad","#SCORE_EVENT_HUNTED_SQUAD_KILLED","#SCORE_EVENT_HUNTED_SQUAD_KILLED","POINTVALUE_HUNTED_ELIMINATE_SQUAD","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_multiple","",0
+"HuntedAquireAsset","#SCORE_EVENT_HUNTED_AQUIRE_ASSET","#SCORE_EVENT_HUNTED_AQUIRE_ASSET","POINTVALUE_HUNTED_AQUIRE_ASSET","","DEFENSE","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"HuntedSecureAsset","#SCORE_EVENT_HUNTED_AQUIRE_SECURED","#SCORE_EVENT_HUNTED_AQUIRE_SECURED","POINTVALUE_HUNTED_SECURE_ASSET","","DEFENSE","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"HuntedExtractAsset","#SCORE_EVENT_HUNTED_AQUIRE_EXTRACTED","#SCORE_EVENT_HUNTED_AQUIRE_EXTRACTED","POINTVALUE_HUNTED_EXTRACT_ASSET","","DEFENSE","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"HuntedSurvival","#SCORE_EVENT_HUNTED_OBJECTIVE_SURVIVAL","#SCORE_EVENT_HUNTED_OBJECTIVE_SURVIVAL","POINTVALUE_HUNTED_OBJECTIVE_SURVIVAL","","DEFENSE","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"HuntedMissionSurvival","#SCORE_EVENT_HUNTED_MISSION_SURVIVAL","#SCORE_EVENT_HUNTED_MISSION_SURVIVAL","POINTVALUE_HUNTED_MISSION_SURVIVAL","","DEFENSE","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"MFDMarked","#SCORE_EVENT_MARKED_FOR_DEATH_TARGET","#SCORE_EVENT_MARKED_FOR_DEATH_TARGET","POINTVALUE_MARKED_TARGET","","","0","1","0","0","","CALLINGCARD MEDAL","rui/medals/titanfall","",0
+"BombPlantRaid","#SCORE_EVENT_BOMB_PLANT_POINTS","#SCORE_EVENT_BOMB_PLANT_POINTS","GAMEMODE_RAID_SCORE_PLANT","","ASSAULT","0","0","0","0","","MEDAL GAMEMODE","rui/medals/bomb","",0
+"ATCOOPAirDroneKilled","#SCORE_EVENT_AT_AIR_DRONE_KILLED","#SCORE_EVENT_AT_AIR_DRONE_KILLED","ATCOOP_SCORE_AIR_DRONE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPBossKilled","#SCORE_EVENT_AT_BOSS_KILLED","#SCORE_EVENT_AT_BOSS_KILLED","ATCOOP_SCORE_BOSS","","ASSAULT","0","0","0","0","","CALLINGCARD MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPBountySurvived","#SCORE_EVENT_AT_BOUNTY_SURVIVED","#SCORE_EVENT_AT_BOUNTY_SURVIVED","ATCOOP_SCORE_BOUNTY_SURVIVAL","","","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/victory","",0
+"ATCOOPGruntKilled","#SCORE_EVENT_AT_GRUNT_KILLED","#SCORE_EVENT_AT_GRUNT_KILLED","ATCOOP_SCORE_GRUNT","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"ATCOOPMegaTurretKilled","#SCORE_EVENT_AT_MEGA_TURRET_KILLED","#SCORE_EVENT_AT_MEGA_TURRET_KILLED","ATCOOP_SCORE_MEGATURRET","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPSentryTurretKilled","#SCORE_EVENT_AT_SENTRY_TURRET_KILLED","#SCORE_EVENT_AT_SENTRY_TURRET_KILLED","ATCOOP_SCORE_SENTRYTURRET","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPPilotKilled","#SCORE_EVENT_AT_PILOT_KILLED","#SCORE_EVENT_AT_PILOT_KILLED","ATCOOP_SCORE_PILOT","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"ATCOOPProwlerKilled","#SCORE_EVENT_AT_PROWLER_KILLED","#SCORE_EVENT_AT_PROWLER_KILLED","ATCOOP_SCORE_PROWLER","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"ATCOOPSpectreKilled","#SCORE_EVENT_AT_SPECTRE_KILLED","#SCORE_EVENT_AT_SPECTRE_KILLED","ATCOOP_SCORE_SPECTRE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPStalkerKilled","#SCORE_EVENT_AT_STALKER_KILLED","#SCORE_EVENT_AT_STALKER_KILLED","ATCOOP_SCORE_STALKER","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPSuperSpectreKilled","#SCORE_EVENT_AT_SUPER_SPECTRE_KILLED","#SCORE_EVENT_AT_SUPER_SPECTRE_KILLED","ATCOOP_SCORE_SUPER_SPECTRE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPTitanDoomed","#SCORE_EVENT_AT_TITAN_DOOMED","#SCORE_EVENT_AT_TITAN_DOOMED","ATCOOP_SCORE_TITAN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPTitanKilled","#SCORE_EVENT_AT_TITAN_DOOMED","#SCORE_EVENT_AT_TITAN_DOOMED","ATCOOP_SCORE_TITAN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"ATCOOPCashedBonus","#SCORE_EVENT_AT_CASHED_BONUS","#SCORE_EVENT_AT_CASHED_BONUS","ATCOOP_SCORE_BONUS","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"ATCOOPBonusStolen","#SCORE_EVENT_AT_BONUS_STOLEN","#SCORE_EVENT_AT_BONUS_STOLEN","ATCOOP_SCORE_BONUS_STOLEN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXAirDroneKilled","#SCORE_EVENT_AT_AIR_DRONE_KILLED","#SCORE_EVENT_AT_AIR_DRONE_KILLED","PVE_SANDBOX_SCORE_AIR_DRONE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXBossKilled","#SCORE_EVENT_AT_BOSS_KILLED","#SCORE_EVENT_AT_BOSS_KILLED","PVE_SANDBOX_SCORE_BOSS","","ASSAULT","0","0","0","0","","CALLINGCARD MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXBountySurvived","#SCORE_EVENT_AT_BOUNTY_SURVIVED","#SCORE_EVENT_AT_BOUNTY_SURVIVED","PVE_SANDBOX_SCORE_BOUNTY_SURVIVAL","","","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/victory","",0
+"PVESANDBOXGruntKilled","#SCORE_EVENT_AT_GRUNT_KILLED","#SCORE_EVENT_AT_GRUNT_KILLED","PVE_SANDBOX_SCORE_GRUNT","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"PVESANDBOXMegaTurretKilled","#SCORE_EVENT_AT_MEGA_TURRET_KILLED","#SCORE_EVENT_AT_MEGA_TURRET_KILLED","PVE_SANDBOX_SCORE_MEGATURRET","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXSentryTurretKilled","#SCORE_EVENT_AT_SENTRY_TURRET_KILLED","#SCORE_EVENT_AT_SENTRY_TURRET_KILLED","PVE_SANDBOX_SCORE_SENTRYTURRET","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXPilotKilled","#SCORE_EVENT_AT_PILOT_KILLED","#SCORE_EVENT_AT_PILOT_KILLED","PVE_SANDBOX_SCORE_PILOT","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"PVESANDBOXProwlerKilled","#SCORE_EVENT_AT_PROWLER_KILLED","#SCORE_EVENT_AT_PROWLER_KILLED","PVE_SANDBOX_SCORE_PROWLER","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"PVESANDBOXSpectreKilled","#SCORE_EVENT_AT_SPECTRE_KILLED","#SCORE_EVENT_AT_SPECTRE_KILLED","PVE_SANDBOX_SCORE_SPECTRE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXStalkerKilled","#SCORE_EVENT_AT_STALKER_KILLED","#SCORE_EVENT_AT_STALKER_KILLED","PVE_SANDBOX_SCORE_STALKER","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXSuperSpectreKilled","#SCORE_EVENT_AT_SUPER_SPECTRE_KILLED","#SCORE_EVENT_AT_SUPER_SPECTRE_KILLED","PVE_SANDBOX_SCORE_SUPER_SPECTRE","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXTitanDoomed","#SCORE_EVENT_AT_TITAN_DOOMED","#SCORE_EVENT_AT_TITAN_DOOMED","PVE_SANDBOX_SCORE_TITAN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXTitanKilled","#SCORE_EVENT_AT_TITAN_DOOMED","#SCORE_EVENT_AT_TITAN_DOOMED","PVE_SANDBOX_SCORE_TITAN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"PVESANDBOXCashedBonus","#SCORE_EVENT_AT_CASHED_BONUS","#SCORE_EVENT_AT_CASHED_BONUS","PVE_SANDBOX_SCORE_BONUS","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"PVESANDBOXBonusStolen","#SCORE_EVENT_AT_BONUS_STOLEN","#SCORE_EVENT_AT_BONUS_STOLEN","PVE_SANDBOX_SCORE_BONUS_STOLEN","","ASSAULT","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"FDAirDroneKilled","#SCORE_EVENT_AT_AIR_DRONE_KILLED","#SCORE_EVENT_AT_AIR_DRONE_KILLED","FD_SCORE_AIR_DRONE","","","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"FDGruntKilled","#SCORE_EVENT_AT_GRUNT_KILLED","#SCORE_EVENT_AT_GRUNT_KILLED","FD_SCORE_GRUNT","","","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill","",0
+"FDSpectreKilled","#SCORE_EVENT_AT_SPECTRE_KILLED","#SCORE_EVENT_AT_SPECTRE_KILLED","FD_SCORE_SPECTRE","","","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"FDStalkerKilled","#SCORE_EVENT_AT_STALKER_KILLED","#SCORE_EVENT_AT_STALKER_KILLED","FD_SCORE_STALKER","","","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"FDSuperSpectreKilled","#SCORE_EVENT_AT_SUPER_SPECTRE_KILLED","#SCORE_EVENT_AT_SUPER_SPECTRE_KILLED","FD_SCORE_SUPER_SPECTRE","","","0","0","0","0","","ATTRITION MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"FDTitanKilled","#SCORE_EVENT_AT_TITAN_DOOMED","#SCORE_EVENT_AT_TITAN_DOOMED","FD_SCORE_TITAN","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"FDEnemiesKilled","#SCORE_EVENT_FD_ENEMIES_KILLED","#SCORE_EVENT_FD_ENEMIES_KILLED","FD_SCORE_ENEMIES","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/kill","",0
+"FDWaveMVP","#SCORE_EVENT_FD_WAVE_MVP","#SCORE_EVENT_FD_WAVE_MVP","FD_SCORE_MVP","","","0","0","0","0","","MEDAL_FORCED GAMEMODE CALLINGCARD","rui/medals/mvp","",0
+"FDTeamFinalWave","#SCORE_EVENT_FD_TEAM_FINAL_WAVE","#SCORE_EVENT_FD_TEAM_FINAL_WAVE","FD_SCORE_TEAM_FINAL_WAVE","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"FDTeamWave","#SCORE_EVENT_FD_TEAM_WAVE","#SCORE_EVENT_FD_TEAM_WAVE","FD_SCORE_TEAM_WAVE","","","0","0","0","0","","MEDAL_FORCED GAMEMODE","rui/medals/harvester_protect","",0
+"FDTeamFlawlessWave","#SCORE_EVENT_FD_TEAM_FLAWLESS_WAVE","#SCORE_EVENT_FD_TEAM_FLAWLESS_WAVE","FD_SCORE_TEAM_FLAWLESS_WAVE","","","0","0","0","0","","MEDAL_FORCED GAMEMODE","rui/medals/shielded_harvester","",0
+"PasTitanHunter","#GEAR_AT_HUNTER_KIT","#GEAR_AT_HUNTER_KIT","","","","0","0","0","0","KILL","MEDAL","rui/medals/kill_robot","",0
+"Execution","#SCORE_EVENT_EXECUTION","#SCORE_EVENT_EXECUTION","0","BURN_METER_LARGE_POINT_VALUE","","0","0","0","0","","MEDAL","rui/medals/kill","",0
+"FDRepairTurret","#SCORE_EVENT_REPAIR_TURRET","#SCORE_EVENT_REPAIR_TURRET","FD_SCORE_REPAIR_TURRET","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"FDDidntDie","#SCORE_EVENT_DIDNT_DIE","#SCORE_EVENT_DIDNT_DIE","FD_SCORE_DIDNT_DIE","","","0","0","0","0","","MEDAL_FORCED GAMEMODE","rui/medals/heal","",0
+"FDShieldHarvester","#SCORE_EVENT_FD_SHIELD_HARVESTER","#SCORE_EVENT_FD_SHIELD_HARVESTER","FD_SCORE_SHIELD_HARVESTER","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/shielded_harvester","",0
+"FDSonarPulse","#SCORE_EVENT_FD_SONAR_PULSE","#SCORE_EVENT_FD_SONAR_PULSE","FD_SCORE_SONAR_PULSE","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"FDArcTrapTriggered","#SCORE_EVENT_FD_ARC_TRAP_TRIGGERED","#SCORE_EVENT_FD_ARC_TRAP_TRIGGERED","FD_SCORE_ARC_TRAP_TRIGGERED","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"FDTetherTriggered","#SCORE_EVENT_FD_TETHER_TRAP_TRIGGERED","#SCORE_EVENT_FD_TETHER_TRAP_TRIGGERED","FD_SCORE_TETHER_TRAP_TRIGGERED","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/kill_robot","",0
+"FDArcWave","#SCORE_EVENT_FD_ARC_WAVE","#SCORE_EVENT_FD_ARC_WAVE","FD_SCORE_ARC_WAVE","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/medal_prototype","",0
+"FDTeamHeal","#SCORE_EVENT_FD_TEAM_HEAL","#SCORE_EVENT_FD_TEAM_HEAL","FD_SCORE_TEAM_HEAL","","","0","0","0","0","","MEDAL GAMEMODE","rui/medals/heal","",0
+"FDDamageBonus","#SCORE_EVENT_FD_DAMAGE_BONUS","#SCORE_EVENT_FD_DAMAGE_BONUS","FD_SCORE_DAMAGE_BONUS","","","0","0","0","0","","MEDAL_FORCED GAMEMODE SHOW_SCORE","rui/medals/medal_prototype","",0
+"FDHealingBonus","#SCORE_EVENT_FD_HEALING_BONUS","#SCORE_EVENT_FD_HEALING_BONUS","FD_SCORE_HEALING_BONUS","","","0","0","0","0","","MEDAL_FORCED GAMEMODE SHOW_SCORE","rui/medals/heal","",0
+"FDSupportBonus","#SCORE_EVENT_FD_SUPPORT_BONUS","#SCORE_EVENT_FD_SUPPORT_BONUS","FD_SCORE_SUPPORT_BONUS","","","0","0","0","0","","MEDAL_FORCED GAMEMODE SHOW_SCORE","rui/medals/kill_robot","",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/sp_levels.csv b/Northstar.CustomServers/mod/scripts/datatable/sp_levels.csv
new file mode 100644
index 00000000..f373d4ed
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/sp_levels.csv
@@ -0,0 +1,17 @@
+levelNum,level,startPoint,levelId,mainEntry,showLions,title,desc,missionSelectImage
+0,"sp_training","Pod Intro","sp_training",1,1,"#SP_TRAINING_CAMPAIGN_NAME","#SP_TRAINING_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image1"
+0,"sp_training","Gauntlet Mode","training_gauntlet_mode",0,1,"","",""
+1,"sp_crashsite","LevelStart","sp_crashsite",1,1,"#SP_CRASHSITE_CAMPAIGN_NAME","#SP_CRASHSITE_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image2"
+2,"sp_sewers1","Channel Mortar Run","sp_sewers1",1,1,"#SP_SEWERS1_CAMPAIGN_NAME","#SP_SEWERS1_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image3"
+3,"sp_boomtown_start","Intro","sp_boomtown_start",1,1,"#SP_BOOMTOWN_START_CAMPAIGN_NAME","#SP_BOOMTOWN_START_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image4"
+3,"sp_boomtown","Start","sp_boomtown",0,1,"","",""
+3,"sp_boomtown_end","Intro Caves","sp_boomtown_end",0,1,"","",""
+4,"sp_hub_timeshift","LECTURE HALLS","sp_hub_timeshift",1,0,"#SP_HUB_TIMESHIFT_CAMPAIGN_NAME","#SP_HUB_TIMESHIFT_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image5"
+4,"sp_timeshift_spoke02","Timeshift Device","sp_timeshift_spoke02",0,1,"","",""
+4,"sp_hub_timeshift","PRISTINE CAMPUS","timeshift_pt3",0,1,"","",""
+5,"sp_beacon","Level Start","sp_beacon",1,0,"#SP_BEACON_CAMPAIGN_NAME","#SP_BEACON_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image6"
+5,"sp_beacon_spoke0","Level Start","sp_beacon_spoke0",0,1,"","",""
+5,"sp_beacon","Spoke_0_Complete","beacon_pt3",0,1,"","",""
+6,"sp_tday","Intro","sp_tday",1,1,"#SP_TDAY_CAMPAIGN_NAME","#SP_TDAY_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image7"
+7,"sp_s2s","Level Start","sp_s2s",1,1,"#SP_S2S_CAMPAIGN_NAME","#SP_S2S_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image8"
+8,"sp_skyway_v1","Level Start","sp_skyway_v1",1,1,"#SP_SKYWAY_V1_CAMPAIGN_NAME","#SP_SKYWAY_V1_CAMPAIGN_DESC_MILITIA","rui/menu/level_select/level_image9" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/spectre_chatter_mp.csv b/Northstar.CustomServers/mod/scripts/datatable/spectre_chatter_mp.csv
new file mode 100644
index 00000000..4194a938
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/spectre_chatter_mp.csv
@@ -0,0 +1,8 @@
+conversationname,priority,debounce
+"diag_imc_spectre_gs_spotclosetitancall_01",200,20.000000
+"diag_imc_spectre_gs_engagepilotenemy_01_1",200,20.000000
+"diag_imc_spectre_gs_grenadeout_01_1",200,20.000000
+"diag_imc_spectre_gs_killenemypilot_01_1",200,20.000000
+"diag_imc_spectre_gs_gruntkillstitan_02_1",200,20.000000
+"diag_imc_spectre_gs_squaddeplete_01_1",200,20.000000
+"diag_imc_spectre_gs_allygrundown_05_1",200,20.000000 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/spotlight_images.csv b/Northstar.CustomServers/mod/scripts/datatable/spotlight_images.csv
new file mode 100644
index 00000000..1e6946c1
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/spotlight_images.csv
@@ -0,0 +1,30 @@
+image
+"rui/menu/main_menu/spotlight_01"
+"rui/menu/main_menu/spotlight_02"
+"rui/menu/main_menu/spotlight_03"
+"rui/menu/main_menu/spotlight_04"
+"rui/menu/main_menu/spotlight_05"
+"rui/menu/main_menu/spotlight_06"
+"rui/menu/main_menu/spotlight_07"
+"rui/menu/main_menu/spotlight_08"
+"rui/menu/main_menu/spotlight_09"
+"rui/menu/main_menu/spotlight_10"
+"rui/menu/main_menu/spotlight_11"
+"rui/menu/main_menu/spotlight_12"
+"rui/menu/main_menu/spotlight_13"
+"rui/menu/main_menu/spotlight_14"
+"rui/menu/main_menu/spotlight_15"
+"rui/menu/main_menu/spotlight_16"
+"rui/menu/main_menu/spotlight_17"
+"rui/menu/main_menu/spotlight_18"
+"rui/menu/main_menu/spotlight_19"
+"rui/menu/main_menu/spotlight_20"
+"rui/menu/main_menu/spotlight_21"
+"rui/menu/main_menu/spotlight_22"
+"rui/menu/main_menu/spotlight_23"
+"rui/menu/main_menu/spotlight_24"
+"rui/menu/main_menu/spotlight_25"
+"rui/menu/main_menu/spotlight_26"
+"rui/menu/main_menu/spotlight_27"
+"rui/menu/main_menu/spotlight_28"
+"rui/menu/main_menu/spotlight_29" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/startpoints.csv b/Northstar.CustomServers/mod/scripts/datatable/startpoints.csv
new file mode 100644
index 00000000..a171cd8a
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/startpoints.csv
@@ -0,0 +1,313 @@
+level,startpoint,loadScreenIndex,hasDetente,spLog,spLogTitle,isLeft
+"sp_grunt_arena","Pilot vs 3 Grunts (Trial)",0,0,"","",0
+"sp_grunt_arena","Pilot and Leeched Spectre vs Grunts (Trial)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Spectre (Trial)",0,0,"","",0
+"sp_grunt_arena","Arc Grenades vs Spectres and Grunts (Trial)",0,0,"","",0
+"sp_grunt_arena","Learn to use Shield Drone (Trial)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Grunts (Small Arena)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Grunts (Big Arena)",0,0,"","",0
+"sp_grunt_arena","Shield Drone vs Turrets (Trial)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Captains (Trial)",0,0,"","",0
+"sp_grunt_arena","Use Turrets vs AI (Trial)",0,0,"","",0
+"sp_grunt_arena","Turret Maze Part 1 (Trial)",0,0,"","",0
+"sp_grunt_arena","Turret Maze Part 2 (Trial)",0,0,"","",0
+"sp_grunt_arena","Learn to use Suicide Spectres (Trial)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Mixed AI (Big Arena)",0,0,"","",0
+"sp_grunt_arena","Pilot and Titan vs Many AI (Big Arena)",0,0,"","",0
+"sp_grunt_arena","Titan Wingman Arena (Big Arena)",0,0,"","",0
+"sp_grunt_arena","Titan vs Many AI (Big Arena)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Super Spectre (Big Arena)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Super Spectres (Small Arena)",0,0,"","",0
+"sp_grunt_arena","Titan vs Super Spectres (Big Arena)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Spectres (Big Arena)",0,0,"","",0
+"sp_grunt_arena","Pilot vs Stalker progressive (Big Arena)",0,0,"","",0
+"sp_grunt_arena","The End!",0,0,"","",0
+"sp_ab_gym","gym_windmills",0,0,"","",0
+"sp_ab_gym","gym_bongo_board",0,0,"","",0
+"sp_ab_testfire2","room1",0,0,"","",0
+"sp_ab_testfire2","room2",0,0,"","",0
+"sp_ola_hub","topside",0,0,"","",0
+"sp_ola_hub","hub_intro",0,0,"","",0
+"sp_ola_hub","engine1_connect",0,0,"","",0
+"sp_ola_spoke1","sp_ola_spoke1",0,0,"","",0
+"sp_ola_spoke1","ola_spoke1_cleanroom",0,0,"","",0
+"sp_ola_spoke1","ola_spoke1_powerrod_factory",0,0,"","",0
+"sp_ola_spoke1","ola_spoke1_canyon",0,0,"","",0
+"sp_ola_sewers","ola_sewers_drain",0,0,"","",0
+"sp_ola_sewers","ola_sewers_sludge_test1",0,0,"","",0
+"sp_ola_sewers","ola_sewers_sludge_test2",0,0,"","",0
+"sp_ola_sewers","ola_sewers_sludge_test3",0,0,"","",0
+"sp_ola_sewers","ola_sewers_sludge_test4",0,0,"","",0
+"sp_ola_sewers","ola_sewers_sludge_bt_test1",0,0,"","",0
+"sp_ola_canyon_test","ola_canyontest1",0,0,"","",0
+"sp_ola_canyon_test","ola_canyontest2",0,0,"","",0
+"sp_ola_canyon_test","ola_canyontest3",0,0,"","",0
+"sp_ola_canyon_test","ola_canyontest4",0,0,"","",0
+"sp_ola_canyon_test","dish_array",0,0,"","",0
+"sp_ola_canyon_test","ledge_run",0,0,"","",0
+"sp_ab_canyon_prowler","prowler_canyon_1",0,0,"","",0
+"sp_ab_canyon_prowler","prowler_canyon_2",0,0,"","",0
+"sp_ab_canyon_prowler","prowler_canyon_3",0,0,"","",0
+"sp_hub_timeshift","LECTURE HALLS",0,1,"SP_MISSION_LOG_TIMESHIFT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_hub_timeshift","OVERGROWN CAMPUS",1,0,"SP_MISSION_LOG_TIMESHIFT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_hub_timeshift","Corpse Search",1,0,"SP_MISSION_LOG_TIMESHIFT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_hub_timeshift","BT EXPLAINS IT ALL",1,0,"SP_MISSION_LOG_TIMESHIFT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_hub_timeshift","Zipline",1,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_hub_timeshift","Security",1,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_hub_timeshift","Skybridge",1,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_hub_timeshift","PRISTINE CAMPUS",2,1,"SP_MISSION_LOG_TIMESHIFT_SPLOG1","MENU_SP_LOG_TITLE_TIMESHIFT2",1
+"sp_hub_timeshift","Hub Fight",2,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG1","MENU_SP_LOG_TITLE_TIMESHIFT2",1
+"sp_hub_timeshift","Extended Bridge",2,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG1","MENU_SP_LOG_TITLE_TIMESHIFT2",1
+"sp_hub_timeshift","REACTOR MELTDOWN",2,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG1","MENU_SP_LOG_TITLE_TIMESHIFT2",1
+"sp_hub_timeshift","Core Scan",2,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG1","MENU_SP_LOG_TITLE_TIMESHIFT2",1
+"sp_hub_timeshift","Level End",2,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG1","MENU_SP_LOG_TITLE_TIMESHIFT2",1
+"sp_timeshift_spoke02","Timeshift Device",1,1,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_timeshift_spoke02","WILDLIFE RESEARCH",1,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_timeshift_spoke02","First Timeshift Fight",1,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_timeshift_spoke02","Elevator Fight",1,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_timeshift_spoke02","HUMAN RESEARCH",2,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_timeshift_spoke02","Sphere Room",2,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",1
+"sp_timeshift_spoke02","Human Room",3,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",0
+"sp_timeshift_spoke02","CAMPUS RETURN",3,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",0
+"sp_timeshift_spoke02","Fan Drop",3,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",0
+"sp_timeshift_spoke02","Fan Drop End",3,0,"SP_MISSION_LOG_TIMESHIFT_SPLOG0","MENU_SP_LOG_TITLE_TIMESHIFT1",0
+"sp_beacon","Level Start",1,1,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",0
+"sp_beacon","Control Room",1,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Spoke_0_Complete",2,1,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Power Relays Online",1,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Fastball to Spoke 1",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Spoke 1 Start",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Spoke 1 First Combat",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Spoke 1 Second Combat",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Spoke 1 Pillar Room",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Double Crane Puzzle",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Second Beacon",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Spoke 1 Arena",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Wallrun Panels",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Back at HUB",3,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Climb Dish",1,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon","Final Battle",4,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",0
+"sp_beacon","Send Signal",4,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",0
+"sp_beacon","Beacon Ending",4,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",0
+"sp_beacon_spoke0","Level Start",0,1,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon_spoke0","First Fight",1,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon_spoke0","Get Arc Tool",1,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon_spoke0","Got Arc Tool",2,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",0
+"sp_beacon_spoke0","Horizontal Fan",2,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",0
+"sp_beacon_spoke0","Horizontal Fan Complete",2,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",0
+"sp_beacon_spoke0","Fan Wallrun",2,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",0
+"sp_beacon_spoke2","Level Start",0,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon_spoke2","Fastball",0,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_beacon_spoke2","Loading Dock",0,0,"#SP_MISSION_LOG_BEACON","MENU_SP_OBJECTIVES_TITLE",1
+"sp_boomtown_ride","1 - Level Start",0,0,"","",0
+"sp_boomtown_ride","2 - Assembly Line",0,0,"","",0
+"sp_boomtown_ride","3 - First Fight",0,0,"","",0
+"sp_boomtown_ride","4 - First Ride",0,0,"","",0
+"sp_boomtown_ride","5 - Second Ride",0,0,"","",0
+"sp_ab_moving_geo_combat_1","1 - Moving Cover",0,0,"","",0
+"sp_ab_moving_geo_combat_1","2 - Moving Cover with High Ceiling",0,0,"","",0
+"sp_ab_moving_geo_combat_1","3 - Rotating Cover (CQC)",0,0,"","",0
+"sp_ab_moving_geo_combat_1","4 - Rotating Cover - Only Grunts",0,0,"","",0
+"sp_ab_moving_geo_combat_1","5 - Rotating Cover - Stationary Reaper",0,0,"","",0
+"sp_ab_moving_geo_combat_1","6 - Rotating Cover - Mobile Reaper",0,0,"","",0
+"sp_ab_moving_geo_combat_1","7 - Building Assembly Battle",0,0,"","",0
+"sp_training","Pod Intro",1,1,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Basic Movement",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Zen Garden",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Firing Range",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Gauntlet",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Gauntlet Challenge",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Titanfall",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Pod Outro",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Meet OG",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Gauntlet Mode",1,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_FIRSTRUN",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_WIP",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_01",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_02",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_03",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_04",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_05",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_06",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_07",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_08",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","DEV_GHOSTREC_GAUNTLET_CHAL_09",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Pod Intro DEV",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_training","Pod Outro DEV",0,0,"#SP_MISSION_LOG_TRAINING","MENU_SP_OBJECTIVES_TITLE",0
+"sp_wild","1 - Spoke 1: Escape Pod",0,0,"","",0
+"sp_wild","2 - Spoke 1: Basic Movement",0,0,"","",0
+"sp_wild","placeholder 3 - Spoke 1: Spectre Forest",0,0,"","",0
+"sp_wild","placeholder 4 - Spoke 1: Friend or Foe?",0,0,"","",0
+"sp_wild","placeholder 5 - Spoke 1: Grand Reveal",0,0,"","",0
+"sp_wild","placeholder 6 - Spoke 1: Checkpoint Zulu",0,0,"","",0
+"sp_wild","placeholder 7 - Spoke 1: Pistol Puzzle",0,0,"","",0
+"sp_wild","placeholder 8 - Spoke 1: Intruder",0,0,"","",0
+"sp_wild","placeholder 9 - HUB p-1: Gravity",0,0,"","",0
+"sp_wild","placeholder 10 - HUB p-1: Flirtation",0,0,"","",0
+"sp_wild","placeholder 11 - HUB p-1: Chase",0,0,"","",0
+"sp_wild","placeholder 12 - Spoke 2: Gorilla in the Mist",0,0,"","",0
+"sp_wild","13 - Spoke 2: Upgrades",0,0,"","",0
+"sp_wild","placeholder 14 - Spoke 2: A whole new world",0,0,"","",0
+"sp_wild","placeholder 15 - HUB p-2: Making Friends",0,0,"","",0
+"sp_wild","placeholder 16 - HUB p-2: Influencing People",0,0,"","",0
+"sp_wild","placeholder 17 - Spoke 3: Survivors",0,0,"","",0
+"sp_wild","placeholder 18 - Spoke 3: Prisoners",0,0,"","",0
+"sp_wild","placeholder 19 - HUB p-3: Snatch and Grab",0,0,"","",0
+"sp_wild","placeholder 20 - HUB p-3: Hero to Zero",0,0,"","",0
+"sp_wild","placeholder 21 - Spoke 4: Outpost Cherokee",0,0,"","",0
+"sp_wild","placeholder 22 - Spoke 4: Grenades",0,0,"","",0
+"sp_wild","placeholder 23 - Spoke 4: QRF",0,0,"","",0
+"sp_wild","placeholder 24 - Spoke 4: Those Eyes",0,0,"","",0
+"sp_wild","placeholder 25 - Spoke 5: Tree of Life",0,0,"","",0
+"sp_wild","26 - Spoke 5: Gift From the Gods",0,0,"","",0
+"sp_wild","placeholder 27 - HUB p-4: Energizer",0,0,"","",0
+"sp_wild","placeholder 28 - HUB p-4: One Man Army",0,0,"","",0
+"sp_wild","placeholder 29 - Spoke 5: p-2: Sleeping Beauty",0,0,"","",0
+"sp_wild","placeholder 30 - Spoke 5: p-2: Prowler Den",0,0,"","",0
+"sp_wild","placeholder 31 - Spoke 5: p-2: Common Ground",0,0,"","",0
+"sp_wild","placeholder 32 - Spoke 5: p-2: Dharma Hatch",0,0,"","",0
+"sp_crashsite","LevelStart",1,1,"SP_MISSION_LOG_WILDS_SPLOG0","MENU_SP_LOG_TITLE_WILDS",1
+"sp_crashsite","BT_Intro",1,0,"SP_MISSION_LOG_WILDS_SPLOG0","MENU_SP_LOG_TITLE_WILDS",1
+"sp_crashsite","Blisk_Intro",1,0,"SP_MISSION_LOG_WILDS_SPLOG0","MENU_SP_LOG_TITLE_WILDS",1
+"sp_crashsite","FamilyPhoto",1,0,"SP_MISSION_LOG_WILDS_SPLOG0","MENU_SP_LOG_TITLE_WILDS",1
+"sp_crashsite","Waking_Up",2,0,"SP_MISSION_LOG_WILDS_SPLOG0","MENU_SP_LOG_TITLE_WILDS",1
+"sp_crashsite","Field_Promotion",2,0,"SP_MISSION_LOG_WILDS_SPLOG0","MENU_SP_LOG_TITLE_WILDS",1
+"sp_crashsite","Grave",2,0,"SP_MISSION_LOG_WILDS_ALT","MENU_SP_OBJECTIVES_TITLE",0
+"sp_crashsite","Battery2_Path",2,0,"SP_MISSION_LOG_WILDS_ALT","MENU_SP_OBJECTIVES_TITLE",0
+"sp_crashsite","Battery2_Combat",2,0,"SP_MISSION_LOG_WILDS_ALT","MENU_SP_OBJECTIVES_TITLE",0
+"sp_crashsite","Battery2_Ship",2,0,"SP_MISSION_LOG_WILDS_ALT","MENU_SP_OBJECTIVES_TITLE",0
+"sp_crashsite","Battery3_Path",2,0,"SP_MISSION_LOG_WILDS_ALT","MENU_SP_OBJECTIVES_TITLE",0
+"sp_crashsite","Battery3_Combat",2,0,"SP_MISSION_LOG_WILDS_ALT","MENU_SP_OBJECTIVES_TITLE",0
+"sp_crashsite","Battery3_Ship",2,0,"SP_MISSION_LOG_WILDS_ALT","MENU_SP_OBJECTIVES_TITLE",0
+"sp_crashsite","PilotLink",2,0,"SP_MISSION_LOG_WILDS_ALT","MENU_SP_OBJECTIVES_TITLE",0
+"sp_ship_01","S2S_GOBLIN",0,0,"","",0
+"sp_ship_01","Goblin Deploy",0,0,"","",0
+"sp_ship_01","Goblin Deploy Zip",0,0,"","",0
+"sp_ship_01","S2S_FASTBALL",0,0,"","",0
+"sp_ship_01","S2S_REDEYE",0,0,"","",0
+"sp_ship_01","S2S_SENTINEL",0,0,"","",0
+"sp_ship_01","Model Test [ solid ]",0,0,"","",0
+"sp_ship_01","Model Test [ notsolid ]",0,0,"","",0
+"sp_ship_01","Bug: Skycam Parent",0,0,"","",0
+"sp_ship_01","Bug: Skycam Move/Rotate",0,0,"","",0
+"sp_ship_01","Bug: Ents outside Bounds",0,0,"","",0
+"sp_ship_01","S2S_COMBAT1-p1",0,0,"","",0
+"sp_ship_01","S2S_COMBAT1-p2",0,0,"","",0
+"sp_ship_01","S2S_COMBAT1-p3",0,0,"","",0
+"sp_ship_01","S2S_COMBAT1-p4",0,0,"","",0
+"sp_ship_03","hull combat p1",0,0,"","",0
+"sp_ship_04","hull combat p1",0,0,"","",0
+"sp_ship_05","hull combat p1",0,0,"","",0
+"sp_s2s","Level Start",1,1,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Intro",1,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Gibraltar",1,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Boss Intro",1,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Widow Fall",1,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Barker Ship",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","FastBall 1",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Malta Intro",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Malta Drone Room",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Malta Guns",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Malta Widow Jump",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Malta Hangar",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Malta Breach",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Malta Bridge",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Reunite with BT",2,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Malta Deck",3,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","BT Tackle",3,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Boss Fight",3,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Viper Dead",3,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",0
+"sp_s2s","Life Boats",4,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",1
+"sp_s2s","Core Room",4,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",1
+"sp_s2s","OLA Crash",4,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",1
+"sp_s2s","--------------",4,0,"SP_MISSION_LOG_S2S","MENU_SP_OBJECTIVES_TITLE",1
+"sp_s2s","TestBed",1,0,"","",0
+"sp_s2s","Dropship Combat Test",1,0,"","",0
+"sp_s2s","LightEdit Connect",1,0,"","",0
+"sp_s2s","TRAILER bridge",1,0,"","",0
+"sp_scrapyard_test","Arena 1",0,0,"","",0
+"sp_sewers1","Channel Mortar Run",1,1,"SP_MISSION_LOG_SEWERS","MENU_SP_OBJECTIVES_TITLE",1
+"sp_sewers1","Sewer Split",2,0,"SP_MISSION_LOG_SEWERS","MENU_SP_OBJECTIVES_TITLE",0
+"sp_sewers1","Sludge Falls",3,0,"SP_MISSION_LOG_SEWERS","MENU_SP_OBJECTIVES_TITLE",1
+"sp_sewers1","Corkscrew Room",4,0,"SP_MISSION_LOG_SEWERS","MENU_SP_OBJECTIVES_TITLE",0
+"sp_sewers1","Pipe Room Climb",5,0,"SP_MISSION_LOG_SEWERS","MENU_SP_OBJECTIVES_TITLE",1
+"sp_sewers1","Sewer Arena",6,0,"SP_MISSION_LOG_SEWERS","MENU_SP_OBJECTIVES_TITLE",1
+"sp_sewers1","Sewer Arena Defend",6,0,"SP_MISSION_LOG_SEWERS","MENU_SP_OBJECTIVES_TITLE",1
+"sp_sewers1","Kane Arena",7,0,"SP_MISSION_LOG_SEWERS","MENU_SP_OBJECTIVES_TITLE",1
+"sp_davis_sewer_battle","Sneaky Section",0,0,"","",0
+"sp_davis_sewer_battle","Combat Arena",0,0,"","",0
+"sp_davis_sewer_battle","Cylinder Tick Tunnels",0,0,"","",0
+"sp_ab_titan_arena","TvT Linear w 1.5 Boss Titans",0,0,"","",0
+"sp_ab_titan_arena","TvT Arena",0,0,"","",0
+"sp_ab_titan_arena","1v1 Arena A",0,0,"","",0
+"sp_ab_titan_arena","1v1 Arena B",0,0,"","",0
+"sp_ab_titan_arena","1v1 Arena C",0,0,"","",0
+"sp_ab_titan_arena","1v1 Arena D",0,0,"","",0
+"sp_boomtown_townboot","Town Boot",0,0,"","",0
+"sp_boomtown_townboot","Town Delivery",0,0,"","",0
+"sp_boomtown_townboot","Spotlight",0,0,"","",0
+"sp_ab_wilds_combat_test","Battery2",0,0,"","",0
+"sp_ab_wilds_combat_test","Battery3",0,0,"","",0
+"sp_skyway_v1","Level Start",0,1,"","",0
+"sp_skyway_v1","Torture Room B",1,0,"","",0
+"sp_skyway_v1","Smart Pistol Run",1,0,"","",0
+"sp_skyway_v1","Bridge Fight",2,0,"","",0
+"sp_skyway_v1","BT Reunion",3,0,"","",0
+"sp_skyway_v1","Titan Hill",3,0,"","",0
+"sp_skyway_v1","Titan Smash Hallway",3,0,"","",0
+"sp_skyway_v1","Sculptor Climb",3,0,"","",0
+"sp_skyway_v1","Targeting Room",4,0,"","",0
+"sp_skyway_v1","Injector Room",4,0,"","",0
+"sp_skyway_v1","Blisk's Farewell",4,0,"","",0
+"sp_skyway_v1","BT Sacrifice",4,0,"","",0
+"sp_skyway_v1","Rising World Run",5,0,"","",0
+"sp_skyway_v1","Rising World Jump",5,0,"","",0
+"sp_skyway_v1","Exploding Planet",5,0,"","",0
+"sp_skyway_v1","Harmony",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","--- CREDITS ---",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","sarah intro",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","walk",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","hug",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","cheer",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","actor jack cooper",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","actor BT / OG",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","actor blisk",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","actor sarah",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","actor bosses",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","actor marder",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","actors 6-4",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","actor barker",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","new BT",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","board ship",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_skyway_v1","final image",5,0,"SP_MISSION_LOG_SKYWAY_ALT","MENU_SP_OBJECTIVES_TITLE",1
+"sp_template","Level Start",0,0,"","",0
+"sp_template","Big Fight",0,0,"","",0
+"sp_template","Finale",0,0,"","",0
+"sp_tday","Intro",1,1,"SP_MISSION_LOG_TDAY","MENU_SP_OBJECTIVES_TITLE",1
+"sp_tday","Wall Bombardment",1,0,"SP_MISSION_LOG_TDAY","MENU_SP_OBJECTIVES_TITLE",1
+"sp_tday","Militia Big Charge",1,0,"SP_MISSION_LOG_TDAY","MENU_SP_OBJECTIVES_TITLE",1
+"sp_tday","Underground Fuel Storage",2,0,"SP_MISSION_LOG_TDAY","MENU_SP_OBJECTIVES_TITLE",1
+"sp_tday","Elevator",2,0,"SP_MISSION_LOG_TDAY","MENU_SP_OBJECTIVES_TITLE",1
+"sp_tday","VTOL",3,0,"SP_MISSION_LOG_TDAY","MENU_SP_OBJECTIVES_TITLE",0
+"sp_tday","Fire on the Runway",4,0,"SP_MISSION_LOG_TDAY","MENU_SP_OBJECTIVES_TITLE",0
+"sp_tday","OLA Launch",4,0,"SP_MISSION_LOG_TDAY","MENU_SP_OBJECTIVES_TITLE",0
+"sp_boomtown_start","Intro",0,1,"SP_MISSION_LOG_BOOMTOWN_1","MENU_SP_OBJECTIVES_TITLE",0
+"sp_boomtown_start","Prop House",1,0,"SP_MISSION_LOG_BOOMTOWN_1","MENU_SP_OBJECTIVES_TITLE",1
+"sp_boomtown_start","Narrow Hallway",1,0,"SP_MISSION_LOG_BOOMTOWN_1","MENU_SP_OBJECTIVES_TITLE",1
+"sp_boomtown_start","Titan Arena",1,0,"SP_MISSION_LOG_BOOMTOWN_1","MENU_SP_OBJECTIVES_TITLE",1
+"sp_boomtown_start","Loading Dock",1,0,"SP_MISSION_LOG_BOOMTOWN_1","MENU_SP_OBJECTIVES_TITLE",1
+"sp_boomtown","Start",0,1,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown","Assembly_Start",1,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown","Assembly_Dirt",1,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown","Assembly_Wallrun",1,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown","Assembly_Furniture",1,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown","Assembly_Walls",1,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown","Assembly_Highway",1,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown","Town_Climb_Entry",1,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown","ReaperTown",2,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG0","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown_end","Intro Caves",0,1,"SP_MISSION_LOG_BOOMTOWN_SPLOG1","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown_end","Prowler Towers",0,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG1","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown_end","Above The Dome",1,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG1","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown_end","Pre Ash Fight",2,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG1","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"sp_boomtown_end","Ash Fight",3,0,"SP_MISSION_LOG_BOOMTOWN_SPLOG1","MENU_SP_LOG_TITLE_BOOMTOWN",1
+"eof","eof",0,0,"","",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_abilities.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_abilities.csv
new file mode 100644
index 00000000..46164792
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_abilities.csv
@@ -0,0 +1,36 @@
+itemRef,type,damageSource,hidden
+"mp_titancore_flame_wave","TITAN_CORE_ABILITY",1,0
+"mp_titancore_flight_core","TITAN_CORE_ABILITY",0,0
+"mp_titancore_laser_cannon","TITAN_CORE_ABILITY",1,0
+"mp_titancore_salvo_core","TITAN_CORE_ABILITY",1,0
+"mp_titancore_shift_core","TITAN_CORE_ABILITY",0,0
+"mp_titancore_siege_mode","TITAN_CORE_ABILITY",0,0
+"mp_titancore_amp_core","TITAN_CORE_ABILITY",0,0
+"mp_titancore_dash_core","TITAN_CORE_ABILITY",0,0
+"mp_titanweapon_arc_wave","TITAN_ORDNANCE",1,0
+"mp_titanweapon_dumbfire_rockets","TITAN_ORDNANCE",1,0
+"mp_titanweapon_flame_wall","TITAN_ORDNANCE",1,0
+"mp_titanweapon_homing_rockets","TITAN_ORDNANCE",1,0
+"mp_titanweapon_laser_lite","TITAN_ORDNANCE",1,0
+"mp_titanweapon_salvo_rockets","TITAN_ORDNANCE",1,0
+"mp_titanweapon_shoulder_rockets","TITAN_ORDNANCE",1,0
+"mp_titanweapon_tracker_rockets","TITAN_ORDNANCE",1,0
+"mp_titanability_power_shot","TITAN_ORDNANCE",1,0
+"mp_titanability_tether_trap","TITAN_ORDNANCE",0,0
+"mp_titanability_basic_block","TITAN_SPECIAL",0,0
+"mp_titanability_particle_wall","TITAN_SPECIAL",0,0
+"mp_titanweapon_vortex_shield","TITAN_SPECIAL",1,0
+"mp_titanweapon_vortex_shield_ion","TITAN_SPECIAL",1,0
+"mp_titanweapon_heat_shield","TITAN_SPECIAL",1,0
+"mp_titanability_gun_shield","TITAN_SPECIAL",1,0
+"mp_titanability_smoke","TITAN_SPECIAL",0,0
+"mp_titanability_hover","TITAN_ANTIRODEO",0,0
+"mp_titanability_phase_dash","TITAN_ANTIRODEO",0,0
+"mp_titanability_laser_trip","TITAN_ANTIRODEO",0,0
+"mp_titanability_slow_trap","TITAN_ANTIRODEO",0,0
+"mp_titanability_ammo_swap","TITAN_ANTIRODEO",1,0
+"mp_titanability_sonar_pulse","TITAN_ANTIRODEO",1,0
+"mp_titanability_rocketeer_ammo_swap","TITAN_ANTIRODEO",0,0
+"mp_titanweapon_stun_laser","TITAN_SPECIAL",1,0
+"mp_titanability_rearm","TITAN_ANTIRODEO",1,0
+"mp_titancore_upgrade","TITAN_CORE_ABILITY",0,0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_executions.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_executions.csv
new file mode 100644
index 00000000..09c4a4d1
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_executions.csv
@@ -0,0 +1,26 @@
+ref,setfile,attackerAnim,attackerAnimVsAutoTitan,victimAnim_lt,victimAnim_md,victimAnim_hv,victimAnim_pt_lt,victimAnim_pt_md,victimAnim_pt_hv,attackerAnim_pt_lt,attackerAnim_pt_mt,attackerAnim_pt_ht,sound_1p,sound_3p,camAttach,type,name,description,image,hidden,cost,reqPrime,linkedExecutions
+"execution_legion","titan_ogre_minigun","htPRED_MP_Sync_Execution_Attacker","htPRED_MP_Sync_Execution_Attacker","t_MeleeExecuted_By_htPred","t_MeleeExecuted_By_htPred","t_MeleeExecuted_By_htPred","","","","","","","Ogre_1p_Sync_Melee","Ogre_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_LEGION_EXECUTION","#TITAN_EXECUTION_LEGION","#TITAN_EXECUTION_LEGION_DESC","rui/titan_loadout/execution/titan_execution_legion",0,0,0,""
+"execution_legion_prime","titan_ogre_legion_prime","htLegionPrime_MP_Sync_Execution_attacker","htLegionPrime_MP_Sync_Execution_attacker","t_MeleeExecuted_By_htLegionPrime","t_MeleeExecuted_By_htLegionPrime","t_MeleeExecuted_By_htLegionPrime","","","","","","","Ogre_1p_Sync_Melee","Ogre_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_LEGION_EXECUTION","#TITAN_EXECUTION_LEGION_PRIME","#TITAN_EXECUTION_LEGION_PRIME_DESC","rui/titan_loadout/execution/titan_execution_legion_prime",0,0,1,""
+"execution_scorch","titan_ogre_meteor","htThermite_MP_Sync_Execution_Attacker","htThermite_MP_Sync_Execution_Attacker","t_MeleeExecuted_By_htThermite","t_MeleeExecuted_By_htThermite","t_MeleeExecuted_By_htThermite","","","","","","","Ogre_1p_Sync_Melee","Ogre_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_SCORCH_EXECUTION","#TITAN_EXECUTION_SCORCH","#TITAN_EXECUTION_SCORCH_DESC","rui/titan_loadout/execution/titan_execution_scorch",0,0,0,""
+"execution_scorch_prime","titan_ogre_scorch_prime","htScorchPrime_MP_Sync_Execution_attacker","htScorchPrime_MP_Sync_Execution_attacker","t_MeleeExecuted_By_htScorchPrime","t_MeleeExecuted_By_htScorchPrime","t_MeleeExecuted_By_htScorchPrime","","","","","","","Ogre_1p_Sync_Melee","Ogre_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_SCORCH_EXECUTION","#TITAN_EXECUTION_SCORCH_PRIME","#TITAN_EXECUTION_SCORCH_PRIME_DESC","rui/titan_loadout/execution/titan_execution_scorch_prime",0,0,1,""
+"execution_ronin","titan_stryder_leadwall","lt_execution_attacker_sword_01","lt_execution_attacker_sword_01","lt_execution_victim_sword_01","mt_execution_victim_sword_01","ht_execution_victim_sword_01","pt_lt_execution_victim_sword_01","pt_mt_execution_victim_sword_01","pt_ht_execution_victim_sword_01","","","","Stryder_1p_Sync_Melee","Stryder_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_RONIN_EXECUTION","#TITAN_EXECUTION_RONIN","#TITAN_EXECUTION_RONIN_DESC","rui/titan_loadout/execution/titan_execution_ronin",0,0,0,""
+"execution_ronin_prime","titan_stryder_ronin_prime","lt_execution_attacker_sword_02_prime","lt_execution_attacker_sword_02_prime","lt_execution_victim_sword_02","mt_execution_victim_sword_02","ht_execution_victim_sword_02","pt_lt_execution_victim_sword_02","pt_mt_execution_victim_sword_02","pt_ht_execution_victim_sword_02","","","","Stryder_1p_Sync_Melee","Stryder_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_RONIN_EXECUTION","#TITAN_EXECUTION_RONIN_PRIME","#TITAN_EXECUTION_RONIN_PRIME_DESC","rui/titan_loadout/execution/titan_execution_ronin_prime",0,0,1,""
+"execution_ion","titan_atlas_stickybomb","mt_execution_attacker_laser","mt_execution_attacker_laser","lt_execution_victim_laser","mt_execution_victim_laser","ht_execution_victim_laser","pt_execution_victim_laser","pt_execution_victim_laser","pt_execution_victim_laser","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_ION_EXECUTION","#TITAN_EXECUTION_ION","#TITAN_EXECUTION_ION_DESC","rui/titan_loadout/execution/titan_execution_ion",0,0,0,""
+"execution_ion_prime","titan_atlas_ion_prime","mt_execution_attacker_ion_prime","mt_execution_attacker_ion_prime","lt_execution_victim_ion_prime","mt_execution_victim_ion_prime","ht_execution_victim_ion_prime","pt_execution_victim_ion_prime","pt_execution_victim_ion_prime","pt_execution_victim_ion_prime","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_ION_EXECUTION","#TITAN_EXECUTION_ION_PRIME","#TITAN_EXECUTION_ION_PRIME_DESC","rui/titan_loadout/execution/titan_execution_ion_prime",0,0,1,""
+"execution_bt","titan_buddy","bt_synced_titan_execute_flip_takedown_A","bt_synced_titan_execute_flip_takedown_A","titan_synced_bt_execute_flip_takedown_V","titan_synced_bt_execute_flip_takedown_V","titan_synced_bt_execute_flip_takedown_V","","","","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","","","","",1,0,0,""
+"execution_tone","titan_atlas_tracker","mt_execution_attacker_tone","mt_execution_attacker_tone_auto","lt_execution_victim_tone","mt_execution_victim_tone","ht_execution_victim_tone","pt_execution_victim_tone","pt_execution_victim_tone","pt_execution_victim_tone","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_TONE_EXECUTION","#TITAN_EXECUTION_TONE","#TITAN_EXECUTION_TONE_DESC","rui/titan_loadout/execution/titan_execution_tone",0,0,0,""
+"execution_tone_prime","titan_atlas_tone_prime","mt_execution_attacker_tone_prime","mt_execution_attacker_tone_prime","lt_execution_victim_tone_prime","mt_execution_victim_tone_prime","ht_execution_victim_tone_prime","","","","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_TONE_EXECUTION","#TITAN_EXECUTION_TONE_PRIME","#TITAN_EXECUTION_TONE_PRIME_DESC","rui/titan_loadout/execution/titan_execution_tone_prime",0,0,1,""
+"execution_northstar","titan_stryder_sniper","lt_execution_attacker_sweep_01","lt_execution_attacker_sweep_01","lt_execution_victim_sweep_01","mt_execution_victim_sweep_01","ht_execution_victim_sweep_01","","","","","","","Stryder_1p_Sync_Melee","Stryder_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_NORTHSTAR_EXECUTION","#TITAN_EXECUTION_NORTHSTAR","#TITAN_EXECUTION_NORTHSTAR_DESC","rui/titan_loadout/execution/titan_execution_northstar",0,0,0,""
+"execution_northstar_prime","titan_stryder_northstar_prime","lt_execution_attacker_armsrip","lt_execution_attacker_armsrip","lt_execution_victim_armsrip","mt_execution_victim_armsrip","ht_execution_victim_armsrip","","","","","","","Stryder_1p_Sync_Melee","Stryder_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_NORTHSTAR_EXECUTION","#TITAN_EXECUTION_NORTHSTAR_PRIME","#TITAN_EXECUTION_NORTHSTAR_PRIME_DESC","rui/titan_loadout/execution/titan_execution_northstar_prime",0,0,1,""
+"execution_vanguard","titan_atlas_vanguard","mt_execution_attacker_vanguard","mt_execution_autoTitan_attacker_vanguard","lt_execution_victim_vanguard","mt_execution_victim_vanguard","ht_execution_victim_vanguard","pt_lt_execution_victim_vanguard","pt_mt_execution_victim_vanguard","pt_ht_execution_victim_vanguard","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","TITAN_VANGUARD_EXECUTION","#TITAN_EXECUTION_VANGUARD","#TITAN_EXECUTION_VANGUARD_DESC","rui/titan_loadout/execution/titan_execution_monarch",0,0,0,""
+"execution_vanguard_kit","titan_atlas_vanguard_kit","mt_execution_battery_attacker_vanguard","mt_execution_battery_attacker_vanguard","lt_execution_battery_victim_vanguard","mt_execution_battery_victim_vanguard","ht_execution_battery_victim_vanguard","pt_lt_execution_battery_victim_vanguard","pt_mt_execution_battery_victim_vanguard","pt_ht_execution_battery_victim_vanguard","pt_lt_execution_battery_attacker_vanguard","pt_mt_execution_battery_attacker_vanguard","pt_ht_execution_battery_attacker_vanguard","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","","","","",1,0,0,""
+"execution_random_0","","","","","","","","","","","","","","","","TITAN_ION_EXECUTION","#TITAN_EXECUTION_RANDOM","#TITAN_EXECUTION_RANDOM_DESC","rui/pilot_loadout/execution/execution_random",0,0,0,"execution_ion,execution_ion_prime"
+"execution_random_1","","","","","","","","","","","","","","","","TITAN_SCORCH_EXECUTION","#TITAN_EXECUTION_RANDOM","#TITAN_EXECUTION_RANDOM_DESC","rui/pilot_loadout/execution/execution_random",0,0,0,"execution_scorch,execution_scorch_prime"
+"execution_random_2","","","","","","","","","","","","","","","","TITAN_NORTHSTAR_EXECUTION","#TITAN_EXECUTION_RANDOM","#TITAN_EXECUTION_RANDOM_DESC","rui/pilot_loadout/execution/execution_random",0,0,0,"execution_northstar,execution_northstar_prime"
+"execution_random_3","","","","","","","","","","","","","","","","TITAN_RONIN_EXECUTION","#TITAN_EXECUTION_RANDOM","#TITAN_EXECUTION_RANDOM_DESC","rui/pilot_loadout/execution/execution_random",0,0,0,"execution_ronin,execution_ronin_prime"
+"execution_random_4","","","","","","","","","","","","","","","","TITAN_TONE_EXECUTION","#TITAN_EXECUTION_RANDOM","#TITAN_EXECUTION_RANDOM_DESC","rui/pilot_loadout/execution/execution_random",0,0,0,"execution_tone,execution_tone_prime"
+"execution_random_5","","","","","","","","","","","","","","","","TITAN_LEGION_EXECUTION","#TITAN_EXECUTION_RANDOM","#TITAN_EXECUTION_RANDOM_DESC","rui/pilot_loadout/execution/execution_random",0,0,0,"execution_legion,execution_legion_prime"
+"execution_random_6","","","","","","","","","","","","","","","","TITAN_VANGUARD_EXECUTION","#TITAN_EXECUTION_RANDOM","#TITAN_EXECUTION_RANDOM_DESC","rui/pilot_loadout/execution/execution_random",0,0,0,"execution_vanguard"
+"execution_bt_flip","titan_buddy","bt_synced_titan_execute_flip_takedown_A","bt_synced_titan_execute_flip_takedown_A","titan_synced_bt_execute_flip_takedown_V","titan_synced_bt_execute_flip_takedown_V","titan_synced_bt_execute_flip_takedown_V","","","","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","","","","",1,0,0,""
+"execution_bt_kickshoot","titan_buddy","bt_synced_titan_execute_kickshoot_A","bt_synced_titan_execute_kickshoot_A","titan_synced_bt_execute_kickshoot_V","titan_synced_bt_execute_kickshoot_V","titan_synced_bt_execute_kickshoot_V","pt_lt_synced_bt_execute_kickshoot_V","pt_mt_synced_bt_execute_kickshoot_V","pt_ht_synced_bt_execute_kickshoot_V","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","","","","",1,0,0,""
+"execution_bt_pilotrip","titan_buddy","bt_synced_titan_execute_pilot_rip_A","bt_synced_titan_execute_pilot_rip_A","titan_synced_bt_execute_pilot_rip_V","titan_synced_bt_execute_pilot_rip_V","titan_synced_bt_execute_pilot_rip_V","pt_lt_synced_bt_execute_pilot_rip_V","pt_mt_synced_bt_execute_pilot_rip_V","pt_ht_synced_bt_execute_pilot_rip_V","","","","Atlas_1p_Sync_Melee","Atlas_3p_Sync_Melee","vehicle_driver_eyes camera_2 camera_3","","","","",1,0,0,"" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_fd_upgrades.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_fd_upgrades.csv
new file mode 100644
index 00000000..aab61afb
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_fd_upgrades.csv
@@ -0,0 +1,50 @@
+ref,parentref,upgradeType,name,description,image,lockedImage,cost,slot,unlockLevel
+"fd_upgrade_tone_utility_tier_1","tone","utility","#FD_UPGRADE_TONE_UTILITY_TIER_1","#FD_UPGRADE_TONE_UTILITY_TIER_1_DESC","rui/menu/fd_menu/upgrade_tone_signal_strength","rui/menu/fd_menu/disabled/upgrade_tone_signal_strength",1,2,12
+"fd_upgrade_tone_utility_tier_2","tone","utility","#FD_UPGRADE_TONE_UTILITY_TIER_2","#FD_UPGRADE_TONE_UTILITY_TIER_2_DESC","rui/menu/fd_menu/upgrade_tone_weak_points","rui/menu/fd_menu/disabled/upgrade_tone_weak_points",2,3,6
+"fd_upgrade_tone_defense_tier_1","tone","defensive","#FD_UPGRADE_TONE_DEFENSE_TIER_1","#FD_UPGRADE_TONE_DEFENSE_TIER_1_DESC","rui/menu/fd_menu/upgrade_tone_chassis","rui/menu/fd_menu/disabled/upgrade_tone_chassis",1,0,4
+"fd_upgrade_tone_defense_tier_2","tone","defensive","#FD_UPGRADE_TONE_DEFENSE_TIER_2","#FD_UPGRADE_TONE_DEFENSE_TIER_2_DESC","rui/menu/fd_menu/upgrade_tone_shield","rui/menu/fd_menu/disabled/upgrade_tone_shield",2,1,10
+"fd_upgrade_tone_weapon_tier_1","tone","weapon","#FD_UPGRADE_TONE_WEAPON_TIER_1","#FD_UPGRADE_TONE_WEAPON_TIER_1_DESC","rui/menu/fd_menu/upgrade_tone_splasher_rounds","rui/menu/fd_menu/disabled/upgrade_tone_splasher_rounds",1,4,8
+"fd_upgrade_tone_weapon_tier_2","tone","weapon","#FD_UPGRADE_TONE_WEAPON_TIER_2","#FD_UPGRADE_TONE_WEAPON_TIER_2_DESC","rui/menu/fd_menu/upgrade_tone_extended_ammo","rui/menu/fd_menu/disabled/upgrade_tone_extended_ammo",2,5,2
+"fd_upgrade_tone_ultimate","tone","ultimate","#FD_UPGRADE_TONE_ULTIMATE","#FD_UPGRADE_TONE_ULTIMATE_DESC","rui/menu/fd_menu/upgrade_tone_salvo_core","rui/menu/fd_menu/disabled/upgrade_tone_salvo_core",3,6,14
+"fd_upgrade_ion_utility_tier_1","ion","utility","#FD_UPGRADE_ION_UTILITY_TIER_1","#FD_UPGRADE_ION_UTILITY_TIER_1_DESC","rui/menu/fd_menu/upgrade_ion_energy_master","rui/menu/fd_menu/disabled/upgrade_ion_energy_master",1,2,12
+"fd_upgrade_ion_utility_tier_2","ion","utility","#FD_UPGRADE_ION_UTILITY_TIER_2","#FD_UPGRADE_ION_UTILITY_TIER_2_DESC","rui/menu/fd_menu/upgrade_ion_energy_storage","rui/menu/fd_menu/disabled/upgrade_ion_energy_storage",2,3,6
+"fd_upgrade_ion_defense_tier_1","ion","defensive","#FD_UPGRADE_ION_DEFENSE_TIER_1","#FD_UPGRADE_ION_DEFENSE_TIER_1_DESC","rui/menu/fd_menu/upgrade_ion_chassis","rui/menu/fd_menu/disabled/upgrade_ion_chassis",1,0,4
+"fd_upgrade_ion_defense_tier_2","ion","defensive","#FD_UPGRADE_ION_DEFENSE_TIER_2","#FD_UPGRADE_ION_DEFENSE_TIER_2_DESC","rui/menu/fd_menu/upgrade_ion_shield","rui/menu/fd_menu/disabled/upgrade_ion_shield",2,1,10
+"fd_upgrade_ion_weapon_tier_1","ion","weapon","#FD_UPGRADE_ION_WEAPON_TIER_1","#FD_UPGRADE_ION_WEAPON_TIER_1_DESC","rui/menu/fd_menu/upgrade_ion_split_shot","rui/menu/fd_menu/disabled/upgrade_ion_split_shot",1,4,2
+"fd_upgrade_ion_weapon_tier_2","ion","weapon","#FD_UPGRADE_ION_WEAPON_TIER_2","#FD_UPGRADE_ION_WEAPON_TIER_2_DESC","rui/menu/fd_menu/upgrade_ion_splitter_damage","rui/menu/fd_menu/disabled/upgrade_ion_splitter_damage",2,5,8
+"fd_upgrade_ion_ultimate","ion","ultimate","#FD_UPGRADE_ION_ULTIMATE","#FD_UPGRADE_ION_ULTIMATE_DESC","rui/menu/fd_menu/upgrade_ion_laser_reset","rui/menu/fd_menu/disabled/upgrade_ion_laser_reset",3,6,14
+"fd_upgrade_vanguard_utility_tier_1","vanguard","utility","#FD_UPGRADE_VANGUARD_UTILITY_TIER_1","#FD_UPGRADE_VANGUARD_UTILITY_TIER_1_DESC","rui/menu/fd_menu/upgrade_monarch_energize_smoke","rui/menu/fd_menu/disabled/upgrade_monarch_energize_smoke",1,2,2
+"fd_upgrade_vanguard_utility_tier_2","vanguard","utility","#FD_UPGRADE_VANGUARD_UTILITY_TIER_2","#FD_UPGRADE_VANGUARD_UTILITY_TIER_2_DESC","rui/menu/fd_menu/upgrade_monarch_energize_smoke_2","rui/menu/fd_menu/disabled/upgrade_monarch_energize_smoke_2",2,3,8
+"fd_upgrade_vanguard_defense_tier_1","vanguard","defensive","#FD_UPGRADE_VANGUARD_DEFENSE_TIER_1","#FD_UPGRADE_VANGUARD_DEFENSE_TIER_1_DESC","rui/menu/fd_menu/upgrade_monarch_chassis","rui/menu/fd_menu/disabled/upgrade_monarch_chassis",1,0,4
+"fd_upgrade_vanguard_defense_tier_2","vanguard","defensive","#FD_UPGRADE_VANGUARD_DEFENSE_TIER_2","#FD_UPGRADE_VANGUARD_DEFENSE_TIER_2_DESC","rui/menu/fd_menu/upgrade_monarch_shield","rui/menu/fd_menu/disabled/upgrade_monarch_shield",2,1,10
+"fd_upgrade_vanguard_weapon_tier_1","vanguard","weapon","#FD_UPGRADE_VANGUARD_WEAPON_TIER_1","#FD_UPGRADE_VANGUARD_WEAPON_TIER_1_DESC","rui/menu/fd_menu/upgrade_monarch_threat_optics","rui/menu/fd_menu/disabled/upgrade_monarch_threat_optics",1,4,6
+"fd_upgrade_vanguard_weapon_tier_2","vanguard","weapon","#FD_UPGRADE_VANGUARD_WEAPON_TIER_2","#FD_UPGRADE_VANGUARD_WEAPON_TIER_2_DESC","rui/menu/fd_menu/upgrade_monarch_xo16_sniper","rui/menu/fd_menu/disabled/upgrade_monarch_xo16_sniper",2,5,12
+"fd_upgrade_vanguard_ultimate","vanguard","ultimate","#FD_UPGRADE_VANGUARD_ULTIMATE","#FD_UPGRADE_VANGUARD_ULTIMATE_DESC","rui/menu/fd_menu/upgrade_monarch_apex_titan","rui/menu/fd_menu/disabled/upgrade_monarch_apex_titan",3,6,14
+"fd_upgrade_ronin_utility_tier_1","ronin","utility","#FD_UPGRADE_RONIN_UTILITY_TIER_1","#FD_UPGRADE_RONIN_UTILITY_TIER_1_DESC","rui/menu/fd_menu/upgrade_ronin_ghost_machine","rui/menu/fd_menu/disabled/upgrade_ronin_ghost_machine",1,2,6
+"fd_upgrade_ronin_utility_tier_2","ronin","utility","#FD_UPGRADE_RONIN_UTILITY_TIER_2","#FD_UPGRADE_RONIN_UTILITY_TIER_2_DESC","rui/menu/fd_menu/upgrade_ronin_wraith","rui/menu/fd_menu/disabled/upgrade_ronin_wraith",2,3,12
+"fd_upgrade_ronin_defense_tier_1","ronin","defensive","#FD_UPGRADE_RONIN_DEFENSE_TIER_1","#FD_UPGRADE_RONIN_DEFENSE_TIER_1_DESC","rui/menu/fd_menu/upgrade_ronin_chassis","rui/menu/fd_menu/disabled/upgrade_ronin_chassis",1,0,4
+"fd_upgrade_ronin_defense_tier_2","ronin","defensive","#FD_UPGRADE_RONIN_DEFENSE_TIER_2","#FD_UPGRADE_RONIN_DEFENSE_TIER_2_DESC","rui/menu/fd_menu/upgrade_ronin_shield","rui/menu/fd_menu/disabled/upgrade_ronin_shield",2,1,10
+"fd_upgrade_ronin_weapon_tier_1","ronin","weapon","#FD_UPGRADE_RONIN_WEAPON_TIER_1","#FD_UPGRADE_RONIN_WEAPON_TIER_1_DESC","rui/menu/fd_menu/upgrade_ronin_sword_mastery","rui/menu/fd_menu/disabled/upgrade_ronin_sword_mastery",1,4,2
+"fd_upgrade_ronin_weapon_tier_2","ronin","weapon","#FD_UPGRADE_RONIN_WEAPON_TIER_2","#FD_UPGRADE_RONIN_WEAPON_TIER_2_DESC","rui/menu/fd_menu/upgrade_ronin_kinetic_transfer","rui/menu/fd_menu/disabled/upgrade_ronin_kinetic_transfer",2,5,8
+"fd_upgrade_ronin_ultimate","ronin","ultimate","#FD_UPGRADE_RONIN_ULTIMATE","#FD_UPGRADE_RONIN_ULTIMATE_DESC","rui/menu/fd_menu/upgrade_ronin_blade_master","rui/menu/fd_menu/disabled/upgrade_ronin_blade_master",3,6,14
+"fd_upgrade_northstar_utility_tier_1","northstar","utility","#FD_UPGRADE_NORTHSTAR_UTILITY_TIER_1","#FD_UPGRADE_NORTHSTAR_UTILITY_TIER_1_DESC","rui/menu/fd_menu/upgrade_northstar_explosive_trap","rui/menu/fd_menu/disabled/upgrade_northstar_explosive_trap",1,2,2
+"fd_upgrade_northstar_utility_tier_2","northstar","utility","#FD_UPGRADE_NORTHSTAR_UTILITY_TIER_2","#FD_UPGRADE_NORTHSTAR_UTILITY_TIER_2_DESC","rui/menu/fd_menu/upgrade_northstar_trap_master","rui/menu/fd_menu/disabled/upgrade_northstar_trap_master",2,3,8
+"fd_upgrade_northstar_defense_tier_1","northstar","defensive","#FD_UPGRADE_NORTHSTAR_DEFENSE_TIER_1","#FD_UPGRADE_NORTHSTAR_DEFENSE_TIER_1_DESC","rui/menu/fd_menu/upgrade_northstar_chassis","rui/menu/fd_menu/disabled/upgrade_northstar_chassis",1,0,4
+"fd_upgrade_northstar_defense_tier_2","northstar","defensive","#FD_UPGRADE_NORTHSTAR_DEFENSE_TIER_2","#FD_UPGRADE_NORTHSTAR_DEFENSE_TIER_2_DESC","rui/menu/fd_menu/upgrade_northstar_shield","rui/menu/fd_menu/disabled/upgrade_northstar_shield",2,1,10
+"fd_upgrade_northstar_weapon_tier_1","northstar","weapon","#FD_UPGRADE_NORTHSTAR_WEAPON_TIER_1","#FD_UPGRADE_NORTHSTAR_WEAPON_TIER_1_DESC","rui/menu/fd_menu/upgrade_northstar_quick_charge","rui/menu/fd_menu/disabled/upgrade_northstar_quick_charge",1,4,6
+"fd_upgrade_northstar_weapon_tier_2","northstar","weapon","#FD_UPGRADE_NORTHSTAR_WEAPON_TIER_2","#FD_UPGRADE_NORTHSTAR_WEAPON_TIER_2_DESC","rui/menu/fd_menu/upgrade_northstar_one_shot_kill","rui/menu/fd_menu/disabled/upgrade_northstar_one_shot_kill",2,5,12
+"fd_upgrade_northstar_ultimate","northstar","ultimate","#FD_UPGRADE_NORTHSTAR_ULTIMATE","#FD_UPGRADE_NORTHSTAR_ULTIMATE_DESC","rui/menu/fd_menu/upgrade_northstar_twin_cluster","rui/menu/fd_menu/disabled/upgrade_northstar_twin_cluster",3,6,14
+"fd_upgrade_scorch_utility_tier_1","scorch","utility","#FD_UPGRADE_SCORCH_UTILITY_TIER_1","#FD_UPGRADE_SCORCH_UTILITY_TIER_1_DESC","rui/menu/fd_menu/upgrade_scorch_hot_streak","rui/menu/fd_menu/disabled/upgrade_scorch_hot_streak",1,2,6
+"fd_upgrade_scorch_utility_tier_2","scorch","utility","#FD_UPGRADE_SCORCH_UTILITY_TIER_2","#FD_UPGRADE_SCORCH_UTILITY_TIER_2_DESC","rui/menu/fd_menu/upgrade_scorch_roaring_flame","rui/menu/fd_menu/disabled/upgrade_scorch_roaring_flame",2,3,8
+"fd_upgrade_scorch_defense_tier_1","scorch","defensive","#FD_UPGRADE_SCORCH_DEFENSE_TIER_1","#FD_UPGRADE_SCORCH_DEFENSE_TIER_1_DESC","rui/menu/fd_menu/upgrade_scorch_chassis","rui/menu/fd_menu/disabled/upgrade_scorch_chassis",1,0,4
+"fd_upgrade_scorch_defense_tier_2","scorch","defensive","#FD_UPGRADE_SCORCH_DEFENSE_TIER_2","#FD_UPGRADE_SCORCH_DEFENSE_TIER_2_DESC","rui/menu/fd_menu/upgrade_scorch_shield","rui/menu/fd_menu/disabled/upgrade_scorch_shield",2,1,10
+"fd_upgrade_scorch_weapon_tier_1","scorch","weapon","#FD_UPGRADE_SCORCH_WEAPON_TIER_1","#FD_UPGRADE_SCORCH_WEAPON_TIER_1_DESC","rui/menu/fd_menu/upgrade_scorch_double_threat","rui/menu/fd_menu/disabled/upgrade_scorch_double_threat",1,4,2
+"fd_upgrade_scorch_weapon_tier_2","scorch","weapon","#FD_UPGRADE_SCORCH_WEAPON_TIER_2","#FD_UPGRADE_SCORCH_WEAPON_TIER_2_DESC","rui/menu/fd_menu/upgrade_scorch_triple_threat","rui/menu/fd_menu/disabled/upgrade_scorch_triple_threat",2,5,12
+"fd_upgrade_scorch_ultimate","scorch","ultimate","#FD_UPGRADE_SCORCH_ULTIMATE","#FD_UPGRADE_SCORCH_ULTIMATE_DESC","rui/menu/fd_menu/upgrade_scorch_explosive_barrells","rui/menu/fd_menu/disabled/upgrade_scorch_explosive_barrells",3,6,14
+"fd_upgrade_legion_utility_tier_1","legion","utility","#FD_UPGRADE_LEGION_UTILITY_TIER_1","#FD_UPGRADE_LEGION_UTILITY_TIER_1_DESC","rui/menu/fd_menu/upgrade_legion_executioner","rui/menu/fd_menu/disabled/upgrade_legion_executioner",1,2,6
+"fd_upgrade_legion_utility_tier_2","legion","utility","#FD_UPGRADE_LEGION_UTILITY_TIER_2","#FD_UPGRADE_LEGION_UTILITY_TIER_2_DESC","rui/menu/fd_menu/upgrade_legion_drill_shot","rui/menu/fd_menu/disabled/upgrade_legion_drill_shot",2,3,12
+"fd_upgrade_legion_defense_tier_1","legion","defensive","#FD_UPGRADE_LEGION_DEFENSE_TIER_1","#FD_UPGRADE_LEGION_DEFENSE_TIER_1_DESC","rui/menu/fd_menu/upgrade_legion_chassis","rui/menu/fd_menu/disabled/upgrade_legion_chassis",1,0,4
+"fd_upgrade_legion_defense_tier_2","legion","defensive","#FD_UPGRADE_LEGION_DEFENSE_TIER_2","#FD_UPGRADE_LEGION_DEFENSE_TIER_2_DESC","rui/menu/fd_menu/upgrade_legion_shield","rui/menu/fd_menu/disabled/upgrade_legion_shield",2,1,10
+"fd_upgrade_legion_weapon_tier_1","legion","weapon","#FD_UPGRADE_LEGION_WEAPON_TIER_1","#FD_UPGRADE_LEGION_WEAPON_TIER_1_DESC","rui/menu/fd_menu/upgrade_legion_piercing_shot","rui/menu/fd_menu/disabled/upgrade_legion_piercing_shot",1,4,2
+"fd_upgrade_legion_weapon_tier_2","legion","weapon","#FD_UPGRADE_LEGION_WEAPON_TIER_2","#FD_UPGRADE_LEGION_WEAPON_TIER_2_DESC","rui/menu/fd_menu/upgrade_legion_redirect","rui/menu/fd_menu/disabled/upgrade_legion_redirect",2,5,8
+"fd_upgrade_legion_ultimate","legion","ultimate","#FD_UPGRADE_LEGION_ULTIMATE","#FD_UPGRADE_LEGION_ULTIMATE_DESC","rui/menu/fd_menu/upgrade_legion_double_down","rui/menu/fd_menu/disabled/upgrade_legion_double_down",3,6,14 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_nose_art.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_nose_art.csv
new file mode 100644
index 00000000..56bcf556
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_nose_art.csv
@@ -0,0 +1,191 @@
+ref,titanRef,image,name,cost
+"ion_nose_art_none","ion","rui/menu/common/no_art","#FACTORY_ISSUE_NAME",0
+"ion_nose_art_01","ion","rui/titan_loadout/nose_art/ion_nose_art_01","#ION_NOSE_ART_01",0
+"ion_nose_art_02","ion","rui/titan_loadout/nose_art/ion_nose_art_02","#ION_NOSE_ART_02",0
+"ion_nose_art_03","ion","rui/titan_loadout/nose_art/ion_nose_art_03","#ION_NOSE_ART_03",0
+"ion_nose_art_04","ion","rui/titan_loadout/nose_art/ion_nose_art_04","#ION_NOSE_ART_04",0
+"ion_nose_art_05","ion","rui/titan_loadout/nose_art/ion_nose_art_05","#ION_NOSE_ART_05",300
+"ion_nose_art_06","ion","rui/titan_loadout/nose_art/ion_nose_art_06","#ION_NOSE_ART_06",0
+"ion_nose_art_07","ion","rui/titan_loadout/nose_art/ion_nose_art_07","#ION_NOSE_ART_07",0
+"ion_nose_art_08","ion","rui/titan_loadout/nose_art/ion_nose_art_08","#ION_NOSE_ART_08",0
+"ion_nose_art_09","ion","rui/titan_loadout/nose_art/ion_nose_art_09","#ION_NOSE_ART_09",0
+"ion_nose_art_10","ion","rui/titan_loadout/nose_art/ion_nose_art_10","#ION_NOSE_ART_10",0
+"ion_nose_art_11","ion","rui/titan_loadout/nose_art/ion_nose_art_11","#ION_NOSE_ART_11",0
+"ion_nose_art_12","ion","rui/titan_loadout/nose_art/ion_nose_art_12","#ION_NOSE_ART_12",0
+"ion_nose_art_13","ion","rui/titan_loadout/nose_art/ion_nose_art_13","#ION_NOSE_ART_13",0
+"ion_nose_art_14","ion","rui/titan_loadout/nose_art/ion_nose_art_14","#ION_NOSE_ART_14",0
+"scorch_nose_art_none","scorch","rui/menu/common/no_art","#FACTORY_ISSUE_NAME",0
+"scorch_nose_art_01","scorch","rui/titan_loadout/nose_art/scorch_nose_art_01","#SCORCH_NOSE_ART_01",0
+"scorch_nose_art_02","scorch","rui/titan_loadout/nose_art/scorch_nose_art_02","#SCORCH_NOSE_ART_02",0
+"scorch_nose_art_03","scorch","rui/titan_loadout/nose_art/scorch_nose_art_03","#SCORCH_NOSE_ART_03",0
+"scorch_nose_art_04","scorch","rui/titan_loadout/nose_art/scorch_nose_art_04","#SCORCH_NOSE_ART_04",0
+"scorch_nose_art_05","scorch","rui/titan_loadout/nose_art/scorch_nose_art_05","#SCORCH_NOSE_ART_05",0
+"scorch_nose_art_06","scorch","rui/titan_loadout/nose_art/scorch_nose_art_06","#SCORCH_NOSE_ART_06",0
+"scorch_nose_art_07","scorch","rui/titan_loadout/nose_art/scorch_nose_art_07","#SCORCH_NOSE_ART_07",0
+"scorch_nose_art_08","scorch","rui/titan_loadout/nose_art/scorch_nose_art_08","#SCORCH_NOSE_ART_08",0
+"scorch_nose_art_09","scorch","rui/titan_loadout/nose_art/scorch_nose_art_09","#SCORCH_NOSE_ART_09",0
+"scorch_nose_art_10","scorch","rui/titan_loadout/nose_art/scorch_nose_art_10","#SCORCH_NOSE_ART_10",0
+"scorch_nose_art_11","scorch","rui/titan_loadout/nose_art/scorch_nose_art_11","#SCORCH_NOSE_ART_11",0
+"scorch_nose_art_12","scorch","rui/titan_loadout/nose_art/scorch_nose_art_12","#SCORCH_NOSE_ART_12",300
+"scorch_nose_art_13","scorch","rui/titan_loadout/nose_art/scorch_nose_art_13","#SCORCH_NOSE_ART_13",0
+"scorch_nose_art_14","scorch","rui/titan_loadout/nose_art/scorch_nose_art_14","#SCORCH_NOSE_ART_14",0
+"ronin_nose_art_none","ronin","rui/menu/common/no_art","#FACTORY_ISSUE_NAME",0
+"ronin_nose_art_01","ronin","rui/titan_loadout/nose_art/ronin_nose_art_01","#RONIN_NOSE_ART_01",0
+"ronin_nose_art_02","ronin","rui/titan_loadout/nose_art/ronin_nose_art_02","#RONIN_NOSE_ART_02",0
+"ronin_nose_art_03","ronin","rui/titan_loadout/nose_art/ronin_nose_art_03","#RONIN_NOSE_ART_03",0
+"ronin_nose_art_04","ronin","rui/titan_loadout/nose_art/ronin_nose_art_04","#RONIN_NOSE_ART_04",0
+"ronin_nose_art_05","ronin","rui/titan_loadout/nose_art/ronin_nose_art_05","#RONIN_NOSE_ART_05",0
+"ronin_nose_art_06","ronin","rui/titan_loadout/nose_art/ronin_nose_art_06","#RONIN_NOSE_ART_06",0
+"ronin_nose_art_07","ronin","rui/titan_loadout/nose_art/ronin_nose_art_07","#RONIN_NOSE_ART_07",0
+"ronin_nose_art_08","ronin","rui/titan_loadout/nose_art/ronin_nose_art_08","#RONIN_NOSE_ART_08",300
+"ronin_nose_art_09","ronin","rui/titan_loadout/nose_art/ronin_nose_art_09","#RONIN_NOSE_ART_09",0
+"ronin_nose_art_10","ronin","rui/titan_loadout/nose_art/ronin_nose_art_10","#RONIN_NOSE_ART_10",0
+"ronin_nose_art_11","ronin","rui/titan_loadout/nose_art/ronin_nose_art_11","#RONIN_NOSE_ART_11",0
+"ronin_nose_art_12","ronin","rui/titan_loadout/nose_art/ronin_nose_art_12","#RONIN_NOSE_ART_12",300
+"ronin_nose_art_13","ronin","rui/titan_loadout/nose_art/ronin_nose_art_13","#RONIN_NOSE_ART_13",0
+"ronin_nose_art_14","ronin","rui/titan_loadout/nose_art/ronin_nose_art_14","#RONIN_NOSE_ART_14",0
+"tone_nose_art_none","tone","rui/menu/common/no_art","#FACTORY_ISSUE_NAME",0
+"tone_nose_art_01","tone","rui/titan_loadout/nose_art/tone_nose_art_01","#TONE_NOSE_ART_01",0
+"tone_nose_art_02","tone","rui/titan_loadout/nose_art/tone_nose_art_02","#TONE_NOSE_ART_02",0
+"tone_nose_art_03","tone","rui/titan_loadout/nose_art/tone_nose_art_03","#TONE_NOSE_ART_03",0
+"tone_nose_art_04","tone","rui/titan_loadout/nose_art/tone_nose_art_04","#TONE_NOSE_ART_04",0
+"tone_nose_art_05","tone","rui/titan_loadout/nose_art/tone_nose_art_05","#TONE_NOSE_ART_05",0
+"tone_nose_art_06","tone","rui/titan_loadout/nose_art/tone_nose_art_06","#TONE_NOSE_ART_06",0
+"tone_nose_art_07","tone","rui/titan_loadout/nose_art/tone_nose_art_07","#TONE_NOSE_ART_07",0
+"tone_nose_art_08","tone","rui/titan_loadout/nose_art/tone_nose_art_08","#TONE_NOSE_ART_08",0
+"tone_nose_art_09","tone","rui/titan_loadout/nose_art/tone_nose_art_09","#TONE_NOSE_ART_09",0
+"tone_nose_art_10","tone","rui/titan_loadout/nose_art/tone_nose_art_10","#TONE_NOSE_ART_10",0
+"tone_nose_art_11","tone","rui/titan_loadout/nose_art/tone_nose_art_11","#TONE_NOSE_ART_11",300
+"tone_nose_art_12","tone","rui/titan_loadout/nose_art/tone_nose_art_12","#TONE_NOSE_ART_12",300
+"tone_nose_art_13","tone","rui/titan_loadout/nose_art/tone_nose_art_13","#TONE_NOSE_ART_13",0
+"tone_nose_art_14","tone","rui/titan_loadout/nose_art/tone_nose_art_14","#TONE_NOSE_ART_14",0
+"northstar_nose_art_none","northstar","rui/menu/common/no_art","#FACTORY_ISSUE_NAME",0
+"northstar_nose_art_01","northstar","rui/titan_loadout/nose_art/northstar_nose_art_01","#NORTHSTAR_NOSE_ART_01",0
+"northstar_nose_art_02","northstar","rui/titan_loadout/nose_art/northstar_nose_art_02","#NORTHSTAR_NOSE_ART_02",0
+"northstar_nose_art_03","northstar","rui/titan_loadout/nose_art/northstar_nose_art_03","#NORTHSTAR_NOSE_ART_03",0
+"northstar_nose_art_04","northstar","rui/titan_loadout/nose_art/northstar_nose_art_04","#NORTHSTAR_NOSE_ART_04",0
+"northstar_nose_art_05","northstar","rui/titan_loadout/nose_art/northstar_nose_art_05","#NORTHSTAR_NOSE_ART_05",0
+"northstar_nose_art_06","northstar","rui/titan_loadout/nose_art/northstar_nose_art_06","#NORTHSTAR_NOSE_ART_06",0
+"northstar_nose_art_07","northstar","rui/titan_loadout/nose_art/northstar_nose_art_07","#NORTHSTAR_NOSE_ART_07",0
+"northstar_nose_art_08","northstar","rui/titan_loadout/nose_art/northstar_nose_art_08","#NORTHSTAR_NOSE_ART_08",0
+"northstar_nose_art_09","northstar","rui/titan_loadout/nose_art/northstar_nose_art_09","#NORTHSTAR_NOSE_ART_09",0
+"northstar_nose_art_10","northstar","rui/titan_loadout/nose_art/northstar_nose_art_10","#NORTHSTAR_NOSE_ART_10",0
+"northstar_nose_art_11","northstar","rui/titan_loadout/nose_art/northstar_nose_art_11","#NORTHSTAR_NOSE_ART_11",0
+"northstar_nose_art_12","northstar","rui/titan_loadout/nose_art/northstar_nose_art_12","#NORTHSTAR_NOSE_ART_12",300
+"northstar_nose_art_13","northstar","rui/titan_loadout/nose_art/northstar_nose_art_13","#NORTHSTAR_NOSE_ART_13",300
+"northstar_nose_art_14","northstar","rui/titan_loadout/nose_art/northstar_nose_art_14","#NORTHSTAR_NOSE_ART_14",0
+"legion_nose_art_none","legion","rui/menu/common/no_art","#FACTORY_ISSUE_NAME",0
+"legion_nose_art_01","legion","rui/titan_loadout/nose_art/legion_nose_art_01","#LEGION_NOSE_ART_01",0
+"legion_nose_art_02","legion","rui/titan_loadout/nose_art/legion_nose_art_02","#LEGION_NOSE_ART_02",0
+"legion_nose_art_03","legion","rui/titan_loadout/nose_art/legion_nose_art_03","#LEGION_NOSE_ART_03",0
+"legion_nose_art_04","legion","rui/titan_loadout/nose_art/legion_nose_art_04","#LEGION_NOSE_ART_04",0
+"legion_nose_art_05","legion","rui/titan_loadout/nose_art/legion_nose_art_05","#LEGION_NOSE_ART_05",0
+"legion_nose_art_06","legion","rui/titan_loadout/nose_art/legion_nose_art_06","#LEGION_NOSE_ART_06",0
+"legion_nose_art_07","legion","rui/titan_loadout/nose_art/legion_nose_art_07","#LEGION_NOSE_ART_07",0
+"legion_nose_art_08","legion","rui/titan_loadout/nose_art/legion_nose_art_08","#LEGION_NOSE_ART_08",0
+"legion_nose_art_09","legion","rui/titan_loadout/nose_art/legion_nose_art_09","#LEGION_NOSE_ART_09",0
+"legion_nose_art_10","legion","rui/titan_loadout/nose_art/legion_nose_art_10","#LEGION_NOSE_ART_10",0
+"legion_nose_art_11","legion","rui/titan_loadout/nose_art/legion_nose_art_11","#LEGION_NOSE_ART_11",0
+"legion_nose_art_12","legion","rui/titan_loadout/nose_art/legion_nose_art_12","#LEGION_NOSE_ART_12",300
+"legion_nose_art_13","legion","rui/titan_loadout/nose_art/legion_nose_art_13","#LEGION_NOSE_ART_13",0
+"legion_nose_art_14","legion","rui/titan_loadout/nose_art/legion_nose_art_14","#LEGION_NOSE_ART_14",300
+"ion_nose_art_17","ion","rui/titan_loadout/nose_art/ion_nose_art_17","#ION_NOSE_ART_17",0
+"ion_nose_art_18","ion","rui/titan_loadout/nose_art/ion_nose_art_18","#ION_NOSE_ART_18",0
+"ion_nose_art_19","ion","rui/titan_loadout/nose_art/ion_nose_art_19","#ION_NOSE_ART_19",0
+"ion_nose_art_20","ion","rui/titan_loadout/nose_art/ion_nose_art_20","#ION_NOSE_ART_20",0
+"ion_nose_art_21","ion","rui/titan_loadout/nose_art/ion_nose_art_21","#ION_NOSE_ART_21",0
+"scorch_nose_art_15","scorch","rui/titan_loadout/nose_art/scorch_nose_art_15","#SCORCH_NOSE_ART_15",0
+"scorch_nose_art_16","scorch","rui/titan_loadout/nose_art/scorch_nose_art_16","#SCORCH_NOSE_ART_16",0
+"scorch_nose_art_17","scorch","rui/titan_loadout/nose_art/scorch_nose_art_17","#SCORCH_NOSE_ART_17",0
+"scorch_nose_art_18","scorch","rui/titan_loadout/nose_art/scorch_nose_art_18","#SCORCH_NOSE_ART_18",0
+"scorch_nose_art_19","scorch","rui/titan_loadout/nose_art/scorch_nose_art_19","#SCORCH_NOSE_ART_19",0
+"ronin_nose_art_16","ronin","rui/titan_loadout/nose_art/ronin_nose_art_16","#RONIN_NOSE_ART_16",0
+"ronin_nose_art_17","ronin","rui/titan_loadout/nose_art/ronin_nose_art_17","#RONIN_NOSE_ART_17",0
+"ronin_nose_art_18","ronin","rui/titan_loadout/nose_art/ronin_nose_art_18","#RONIN_NOSE_ART_18",0
+"ronin_nose_art_19","ronin","rui/titan_loadout/nose_art/ronin_nose_art_19","#RONIN_NOSE_ART_19",0
+"ronin_nose_art_20","ronin","rui/titan_loadout/nose_art/ronin_nose_art_20","#RONIN_NOSE_ART_20",0
+"tone_nose_art_17","tone","rui/titan_loadout/nose_art/tone_nose_art_17","#TONE_NOSE_ART_17",0
+"tone_nose_art_18","tone","rui/titan_loadout/nose_art/tone_nose_art_18","#TONE_NOSE_ART_18",0
+"tone_nose_art_19","tone","rui/titan_loadout/nose_art/tone_nose_art_19","#TONE_NOSE_ART_19",0
+"tone_nose_art_20","tone","rui/titan_loadout/nose_art/tone_nose_art_20","#TONE_NOSE_ART_20",0
+"tone_nose_art_21","tone","rui/titan_loadout/nose_art/tone_nose_art_21","#TONE_NOSE_ART_21",0
+"northstar_nose_art_18","northstar","rui/titan_loadout/nose_art/northstar_nose_art_18","#NORTHSTAR_NOSE_ART_18",0
+"northstar_nose_art_19","northstar","rui/titan_loadout/nose_art/northstar_nose_art_19","#NORTHSTAR_NOSE_ART_19",0
+"northstar_nose_art_20","northstar","rui/titan_loadout/nose_art/northstar_nose_art_20","#NORTHSTAR_NOSE_ART_20",0
+"northstar_nose_art_21","northstar","rui/titan_loadout/nose_art/northstar_nose_art_21","#NORTHSTAR_NOSE_ART_21",0
+"northstar_nose_art_22","northstar","rui/titan_loadout/nose_art/northstar_nose_art_22","#NORTHSTAR_NOSE_ART_22",0
+"legion_nose_art_17","legion","rui/titan_loadout/nose_art/legion_nose_art_17","#LEGION_NOSE_ART_17",0
+"legion_nose_art_18","legion","rui/titan_loadout/nose_art/legion_nose_art_18","#LEGION_NOSE_ART_18",0
+"legion_nose_art_19","legion","rui/titan_loadout/nose_art/legion_nose_art_19","#LEGION_NOSE_ART_19",0
+"legion_nose_art_20","legion","rui/titan_loadout/nose_art/legion_nose_art_20","#LEGION_NOSE_ART_20",0
+"legion_nose_art_21","legion","rui/titan_loadout/nose_art/legion_nose_art_21","#LEGION_NOSE_ART_21",0
+"ion_nose_art_22","ion","rui/titan_loadout/nose_art/ion_nose_art_22","#ION_NOSE_ART_22",0
+"ion_nose_art_23","ion","rui/titan_loadout/nose_art/ion_nose_art_23","#ION_NOSE_ART_23",0
+"ion_nose_art_24","ion","rui/titan_loadout/nose_art/ion_nose_art_24","#ION_NOSE_ART_24",0
+"ion_nose_art_25","ion","rui/titan_loadout/nose_art/ion_nose_art_25","#ION_NOSE_ART_25",0
+"ion_nose_art_26","ion","rui/titan_loadout/nose_art/ion_nose_art_26","#ION_NOSE_ART_26",0
+"scorch_nose_art_20","scorch","rui/titan_loadout/nose_art/scorch_nose_art_20","#SCORCH_NOSE_ART_20",0
+"scorch_nose_art_21","scorch","rui/titan_loadout/nose_art/scorch_nose_art_21","#SCORCH_NOSE_ART_21",0
+"scorch_nose_art_22","scorch","rui/titan_loadout/nose_art/scorch_nose_art_22","#SCORCH_NOSE_ART_22",0
+"scorch_nose_art_23","scorch","rui/titan_loadout/nose_art/scorch_nose_art_23","#SCORCH_NOSE_ART_23",0
+"scorch_nose_art_24","scorch","rui/titan_loadout/nose_art/scorch_nose_art_24","#SCORCH_NOSE_ART_24",0
+"ronin_nose_art_21","ronin","rui/titan_loadout/nose_art/ronin_nose_art_21","#RONIN_NOSE_ART_21",0
+"ronin_nose_art_22","ronin","rui/titan_loadout/nose_art/ronin_nose_art_22","#RONIN_NOSE_ART_22",0
+"ronin_nose_art_23","ronin","rui/titan_loadout/nose_art/ronin_nose_art_23","#RONIN_NOSE_ART_23",0
+"ronin_nose_art_24","ronin","rui/titan_loadout/nose_art/ronin_nose_art_24","#RONIN_NOSE_ART_24",0
+"ronin_nose_art_25","ronin","rui/titan_loadout/nose_art/ronin_nose_art_25","#RONIN_NOSE_ART_25",0
+"tone_nose_art_22","tone","rui/titan_loadout/nose_art/tone_nose_art_22","#TONE_NOSE_ART_22",0
+"tone_nose_art_23","tone","rui/titan_loadout/nose_art/tone_nose_art_23","#TONE_NOSE_ART_23",0
+"tone_nose_art_24","tone","rui/titan_loadout/nose_art/tone_nose_art_24","#TONE_NOSE_ART_24",0
+"tone_nose_art_25","tone","rui/titan_loadout/nose_art/tone_nose_art_25","#TONE_NOSE_ART_25",0
+"tone_nose_art_26","tone","rui/titan_loadout/nose_art/tone_nose_art_26","#TONE_NOSE_ART_26",0
+"northstar_nose_art_23","northstar","rui/titan_loadout/nose_art/northstar_nose_art_23","#NORTHSTAR_NOSE_ART_23",0
+"northstar_nose_art_24","northstar","rui/titan_loadout/nose_art/northstar_nose_art_24","#NORTHSTAR_NOSE_ART_24",0
+"northstar_nose_art_25","northstar","rui/titan_loadout/nose_art/northstar_nose_art_25","#NORTHSTAR_NOSE_ART_25",0
+"northstar_nose_art_26","northstar","rui/titan_loadout/nose_art/northstar_nose_art_26","#NORTHSTAR_NOSE_ART_26",0
+"northstar_nose_art_27","northstar","rui/titan_loadout/nose_art/northstar_nose_art_27","#NORTHSTAR_NOSE_ART_27",0
+"legion_nose_art_22","legion","rui/titan_loadout/nose_art/legion_nose_art_22","#LEGION_NOSE_ART_22",0
+"legion_nose_art_23","legion","rui/titan_loadout/nose_art/legion_nose_art_23","#LEGION_NOSE_ART_23",0
+"legion_nose_art_24","legion","rui/titan_loadout/nose_art/legion_nose_art_24","#LEGION_NOSE_ART_24",0
+"legion_nose_art_25","legion","rui/titan_loadout/nose_art/legion_nose_art_25","#LEGION_NOSE_ART_25",0
+"legion_nose_art_26","legion","rui/titan_loadout/nose_art/legion_nose_art_26","#LEGION_NOSE_ART_26",0
+"vanguard_nose_art_none","vanguard","rui/menu/common/no_art","#FACTORY_ISSUE_NAME",0
+"vanguard_nose_art_01","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_02","#VANGUARD_NOSE_ART_01",0
+"vanguard_nose_art_02","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_03","#VANGUARD_NOSE_ART_02",0
+"vanguard_nose_art_03","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_04","#VANGUARD_NOSE_ART_03",0
+"vanguard_nose_art_04","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_05","#VANGUARD_NOSE_ART_04",0
+"vanguard_nose_art_05","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_06","#VANGUARD_NOSE_ART_05",0
+"vanguard_nose_art_06","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_07","#VANGUARD_NOSE_ART_06",0
+"vanguard_nose_art_07","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_08","#VANGUARD_NOSE_ART_07",0
+"vanguard_nose_art_08","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_09","#VANGUARD_NOSE_ART_08",0
+"vanguard_nose_art_09","vanguard","rui/titan_loadout/nose_art/monarch_nose_art_10","#VANGUARD_NOSE_ART_09",0
+"ion_nose_art_27","ion","rui/titan_loadout/nose_art/ion_nose_art_27","#ION_NOSE_ART_27",0
+"ion_nose_art_28","ion","rui/titan_loadout/nose_art/ion_nose_art_28","#ION_NOSE_ART_28",0
+"ion_nose_art_29","ion","rui/titan_loadout/nose_art/ion_nose_art_29","#ION_NOSE_ART_29",0
+"ion_nose_art_30","ion","rui/titan_loadout/nose_art/ion_nose_art_30","#ION_NOSE_ART_30",0
+"ion_nose_art_31","ion","rui/titan_loadout/nose_art/ion_nose_art_31","#ION_NOSE_ART_31",0
+"scorch_nose_art_25","scorch","rui/titan_loadout/nose_art/scorch_nose_art_25","#SCORCH_NOSE_ART_25",0
+"scorch_nose_art_26","scorch","rui/titan_loadout/nose_art/scorch_nose_art_26","#SCORCH_NOSE_ART_26",0
+"scorch_nose_art_27","scorch","rui/titan_loadout/nose_art/scorch_nose_art_27","#SCORCH_NOSE_ART_27",0
+"scorch_nose_art_28","scorch","rui/titan_loadout/nose_art/scorch_nose_art_28","#SCORCH_NOSE_ART_28",0
+"scorch_nose_art_29","scorch","rui/titan_loadout/nose_art/scorch_nose_art_29","#SCORCH_NOSE_ART_29",0
+"ronin_nose_art_26","ronin","rui/titan_loadout/nose_art/ronin_nose_art_26","#RONIN_NOSE_ART_26",0
+"ronin_nose_art_27","ronin","rui/titan_loadout/nose_art/ronin_nose_art_27","#RONIN_NOSE_ART_27",0
+"ronin_nose_art_28","ronin","rui/titan_loadout/nose_art/ronin_nose_art_28","#RONIN_NOSE_ART_28",0
+"ronin_nose_art_29","ronin","rui/titan_loadout/nose_art/ronin_nose_art_29","#RONIN_NOSE_ART_29",0
+"ronin_nose_art_30","ronin","rui/titan_loadout/nose_art/ronin_nose_art_30","#RONIN_NOSE_ART_30",0
+"tone_nose_art_27","tone","rui/titan_loadout/nose_art/tone_nose_art_27","#TONE_NOSE_ART_27",0
+"tone_nose_art_28","tone","rui/titan_loadout/nose_art/tone_nose_art_28","#TONE_NOSE_ART_28",0
+"tone_nose_art_29","tone","rui/titan_loadout/nose_art/tone_nose_art_29","#TONE_NOSE_ART_29",0
+"tone_nose_art_30","tone","rui/titan_loadout/nose_art/tone_nose_art_30","#TONE_NOSE_ART_30",0
+"tone_nose_art_31","tone","rui/titan_loadout/nose_art/tone_nose_art_31","#TONE_NOSE_ART_31",0
+"northstar_nose_art_28","northstar","rui/titan_loadout/nose_art/northstar_nose_art_28","#NORTHSTAR_NOSE_ART_28",0
+"northstar_nose_art_29","northstar","rui/titan_loadout/nose_art/northstar_nose_art_29","#NORTHSTAR_NOSE_ART_29",0
+"northstar_nose_art_30","northstar","rui/titan_loadout/nose_art/northstar_nose_art_30","#NORTHSTAR_NOSE_ART_30",0
+"northstar_nose_art_31","northstar","rui/titan_loadout/nose_art/northstar_nose_art_31","#NORTHSTAR_NOSE_ART_31",0
+"northstar_nose_art_32","northstar","rui/titan_loadout/nose_art/northstar_nose_art_32","#NORTHSTAR_NOSE_ART_32",0
+"legion_nose_art_27","legion","rui/titan_loadout/nose_art/legion_nose_art_27","#LEGION_NOSE_ART_27",0
+"legion_nose_art_28","legion","rui/titan_loadout/nose_art/legion_nose_art_28","#LEGION_NOSE_ART_28",0
+"legion_nose_art_29","legion","rui/titan_loadout/nose_art/legion_nose_art_29","#LEGION_NOSE_ART_29",0
+"legion_nose_art_30","legion","rui/titan_loadout/nose_art/legion_nose_art_30","#LEGION_NOSE_ART_30",0
+"legion_nose_art_31","legion","rui/titan_loadout/nose_art/legion_nose_art_31","#LEGION_NOSE_ART_31",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_os_conversations.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_os_conversations.csv
new file mode 100644
index 00000000..6d7a8623
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_os_conversations.csv
@@ -0,0 +1,67 @@
+conversationname,priority,debounce
+"bettyAlarm",400,0.100000
+"warning",400,3.000000
+"caution",400,3.000000
+"multiTitanEngage",400,15.000000
+"outnumbered2to1",400,15.000000
+"outnumbered3to1",400,15.000000
+"outnumbered4to1",400,15.000000
+"embark",400,3.000000
+"disembark",400,3.000000
+"guard",2000,3.000000
+"follow",2000,3.000000
+"briefCriticalDamage",400,10.000000
+"manualEjectNotice",400,0.100000
+"autoEjectNotice",400,0.100000
+"doomState",400,0.100000
+"halfDoomState",400,0.100000
+"rodeoWarning",2000,3.000000
+"allyRodeoAttach",400,3.000000
+"allyRodeoDetach",400,3.000000
+"killEnemyRodeo",400,10.000000
+"warningEnemyPilot",400,10.000000
+"warningEnemyPilotMulti",400,10.000000
+"elimTarget",400,3.000000
+"elimEnemyPilot",400,3.000000
+"ejectedEnemy",400,3.000000
+"ejectedFriendly",400,3.000000
+"assistedByFriendlyTitan",400,10.000000
+"elimEnemyTitan",400,3.000000
+"elimFriendlyTitan",400,3.000000
+"assistedByFriendlyPilot",400,3.000000
+"hostileTitanInbound",2000,3.000000
+"friendlyRodeoOnEnemyTitan",400,3.000000
+"autoEngageGrunt",400,10.000000
+"autoEngagePilot",400,10.000000
+"autoEngageTitan",400,10.000000
+"autoEngageTitans",400,10.000000
+"killEnemyRodeoGnrc",400,10.000000
+"batteryGot",400,3.000000
+"predRangeLong",400,3.000000
+"predRangeShort",400,3.000000
+"smartCoreOnline",400,3.000000
+"smartCoreActivated",400,3.000000
+"smartCoreOffline",400,3.000000
+"flamewavecoreOnline",400,3.000000
+"flamewavecoreActivated",400,3.000000
+"hack_bt_workaround",400,3.000000
+"lasercoreOnline",400,3.000000
+"lasercoreActivated",400,3.000000
+"sonarPulse",400,3.000000
+"flightCoreOnline",400,3.000000
+"flightCoreActivated",400,3.000000
+"flightCoreOffline",400,3.000000
+"SalvocoreOnline",400,3.000000
+"SalvocoreActivated",400,3.000000
+"swordCoreOnline",400,3.000000
+"swordCoreActivated",400,3.000000
+"swordCoreOffline",400,3.000000
+"burstCoreOnline",400,3.000000
+"burstCoreActivated",400,3.000000
+"UpgradecoreOnline",400,3.000000
+"UpgradecoreActivated",400,3.000000
+"upgradeTo1",400,3.000000
+"upgradeTo2",400,3.000000
+"upgradeTo3",400,3.000000
+"upgradeToFin",400,3.000000
+"upgradeShieldReplenish",400,3.000000 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_passives.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_passives.csv
new file mode 100644
index 00000000..79f093c2
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_passives.csv
@@ -0,0 +1,52 @@
+passive,type,name,description,longdescription,image,hidden,cost
+"pas_build_up_nuclear_core","TITAN_GENERAL_PASSIVE","#GEAR_NUCLEAR_CORE","#GEAR_NUCLEAR_CORE_DESC","#GEAR_NUCLEAR_CORE_LONGDESC","rui/titan_loadout/passive/nuke_eject",0,5
+"pas_enhanced_titan_ai","TITAN_GENERAL_PASSIVE","#GEAR_ASSAULT_CHIP","#GEAR_ASSAULT_CHIP_DESC","#GEAR_ASSAULT_CHIP_LONGDESC","rui/titan_loadout/passive/assault_chip",0,25
+"pas_auto_eject","TITAN_GENERAL_PASSIVE","#GEAR_AUTO_EJECT","#GEAR_AUTO_EJECT_DESC","#GEAR_AUTO_EJECT_LONGDESC","rui/titan_loadout/passive/auto_eject",0,25
+"pas_hyper_core","TITAN_GENERAL_PASSIVE","#GEAR_CORE_HEADSTART","#GEAR_CORE_HEADSTART_DESC","#GEAR_CORE_HEADSTART_LONGDESC","rui/titan_loadout/passive/overcore",0,5
+"pas_anti_rodeo","TITAN_GENERAL_PASSIVE","#GEAR_ANTI_RODEO","#GEAR_ANTI_RODEO_DESC","#GEAR_ANTI_RODEO_LONGDESC","rui/titan_loadout/passive/improved_anti_rodeo",0,5
+"pas_mobility_dash_capacity","TITAN_GENERAL_PASSIVE","#GEAR_DASH_CAPACITY","#GEAR_DASH_CAPACITY_DESC","#GEAR_DASH_CAPACITY_LONGDESC","rui/titan_loadout/passive/dash_plus",0,5
+"pas_warpfall","TITAN_TITANFALL_PASSIVE","#GEAR_WARPFALL","#GEAR_WARPFALL_DESC","#GEAR_WARPFALL_LONGDESC","rui/titan_loadout/passive/titanfall_kit_warpfall",0,25
+"pas_bubbleshield","TITAN_TITANFALL_PASSIVE","#GEAR_BUBBLESHIELD","#GEAR_BUBBLESHIELD_DESC","#GEAR_BUBBLESHIELD_LONGDESC","rui/titan_loadout/passive/titanfall_kit_bubbleshield",0,14
+"pas_ronin_weapon","TITAN_RONIN_PASSIVE","#GEAR_RONIN_WEAPON","#GEAR_RONIN_WEAPON_DESC","#GEAR_RONIN_WEAPON_LONGDESC","rui/titan_loadout/passive/ronin_ricochet_round",0,25
+"pas_northstar_weapon","TITAN_NORTHSTAR_PASSIVE","#GEAR_NORTHSTAR_WEAPON","#GEAR_NORTHSTAR_WEAPON_DESC","#GEAR_NORTHSTAR_WEAPON_LONGDESC","rui/titan_loadout/passive/northstar_piercing_shot",0,25
+"pas_ion_weapon","TITAN_ION_PASSIVE","#GEAR_ION_WEAPON","#GEAR_ION_WEAPON_DESC","#GEAR_ION_WEAPON_LONGDESC","rui/titan_loadout/passive/ion_entangled_energy",0,25
+"pas_tone_weapon","TITAN_TONE_PASSIVE","#GEAR_TONE_WEAPON","#GEAR_TONE_WEAPON_DESC","#GEAR_TONE_WEAPON_LONGDESC","rui/titan_loadout/passive/tone_enhanced_tracker",0,25
+"pas_scorch_weapon","TITAN_SCORCH_PASSIVE","#GEAR_SCORCH_WEAPON","#GEAR_SCORCH_WEAPON_DESC","#GEAR_SCORCH_WEAPON_LONGDESC","rui/titan_loadout/passive/scorch_wildfire_launcher",0,25
+"pas_legion_weapon","TITAN_LEGION_PASSIVE","#GEAR_LEGION_WEAPON","#GEAR_LEGION_WEAPON_DESC","#GEAR_LEGION_WEAPON_LONGDESC","rui/titan_loadout/passive/legion_enhanced_ammo",0,25
+"pas_ion_tripwire","TITAN_ION_PASSIVE","#GEAR_ION_TRIPWIRE","#GEAR_ION_TRIPWIRE_DESC","#GEAR_ION_TRIPWIRE_LONGDESC","rui/titan_loadout/passive/ion_zero_point_tripwire",0,24
+"pas_ion_vortex","TITAN_ION_PASSIVE","#GEAR_ION_VORTEX","#GEAR_ION_VORTEX_DESC","#GEAR_ION_VORTEX_LONGDESC","rui/titan_loadout/passive/ion_vortex_amp",0,24
+"pas_ion_lasercannon","TITAN_ION_PASSIVE","#GEAR_ION_LASERCANNON","#GEAR_ION_LASERCANNON_DESC","#GEAR_ION_LASERCANNON_LONGDESC","rui/titan_loadout/passive/ion_grand_canon",0,24
+"pas_tone_rockets","TITAN_TONE_PASSIVE","#GEAR_TONE_ROCKETS","#GEAR_TONE_ROCKETS_DESC","#GEAR_TONE_ROCKETS_LONGDESC","rui/titan_loadout/passive/tone_rocket_barrage",0,24
+"pas_tone_sonar","TITAN_TONE_PASSIVE","#GEAR_TONE_SONAR","#GEAR_TONE_SONAR_DESC","#GEAR_TONE_SONAR_LONGDESC","rui/titan_loadout/passive/tone_pulse_echo",0,24
+"pas_tone_wall","TITAN_TONE_PASSIVE","#GEAR_TONE_WALL","#GEAR_TONE_WALL_DESC","#GEAR_TONE_WALL_LONGDESC","rui/titan_loadout/passive/tone_reinforced_partical_wall",0,24
+"pas_ronin_arcwave","TITAN_RONIN_PASSIVE","#GEAR_RONIN_ARCWAVE","#GEAR_RONIN_ARCWAVE_DESC","#GEAR_RONIN_ARCWAVE_LONGDESC","rui/titan_loadout/passive/ronin_thunderstorm",0,24
+"pas_ronin_phase","TITAN_RONIN_PASSIVE","#GEAR_RONIN_PHASE","#GEAR_RONIN_PHASE_DESC","#GEAR_RONIN_PHASE_LONGDESC","rui/titan_loadout/passive/ronin_temporal_anomaly",0,24
+"pas_ronin_swordcore","TITAN_RONIN_PASSIVE","#GEAR_RONIN_SWORDCORE","#GEAR_RONIN_SWORDCORE_DESC","#GEAR_RONIN_SWORDCORE_LONGDESC","rui/titan_loadout/passive/ronin_highlander",0,24
+"pas_northstar_cluster","TITAN_NORTHSTAR_PASSIVE","#GEAR_NORTHSTAR_CLUSTER","#GEAR_NORTHSTAR_CLUSTER_DESC","#GEAR_NORTHSTAR_CLUSTER_LONGDESC","rui/titan_loadout/passive/northstar_enhanced_payload",0,24
+"pas_northstar_trap","TITAN_NORTHSTAR_PASSIVE","#GEAR_NORTHSTAR_TRAP","#GEAR_NORTHSTAR_TRAP_DESC","#GEAR_NORTHSTAR_TRAP_LONGDESC","rui/titan_loadout/passive/northstar_twin_trap",0,24
+"pas_northstar_flightcore","TITAN_NORTHSTAR_PASSIVE","#GEAR_NORTHSTAR_FLIGHTCORE","#GEAR_NORTHSTAR_FLIGHTCORE_DESC","#GEAR_NORTHSTAR_FLIGHTCORE_LONGDESC","rui/titan_loadout/passive/northstar_viper_thrusters",0,24
+"pas_scorch_firewall","TITAN_SCORCH_PASSIVE","#GEAR_SCORCH_FIREWALL","#GEAR_SCORCH_FIREWALL_DESC","#GEAR_SCORCH_FIREWALL_LONGDESC","rui/titan_loadout/passive/scorch_fuel",0,24
+"pas_scorch_shield","TITAN_SCORCH_PASSIVE","#GEAR_SCORCH_SHIELD","#GEAR_SCORCH_SHIELD_DESC","#GEAR_SCORCH_SHIELD_LONGDESC","rui/titan_loadout/passive/scorch_inferno_shield",0,24
+"pas_scorch_selfdmg","TITAN_SCORCH_PASSIVE","#GEAR_SCORCH_SELFDMG","#GEAR_SCORCH_SELFDMG_DESC","#GEAR_SCORCH_SELFDMG_LONGDESC","rui/titan_loadout/passive/scorch_tempered_plating",0,24
+"pas_legion_spinup","TITAN_LEGION_PASSIVE","#GEAR_LEGION_SPINUP","#GEAR_LEGION_SPINUP_DESC","#GEAR_LEGION_SPINUP_LONGDESC","rui/titan_loadout/passive/legion_lightweight_alloys",0,24
+"pas_legion_gunshield","TITAN_LEGION_PASSIVE","#GEAR_LEGION_GUNSHIELD","#GEAR_LEGION_GUNSHIELD_DESC","#GEAR_LEGION_GUNSHIELD_LONGDESC","rui/titan_loadout/passive/legion_bulwark",0,24
+"pas_legion_smartcore","TITAN_LEGION_PASSIVE","#GEAR_LEGION_SMARTCORE","#GEAR_LEGION_SMARTCORE_DESC","#GEAR_LEGION_SMARTCORE_LONGDESC","rui/titan_loadout/passive/legion_sensor_array",0,24
+"pas_ion_weapon_ads","TITAN_ION_PASSIVE","#GEAR_ION_SPLIT","#GEAR_ION_SPLIT_DESC","#GEAR_ION_SPLIT_LONGDESC","rui/titan_loadout/passive/ion_diffraction_lens",0,24
+"pas_tone_burst","TITAN_TONE_PASSIVE","#GEAR_TONE_BURST","#GEAR_TONE_BURST_DESC","#GEAR_TONE_BURST_LONGDESC","rui/titan_loadout/passive/tone_40mm_burst",0,24
+"pas_legion_chargeshot","TITAN_LEGION_PASSIVE","#GEAR_LEGION_CHARGESHOT","#GEAR_LEGION_CHARGESHOT_DESC","#GEAR_LEGION_CHARGESHOT_LONGDESC","rui/titan_loadout/passive/legion_siege_mode",0,24
+"pas_ronin_autoshift","TITAN_RONIN_PASSIVE","#GEAR_RONIN_AUTOSHIFT","#GEAR_RONIN_AUTOSHIFT_DESC","#GEAR_RONIN_AUTOSHIFT_LONGDESC","rui/titan_loadout/passive/ronin_auto_shift",0,24
+"pas_northstar_optics","TITAN_NORTHSTAR_PASSIVE","#GEAR_NORTHSTAR_OPTICS","#GEAR_NORTHSTAR_OPTICS_DESC","#GEAR_NORTHSTAR_OPTICS_LONGDESC","rui/titan_loadout/passive/northstar_threat_optics",0,24
+"pas_scorch_flamecore","TITAN_SCORCH_PASSIVE","#GEAR_SCORCH_FLAMECORE","#GEAR_SCORCH_FLAMECORE_DESC","#GEAR_SCORCH_FLAMECORE_LONGDESC","rui/titan_loadout/passive/scorch_scorched_earth",0,24
+"pas_vanguard_coremeter","TITAN_VANGUARD_PASSIVE","#GEAR_VANGUARD_COREMETER","#GEAR_VANGUARD_COREMETER_DESC","#GEAR_VANGUARD_COREMETER_LONGDESC","rui/titan_loadout/passive/vanguard_siphon",0,24
+"pas_vanguard_shield","TITAN_VANGUARD_PASSIVE","#GEAR_VANGUARD_SHIELD","#GEAR_VANGUARD_SHIELD_DESC","#GEAR_VANGUARD_SHIELD_LONGDESC","rui/titan_loadout/passive/vanguard_survivor",0,24
+"pas_vanguard_rearm","TITAN_VANGUARD_PASSIVE","#GEAR_VANGUARD_REARM","#GEAR_VANGUARD_REARM_DESC","#GEAR_VANGUARD_REARM_LONGDESC","rui/titan_loadout/passive/vanguard_rearm",0,24
+"pas_vanguard_doom","TITAN_VANGUARD_PASSIVE","#GEAR_VANGUARD_DOOM","#GEAR_VANGUARD_DOOM_DESC","#GEAR_VANGUARD_DOOM_LONGDESC","rui/titan_loadout/passive/vanguard_fittest",0,24
+"pas_vanguard_core1","TITAN_UPGRADE1_PASSIVE","#GEAR_VANGUARD_CORE1","#GEAR_VANGUARD_CORE1_DESC","#GEAR_VANGUARD_CORE1_LONGDESC","rui/titan_loadout/passive/monarch_core_arc_rounds",0,24
+"pas_vanguard_core2","TITAN_UPGRADE1_PASSIVE","#GEAR_VANGUARD_CORE2","#GEAR_VANGUARD_CORE2_DESC","#GEAR_VANGUARD_CORE2_LONGDESC","rui/titan_loadout/passive/monarch_core_missile_racks",0,24
+"pas_vanguard_core3","TITAN_UPGRADE1_PASSIVE","#GEAR_VANGUARD_CORE3","#GEAR_VANGUARD_CORE3_DESC","#GEAR_VANGUARD_CORE3_LONGDESC","rui/titan_loadout/passive/monarch_core_energy_field",0,24
+"pas_vanguard_core4","TITAN_UPGRADE2_PASSIVE","#GEAR_VANGUARD_CORE4","#GEAR_VANGUARD_CORE4_DESC","#GEAR_VANGUARD_CORE4_LONGDESC","rui/titan_loadout/passive/monarch_core_swift_rearm",0,24
+"pas_vanguard_core5","TITAN_UPGRADE2_PASSIVE","#GEAR_VANGUARD_CORE5","#GEAR_VANGUARD_CORE5_DESC","#GEAR_VANGUARD_CORE5_LONGDESC","rui/titan_loadout/passive/monarch_core_maelstrom",0,24
+"pas_vanguard_core6","TITAN_UPGRADE2_PASSIVE","#GEAR_VANGUARD_CORE6","#GEAR_VANGUARD_CORE6_DESC","#GEAR_VANGUARD_CORE6_LONGDESC","rui/titan_loadout/passive/monarch_core_energy_transfer",0,24
+"pas_vanguard_core7","TITAN_UPGRADE3_PASSIVE","#GEAR_VANGUARD_CORE7","#GEAR_VANGUARD_CORE7_DESC","#GEAR_VANGUARD_CORE7_LONGDESC","rui/titan_loadout/passive/monarch_core_multi_target",0,24
+"pas_vanguard_core8","TITAN_UPGRADE3_PASSIVE","#GEAR_VANGUARD_CORE8","#GEAR_VANGUARD_CORE8_DESC","#GEAR_VANGUARD_CORE8_LONGDESC","rui/titan_loadout/passive/monarch_core_superior_chassis",0,24
+"pas_vanguard_core9","TITAN_UPGRADE3_PASSIVE","#GEAR_VANGUARD_CORE9","#GEAR_VANGUARD_CORE9_DESC","#GEAR_VANGUARD_CORE9_LONGDESC","rui/titan_loadout/passive/monarch_core_xo16",0,24 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_primary_mods.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_primary_mods.csv
new file mode 100644
index 00000000..d25cc30d
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_primary_mods.csv
@@ -0,0 +1 @@
+mod,weapon,statDamage,statAccuracy,statRange,statFireRate,statClipSize,hidden \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_primary_mods_common.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_primary_mods_common.csv
new file mode 100644
index 00000000..e4659a7e
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_primary_mods_common.csv
@@ -0,0 +1 @@
+mod,name,description,image \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_primary_weapons.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_primary_weapons.csv
new file mode 100644
index 00000000..c617d653
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_primary_weapons.csv
@@ -0,0 +1,10 @@
+itemRef,hidden,defaultMod2,hidden
+"mp_titanweapon_leadwall",0,"",0
+"mp_titanweapon_meteor",0,"",0
+"mp_titanweapon_particle_accelerator",0,"",0
+"mp_titanweapon_predator_cannon",0,"",0
+"mp_titanweapon_sniper",0,"",0
+"mp_titanweapon_sticky_40mm",0,"",0
+"mp_titanweapon_xo16_shorty",1,"",0
+"mp_titanweapon_rocketeer_rocketstream",1,"",0
+"mp_titanweapon_xo16_vanguard",0,"",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_properties.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_properties.csv
new file mode 100644
index 00000000..4b9369ca
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_properties.csv
@@ -0,0 +1,10 @@
+setFile,primeSetFile,titanRef,primeTitanRef,titanLoadoutInSP,mp_npcUseAllowed,difficulty,coreBuildingIcon,coreReadyIcon,hintIcon,loadoutIcon,loadoutIconFD,fdRole,desc,speedDisplay,damageDisplay,healthDisplay,dashDisplay,primary,melee,ordnance,special,antirodeo,coreAbility,passive1,passive2,passive3,passive4,passive5,passive6,titanExecution,bossCharacter,hidden,menuItem,menuTitle,menuSubTitle,menuLongDesc,menuFlavorText,dialogHint,weaponImage
+"titan_buddy","","bt","",1,0,1,"rui\titan_loadout\core\titan_core_burst_core","rui\titan_loadout\core\titan_core_burst_core","rui\menu\common\bulb_hint_icon","rui/menu/postgame/vanguard_icon","rui/menu/fd_menu/fd_icon_monarch","#FD_ROLE_MONARCH","#MP_TITAN_LOADOUT_DESC_XO16",2,1,2,2,"mp_titanweapon_xo16_shorty","melee_titan_punch","mp_titanweapon_shoulder_rockets","mp_titanweapon_vortex_shield","mp_titanability_smoke","mp_titancore_amp_core","TITAN_GENERAL_PASSIVE","TITAN_GENERAL_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","","",1,"#SP_TITAN_LOADOUT_MENUITEM_XO16","#SP_TITAN_LOADOUT_TITLE_XO16","#SP_TITAN_LOADOUT_SUBTITLE_XO16","#SP_TITAN_LOADOUT_DESC_XO16","#SP_TITAN_LOADOUT_FLAVOR_XO16","diag_sp_extra_GB101_47_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_x016_shorty"
+"titan_atlas_tracker","titan_atlas_tone_prime","tone","tone_prime",1,1,2,"rui\titan_loadout\core\titan_core_salvo","rui\titan_loadout\core\titan_core_salvo","rui\menu\common\bulb_hint_icon","rui/menu/postgame/tone_icon","rui/menu/fd_menu/fd_icon_tone","#FD_ROLE_TONE","#MP_TITAN_LOADOUT_DESC_TONE",2,2,2,1,"mp_titanweapon_sticky_40mm","melee_titan_punch_tone","mp_titanweapon_tracker_rockets","mp_titanability_particle_wall","mp_titanability_sonar_pulse","mp_titancore_salvo_core","TITAN_GENERAL_PASSIVE","TITAN_TONE_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","TITAN_TONE_EXECUTION","Richter",0,"#SP_TITAN_LOADOUT_MENUITEM_TONE","#SP_TITAN_LOADOUT_TITLE_TONE","#SP_TITAN_LOADOUT_SUBTITLE_TONE","#SP_TITAN_LOADOUT_DESC_TONE","#SP_TITAN_LOADOUT_FLAVOR_TONE","diag_sp_extra_GB101_37_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_40mm"
+"titan_ogre_meteor","titan_ogre_scorch_prime","scorch","scorch_prime",1,1,3,"rui\titan_loadout\core\titan_core_flame_wave","rui\titan_loadout\core\titan_core_flame_wave","rui\menu\common\bulb_hint_icon","rui/menu/postgame/scorch_icon","rui/menu/fd_menu/fd_icon_scorch","#FD_ROLE_SCORCH","#MP_TITAN_LOADOUT_DESC_SCORCH",1,3,3,0,"mp_titanweapon_meteor","melee_titan_punch_scorch","mp_titanweapon_flame_wall","mp_titanweapon_heat_shield","mp_titanability_slow_trap","mp_titancore_flame_wave","TITAN_GENERAL_PASSIVE","TITAN_SCORCH_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","TITAN_SCORCH_EXECUTION","Kane",0,"#SP_TITAN_LOADOUT_MENUITEM_SCORCH","#SP_TITAN_LOADOUT_TITLE_SCORCH","#SP_TITAN_LOADOUT_SUBTITLE_SCORCH","#SP_TITAN_LOADOUT_DESC_SCORCH","#SP_TITAN_LOADOUT_FLAVOR_SCORCH","diag_sp_extra_GB101_33_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_meteor"
+"titan_stryder_rocketeer","","brute4","",1,0,1,"rui\titan_loadout\core\titan_core_flight","rui\titan_loadout\core\titan_core_flight","rui\menu\common\bulb_hint_icon","rui/menu/postgame/northstar_icon","rui/menu/fd_menu/fd_icon_northstar","#FD_ROLE_NORTHSTAR","#MP_TITAN_LOADOUT_DESC_BRUTE",3,2,1,2,"mp_titanweapon_rocketeer_rocketstream","melee_titan_punch","mp_titanweapon_shoulder_rockets","mp_titanweapon_vortex_shield","mp_titanability_hover","mp_titancore_flight_core","TITAN_GENERAL_PASSIVE","TITAN_GENERAL_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","","",0,"#SP_TITAN_LOADOUT_MENUITEM_BRUTE","#SP_TITAN_LOADOUT_TITLE_BRUTE","#SP_TITAN_LOADOUT_SUBTITLE_BRUTE","#SP_TITAN_LOADOUT_DESC_BRUTE","#SP_TITAN_LOADOUT_FLAVOR_BRUTE","diag_sp_extra_GB101_47_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_rocketeer"
+"titan_atlas_stickybomb","titan_atlas_ion_prime","ion","ion_prime",1,1,1,"rui\titan_loadout\core\titan_core_laser","rui\titan_loadout\core\titan_core_laser","rui\menu\common\bulb_hint_icon","rui/menu/postgame/ion_icon","rui/menu/fd_menu/fd_icon_ion","#FD_ROLE_ION","#MP_TITAN_LOADOUT_DESC_ION",2,1,2,1,"mp_titanweapon_particle_accelerator","melee_titan_punch_ion","mp_titanweapon_laser_lite","mp_titanweapon_vortex_shield_ion","mp_titanability_laser_trip","mp_titancore_laser_cannon","TITAN_GENERAL_PASSIVE","TITAN_ION_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","TITAN_ION_EXECUTION","Slone",0,"#SP_TITAN_LOADOUT_MENUITEM_ION","#SP_TITAN_LOADOUT_TITLE_ION","#SP_TITAN_LOADOUT_SUBTITLE_ION","#SP_TITAN_LOADOUT_DESC_ION","#SP_TITAN_LOADOUT_FLAVOR_ION","diag_sp_extra_GB101_34_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_particle_accelerator"
+"titan_stryder_leadwall","titan_stryder_ronin_prime","ronin","ronin_prime",1,1,3,"rui\titan_loadout\core\titan_core_sword","rui\titan_loadout\core\titan_core_sword","rui\menu\common\bulb_hint_icon","rui/menu/postgame/ronin_icon","rui/menu/fd_menu/fd_icon_ronin","#FD_ROLE_RONIN","#MP_TITAN_LOADOUT_DESC_RONIN",3,2,1,2,"mp_titanweapon_leadwall","melee_titan_sword","mp_titanweapon_arc_wave","mp_titanability_basic_block","mp_titanability_phase_dash","mp_titancore_shift_core","TITAN_GENERAL_PASSIVE","TITAN_RONIN_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","TITAN_RONIN_EXECUTION","Ash",0,"#SP_TITAN_LOADOUT_MENUITEM_RONIN","#SP_TITAN_LOADOUT_TITLE_RONIN","#SP_TITAN_LOADOUT_SUBTITLE_RONIN","#SP_TITAN_LOADOUT_DESC_RONIN","#SP_TITAN_LOADOUT_FLAVOR_RONIN","diag_sp_extra_GB101_35_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_leadwall"
+"titan_stryder_sniper","titan_stryder_northstar_prime","northstar","northstar_prime",1,1,2,"rui\titan_loadout\core\titan_core_flight","rui\titan_loadout\core\titan_core_flight","rui\menu\common\bulb_hint_icon","rui/menu/postgame/northstar_icon","rui/menu/fd_menu/fd_icon_northstar","#FD_ROLE_NORTHSTAR","#MP_TITAN_LOADOUT_DESC_NORTHSTAR",3,3,1,2,"mp_titanweapon_sniper","melee_titan_punch_northstar","mp_titanweapon_dumbfire_rockets","mp_titanability_tether_trap","mp_titanability_hover","mp_titancore_flight_core","TITAN_GENERAL_PASSIVE","TITAN_NORTHSTAR_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","TITAN_NORTHSTAR_EXECUTION","Viper",0,"#SP_TITAN_LOADOUT_MENUITEM_NORTHSTAR","#SP_TITAN_LOADOUT_TITLE_NORTHSTAR","#SP_TITAN_LOADOUT_SUBTITLE_NORTHSTAR","#SP_TITAN_LOADOUT_DESC_NORTHSTAR","#SP_TITAN_LOADOUT_FLAVOR_NORTHSTAR","diag_sp_extra_GB101_36_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_sniper"
+"titan_ogre_minigun","titan_ogre_legion_prime","legion","legion_prime",1,1,2,"rui\titan_loadout\core\titan_core_smart","rui\titan_loadout\core\titan_core_smart","rui\menu\common\bulb_hint_icon","rui/menu/postgame/legion_icon","rui/menu/fd_menu/fd_icon_legion","#FD_ROLE_LEGION","#MP_TITAN_LOADOUT_DESC_LEGION",1,3,3,0,"mp_titanweapon_predator_cannon","melee_titan_punch_legion","mp_titanability_power_shot","mp_titanability_gun_shield","mp_titanability_ammo_swap","mp_titancore_siege_mode","TITAN_GENERAL_PASSIVE","TITAN_LEGION_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","TITAN_LEGION_EXECUTION","Blisk",0,"#SP_TITAN_LOADOUT_MENUITEM_LEGION","#SP_TITAN_LOADOUT_TITLE_LEGION","#SP_TITAN_LOADOUT_SUBTITLE_LEGION","#SP_TITAN_LOADOUT_DESC_LEGION","#SP_TITAN_LOADOUT_FLAVOR_LEGION","diag_sp_extra_GB101_32_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_predator"
+"titan_atlas_vanguard","","vanguard","vanguard_prime",0,1,3,"rui\titan_loadout\core\titan_core_smart","rui\titan_loadout\core\titan_core_smart","rui\menu\common\bulb_hint_icon","rui/menu/postgame/vanguard_icon","rui/menu/fd_menu/fd_icon_monarch","#FD_ROLE_MONARCH","#MP_TITAN_LOADOUT_DESC_VANGUARD",2,2,2,1,"mp_titanweapon_xo16_vanguard","melee_titan_punch_vanguard","mp_titanweapon_salvo_rockets","mp_titanweapon_stun_laser","mp_titanability_rearm","mp_titancore_upgrade","TITAN_GENERAL_PASSIVE","TITAN_VANGUARD_PASSIVE","TITAN_TITANFALL_PASSIVE","TITAN_UPGRADE1_PASSIVE","TITAN_UPGRADE2_PASSIVE","TITAN_UPGRADE3_PASSIVE","TITAN_VANGUARD_EXECUTION","Richter",0,"#SP_TITAN_LOADOUT_MENUITEM_TONE","#SP_TITAN_LOADOUT_TITLE_TONE","#SP_TITAN_LOADOUT_SUBTITLE_TONE","#SP_TITAN_LOADOUT_DESC_TONE","#SP_TITAN_LOADOUT_FLAVOR_TONE","diag_sp_extra_GB101_37_01_mcor_bt","rui\titan_loadout\loadout_select\ls_wep_ttn_40mm" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_skins.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_skins.csv
new file mode 100644
index 00000000..da8b3172
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_skins.csv
@@ -0,0 +1,56 @@
+ref,titanRef,image,name,cost,skinIndex
+"ion_skin_01","ion","rui/titan_loadout/skins/ion_skin_01","#ION_SKIN_01",0,1
+"ion_skin_02","ion","rui/titan_loadout/skins/ion_skin_02","#ION_SKIN_02",0,3
+"ion_skin_03","ion","rui/titan_loadout/skins/ion_skin_03","#ION_SKIN_03",500,4
+"ion_skin_04","ion","rui/titan_loadout/skins/ion_skin_04","#ION_SKIN_04",0,5
+"ion_skin_06","ion","rui/titan_loadout/skins/ion_skin_06","#ION_SKIN_06",0,6
+"scorch_skin_01","scorch","rui/titan_loadout/skins/scorch_skin_01","#SCORCH_SKIN_01",0,1
+"scorch_skin_02","scorch","rui/titan_loadout/skins/scorch_skin_02","#SCORCH_SKIN_02",0,3
+"scorch_skin_03","scorch","rui/titan_loadout/skins/scorch_skin_03","#SCORCH_SKIN_03",0,4
+"scorch_skin_04","scorch","rui/titan_loadout/skins/scorch_skin_04","#SCORCH_SKIN_04",0,5
+"ronin_skin_01","ronin","rui/titan_loadout/skins/ronin_skin_01","#RONIN_SKIN_01",0,1
+"ronin_skin_02","ronin","rui/titan_loadout/skins/ronin_skin_02","#RONIN_SKIN_02",0,3
+"ronin_skin_03","ronin","rui/titan_loadout/skins/ronin_skin_03","#RONIN_SKIN_03",0,4
+"ronin_skin_04","ronin","rui/titan_loadout/skins/ronin_skin_04","#RONIN_SKIN_04",0,5
+"tone_skin_01","tone","rui/titan_loadout/skins/tone_skin_01","#TONE_SKIN_01",0,1
+"tone_skin_02","tone","rui/titan_loadout/skins/tone_skin_02","#TONE_SKIN_02",0,3
+"tone_skin_03","tone","rui/titan_loadout/skins/tone_skin_03","#TONE_SKIN_03",0,4
+"tone_skin_04","tone","rui/titan_loadout/skins/tone_skin_04","#TONE_SKIN_04",0,5
+"northstar_skin_01","northstar","rui/titan_loadout/skins/northstar_skin_01","#NORTHSTAR_SKIN_01",0,1
+"northstar_skin_02","northstar","rui/titan_loadout/skins/northstar_skin_02","#NORTHSTAR_SKIN_02",0,3
+"northstar_skin_03","northstar","rui/titan_loadout/skins/northstar_skin_03","#NORTHSTAR_SKIN_03",0,4
+"northstar_skin_04","northstar","rui/titan_loadout/skins/northstar_skin_04","#NORTHSTAR_SKIN_04",0,5
+"legion_skin_01","legion","rui/titan_loadout/skins/legion_skin_01","#LEGION_SKIN_01",0,1
+"legion_skin_02","legion","rui/titan_loadout/skins/legion_skin_02","#LEGION_SKIN_02",0,3
+"legion_skin_03","legion","rui/titan_loadout/skins/legion_skin_03","#LEGION_SKIN_03",0,4
+"legion_skin_04","legion","rui/titan_loadout/skins/legion_skin_04","#LEGION_SKIN_04",0,5
+"ion_skin_10","ion","rui/titan_loadout/skins/ion_skin_10","#ION_SKIN_10",0,7
+"tone_skin_06","tone","rui/titan_loadout/skins/tone_skin_06","#TONE_SKIN_06",0,6
+"scorch_skin_07","scorch","rui/titan_loadout/skins/scorch_skin_07","#SCORCH_SKIN_07",0,6
+"ronin_skin_10","ronin","rui/titan_loadout/skins/ronin_skin_10","#RONIN_SKIN_10",0,6
+"northstar_skin_10","northstar","rui/titan_loadout/skins/northstar_skin_10","#NORTHSTAR_SKIN_10",0,6
+"legion_skin_07","legion","rui/titan_loadout/skins/legion_skin_07","#LEGION_SKIN_07",0,6
+"ion_skin_11","ion","rui/titan_loadout/skins/ion_skin_11","#ION_SKIN_11",0,8
+"tone_skin_07","tone","rui/titan_loadout/skins/tone_skin_07","#TONE_SKIN_07",0,7
+"scorch_skin_08","scorch","rui/titan_loadout/skins/scorch_skin_08","#SCORCH_SKIN_08",0,7
+"ronin_skin_11","ronin","rui/titan_loadout/skins/ronin_skin_11","#RONIN_SKIN_11",0,7
+"northstar_skin_11","northstar","rui/titan_loadout/skins/northstar_skin_11","#NORTHSTAR_SKIN_11",0,7
+"legion_skin_08","legion","rui/titan_loadout/skins/legion_skin_08","#LEGION_SKIN_08",0,7
+"vanguard_skin_01","vanguard","rui/titan_loadout/skins/legion_skin_01","#LEGION_SKIN_01",0,1
+"vanguard_skin_02","vanguard","rui/titan_loadout/skins/legion_skin_02","#LEGION_SKIN_02",0,3
+"vanguard_skin_03","vanguard","rui/titan_loadout/skins/legion_skin_03","#LEGION_SKIN_03",0,4
+"vanguard_skin_07","vanguard","rui/titan_loadout/skins/legion_skin_07","#LEGION_SKIN_07",0,6
+"vanguard_skin_08","vanguard","rui/titan_loadout/skins/legion_skin_08","#LEGION_SKIN_08",0,7
+"northstar_skin_06","northstar","rui/titan_loadout/skins/northstar_skin_06","#NORTHSTAR_SKIN_06",0,8
+"ronin_skin_07","ronin","rui/titan_loadout/skins/ronin_skin_07","#RONIN_SKIN_07",0,8
+"scorch_skin_06","scorch","rui/titan_loadout/skins/scorch_skin_06","#SCORCH_SKIN_06",0,8
+"ion_skin_07","ion","rui/titan_loadout/skins/ion_skin_07","#ION_SKIN_07",0,9
+"legion_skin_09","legion","rui/titan_loadout/skins/legion_skin_09","#LEGION_SKIN_09",0,8
+"tone_skin_08","tone","rui/titan_loadout/skins/tone_skin_08","#TONE_SKIN_08",0,8
+"northstar_skin_fd","northstar","rui/titan_loadout/skins/northstar_skin_fd","#NORTHSTAR_SKIN_FD",0,9
+"ronin_skin_fd","ronin","rui/titan_loadout/skins/ronin_skin_fd","#RONIN_SKIN_FD",0,9
+"scorch_skin_fd","scorch","rui/titan_loadout/skins/scorch_skin_fd","#SCORCH_SKIN_FD",0,9
+"ion_skin_fd","ion","rui/titan_loadout/skins/ion_skin_fd","#ION_SKIN_FD",0,10
+"legion_skin_fd","legion","rui/titan_loadout/skins/legion_skin_fd","#LEGION_SKIN_FD",0,9
+"tone_skin_fd","tone","rui/titan_loadout/skins/tone_skin_fd","#TONE_SKIN_FD",0,9
+"monarch_skin_fd","vanguard","rui/titan_loadout/skins/monarch_skin_fd","#MONARCH_SKIN_FD",0,3 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titan_voices.csv b/Northstar.CustomServers/mod/scripts/datatable/titan_voices.csv
new file mode 100644
index 00000000..8a762f9b
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titan_voices.csv
@@ -0,0 +1,9 @@
+weapon,name,description,image,hidden
+"titanos_bt","#TITAN_OS_BT_NAME","#TITAN_OS_BT_LONGDESC","material/ui/menu/voice_personality_icons/betty_voice_icon_gen.rpak",0
+"titanos_legion","#TITAN_OS_LEGION_NAME","#TITAN_OS_LEGION_LONGDESC","material/ui/menu/voice_personality_icons/betty_voice_icon_gen.rpak",0
+"titanos_scorch","#TITAN_OS_SCORCH_NAME","#TITAN_OS_SCORCH_LONGDESC","material/ui/menu/voice_personality_icons/betty_voice_icon_gen.rpak",0
+"titanos_ronin","#TITAN_OS_RONIN_NAME","#TITAN_OS_RONIN_LONGDESC","material/ui/menu/voice_personality_icons/betty_voice_icon_gen.rpak",0
+"titanos_northstar","#TITAN_OS_NORTHSTAR_NAME","#TITAN_NORTHSTAR_BETTY_LONGDESC","material/ui/menu/voice_personality_icons/betty_voice_icon_gen.rpak",0
+"titanos_ion","#TITAN_OS_ION_NAME","#TITAN_OS_ION_LONGDESC","material/ui/menu/voice_personality_icons/betty_voice_icon_gen.rpak",0
+"titanos_tone","#TITAN_OS_TONE_NAME","#TITAN_OS_TONE_LONGDESC","material/ui/menu/voice_personality_icons/betty_voice_icon_gen.rpak",0
+"titanos_vanguard","#TITAN_OS_VANGUARD_NAME","#TITAN_OS_VANGUARD_LONGDESC","material/ui/menu/voice_personality_icons/betty_voice_icon_gen.rpak",0 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/titans_mp.csv b/Northstar.CustomServers/mod/scripts/datatable/titans_mp.csv
new file mode 100644
index 00000000..a0873690
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/titans_mp.csv
@@ -0,0 +1,8 @@
+titanRef,cost,coreIcon,image
+"ion",5,"rui/titan_loadout/core/titan_core_laser","rui/menu/postgame/ion_icon"
+"scorch",5,"rui/titan_loadout/core/titan_core_flame_wave","rui/menu/postgame/scorch_icon"
+"northstar",5,"rui/titan_loadout/core/titan_core_flight","rui/menu/postgame/northstar_icon"
+"ronin",10,"rui/titan_loadout/core/titan_core_sword","rui/menu/postgame/ronin_icon"
+"tone",15,"rui/titan_loadout/core/titan_core_salvo","rui/menu/postgame/tone_icon"
+"legion",20,"rui/titan_loadout/core/titan_core_smart","rui/menu/postgame/legion_icon"
+"vanguard",200,"rui/titan_loadout/core/titan_core_vanguard","rui/menu/postgame/vanguard_icon" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/unlocks_faction_level.csv b/Northstar.CustomServers/mod/scripts/datatable/unlocks_faction_level.csv
new file mode 100644
index 00000000..6f9fa4c1
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/unlocks_faction_level.csv
@@ -0,0 +1,82 @@
+factionLevel,faction_apex,faction_64,faction_vinson,faction_marauder,faction_aces,faction_ares,faction_marvin
+0,"faction_apex","faction_64","faction_vinson","faction_marauder","faction_aces","faction_ares","faction_marvin"
+1,"","","","","","",""
+2,"gc_icon_dollarsign,random","gc_icon_fox,random ","gc_icon_gear,random ","gc_icon_prowler,random ","gc_icon_ace,random","gc_icon_radar,random","gc_icon_mrvn,random"
+3,"random,callsign_06_col","random,callsign_96_col","random,callsign_05_col","random,callsign_46_col","random,callsign_01_col","random,callsign_41_col","random,callsign_163_col"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random,callsign_06_col_fire","random,callsign_96_col_fire","random,callsign_05_col_fire","random,callsign_46_col_fire","random,callsign_01_col_fire","random,callsign_41_col_fire","random,callsign_163_col_fire"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random,callsign_06_col_gold","random,callsign_96_col_gold","random,callsign_05_col_gold","random,callsign_46_col_gold","random,callsign_01_col_gold","random,callsign_41_col_gold","random,callsign_163_col_gold"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random,callsign_163_col_prism"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random,callsign_164_col"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random,callsign_164_col_fire"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random,callsign_164_col_gold"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random,callsign_164_col_prism"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random"
+1,"random","random","random","random","random","random","random"
+2,"random","random","random","random","random","random","random"
+3,"random","random","random","random","random","random","random"
+4,"random","random","random","random","random","random","random"
+5,"random","random","random","random","random","random","random" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/unlocks_fd_titan_level.csv b/Northstar.CustomServers/mod/scripts/datatable/unlocks_fd_titan_level.csv
new file mode 100644
index 00000000..957af6d6
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/unlocks_fd_titan_level.csv
@@ -0,0 +1,26 @@
+titanLevel,ion,scorch,northstar,ronin,tone,legion,vanguard,end
+"","ion","scorch","northstar","ronin","tone","legion","vanguard","END"
+"1","","","","","","","",""
+"2","fd_upgrade_ion_weapon_tier_2","fd_upgrade_scorch_weapon_tier_1","fd_upgrade_northstar_utility_tier_1","fd_upgrade_ronin_weapon_tier_1","fd_upgrade_tone_weapon_tier_1","fd_upgrade_legion_weapon_tier_1","fd_upgrade_vanguard_utility_tier_1",""
+"3","random","random","random","random","random","random","random",""
+"4","random","random","random","random","random","random","random",""
+"5","fd_upgrade_ion_defense_tier_1","fd_upgrade_scorch_defense_tier_1","fd_upgrade_northstar_defense_tier_1","fd_upgrade_ronin_defense_tier_1","fd_upgrade_tone_defense_tier_1","fd_upgrade_legion_defense_tier_1","fd_upgrade_vanguard_defense_tier_1",""
+"6","random","random","random","random","random","random","random",""
+"7","random","random","random","random","random","random","random",""
+"8","fd_upgrade_ion_utility_tier_2","fd_upgrade_scorch_utility_tier_1","fd_upgrade_northstar_weapon_tier_1","fd_upgrade_ronin_utility_tier_1","fd_upgrade_tone_utility_tier_2","fd_upgrade_legion_utility_tier_1","fd_upgrade_vanguard_weapon_tier_1",""
+"9","random","random","random","random","random","random","random",""
+"10","random","random","random","random","random","random","random",""
+"11","fd_upgrade_ion_weapon_tier_1","fd_upgrade_scorch_utility_tier_2","fd_upgrade_northstar_utility_tier_2","fd_upgrade_ronin_weapon_tier_2","fd_upgrade_tone_weapon_tier_2","fd_upgrade_legion_weapon_tier_2","fd_upgrade_vanguard_utility_tier_2",""
+"12","random","random","random","random","random","random","random",""
+"13","random","random","random","random","random","random","random",""
+"14","fd_upgrade_ion_defense_tier_2","fd_upgrade_scorch_defense_tier_2","fd_upgrade_northstar_defense_tier_2","fd_upgrade_ronin_defense_tier_2","fd_upgrade_tone_defense_tier_2","fd_upgrade_legion_defense_tier_2","fd_upgrade_vanguard_defense_tier_2",""
+"15","random","random","random","random","random","random","random",""
+"16","random","random","random","random","random","random","random",""
+"17","fd_upgrade_ion_utility_tier_1","fd_upgrade_scorch_weapon_tier_2","fd_upgrade_northstar_weapon_tier_2","fd_upgrade_ronin_utility_tier_2","fd_upgrade_tone_utility_tier_1","fd_upgrade_legion_utility_tier_2","fd_upgrade_vanguard_weapon_tier_2",""
+"18","random","random","random","random","random","random","random",""
+"19","random","random","random","random","random","random","random",""
+"20","fd_upgrade_ion_ultimate","fd_upgrade_scorch_ultimate","fd_upgrade_northstar_ultimate","fd_upgrade_ronin_ultimate","fd_upgrade_tone_ultimate","fd_upgrade_legion_ultimate","fd_upgrade_vanguard_ultimate",""
+"21","random","random","random","random","random","random","random",""
+"22","random","random","random","random","random","random","random",""
+"23","random","random","random","random","random","random","random",""
+"24","callsign_24_col_prism","callsign_47_col_prism","callsign_36_col_prism","callsign_45_col_prism","callsign_68_col_prism","callsign_26_col_prism","callsign_165_col_prism","" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/unlocks_player_level.csv b/Northstar.CustomServers/mod/scripts/datatable/unlocks_player_level.csv
new file mode 100644
index 00000000..693bcc15
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/unlocks_player_level.csv
@@ -0,0 +1,996 @@
+playerLevel,pilotWeapons,pilotOffhands,pilotKits,pilotTactical,titanChassis,burnCard,feature,callSign,callsignIcon,pilotExecutions,pilotCamo,factions,random
+1,"mp_weapon_rspn101_og, mp_weapon_rspn101,mp_weapon_car,mp_weapon_lmg,mp_weapon_shotgun,mp_weapon_sniper,mp_weapon_smr,mp_weapon_semipistol,mp_weapon_autopistol,mp_weapon_defender,mp_weapon_mgl,mp_weapon_wingman_n","mp_weapon_frag_grenade,mp_weapon_grenade_emp,mp_weapon_thermite_grenade","pas_enemy_death_icons,pas_wallhang,pas_fast_health_regen,pas_power_cell","mp_ability_grapple,mp_ability_cloak,mp_weapon_grenade_sonar,mp_ability_shifter_super,grapple,geist,medium","ion,scorch,northstar","burnmeter_amped_weapons","cp,ctf,lts,ffa,fw,ps,tdm,at,aitdm,lf,communities,happy_hour, pilot_loadout_1, pilot_loadout_2, pilot_loadout_3, pilot_loadout_4, pilot_loadout_5, pilot_loadout_6,hunted,mfd,fd_easy,fd_normal","callsign_16_col","gc_icon_titanfall","execution_neck_snap","pilot_camo_skin00","faction_marauder",""
+2,"mp_weapon_lstar","","","","","","","","","","","",""
+3,"","","","mp_ability_heal,nomad","","","","","","","","",""
+4,"","","","","","","coliseum","","","","","",""
+5,"mp_weapon_epg","","","","","","","callsign_53_col","","","","",""
+6,"","","","","","burnmeter_ticks","","","","","","",""
+7,"","","","","ronin","","","","","","","",""
+8,"mp_weapon_alternator_smg","","","","","","","","","","","",""
+9,"","mp_weapon_grenade_gravity","","","","","","","","","","",""
+10,"mp_weapon_shotgun_pistol","","","","","","","","","","pilot_camo_skin01","",""
+11,"","","","","tone","","","","","","","",""
+12,"","","","","","","","","","","","faction_apex",""
+13,"mp_weapon_hemlok","","","","","","","","","","","",""
+14,"","","","mp_weapon_deployable_cover,heavy","","","","","","","","",""
+15,"","","","","legion","","","callsign_67_col","","","","",""
+16,"mp_weapon_doubletake","","","","","","","","","","","",""
+17,"","","","","","burnmeter_ap_turret_weapon","","","","","","",""
+18,"","","pas_ordnance_pack","","","","","","","","","",""
+19,"mp_weapon_mastiff","","","","","","","","","","","",""
+20,"","","","","vanguard","","pilot_loadout_7","","","","pilot_camo_skin03","faction_vinson",""
+21,"mp_weapon_arc_launcher","","","","","","","","","","","",""
+22,"","mp_weapon_grenade_electric_smoke","","","","","","","","","","",""
+23,"","","","","","burnmeter_maphack","","","","","","",""
+24,"mp_weapon_esaw","","","","","","","","","","","",""
+25,"","","pas_ads_hover","","","","","callsign_100_col","","","","",""
+26,"","","","mp_ability_shifter,light","","","","","","","","",""
+27,"mp_weapon_hemlok_smg","","","","","","","","","","","",""
+28,"","","","","","burnmeter_emergency_battery","","","","","","",""
+29,"","","pas_fast_embark","","","","","","","","","",""
+30,"mp_weapon_softball","","","","","","pilot_loadout_8","","","","pilot_camo_skin02","",""
+31,"","","","","","burnmeter_radar_jammer","","","","","","",""
+32,"mp_weapon_wingman","","","","","","","","","","","",""
+33,"","","","","","","","","","","","faction_aces",""
+34,"","","","","","burnmeter_at_turret_weapon","","","","","","",""
+35,"mp_weapon_g2","","","","","","","callsign_09_col","","","","",""
+36,"","","","mp_ability_holopilot,stalker","","","","","","","","",""
+37,"","mp_weapon_satchel","","","","","","","","","","",""
+38,"mp_weapon_dmr","","","","","","","","","","","",""
+39,"","","","","","","","","","","","faction_64",""
+40,"","","pas_stealth_movement","","","","pilot_loadout_9","","","","pilot_camo_skin97","",""
+41,"mp_weapon_r97","","","","","","","","","","","",""
+42,"","","","","","burnmeter_smart_pistol","","","","","","",""
+43,"mp_weapon_rocket_launcher","","","","","","","","","","","",""
+44,"","","","","","burnmeter_phase_rewind","","","","","","",""
+45,"mp_weapon_pulse_lmg","","","","","","","callsign_70_col","","","","",""
+46,"","","","","","burnmeter_hard_cover","","","","","","",""
+47,"mp_weapon_vinson","","","","","","","","","","","",""
+48,"","","","","","burnmeter_holopilot_nova","","","","","","",""
+49,"","","pas_at_hunter","","","","","","","","","faction_ares",""
+50,"","","","","","burnmeter_random_foil","pilot_loadout_10","","gc_icon_gen0","","pilot_camo_skin24","faction_marvin",""
+1,"","","","","","","","callsign_35_col","gc_icon_gen1","","pilot_camo_skin16","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","callsign_53_col_fire","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","callsign_67_col_fire","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","callsign_100_col_fire","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","pilot_camo_skin25","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","callsign_09_col_fire","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","callsign_70_col_fire","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","callsign_07_col","gc_icon_gen2","","pilot_camo_skin14","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","callsign_53_col_gold","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","callsign_67_col_gold","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","callsign_100_col_gold","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","pilot_camo_skin26","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","callsign_09_col_gold","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","callsign_70_col_gold","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","callsign_34_col","gc_icon_gen3","","pilot_camo_skin83","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","pilot_camo_skin27","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","callsign_39_col","gc_icon_gen4","","pilot_camo_skin31","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","pilot_camo_skin19","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","callsign_16_col_fire","gc_icon_gen5","","pilot_camo_skin82","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","callsign_35_col_fire","gc_icon_sgt_major,gc_icon_gen6","","pilot_camo_skin15","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","callsign_07_col_fire","gc_icon_gen7","","pilot_camo_skin17","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","callsign_34_col_fire","gc_icon_gen8","","pilot_camo_skin81","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","callsign_39_col_fire","gc_icon_gen9","","pilot_camo_skin18","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","callsign_16_col_gold,callsign_35_col_gold,callsign_07_col_gold,callsign_34_col_gold,callsign_39_col_gold","","","pilot_camo_skin30","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random"
+46,"","","","","","","","","","","","",""
+47,"","","","","","","","","","","","",""
+48,"","","","","","","","","","","","",""
+49,"","","","","","","","","","","","",""
+50,"","","","","","","","","","","","",""
+1,"","","","","","","","","","","","",""
+2,"","","","","","","","","","","","",""
+3,"","","","","","","","","","","","",""
+4,"","","","","","","","","","","","",""
+5,"","","","","","","","","","","","","random"
+6,"","","","","","","","","","","","",""
+7,"","","","","","","","","","","","",""
+8,"","","","","","","","","","","","",""
+9,"","","","","","","","","","","","",""
+10,"","","","","","","","","","","","",""
+11,"","","","","","","","","","","","",""
+12,"","","","","","","","","","","","",""
+13,"","","","","","","","","","","","",""
+14,"","","","","","","","","","","","",""
+15,"","","","","","","","","","","","","random"
+16,"","","","","","","","","","","","",""
+17,"","","","","","","","","","","","",""
+18,"","","","","","","","","","","","",""
+19,"","","","","","","","","","","","",""
+20,"","","","","","","","","","","","",""
+21,"","","","","","","","","","","","",""
+22,"","","","","","","","","","","","",""
+23,"","","","","","","","","","","","",""
+24,"","","","","","","","","","","","",""
+25,"","","","","","","","","","","","","random"
+26,"","","","","","","","","","","","",""
+27,"","","","","","","","","","","","",""
+28,"","","","","","","","","","","","",""
+29,"","","","","","","","","","","","",""
+30,"","","","","","","","","","","","",""
+31,"","","","","","","","","","","","",""
+32,"","","","","","","","","","","","",""
+33,"","","","","","","","","","","","",""
+34,"","","","","","","","","","","","",""
+35,"","","","","","","","","","","","","random"
+36,"","","","","","","","","","","","",""
+37,"","","","","","","","","","","","",""
+38,"","","","","","","","","","","","",""
+39,"","","","","","","","","","","","",""
+40,"","","","","","","","","","","","",""
+41,"","","","","","","","","","","","",""
+42,"","","","","","","","","","","","",""
+43,"","","","","","","","","","","","",""
+44,"","","","","","","","","","","","",""
+45,"","","","","","","","","","","","","random" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/unlocks_random.csv b/Northstar.CustomServers/mod/scripts/datatable/unlocks_random.csv
new file mode 100644
index 00000000..b35f17af
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/unlocks_random.csv
@@ -0,0 +1,359 @@
+ref,weight
+"callsign_02_col",15.000000
+"callsign_08_col",15.000000
+"callsign_10_col",15.000000
+"callsign_12_col",15.000000
+"callsign_17_col",15.000000
+"callsign_20_col",15.000000
+"callsign_25_col",15.000000
+"callsign_28_col",15.000000
+"callsign_32_col",15.000000
+"callsign_43_col",15.000000
+"callsign_44_col",15.000000
+"callsign_49_col",15.000000
+"callsign_50_col",15.000000
+"callsign_52_col",15.000000
+"callsign_54_col",15.000000
+"callsign_56_col",15.000000
+"callsign_58_col",15.000000
+"callsign_60_col",15.000000
+"callsign_61_col",15.000000
+"callsign_62_col",15.000000
+"callsign_63_col",15.000000
+"callsign_64_col",15.000000
+"callsign_71_col",15.000000
+"callsign_72_col",15.000000
+"callsign_73_col",15.000000
+"callsign_74_col",15.000000
+"callsign_77_col",15.000000
+"callsign_80_col",15.000000
+"callsign_81_col",15.000000
+"callsign_82_col",15.000000
+"callsign_83_col",15.000000
+"callsign_84_col",15.000000
+"callsign_85_col",15.000000
+"callsign_86_col",15.000000
+"callsign_87_col",15.000000
+"callsign_88_col",15.000000
+"callsign_89_col",15.000000
+"callsign_90_col",15.000000
+"callsign_91_col",15.000000
+"callsign_93_col",15.000000
+"callsign_95_col",15.000000
+"callsign_98_col",15.000000
+"callsign_101_col",15.000000
+"callsign_102_col",15.000000
+"callsign_02_col_prism",3.000000
+"callsign_08_col_prism",3.000000
+"callsign_10_col_prism",3.000000
+"callsign_12_col_prism",3.000000
+"callsign_17_col_prism",3.000000
+"callsign_20_col_prism",3.000000
+"callsign_25_col_prism",3.000000
+"callsign_28_col_prism",3.000000
+"callsign_32_col_prism",3.000000
+"callsign_43_col_prism",3.000000
+"callsign_44_col_prism",3.000000
+"callsign_49_col_prism",3.000000
+"callsign_50_col_prism",3.000000
+"callsign_52_col_prism",3.000000
+"callsign_54_col_prism",3.000000
+"callsign_56_col_prism",3.000000
+"callsign_58_col_prism",3.000000
+"callsign_60_col_prism",3.000000
+"callsign_61_col_prism",3.000000
+"callsign_62_col_prism",3.000000
+"callsign_63_col_prism",3.000000
+"callsign_64_col_prism",3.000000
+"callsign_71_col_prism",3.000000
+"callsign_72_col_prism",3.000000
+"callsign_73_col_prism",3.000000
+"callsign_74_col_prism",3.000000
+"callsign_77_col_prism",3.000000
+"callsign_80_col_prism",3.000000
+"callsign_81_col_prism",3.000000
+"callsign_82_col_prism",3.000000
+"callsign_83_col_prism",3.000000
+"callsign_84_col_prism",3.000000
+"callsign_85_col_prism",3.000000
+"callsign_86_col_prism",3.000000
+"callsign_87_col_prism",3.000000
+"callsign_88_col_prism",3.000000
+"callsign_89_col_prism",3.000000
+"callsign_90_col_prism",3.000000
+"callsign_91_col_prism",3.000000
+"callsign_93_col_prism",3.000000
+"callsign_95_col_prism",3.000000
+"callsign_98_col_prism",3.000000
+"callsign_101_col_prism",3.000000
+"callsign_102_col_prism",3.000000
+"execution_backshot",3.000000
+"execution_combo",3.000000
+"execution_knockout",3.000000
+"credit_award",50.000000
+"coliseum_ticket",35.000000
+"northstar.northstar_nose_art_12",5.000000
+"northstar.northstar_nose_art_13",5.000000
+"ronin.ronin_nose_art_08",5.000000
+"ronin.ronin_nose_art_12",5.000000
+"tone.tone_nose_art_11",5.000000
+"tone.tone_nose_art_12",5.000000
+"scorch.scorch_nose_art_12",5.000000
+"legion.legion_nose_art_12",5.000000
+"legion.legion_nose_art_14",5.000000
+"camo_skin04",5.000000
+"camo_skin05",5.000000
+"camo_skin06",5.000000
+"camo_skin11",5.000000
+"camo_skin12",5.000000
+"camo_skin20",5.000000
+"camo_skin21",5.000000
+"camo_skin22",5.000000
+"camo_skin23",5.000000
+"camo_skin28",5.000000
+"camo_skin29",5.000000
+"camo_skin32",5.000000
+"camo_skin33",5.000000
+"camo_skin34",5.000000
+"camo_skin35",5.000000
+"camo_skin36",5.000000
+"camo_skin37",5.000000
+"camo_skin38",5.000000
+"camo_skin39",5.000000
+"camo_skin40",5.000000
+"camo_skin41",5.000000
+"camo_skin42",5.000000
+"camo_skin43",5.000000
+"camo_skin44",5.000000
+"camo_skin45",5.000000
+"camo_skin46",5.000000
+"camo_skin47",5.000000
+"camo_skin48",5.000000
+"camo_skin49",5.000000
+"camo_skin50",5.000000
+"camo_skin51",5.000000
+"camo_skin52",5.000000
+"camo_skin53",5.000000
+"camo_skin54",5.000000
+"camo_skin68",5.000000
+"camo_skin69",5.000000
+"camo_skin70",5.000000
+"camo_skin71",5.000000
+"camo_skin72",5.000000
+"camo_skin73",5.000000
+"camo_skin74",5.000000
+"camo_skin75",5.000000
+"camo_skin76",5.000000
+"camo_skin77",5.000000
+"camo_skin78",5.000000
+"camo_skin79",5.000000
+"camo_skin80",5.000000
+"camo_skin84",5.000000
+"camo_skin86",5.000000
+"camo_skin88",5.000000
+"camo_skin89",5.000000
+"camo_skin90",5.000000
+"camo_skin95",5.000000
+"camo_skin96",5.000000
+"camo_skin98",5.000000
+"camo_skin99",5.000000
+"pilot_camo_skin04",10.000000
+"pilot_camo_skin05",10.000000
+"pilot_camo_skin06",10.000000
+"pilot_camo_skin11",10.000000
+"pilot_camo_skin12",10.000000
+"pilot_camo_skin20",10.000000
+"pilot_camo_skin21",10.000000
+"pilot_camo_skin22",10.000000
+"pilot_camo_skin23",10.000000
+"pilot_camo_skin28",10.000000
+"pilot_camo_skin29",10.000000
+"pilot_camo_skin32",10.000000
+"pilot_camo_skin33",10.000000
+"pilot_camo_skin34",10.000000
+"pilot_camo_skin35",10.000000
+"pilot_camo_skin36",10.000000
+"pilot_camo_skin37",10.000000
+"pilot_camo_skin38",10.000000
+"pilot_camo_skin39",10.000000
+"pilot_camo_skin40",10.000000
+"pilot_camo_skin41",10.000000
+"pilot_camo_skin42",10.000000
+"pilot_camo_skin43",10.000000
+"pilot_camo_skin44",10.000000
+"pilot_camo_skin45",10.000000
+"pilot_camo_skin46",10.000000
+"pilot_camo_skin47",10.000000
+"pilot_camo_skin48",10.000000
+"pilot_camo_skin49",10.000000
+"pilot_camo_skin50",10.000000
+"pilot_camo_skin51",10.000000
+"pilot_camo_skin52",10.000000
+"pilot_camo_skin53",10.000000
+"pilot_camo_skin54",10.000000
+"pilot_camo_skin68",10.000000
+"pilot_camo_skin69",10.000000
+"pilot_camo_skin70",10.000000
+"pilot_camo_skin71",10.000000
+"pilot_camo_skin72",10.000000
+"pilot_camo_skin73",10.000000
+"pilot_camo_skin74",10.000000
+"pilot_camo_skin75",10.000000
+"pilot_camo_skin76",10.000000
+"pilot_camo_skin77",10.000000
+"pilot_camo_skin78",10.000000
+"pilot_camo_skin79",10.000000
+"pilot_camo_skin80",10.000000
+"pilot_camo_skin84",10.000000
+"pilot_camo_skin86",10.000000
+"pilot_camo_skin88",10.000000
+"pilot_camo_skin89",10.000000
+"pilot_camo_skin90",10.000000
+"pilot_camo_skin95",10.000000
+"pilot_camo_skin96",10.000000
+"pilot_camo_skin98",10.000000
+"pilot_camo_skin99",10.000000
+"titan_camo_skin04",10.000000
+"titan_camo_skin05",10.000000
+"titan_camo_skin06",10.000000
+"titan_camo_skin11",10.000000
+"titan_camo_skin12",10.000000
+"titan_camo_skin20",10.000000
+"titan_camo_skin21",10.000000
+"titan_camo_skin22",10.000000
+"titan_camo_skin23",10.000000
+"titan_camo_skin28",10.000000
+"titan_camo_skin29",10.000000
+"titan_camo_skin32",10.000000
+"titan_camo_skin33",10.000000
+"titan_camo_skin34",10.000000
+"titan_camo_skin35",10.000000
+"titan_camo_skin36",10.000000
+"titan_camo_skin37",10.000000
+"titan_camo_skin38",10.000000
+"titan_camo_skin39",10.000000
+"titan_camo_skin40",10.000000
+"titan_camo_skin41",10.000000
+"titan_camo_skin42",10.000000
+"titan_camo_skin43",10.000000
+"titan_camo_skin44",10.000000
+"titan_camo_skin45",10.000000
+"titan_camo_skin46",10.000000
+"titan_camo_skin47",10.000000
+"titan_camo_skin48",10.000000
+"titan_camo_skin49",10.000000
+"titan_camo_skin50",10.000000
+"titan_camo_skin51",10.000000
+"titan_camo_skin52",10.000000
+"titan_camo_skin53",10.000000
+"titan_camo_skin54",10.000000
+"titan_camo_skin68",10.000000
+"titan_camo_skin69",10.000000
+"titan_camo_skin70",10.000000
+"titan_camo_skin71",10.000000
+"titan_camo_skin72",10.000000
+"titan_camo_skin73",10.000000
+"titan_camo_skin74",10.000000
+"titan_camo_skin75",10.000000
+"titan_camo_skin76",10.000000
+"titan_camo_skin77",10.000000
+"titan_camo_skin78",10.000000
+"titan_camo_skin79",10.000000
+"titan_camo_skin80",10.000000
+"titan_camo_skin84",10.000000
+"titan_camo_skin86",10.000000
+"titan_camo_skin88",10.000000
+"titan_camo_skin89",10.000000
+"titan_camo_skin90",10.000000
+"titan_camo_skin95",10.000000
+"titan_camo_skin96",10.000000
+"titan_camo_skin98",10.000000
+"titan_camo_skin99",10.000000
+"gc_icon_5star",15.000000
+"gc_icon_angryface",15.000000
+"gc_icon_bear",15.000000
+"gc_icon_bee",15.000000
+"gc_icon_bigcat",15.000000
+"gc_icon_bird",15.000000
+"gc_icon_bunnyskull",15.000000
+"gc_icon_chicken",15.000000
+"gc_icon_club",15.000000
+"gc_icon_corporal",15.000000
+"gc_icon_cow",15.000000
+"gc_icon_cupcake",15.000000
+"gc_icon_dataknife",15.000000
+"gc_icon_doublerainbow",15.000000
+"gc_icon_dragon",15.000000
+"gc_icon_earthworm",15.000000
+"gc_icon_fair_warning",15.000000
+"gc_icon_falcon",15.000000
+"gc_icon_fingerprint",15.000000
+"gc_icon_flying_skull",15.000000
+"gc_icon_ghostface",15.000000
+"gc_icon_hamburger",15.000000
+"gc_icon_hammer",15.000000
+"gc_icon_handprint",15.000000
+"gc_icon_hawkmoth",15.000000
+"gc_icon_heartless",15.000000
+"gc_icon_hvt",15.000000
+"gc_icon_jollyrgr",15.000000
+"gc_icon_knife",15.000000
+"gc_icon_lol",15.000000
+"gc_icon_mad_hat",15.000000
+"gc_icon_marksman",15.000000
+"gc_icon_omg",15.000000
+"gc_icon_ordnance",15.000000
+"gc_icon_pizza",15.000000
+"gc_icon_pvt",15.000000
+"gc_icon_question",15.000000
+"gc_icon_rainbow",15.000000
+"gc_icon_ram",15.000000
+"gc_icon_respawn",15.000000
+"gc_icon_saturn",15.000000
+"gc_icon_scorpion",15.000000
+"gc_icon_senior_sgt_e6",15.000000
+"gc_icon_senior_sgt",15.000000
+"gc_icon_sgt",15.000000
+"gc_icon_skull",15.000000
+"gc_icon_snake",15.000000
+"gc_icon_stab",15.000000
+"gc_icon_teabage",15.000000
+"gc_icon_widow",15.000000
+"gc_icon_witch_hat",15.000000
+"gc_icon_wizard_hat",15.000000
+"gc_icon_wraith",15.000000
+"gc_icon_wtf",15.000000
+"gc_icon_down",35.000000
+"gc_icon_joy",35.000000
+"execution_face_stab",1.000000
+"ion.ion_skin_03",1.000000
+"camo_skin08",1.000000
+"pilot_camo_skin08",1.000000
+"titan_camo_skin08",1.000000
+"ion.ion_nose_art_05",1.000000
+"callsign_tt_gameover",10.000000
+"callsign_tt_gameover_prism",1.000000
+"callsign_tt_guardtheflag",10.000000
+"callsign_tt_guardtheflag_prism",1.000000
+"callsign_tt_megamarvin",10.000000
+"callsign_tt_megamarvin_prism",1.000000
+"callsign_tt_nessievault",10.000000
+"callsign_tt_nessievault_prism",1.000000
+"callsign_tt_nsbt",10.000000
+"callsign_tt_nsbt_prism",1.000000
+"callsign_tt_protocol2",10.000000
+"callsign_tt_protocol2_prism",1.000000
+"callsign_tt_rekt",10.000000
+"callsign_tt_rekt_prism",1.000000
+"callsign_tt_titantoons",10.000000
+"callsign_tt_titantoons_prism",1.000000
+"callsign_eat_ion",1.000000
+"callsign_eat_legion",1.000000
+"callsign_eat_northstar",1.000000
+"callsign_eat_ronin",1.000000
+"callsign_eat_scorch",1.000000
+"callsign_eat_tone",1.000000
+"callsign_126_col_prism",1.000000
+"callsign_127_col_prism",1.000000
+"callsign_130_col_prism",1.000000
+"callsign_131_col_prism",1.000000 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/unlocks_titan_level.csv b/Northstar.CustomServers/mod/scripts/datatable/unlocks_titan_level.csv
new file mode 100644
index 00000000..24f54fea
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/unlocks_titan_level.csv
@@ -0,0 +1,402 @@
+titanLevel,ion,scorch,northstar,ronin,tone,legion,vanguard,end
+"","ion","scorch","northstar","ronin","tone","legion","vanguard","END"
+"1","pas_ion_weapon,pas_mobility_dash_capacity,pas_enhanced_titan_ai,pas_auto_eject,pas_bubbleshield,titan_camo_skin00,camo_skin00,ion_nose_art_none,pas_vanguard_core1, pas_vanguard_core4,pas_vanguard_core7,execution_random_0,execution_ion,execution_ion_prime","pas_scorch_weapon,pas_mobility_dash_capacity,pas_enhanced_titan_ai,pas_auto_eject,pas_bubbleshield,titan_camo_skin00,camo_skin00,scorch_nose_art_none,pas_vanguard_core1, pas_vanguard_core4,pas_vanguard_core7,execution_random_1,execution_scorch,execution_scorch_prime","pas_northstar_weapon,pas_mobility_dash_capacity,pas_enhanced_titan_ai,pas_auto_eject,pas_bubbleshield,titan_camo_skin00,camo_skin00,northstar_nose_art_none,pas_vanguard_core1, pas_vanguard_core4,pas_vanguard_core7,execution_random_2,execution_northstar,execution_northstar_prime","pas_ronin_weapon,pas_mobility_dash_capacity,pas_enhanced_titan_ai,pas_auto_eject,pas_bubbleshield,titan_camo_skin00,camo_skin00,ronin_nose_art_none,pas_vanguard_core1, pas_vanguard_core4,pas_vanguard_core7,execution_random_3,execution_ronin,execution_ronin_prime","pas_tone_weapon,pas_mobility_dash_capacity,pas_enhanced_titan_ai,pas_auto_eject,pas_bubbleshield,titan_camo_skin00,camo_skin00,tone_nose_art_none,pas_vanguard_core1, pas_vanguard_core4,pas_vanguard_core7,execution_random_4,execution_tone,execution_tone_prime","pas_legion_weapon,pas_mobility_dash_capacity,pas_enhanced_titan_ai,pas_auto_eject,pas_bubbleshield,titan_camo_skin00,camo_skin00,legion_nose_art_none,pas_vanguard_core1, pas_vanguard_core4,pas_vanguard_core7,execution_random_5,execution_legion,execution_legion_prime","pas_vanguard_shield,pas_mobility_dash_capacity,pas_enhanced_titan_ai,pas_auto_eject,pas_bubbleshield,pas_vanguard_core1,pas_vanguard_core4,pas_vanguard_core7,titan_camo_skin00,camo_skin00,vanguard_nose_art_none,execution_random_6,execution_vanguard",""
+"2","pas_hyper_core","pas_hyper_core","pas_hyper_core","pas_hyper_core","pas_hyper_core","pas_hyper_core","pas_hyper_core",""
+"3","pas_build_up_nuclear_core","pas_build_up_nuclear_core","pas_build_up_nuclear_core","pas_build_up_nuclear_core","pas_build_up_nuclear_core","pas_build_up_nuclear_core","pas_vanguard_core2,pas_build_up_nuclear_core",""
+"4","pas_anti_rodeo,ion_nose_art_14","pas_anti_rodeo,scorch_nose_art_11","pas_anti_rodeo,northstar_nose_art_11","pas_anti_rodeo,ronin_nose_art_14","pas_anti_rodeo,tone_nose_art_13","pas_anti_rodeo,legion_nose_art_13","pas_anti_rodeo,vanguard_nose_art_01",""
+"5","pas_warpfall,random","pas_warpfall,random","pas_warpfall,random","pas_warpfall,random","pas_warpfall,random","pas_warpfall,random","pas_warpfall,pas_vanguard_core5,random",""
+"6","pas_ion_tripwire,titan_camo_skin01","pas_scorch_selfdmg,titan_camo_skin01","pas_northstar_cluster,titan_camo_skin01","pas_ronin_arcwave,titan_camo_skin01","pas_tone_wall,titan_camo_skin01","pas_legion_smartcore,titan_camo_skin01","pas_vanguard_coremeter,titan_camo_skin01",""
+"7","pas_ion_vortex","pas_scorch_shield","pas_northstar_trap","pas_ronin_phase","pas_tone_sonar","pas_legion_gunshield","pas_vanguard_core8,pas_vanguard_rearm",""
+"8","pas_ion_lasercannon,camo_skin01","pas_scorch_firewall,camo_skin01","pas_northstar_flightcore,camo_skin01","pas_ronin_swordcore,camo_skin01","pas_tone_rockets,camo_skin01","pas_legion_spinup,camo_skin01","pas_vanguard_core3,pas_vanguard_doom,camo_skin01",""
+"9","titan_camo_skin03,pas_ion_weapon_ads","titan_camo_skin03,pas_scorch_flamecore","titan_camo_skin03,pas_northstar_optics","titan_camo_skin03,pas_ronin_autoshift","titan_camo_skin03,pas_tone_burst","titan_camo_skin03,pas_legion_chargeshot","titan_camo_skin03,pas_vanguard_core6",""
+"10","gc_icon_cateye,ion_nose_art_01","gc_icon_fireball,scorch_nose_art_02","gc_icon_stinger,northstar_nose_art_08","gc_icon_sword,ronin_nose_art_02","gc_icon_crosshair,tone_nose_art_14","gc_icon_bullet,legion_nose_art_09","gc_icon_monarch,vanguard_nose_art_02,pas_vanguard_core9",""
+"11","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03",""
+"12","titan_camo_skin02","titan_camo_skin02","titan_camo_skin02","titan_camo_skin02","titan_camo_skin02","titan_camo_skin02","titan_camo_skin02",""
+"13","","","","","","","",""
+"14","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02",""
+"15","titan_camo_skin97","titan_camo_skin97","titan_camo_skin97","titan_camo_skin97","titan_camo_skin97","titan_camo_skin97","titan_camo_skin97",""
+"16","","","","","","","",""
+"17","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97",""
+"18","titan_camo_skin24","titan_camo_skin24","titan_camo_skin24","titan_camo_skin24","titan_camo_skin24","titan_camo_skin24","titan_camo_skin24",""
+"19","","","","","","","",""
+"20","camo_skin24,ion_nose_art_08","camo_skin24,scorch_nose_art_03","camo_skin24,northstar_nose_art_06","camo_skin24,ronin_nose_art_11","camo_skin24,tone_nose_art_04","camo_skin24,legion_nose_art_06","camo_skin24,vanguard_nose_art_03",""
+"1","titan_camo_skin16,callsign_24_col","titan_camo_skin16,callsign_47_col","titan_camo_skin16,callsign_36_col","titan_camo_skin16,callsign_45_col","titan_camo_skin16,callsign_68_col","titan_camo_skin16,callsign_26_col","titan_camo_skin16,callsign_165_col",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","camo_skin16","camo_skin16","camo_skin16","camo_skin16","camo_skin16","camo_skin16","camo_skin16",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","titan_camo_skin25","titan_camo_skin25","titan_camo_skin25","titan_camo_skin25","titan_camo_skin25","titan_camo_skin25","titan_camo_skin25",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_04","scorch_nose_art_05","northstar_nose_art_04","ronin_nose_art_05","tone_nose_art_03","legion_nose_art_03","vanguard_nose_art_04",""
+"1","titan_camo_skin14,callsign_24_col_fire","titan_camo_skin14,callsign_47_col_fire","titan_camo_skin14,callsign_36_col_fire","titan_camo_skin14,callsign_45_col_fire","titan_camo_skin14,callsign_68_col_fire","titan_camo_skin14,callsign_26_col_fire","titan_camo_skin14,callsign_165_col_fire",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","titan_camo_skin26","titan_camo_skin26","titan_camo_skin26","titan_camo_skin26","titan_camo_skin26","titan_camo_skin26","titan_camo_skin26",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_03","scorch_nose_art_01","northstar_nose_art_01","ronin_nose_art_06","tone_nose_art_07","legion_nose_art_10","vanguard_nose_art_05",""
+"1","titan_camo_skin83,callsign_24_col_gold","titan_camo_skin83,callsign_47_col_gold","titan_camo_skin83,callsign_36_col_gold","titan_camo_skin83,callsign_45_col_gold","titan_camo_skin83,callsign_68_col_gold","titan_camo_skin83,callsign_26_col_gold","titan_camo_skin83,callsign_165_col_gold",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","titan_camo_skin27","titan_camo_skin27","titan_camo_skin27","titan_camo_skin27","titan_camo_skin27","titan_camo_skin27","titan_camo_skin27",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_12","scorch_nose_art_13","northstar_nose_art_09","ronin_nose_art_04","tone_nose_art_02","legion_nose_art_02","vanguard_nose_art_06",""
+"1","titan_camo_skin31","titan_camo_skin31","titan_camo_skin31","titan_camo_skin31","titan_camo_skin31","titan_camo_skin31","titan_camo_skin31",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","titan_camo_skin19","titan_camo_skin19","titan_camo_skin19","titan_camo_skin19","titan_camo_skin19","titan_camo_skin19","titan_camo_skin19",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_13","scorch_nose_art_10","northstar_nose_art_03","ronin_nose_art_09","tone_nose_art_09","legion_nose_art_01","vanguard_nose_art_07",""
+"1","titan_camo_skin82","titan_camo_skin82","titan_camo_skin82","titan_camo_skin82","titan_camo_skin82","titan_camo_skin82","titan_camo_skin82",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","titan_camo_skin15","titan_camo_skin15","titan_camo_skin15","titan_camo_skin15","titan_camo_skin15","titan_camo_skin15","titan_camo_skin15",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_02","scorch_nose_art_04","northstar_nose_art_02","ronin_nose_art_01","tone_nose_art_01","legion_nose_art_08","",""
+"1","titan_camo_skin17","titan_camo_skin17","titan_camo_skin17","titan_camo_skin17","titan_camo_skin17","titan_camo_skin17","titan_camo_skin17",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_06","scorch_nose_art_14","northstar_nose_art_10","ronin_nose_art_13","tone_nose_art_05","legion_nose_art_11","vanguard_nose_art_08",""
+"1","titan_camo_skin81","titan_camo_skin81","titan_camo_skin81","titan_camo_skin81","titan_camo_skin81","titan_camo_skin81","titan_camo_skin81",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_11","scorch_nose_art_09","northstar_nose_art_05","ronin_nose_art_10","tone_nose_art_08","legion_nose_art_04","",""
+"1","titan_camo_skin18","titan_camo_skin18","titan_camo_skin18","titan_camo_skin18","titan_camo_skin18","titan_camo_skin18","titan_camo_skin18",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_10","scorch_nose_art_08","northstar_nose_art_14","ronin_nose_art_03","tone_nose_art_06","legion_nose_art_05","vanguard_nose_art_09",""
+"1","titan_camo_skin30","titan_camo_skin30","titan_camo_skin30","titan_camo_skin30","titan_camo_skin30","titan_camo_skin30","titan_camo_skin30",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","ion_nose_art_09","","","","","","",""
+"1","","","","","","","",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","",""
+"1","random","random","random","random","random","random","random",""
+"2","","","","","","","",""
+"3","","","","","","","",""
+"4","","","","","","","",""
+"5","","","","","","","",""
+"6","","","","","","","",""
+"7","","","","","","","",""
+"8","","","","","","","",""
+"9","","","","","","","",""
+"10","","","","","","","",""
+"11","","","","","","","",""
+"12","","","","","","","",""
+"13","","","","","","","",""
+"14","","","","","","","",""
+"15","","","","","","","",""
+"16","","","","","","","",""
+"17","","","","","","","",""
+"18","","","","","","","",""
+"19","","","","","","","",""
+"20","","","","","","","","" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/unlocks_weapon_level_pilot.csv b/Northstar.CustomServers/mod/scripts/datatable/unlocks_weapon_level_pilot.csv
new file mode 100644
index 00000000..5c881ac5
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/unlocks_weapon_level_pilot.csv
@@ -0,0 +1,383 @@
+weaponLevel,weap1,weap2,weap3,weap4,weap5,weap6,weap7,weap8,weap9,weap10,weap11,weap12,weap13,weap14,weap15,weap16,weap17,weap18,weap19,weap20,weap21,weap22,weap23,weap24,weap25,weap26,weap27,weap28,weap29,weap30,end
+"","mp_weapon_alternator_smg","mp_weapon_arc_launcher","mp_weapon_autopistol","mp_weapon_car","mp_weapon_defender","mp_weapon_dmr","mp_weapon_doubletake","mp_weapon_epg","mp_weapon_esaw","mp_weapon_g2","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_semipistol","mp_weapon_shotgun","mp_weapon_shotgun_pistol","mp_weapon_smr","mp_weapon_sniper","mp_weapon_softball","mp_weapon_vinson","mp_weapon_wingman","mp_weapon_wingman_n","mp_weapon_rspn101_og","END"
+"1","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen","camo_skin00, pro_screen",""
+"2","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo","extended_ammo",""
+"3","hcog","pas_fast_reload","silencer","holosight","quick_charge","scope_4x","scope_4x","pas_run_and_gun","aog","hcog","hcog","holosight","aog","aog","holosight","pas_fast_reload","pas_run_and_gun","holosight","pas_fast_reload","hcog","silencer","holosight","silencer","pas_run_and_gun","scope_4x","pas_run_and_gun","hcog","silencer","ricochet","hcog",""
+"4","pas_run_and_gun","pas_fast_ads","pas_run_and_gun","pas_run_and_gun","pas_fast_ads","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun","pas_fast_ads","pas_fast_reload","pas_run_and_gun","pas_fast_ads","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun","pas_run_and_gun",""
+"5","pas_fast_reload","pas_fast_swap","pas_fast_reload","pas_fast_reload","pas_fast_swap","pas_fast_ads","pas_fast_ads","pas_fast_ads","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_fast_swap","pas_fast_ads","pas_fast_reload","pas_fast_swap","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_fast_ads","pas_fast_ads","pas_fast_ads","pas_fast_reload","pas_fast_reload","pas_fast_reload","pas_fast_reload",""
+"6","redline_sight","secondarymod2","pas_fast_ads","redline_sight","secondarymod2","pas_fast_swap","pas_fast_swap","pas_fast_swap","redline_sight","redline_sight","redline_sight","redline_sight","redline_sight","redline_sight","redline_sight","secondarymod2","pas_fast_swap","redline_sight","secondarymod2","redline_sight","pas_fast_ads","redline_sight","pas_fast_ads","pas_fast_swap","pas_fast_swap","pas_fast_swap","redline_sight","pas_fast_ads","pas_fast_ads","redline_sight",""
+"7","pas_fast_ads","","tactical_cdr_on_kill","pas_fast_ads","","tactical_cdr_on_kill","tactical_cdr_on_kill","tactical_cdr_on_kill","pas_fast_ads","pas_fast_ads","pas_fast_ads","pas_fast_ads","pas_fast_ads","pas_fast_ads","pas_fast_ads","","tactical_cdr_on_kill","pas_fast_ads","","pas_fast_ads","tactical_cdr_on_kill","pas_fast_ads","tactical_cdr_on_kill","tactical_cdr_on_kill","tactical_cdr_on_kill","tactical_cdr_on_kill","pas_fast_ads","tactical_cdr_on_kill","tactical_cdr_on_kill","pas_fast_ads",""
+"8","pas_fast_swap,random","random","secondarymod2,random","pas_fast_swap,random","random","threat_scope,random","threat_scope,random","random,primarymod2","pas_fast_swap,random","pas_fast_swap,random","pas_fast_swap,random","pas_fast_swap,random","pas_fast_swap,random","pas_fast_swap,random","pas_fast_swap,random","random","primarymod2,random","pas_fast_swap,random","random","pas_fast_swap,random","secondarymod2,random","pas_fast_swap,random","primarymod2,random","primarymod2,random","threat_scope,random","primarymod2,random","pas_fast_swap,random","secondarymod2,random","primarymod2,random","pas_fast_swap,random",""
+"9","tactical_cdr_on_kill","","","tactical_cdr_on_kill","","primarymod2","ricochet","","tactical_cdr_on_kill","tactical_cdr_on_kill","tactical_cdr_on_kill","tactical_cdr_on_kill","tactical_cdr_on_kill","tactical_cdr_on_kill","tactical_cdr_on_kill","","","tactical_cdr_on_kill","","tactical_cdr_on_kill","","tactical_cdr_on_kill","","","ricochet","","tactical_cdr_on_kill","","","tactical_cdr_on_kill",""
+"10","threat_scope,camo_skin01","camo_skin01","camo_skin01","threat_scope,camo_skin01","camo_skin01","camo_skin01","primarymod2,camo_skin01","camo_skin01","threat_scope,camo_skin01","threat_scope,camo_skin01","threat_scope,camo_skin01","threat_scope,camo_skin01","threat_scope,camo_skin01","threat_scope,camo_skin01","threat_scope,camo_skin01","camo_skin01","camo_skin01","threat_scope,camo_skin01","camo_skin01","threat_scope,camo_skin01","camo_skin01","threat_scope,camo_skin01","camo_skin01","camo_skin01","primarymod2,camo_skin01","camo_skin01","threat_scope,camo_skin01","camo_skin01","camo_skin01","threat_scope,camo_skin01",""
+"11","primarymod2","","","primarymod2","","","","","primarymod2","primarymod2","primarymod2","primarymod2","primarymod2","primarymod2","primarymod2","","","primarymod2","","primarymod2","","primarymod2","","","","","primarymod2","","","primarymod2",""
+"12","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03","camo_skin03",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02","camo_skin02",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97","camo_skin97",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24","camo_skin24",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","gc_icon_yuckface","gc_icon_raincloud","gc_icon_diamond","gc_icon_wasp","gc_icon_bullseye","gc_icon_fin","gc_icon_dice","gc_icon_comet","gc_icon_heart","gc_icon_ramskull","gc_icon_waves","gc_icon_lightning","gc_icon_hawk","gc_icon_star","gc_icon_paw","gc_icon_frag","gc_icon_moon","gc_icon_assault","gc_icon_rocket","gc_icon_atom","gc_icon_spade","gc_icon_8ball","gc_icon_clawmark","gc_icon_bomb_02","gc_icon_happyface","gc_icon_bomb_01","gc_icon_medic","gc_icon_dragonfly","gc_icon_b3_wing","gc_icon_mushroom",""
+"1","primarymod3,camo_skin16","secondarymod3,camo_skin16","secondarymod3,camo_skin16","primarymod3,camo_skin16","secondarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","secondarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","secondarymod3,camo_skin16","primarymod3,camo_skin16","secondarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16","secondarymod3,camo_skin16","primarymod3,camo_skin16","primarymod3,camo_skin16",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","callsign_13_col","callsign_76_col","callsign_29_col","callsign_55_col","callsign_66_col","callsign_30_col","callsign_21_col","callsign_94_col","callsign_15_col","callsign_31_col","callsign_40_col","callsign_18_col","callsign_27_col","callsign_22_col","callsign_48_col","callsign_42_col","callsign_78_col","callsign_75_col","callsign_92_col","callsign_03_col","callsign_37_col","callsign_79_col","callsign_51_col","callsign_11_col","callsign_57_col","callsign_59_col","callsign_69_col","callsign_19_col","callsign_139_col","callsign_128_col",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25","camo_skin25",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14","camo_skin14",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","callsign_13_col_fire","callsign_76_col_fire","callsign_29_col_fire","callsign_55_col_fire","callsign_66_col_fire","callsign_30_col_fire","callsign_21_col_fire","callsign_94_col_fire","callsign_15_col_fire","callsign_31_col_fire","callsign_40_col_fire","callsign_18_col_fire","callsign_27_col_fire","callsign_22_col_fire","callsign_48_col_fire","callsign_42_col_fire","callsign_78_col_fire","callsign_75_col_fire","callsign_92_col_fire","callsign_03_col_fire","callsign_37_col_fire","callsign_79_col_fire","callsign_51_col_fire","callsign_11_col_fire","callsign_57_col_fire","callsign_59_col_fire","callsign_69_col_fire","callsign_19_col_fire","callsign_139_col_fire","callsign_128_col_fire",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26","camo_skin26",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83","camo_skin83",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","callsign_13_col_gold","callsign_76_col_gold","callsign_29_col_gold","callsign_55_col_gold","callsign_66_col_gold","callsign_30_col_gold","callsign_21_col_gold","callsign_94_col_gold","callsign_15_col_gold","callsign_31_col_gold","callsign_40_col_gold","callsign_18_col_gold","callsign_27_col_gold","callsign_22_col_gold","callsign_48_col_gold","callsign_42_col_gold","callsign_78_col_gold","callsign_75_col_gold","callsign_92_col_gold","callsign_03_col_gold","callsign_37_col_gold","callsign_79_col_gold","callsign_51_col_gold","callsign_11_col_gold","callsign_57_col_gold","callsign_59_col_gold","callsign_69_col_gold","callsign_19_col_gold","callsign_139_col_gold","callsign_128_col_gold",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27","camo_skin27",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31","camo_skin31",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19","camo_skin19",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82","camo_skin82",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15","camo_skin15",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17","camo_skin17",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81","camo_skin81",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18","camo_skin18",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30","camo_skin30",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random",""
+"2","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"3","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"4","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"5","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"6","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"7","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"8","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"9","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"10","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"11","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"12","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"13","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"14","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"15","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"16","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"17","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"18","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"19","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"20","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
+"1","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","random","" \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/weapon_skins.csv b/Northstar.CustomServers/mod/scripts/datatable/weapon_skins.csv
new file mode 100644
index 00000000..0f074882
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/weapon_skins.csv
@@ -0,0 +1,35 @@
+ref,weaponRef,image,name,cost,skinIndex,skinType
+"skin_rspn101_wasteland","mp_weapon_rspn101","rui/weapon_skin_swatches/swatch_wasteland","#SKIN_WASTELAND",0,8,1
+"skin_g2_masterwork","mp_weapon_g2","rui/weapon_skin_swatches/swatch_masterwork","#SKIN_MASTERWORK",0,6,1
+"skin_vinson_blue_fade","mp_weapon_vinson","rui/weapon_skin_swatches/swatch_blue_fade","#SKIN_BLUE_FADE",0,7,1
+"skin_car_crimson_fury","mp_weapon_car","rui/weapon_skin_swatches/swatch_crimson_fury","#SKIN_CRIMSON_FURY",0,6,1
+"skin_alternator_patriot","mp_weapon_alternator_smg","rui/weapon_skin_swatches/swatch_patriot","#SKIN_PATRIOT",0,2,1
+"skin_shotgun_badlands","mp_weapon_shotgun","rui/weapon_skin_swatches/swatch_badlands","#SKIN_BADLANDS",0,4,1
+"skin_wingman_aqua_fade","mp_weapon_wingman","rui/weapon_skin_swatches/swatch_aqua_fade","#SKIN_AQUA_FADE",0,7,1
+"skin_rocket_launcher_psych_spectre","mp_weapon_rocket_launcher","rui/weapon_skin_swatches/swatch_psych_spectre","#SKIN_PHANTOM",0,2,1
+"skin_rspn101_patriot","mp_weapon_rspn101","rui/weapon_skin_swatches/swatch_patriot","#SKIN_PATRIOT",0,9,1
+"skin_hemlok_mochi","mp_weapon_hemlok","rui/weapon_skin_swatches/swatch_mochi","#SKIN_MOCHI",0,5,1
+"skin_r97_purple_fade","mp_weapon_r97","rui/weapon_skin_swatches/swatch_purple_fade","#SKIN_PURPLE_FADE",0,7,1
+"skin_kraber_masterwork","mp_weapon_sniper","rui/weapon_skin_swatches/swatch_masterwork","#SKIN_MASTERWORK",0,2,1
+"skin_spitfire_lead_farmer","mp_weapon_lmg","rui/weapon_skin_swatches/swatch_lead_farmer","#SKIN_LEAD_FARMER",0,8,1
+"skin_devotion_rspn_customs","mp_weapon_esaw","rui/weapon_skin_swatches/swatch_rspn_customs","#SKIN_RSPN_CUSTOMS",0,8,1
+"skin_mozambique_crimson_fury","mp_weapon_shotgun_pistol","rui/weapon_skin_swatches/swatch_crimson_fury","#SKIN_CRIMSON_FURY",0,7,1
+"skin_thunderbolt_8bit","mp_weapon_arc_launcher","rui/weapon_skin_swatches/swatch_8_bit","#SKIN_8_BIT",0,2,1
+"skin_lstar_heatsink","mp_weapon_lstar","rui/weapon_skin_swatches/swatch_heatsink","#SKIN_HEAT_SINK",0,6,1
+"skin_mastiff_crimson_fury","mp_weapon_mastiff","rui/weapon_skin_swatches/swatch_crimson_fury","#SKIN_CRIMSON_FURY",0,7,1
+"skin_sidewinder_masterwork","mp_weapon_smr","rui/weapon_skin_swatches/swatch_masterwork","#SKIN_MASTERWORK",0,2,1
+"skin_rspn101_halloween","mp_weapon_rspn101","rui/weapon_skin_swatches/swatch_rspn101_halloween","#SKIN_HALLOWEEN",0,10,1
+"skin_car_halloween","mp_weapon_car","rui/weapon_skin_swatches/swatch_car_halloween","#SKIN_HALLOWEEN",0,8,1
+"skin_spitfire_halloween","mp_weapon_lmg","rui/weapon_skin_swatches/swatch_spitfire_halloween","#SKIN_HALLOWEEN",0,9,1
+"skin_rspn101_og_blue_fade","mp_weapon_rspn101_og","rui/weapon_skin_swatches/swatch_blue_fade","#SKIN_BLUE_FADE",0,7,1
+"skin_vinson_badlands","mp_weapon_vinson","rui/weapon_skin_swatches/swatch_badlands_flatline","#SKIN_BADLANDS",0,8,1
+"skin_volt_heatsink","mp_weapon_hemlok_smg","rui/weapon_skin_swatches/swatch_heatsink","#SKIN_HEAT_SINK",0,6,1
+"skin_alternator_headhunter","mp_weapon_alternator_smg","rui/weapon_skin_swatches/swatch_headhunter","#SKIN_HEADHUNTER",0,5,1
+"skin_softball_masterwork","mp_weapon_softball","rui/weapon_skin_swatches/swatch_masterwork","#SKIN_MASTERWORK",0,2,1
+"skin_epg_mrvn","mp_weapon_epg","rui/weapon_skin_swatches/swatch_mrvn","#SKIN_MRVN",0,5,1
+"skin_dmr_phantom","mp_weapon_dmr","rui/weapon_skin_swatches/swatch_psych_spectre","#SKIN_PHANTOM",0,3,1
+"skin_doubletake_masterwork","mp_weapon_doubletake","rui/weapon_skin_swatches/swatch_masterwork","#SKIN_MASTERWORK",0,2,1
+"skin_g2_purple_fade","mp_weapon_g2","rui/weapon_skin_swatches/swatch_purple_fade","#SKIN_PURPLE_FADE",0,7,1
+"skin_coldwar_heatsink","mp_weapon_pulse_lmg","rui/weapon_skin_swatches/swatch_heatsink","#SKIN_HEAT_SINK",0,6,1
+"skin_r97_sky","mp_weapon_r97","rui/weapon_skin_swatches/swatch_sky","#SKIN_SKY",0,5,1
+"skin_rspn101_crimson_fury","mp_weapon_rspn101","rui/weapon_skin_swatches/swatch_crimson_fury","#SKIN_CRIMSON_FURY",0,7,1 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/xp_per_faction_level.csv b/Northstar.CustomServers/mod/scripts/datatable/xp_per_faction_level.csv
new file mode 100644
index 00000000..e28cf8c0
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/xp_per_faction_level.csv
@@ -0,0 +1,6 @@
+level,xpPerLevel
+1,2
+2,3
+3,4
+4,5
+5,5 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/xp_per_fd_titan_level.csv b/Northstar.CustomServers/mod/scripts/datatable/xp_per_fd_titan_level.csv
new file mode 100644
index 00000000..28c33f5e
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/xp_per_fd_titan_level.csv
@@ -0,0 +1,25 @@
+level,xpPerLevel
+1,12
+2,12
+3,14
+4,14
+5,16
+6,16
+7,18
+8,18
+9,20
+10,20
+11,20
+12,20
+13,20
+14,20
+15,20
+16,20
+17,20
+18,20
+19,20
+20,20
+21,20
+22,20
+23,20
+24,20 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/xp_per_player_level.csv b/Northstar.CustomServers/mod/scripts/datatable/xp_per_player_level.csv
new file mode 100644
index 00000000..36e52bc8
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/xp_per_player_level.csv
@@ -0,0 +1,51 @@
+level,xpPerLevel
+1,3
+2,4
+3,5
+4,6
+5,7
+6,8
+7,9
+8,10
+9,10
+10,10
+11,10
+12,10
+13,10
+14,10
+15,10
+16,10
+17,10
+18,10
+19,10
+20,10
+21,10
+22,10
+23,10
+24,10
+25,10
+26,10
+27,10
+28,10
+29,10
+30,10
+31,10
+32,10
+33,10
+34,10
+35,10
+36,10
+37,10
+38,10
+39,10
+40,10
+41,10
+42,10
+43,10
+44,10
+45,10
+46,10
+47,10
+48,10
+49,10
+50,10 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/xp_per_titan_level.csv b/Northstar.CustomServers/mod/scripts/datatable/xp_per_titan_level.csv
new file mode 100644
index 00000000..d607f589
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/xp_per_titan_level.csv
@@ -0,0 +1,21 @@
+level,xpPerLevel
+1,3
+2,5
+3,7
+4,10
+5,10
+6,10
+7,10
+8,10
+9,10
+10,10
+11,10
+12,10
+13,10
+14,10
+15,10
+16,10
+17,10
+18,10
+19,10
+20,10 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/datatable/xp_per_weapon_level.csv b/Northstar.CustomServers/mod/scripts/datatable/xp_per_weapon_level.csv
new file mode 100644
index 00000000..a6859594
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/datatable/xp_per_weapon_level.csv
@@ -0,0 +1,21 @@
+level,default,sniper,pistol,antititan
+1,3,2,2,2
+2,5,3,3,3
+3,7,4,4,4
+4,10,5,5,4
+5,10,5,5,4
+6,10,5,5,4
+7,10,5,5,4
+8,10,5,5,4
+9,10,5,5,4
+10,10,5,5,4
+11,10,5,5,4
+12,10,5,5,4
+13,10,5,5,4
+14,10,5,5,4
+15,10,5,5,4
+16,10,5,5,4
+17,10,5,5,4
+18,10,5,5,4
+19,10,5,5,4
+20,10,5,5,4 \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_bubble_shield.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_bubble_shield.gnut
index 30758bec..f09ef957 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_bubble_shield.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_bubble_shield.gnut
@@ -223,6 +223,7 @@ entity function CreateBubbleShieldWithSettings( int team, vector origin, vector
entity neutralColoredFX = StartParticleEffectInWorld_ReturnEntity( BUBBLE_SHIELD_FX_PARTICLE_SYSTEM_INDEX, coloredFXOrigin, <0, 0, 0> )
SetTeam( neutralColoredFX, team )
bubbleShieldDotS.neutralColoredFX <- neutralColoredFX
+ neutralColoredFX.DisableHibernation()
bubbleShieldFXs.append( neutralColoredFX )
}
else
@@ -232,11 +233,13 @@ entity function CreateBubbleShieldWithSettings( int team, vector origin, vector
SetTeam( friendlyColoredFX, team )
friendlyColoredFX.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
EffectSetControlPointVector( friendlyColoredFX, 1, FRIENDLY_COLOR_FX )
+ friendlyColoredFX.DisableHibernation()
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 )
+ enemyColoredFX.DisableHibernation()
bubbleShieldDotS.friendlyColoredFX <- friendlyColoredFX
bubbleShieldDotS.enemyColoredFX <- enemyColoredFX
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_chat.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_chat.gnut
index 97ed959c..44836bc9 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_chat.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_chat.gnut
@@ -25,26 +25,26 @@ void function Chat_PrivateMessage(entity fromPlayer, entity toPlayer, string tex
}
// Broadcasts a message from the server to all players.
-void function Chat_ServerBroadcast(string text)
+void function Chat_ServerBroadcast(string text, bool withServerTag = true)
{
NSBroadcastMessage(
-1,
-1,
text,
- false,
+ !withServerTag,
false,
eChatMessageType.CHAT
)
}
// Sends a message from the server to one player. Will be shown as a whisper if whisper is set.
-void function Chat_ServerPrivateMessage(entity toPlayer, string text, bool whisper)
+void function Chat_ServerPrivateMessage(entity toPlayer, string text, bool whisper, bool withServerTag = true)
{
NSBroadcastMessage(
-1,
toPlayer.GetPlayerIndex(),
text,
- false,
+ !withServerTag,
false,
whisper ? eChatMessageType.WHISPER : eChatMessageType.CHAT
)
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut
index d2621db3..3704b5cc 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_codecallbacks_common.gnut
@@ -46,6 +46,8 @@ global function CodeCallback_OnEntityChangedTeam
global function AddEntityCallback_OnDamaged
global function RemoveEntityCallback_OnDamaged
+global function AddEntityCallback_OnFinalDamaged
+global function RemoveEntityCallback_OnFinalDamaged
global function AddEntityCallback_OnPostDamaged
global function RemoveEntityCallback_OnPostDamaged
global function AddEntityCallback_OnKilled
@@ -121,6 +123,13 @@ void function CodeCallback_DamageEntity( entity ent, var damageInfo )
printt( " after class damage final callbacks:", DamageInfo_GetDamage( damageInfo ) )
#endif
+ foreach ( callbackFunc in ent.e.entFinalDamageCallbacks )
+ callbackFunc( ent, damageInfo )
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after AddEntityCallback_OnFinalDamaged 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 )
{
@@ -566,7 +575,23 @@ void function RemoveEntityCallback_OnDamaged( entity ent, void functionref( enti
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 )
+ if ( ent.e.entDamageCallbacks.len() == 0 && ent.e.entFinalDamageCallbacks.len() == 0 && ent.e.entPostDamageCallbacks.len() == 0 )
+ ent.SetDamageNotifications( false )
+}
+
+void function AddEntityCallback_OnFinalDamaged( entity ent, void functionref( entity ent, var damageInfo ) callbackFunc )
+{
+ Assert( !ent.e.entFinalDamageCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " to entity" )
+
+ ent.SetDamageNotifications( true )
+ ent.e.entFinalDamageCallbacks.append( callbackFunc )
+}
+
+void function RemoveEntityCallback_OnFinalDamaged( entity ent, void functionref( entity ent, var damageInfo ) callbackFunc )
+{
+ ent.e.entFinalDamageCallbacks.fastremovebyvalue( callbackFunc )
+
+ if ( ent.e.entFinalDamageCallbacks.len() == 0 && ent.e.entPostDamageCallbacks.len() == 0 && ent.e.entDamageCallbacks.len() == 0 )
ent.SetDamageNotifications( false )
}
@@ -585,7 +610,7 @@ void function RemoveEntityCallback_OnPostDamaged( entity ent, void functionref(
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 )
+ if ( ent.e.entPostDamageCallbacks.len() == 0 && ent.e.entDamageCallbacks.len() == 0 && ent.e.entFinalDamageCallbacks.len() == 0 )
ent.SetDamageNotifications( false )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut
index 4a7f8189..ee21116d 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_custom_codecallbacks.gnut
@@ -1,7 +1,10 @@
untyped
global function AddCallback_OnReceivedSayTextMessage
-global function NSSetupChathooksServer
+
+// this is global due to squirrel bridge v3 making native not be able to find non-global funcs properly
+// temp fix (surely it will get replaced), do not use this function please
+global function CServerGameDLL_ProcessMessageStartThread
global struct ClServer_MessageStruct {
string message
@@ -53,7 +56,3 @@ void function AddCallback_OnReceivedSayTextMessage( ClServer_MessageStruct funct
{
NsCustomCallbacks.OnReceivedSayTextMessageCallbacks.append(callbackFunc)
}
-
-void function NSSetupChathooksServer() {
- getroottable().rawset("CServerGameDLL_ProcessMessageStartThread", CServerGameDLL_ProcessMessageStartThread)
-}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut
index 378ceae3..9dadea15 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_entitystructs.gnut
@@ -232,6 +232,7 @@ global struct ServerEntityStruct
SpawnPointData spawnPointData
array<void functionref( entity ent, var damageInfo )> entDamageCallbacks
+ array<void functionref( entity ent, var damageInfo )> entFinalDamageCallbacks
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
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut
index 37b89169..3bdb1d8a 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_harvester.gnut
@@ -1 +1,72 @@
-//fuck \ No newline at end of file
+global function SpawnHarvester
+global function generateBeamFX
+global function generateShieldFX
+
+global struct HarvesterStruct {
+ entity harvester
+ entity particleBeam
+ entity particleShield
+ entity rings
+ float lastDamage
+ bool shieldBoost
+ bool harvesterShieldDown
+ float harvesterDamageTaken
+ bool havesterWasDamaged
+
+}
+
+HarvesterStruct function SpawnHarvester( vector origin, vector angles, int health, int shieldHealth, int team )
+{
+ entity harvester = CreateEntity( "prop_script" )
+ harvester.SetValueForModelKey( $"models/props/generator_coop/generator_coop.mdl" )
+ harvester.SetOrigin( origin )
+ harvester.SetAngles( angles )
+ harvester.kv.solid = SOLID_VPHYSICS
+
+ harvester.SetMaxHealth( health )
+ harvester.SetHealth( health )
+ harvester.SetShieldHealthMax( shieldHealth )
+ harvester.SetShieldHealth( shieldHealth )
+ harvester.EnableAttackableByAI( 30, 0, AI_AP_FLAG_NONE )
+ SetCustomSmartAmmoTarget( harvester, true )
+ SetObjectCanBeMeleed( harvester, true )
+ SetVisibleEntitiesInConeQueriableEnabled( harvester, true )
+ SetTeam(harvester,team)
+
+ DispatchSpawn( harvester )
+
+
+ entity blackbox = CreatePropDynamic( MODEL_HARVESTER_TOWER_COLLISION, origin, angles, 0 )
+ blackbox.Hide()
+ blackbox.Solid()
+ // blackbox.kv.CollisionGroup = TRACE_COLLISION_GROUP_PLAYER
+
+ entity rings = CreatePropDynamic( MODEL_HARVESTER_TOWER_RINGS, origin, angles, 6 )
+ thread PlayAnim( rings, "generator_cycle_fast" )
+
+
+
+ HarvesterStruct ret
+ ret.harvester = harvester
+ ret.lastDamage = Time()
+ ret.rings = rings
+
+ return ret
+}
+
+HarvesterStruct function generateBeamFX( HarvesterStruct harvester )
+{
+ entity Harvester_Beam = StartParticleEffectOnEntity_ReturnEntity( harvester.harvester, GetParticleSystemIndex( FX_HARVESTER_BEAM ), FX_PATTACH_ABSORIGIN_FOLLOW ,0 )
+ EffectSetControlPointVector( Harvester_Beam, 1, GetShieldTriLerpColor( 0.0 ) )
+ harvester.particleBeam = Harvester_Beam
+ Harvester_Beam.DisableHibernation()
+ return harvester
+}
+
+HarvesterStruct function generateShieldFX( HarvesterStruct harvester )
+{
+ entity Harvester_Shield = StartParticleEffectOnEntity_ReturnEntity( harvester.harvester, GetParticleSystemIndex( FX_HARVESTER_OVERSHIELD ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 )
+ EffectSetControlPointVector( Harvester_Shield, 1, GetShieldTriLerpColor( 0.0 ) )
+ harvester.particleShield = Harvester_Shield
+ return harvester
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
index 539b72bc..96623086 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut
@@ -5698,7 +5698,13 @@ bool function IsUnlockValid( string ref, string parentRef = "" )
bool function IsSubItemLocked( entity player, string ref, string parentRef )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
+ return false
+
+ if ( IsItemPurchasableEntitlement( ref, parentRef ) )
+ return false
+
+ if ( GetItemType( ref ) == eItemTypes.PRIME_TITAN || GetSubitemType( parentRef, ref ) == eItemTypes.PRIME_TITAN )
return false
if ( IsItemInEntitlementUnlock( ref, parentRef ) )
@@ -5817,7 +5823,13 @@ bool function IsSubItemLocked( entity player, string ref, string parentRef )
bool function IsItemLocked( entity player, string ref )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
+ return false
+
+ if ( IsItemPurchasableEntitlement( ref ) )
+ return false
+
+ if ( GetItemType( ref ) == eItemTypes.PRIME_TITAN )
return false
if ( IsItemInEntitlementUnlock( ref ) )
@@ -5906,7 +5918,7 @@ bool function IsItemLockedForEntitlement( entity player, string ref, string pare
bool function IsSubItemOwned( entity player, string ref, string parentRef )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return false
Assert( IsValid( player ) )
@@ -5990,7 +6002,7 @@ bool function IsSubItemOwned( entity player, string ref, string parentRef )
bool function IsItemOwned( entity player, string ref )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return false
Assert( IsValid( player ) )
@@ -10082,7 +10094,7 @@ void function InitUnlockAsEntitlement( string itemRef, string parentRef, int ent
unlock = file.entitlementUnlocks[fullRef]
}
- unlock.entitlementIds.append( entitlementId )
+ unlock.entitlementIds.append( 1 ) // Using `1` here instead of the huge DLC check I did previously. Having the `1` seems to keep all paid cosmetics unlocked with progression enabled.
}
array<int> function GetEntitlementIds( string itemRef, string parentRef = "" )
@@ -10219,6 +10231,10 @@ void function StatUnlock_Unlocked( entity player, string itemRef, string parentR
if ( IsItemNew( player, itemRef, parentRef ) )
return
+ // early out if the player has progression disabled
+ if ( !ProgressionEnabledForPlayer( player ) )
+ return
+
int refGuid = file.itemRefToGuid[itemRef]
int parentRefGuid = parentRef == "" ? 0 : file.itemRefToGuid[parentRef]
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
index 3e8ac9ea..141cfe15 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut
@@ -51,8 +51,6 @@ void function SvLoadoutsMP_Init()
AddClientCommandCallback( "InGameMPMenuClosed", ClientCommandCallback_InGameMPMenuClosed )
AddClientCommandCallback( "LoadoutMenuClosed", ClientCommandCallback_LoadoutMenuClosed )
}
-
- AddCallback_OnPlayerKilled( DestroyDroppedWeapon )
}
void function SetLoadoutGracePeriodEnabled( bool enabled )
@@ -62,20 +60,10 @@ void function SetLoadoutGracePeriodEnabled( bool enabled )
void function SetWeaponDropsEnabled( bool enabled )
{
- file.weaponDropsEnabled = enabled
-}
-
-void function DestroyDroppedWeapon( entity victim, entity attacker, var damageInfo )
-{
- if ( !file.weaponDropsEnabled && IsValid( victim.GetActiveWeapon() ) )
- thread DelayDestroyDroppedWeapon( victim.GetActiveWeapon() )
-}
-
-void function DelayDestroyDroppedWeapon( entity weapon )
-{
- WaitEndFrame()
- if ( IsValid( weapon ) )
- weapon.Destroy()
+ if( enabled )
+ FlagSet( "WeaponDropsAllowed" )
+ else
+ FlagClear( "WeaponDropsAllowed" )
}
void function AddCallback_OnTryGetTitanLoadout( TryGetTitanLoadoutCallbackType callback )
@@ -179,6 +167,8 @@ bool function ClientCommandCallback_SetPersistentLoadoutValue( entity player, ar
if ( args[0] == "pilot" )
SetPlayerLoadoutDirty( player )
+ UnlockAchievement( player, achievements.CUSTOMIZE_LOADOUT )
+
return true
}
@@ -191,6 +181,10 @@ bool function ClientCommandCallback_SwapSecondaryAndWeapon3PersistentLoadoutData
// get loadout
int index = args[0].tointeger()
+
+ if ( !IsValidPilotLoadoutIndex(index) )
+ return false
+
PilotLoadoutDef loadout = GetPilotLoadoutFromPersistentData( player, index )
// swap loadouts
@@ -222,7 +216,6 @@ bool function ClientCommandCallback_SetBurnCardPersistenceSlot( entity player, a
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
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut
index 02be47a4..6499faa2 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_menu_callbacks.gnut
@@ -9,7 +9,7 @@ void function MenuCallbacks_Init()
bool function ClientCommandCallback_LeaveMatch( entity player, array<string> args )
{
// note: this is imperfect if we have multiple people of the same uid on a server, but that's only a thing in testing
- if ( NSIsPlayerIndexLocalPlayer( player.GetPlayerIndex() ) )
+ if ( NSIsPlayerLocalPlayer( player ) )
{
if ( GetConVarBool( "ns_should_return_to_lobby" ) && GetMapName() != "mp_lobby" )
{
@@ -41,9 +41,11 @@ void function WritePersistenceAndLeaveForLocalPlayerOnly( entity player )
void function WritePersistenceAndLeave( entity player )
{
+ player.EndSignal( "OnDestroy" )
+
// write player persistence before we leave, since leaving player might load local lobby before server writes persistence, so they won't get newest
// not super essential, but a nice qol thing
- NSEarlyWritePlayerIndexPersistenceForLeave( player.GetPlayerIndex() )
+ NSEarlyWritePlayerPersistenceForLeave( player )
while ( NSIsWritingPlayerPersistence() )
WaitFrame()
@@ -63,6 +65,8 @@ bool function ClientCommandCallback_GenUp( entity player, array<string> args )
player.GenChanged()
player.XPChanged()
}
+
+ RegenPersistentLoadouts(player)
return true
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut b/Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut
index c79265ac..af3dfea5 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_northstar_cheatcommands.nut
@@ -14,6 +14,11 @@ bool function ClientCommandCallbackToggleNoclip( entity player, array<string> ar
{
if ( !GetConVarBool( "sv_cheats" ) )
return true
+ if( player.GetParent() ) // change movetype while setparented will crash the server
+ {
+ print( player + " failed noclipping because the entity is parented" )
+ return true
+ }
print( player + " TOGGLED NOCLIP" )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut
index c0e69ba5..4e5c5aa9 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_utility.gnut
@@ -2127,8 +2127,13 @@ void function EntityDemigod_TryAdjustDamageInfo( entity ent, var damageInfo )
return
int bottomLimit = 5
+ // Set it up so that you at least take 1 damage, for hit indicators etc to trigger
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
+ {
+ // Prevent going over max health to avoid a server crash.
+ int newHealth = bottomLimit + 1
+ ent.SetHealth( ent.GetMaxHealth() < newHealth ? ent.GetMaxHealth() : newHealth )
+ }
int health = ent.GetHealth()
@@ -4012,7 +4017,7 @@ int function GameTime_TimeLeftMinutes()
if ( GetGameState() == eGameState.Prematch )
return int( ( expect float( GetServerVar( "gameStartTime" ) ) - Time()) / 60.0 )
- return floor( GameTime_TimeLimitMinutes() - GameTime_PlayingTime() / 60 ).tointeger()
+ return floor( GameTime_PlayingTime() / 60 ).tointeger()
}
int function GameTime_TimeLeftSeconds()
@@ -4020,30 +4025,25 @@ int function GameTime_TimeLeftSeconds()
if ( GetGameState() == eGameState.Prematch )
return int( expect float( GetServerVar( "gameStartTime" ) ) - Time() )
- return floor( GameTime_TimeLimitSeconds() - GameTime_PlayingTime() ).tointeger()
+ return GameTime_PlayingTime().tointeger()
}
+// WARN: this function includes WaitingForPlayers and Prematch duration!
int function GameTime_Seconds()
{
return floor( Time() ).tointeger()
}
+// WARN: this function includes WaitingForPlayers Prematch duration!
int function GameTime_Minutes()
{
return int( floor( GameTime_Seconds() / 60 ) )
}
+// this function only counts the time limit during eGameState.Playing
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
@@ -4052,17 +4052,15 @@ float function GameTime_PlayingTimeSince( float sinceTime )
if ( gameState > eGameState.SuddenDeath )
return (expect float( GetServerVar( "roundEndTime" ) ) - expect float( GetServerVar( "roundStartTime" ) ) )
else
- return sinceTime - expect float( GetServerVar( "roundStartTime" ) )
-
+ return floor( expect float( GetServerVar( "roundEndTime" ) ) - Time() )
}
else
{
if ( gameState > eGameState.SuddenDeath )
return (expect float( GetServerVar( "gameEndTime" ) ) - expect float( GetServerVar( "gameStartTime" ) ) )
else
- return sinceTime - expect float( GetServerVar( "gameStartTime" ) )
+ return floor( expect float( GetServerVar( "gameEndTime" ) ) - Time() )
}
-
unreachable
}
@@ -4406,4 +4404,4 @@ bool function PlayerHasTitan( entity player )
return true
return false
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut b/Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut
index c5887f2b..47dd9294 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_utility_shared.nut
@@ -1601,6 +1601,9 @@ float function GetPulseFrac( rate = 1, startTime = 0 )
bool function IsPetTitan( titan )
{
Assert( titan.IsTitan() )
+
+ if ( !titan.GetTitanSoul() )
+ return false
return titan.GetTitanSoul().GetBossPlayer() != null
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
index 6f044b7a..97d993e6 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut
@@ -1,6 +1,7 @@
global function SvXP_Init
global function PlayerProgressionAllowed
global function HandleXPGainForScoreEvent
+global function AddXP
void function SvXP_Init()
{
@@ -15,7 +16,10 @@ void function SetupPlayerPreviousXPValues( entity player )
player.SetPersistentVar( "previousFactionXP[" + xpFaction + "]", FactionGetXP( player, xpFaction ) )
foreach ( string xpTitan in shTitanXP.titanClasses )
+ {
player.SetPersistentVar( "previousTitanXP[" + xpTitan + "]", TitanGetXP( player, xpTitan ) )
+ player.SetPersistentVar( "fdPreviousTitanXP[" + xpTitan + "]", FD_TitanGetXP( player, xpTitan ) )
+ }
foreach ( string xpWeapon in shWeaponXP.weaponClassNames )
player.SetPersistentVar( GetItemPersistenceStruct( xpWeapon ) + ".previousWeaponXP", WeaponGetXP( player, xpWeapon ) )
@@ -29,46 +33,51 @@ bool function PlayerProgressionAllowed( entity player )
void function HandleXPGainForScoreEvent( entity player, ScoreEvent event )
{
// note: obviously all xp stuff can be cheated in if people want to on customs, this is mainly just here for fun for those who want it and feature completeness
- // most score events don't have this, so we'll set this to the xp value of other categories later if needed
+
int xpValue = ScoreEvent_GetXPValue( event )
int weaponXp = ScoreEvent_GetXPValueWeapon( event )
int titanXp = ScoreEvent_GetXPValueTitan( event )
-
- if ( xpValue < weaponXp )
- xpValue = weaponXp
- else if ( xpValue < titanXp )
- xpValue = titanXp
+ int factionXp = ScoreEvent_GetXPValueFaction( event )
+
+ if ( player.GetPlayerNetInt( "xpMultiplier" ) > 0 || GetCurrentPlaylistVarInt( "double_xp_enabled", 0 ) == 1 )
+ {
+ xpValue *= 2
+ weaponXp *= 2
+ titanXp *= 2
+ factionXp *= 2
+ }
entity weapon = player.GetActiveWeapon()
- if ( IsValid( weapon ) && ShouldTrackXPForWeapon( weapon.GetWeaponClassName() ) )
- AddWeaponXP( player, xpValue )
+ if ( IsValid( weapon ) && ShouldTrackXPForWeapon( weapon.GetWeaponClassName() ) && weaponXp != 0 )
+ AddWeaponXP( player, weaponXp )
// if we specifically gain titan xp, then give titan xp no matter what, otherwise only give it when we're in a titan
- if ( titanXp != 0 || player.IsTitan() )
- AddTitanXP( player, xpValue )
-
- // most events don't have faction xp but almost everything should give it
- int factionXp = ScoreEvent_GetXPValueFaction( event )
- if ( xpValue > factionXp )
- factionXp = xpValue
- else if ( xpValue < factionXp )
- xpValue = factionXp
+ if ( titanXp != 0 )
+ AddTitanXP( player, titanXp )
if ( factionXp != 0 )
AddFactionXP( player, factionXp )
- if ( xpValue == 0 )
- return
-
// global xp
+ if ( xpValue != 0 )
+ AddXP( player, xpValue )
+}
+
+void function AddXP( entity player, int amount )
+{
int oldXp = player.GetPersistentVarAsInt( "xp" )
- if(oldXp<0) oldXp = 0
+ if( oldXp < 0 ) oldXp = 0
int oldLevel = GetLevelForXP( oldXp )
- player.SetPersistentVar( "xp", min( oldXp + xpValue, PlayerGetMaxXPPerGen() ) )
+ player.SetPersistentVar( "xp", min( oldXp + amount, PlayerGetMaxXPPerGen() ) )
player.XPChanged() // network xp change to client, gen can't change here
int newXp = player.GetPersistentVarAsInt( "xp" )
int newLevel = GetLevelForXP( newXp )
if ( newLevel != oldLevel )
+ {
Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerLeveledUp", player.GetPersistentVarAsInt( "gen" ), newLevel )
-} \ No newline at end of file
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForPlayerLevels( player, player.GetPersistentVarAsInt( "gen" ), newLevel )
+ }
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut
index 9717c76d..89fb7a82 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers.gnut
@@ -60,6 +60,19 @@ function AiSoldiers_Init()
level.COOP_AT_WEAPON_RATES[ "mp_weapon_smr" ] <- 0.4
level.COOP_AT_WEAPON_RATES[ "mp_weapon_mgl" ] <- 0.1
+ // add stub death callback, because in _codecallbacks_common.gnut there is
+ // CodeCallback_OnEntityKilled which is only called when an entity is being tracked. An
+ // entity is set to be tracked if it has a death callback for it's class, unfortunately this
+ // is then relayed to clients and used for client side death callbacks. The end result of
+ // not having this function called is that clients become completely unaware of any grunt
+ // deaths. A noticeable difference here is that grunts do not play the kill confirmed audio
+ // except on War Games, which does register a callback for grunt deaths to make them dissolve.
+ //
+ // Whilst this may seem like a bit of a hacky solution, it is generally better than simply
+ // tracking all entities. If a different callback is created in the future for grunt deaths
+ // that is not specific to a gamemode, map, etc. then this could be removed
+ AddDeathCallback( "npc_soldier", void function( entity guy, var damageInfo ){} )
+
PrecacheSprite( $"sprites/glow_05.vmt" )
FlagInit( "disable_npcs" )
FlagInit( "Disable_IMC" )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
index afa8ff3d..107a3f7f 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/burnmeter/_burnmeter.gnut
@@ -15,8 +15,10 @@ global function InitBurnMeterPersistentData
const float PHASE_REWIND_LENGTH = 2.0
// taken from wraith portal in apex, assuming it's the same as tf2's
-const float PHASE_REWIND_PATH_SNAPSHOT_INTERVAL = 0.1
-const int PHASE_REWIND_MAX_SNAPSHOTS = int( PHASE_REWIND_LENGTH / PHASE_REWIND_PATH_SNAPSHOT_INTERVAL )
+// 0.1 can be too fast for ttf2 scripts
+const float PHASE_REWIND_PATH_SNAPSHOT_INTERVAL = 0.2 // mainly controlled by this const, the higher the rewind path will be longer, assuming this one is like vanilla's
+const int PHASE_REWIND_MAX_SNAPSHOTS = int( PHASE_REWIND_LENGTH / PHASE_REWIND_PATH_SNAPSHOT_INTERVAL ) - 1
+const int PHASE_REWIND_DATA_MAX_POSITIONS = int( PHASE_REWIND_LENGTH * 10 ) + 1 // hardcoded but maybe good
const float AMPED_WEAPONS_LENGTH = 30.0
@@ -216,18 +218,18 @@ void function PhaseRewindLifetime( entity player )
{
PhaseRewindData rewindData
rewindData.origin = player.GetOrigin()
- rewindData.angles = player.GetAngles()
+ rewindData.angles = < 0, player.EyeAngles().y, 0 >
rewindData.velocity = player.GetVelocity()
rewindData.wasInContextAction = player.ContextAction_IsActive()
rewindData.wasCrouched = player.IsCrouched()
- if ( player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.len() >= PHASE_REWIND_MAX_SNAPSHOTS )
+ if ( player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.len() >= PHASE_REWIND_DATA_MAX_POSITIONS )
{
// shift all snapshots left
- for ( int i = 0; i < PHASE_REWIND_MAX_SNAPSHOTS - 1; i++ )
+ for ( int i = 0; i < PHASE_REWIND_DATA_MAX_POSITIONS - 1; i++ )
player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ i ] = player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ i + 1 ]
- player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ PHASE_REWIND_MAX_SNAPSHOTS - 1 ] = rewindData
+ player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions[ PHASE_REWIND_DATA_MAX_POSITIONS - 1 ] = rewindData
}
else
player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions.append( rewindData )
@@ -257,7 +259,9 @@ void function UseBurnCardWeapon( entity weapon, entity player )
if ( PlayerEarnMeter_IsRewardAvailable( player ) )
PlayerEarnMeter_SetRewardUsed( player )
- PlayerInventory_PopInventoryItem( player )
+ thread PlayerInventory_PopInventoryItem( player )
+
+ UpdatePlayerStat( player, "misc_stats", "boostsActivated" )
}
void function UseBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer )
@@ -287,10 +291,14 @@ void function InitBurnMeterPersistentData( entity player )
void function PlayerUsesAmpedWeaponsBurncard( entity player )
{
thread PlayerUsesAmpedWeaponsBurncardThreaded( player )
+
}
void function PlayerUsesAmpedWeaponsBurncardThreaded( entity player )
{
+ player.Signal( "StopAmpedWeapons" )
+ player.EndSignal("StopAmpedWeapons")
+
array<entity> weapons = player.GetMainWeapons()
//weapons.extend( player.GetOffhandWeapons() ) // idk? unsure of vanilla behaviour here
foreach ( entity weapon in weapons )
@@ -403,6 +411,10 @@ void function PlayerUsesMaphackBurncardThreaded( entity player )
entities.extend( GetPlayerDecoyArray() )
IncrementSonarPerTeam( playerTeam )
+
+ if ( IsAlive( player ) ) // only owner can see the pulse
+ Remote_CallFunction_Replay( player, "ServerCallback_SonarPulseFromPosition", player.GetOrigin().x, player.GetOrigin().y, player.GetOrigin().z, SONAR_GRENADE_RADIUS )
+
foreach ( entity ent in entities )
{
if ( !IsValid( ent ) ) // Not sure why we can get invalid entities at this point
@@ -410,9 +422,6 @@ void function PlayerUsesMaphackBurncardThreaded( entity player )
if ( ent.IsPlayer() )
{
- if ( IsAlive( player ) )
- Remote_CallFunction_Replay( ent, "ServerCallback_SonarPulseFromPosition", player.GetOrigin().x, player.GetOrigin().y, player.GetOrigin().z, SONAR_GRENADE_RADIUS )
-
// Map Hack also gives radar on enemies for longer than the sonar duration.
if ( ent.GetTeam() == playerTeam )
thread ScanMinimap( ent, false, MAPHACK_PULSE_DELAY - 0.2 )
@@ -466,18 +475,65 @@ void function PlayerUsesPhaseRewindBurncardThreaded( entity player )
array<PhaseRewindData> positions = clone player.p.burnCardPhaseRewindStruct.phaseRetreatSavedPositions
+ array<PhaseRewindData> tempArray
+ int segment = positions.len() / PHASE_REWIND_MAX_SNAPSHOTS
+ for( int i = 0; i < PHASE_REWIND_MAX_SNAPSHOTS; i++ )
+ {
+ if( positions.len() <= segment * i )
+ break
+ tempArray.append( positions[ segment * i ] )
+ }
+ positions = tempArray
+
+ vector startOrigin = player.GetOrigin()
ViewConeZero( player )
player.HolsterWeapon()
player.SetPredictionEnabled( false )
PhaseShift( player, 0.0, positions.len() * PHASE_REWIND_PATH_SNAPSHOT_INTERVAL * 1.5 )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "pilot_phaserewind_1p" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "pilot_phaserewind_3p" )
for ( int i = positions.len() - 1; i > -1; i-- )
{
- mover.NonPhysicsMoveTo( positions[ i ].origin, PHASE_REWIND_PATH_SNAPSHOT_INTERVAL, 0, 0 )
- mover.NonPhysicsRotateTo( positions[ i ].angles, PHASE_REWIND_PATH_SNAPSHOT_INTERVAL, 0, 0 )
+ // should looks better?
+ float moveTime = PHASE_REWIND_PATH_SNAPSHOT_INTERVAL + 0.1
+ player.SetVelocity( -positions[ i ].velocity )
+ mover.NonPhysicsMoveTo( positions[ i ].origin, moveTime, 0, 0 )
+ mover.NonPhysicsRotateTo( positions[ i ].angles, moveTime, 0, 0 )
wait PHASE_REWIND_PATH_SNAPSHOT_INTERVAL
}
player.SetVelocity( <0, 0, 0> )
+ if( positions[0].wasCrouched )
+ player.SetOrigin( player.GetOrigin() + < 0, 0, 20 > )
+
+ // fix position after rewind
+ PutPhaseRewindedPlayerInSafeSpot( player, 1 )
+}
+
+void function PutPhaseRewindedPlayerInSafeSpot( entity player, int severity )
+{
+ vector baseOrigin = player.GetOrigin()
+
+ if ( PutEntityInSafeSpot( player, player, null, < baseOrigin.x, baseOrigin.y + severity, baseOrigin.z >, baseOrigin ) )
+ return
+
+ if ( PutEntityInSafeSpot( player, player, null, < baseOrigin.x, baseOrigin.y - severity, baseOrigin.z >, baseOrigin ) )
+ return
+
+ if ( PutEntityInSafeSpot( player, player, null, < baseOrigin.x + severity, baseOrigin.y, baseOrigin.z >, baseOrigin ) )
+ return
+
+ if ( PutEntityInSafeSpot( player, player, null, < baseOrigin.x - severity, baseOrigin.y, baseOrigin.z >, baseOrigin ) )
+ return
+
+ if ( PutEntityInSafeSpot( player, player, null, < baseOrigin.x, baseOrigin.y, baseOrigin.z + severity >, baseOrigin ) )
+ return
+
+ if ( PutEntityInSafeSpot( player, player, null, < baseOrigin.x, baseOrigin.y, baseOrigin.z - severity >, baseOrigin ) )
+ return
+
+ return PutPhaseRewindedPlayerInSafeSpot( player, severity + 5 )
+
}
void function PlayerUsesNukeTitanBurncard( entity player )
@@ -559,4 +615,4 @@ void function PlayerUsesReaperfallBurncard( entity player )
DispatchSpawn( reaper )
thread SuperSpectre_WarpFall( reaper )
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/class/cplayer.nut b/Northstar.CustomServers/mod/scripts/vscripts/class/cplayer.nut
index b9f8f7eb..19c6b6df 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/class/cplayer.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/class/cplayer.nut
@@ -328,6 +328,26 @@ function CodeCallback_RegisterClass_CPlayer()
}
}
}
+
+ function CPlayer::GetUserInfoString( key, defaultValue = "" )
+ {
+ return GetUserInfoKVString_Internal( this, key, defaultValue )
+ }
+
+ function CPlayer::GetUserInfoInt( key, defaultValue = 0 )
+ {
+ return GetUserInfoKVInt_Internal( this, key, defaultValue )
+ }
+
+ function CPlayer::GetUserInfoFloat( key, defaultValue = 0 )
+ {
+ return GetUserInfoKVFloat_Internal( this, key, defaultValue )
+ }
+
+ function CPlayer::GetUserInfoBool( key, defaultValue = false )
+ {
+ return GetUserInfoKVBool_Internal( this, key, defaultValue )
+ }
}
void function PlayerDropsScriptedItems( entity player )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut
index 1a70c289..4eb423fd 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/conversation/_grunt_chatter_mp.gnut
@@ -1,11 +1,33 @@
global function GruntChatter_MP_Init
global function PlayGruntChatterMPLine
+const float CHATTER_FRIENDLY_GRUNT_DOWN_DIST_MAX = 1100.0
+const float CHATTER_SQUAD_DEPLETED_FRIENDLY_NEARBY_DIST = 650.0 // if any other friendly grunt is within this dist, squad deplete chatter won't play
+const float CHATTER_ENEMY_TITAN_DOWN_DIST_MAX = 1500.0
+const float CHATTER_NEARBY_GRUNT_TRACEFRAC_MIN = 0.95 // for when we need "LOS" trace
+
void function GruntChatter_MP_Init()
{
- //ShGruntChatter_MP_Init()
+ Assert( IsMultiplayer(), "MP Grunt chatter is restricted to Multiplayer only." )
+
+ AddCallback_OnPlayerKilled( GruntChatter_OnPlayerOrNPCKilled )
+ AddCallback_OnNPCKilled( GruntChatter_OnPlayerOrNPCKilled )
}
+
+
+
+/*=====================================================================================================================================================
+ _____ _ _____ _ _ _ __ __ _ _ _ _
+ / ____| | | / ____|| | | | | | | \/ | | || | (_) | |
+ | | __ _ __ _ _ _ __ | |_ | | | |__ __ _ | |_ | |_ ___ _ __ | \ / | _ _ | || |_ _ _ __ | | __ _ _ _ ___ _ __
+ | | |_ || '__|| | | || '_ \ | __| | | | '_ \ / _` || __|| __|/ _ \| '__| | |\/| || | | || || __|| || '_ \ | | / _` || | | | / _ \| '__|
+ | |__| || | | |_| || | | || |_ | |____ | | | || (_| || |_ | |_| __/| | | | | || |_| || || |_ | || |_) || || (_| || |_| || __/| |
+ \_____||_| \__,_||_| |_| \__| \_____||_| |_| \__,_| \__| \__|\___||_| |_| |_| \__,_||_| \__||_|| .__/ |_| \__,_| \__, | \___||_|
+ | | __/ |
+ |_| |___/
+/*===================================================================================================================================================*/
+
void function PlayGruntChatterMPLine( entity grunt, string conversationType )
{
#if !GRUNT_CHATTER_MP_ENABLED
@@ -15,4 +37,175 @@ void function PlayGruntChatterMPLine( entity grunt, string conversationType )
foreach ( entity player in GetPlayerArray() )
if ( ShouldPlayGruntChatterMPLine( conversationType, player, grunt ) )
Remote_CallFunction_Replay( player, "ServerCallback_PlayGruntChatterMP", GetConversationIndex( conversationType ), grunt.GetEncodedEHandle() )
+}
+
+void function GruntChatter_OnPlayerOrNPCKilled( entity deadGuy, entity attacker, var damageInfo )
+{
+ if ( !IsValid( deadGuy ) || !IsValid( attacker ) )
+ return
+
+ if( IsGrunt( attacker ) && IsPilot( deadGuy ) )
+ PlayGruntChatterMPLine( attacker, "bc_killenemypilot" )
+ else
+ GruntChatter_TryEnemyTitanDown( deadGuy )
+
+ if ( IsGrunt( deadGuy ) )
+ {
+ GruntChatter_TryFriendlyDown( deadGuy )
+ GruntChatter_TrySquadDepleted( deadGuy )
+ }
+}
+
+void function GruntChatter_TryFriendlyDown( entity deadGuy )
+{
+ entity closestGrunt = GruntChatter_FindClosestFriendlyHumanGrunt_LOS( deadGuy.GetOrigin(), deadGuy.GetTeam(), CHATTER_FRIENDLY_GRUNT_DOWN_DIST_MAX )
+ if ( !closestGrunt )
+ return
+
+ if ( !GruntChatter_CanGruntChatterNow( closestGrunt ) )
+ return
+
+ PlayGruntChatterMPLine( closestGrunt, "bc_allygruntdown" )
+}
+
+void function GruntChatter_TrySquadDepleted( entity deadGuy )
+{
+ string deadGuySquadName = expect string( deadGuy.kv.squadname )
+ if ( deadGuySquadName == "" )
+ return
+
+ array<entity> squad = GetNPCArrayBySquad( deadGuySquadName )
+ entity lastSquadMember
+ if ( squad.len() == 1 )
+ lastSquadMember = squad[0]
+
+ if ( !GruntChatter_CanGruntChatterNow( lastSquadMember ) )
+ return
+
+ if ( lastSquadMember.GetNPCState() == "idle" )
+ return
+
+ // if another grunt from another squad is nearby, don't chatter about being alone
+ array<entity> nearbyGrunts = GetNearbyFriendlyGrunts( lastSquadMember.GetOrigin(), lastSquadMember.GetTeam(), CHATTER_SQUAD_DEPLETED_FRIENDLY_NEARBY_DIST )
+ nearbyGrunts.fastremovebyvalue( lastSquadMember )
+ if ( nearbyGrunts.len() )
+ return
+
+ PlayGruntChatterMPLine( lastSquadMember, "bc_squaddeplete" )
+}
+
+void function GruntChatter_TryEnemyTitanDown( entity deadGuy )
+{
+ if ( deadGuy.IsTitan() )
+ {
+ entity closestGrunt = GruntChatter_FindClosestEnemyHumanGrunt_LOS( deadGuy.GetOrigin(), deadGuy.GetTeam(), CHATTER_ENEMY_TITAN_DOWN_DIST_MAX )
+ if ( !closestGrunt )
+ return
+
+ PlayGruntChatterMPLine( closestGrunt, "bc_enemytitandown" )
+ }
+}
+
+entity function GruntChatter_FindClosestEnemyHumanGrunt_LOS( vector searchOrigin, int enemyTeam, float searchDist )
+{
+ array<entity> humanGrunts = GetNearbyEnemyHumanGrunts( searchOrigin, enemyTeam, searchDist )
+ return GruntChatter_GetClosestGrunt_LOS( humanGrunts, searchOrigin )
+}
+
+entity function GruntChatter_FindClosestFriendlyHumanGrunt_LOS( vector searchOrigin, int friendlyTeam, float searchDist )
+{
+ array<entity> humanGrunts = GetNearbyFriendlyHumanGrunts( searchOrigin, friendlyTeam, searchDist )
+ return GruntChatter_GetClosestGrunt_LOS( humanGrunts, searchOrigin )
+}
+
+entity function GruntChatter_GetClosestGrunt_LOS( array<entity> nearbyGrunts, vector searchOrigin )
+{
+ entity closestGrunt = null
+ float closestDist = 10000
+
+ foreach ( grunt in nearbyGrunts )
+ {
+ vector gruntOrigin = grunt.GetOrigin()
+
+ // CanSee doesn't return true if the target is dead
+ if ( !GruntChatter_CanGruntTraceToLocation( grunt, searchOrigin ) )
+ continue
+
+ if ( !closestGrunt )
+ {
+ closestGrunt = grunt
+ continue
+ }
+
+ float distFromSearchOrigin = Distance( grunt.GetOrigin(), searchOrigin )
+
+ if ( closestDist > distFromSearchOrigin )
+ continue
+
+ closestGrunt = grunt
+ closestDist = distFromSearchOrigin
+ }
+
+ return closestGrunt
+}
+
+bool function GruntChatter_CanGruntTraceToLocation( entity grunt, vector traceEnd )
+{
+ float traceFrac = TraceLineSimple( grunt.GetOrigin(), traceEnd, grunt )
+ return traceFrac > CHATTER_NEARBY_GRUNT_TRACEFRAC_MIN
+}
+
+array<entity> function GetNearbyFriendlyHumanGrunts( vector searchOrigin, int friendlyTeam, float ornull searchRange = null )
+{
+ array<entity> nearbyGrunts = GetNearbyFriendlyGrunts( searchOrigin, friendlyTeam, searchRange )
+ array<entity> humanGrunts = []
+ foreach ( grunt in nearbyGrunts )
+ {
+ if ( grunt.IsMechanical() )
+ continue
+
+ humanGrunts.append( grunt )
+ }
+
+ return humanGrunts
+}
+
+array<entity> function GetNearbyEnemyHumanGrunts( vector searchOrigin, int enemyTeam, float ornull searchRange = null )
+{
+ array<entity> nearbyGrunts = GetNearbyEnemyGrunts( searchOrigin, enemyTeam, searchRange )
+ array<entity> humanGrunts = []
+ foreach ( grunt in nearbyGrunts )
+ {
+ if ( grunt.IsMechanical() )
+ continue
+
+ humanGrunts.append( grunt )
+ }
+
+ return humanGrunts
+}
+
+bool function GruntChatter_CanGruntChatterNow( entity grunt )
+{
+ if ( !IsAlive( grunt ) )
+ return false
+
+ if ( !GruntChatter_IsGruntTypeEligibleForChatter( grunt ) )
+ return false
+
+ if ( grunt.ContextAction_IsMeleeExecution() )
+ return false
+
+ string squadname = expect string( grunt.kv.squadname )
+ // we only care about this because the grunt conversation system wants it
+ return squadname != ""
+}
+
+bool function GruntChatter_IsGruntTypeEligibleForChatter( entity grunt )
+{
+ if ( !IsGrunt( grunt ) )
+ return false
+
+ // mechanical grunts don't chatter
+ return !grunt.IsMechanical()
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter.gnut b/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter.gnut
index dda84976..b4e77375 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter.gnut
@@ -315,12 +315,12 @@ void function PlayerEarnMeter_Empty( entity player )
PlayerEarnMeter_SetRewardFrac( player, 0.0 )
}
-
void function EarnMeterDecayThink( entity player )
{
player.EndSignal( "OnDeath" )
player.Signal( "EarnMeterDecayThink" )
player.EndSignal( "EarnMeterDecayThink" )
+ thread OverDriveClearOnDeath( player )
if ( EarnMeter_DecayHold() < 0 )
return
@@ -348,6 +348,12 @@ void function EarnMeterDecayThink( entity player )
}
}
+void function OverDriveClearOnDeath( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+ player.WaitSignal( "OnDeath" )
+ PlayerEarnMeter_SetEarnedFrac( player, PlayerEarnMeter_GetOwnedFrac( player ) )
+}
bool function PlayerEarnMeter_TryMakeGoalAvailable( entity player )
{
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
index bce8b4c7..760daef0 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut
@@ -69,11 +69,21 @@ struct {
entity evacIcon
} file
+struct EvacShipSetting
+{
+ asset shipModel
+ string flyinSound
+ string hoverSound
+ string flyoutSound
+}
+
void function Evac_Init()
{
EvacShared_Init()
RegisterSignal( "EvacShipLeaves" )
RegisterSignal( "EvacOver" )
+
+ PrecacheParticleSystem( FX_EVAC_MARKER )
}
void function AddEvacNode( entity evacNode )
@@ -100,7 +110,7 @@ void function EvacEpilogueSetup()
void function EvacEpilogue()
{
- int winner = GetWinningTeam()
+ int winner = GetWinningTeam()
// make sure we don't run evac if it won't be supported for current map/gamestate
bool canRunEvac = GetCurrentPlaylistVarInt( "max_teams", 2 ) == 2 &&
@@ -110,6 +120,10 @@ void function EvacEpilogue()
if ( canRunEvac )
{
thread SetRespawnAndWait( false )
+
+ // no players can evac? end match
+ thread CheckIfAnyPlayerLeft( GetOtherTeam( winner ) )
+
thread Evac( GetOtherTeam( winner ), EVAC_INITIAL_WAIT, EVAC_ARRIVAL_TIME, EVAC_WAIT_TIME, EvacEpiloguePlayerCanBoard, EvacEpilogueShouldLeaveEarly, EvacEpilogueCompleted )
}
else
@@ -139,6 +153,10 @@ void function SetRespawnAndWait( bool mode )
{
wait GAME_EPILOGUE_PLAYER_RESPAWN_LEEWAY
SetRespawnsEnabled( mode )
+
+ // clear any respawn availablity, or players are able to save there respawn for whenever they want
+ foreach( entity player in GetPlayerArray() )
+ ClearRespawnAvailable( player )
}
bool function EvacEpiloguePlayerCanBoard( entity dropship, entity player )
@@ -174,12 +192,16 @@ void function EvacEpilogueCompleted( entity dropship )
ScreenFadeToBlackForever( player, 2.0 )
wait 2.0
- SetGameState( eGameState.Postmatch )
+ if( GetGameState() != eGameState.Postmatch )
+ SetGameState( eGameState.Postmatch )
}
// global evac func, anything can call this, it's not necessarily an epilogue thing
void function Evac( int evacTeam, float initialWait, float arrivalTime, float waitTime, bool functionref( entity, entity ) canBoardCallback, bool functionref( entity ) shouldLeaveEarlyCallback, void functionref( entity ) completionCallback, entity customEvacNode = null )
{
+ // get evac ship sound and model for specific team
+ EvacShipSetting evacShip = GetEvacShipSettingByTeam( evacTeam )
+
wait initialWait
// setup evac nodes if not manually registered
@@ -212,23 +234,39 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
DispatchSpawn( file.evacIcon )
file.evacIcon.DisableHibernation()
+ // start evac beam
+ int index = GetParticleSystemIndex( FX_EVAC_MARKER )
+
+ entity effectFriendly = StartParticleEffectInWorld_ReturnEntity( index, evacNode.GetOrigin(), < 0,0,0 > )
+ SetTeam( effectFriendly, evacTeam )
+ effectFriendly.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
+
wait 0.5 // need to wait here, or the target won't appear on clients for some reason
// eta until arrive
SetTeamActiveObjective( evacTeam, "EG_DropshipExtract", Time() + arrivalTime, file.evacIcon )
SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract", Time() + arrivalTime, file.evacIcon )
+ foreach( entity player in GetPlayerArrayOfTeam( evacTeam ) ) //Show the Evac Match Goal for players of the team that lost the match
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 0 )
+
// would've liked to use cd_dropship_rescue_side_start length here, but can't since this is done before dropship spawn, can't
wait arrivalTime - 4.33333
entity dropship = CreateDropship( evacTeam, evacNode.GetOrigin(), evacNode.GetAngles() )
- dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
- dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+
+ thread DropShipTempHide( dropship ) // prevent showing model and health bar on spawn
+ dropship.SetModel( evacShip.shipModel )
+ dropship.SetValueForModelKey( evacShip.shipModel )
+
dropship.SetMaxHealth( EVAC_SHIP_HEALTH )
dropship.SetHealth( EVAC_SHIP_HEALTH )
dropship.SetShieldHealth( EVAC_SHIP_SHIELDS )
SetTargetName( dropship, "#NPC_EVAC_DROPSHIP" )
DispatchSpawn( dropship )
- dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+
+ // reduce nuclear core's damage
+ AddEntityCallback_OnDamaged( dropship, EvacDropshipDamaged )
+
AddEntityCallback_OnKilled( dropship, EvacDropshipKilled )
dropship.s.evacSlots <- [ null, null, null, null, null, null, null, null ]
@@ -241,9 +279,15 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
dropship.s.evacTrigger.Destroy()
// this should be for both teams
- SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipDestroyed" )
- SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_DropshipExtractDropshipDestroyed" )
-
+ if( !IsValid( dropship ) )
+ {
+ SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipDestroyed" )
+ SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_DropshipExtractDropshipDestroyed" )
+
+ foreach( entity player in GetPlayerArrayOfTeam( evacTeam ) )
+ SetPlayerChallengeEvacState( player, 0 )
+ }
+
foreach ( entity player in dropship.s.evacSlots )
{
if ( !IsValid( player ) )
@@ -255,10 +299,14 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
// this is called whether dropship is destroyed or evac finishes, callback can handle this itself
thread completionCallback( dropship )
})
-
+
// flyin
Spectator_SetCustomSpectatorFunc( EvacSpectatorFunc )
thread PlayAnim( dropship, "cd_dropship_rescue_side_start", evacNode )
+
+ // fly in sound and effect
+ EmitSoundOnEntity( dropship, evacShip.flyinSound )
+ thread WarpInEffectEvacShip( dropship )
// calculate time until idle start
float sequenceDuration = dropship.GetSequenceDuration( "cd_dropship_rescue_side_start" )
@@ -266,11 +314,22 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
wait sequenceDuration * cycleFrac
thread PlayAnim( dropship, "cd_dropship_rescue_side_idle", evacNode )
+
+ // hover sound
+ EmitSoundOnEntity( dropship, evacShip.hoverSound )
// eta until leave
- SetTeamActiveObjective( evacTeam, "EG_DropshipExtract2", Time() + EVAC_WAIT_TIME, file.evacIcon )
- SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract2", Time() + EVAC_WAIT_TIME, file.evacIcon )
+ SetTeamActiveObjective( evacTeam, "EG_DropshipExtract2", Time() + waitTime, file.evacIcon )
+ SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract2", Time() + waitTime, file.evacIcon )
+ // dialogue
+ PlayFactionDialogueToTeam( "mp_evacGo", evacTeam )
+ PlayFactionDialogueToTeam( "mp_evacStop", GetOtherTeam( evacTeam ) )
+
+ // stop evac beam
+ if( IsValid( effectFriendly ) )
+ EffectStop( effectFriendly )
+
// setup evac trigger
entity trigger = CreateEntity( "trigger_cylinder" )
trigger.SetRadius( 150 )
@@ -298,6 +357,10 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
WaitFrame()
}
+
+ // fly out sound
+ StopSoundOnEntity( dropship, evacShip.hoverSound )
+ EmitSoundOnEntity( dropship, evacShip.flyoutSound )
// holster all weapons
foreach ( entity player in dropship.s.evacSlots )
@@ -320,6 +383,12 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
Remote_CallFunction_NonReplay( player, "ServerCallback_PlayScreenFXWarpJump" )
wait WARPINFXTIME
+
+ dropship.kv.VisibilityFlags = 0 // prevent jetpack trails being like "dive" into ground
+ WaitFrame() // better wait because we are server
+ if( !IsValid( dropship ) )
+ return
+ thread __WarpOutEffectShared( dropship )
// go to space
@@ -339,24 +408,64 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa
if ( !PlayerInDropship( player, dropship ) )
{
if ( player.GetTeam() == dropship.GetTeam() )
+ {
SetPlayerActiveObjective( player, "EG_DropshipExtractFailedEscape" )
+ SetPlayerChallengeEvacState( player, 2 )
+ }
continue
}
SetPlayerActiveObjective( player, "EG_DropshipExtractSuccessfulEscape" )
+
+ // let evacing team able to see the ship again
+ dropship.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
// skybox
player.SetSkyCamera( GetEnt( SKYBOXSPACE ) )
Remote_CallFunction_NonReplay( player, "ServerCallback_DisableHudForEvac" )
Remote_CallFunction_NonReplay( player, "ServerCallback_SetClassicSkyScale", dropship.GetEncodedEHandle(), 0.7 )
Remote_CallFunction_NonReplay( player, "ServerCallback_SetMapSettings", 4.0, false, 0.4, 0.125 )
+ SetPlayerChallengeEvacState( player, 1 )
// display player [Evacuated] in killfeed
foreach ( entity otherPlayer in GetPlayerArray() )
Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_EvacObit", player.GetEncodedEHandle() )
}
-
+
+ // award player score to evacing team
+ int evacCount = 0
+ array<entity> evacingPlayers = GetPlayerArrayOfTeam( dropship.GetTeam() ) // all players that are supposed to evac in the dropship
+
+ // count how many players are in the dropship
+ foreach ( entity player in evacingPlayers )
+ {
+ if ( !PlayerInDropship( player, dropship ) )
+ continue
+
+ evacCount++
+ }
+
+ bool allEvac = evacCount == evacingPlayers.len()
+
+ foreach(entity player in evacingPlayers)
+ {
+ if ( !PlayerInDropship( player, dropship ) )
+ continue
+
+ AddPlayerScore( player, "HotZoneExtract" )
+ UpdatePlayerStat( player, "misc_stats", "evacsSurvived" )
+
+ if ( allEvac )
+ AddPlayerScore( player, "TeamBonusFullEvac" )
+ }
+
+ // sole survivor (but not the only one on the team)
+ if ( evacCount == 1 && !allEvac )
+ {
+ // we can assume there is one player in the array because otherwise evacCount wouldn't be 1
+ AddPlayerScore( evacingPlayers[0], "SoleSurvivor" )
+ }
}
void function AddPlayerToEvacDropship( entity dropship, entity player )
@@ -376,7 +485,12 @@ void function AddPlayerToEvacDropship( entity dropship, entity player )
// no slots available
if ( !PlayerInDropship( player, dropship ) )
return
-
+
+ UpdatePlayerStat( player, "misc_stats", "evacsAttempted" )
+
+ // need to cancel if the dropship dies
+ dropship.EndSignal( "OnDeath", "OnDestroy" )
+
player.SetInvulnerable()
player.UnforceCrouch()
player.ForceStand()
@@ -391,7 +505,7 @@ void function AddPlayerToEvacDropship( entity dropship, entity player )
EmitSoundOnEntityOnlyToPlayer( player, player, SHIFTER_START_SOUND_3P )
// should play SHIFTER_START_SOUND_1P when they actually arrive in the ship i think, unsure how this is supposed to be done
PlayPhaseShiftDisappearFX( player )
- waitthread FirstPersonSequence( fp, player, dropship )
+ FirstPersonSequence( fp, player, dropship )
FirstPersonSequenceStruct idleFp
idleFp.firstPersonAnimIdle = EVAC_IDLE_ANIMS_1P[ slot ]
@@ -426,3 +540,97 @@ void function EvacDropshipKilled( entity dropship, var damageInfo )
}
}
}
+
+// if there's no player left in evacing team, we end this match
+void function CheckIfAnyPlayerLeft( int evacTeam )
+{
+ wait GAME_EPILOGUE_PLAYER_RESPAWN_LEEWAY
+ float startTime = Time()
+
+ OnThreadEnd(
+ function() : ( evacTeam )
+ {
+ SetTeamActiveObjective( evacTeam, "EG_DropshipExtractEvacPlayersKilled" )
+ SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtractEvacPlayersKilled" )
+ thread EvacEpilogueCompleted( null )
+
+ // score for killing the entire evacing team
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( player.GetTeam() == evacTeam )
+ continue
+
+ AddPlayerScore( player, "TeamBonusKilledAll")
+ }
+ }
+ )
+ while( true )
+ {
+ if( GetPlayerArrayOfTeam_Alive( evacTeam ).len() == 0 )
+ break
+ if( GetGameState() == eGameState.Postmatch )
+ return
+ WaitFrame()
+ }
+}
+
+void function DropShipTempHide( entity dropship )
+{
+ dropship.kv.VisibilityFlags = 0 // or it will still shows the jetpack fxs
+ HideName( dropship )
+ wait 0.46
+ if( IsValid( dropship ) )
+ {
+ dropship.kv.VisibilityFlags = ENTITY_VISIBLE_TO_EVERYONE
+ ShowName( dropship )
+ }
+}
+
+EvacShipSetting function GetEvacShipSettingByTeam( int team )
+{
+ EvacShipSetting tempSetting
+ tempSetting.shipModel = $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl"
+ tempSetting.flyinSound = "Goblin_IMC_Evac_Flyin"
+ tempSetting.hoverSound = "Goblin_IMC_Evac_Hover"
+ tempSetting.flyoutSound = "Goblin_IMC_Evac_FlyOut"
+
+ if( team == TEAM_MILITIA )
+ {
+ tempSetting.shipModel = $"models/vehicle/crow_dropship/crow_dropship_hero.mdl"
+ tempSetting.flyinSound = "Crow_MCOR_Evac_Flyin"
+ tempSetting.hoverSound = "Crow_MCOR_Evac_Hover"
+ tempSetting.flyoutSound = "Crow_MCOR_Evac_Flyout"
+ }
+ return tempSetting
+}
+
+void function EvacDropshipDamaged( entity evacShip, var damageInfo )
+{
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if( damageSourceID == damagedef_nuclear_core )
+ DamageInfo_SetDamage( damageInfo, DamageInfo_GetDamage( damageInfo ) * EVAC_SHIP_DAMAGE_MULTIPLIER_AGAINST_NUCLEAR_CORE )
+}
+
+void function WarpInEffectEvacShip( entity dropship )
+{
+ dropship.EndSignal( "OnDestroy" )
+ float sfxWait = 0.1
+ float totalTime = WARPINFXTIME
+ float preWaitTime = 0.16 // give it some time so it's actually playing anim, and we can get it's "origin" attatch for playing warp in effect
+ string sfx = "dropship_warpin"
+
+ wait preWaitTime
+
+ int attach = dropship.LookupAttachment( "origin" )
+ vector origin = dropship.GetAttachmentOrigin( attach )
+ vector angles = dropship.GetAttachmentAngles( attach )
+
+ entity fx = PlayFX( FX_GUNSHIP_CRASH_EXPLOSION_ENTRANCE, origin, angles )
+ fx.FXEnableRenderAlways()
+ fx.DisableHibernation()
+
+ wait sfxWait
+ EmitSoundAtPosition( TEAM_UNASSIGNED, origin, sfx )
+
+ wait totalTime - sfxWait
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
index 5fd7d101..5aee1104 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut
@@ -3,8 +3,20 @@ global function AddFactionXP
void function AddFactionXP( entity player, int amount )
{
string faction = GetFactionChoice( player )
+ int oldLevel = FactionGetLevel( player, faction )
+ int FactionXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.FACTION_LEVELED + "]" )
+
// increment xp
player.SetPersistentVar( "factionXP[" + faction + "]", min( FactionGetXP( player, faction ) + amount, FactionGetMaxXP( faction ) ) )
// note: no notif for faction level up
+ if ( FactionGetLevel( player, faction ) != oldLevel )
+ {
+ AddPlayerScore( player, "FactionLevelUp" )
+ IncrementPlayerChallengeFactionLeveledUp( player )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.FACTION_LEVELED + "]", FactionXPMatch + 1 )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForFactionLevels( player, faction, oldLevel, FactionGetLevel( player, faction ) )
+ }
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
index d6d578bb..4ed7ee4a 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut
@@ -1,7 +1,6 @@
global function AiGameModes_Init
-global function AiGameModes_SetGruntWeapons
-global function AiGameModes_SetSpectreWeapons
+global function AiGameModes_SetNPCWeapons
global function AiGameModes_SpawnDropShip
global function AiGameModes_SpawnDropPod
@@ -15,25 +14,20 @@ const INTRO_DROPSHIP_CUTOFF = 2000
struct
{
- array< string > gruntWeapons = [ "mp_weapon_rspn101" ]
- array< string > spectreWeapons = [ "mp_weapon_hemlok_smg" ]
+ table< string, array<string> > npcWeaponsTable // npcs have their default weapon in aisettings file
} file
void function AiGameModes_Init()
{
-
}
//------------------------------------------------------
-void function AiGameModes_SetGruntWeapons( array< string > weapons )
-{
- file.gruntWeapons = weapons
-}
-
-void function AiGameModes_SetSpectreWeapons( array< string > weapons )
+void function AiGameModes_SetNPCWeapons( string npcClass, array<string> weapons )
{
- file.spectreWeapons = weapons
+ if ( !( npcClass in file.npcWeaponsTable ) )
+ file.npcWeaponsTable[ npcClass ] <- []
+ file.npcWeaponsTable[ npcClass ] = weapons
}
//------------------------------------------------------
@@ -59,7 +53,7 @@ void function AiGameModes_SpawnDropShip( vector pos, vector rot, int team, int c
foreach ( guy in guys )
{
- ReplaceWeapon( guy, file.gruntWeapons[ RandomInt( file.gruntWeapons.len() ) ], [] )
+ SetUpNPCWeapons( guy )
guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
}
@@ -68,31 +62,23 @@ void function AiGameModes_SpawnDropShip( vector pos, vector rot, int team, int c
}
-void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string content /*( ͡° ͜ʖ ͡°)*/, void functionref( array<entity> guys ) squadHandler = null )
+void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string content /*( ͡° ͜ʖ ͡°)*/, void functionref( array<entity> guys ) squadHandler = null, int flags = 0 )
{
- string squadName = MakeSquadName( team, UniqueString( "" ) )
- array<entity> guys
-
entity pod = CreateDropPod( pos, <0,0,0> )
- InitFireteamDropPod( pod )
-
+ InitFireteamDropPod( pod, flags )
+
+ waitthread LaunchAnimDropPod( pod, "pod_testpath", pos, rot )
+
+ string squadName = MakeSquadName( team, UniqueString( "" ) )
+ array<entity> guys
for ( int i = 0; i < 4 ;i++ )
{
entity npc = CreateNPC( content, team, pos,<0,0,0> )
DispatchSpawn( npc )
SetSquad( npc, squadName )
- switch ( content )
- {
- case "npc_soldier":
- ReplaceWeapon( npc, file.gruntWeapons[ RandomInt( file.gruntWeapons.len() ) ], [] )
- break
-
- case "npc_spectre":
- ReplaceWeapon( npc, file.spectreWeapons[ RandomInt( file.spectreWeapons.len() ) ], [] )
- break
- }
+ SetUpNPCWeapons( npc )
npc.SetParent( pod, "ATTACH", true )
@@ -100,37 +86,100 @@ void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string
guys.append( npc )
}
- // The order here is different so we can show on minimap while were still falling
+ ActivateFireteamDropPod( pod, guys )
+
+ // start searching for enemies
if ( squadHandler != null )
thread squadHandler( guys )
-
- waitthread LaunchAnimDropPod( pod, "pod_testpath", pos, rot )
-
- ActivateFireteamDropPod( pod, guys )
}
+const float REAPER_WARPFALL_DELAY = 4.7 // same as fd does
void function AiGameModes_SpawnReaper( vector pos, vector rot, int team, string aiSettings = "", void functionref( entity reaper ) reaperHandler = null )
{
+ float reaperLandTime = REAPER_WARPFALL_DELAY + 1.2 // reaper takes ~1.2s to warpfall
+ thread HotDrop_Spawnpoint( pos, team, reaperLandTime, false, damagedef_reaper_fall )
+
+ wait REAPER_WARPFALL_DELAY
entity reaper = CreateSuperSpectre( team, pos, rot )
+ reaper.EndSignal( "OnDestroy" )
+ // reaper highlight
+ Highlight_SetFriendlyHighlight( reaper, "sp_enemy_pilot" )
+ reaper.Highlight_SetParam( 1, 0, < 1,1,1 > )
+ SetDefaultMPEnemyHighlight( reaper )
+ Highlight_SetEnemyHighlight( reaper, "enemy_titan" )
+
SetSpawnOption_Titanfall( reaper )
SetSpawnOption_Warpfall( reaper )
if ( aiSettings != "" )
SetSpawnOption_AISettings( reaper, aiSettings )
+ HideName( reaper ) // prevent flash a name onto it
DispatchSpawn( reaper )
-
+
+ reaper.WaitSignal( "WarpfallComplete" )
+ ShowName( reaper ) // show name again after drop
if ( reaperHandler != null )
thread reaperHandler( reaper )
}
+// copied from cl_replacement_titan_hud.gnut
+void function HotDrop_Spawnpoint( vector origin, int team, float impactTime, bool hasFriendlyWarning = false, int damageDef = -1 )
+{
+ array<entity> targetEffects = []
+ vector surfaceNormal = < 0, 0, 1 >
+
+ int index = GetParticleSystemIndex( $"P_ar_titan_droppoint" )
+
+ if( hasFriendlyWarning )
+ {
+ entity effectFriendly = StartParticleEffectInWorld_ReturnEntity( index, origin, surfaceNormal )
+ SetTeam( effectFriendly, team )
+ EffectSetControlPointVector( effectFriendly, 1, FRIENDLY_COLOR_FX )
+ effectFriendly.kv.VisibilityFlags = ENTITY_VISIBLE_TO_FRIENDLY
+ effectFriendly.DisableHibernation() // prevent it from fading out
+ targetEffects.append( effectFriendly )
+ }
+
+ entity effectEnemy = StartParticleEffectInWorld_ReturnEntity( index, origin, surfaceNormal )
+ SetTeam( effectEnemy, team )
+ EffectSetControlPointVector( effectEnemy, 1, ENEMY_COLOR_FX )
+ effectEnemy.kv.VisibilityFlags = ENTITY_VISIBLE_TO_ENEMY
+ effectEnemy.DisableHibernation() // prevent it from fading out
+ targetEffects.append( effectEnemy )
+
+ // so enemy npcs will mostly avoid them
+ entity damageAreaInfo
+ if ( damageDef > -1 )
+ {
+ damageAreaInfo = CreateEntity( "info_target" )
+ DispatchSpawn( damageAreaInfo )
+ AI_CreateDangerousArea_DamageDef( damageDef, damageAreaInfo, team, true, true )
+ }
+
+ wait impactTime
+
+ // clean up
+ foreach( entity targetEffect in targetEffects )
+ {
+ if ( IsValid( targetEffect ) )
+ EffectStop( targetEffect )
+ }
+ if ( IsValid( damageAreaInfo ) )
+ damageAreaInfo.Destroy()
+}
+
// including aisettings stuff specifically for at bounty titans
+const float TITANFALL_WARNING_DURATION = 5.0
void function AiGameModes_SpawnTitan( vector pos, vector rot, int team, string setFile, string aiSettings = "", void functionref( entity titan ) titanHandler = null )
{
entity titan = CreateNPCTitan( setFile, TEAM_BOTH, pos, rot )
SetSpawnOption_Titanfall( titan )
SetSpawnOption_Warpfall( titan )
+
+ // modified: do a hotdrop spawnpoint warning
+ thread HotDrop_Spawnpoint( pos, team, TITANFALL_WARNING_DURATION, false, damagedef_titan_fall )
if ( aiSettings != "" )
SetSpawnOption_AISettings( titan, aiSettings )
@@ -141,12 +190,27 @@ void function AiGameModes_SpawnTitan( vector pos, vector rot, int team, string s
thread titanHandler( titan )
}
-// entity.ReplaceActiveWeapon gave grunts archers sometimes, this is my replacement for it
-void function ReplaceWeapon( entity guy, string weapon, array<string> mods )
+void function SetUpNPCWeapons( entity guy )
{
- guy.TakeActiveWeapon()
- guy.GiveWeapon( weapon, mods )
- guy.SetActiveWeaponByName( weapon )
+ string className = guy.GetClassName()
+
+ array<string> mainWeapons
+ if ( className in file.npcWeaponsTable )
+ mainWeapons = file.npcWeaponsTable[ className ]
+
+ if ( mainWeapons.len() == 0 ) // no valid weapons
+ return
+
+ // take off existing main weapons, or sometimes they'll have a archer by default
+ foreach ( entity weapon in guy.GetMainWeapons() )
+ guy.TakeWeapon( weapon.GetWeaponClassName() )
+
+ if ( mainWeapons.len() > 0 )
+ {
+ string weaponName = mainWeapons[ RandomInt( mainWeapons.len() ) ]
+ guy.GiveWeapon( weaponName )
+ guy.SetActiveWeaponByName( weaponName )
+ }
}
// Checks if we can spawn a dropship at a node, this should guarantee dropship ziplines
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
index 9a94b848..7d73c926 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut
@@ -1,22 +1,36 @@
untyped
global function GamemodeAITdm_Init
-const SQUADS_PER_TEAM = 3
+// these are now default settings
+const int SQUADS_PER_TEAM = 4
-const REAPERS_PER_TEAM = 2
+const int REAPERS_PER_TEAM = 2
-const LEVEL_SPECTRES = 125
-const LEVEL_STALKERS = 380
-const LEVEL_REAPERS = 500
+const int LEVEL_SPECTRES = 125
+const int LEVEL_STALKERS = 380
+const int LEVEL_REAPERS = 500
+
+// add settings
+global function AITdm_SetSquadsPerTeam
+global function AITdm_SetReapersPerTeam
+global function AITdm_SetLevelSpectres
+global function AITdm_SetLevelStalkers
+global function AITdm_SetLevelReapers
struct
{
// Due to team based escalation everything is an array
- array< int > levels = [ LEVEL_SPECTRES, LEVEL_SPECTRES ]
+ array< int > levels = [] // Initilazed in `Spawner_Threaded`
array< array< string > > podEntities = [ [ "npc_soldier" ], [ "npc_soldier" ] ]
array< bool > reapers = [ false, false ]
-} file
+ // default settings
+ int squadsPerTeam = SQUADS_PER_TEAM
+ int reapersPerTeam = REAPERS_PER_TEAM
+ int levelSpectres = LEVEL_SPECTRES
+ int levelStalkers = LEVEL_STALKERS
+ int levelReapers = LEVEL_REAPERS
+} file
void function GamemodeAITdm_Init()
{
@@ -34,18 +48,48 @@ void function GamemodeAITdm_Init()
if ( GetCurrentPlaylistVarInt( "aitdm_archer_grunts", 0 ) == 0 )
{
- AiGameModes_SetGruntWeapons( [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] )
- AiGameModes_SetSpectreWeapons( [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] )
+ AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] )
+ AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] )
+ AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_hemlok_smg", "mp_weapon_lstar", "mp_weapon_mastiff" ] )
}
else
{
- AiGameModes_SetGruntWeapons( [ "mp_weapon_rocket_launcher" ] )
- AiGameModes_SetSpectreWeapons( [ "mp_weapon_rocket_launcher" ] )
+ AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rocket_launcher" ] )
+ AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_rocket_launcher" ] )
+ AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_rocket_launcher" ] )
}
ScoreEvent_SetupEarnMeterValuesForMixedModes()
+ SetupGenericTDMChallenge()
}
+// add settings
+void function AITdm_SetSquadsPerTeam( int squads )
+{
+ file.squadsPerTeam = squads
+}
+
+void function AITdm_SetReapersPerTeam( int reapers )
+{
+ file.reapersPerTeam = reapers
+}
+
+void function AITdm_SetLevelSpectres( int level )
+{
+ file.levelSpectres = level
+}
+
+void function AITdm_SetLevelStalkers( int level )
+{
+ file.levelStalkers = level
+}
+
+void function AITdm_SetLevelReapers( int level )
+{
+ file.levelReapers = level
+}
+//
+
// Starts skyshow, this also requiers AINs but doesn't crash if they're missing
void function OnPrematchStart()
{
@@ -74,10 +118,12 @@ void function HandleScoreEvent( entity victim, entity attacker, var damageInfo )
// Basic checks
if ( victim == attacker || !( attacker.IsPlayer() || attacker.IsTitan() ) || GetGameState() != eGameState.Playing )
return
-
// Hacked spectre filter
if ( victim.GetOwner() == attacker )
return
+ // NPC titans without an owner player will not count towards any team's score
+ if ( attacker.IsNPC() && attacker.IsTitan() && !IsValid( GetPetTitanOwner( attacker ) ) )
+ return
// Split score so we can check if we are over the score max
// without showing the wrong value on client
@@ -189,7 +235,7 @@ void function SpawnIntroBatch_Threaded( int team )
int ships = shipNodes.len()
- for ( int i = 0; i < SQUADS_PER_TEAM; i++ )
+ for ( int i = 0; i < file.squadsPerTeam; i++ )
{
if ( pods != 0 || ships == 0 )
{
@@ -234,6 +280,7 @@ void function Spawner_Threaded( int team )
// used to index into escalation arrays
int index = team == TEAM_MILITIA ? 0 : 1
+ file.levels = [ file.levelSpectres, file.levelSpectres ] // due we added settings, should init levels here!
while( true )
{
@@ -248,7 +295,7 @@ void function Spawner_Threaded( int team )
if ( file.reapers[ index ] )
{
array< entity > points = SpawnPoints_GetDropPod()
- if ( reaperCount < REAPERS_PER_TEAM )
+ if ( reaperCount < file.reapersPerTeam )
{
entity node = points[ GetSpawnPointIndex( points, team ) ]
waitthread AiGameModes_SpawnReaper( node.GetOrigin(), node.GetAngles(), team, "npc_super_spectre_aitdm", ReaperHandler )
@@ -256,7 +303,7 @@ void function Spawner_Threaded( int team )
}
// NORMAL SPAWNS
- if ( count < SQUADS_PER_TEAM * 4 - 2 )
+ if ( count < file.squadsPerTeam * 4 - 2 )
{
string ent = file.podEntities[ index ][ RandomInt( file.podEntities[ index ].len() ) ]
@@ -302,19 +349,19 @@ void function Escalate( int team )
// Based on score escalate a team
switch ( file.levels[ index ] )
{
- case LEVEL_SPECTRES:
- file.levels[ index ] = LEVEL_STALKERS
+ case file.levelSpectres:
+ file.levels[ index ] = file.levelStalkers
file.podEntities[ index ].append( "npc_spectre" )
SetGlobalNetInt( defcon, 2 )
return
- case LEVEL_STALKERS:
- file.levels[ index ] = LEVEL_REAPERS
+ case file.levelStalkers:
+ file.levels[ index ] = file.levelReapers
file.podEntities[ index ].append( "npc_stalker" )
SetGlobalNetInt( defcon, 3 )
return
- case LEVEL_REAPERS:
+ case file.levelReapers:
file.reapers[ index ] = true
SetGlobalNetInt( defcon, 4 )
return
@@ -351,30 +398,47 @@ int function GetSpawnPointIndex( array< entity > points, int team )
// AI can also flee deeper into their zone suggesting someone spent way too much time on this
void function SquadHandler( array<entity> guys )
{
+ int team = guys[0].GetTeam()
+ // show the squad enemy radar
+ array<entity> players = GetPlayerArrayOfEnemies( team )
+ foreach ( entity guy in guys )
+ {
+ if ( IsAlive( guy ) )
+ {
+ foreach ( player in players )
+ guy.Minimap_AlwaysShow( 0, player )
+ }
+ }
+
// Not all maps have assaultpoints / have weird assault points ( looking at you ac )
// So we use enemies with a large radius
- array< entity > points = GetNPCArrayOfEnemies( guys[0].GetTeam() )
-
- if ( points.len() == 0 )
+ while ( GetNPCArrayOfEnemies( team ).len() == 0 ) // if we can't find any enemy npcs, keep waiting
+ WaitFrame()
+
+ // our waiting is end, check if any soldiers left
+ bool squadAlive = false
+ foreach ( entity guy in guys )
+ {
+ if ( IsAlive( guy ) )
+ squadAlive = true
+ else
+ guys.removebyvalue( guy )
+ }
+ if ( !squadAlive )
return
+
+ array<entity> points = GetNPCArrayOfEnemies( team )
vector point
point = points[ RandomInt( points.len() ) ].GetOrigin()
- array<entity> players = GetPlayerArrayOfEnemies( guys[0].GetTeam() )
-
- // Setup AI
+ // Setup AI, first assault point
foreach ( guy in guys )
{
guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
guy.AssaultPoint( point )
guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others
-
- // show on enemy radar
- foreach ( player in players )
- guy.Minimap_AlwaysShow( 0, player )
-
-
+
//thread AITdm_CleanupBoredNPCThread( guy )
}
@@ -392,16 +456,32 @@ void function SquadHandler( array<entity> guys )
// Stop func if our squad has been killed off
if ( guys.len() == 0 )
return
+ }
+
+ // Get point and send our whole squad to it
+ points = GetNPCArrayOfEnemies( team )
+ if ( points.len() == 0 ) // can't find any points here
+ {
+ // Have to wait some amount of time before continuing
+ // because if we don't the server will continue checking this
+ // forever, aren't loops fun?
+ // This definitely didn't waste ~8 hours of my time reverting various
+ // launcher PRs before finding this mods PR that caused servers to
+ // freeze forever before having their process killed by the dedi watchdog
+ // without any logging. If anyone reads this, PLEASE add logging to your scripts
+ // for when weird edge cases happen, it can literally only help debugging. -Spoon
+ WaitFrame()
+ continue
+ }
- // Get point and send guy to it
- points = GetNPCArrayOfEnemies( guy.GetTeam() )
- if ( points.len() == 0 )
- continue
-
- point = points[ RandomInt( points.len() ) ].GetOrigin()
-
- guy.AssaultPoint( point )
+ point = points[ RandomInt( points.len() ) ].GetOrigin()
+
+ foreach ( guy in guys )
+ {
+ if ( IsAlive( guy ) )
+ guy.AssaultPoint( point )
}
+
wait RandomFloatRange(5.0,15.0)
}
}
@@ -507,4 +587,4 @@ void function AITdm_CleanupBoredNPCThread( entity guy )
print( "cleaning up bored npc: " + guy + " from team " + guy.GetTeam() )
guy.Destroy()
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
index 915e03e0..9cf0021d 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut
@@ -1,12 +1,54 @@
+untyped // AddCallback_OnUseEntity() needs this
global function GamemodeAt_Init
global function RateSpawnpoints_AT
-const int BH_AI_TEAM = TEAM_BOTH
-const int BOUNTY_TITAN_DAMAGE_POOL = 400 // Rewarded for damage
-const int BOUNTY_TITAN_KILL_REWARD = 100 // Rewarded for kill
-const float WAVE_STATE_TRANSITION_TIME = 5.0
+// Old bobr note which still applies after a year :)
+// IMPLEMENTATION NOTES:
+// bounty hunt is a mode that was clearly pretty heavily developed, and had alot of scrapped concepts (i.e. most wanted player bounties, turret bounties, collectable blackbox objectives)
+// in the interest of time, this script isn't gonna support any of that atm
+// alot of the remote functions also take parameters that aren't used, i'm not gonna populate these and just use default values for now instead
+// however, if you do want to mess with this stuff, almost all the remote functions for this stuff are still present in cl_gamemode_at, and should work fine with minimal fuckery in my experience
+
+
+// Bank settings
+const float AT_BANKS_OPEN_DURATION = 45.0 // Bank open time
+const int AT_BANK_DEPOSIT_RATE = 10 // Amount deposited per second
+const int AT_BANK_DEPOSIT_RADIUS = 256 // bank radius for depositing
+const float AT_BANK_FORCE_CLOSE_DELAY = 4.0 // If all bonus money has been deposited close the banks after this constant early
+
+// TODO: The reference function no longer exists, check if this still holds true
+// VoyageDB: HACK score events... respawn made things in AT_SetScoreEventOverride() really messed up, have to do some hack here
+const array<string> AT_ENABLE_SCOREEVENTS =
+[
+ // these are disabled in AT_SetScoreEventOverride(), but related scoreEvents are not implemented into gamemode
+ // needs to re-enable them
+ "DoomTitan",
+ "DoomAutoTitan"
+]
+const array<string> AT_DISABLE_SCOREEVENTS =
+[
+ // these are missed in AT_SetScoreEventOverride(), but game actually used them
+ // needs to disable them
+ "KillStalker"
+]
+
+// Wave settings
+// General
+const int AT_AI_TEAM = TEAM_BOTH // Allow AI to attack and be attacked by both player teams
+const float AT_FIRST_WAVE_START_DELAY = 10.0 // First wave has an extra delay before begining
+const float AT_WAVE_TRANSITION_DELAY = 5.0 // Time between each wave and banks opening/closing
+const float AT_WAVE_END_ANNOUNCEMENT_DELAY = 1.0 // Extra wait before announcing wave cleaned
+
+// Squad settings
+const int AT_DROPPOD_SQUADS_ALLOWED_ON_FIELD = 4 // default is 4 droppod squads on field, won't use if AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK turns on // TODO: verify this
-const array<string> VALID_BOUNTY_TITAN_SETTINGS = [
+// Titan bounty settings
+const float AT_BOUNTY_TITAN_CHECK_DELAY = 10.0 // wait for bounty titans landing before we start checking their life state
+const float AT_BOUNTY_TITAN_HEALTH_MULTIPLIER = 3 // TODO: Verify this
+
+// Titan boss settings, check sh_gamemode_at.nut for more info
+const array<string> AT_BOUNTY_TITANS_AI_SETTINGS =
+[
"npc_titan_atlas_stickybomb_bounty",
"npc_titan_atlas_tracker_bounty",
"npc_titan_ogre_minigun_bounty",
@@ -16,102 +58,582 @@ const array<string> VALID_BOUNTY_TITAN_SETTINGS = [
"npc_titan_atlas_vanguard_bounty"
]
+// Extra
+// Respawn didn't use the "totalAllowedOnField" for npc spawning, they only allow 1 squad to be on field for each type of npc. enabling this might cause too much npcs spawning and crash the game
+const bool AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK = false
-// IMPLEMENTATION NOTES:
-// bounty hunt is a mode that was clearly pretty heavily developed, and had alot of scrapped concepts (i.e. most wanted player bounties, turret bounties, collectable blackbox objectives)
-// in the interest of time, this script isn't gonna support any of that atm
-// alot of the remote functions also take parameters that aren't used, i'm not gonna populate these and just use default values for now instead
-// however, if you do want to mess with this stuff, almost all the remote functions for this stuff are still present in cl_gamemode_at, and should work fine with minimal fuckery in my experience
+// Objectives
+const int AT_OBJECTIVE_EMPTY = -1 // Remove objective
+const int AT_OBJECTIVE_KILL_DZ = 104 // #AT_OBJECTIVE_KILL_DZ
+const int AT_OBJECTIVE_KILL_DZ_MULTI = 105 // #AT_OBJECTIVE_KILL_DZ_MULTI
+const int AT_OBJECTIVE_KILL_BOSS = 106 // #AT_OBJECTIVE_KILL_BOSS
+const int AT_OBJECTIVE_KILL_BOSS_MULTI = 107 // #AT_OBJECTIVE_KILL_BOSS_MULTI
+const int AT_OBJECTIVE_BANK_OPEN = 109 // #AT_BANK_OPEN_OBJECTIVE
-struct {
- array<entity> campsToRegisterOnEntitiesDidLoad
+// When a player tries to deposit when they have 0 bonus money
+// we show a help mesage, this is the ratelimit for that message
+// so that we dont spam it too much
+const float AT_PLAYER_HUD_MESSAGE_COOLDOWN = 2.5
+// Due to bad navmeshes NPCs may wonder off to bumfuck nowhere or the game
+// might teleport them into the map while trying to correct their position
+// This obviously breaks bounty hunt where the objective is to kill ALL ai
+// so we try to cleanup the camps after a set amount of time of inactivity
+const int AT_CAMP_BORED_NPCS_LEFT_TO_START_CLEANUP = 3
+const float AT_CAMP_BORED_CLEANUP_WAIT = 60.0
+struct
+{
array<entity> banks
array<AT_WaveOrigin> camps
+
+ // Used to track ScriptmanagedEntArrays of ai squads
+ table< int, array<int> > campScriptEntArrays
- table< int, table< string, int > > trackedCampNPCSpawns
+ table< entity, bool > titanIsBountyBoss
+ table< entity, int > bountyTitanRewards
+ table< entity, int > npcStolenBonus
+ table< entity, bool > playerBankUploading
+ table< entity, table<entity, int> > playerSavedBountyDamage
+ table< entity, float > playerHudMessageAllowedTime
} file
void function GamemodeAt_Init()
{
- AddCallback_GameStateEnter( eGameState.Playing, RunATGame )
-
+ // wave
+ RegisterSignal( "ATWaveEnd" )
+ // camp
+ RegisterSignal( "ATCampClean" )
+ RegisterSignal( "ATAllCampsClean" )
+
+ // Set-up score callbacks
+ ScoreEvent_SetupEarnMeterValuesForMixedModes()
+ AddCallback_OnPlayerKilled( AT_PlayerOrNPCKilledScoreEvent )
+ AddCallback_OnNPCKilled( AT_PlayerOrNPCKilledScoreEvent )
+
+ // Set npc weapons
+ AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] )
+ AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] )
+ AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_hemlok_smg", "mp_weapon_lstar", "mp_weapon_mastiff" ] )
+
+ // Gamestate callbacks
+ AddCallback_GameStateEnter( eGameState.Prematch, OnATGamePrematch )
+ AddCallback_GameStateEnter( eGameState.Playing, OnATGamePlaying )
+
+ // Initilaze player
AddCallback_OnClientConnected( InitialiseATPlayer )
- AddSpawnCallbackEditorClass( "info_target", "info_attrition_bank", CreateATBank )
- AddSpawnCallbackEditorClass( "info_target", "info_attrition_camp", CreateATCamp )
- AddCallback_EntitiesDidLoad( CreateATCamps_Delayed )
+ // Initilaze gamemode entities
+ AddCallback_EntitiesDidLoad( OnEntitiesDidLoad )
}
void function RateSpawnpoints_AT( int checkclass, array<entity> spawnpoints, int team, entity player )
{
- RateSpawnpoints_Generic( checkclass, spawnpoints, team, player ) // temp
+ RateSpawnpoints_Generic( checkclass, spawnpoints, team, player )
}
-// world and player inits
+
+
+////////////////////////////////////////
+///// GAMESTATE CALLBACK FUNCTIONS /////
+////////////////////////////////////////
+
+void function OnATGamePrematch()
+{
+ AT_ScoreEventsValueSetUp()
+}
+
+void function OnATGamePlaying()
+{
+ thread AT_GameLoop_Threaded()
+}
+
+////////////////////////////////////////////
+///// GAMESTATE CALLBACK FUNCTIONS END /////
+////////////////////////////////////////////
+
+
+
+////////////////////////////
+///// PLAYER FUNCTIONS /////
+////////////////////////////
void function InitialiseATPlayer( entity player )
{
Remote_CallFunction_NonReplay( player, "ServerCallback_AT_OnPlayerConnected" )
+ player.SetPlayerNetInt( "AT_bonusPointMult", 1 )
+ file.playerBankUploading[ player ] <- false
+ file.playerSavedBountyDamage[ player ] <- {}
+ file.playerHudMessageAllowedTime[ player ] <- 0.0
+ thread AT_PlayerTitleThink( player )
+ thread AT_PlayerObjectiveThink( player )
}
-void function CreateATBank( entity spawnpoint )
+void function AT_PlayerTitleThink( entity player )
{
- entity bank = CreatePropDynamic( spawnpoint.GetModelName(), spawnpoint.GetOrigin(), spawnpoint.GetAngles(), SOLID_VPHYSICS )
- bank.SetScriptName( "AT_Bank" )
-
- // create tracker ent
- // we don't need to store these at all, client just needs to get them
- DispatchSpawn( GetAvailableBankTracker( bank ) )
+ player.EndSignal( "OnDestroy" )
+
+ while ( true )
+ {
+ if ( GetGameState() == eGameState.Playing )
+ {
+ // Set player money count
+ player.SetTitle( "$" + string( AT_GetPlayerBonusPoints( player ) ) )
+
+ if( AT_GetPlayerBonusPoints( player ) >= 600 && !HasPlayerCompletedMeritScore( player ) ) //Challenge is: "Earn $600."
+ {
+ AddPlayerScore( player, "ChallengeATAssault" )
+ SetPlayerChallengeMeritScore( player )
+ }
+ }
+ else if ( GetGameState() >= eGameState.WinnerDetermined )
+ {
+ if ( player.IsTitan() )
+ player.SetTitle( GetTitanPlayerTitle( player ) )
+ else
+ player.SetTitle( "" )
+
+ return
+ }
+
+ WaitFrame()
+ }
+}
+
+string function GetTitanPlayerTitle( entity player )
+{
+ entity soul = player.GetTitanSoul()
+
+ if ( !IsValid( soul ) )
+ return ""
- thread PlayAnim( bank, "mh_inactive_idle" )
+ string settings = GetSoulPlayerSettings( soul )
+ var title = GetPlayerSettingsFieldForClassName( settings, "printname" )
+
+ if ( title == null )
+ return ""
- file.banks.append( bank )
+ return expect string( title )
+}
+
+void function AT_PlayerObjectiveThink( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ int curObjective = AT_OBJECTIVE_EMPTY
+ while ( true )
+ {
+ // game entered other state
+ if ( GetGameState() >= eGameState.WinnerDetermined )
+ {
+ player.SetPlayerNetInt( "gameInfoStatusText", AT_OBJECTIVE_EMPTY )
+ return
+ }
+
+ int nextObjective = AT_OBJECTIVE_EMPTY
+
+ // Determine objective text for player
+ if ( !IsAlive( player ) ) // Don't show objective to dead players
+ {
+ nextObjective = AT_OBJECTIVE_EMPTY
+ }
+ else // We're still alive
+ {
+ if ( GetGlobalNetBool( "banksOpen" ) )
+ {
+ nextObjective = AT_OBJECTIVE_BANK_OPEN
+ }
+ else if ( GetGlobalNetBool( "preBankPhase" ) )
+ {
+ nextObjective = AT_OBJECTIVE_EMPTY
+ }
+ else
+ {
+ // No checks have passed, try to do a "Kill all x near the marked dropzone" objective
+ int dropZoneActiveCount = 0
+ int bossAliveCount = 0
+ array<entity> campEnts
+ campEnts.append( GetGlobalNetEnt( "camp1Ent" ) )
+ campEnts.append( GetGlobalNetEnt( "camp2Ent" ) )
+
+ foreach ( entity ent in campEnts )
+ {
+ if ( IsValid( ent ) )
+ {
+ if ( ent.IsTitan() )
+ bossAliveCount += 1
+ else
+ dropZoneActiveCount += 1
+ }
+ }
+
+ switch( dropZoneActiveCount )
+ {
+ case 1:
+ nextObjective = AT_OBJECTIVE_KILL_DZ
+ break
+ case 2:
+ nextObjective = AT_OBJECTIVE_KILL_DZ_MULTI
+ break
+ }
+
+ switch( bossAliveCount )
+ {
+ case 1:
+ nextObjective = AT_OBJECTIVE_KILL_BOSS
+ break
+ case 2:
+ nextObjective = AT_OBJECTIVE_KILL_BOSS_MULTI
+ break
+ }
+
+ // We couldn't get an objective, set it to empty
+ if ( dropZoneActiveCount == 0 && bossAliveCount == 0 )
+ nextObjective = AT_OBJECTIVE_EMPTY
+ }
+ }
+
+ // Set the objective when changed
+ if ( curObjective != nextObjective )
+ {
+ player.SetPlayerNetInt( "gameInfoStatusText", nextObjective )
+ curObjective = nextObjective
+ }
+
+ WaitFrame()
+ }
}
-void function CreateATCamp( entity spawnpoint )
+////////////////////////////////
+///// PLAYER FUNCTIONS END /////
+////////////////////////////////
+
+
+
+////////////////////////////////////////
+///// GAMEMODE INITILAZE FUNCTIONS /////
+////////////////////////////////////////
+
+void function OnEntitiesDidLoad()
{
- // delay this so we don't do stuff before all spawns are initialised and that
- file.campsToRegisterOnEntitiesDidLoad.append( spawnpoint )
+ foreach ( entity info_target in GetEntArrayByClass_Expensive( "info_target" ) )
+ {
+ if( info_target.HasKey( "editorclass" ) )
+ {
+ switch( info_target.kv.editorclass )
+ {
+ case "info_attrition_bank":
+ entity bank = CreateEntity( "prop_script" )
+ bank.SetScriptName( "AT_Bank" ) // VoyageDB: don't know how to make client able to track it
+ bank.SetOrigin( info_target.GetOrigin() )
+ bank.SetAngles( info_target.GetAngles() )
+ DispatchSpawn( bank )
+ bank.kv.solid = SOLID_VPHYSICS
+ bank.SetModel( info_target.GetModelName() )
+
+ // Minimap icon init
+ bank.Minimap_SetCustomState( eMinimapObject_prop_script.AT_BANK )
+ bank.Minimap_SetAlignUpright( true )
+ bank.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ bank.Minimap_Hide( TEAM_IMC, null )
+ bank.Minimap_Hide( TEAM_MILITIA, null )
+
+ // Create tracker ent
+ // we don't need to store these at all, client just needs to get them
+ DispatchSpawn( GetAvailableBankTracker( bank ) )
+
+ // Make sure the bank is in it's disabled pose
+ thread PlayAnim( bank, "mh_inactive_idle" )
+ // Set the bank usable
+ AddCallback_OnUseEntity( bank, OnPlayerUseBank )
+ bank.SetUsable()
+ bank.SetUsePrompts( "#AT_USE_BANK_CLOSED", "#AT_USE_BANK_CLOSED" )
+
+ file.banks.append( bank )
+ break;
+ case "info_attrition_camp":
+ AT_WaveOrigin campStruct
+ campStruct.ent = info_target
+ campStruct.origin = info_target.GetOrigin()
+ campStruct.radius = expect string( info_target.kv.radius ).tofloat()
+ campStruct.height = expect string( info_target.kv.height ).tofloat()
+
+ // Assumes every info_attrition_camp will have all 9 phases, possibly not a good idea?
+ // TODO: verify this on all vanilla maps before release
+ for ( int i = 0; i < 9; i++ )
+ campStruct.phaseAllowed.append( expect string( info_target.kv[ "phase_" + ( i + 1 ) ] ) == "1" )
+
+ // Get droppod spawns within the camp
+ foreach ( entity spawnpoint in SpawnPoints_GetDropPod() )
+ {
+ vector campPos = info_target.GetOrigin()
+ vector spawnPos = spawnpoint.GetOrigin()
+ if ( Distance( campPos, spawnPos ) < campStruct.radius )
+ campStruct.dropPodSpawnPoints.append( spawnpoint )
+ }
+
+ // Get titan spawns within the camp
+ foreach ( entity spawnpoint in SpawnPoints_GetTitan() )
+ {
+ vector campPos = info_target.GetOrigin()
+ vector spawnPos = spawnpoint.GetOrigin()
+ if ( Distance( campPos, spawnPos ) < campStruct.radius )
+ campStruct.titanSpawnPoints.append( spawnpoint )
+ }
+
+ file.camps.append( campStruct )
+ break;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////
+///// GAMEMODE INITILAZE FUNCTIONS END /////
+////////////////////////////////////////////
+
+
+
+/////////////////////////////
+///// SCORING FUNCTIONS /////
+/////////////////////////////
+
+// TODO: Don't reward in postmatch
+// TODO: Dropping a titan on a bounty with it's dome-shield still up rewards you the bonus, but
+// it doesn't actually damage the bounty titan
+
+void function AT_ScoreEventsValueSetUp()
+{
+ ScoreEvent_SetEarnMeterValues( "KillTitan", 0.10, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "KillAutoTitan", 0.10, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "AttritionTitanKilled", 0.10, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "KillPilot", 0.10, 0.10 )
+ ScoreEvent_SetEarnMeterValues( "AttritionPilotKilled", 0.10, 0.10 )
+ ScoreEvent_SetEarnMeterValues( "AttritionBossKilled", 0.10, 0.20 )
+ ScoreEvent_SetEarnMeterValues( "AttritionGruntKilled", 0.02, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "AttritionSpectreKilled", 0.02, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "AttritionStalkerKilled", 0.02, 0.02, 0.5 )
+ ScoreEvent_SetEarnMeterValues( "AttritionSuperSpectreKilled", 0.10, 0.10, 0.5 )
+
+ // HACK
+ foreach ( string eventName in AT_ENABLE_SCOREEVENTS )
+ ScoreEvent_Enable( GetScoreEvent( eventName ) )
+
+ foreach ( string eventName in AT_DISABLE_SCOREEVENTS )
+ ScoreEvent_Disable( GetScoreEvent( eventName ) )
}
-void function CreateATCamps_Delayed()
+void function AT_PlayerOrNPCKilledScoreEvent( entity victim, entity attacker, var damageInfo )
{
- // we delay registering camps until EntitiesDidLoad since they rely on spawnpoints and stuff, which might not all be ready in the creation callback
- // unsure if this would be an issue in practice, but protecting against it in case it would be
- foreach ( entity camp in file.campsToRegisterOnEntitiesDidLoad )
+ if ( !IsValid( attacker ) )
+ return
+
+ // Suicide
+ if ( attacker == victim )
{
- AT_WaveOrigin campStruct
- campStruct.ent = camp
- campStruct.origin = camp.GetOrigin()
- campStruct.radius = expect string( camp.kv.radius ).tofloat()
- campStruct.height = expect string( camp.kv.height ).tofloat()
+ if ( victim.IsPlayer() )
+ AT_PlayerBonusLoss( victim, AT_GetPlayerBonusPoints( victim ) / 2 )
- // assumes every info_attrition_camp will have all 9 phases, possibly not a good idea?
- for ( int i = 0; i < 9; i++ )
- campStruct.phaseAllowed.append( expect string( camp.kv[ "phase_" + ( i + 1 ) ] ) == "1" )
+ return
+ }
+
+ // NPC is the attacker
+ if ( !attacker.IsPlayer() )
+ {
+ if ( attacker.IsTitan() && IsValid( GetPetTitanOwner( attacker ) ) ) // Re-asign attacker
+ attacker = GetPetTitanOwner( attacker )
+ else // NPC steals money from player, killing it will award the stolen bonus + normal reward
+ AT_NPCTryStealBonusPoints( attacker, victim )
- // get droppod spawns
- foreach ( entity spawnpoint in SpawnPoints_GetDropPod() )
- if ( Distance( camp.GetOrigin(), spawnpoint.GetOrigin() ) < 1500.0 )
- campStruct.dropPodSpawnPoints.append( spawnpoint )
+ return
+ }
+
+ // Get event name
+ string eventName = GetAttritionScoreEventName( victim.GetClassName() )
+
+ if ( victim.IsTitan() ) // titan specific
+ eventName = GetAttritionScoreEventNameFromAI( victim )
+
+ if ( eventName == "" ) // no valid scoreEvent
+ return
+
+ int scoreVal = ScoreEvent_GetPointValue( GetScoreEvent( eventName ) )
+
+ // pet titan check
+ if ( victim.IsTitan() && IsValid( GetPetTitanOwner( victim ) ) )
+ {
+ if( GetPetTitanOwner( victim ) == attacker ) // Player ejected
+ return
- foreach ( entity spawnpoint in SpawnPoints_GetTitan() )
- if ( Distance( camp.GetOrigin(), spawnpoint.GetOrigin() ) < 1500.0 )
- campStruct.titanSpawnPoints.append( spawnpoint )
+ if( GetPetTitanOwner( victim ).IsPlayer() ) // Killed player npc titan
+ return
+
+ scoreVal = ATTRITION_SCORE_TITAN_MIN
+ }
+
+ // killed npc
+ if ( victim.IsNPC() )
+ {
+ int bonusFromNPC = 0
+ // If NPC was carrying a bonus award it to the attacker
+ if ( victim in file.npcStolenBonus )
+ {
+ bonusFromNPC = file.npcStolenBonus[ victim ]
+ delete file.npcStolenBonus[ victim ]
+ }
+ AT_AddPlayerBonusPointsForEntityKilled( attacker, scoreVal, damageInfo, bonusFromNPC )
+ AddPlayerScore( attacker, eventName ) // we add scoreEvent here, since basic score events has been overwrited by sh_gamemode_at.nut
+ // update score difference and scoreboard
+ AT_AddToPlayerTeamScore( attacker, scoreVal )
+ }
+
+ // bonus stealing check
+ if ( victim.IsPlayer() )
+ AT_PlayerTryStealBonusPoints( attacker, victim, damageInfo )
+}
+
+bool function AT_NPCTryStealBonusPoints( entity attacker, entity victim )
+{
+ // basic checks
+ if ( !attacker.IsNPC() )
+ return false
- // todo: turret spawns someday maybe
+ if ( !victim.IsPlayer() )
+ return false
+
+ int victimBonus = AT_GetPlayerBonusPoints( victim )
+ int bonusToSteal = victimBonus / 2 // npc always steal half the bonus from player, no extra bonus for killing the player
+ if ( bonusToSteal == 0 ) // player has no bonus!
+ return false
+
+ if ( !( attacker in file.npcStolenBonus ) ) // init
+ file.npcStolenBonus[ attacker ] <- 0
- file.camps.append( campStruct )
+ file.npcStolenBonus[ attacker ] += bonusToSteal
+
+ AT_PlayerBonusLoss( victim, bonusToSteal ) // tell victim of bonus stolen
+
+ if ( !( attacker in file.titanIsBountyBoss ) ) // if attacker npc is not a bounty titan, we make them highlighted
+ NPCBountyStolenHighlight( attacker )
+
+ return true
+}
+
+void function NPCBountyStolenHighlight( entity npc )
+{
+ Highlight_SetEnemyHighlight( npc, "enemy_boss_bounty" )
+}
+
+bool function AT_PlayerTryStealBonusPoints( entity attacker, entity victim, var damageInfo )
+{
+ // basic checks
+ if ( !attacker.IsPlayer() )
+ return false
+
+ if ( !victim.IsPlayer() )
+ return false
+
+ int victimBonus = AT_GetPlayerBonusPoints( victim )
+
+ int minScoreCanSteal = ATTRITION_SCORE_PILOT_MIN
+ if ( victim.IsTitan() )
+ minScoreCanSteal = ATTRITION_SCORE_TITAN_MIN
+
+ int bonusToSteal = victimBonus / 2
+ int attackerScore = bonusToSteal
+ bool realStealBonus = true
+ if ( bonusToSteal <= minScoreCanSteal ) // no enough bonus to steal
+ {
+ attackerScore = minScoreCanSteal // give attacker min bonus
+ realStealBonus = false // we don't do attacker steal events below, just half victim's bonus
}
+
+ // servercallback
+ int victimEHandle = victim.GetEncodedEHandle()
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+
+ // only do attacker events if victim has enough bonus to steal
+ if ( realStealBonus )
+ {
+ Remote_CallFunction_NonReplay(
+ attacker,
+ "ServerCallback_AT_PlayerKillScorePopup",
+ bonusToSteal, // stolenScore
+ victimEHandle, // victimEHandle
+ damageOrigin.x, // x
+ damageOrigin.y, // y
+ damageOrigin.z // z
+ )
+ }
+ else // otherwise we do a normal entity killed scoreEvent
+ {
+ AT_AddPlayerBonusPointsForEntityKilled( attacker, attackerScore, damageInfo )
+ }
+
+ // update score difference and scoreboard
+ AT_AddToPlayerTeamScore( attacker, minScoreCanSteal )
+
+ // steal bonus
+ // only do attacker events if victim has enough bonus to steal
+ if ( realStealBonus )
+ {
+ AT_AddPlayerBonusPoints( attacker, bonusToSteal )
+ AddPlayerScore( attacker, "AttritionBonusStolen" )
+ }
+
+ // tell victim of bonus stolen
+ AT_PlayerBonusLoss( victim, bonusToSteal )
- file.campsToRegisterOnEntitiesDidLoad.clear()
+ return realStealBonus
}
-// scoring funcs
+void function AT_PlayerBonusLoss( entity player, int bonusLoss )
+{
+ AT_AddPlayerBonusPoints( player, -bonusLoss )
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_ShowStolenBonus",
+ bonusLoss // stolenScore
+ )
+}
+
+// team score meter
+void function AT_AddToPlayerTeamScore( entity player, int amount )
+{
+ // do not award any score after the match is ended
+ if ( GetGameState() > eGameState.Playing )
+ return
+
+ // add to scoreboard
+ player.AddToPlayerGameStat( PGS_ASSAULT_SCORE, amount )
+
+ // Check score so we dont go over max
+ if ( GameRules_GetTeamScore(player.GetTeam()) + amount > GetScoreLimit_FromPlaylist() )
+ {
+ amount = GetScoreLimit_FromPlaylist() - GameRules_GetTeamScore(player.GetTeam())
+ }
+
+ // update score difference
+ AddTeamScore( player.GetTeam(), amount )
+}
+
+// bonus points, players earn from killing
+void function AT_AddPlayerBonusPoints( entity player, int amount )
+{
+ // do not award any score after the match is ended
+ if ( GetGameState() > eGameState.Playing )
+ return
+
+ // add to scoreboard
+ player.AddToPlayerGameStat( PGS_SCORE, amount )
+ AT_SetPlayerBonusPoints( player, player.GetPlayerNetInt( "AT_bonusPoints" ) + ( player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256 ) + amount )
+}
+
+int function AT_GetPlayerBonusPoints( entity player )
+{
+ return player.GetPlayerNetInt( "AT_bonusPoints" ) + player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256
+}
-// don't use this where possible as it doesn't set score and stuff
-void function AT_SetPlayerCash( entity player, int amount )
+void function AT_SetPlayerBonusPoints( entity player, int amount )
{
// split into stacks of 256 where necessary
int stacks = amount / 256 // automatically rounds down because int division
@@ -120,198 +642,986 @@ void function AT_SetPlayerCash( entity player, int amount )
player.SetPlayerNetInt( "AT_bonusPoints", amount - stacks * 256 )
}
-void function AT_AddPlayerCash( entity player, int amount )
+// total points, the value player actually uploaded to team score
+void function AT_AddPlayerTotalPoints( entity player, int amount )
{
- // update score difference
- AddTeamScore( player.GetTeam(), amount / 2 )
- AT_SetPlayerCash( player, player.GetPlayerNetInt( "AT_bonusPoints" ) + ( player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256 ) + amount )
+ // update score difference and scoreboard, calling this function meaning player has deposited their bonus to team score
+ AT_AddToPlayerTeamScore( player, amount )
+ AT_SetPlayerTotalPoints( player, player.GetPlayerNetInt( "AT_totalPoints" ) + ( player.GetPlayerNetInt( "AT_totalPoints256" ) * 256 ) + amount )
+}
+
+void function AT_SetPlayerTotalPoints( entity player, int amount )
+{
+ // split into stacks of 256 where necessary
+ int stacks = amount / 256 // automatically rounds down because int division
+
+ player.SetPlayerNetInt( "AT_totalPoints256", stacks )
+ player.SetPlayerNetInt( "AT_totalPoints", amount - stacks * 256 )
+}
+
+// earn points, seems not used
+void function AT_AddPlayerEarnedPoints( entity player, int amount )
+{
+ AT_SetPlayerBonusPoints( player, player.GetPlayerNetInt( "AT_earnedPoints" ) + ( player.GetPlayerNetInt( "AT_earnedPoints256" ) * 256 ) + amount )
+}
+
+void function AT_SetPlayerEarnedPoints( entity player, int amount )
+{
+ // split into stacks of 256 where necessary
+ int stacks = amount / 256 // automatically rounds down because int division
+
+ player.SetPlayerNetInt( "AT_earnedPoints256", stacks )
+ player.SetPlayerNetInt( "AT_earnedPoints", amount - stacks * 256 )
}
-// run gamestate
+// damaging bounty
+void function AT_AddPlayerBonusPointsForBossDamaged( entity player, entity victim, int amount, var damageInfo )
+{
+ AT_AddPlayerBonusPoints( player, amount )
+ // update score difference and scoreboard
+ AT_AddToPlayerTeamScore( player, amount )
+
+ // send servercallback for damaging
+ int bossEHandle = victim.GetEncodedEHandle()
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
-void function RunATGame()
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_BossDamageScorePopup",
+ amount, // damageScore
+ amount, // damageBonus
+ bossEHandle, // bossEHandle
+ damageOrigin.x, // x
+ damageOrigin.y, // y
+ damageOrigin.z // z
+ )
+}
+
+void function AT_AddPlayerBonusPointsForEntityKilled( entity player, int amount, var damageInfo, int extraBonus = 0 )
{
- thread RunATGame_Threaded()
+ AT_AddPlayerBonusPoints( player, amount + extraBonus )
+
+ // send servercallback for damaging
+ int attackerEHandle = player.GetEncodedEHandle()
+ vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo )
+
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_ShowATScorePopup",
+ attackerEHandle, // attackerEHandle
+ amount, // damageScore
+ amount + extraBonus, // damageBonus
+ damageOrigin.x, // damagePosX
+ damageOrigin.y, // damagePosX
+ damageOrigin.z, // damagePosX
+ 0 // damageType ( not used )
+ )
}
-void function RunATGame_Threaded()
+/////////////////////////////////
+///// SCORING FUNCTIONS END /////
+/////////////////////////////////
+
+
+
+//////////////////////////////
+///// GAMELOOP FUNCTIONS /////
+//////////////////////////////
+
+void function AT_GameLoop_Threaded()
{
svGlobal.levelEnt.EndSignal( "GameStateChanged" )
- OnThreadEnd( function()
- {
- SetGlobalNetBool( "banksOpen", false )
- })
+ // game end func
+ // TODO: Cant seem to be able to get this crash ???
+ OnThreadEnd
+ (
+ function()
+ {
+ // prevent crash before entity creation on map change
+ if ( GetGameState() >= eGameState.Prematch )
+ {
+ SetGlobalNetBool( "preBankPhase", false )
+ SetGlobalNetBool( "banksOpen", false )
+ }
+ }
+ )
- wait WAVE_STATE_TRANSITION_TIME // initial wait before first wave
-
- for ( int waveCount = 1; ; waveCount++ )
+ // Initial wait before first wave
+ wait AT_FIRST_WAVE_START_DELAY - AT_WAVE_TRANSITION_DELAY
+
+ int lastWaveId = -1
+ for ( int waveCount = 1; ; waveCount++ )
{
- wait WAVE_STATE_TRANSITION_TIME
+ wait AT_WAVE_TRANSITION_DELAY
// cap to number of real waves
- int waveId = ( waveCount / 2 )
- // last wave is clearly unfinished so don't use, just cap to last actually used one
- if ( waveId >= GetWaveDataSize() - 1 )
+ int waveId = ( waveCount - 1 ) / 2
+ int waveCapAmount = 2
+ waveId = int( min( waveId, GetWaveDataSize() - waveCapAmount ) )
+
+ // New wave dialogue
+ bool waveChanged = lastWaveId != waveId
+ if ( waveChanged )
+ {
+ PlayFactionDialogueToTeam( "bh_newWave", TEAM_IMC )
+ PlayFactionDialogueToTeam( "bh_newWave", TEAM_MILITIA )
+ }
+ else // same wave, second half
{
- waveId = GetWaveDataSize() - 2
- waveCount = waveId * 2
+ PlayFactionDialogueToTeam( "bh_incoming", TEAM_IMC )
+ PlayFactionDialogueToTeam( "bh_incoming", TEAM_MILITIA )
}
+
+ lastWaveId = waveId
SetGlobalNetInt( "AT_currentWave", waveId )
- bool isBossWave = waveCount / float( 2 ) > waveId // odd number waveCount means boss wave
+ bool isBossWave = waveCount % 2 == 0 // even number waveCount means boss wave
// announce the wave
foreach ( entity player in GetPlayerArray() )
{
if ( isBossWave )
+ {
Remote_CallFunction_NonReplay( player, "ServerCallback_AT_AnnounceBoss" )
+ }
else
- Remote_CallFunction_NonReplay( player, "ServerCallback_AT_AnnouncePreParty", 0.0, waveId )
+ {
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_AnnouncePreParty",
+ 0.0, // endTime ( not used )
+ waveId // waveNum
+ )
+ }
}
- wait WAVE_STATE_TRANSITION_TIME
+ wait AT_WAVE_TRANSITION_DELAY
- // run the wave
+ // Run the wave
+ thread AT_CampSpawnThink( waveId, isBossWave )
+
+ if ( !isBossWave )
+ {
+ svGlobal.levelEnt.WaitSignal( "ATAllCampsClean" ) // signaled when all camps cleaned in spawn functions
+ }
+ else
+ {
+ wait AT_BOUNTY_TITAN_CHECK_DELAY
+ // wait until all bounty titans killed
+ while ( IsAlive( GetGlobalNetEnt( "camp1Ent" ) ) || IsAlive( GetGlobalNetEnt( "camp2Ent" ) ) )
+ WaitFrame()
+ }
+
+ // wave end, prebank phase
+ svGlobal.levelEnt.Signal( "ATWaveEnd" ) // defensive fix, destroy existing campEnts
+ SetGlobalNetBool( "preBankPhase", true )
+
+ wait AT_WAVE_END_ANNOUNCEMENT_DELAY
- AT_WaveData wave = GetWaveData( waveId )
- array< array<AT_SpawnData> > campSpawnData
+ // announce wave end
+ foreach ( entity player in GetPlayerArray() )
+ {
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_AnnounceWaveOver",
+ waveId, // waveNum ( not used )
+ 0, // militiaDamageTotal ( not used )
+ 0, // imcDamageTotal ( not used )
+ 0, // milMVP ( not used )
+ 0, // imcMVP ( not used )
+ 0, // milMVPDamage ( not used )
+ 0 // imcMVPDamage ( not used )
+ )
+ }
+
+ wait AT_WAVE_TRANSITION_DELAY
- if ( isBossWave )
- campSpawnData = wave.bossSpawnData
- else
- campSpawnData = wave.spawnDataArrays
+ // banking phase
+ SetGlobalNetBool( "preBankPhase", false )
+ SetGlobalNetTime( "AT_bankStartTime", Time() )
+ SetGlobalNetTime( "AT_bankEndTime", Time() + AT_BANKS_OPEN_DURATION )
+ SetGlobalNetBool( "banksOpen", true )
+
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AT_BankOpen" )
+
+ foreach ( entity bank in file.banks )
+ thread AT_BankActiveThink( bank )
+
- // initialise pending spawns
- foreach ( array< AT_SpawnData > campData in campSpawnData )
+ float endTime = Time() + AT_BANKS_OPEN_DURATION
+ bool forceCloseTriggered = false
+ // wait until no player is holding bonus, or max wait time
+ while ( Time() <= endTime )
{
- foreach ( AT_SpawnData spawnData in campData )
- spawnData.pendingSpawns = spawnData.totalToSpawn
+ // If everyone has deposited their bonuses close the banks early
+ if ( !ATAnyPlayerHasBonus() && !forceCloseTriggered )
+ {
+ forceCloseTriggered = true
+ endTime = Time() + AT_BANK_FORCE_CLOSE_DELAY
+ }
+
+ WaitFrame()
}
- // clear tracked spawns
- file.trackedCampNPCSpawns = {}
- while ( true )
- {
- // if this is ever 0 by the end of this loop, wave is complete
- int numActiveCampSpawners = 0
+ SetGlobalNetBool( "banksOpen", false )
+ foreach ( entity player in GetPlayerArray() )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AT_BankClose" )
+ }
+}
+
+bool function ATAnyPlayerHasBonus()
+{
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( AT_GetPlayerBonusPoints( player ) )
+ return true
+ }
+ return false
+}
+
+//////////////////////////////////
+///// GAMELOOP FUNCTIONS END /////
+//////////////////////////////////
+
+
+
+//////////////////////////
+///// CAMP FUNCTIONS /////
+//////////////////////////
+
+void function AT_CampSpawnThink( int waveId, bool isBossWave )
+{
+ AT_WaveData wave = GetWaveData( waveId )
+ array< array<AT_SpawnData> > campSpawnData
+
+ if ( isBossWave )
+ campSpawnData = wave.bossSpawnData
+ else
+ campSpawnData = wave.spawnDataArrays
+
+ array<AT_WaveOrigin> allCampsToUse
+ foreach ( AT_WaveOrigin campStruct in file.camps )
+ {
+ if ( campStruct.phaseAllowed[ waveId ] )
+ allCampsToUse.append( campStruct )
+ }
+
+ // HACK
+ // There's too many phase3 camps on exoplanet and rise, make sure we always have the correct count
+ int maxCampsForWave = waveId == 0 ? 1 : 2
+ while( allCampsToUse.len() > maxCampsForWave )
+ {
+ // Get the required number of camps
+ array<AT_WaveOrigin> tempCamps
+ for( int i = 0; i < maxCampsForWave; i++ )
+ tempCamps.append( allCampsToUse[RandomInt( allCampsToUse.len() )] )
- // iterate over camp data for wave
- for ( int campIdx = 0; campIdx < campSpawnData.len() && campIdx < file.camps.len(); campIdx++ )
+
+ // Check if they're intersecting, if they are, try again
+ bool intersecting = false
+ for( int i = 0; i < tempCamps.len(); i++ )
+ {
+ AT_WaveOrigin campA = tempCamps[i]
+ for( int j = 0; j < tempCamps.len(); j++ )
{
- if ( !( campIdx in file.trackedCampNPCSpawns ) )
- file.trackedCampNPCSpawns[ campIdx ] <- {}
+ // Don't compare the same two camps
+ if( j == i )
+ continue
+
+ AT_WaveOrigin campB = tempCamps[j]
+
+ if( Distance( campA.origin, campB.origin ) < campA.radius + campB.radius )
+ intersecting = true
+ }
+ }
+
+ if( !intersecting )
+ allCampsToUse = tempCamps
+
+ // If we ever get really unlucky just wait a frame
+ WaitFrame()
+ }
+
+ foreach ( int spawnId, AT_WaveOrigin curCampData in allCampsToUse )
+ {
+ array<AT_SpawnData> curSpawnData = campSpawnData[ spawnId ]
+
+ int totalNPCsToSpawn = 0
+ // initialise pending spawns and get total npcs
+ foreach ( AT_SpawnData spawnData in curSpawnData )
+ {
+ spawnData.pendingSpawns = spawnData.totalToSpawn
+ // add to network variables
+ string npcNetVar = GetNPCNetVarName( spawnData.aitype, spawnId )
+ SetGlobalNetInt( npcNetVar, spawnData.totalToSpawn )
+
+ totalNPCsToSpawn += spawnData.totalToSpawn
+ }
+
+ if ( !isBossWave )
+ {
+ // camp Ent, boss wave will use boss themselves as campEnt
+ string campEntVarName = "camp" + string( spawnId + 1 ) + "Ent"
+ bool waveNotActive = GetGlobalNetBool( "preBankPhase" ) || GetGlobalNetBool( "banksOpen" )
+ if ( !IsValid( GetGlobalNetEnt( campEntVarName ) ) && !waveNotActive )
+ SetGlobalNetEnt( campEntVarName, CreateCampTracker( curCampData, spawnId ) )
- // iterate over ai spawn data for camp
- foreach ( AT_SpawnData spawnData in campSpawnData[ campIdx ] )
+ array<AT_SpawnData> minionSquadDatas
+ foreach ( AT_SpawnData data in curSpawnData )
+ {
+ switch ( data.aitype )
{
- if ( !( spawnData.aitype in file.trackedCampNPCSpawns[ campIdx ] ) )
- file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] <- 0
-
- if ( spawnData.pendingSpawns > 0 || file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] > 0 )
- numActiveCampSpawners++
+ case "npc_soldier":
+ case "npc_spectre":
+ case "npc_stalker":
+ if ( !AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK )
+ minionSquadDatas.append( data )
+ else
+ thread AT_DroppodSquadEvent_Single( curCampData, spawnId, data )
+ break
+
+ case "npc_super_spectre":
+ thread AT_ReaperEvent( curCampData, spawnId, data )
+ break
+ }
+ }
+
+ // minions squad spawn
+ if ( !AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK )
+ {
+ if ( minionSquadDatas.len() > 0 )
+ thread AT_DroppodSquadEvent( curCampData, spawnId, minionSquadDatas )
+ }
+
+ // use campProgressThink for handling wave state
+ thread CampProgressThink( spawnId, totalNPCsToSpawn )
+ }
+ else // bosswave spawn
+ {
+ foreach ( AT_SpawnData data in curSpawnData )
+ {
+ if( data.aitype != "npc_titan" )
+ continue
- // try to spawn as many ai as we can, as long as the camp doesn't already have too many spawned
- int spawnCount
- for ( spawnCount = 0; spawnCount < spawnData.pendingSpawns && spawnCount < spawnData.totalAllowedOnField - file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ]; )
- {
- // not doing this in a generic way atm, but could be good for the future if we want to support more ai
- switch ( spawnData.aitype )
- {
- case "npc_soldier":
- case "npc_spectre":
- case "npc_stalker":
- thread AT_SpawnDroppodSquad( campIdx, spawnData.aitype )
- spawnCount += 4
- break
-
- case "npc_super_spectre":
- thread AT_SpawnReaper( campIdx )
- spawnCount += 1
- break
-
- case "npc_titan":
- thread AT_SpawnBountyTitan( campIdx )
- spawnCount += 1
- break
-
- default:
- print( "BOUNTY HUNT: Tried to spawn unsupported ai of type \"" + "\" at camp " + campIdx )
- }
+ thread AT_BountyTitanEvent( curCampData, spawnId, data )
+ break
+ }
+ }
+ }
+}
+
+void function CampProgressThink( int spawnId, int totalNPCsToSpawn )
+{
+ string campLetter = GetCampLetter( spawnId )
+ string campProgressName = campLetter + "campProgress"
+ string campEntVarName = "camp" + string( spawnId + 1 ) + "Ent"
+
+ // initial wait
+ SetGlobalNetFloat( campProgressName, 1.0 )
+
+ // TODO: random wait, make this a constant ??
+ wait 3.0
+
+ float cleanUpTime = -1.0
+
+ while ( true )
+ {
+ int npcsLeft
+ // get all npcs might be in this camp
+ for ( int i = 0; i < 5; i++ )
+ {
+ string netVarName = string( i + 1 ) + campLetter + "campCount"
+ int netVarValue = GetGlobalNetInt( netVarName )
+ if ( netVarValue >= 0 ) // uninitialized network var starts from -1, avoid checking them
+ npcsLeft += netVarValue
+ }
+
+ float campLeft = float( npcsLeft ) / float( totalNPCsToSpawn )
+ SetGlobalNetFloat( campProgressName, campLeft )
+
+ if( npcsLeft <= AT_CAMP_BORED_NPCS_LEFT_TO_START_CLEANUP && cleanUpTime < 0.0 )
+ {
+ cleanUpTime = Time() + AT_CAMP_BORED_CLEANUP_WAIT
+ print("Cleanup timer started!")
+ }
+
+ if( Time() > cleanUpTime && cleanUpTime > 0.0 && spawnId in file.campScriptEntArrays )
+ {
+ foreach( int handle in file.campScriptEntArrays[spawnId] )
+ {
+ array<entity> entities = GetScriptManagedEntArray( handle )
+ entities.removebyvalue( null )
+ foreach ( entity ent in entities )
+ {
+ if ( IsAlive( ent ) && ent.IsNPC() )
+ {
+ printt( "Killing bored AI " + ent.GetClassName() + " at " + ent.GetOrigin() )
+ ent.Die()
}
-
- // track spawns
- file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] += spawnCount
- spawnData.pendingSpawns -= spawnCount
}
}
+ }
+
+ if ( campLeft <= 0.0 ) // camp wiped!
+ {
+ PlayFactionDialogueToTeam( "bh_cleared" + campLetter, TEAM_IMC )
+ PlayFactionDialogueToTeam( "bh_cleared" + campLetter, TEAM_MILITIA )
+
+ entity campEnt = GetGlobalNetEnt( campEntVarName )
+ if ( IsValid( campEnt ) )
+ campEnt.Signal( "ATCampClean" ) // destroy the camp ent
+
+ // check if both camps being destroyed
+ if ( !IsValid( GetGlobalNetEnt( "camp1Ent" ) ) && !IsValid( GetGlobalNetEnt( "camp2Ent" ) ) )
+ svGlobal.levelEnt.Signal( "ATAllCampsClean" ) // end the wave
- if ( numActiveCampSpawners == 0 )
- break
-
- wait 0.5
+ return
}
-
- wait WAVE_STATE_TRANSITION_TIME
-
- // banking phase
+
+ WaitFrame()
}
}
// entity funcs
+// camp
+entity function CreateCampTracker( AT_WaveOrigin campData, int spawnId )
+{
+ // store data
+ vector campOrigin = campData.origin
+ float campRadius = campData.radius
+ float campHeight = campData.height
+ // add a minimap icon
+ entity mapIconEnt = CreateEntity( "prop_script" )
+ DispatchSpawn( mapIconEnt )
+
+ mapIconEnt.SetOrigin( campOrigin )
+ mapIconEnt.DisableHibernation()
+ SetTeam( mapIconEnt, AT_AI_TEAM )
+ mapIconEnt.Minimap_AlwaysShow( TEAM_IMC, null )
+ mapIconEnt.Minimap_AlwaysShow( TEAM_MILITIA, null )
+
+ mapIconEnt.Minimap_SetCustomState( GetCampMinimapState( spawnId ) )
+ mapIconEnt.Minimap_SetAlignUpright( true )
+ mapIconEnt.Minimap_SetZOrder( MINIMAP_Z_OBJECT )
+ mapIconEnt.Minimap_SetObjectScale( campRadius / 16000.0 ) // proper icon on the map
+
+ // attach a location tracker
+ entity tracker = GetAvailableLocationTracker()
+ tracker.SetOwner( mapIconEnt ) // needs a owner to show up
+ tracker.SetOrigin( campOrigin )
+ SetLocationTrackerRadius( tracker, campRadius )
+ SetLocationTrackerID( tracker, spawnId )
+ DispatchSpawn( tracker )
+
+ thread TrackWaveEndForCampInfo( tracker, mapIconEnt )
+ return tracker
+}
+
+void function TrackWaveEndForCampInfo( entity tracker, entity mapIconEnt )
+{
+ tracker.EndSignal( "OnDestroy" )
+ tracker.EndSignal( "ATCampClean" )
+
+ OnThreadEnd
+ (
+ function(): ( tracker, mapIconEnt )
+ {
+ // camp cleaned, wave or game ended, destroy the camp info
+ if ( IsValid( tracker ) )
+ tracker.Destroy()
+
+ if ( IsValid( mapIconEnt ) )
+ mapIconEnt.Destroy()
+ }
+ )
+
+ WaitSignal( svGlobal.levelEnt, "GameStateChanged", "ATWaveEnd" )
+}
+
+string function GetCampLetter( int spawnId )
+{
+ return spawnId == 0 ? "A" : "B"
+}
+
+int function GetCampMinimapState( int id )
+{
+ switch ( id )
+ {
+ case 0:
+ return eMinimapObject_prop_script.AT_DROPZONE_A
+ case 1:
+ return eMinimapObject_prop_script.AT_DROPZONE_B
+ case 2:
+ return eMinimapObject_prop_script.AT_DROPZONE_C
+ }
+
+ unreachable
+}
+
+//////////////////////////////
+///// CAMP FUNCTIONS END /////
+//////////////////////////////
+
+
+
+//////////////////////////
+///// BANK FUNCTIONS /////
+//////////////////////////
+
+void function AT_BankActiveThink( entity bank )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+ bank.EndSignal( "OnDestroy" )
+
+ // Banks closed
+ OnThreadEnd
+ (
+ function(): ( bank )
+ {
+ if ( IsValid( bank ) )
+ {
+ // Update use prompt
+ if ( GetGameState() != eGameState.Playing )
+ bank.UnsetUsable()
+ else
+ bank.SetUsePrompts( "#AT_USE_BANK_CLOSED", "#AT_USE_BANK_CLOSED" )
+
+ thread PlayAnim( bank, "mh_active_2_inactive" )
+ FadeOutSoundOnEntity( bank, "Mobile_Hardpoint_Idle", 0.5 )
+ bank.Minimap_Hide( TEAM_IMC, null )
+ bank.Minimap_Hide( TEAM_MILITIA, null )
+ }
+ }
+ )
+
+ // Update use prompt to usable
+ bank.SetUsable()
+ bank.SetUsePrompts( "#AT_USE_BANK", "#AT_USE_BANK_PC" )
+
+ thread PlayAnim( bank, "mh_inactive_2_active" )
+ EmitSoundOnEntity( bank, "Mobile_Hardpoint_Idle" )
+
+ // Show minimap icon for bank
+ bank.Minimap_AlwaysShow( TEAM_IMC, null )
+ bank.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ bank.Minimap_SetCustomState( eMinimapObject_prop_script.AT_BANK )
+
+ // Wait for bank close or game end
+ while ( GetGlobalNetBool( "banksOpen" ) )
+ WaitFrame()
+}
+
+function OnPlayerUseBank( bank, player )
+{
+ // Banks are always usable so that we can show the use prompt
+ // Only allow deposit when banks are open
+ if ( !GetGlobalNetBool( "banksOpen" ) )
+ return
+
+ expect entity( bank )
+ expect entity( player )
+
+ // bank.SetUsableByGroup( "pilot" ) didn't seem to work so we just
+ // exit here if player is in a titan
+ if( player.IsTitan() )
+ return
+
+ // Player has no bonus, try to send a tip using SendHUDMessage
+ if ( AT_GetPlayerBonusPoints( player ) == 0 )
+ {
+ ATSendDepositTipToPlayer( player, "#AT_USE_BANK_NO_BONUS_HINT" )
+ return
+ }
+
+ // Prevent more than one instance of this thread running
+ if ( !file.playerBankUploading[ player ] )
+ thread PlayerUploadingBonus_Threaded( bank, player )
+}
+
+bool function ATSendDepositTipToPlayer( entity player, string message )
+{
+ if ( Time() < file.playerHudMessageAllowedTime[ player ] )
+ return false
+
+ SendHudMessage( player, message, -1, 0.4, 255, 255, 255, 255, 0.5, 1.0, 0.5 )
+ file.playerHudMessageAllowedTime[ player ] = Time() + AT_PLAYER_HUD_MESSAGE_COOLDOWN
+
+ return true
+}
+
+struct AT_playerUploadStruct
+{
+ bool uploadSuccess = false
+ int depositedPoints = 0
+}
+
+void function PlayerUploadingBonus_Threaded( entity bank, entity player )
+{
+ bank.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
+
+ file.playerBankUploading[ player ] = true
+
+ // this literally only exists because structs are passed by ref,
+ // and primitives like ints and bools are passed by val
+ // which meant that the OnThreadEnd was just getting 0 and false
+ AT_playerUploadStruct uploadInfo
+
+ // Cleanup and call finish deposit func
+ OnThreadEnd
+ (
+ function(): ( player, uploadInfo )
+ {
+ if ( IsValid( player ) )
+ {
+ file.playerBankUploading[ player ] = false
+
+ // Clean up looping sound
+ StopSoundOnEntity( player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_1P" )
+ StopSoundOnEntity( player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_3P" )
+
+ // Do medal event
+ // TODO: Check if vanilla actually do.s this every time you finish depositing???
+ AddPlayerScore( player, "AttritionCashedBonus" )
+
+ // Do server callback
+ Remote_CallFunction_NonReplay(
+ player,
+ "ServerCallback_AT_FinishDeposit",
+ uploadInfo.depositedPoints // deposit
+ )
+
+ player.SetPlayerNetBool( "AT_playerUploading", false )
+
+ if ( uploadInfo.uploadSuccess ) // Player deposited all remaining bonus
+ {
+ // Emit uploading successful sound
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Successful_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Successful_3P" )
+
+ // player is MVP
+ int ourScore = player.GetPlayerGameStat( PGS_ASSAULT_SCORE )
+ bool isMVP = true
+ foreach(teamPlayer in GetPlayerArrayOfTeam(player.GetTeam()))
+ {
+ if (ourScore < teamPlayer.GetPlayerGameStat( PGS_ASSAULT_SCORE ))
+ {
+ isMVP = false
+ break
+ }
+ }
+ if (isMVP)
+ PlayFactionDialogueToPlayer( "bh_mvp", player )
+ }
+ else // Player was killed or left the bank radius
+ {
+ // Emit uploading failed sound
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Unsuccessful_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Unsuccessful_3P" )
+ }
+ }
+ }
+ )
+
+ // Uploading start sound
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_Start_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_Start_3P" )
+ EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_1P" )
+ EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_3P" )
+
+ player.SetPlayerNetBool( "AT_playerUploading", true )
+
+ // Upload bonus while the player is within range of the bank
+ while ( Distance( player.GetOrigin(), bank.GetOrigin() ) <= AT_BANK_DEPOSIT_RADIUS && GetGlobalNetBool( "banksOpen" ) )
+ {
+ // Calling this moves the "Uploading..." graphic to the same place it is
+ // in vanilla
+ Remote_CallFunction_NonReplay( player, "ServerCallback_AT_ShowRespawnBonusLoss" )
+
+ int bonusToUpload = int( min( AT_BANK_DEPOSIT_RATE, AT_GetPlayerBonusPoints( player ) ) )
+ // No more bonus to upload, return
+ if ( bonusToUpload == 0 )
+ {
+ uploadInfo.uploadSuccess = true
+ return
+ }
+
+ // Remove bonus points and add them to total poins
+ AT_AddPlayerBonusPoints( player, -bonusToUpload )
+ AT_AddPlayerTotalPoints( player, bonusToUpload )
+
+ uploadInfo.depositedPoints += bonusToUpload
+ WaitFrame()
+ }
+}
+
+//////////////////////////////
+///// BANK FUNCTIONS END /////
+//////////////////////////////
-void function AT_SpawnDroppodSquad( int camp, string aiType )
+
+
+/////////////////////////
+///// NPC FUNCTIONS /////
+/////////////////////////
+
+int function GetScriptManagedNPCArrayLength_Alive( int scriptManagerId )
+{
+ array<entity> entities = GetScriptManagedEntArray( scriptManagerId )
+ entities.removebyvalue( null )
+ int npcsAlive = 0
+ foreach ( entity ent in entities )
+ {
+ if ( IsAlive( ent ) && ent.IsNPC() )
+ npcsAlive += 1
+ }
+ return npcsAlive
+}
+
+void function AT_DroppodSquadEvent( AT_WaveOrigin campData, int spawnId, array<AT_SpawnData> minionDatas )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+ // create a script managed array for all handled minions
+ int eventManager = CreateScriptManagedEntArray()
+
+ if( !(spawnId in file.campScriptEntArrays) )
+ file.campScriptEntArrays[spawnId] <- []
+
+ file.campScriptEntArrays[spawnId].append(eventManager)
+
+ int totalAllowedOnField = SQUAD_SIZE * AT_DROPPOD_SQUADS_ALLOWED_ON_FIELD
+ while ( true )
+ {
+ foreach ( AT_SpawnData data in minionDatas )
+ {
+ string ent = data.aitype
+ waitthread AT_SpawnDroppodSquad( campData, spawnId, ent, eventManager )
+ data.pendingSpawns -= SQUAD_SIZE
+ if ( data.pendingSpawns <= 0 ) // current spawn data has reached max spawn amount
+ minionDatas.removebyvalue( data ) // remove this data
+ if ( GetScriptManagedNPCArrayLength_Alive( eventManager ) >= totalAllowedOnField ) // we have enough npcs on field?
+ break // stop following spawning functions
+ }
+ if ( minionDatas.len() == 0 ) // all spawn data has finished spawn
+ return
+
+ int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ while ( npcOnFieldCount >= totalAllowedOnField - SQUAD_SIZE ) // wait until we have lost more than 1 squad
+ {
+ WaitFrame()
+ npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ }
+ }
+}
+
+// for AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK, handles a single spawndata
+void function AT_DroppodSquadEvent_Single( AT_WaveOrigin campData, int spawnId, AT_SpawnData data )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ // get ent and create a script managed array for current event
+ string ent = data.aitype
+ int eventManager = CreateScriptManagedEntArray()
+
+ if( !(spawnId in file.campScriptEntArrays) )
+ file.campScriptEntArrays[spawnId] <- []
+
+ file.campScriptEntArrays[spawnId].append(eventManager)
+
+ int totalAllowedOnField = data.totalAllowedOnField // mostly 12 for grunts and spectres, too much!
+ // start spawner
+ while ( true )
+ {
+ waitthread AT_SpawnDroppodSquad( campData, spawnId, ent, eventManager )
+ data.pendingSpawns -= SQUAD_SIZE
+ if ( data.pendingSpawns <= 0 ) // we have reached max npcs
+ return // stop any spawning functions
+
+ int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ while ( npcOnFieldCount >= totalAllowedOnField - SQUAD_SIZE ) // wait until we have less npcs than allowed count
+ {
+ WaitFrame()
+ npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ }
+ }
+}
+
+void function AT_SpawnDroppodSquad( AT_WaveOrigin campData, int spawnId, string aiType, int scriptManagerId )
{
entity spawnpoint
- if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
- spawnpoint = file.camps[ camp ].ent
+ if ( campData.dropPodSpawnPoints.len() == 0 )
+ spawnpoint = campData.ent
else
- spawnpoint = file.camps[ camp ].dropPodSpawnPoints.getrandom()
+ spawnpoint = campData.dropPodSpawnPoints.getrandom()
+ // anti-crash
+ if ( !IsValid( spawnpoint ) )
+ spawnpoint = campData.ent
// add variation to spawns
wait RandomFloat( 1.0 )
- AiGameModes_SpawnDropPod( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, aiType, void function( array<entity> guys ) : ( camp, aiType )
- {
- AT_HandleSquadSpawn( guys, camp, aiType )
- })
+ AiGameModes_SpawnDropPod(
+ spawnpoint.GetOrigin(),
+ spawnpoint.GetAngles(),
+ AT_AI_TEAM,
+ aiType,
+ // squad handler
+ void function( array<entity> guys ) : ( campData, spawnId, aiType, scriptManagerId )
+ {
+ AT_HandleSquadSpawn( guys, campData, spawnId, aiType, scriptManagerId )
+ },
+ eDropPodFlag.DISSOLVE_AFTER_DISEMBARKS
+ )
}
-void function AT_HandleSquadSpawn( array<entity> guys, int camp, string aiType )
+void function AT_HandleSquadSpawn( array<entity> guys, AT_WaveOrigin campData, int spawnId, string aiType, int scriptManagerId )
{
foreach ( entity guy in guys )
{
- guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
-
- // untrack them on death
- thread AT_WaitToUntrackNPC( guy, camp, aiType )
+ // TODO: NPCs still seem to go outside their camp ???
+ //guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE )
+
+ // tracking lifetime
+ AddToScriptManagedEntArray( scriptManagerId, guy )
+ thread AT_TrackNPCLifeTime( guy, spawnId, aiType )
+
+ thread AT_ForceAssaultAroundCamp( guy, campData )
+ }
+}
+
+void function AT_ForceAssaultAroundCamp( entity guy, AT_WaveOrigin campData )
+{
+ guy.EndSignal( "OnDestroy" )
+ guy.EndSignal( "OnDeath" )
+
+ // goal check
+ vector ornull goalPos = NavMesh_ClampPointForAI(campData.origin, guy)
+ goalPos = goalPos == null ? campData.origin : goalPos
+ expect vector(goalPos)
+
+ float goalRadius = campData.radius / 4
+ float guyGoalRadius = guy.GetMinGoalRadius()
+ if ( guyGoalRadius > goalRadius ) // this npc cannot use forced goal radius?
+ goalRadius = guyGoalRadius
+
+ while( true )
+ {
+ guy.AssaultPoint( goalPos )
+ guy.AssaultSetGoalRadius( goalRadius )
+ guy.AssaultSetFightRadius( 0 )
+ guy.AssaultSetArrivalTolerance( int(goalRadius) )
+
+ wait RandomFloatRange( 1, 5 )
+ }
+}
+
+void function AT_ReaperEvent( AT_WaveOrigin campData, int spawnId, AT_SpawnData data )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ // create a script managed array for current event
+ int eventManager = CreateScriptManagedEntArray()
+
+ if( !(spawnId in file.campScriptEntArrays) )
+ file.campScriptEntArrays[spawnId] <- []
+
+ file.campScriptEntArrays[spawnId].append(eventManager)
+
+ int totalAllowedOnField = 1 // 1 allowed at the same time for heavy armor units
+ if ( AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK )
+ totalAllowedOnField = data.totalAllowedOnField
+
+ while ( true )
+ {
+ waitthread AT_SpawnReaper( campData, spawnId, eventManager )
+ data.pendingSpawns -= 1
+ if ( data.pendingSpawns <= 0 ) // we have reached max npcs
+ return // stop any spawning functions
+
+ int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ while ( npcOnFieldCount >= totalAllowedOnField ) // wait until we have less npcs than allowed count
+ {
+ WaitFrame()
+ npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ }
}
}
-void function AT_SpawnReaper( int camp )
+void function AT_SpawnReaper( AT_WaveOrigin campData, int spawnId, int scriptManagerId )
{
entity spawnpoint
- if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
- spawnpoint = file.camps[ camp ].ent
+ if ( campData.dropPodSpawnPoints.len() == 0 )
+ spawnpoint = campData.ent
else
- spawnpoint = file.camps[ camp ].titanSpawnPoints.getrandom()
+ spawnpoint = campData.dropPodSpawnPoints.getrandom()
+ // anti-crash
+ if ( !IsValid( spawnpoint ) )
+ spawnpoint = campData.ent
// add variation to spawns
wait RandomFloat( 1.0 )
- AiGameModes_SpawnReaper( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, "npc_super_spectre",void function( entity reaper ) : ( camp )
+ AiGameModes_SpawnReaper(
+ spawnpoint.GetOrigin(),
+ spawnpoint.GetAngles(),
+ AT_AI_TEAM,
+ "npc_super_spectre_aitdm",
+ // reaper handler
+ void function( entity reaper ) : ( campData, spawnId, scriptManagerId )
+ {
+ AT_HandleReaperSpawn( reaper, campData, spawnId, scriptManagerId )
+ }
+ )
+}
+
+void function AT_HandleReaperSpawn( entity reaper, AT_WaveOrigin campData, int spawnId, int scriptManagerId )
+{
+ // tracking lifetime
+ AddToScriptManagedEntArray( scriptManagerId, reaper )
+ thread AT_TrackNPCLifeTime( reaper, spawnId, "npc_super_spectre" )
+
+ thread AT_ForceAssaultAroundCamp( reaper, campData )
+}
+
+void function AT_BountyTitanEvent( AT_WaveOrigin campData, int spawnId, AT_SpawnData data )
+{
+ svGlobal.levelEnt.EndSignal( "GameStateChanged" )
+
+ // create a script managed array for current event
+ int eventManager = CreateScriptManagedEntArray()
+
+ int totalAllowedOnField = 1 // 1 allowed at the same time for heavy armor units
+ if ( AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK )
+ totalAllowedOnField = data.totalAllowedOnField
+ while ( true )
{
- thread AT_WaitToUntrackNPC( reaper, camp, "npc_super_spectre" )
- })
+ waitthread AT_SpawnBountyTitan( campData, spawnId, eventManager )
+ data.pendingSpawns -= 1
+ if ( data.pendingSpawns <= 0 ) // we have reached max npcs
+ return // stop any spawning functions
+
+ int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ while ( npcOnFieldCount >= totalAllowedOnField ) // wait until we have less npcs than allowed count
+ {
+ WaitFrame()
+ npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager )
+ }
+ }
}
-void function AT_SpawnBountyTitan( int camp )
+void function AT_SpawnBountyTitan( AT_WaveOrigin campData, int spawnId, int scriptManagerId )
{
entity spawnpoint
- if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 )
- spawnpoint = file.camps[ camp ].ent
+ if ( campData.titanSpawnPoints.len() == 0 )
+ spawnpoint = campData.ent
else
- spawnpoint = file.camps[ camp ].titanSpawnPoints.getrandom()
+ spawnpoint = campData.titanSpawnPoints.getrandom()
+ // anti-crash
+ if ( !IsValid( spawnpoint ) )
+ spawnpoint = campData.ent
// add variation to spawns
wait RandomFloat( 1.0 )
@@ -320,57 +1630,178 @@ void function AT_SpawnBountyTitan( int camp )
int bountyID = 0
try
{
- bountyID = ReserveBossID( VALID_BOUNTY_TITAN_SETTINGS.getrandom() )
+ bountyID = ReserveBossID( AT_BOUNTY_TITANS_AI_SETTINGS.getrandom() )
}
catch ( ex ) {} // if we go above the expected wave count that vanilla supports, there's basically no way to ensure that this func won't error, so default 0 after that point
string aisettings = GetTypeFromBossID( bountyID )
string titanClass = expect string( Dev_GetAISettingByKeyField_Global( aisettings, "npc_titan_player_settings" ) )
+ AiGameModes_SpawnTitan(
+ spawnpoint.GetOrigin(),
+ spawnpoint.GetAngles(),
+ AT_AI_TEAM,
+ titanClass,
+ aisettings,
+ // titan handler
+ void function( entity titan ) : ( campData, spawnId, bountyID, scriptManagerId )
+ {
+ AT_HandleBossTitanSpawn( titan, campData, spawnId, bountyID, scriptManagerId )
+ }
+ )
+}
+
+void function AT_HandleBossTitanSpawn( entity titan, AT_WaveOrigin campData, int spawnId, int bountyID, int scriptManagerId )
+{
+ // set the bounty to be campEnt, for client tracking
+ SetGlobalNetEnt( "camp" + string( spawnId + 1 ) + "Ent", titan )
+ // set up health
+ titan.SetMaxHealth( titan.GetMaxHealth() * AT_BOUNTY_TITAN_HEALTH_MULTIPLIER )
+ titan.SetHealth( titan.GetMaxHealth() )
+ // make minimap always show them and highlight them
+ titan.Minimap_AlwaysShow( TEAM_IMC, null )
+ titan.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ thread BountyBossHighlightThink( titan )
+
+ // set up titan-specific death callbacks, mark it as bounty boss
+ file.titanIsBountyBoss[ titan ] <- true
+ file.bountyTitanRewards[ titan ] <- ATTRITION_SCORE_BOSS_DAMAGE
+ AddEntityCallback_OnPostDamaged( titan, OnBountyTitanPostDamage )
+ AddEntityCallback_OnKilled( titan, OnBountyTitanKilled )
- AiGameModes_SpawnTitan( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, titanClass, aisettings, void function( entity titan ) : ( camp, bountyID )
+ titan.GetTitanSoul().soul.skipDoomState = true
+ // i feel like this should be localised, but there's nothing for it in r1_english?
+ titan.SetTitle( GetNameFromBossID( bountyID ) )
+
+ // tracking lifetime
+ AddToScriptManagedEntArray( scriptManagerId, titan )
+ thread AT_TrackNPCLifeTime( titan, spawnId, "npc_titan" )
+}
+
+void function BountyBossHighlightThink( entity titan )
+{
+ titan.EndSignal( "OnDestroy" )
+ titan.EndSignal( "OnDeath" )
+
+ while ( true )
{
- // set up titan-specific death/damage callbacks
- AddEntityCallback_OnDamaged( titan, OnBountyDamaged)
- AddEntityCallback_OnKilled( titan, OnBountyKilled )
-
- titan.GetTitanSoul().soul.skipDoomState = true
- // i feel like this should be localised, but there's nothing for it in r1_english?
- titan.SetTitle( GetNameFromBossID( bountyID ) )
- thread AT_WaitToUntrackNPC( titan, camp, "npc_titan" )
- } )
+ Highlight_SetEnemyHighlight( titan, "enemy_boss_bounty" )
+ titan.WaitSignal( "StopPhaseShift" ) // prevent phase shift mess up highlights
+ }
}
-// Tracked entities will require their own "wallet"
-// for titans it should be used for rounding error compenstation
-// for infantry it sould be used to store money if the npc kills a player
-void function OnBountyDamaged( entity titan, var damageInfo )
+void function OnBountyTitanPostDamage( entity titan, var damageInfo )
{
entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) ) // delayed by projectile shots
+ return
+ // damaged by npc or something?
if ( !attacker.IsPlayer() )
- attacker = GetLatestAssistingPlayerInfo( titan ).player
-
- if ( IsValid( attacker ) && attacker.IsPlayer() )
{
- int reward = int ( BOUNTY_TITAN_DAMAGE_POOL * DamageInfo_GetDamage( damageInfo ) / titan.GetMaxHealth() )
- printt ( titan.GetMaxHealth(), DamageInfo_GetDamage( damageInfo ) )
-
- AT_AddPlayerCash( attacker, reward )
+ attacker = GetBountyBossDamageOwner( attacker, titan )
+ if ( !IsValid( attacker ) || !attacker.IsPlayer() )
+ return
}
+
+ int rewardSegment = ATTRITION_SCORE_BOSS_DAMAGE
+ int healthSegment = titan.GetMaxHealth() / rewardSegment
+
+ // sometimes damage is not enough to add 1 point, we save the damage for player's next attack
+ if ( !( titan in file.playerSavedBountyDamage[ attacker ] ) )
+ file.playerSavedBountyDamage[ attacker ][ titan ] <- 0
+
+ file.playerSavedBountyDamage[ attacker ][ titan ] += int( DamageInfo_GetDamage( damageInfo ) )
+ if ( file.playerSavedBountyDamage[ attacker ][ titan ] < healthSegment )
+ return // they can't earn reward from this shot
+
+ int damageSegment = file.playerSavedBountyDamage[ attacker ][ titan ] / healthSegment
+ int savedDamageLeft = file.playerSavedBountyDamage[ attacker ][ titan ] % healthSegment
+ file.playerSavedBountyDamage[ attacker ][ titan ] = savedDamageLeft
+
+ float damageFrac = float( damageSegment ) / rewardSegment
+ int rewardLeft = file.bountyTitanRewards[ titan ]
+ int reward = int( ATTRITION_SCORE_BOSS_DAMAGE * damageFrac )
+ if ( reward >= rewardLeft ) // overloaded shot?
+ reward = rewardLeft
+ file.bountyTitanRewards[ titan ] -= reward
+
+ if ( reward > 0 )
+ AT_AddPlayerBonusPointsForBossDamaged( attacker, titan, reward, damageInfo )
}
-void function OnBountyKilled( entity titan, var damageInfo )
+void function OnBountyTitanKilled( entity titan, var damageInfo )
{
entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) ) // delayed by projectile shots
+ return
+ // damaged by npc or something?
if ( !attacker.IsPlayer() )
- attacker = GetLatestAssistingPlayerInfo( titan ).player
+ {
+ attacker = GetBountyBossDamageOwner( attacker, titan )
+ if ( !IsValid( attacker ) || !attacker.IsPlayer() )
+ return
+ }
+
+ // add all remaining reward to attacker
+ // bounty killed bonus handled by AT_PlayerOrNPCKilledScoreEvent()
+ int rewardLeft = file.bountyTitanRewards[ titan ]
+ delete file.bountyTitanRewards[ titan ]
+ if ( rewardLeft > 0 )
+ AT_AddPlayerBonusPointsForBossDamaged( attacker, titan, rewardLeft, damageInfo )
+
+ // remove this bounty's damage saver
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( titan in file.playerSavedBountyDamage[ player ] )
+ delete file.playerSavedBountyDamage[ player ][ titan ]
+ }
+
+ // faction dialogue
+ int team = attacker.GetTeam()
+ PlayFactionDialogueToPlayer( "bh_playerKilledBounty", attacker )
+ PlayFactionDialogueToTeamExceptPlayer( "bh_bountyClaimedByFriendly", team, attacker )
+ PlayFactionDialogueToTeam( "bh_bountyClaimedByEnemy", GetOtherTeam( team ) )
+}
+
+entity function GetBountyBossDamageOwner( entity attacker, entity titan )
+{
+ if ( attacker.IsPlayer() ) // already a player
+ return attacker
- if ( IsValid( attacker ) && attacker.IsPlayer() )
- AT_AddPlayerCash( attacker, BOUNTY_TITAN_KILL_REWARD )
+ if ( attacker.IsTitan() ) // attacker is a npc titan
+ {
+ // try to find it's pet titan owner
+ if ( IsValid( GetPetTitanOwner( attacker ) ) )
+ return GetPetTitanOwner( attacker )
+ }
+
+ // other damages or non-owner npcs, not sure how it happens, just use this titan's last attacker
+ return GetLatestAssistingPlayerInfo( titan ).player
}
-void function AT_WaitToUntrackNPC( entity guy, int camp, string aiType )
+void function AT_TrackNPCLifeTime( entity guy, int spawnId, string aiType )
{
guy.WaitSignal( "OnDeath", "OnDestroy" )
- file.trackedCampNPCSpawns[ camp ][ aiType ]--
+
+ string npcNetVar = GetNPCNetVarName( aiType, spawnId )
+ SetGlobalNetInt( npcNetVar, GetGlobalNetInt( npcNetVar ) - 1 )
+}
+
+
+// network var
+string function GetNPCNetVarName( string className, int spawnId )
+{
+ string npcId = string( GetAiTypeInt( className ) + 1 )
+ string campLetter = GetCampLetter( spawnId )
+ if ( npcId == "0" ) // cannot find this ai support!
+ {
+ if ( className == "npc_super_spectre" ) // stupid, reapers are not handled by GetAiTypeInt(), but it must be 4
+ return "4" + campLetter + "campCount"
+ return ""
+ }
+ return npcId + campLetter + "campCount"
}
+
+/////////////////////////////
+///// NPC FUNCTIONS END /////
+/////////////////////////////
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
index 705b7836..d8b0c9bd 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut
@@ -29,6 +29,8 @@ struct {
array<HardpointStruct> hardpoints
array<CP_PlayerStruct> players
+ table<entity,int> playerAssaultPoints
+ table<entity,int> playerDefensePoints
} file
void function GamemodeCP_Init()
@@ -112,11 +114,13 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama
{
AddPlayerScore( attacker , "HardpointDefense", victim )
attacker.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_DEFENSE)
+ UpdatePlayerScoreForChallenge(attacker,0,POINTVALUE_HARDPOINT_DEFENSE)
}
else if((victimCP.hardpoint.GetTeam()==victim.GetTeam())||(GetHardpointCappingTeam(victimCP)==victim.GetTeam()))
{
AddPlayerScore( attacker, "HardpointAssault", victim )
attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_ASSAULT)
+ UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_ASSAULT,0)
}
}
}
@@ -127,10 +131,12 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama
{
AddPlayerScore( attacker , "HardpointSnipe", victim )
attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_SNIPE)
+ UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_SNIPE,0)
}
else{
AddPlayerScore( attacker , "HardpointSiege", victim )
attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_SIEGE)
+ UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_SIEGE,0)
}
}
else if(attackerCP.hardpoint!=null)//Perimeter Defense
@@ -138,6 +144,7 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama
if(attackerCP.hardpoint.GetTeam()==attacker.GetTeam())
AddPlayerScore( attacker , "HardpointPerimeterDefense", victim)
attacker.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_PERIMETER_DEFENSE)
+ UpdatePlayerScoreForChallenge(attacker,0,POINTVALUE_HARDPOINT_PERIMETER_DEFENSE)
}
foreach(CP_PlayerStruct player in file.players) //Reset Victim Holdtime Counter
@@ -308,6 +315,7 @@ void function CapturePointForTeam(HardpointStruct hardpoint, int Team)
if(player.IsPlayer()){
AddPlayerScore(player,"ControlPointCapture")
player.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_CAPTURE)
+ UpdatePlayerScoreForChallenge(player,POINTVALUE_HARDPOINT_CAPTURE,0)
}
}
}
@@ -319,12 +327,17 @@ void function GamemodeCP_InitPlayer(entity player)
playerStruct.timeOnPoints = [0.0,0.0,0.0]
playerStruct.isOnHardpoint = false
file.players.append(playerStruct)
+ file.playerAssaultPoints[player] <- 0
+ file.playerDefensePoints[player] <- 0
thread PlayerThink(playerStruct)
}
void function GamemodeCP_RemovePlayer(entity player)
{
-
+ if(player in file.playerAssaultPoints)
+ delete file.playerAssaultPoints[player]
+ if(player in file.playerDefensePoints)
+ delete file.playerDefensePoints[player]
foreach(index,CP_PlayerStruct playerStruct in file.players)
{
if(playerStruct.player==player)
@@ -376,11 +389,13 @@ void function PlayerThink(CP_PlayerStruct player)
{
AddPlayerScore(player.player,"ControlPointAmpedHold")
player.player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_HARDPOINT_AMPED_HOLD )
+ UpdatePlayerScoreForChallenge(player.player,0,POINTVALUE_HARDPOINT_AMPED_HOLD)
}
else
{
AddPlayerScore(player.player,"ControlPointHold")
player.player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_HARDPOINT_HOLD )
+ UpdatePlayerScoreForChallenge(player.player,0,POINTVALUE_HARDPOINT_HOLD)
}
}
break
@@ -471,8 +486,10 @@ void function HardpointThink( HardpointStruct hardpoint )
}
else if(cappingTeam==TEAM_UNASSIGNED) // nobody on point
{
- if((GetHardpointState(hardpoint)==CAPTURE_POINT_STATE_AMPED)||(GetHardpointState(hardpoint)==CAPTURE_POINT_STATE_AMPING))
+ if((GetHardpointState(hardpoint)>=CAPTURE_POINT_STATE_AMPED) || (GetHardpointState(hardpoint)==CAPTURE_POINT_STATE_SELF_UNAMPING))
{
+ if (GetHardpointState(hardpoint) == CAPTURE_POINT_STATE_AMPED)
+ SetHardpointState(hardpoint,CAPTURE_POINT_STATE_SELF_UNAMPING) // plays a pulsating effect on the UI only when the hardpoint is amped
SetHardpointCappingTeam(hardpoint,hardpointEnt.GetTeam())
SetHardpointCaptureProgress(hardpoint,max(1.0,GetHardpointCaptureProgress(hardpoint)-(deltaTime/HARDPOINT_AMPED_DELAY)))
if(GetHardpointCaptureProgress(hardpoint)<=1.001) // unamp
@@ -546,8 +563,10 @@ void function HardpointThink( HardpointStruct hardpoint )
}
else if(file.ampingEnabled)//amping or reamping
{
- if(GetHardpointState(hardpoint)<CAPTURE_POINT_STATE_AMPING)
- SetHardpointState(hardpoint,CAPTURE_POINT_STATE_AMPING)
+ // i have no idea why but putting it CAPTURE_POINT_STATE_AMPING will say 'CONTESTED' on the UI
+ // since whether the point is contested is checked above, putting the hardpoint state to a value of 8 fixes it somehow
+ if(GetHardpointState(hardpoint)<=CAPTURE_POINT_STATE_AMPING)
+ SetHardpointState( hardpoint, 8 )
SetHardpointCaptureProgress( hardpoint, min( 2.0, GetHardpointCaptureProgress( hardpoint ) + ( deltaTime / HARDPOINT_AMPED_DELAY * capperAmount ) ) )
if(GetHardpointCaptureProgress(hardpoint)==2.0&&!(GetHardpointState(hardpoint)==CAPTURE_POINT_STATE_AMPED))
{
@@ -570,6 +589,7 @@ void function HardpointThink( HardpointStruct hardpoint )
{
AddPlayerScore(player,"ControlPointAmped")
player.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_AMPED)
+ UpdatePlayerScoreForChallenge(player,0,POINTVALUE_HARDPOINT_AMPED)
}
}
}
@@ -645,7 +665,10 @@ void function OnHardpointEntered( entity trigger, entity player )
hardpoint.militiaCappers.append( player )
foreach(CP_PlayerStruct playerStruct in file.players)
if(playerStruct.player == player)
+ {
playerStruct.isOnHardpoint = true
+ player.SetPlayerNetInt( "playerHardpointID", hardpoint.hardpoint.GetHardpointID() )
+ }
}
void function OnHardpointLeft( entity trigger, entity player )
@@ -661,7 +684,10 @@ void function OnHardpointLeft( entity trigger, entity player )
FindAndRemove( hardpoint.militiaCappers, player )
foreach(CP_PlayerStruct playerStruct in file.players)
if(playerStruct.player == player)
+ {
playerStruct.isOnHardpoint = false
+ player.SetPlayerNetInt( "playerHardpointID", 69 ) // an arbitary number to remove the hud from the player
+ }
}
string function CaptureStateToString( int state )
@@ -675,6 +701,7 @@ string function CaptureStateToString( int state )
case CAPTURE_POINT_STATE_CAPTURED:
return "CAPTURED"
case CAPTURE_POINT_STATE_AMPING:
+ case 8:
return "AMPING"
case CAPTURE_POINT_STATE_AMPED:
return "AMPED"
@@ -703,3 +730,26 @@ string function GetHardpointGroup(entity hardpoint) //Hardpoint Entity B on Home
return string(hardpoint.kv.hardpointGroup)
}
+
+void function UpdatePlayerScoreForChallenge(entity player,int assaultpoints = 0,int defensepoints = 0)
+{
+ if(player in file.playerAssaultPoints)
+ {
+ file.playerAssaultPoints[player] += assaultpoints
+ if( file.playerAssaultPoints[player] >= 1000 && !HasPlayerCompletedMeritScore(player) )
+ {
+ AddPlayerScore(player,"ChallengeCPAssault")
+ SetPlayerChallengeMeritScore(player)
+ }
+ }
+
+ if(player in file.playerDefensePoints)
+ {
+ file.playerDefensePoints[player] += defensepoints
+ if( file.playerDefensePoints[player] >= 500 && !HasPlayerCompletedMeritScore(player) )
+ {
+ AddPlayerScore(player,"ChallengeCPDefense")
+ SetPlayerChallengeMeritScore(player)
+ }
+ }
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
index 99f34164..97addc24 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut
@@ -26,6 +26,8 @@ void function CaptureTheFlag_Init()
{
PrecacheModel( CTF_FLAG_MODEL )
PrecacheModel( CTF_FLAG_BASE_MODEL )
+ PrecacheParticleSystem( FLAG_FX_FRIENDLY )
+ PrecacheParticleSystem( FLAG_FX_ENEMY )
CaptureTheFlagShared_Init()
SetSwitchSidesBased( true )
@@ -73,27 +75,20 @@ void function RateSpawnpoints_CTF( int checkClass, array<entity> spawnpoints, in
bool function VerifyCTFSpawnpoint( entity spawnpoint, int team )
{
// ensure spawnpoints aren't too close to enemy base
+ vector allyFlagSpot
+ vector enemyFlagSpot
+ foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) )
+ {
+ if( spawn.GetTeam() == team )
+ allyFlagSpot = spawn.GetOrigin()
+ else
+ enemyFlagSpot = spawn.GetOrigin()
+ }
- if ( HasSwitchedSides() )
- team = GetOtherTeam( team )
-
- array<entity> startSpawns = SpawnPoints_GetPilotStart( team )
- array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) )
-
- vector averageFriendlySpawns
- vector averageEnemySpawns
-
- foreach ( entity spawn in startSpawns )
- averageFriendlySpawns += spawn.GetOrigin()
-
- averageFriendlySpawns /= startSpawns.len()
-
- foreach ( entity spawn in enemyStartSpawns )
- averageEnemySpawns += spawn.GetOrigin()
-
- averageEnemySpawns /= startSpawns.len()
+ if( Distance2D( spawnpoint.GetOrigin(), allyFlagSpot ) > Distance2D( spawnpoint.GetOrigin(), enemyFlagSpot ) )
+ return false
- return Distance2D( spawnpoint.GetOrigin(), averageEnemySpawns ) / Distance2D( averageFriendlySpawns, averageEnemySpawns ) > 0.35
+ return true
}
void function CTFInitPlayer( entity player )
@@ -164,6 +159,9 @@ void function CreateFlags()
flag.SetValueForModelKey( CTF_FLAG_MODEL )
SetTeam( flag, flagTeam )
flag.MarkAsNonMovingAttachment()
+ flag.Minimap_AlwaysShow( TEAM_IMC, null ) // show flag icon on minimap
+ flag.Minimap_AlwaysShow( TEAM_MILITIA, null )
+ flag.Minimap_SetAlignUpright( true )
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
@@ -291,7 +289,7 @@ void function GiveFlag( entity player, entity flag )
PlayFactionDialogueToTeamExceptPlayer( "ctf_flagPickupFriendly", player.GetTeam(), player )
MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerHasFriendlyFlag, player, player )
- EmitSoundOnEntityToTeam( flag, "UI_CTF_EnemyGrabFlag", flag.GetTeam() )
+ EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyGrabFlag", flag.GetTeam() )
SetFlagStateForTeam( flag.GetTeam(), eFlagState.Away ) // used for held
}
@@ -339,14 +337,13 @@ void function DropFlag( entity player, bool realDrop = true )
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
}
@@ -417,20 +414,33 @@ void function CaptureFlag( entity player, entity flag )
assistList = file.militiaCaptureAssistList
foreach( entity assistPlayer in assistList )
+ {
if ( player != assistPlayer )
AddPlayerScore( assistPlayer, "FlagCaptureAssist", player )
+ if( !HasPlayerCompletedMeritScore( assistPlayer ) )
+ {
+ AddPlayerScore( assistPlayer, "ChallengeCTFCapAssist" )
+ SetPlayerChallengeMeritScore( assistPlayer )
+ }
+ }
assistList.clear()
-
+
// notifs
MessageToPlayer( player, eEventNotifications.YouCapturedTheEnemyFlag )
EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" )
+ if( !HasPlayerCompletedMeritScore( player ) )
+ {
+ AddPlayerScore( player, "ChallengeCTFRetAssist" )
+ SetPlayerChallengeMeritScore( player )
+ }
+
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() )
+ EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyScores", flag.GetTeam() )
if ( GameRules_GetTeamScore( team ) == GameMode_GetRoundScoreLimit( GAMETYPE ) - 1 )
{
@@ -446,6 +456,9 @@ void function OnPlayerEntersFlagReturnTrigger( entity trigger, entity player )
flag = file.imcFlag
else
flag = file.militiaFlag
+
+ if( !IsValid( flag ) || !IsValid( player ) )
+ return
if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null )
return
@@ -481,6 +494,7 @@ void function TryReturnFlag( entity player, entity flag )
})
player.EndSignal( "FlagReturnEnded" )
+ flag.EndSignal( "FlagReturnEnded" ) // avoid multiple players to return one flag at once
player.EndSignal( "OnDeath" )
wait CTF_GetFlagReturnTime()
@@ -488,12 +502,19 @@ void function TryReturnFlag( entity player, entity flag )
// flag return succeeded
// return flag
ResetFlag( flag )
-
+ flag.Signal( "FlagReturnEnded" )
+
// do notifications for return
MessageToPlayer( player, eEventNotifications.YouReturnedFriendlyFlag )
AddPlayerScore( player, "FlagReturn", player )
player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 )
+ if( !HasPlayerCompletedMeritScore( player ) )
+ {
+ AddPlayerScore( player, "ChallengeCTFRetAssist" )
+ SetPlayerChallengeMeritScore( player )
+ }
+
MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerReturnedFriendlyFlag, null, player )
EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_TeamReturnsFlag", flag.GetTeam() )
PlayFactionDialogueToTeam( "ctf_flagReturnedFriendly", flag.GetTeam() )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut
index 27eef177..4bff6038 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ffa.nut
@@ -6,6 +6,9 @@ void function FFA_Init()
ScoreEvent_SetupEarnMeterValuesForMixedModes()
AddCallback_OnPlayerKilled( OnPlayerKilled )
+
+ // modified for northstar
+ AddCallback_OnClientConnected( OnClientConnected )
}
void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
@@ -16,4 +19,18 @@ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
// why isn't this PGS_SCORE? odd game
attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 )
}
+}
+
+// modified for northstar
+void function OnClientConnected( entity player )
+{
+ thread FFAPlayerScoreThink( player ) // good to have this! instead of DisconnectCallback this could handle a null player
+}
+
+void function FFAPlayerScoreThink( entity player )
+{
+ int team = player.GetTeam()
+
+ player.WaitSignal( "OnDestroy" ) // this can handle disconnecting
+ AddTeamScore( team, -GameRules_GetTeamScore( team ) )
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut
index 9d8f84b5..6d0fd3c7 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut
@@ -17,6 +17,7 @@ void function GamemodeFRA_Init()
ScoreEvent_SetEarnMeterValues( "PilotBatteryPickup", 0.0, 0.34 )
EarnMeterMP_SetPassiveMeterGainEnabled( false )
PilotBattery_SetMaxCount( 3 )
+ SetupGenericFFAChallenge()
AddCallback_OnPlayerKilled( FRARemoveEarnMeter )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut
index 31c85a57..8999231d 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut
@@ -8,6 +8,8 @@ struct {
float lastDamageInfoTime
bool shouldDoHighlights
+
+ table< entity, int > pilotstreak
} file
void function GamemodeLts_Init()
@@ -34,6 +36,37 @@ void function GamemodeLts_Init()
ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() )
ClassicMP_ForceDisableEpilogue( true )
AddCallback_GameStateEnter( eGameState.Playing, WaitForThirtySecondsLeft )
+
+ AddCallback_OnClientConnected( SetupPlayerLTSChallenges ) //Just to make up the Match Goals tracking
+ AddCallback_OnClientDisconnected( RemovePlayerLTSChallenges ) //Safety removal of data to prevent crashes
+ AddCallback_OnPlayerKilled( LTSChallengeForPlayerKilled )
+}
+
+void function SetupPlayerLTSChallenges( entity player )
+{
+ file.pilotstreak[ player ] <- 0
+}
+
+void function RemovePlayerLTSChallenges( entity player )
+{
+ if( player in file.pilotstreak )
+ delete file.pilotstreak[ player ]
+}
+
+void function LTSChallengeForPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( victim.IsPlayer() && attacker in file.pilotstreak )
+ {
+ file.pilotstreak[attacker]++
+ if( file.pilotstreak[attacker] >= 2 && !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengeLTS" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
}
void function WaitForThirtySecondsLeft()
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
index 659dbb7a..768bbde1 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut
@@ -180,6 +180,12 @@ void function MarkPlayers( entity imcMark, entity militiaMark )
entity livingMark = GetMarked( GetOtherTeam( deadMark.GetTeam() ) )
livingMark.SetPlayerGameStat( PGS_DEFENSE_SCORE, livingMark.GetPlayerGameStat( PGS_DEFENSE_SCORE ) + 1 )
+ if( !HasPlayerCompletedMeritScore( livingMark ) )
+ {
+ AddPlayerScore( livingMark, "ChallengeMFD" )
+ SetPlayerChallengeMeritScore( livingMark )
+ }
+
// thread this so we don't kill our own thread
thread AddTeamScore( livingMark.GetTeam(), 1 )
}
@@ -188,10 +194,22 @@ void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo
{
if ( victim == GetMarked( victim.GetTeam() ) )
{
- MessageToAll( eEventNotifications.MarkedForDeathKill, null, victim, attacker.GetEncodedEHandle() )
+ // handle suicides. Not sure what the actual message is that vanilla shows for this
+ // but this will prevent crashing for now
+ bool isSuicide = IsSuicide( victim, attacker, DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ entity actualAttacker = isSuicide ? victim : attacker
+
+ MessageToAll( eEventNotifications.MarkedForDeathKill, null, victim, actualAttacker.GetEncodedEHandle() )
svGlobal.levelEnt.Signal( "MarkKilled", { mark = victim } )
- if ( attacker.IsPlayer() )
+ if ( !isSuicide && attacker.IsPlayer() )
+ {
attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 )
+ if( !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengeMFD" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
}
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut
index 57355ad8..c91c27d1 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut
@@ -19,6 +19,7 @@ void function GamemodePs_Init()
AddCallback_OnPlayerKilled( GiveScoreForPlayerKill )
ScoreEvent_SetupEarnMeterValuesForMixedModes()
SetTimeoutWinnerDecisionFunc( CheckScoreForDraw )
+ SetupGenericFFAChallenge()
// spawnzone stuff
SetShouldCreateMinimapSpawnZones( true )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut
index cb277b00..4617476e 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut
@@ -18,6 +18,7 @@ void function GamemodeSpeedball_Init()
Riff_ForceTitanAvailability( eTitanAvailability.Never )
Riff_ForceSetEliminationMode( eEliminationMode.Pilots )
ScoreEvent_SetupEarnMeterValuesForMixedModes()
+ SetupGenericFFAChallenge()
AddSpawnCallbackEditorClass( "script_ref", "info_speedball_flag", CreateFlag )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut
index 5c0e6fec..61ede2d4 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut
@@ -6,6 +6,7 @@ void function GamemodeTdm_Init()
AddCallback_OnPlayerKilled( GiveScoreForPlayerKill )
ScoreEvent_SetupEarnMeterValuesForMixedModes()
SetTimeoutWinnerDecisionFunc( CheckScoreForDraw )
+ SetupGenericTDMChallenge()
}
void function GiveScoreForPlayerKill( entity victim, entity attacker, var damageInfo )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut
index 6b30a399..3ba84394 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut
@@ -2,6 +2,11 @@ global function GamemodeTTDM_Init
const float TTDMIntroLength = 15.0
+struct
+{
+ table< entity, int > challengeCount
+} file
+
void function GamemodeTTDM_Init()
{
Riff_ForceSetSpawnAsTitan( eSpawnAsTitan.Always )
@@ -14,6 +19,8 @@ void function GamemodeTTDM_Init()
ClassicMP_ForceDisableEpilogue( true )
SetTimeoutWinnerDecisionFunc( CheckScoreForDraw )
+ AddCallback_OnClientConnected( SetupPlayerTTDMChallenges ) //Just to make up the Match Goals tracking
+ AddCallback_OnClientDisconnected( RemovePlayerTTDMChallenges ) //Safety removal of data to prevent crashes
AddCallback_OnPlayerKilled( AddTeamScoreForPlayerKilled ) // dont have to track autotitan kills since you cant leave your titan in this mode
// probably needs scoreevent earnmeter values
@@ -56,6 +63,17 @@ void function TTDMIntroShowIntermissionCam( entity player )
thread PlayerWatchesTTDMIntroIntermissionCam( player )
}
+void function SetupPlayerTTDMChallenges( entity player )
+{
+ file.challengeCount[ player ] <- 0
+}
+
+void function RemovePlayerTTDMChallenges( entity player )
+{
+ if( player in file.challengeCount )
+ delete file.challengeCount[ player ]
+}
+
void function PlayerWatchesTTDMIntroIntermissionCam( entity player )
{
player.EndSignal( "OnDestroy" )
@@ -79,6 +97,19 @@ void function AddTeamScoreForPlayerKilled( entity victim, entity attacker, var d
if ( victim == attacker || !victim.IsPlayer() || !attacker.IsPlayer() && GetGameState() == eGameState.Playing )
return
+ if( victim in file.challengeCount )
+ file.challengeCount[victim] = 0
+
+ if( attacker in file.challengeCount )
+ {
+ file.challengeCount[attacker]++
+ if( file.challengeCount[attacker] >= 2 && !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengeTTDM" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
+
AddTeamScore( GetOtherTeam( victim.GetTeam() ), 1 )
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
index 4e8f85ac..9057f7d8 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut
@@ -8,6 +8,7 @@ global function PlayerInventory_EndCriticalSectionForWeaponOnEndFrame
global function PlayerInventory_PushInventoryItem
global function PlayerInventory_PushInventoryItemByBurnRef
global function PlayerInventory_PopInventoryItem
+global function PlayerInventory_TakeAllInventoryItems
global function PlayerInventory_CountBurnRef
struct
@@ -19,6 +20,7 @@ void function Sv_ItemInventory_Init()
{
AddCallback_OnClientConnected( Sv_ItemInventory_OnClientConnected )
AddCallback_OnPlayerGetsNewPilotLoadout( Sv_ItemInventory_OnPlayerGetsNewPilotLoadout )
+ AddCallback_GameStateEnter( eGameState.Prematch, PrematchClearInventory )
}
void function Sv_ItemInventory_OnClientConnected( entity player )
@@ -26,13 +28,21 @@ void function Sv_ItemInventory_OnClientConnected( entity player )
file.playerInventoryStacks[ player ] <- []
}
+void function PrematchClearInventory() // vanilla behavior
+{
+ foreach( entity player in GetPlayerArray() )
+ {
+ PlayerInventory_TakeAllInventoryItems( player )
+ }
+}
+
void function Sv_ItemInventory_OnPlayerGetsNewPilotLoadout( entity player, PilotLoadoutDef newPilotLoadout )
{
array<InventoryItem> playerInventoryStack = file.playerInventoryStacks[ player ]
if (playerInventoryStack.len() > 0) {
InventoryItem topInventoryItem = playerInventoryStack[playerInventoryStack.len() - 1]
- PlayerInventory_GiveInventoryItem(player, topInventoryItem)
+ thread PlayerInventory_GiveInventoryItem(player, topInventoryItem)
}
return
@@ -68,13 +78,25 @@ int function PlayerInventory_CountBurnRef( entity player, string burnRef )
void function PlayerInventory_TakeInventoryItem( entity player )
{
+ player.EndSignal( "OnDestroy" )
entity preexistingWeapon = player.GetOffhandWeapon( OFFHAND_INVENTORY )
- if ( IsValid( preexistingWeapon ) )
- player.TakeWeaponNow( preexistingWeapon.GetWeaponClassName() )
+
+ if( !IsValid( preexistingWeapon ) )
+ return
+ preexistingWeapon.EndSignal( "OnDestroy" )
+ if( preexistingWeapon.GetWeaponClassName() == "mp_ability_burncardweapon" )
+ {
+ var fireTime = preexistingWeapon.GetWeaponInfoFileKeyField( "fire_anim_rate" )
+ if( fireTime )
+ wait fireTime
+ }
+ player.TakeWeaponNow( preexistingWeapon.GetWeaponClassName() )
}
void function PlayerInventory_GiveInventoryItem( entity player, InventoryItem inventoryItem )
{
+ player.EndSignal( "OnDestroy" )
+
array<string> mods = []
if ( inventoryItem.itemType == eInventoryItemType.burnmeter ) {
@@ -84,7 +106,10 @@ void function PlayerInventory_GiveInventoryItem( entity player, InventoryItem in
}
// ensure inventory slot isn't full to avoid crash
- PlayerInventory_TakeInventoryItem( player )
+ waitthread PlayerInventory_TakeInventoryItem( player )
+ entity preexistingWeapon = player.GetOffhandWeapon( OFFHAND_INVENTORY ) // defensive fix
+ if( IsValid( preexistingWeapon ) )
+ player.TakeWeaponNow( preexistingWeapon.GetWeaponClassName() )
player.GiveOffhandWeapon( inventoryItem.weaponRef, OFFHAND_INVENTORY, mods )
}
@@ -94,7 +119,7 @@ void function PlayerInventory_PushInventoryItem( entity player, InventoryItem in
file.playerInventoryStacks[ player ].append(inventoryItem)
player.SetPlayerNetInt( "itemInventoryCount", file.playerInventoryStacks[ player ].len() )
- PlayerInventory_GiveInventoryItem(player, inventoryItem)
+ thread PlayerInventory_GiveInventoryItem(player, inventoryItem)
}
void function PlayerInventory_PushInventoryItemByBurnRef( entity player, string burnRef )
@@ -117,15 +142,23 @@ void function PlayerInventory_PopInventoryItem( entity player )
if (playerInventoryStack.len() > 0) {
InventoryItem nextInventoryItem = playerInventoryStack[playerInventoryStack.len() - 1]
- PlayerInventory_GiveInventoryItem(player, nextInventoryItem)
+ thread PlayerInventory_GiveInventoryItem( player, nextInventoryItem )
} else {
- PlayerInventory_TakeInventoryItem( player )
+ waitthread PlayerInventory_TakeInventoryItem( player )
}
}
return
}
+void function PlayerInventory_TakeAllInventoryItems( entity player )
+{
+ file.playerInventoryStacks[ player ].clear()
+ waitthread PlayerInventory_TakeInventoryItem( player )
+ player.SetPlayerNetInt( "itemInventoryCount", 0 )
+ return
+}
+
void function PlayerInventory_RefreshEquippedState( entity player )
{
@@ -139,4 +172,4 @@ void function PlayerInventory_StartCriticalSection( entity player )
void function PlayerInventory_EndCriticalSectionForWeaponOnEndFrame( entity weapon )
{
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
index ae933b71..8b65ec93 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_lobby.gnut
@@ -14,6 +14,7 @@ void function Lobby_Init()
{
// non-private lobby clientcommands
AddClientCommandCallback( "StartPrivateMatchSearch", ClientCommandCallback_StartPrivateMatchSearch )
+ AddClientCommandCallback( "SetAnnouncementVersionSeen", ClientCommandCallback_SetAnnouncementVersionSeen )
}
}
@@ -37,3 +38,14 @@ bool function ClientCommandCallback_StartPrivateMatchSearch( entity player, arra
return true
}
+
+bool function ClientCommandCallback_SetAnnouncementVersionSeen( entity player, array<string> args )
+{
+ if ( args.len() < 1 )
+ return false
+
+ int version = int( args[0] )
+
+ player.SetPersistentVar( "announcementVersionSeen", version )
+ return true
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut
index c410869e..0a28031a 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/_private_lobby.gnut
@@ -38,14 +38,14 @@ void function SetupPrivateMatchUIVarsWhenReady()
bool function ClientCommandCallback_PrivateMatchLaunch( entity player, array<string> args )
{
if ( GetConVarBool( "ns_private_match_only_host_can_start" ) )
- if ( !NSIsPlayerIndexLocalPlayer( player.GetPlayerIndex() ) )
+ if ( !NSIsPlayerLocalPlayer( player ) )
return true
- PlayerChangedTheGame( player , " changed the game state." , args )
+ LogPrivateMatchChange( player , " changed the game state." , args )
if ( file.startState == ePrivateMatchStartState.STARTING )
{
- PlayerChangedTheGame( player , " canceled the game countdown." , args )
+ LogPrivateMatchChange( player , " canceled the game countdown." , args )
// cancel start if we're already mid-countdown
file.startState = ePrivateMatchStartState.READY
@@ -54,7 +54,7 @@ bool function ClientCommandCallback_PrivateMatchLaunch( entity player, array<str
}
else
{
- PlayerChangedTheGame( player , " started the game countdown." , args )
+ LogPrivateMatchChange( player , " started the game countdown." , args )
// start match
file.startState = ePrivateMatchStartState.STARTING
@@ -73,10 +73,10 @@ bool function ClientCommandCallback_PrivateMatchSetMode( entity player, array<st
return true
if ( GetConVarInt( "ns_private_match_only_host_can_change_settings" ) == 2 )
- if ( !NSIsPlayerIndexLocalPlayer( player.GetPlayerIndex() ) )
+ if ( !NSIsPlayerLocalPlayer( player ) )
return true
- PlayerChangedTheGame( player , " changed the mode to " , args )
+ LogPrivateMatchChange( player , " changed the mode to " , args )
// todo: need to verify this value
file.mode = args[0]
@@ -97,10 +97,10 @@ bool function ClientCommandCallback_SetCustomMap( entity player, array<string> a
return true
if ( GetConVarInt( "ns_private_match_only_host_can_change_settings" ) == 2 )
- if ( !NSIsPlayerIndexLocalPlayer( player.GetPlayerIndex() ) )
+ if ( !NSIsPlayerLocalPlayer( player ) )
return true
- PlayerChangedTheGame( player , " changed the map to " , args )
+ LogPrivateMatchChange( player , " changed the map to " , args )
// todo: need to verify this value
file.map = args[0]
@@ -217,10 +217,10 @@ bool function ClientCommandCallback_PrivateMatchSetPlaylistVarOverride( entity p
return true
if ( GetConVarInt( "ns_private_match_only_host_can_change_settings" ) >= 1 )
- if ( !NSIsPlayerIndexLocalPlayer( player.GetPlayerIndex() ) )
+ if ( !NSIsPlayerLocalPlayer( player ) )
return true
- PlayerChangedTheGame( player , " override the setting " , args )
+ LogPrivateMatchChange( player , " override the setting " , args )
bool found = false
foreach ( string category in GetPrivateMatchSettingCategories() )
@@ -244,23 +244,21 @@ bool function ClientCommandCallback_PrivateMatchSetPlaylistVarOverride( entity p
bool function ClientCommandCallback_ResetMatchSettingsToDefault( entity player, array<string> args )
{
if ( GetConVarInt( "ns_private_match_only_host_can_change_settings" ) >= 1 )
- if ( !NSIsPlayerIndexLocalPlayer( player.GetPlayerIndex() ) )
+ if ( !NSIsPlayerLocalPlayer( player ) )
return true
- PlayerChangedTheGame( player , " reset to default" , args )
+ LogPrivateMatchChange( player , " reset to default" , args )
ClearPlaylistVarOverrides()
return true
}
-void function PlayerChangedTheGame( entity player , string step , array<string> args ){
- if( step.find( "mode" ) || step.find( "map" )){
+void function LogPrivateMatchChange( entity player , string step , array<string> args )
+{
+ if( step.find( "mode" ) || step.find( "map" ) )
print( player.GetPlayerName() + step + args[ 0 ] + ".---" + "UID:" +player.GetUID() )
- }
- else if(step.find("setting")){
+ else if ( step.find( "setting" ) )
print( player.GetPlayerName() + step + args[ 0 ] + " to "+ args[ 1 ] + ".---" + "UID:" +player.GetUID() )
- }
- else{
+ else
print( player.GetPlayerName() + step + ".---" + "UID:" + player.GetUID())
- }
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
index ccccefaf..d2be2ab4 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/lobby/sh_private_lobby_modes_init.gnut
@@ -17,6 +17,7 @@ void function PrivateMatchModesInit()
AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_PILOT", "boosts_enabled", [ "#SETTING_DEFAULT", "#SETTING_DISABLED" ], "1" )
AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_PILOT", "earn_meter_pilot_overdrive", [ "#SETTING_DISABLED", "#SETTING_ENABLED", "Only" ], "1" )
AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_PILOT", "earn_meter_pilot_multiplier", "1.0" )
+ AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_PILOT", "player_force_respawn", "5" )
AddPrivateMatchModeSettingArbitrary( "#MODE_SETTING_CATEGORY_TITAN", "earn_meter_titan_multiplier", "1.0" )
AddPrivateMatchModeSettingEnum( "#MODE_SETTING_CATEGORY_TITAN", "aegis_upgrades", [ "#SETTING_DISABLED", "#SETTING_ENABLED" ], "0" )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype.gnut
index a4c6e187..362407b3 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype.gnut
@@ -1412,15 +1412,28 @@ void function CodeCallback_WeaponFireInCloak( entity player )
//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" )
+ entity cloakWeapon
+ int offhandSlot
+ for ( int i = 0; i <= OFFHAND_MELEE; i++ ) // OFFHAND_MELEE is the largest
{
- player.TakeOffhandWeapon( OFFHAND_LEFT )
- player.GiveOffhandWeapon( "mp_ability_cloak", OFFHAND_LEFT )
- weapon = player.GetOffhandWeapon( OFFHAND_LEFT )
- weapon.SetWeaponPrimaryClipCountAbsolute( 0 )
+ entity nowWeapon = player.GetOffhandWeapon( i )
+ if( IsValid( nowWeapon ))
+ {
+ if( nowWeapon.GetWeaponClassName() == "mp_ability_cloak" )
+ {
+ cloakWeapon = nowWeapon
+ offhandSlot = i
+ }
+ }
+ }
+ if( IsValid( cloakWeapon ) )
+ {
+ array<string> mods = cloakWeapon.GetMods()
+ // can't reset cooldown properly in script, let's give player a empty one
+ player.TakeWeapon( "mp_ability_cloak" ) // not using TakeWeaponNow() to fit vanilla behavior
+ player.GiveOffhandWeapon( "mp_ability_cloak", offhandSlot, mods )
+ cloakWeapon = player.GetOffhandWeapon( offhandSlot )
+ cloakWeapon.SetWeaponPrimaryClipCountAbsolute( 0 )
}
}
else
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
index 1c53167f..b77a37b2 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut
@@ -19,6 +19,8 @@ global function ShouldEntTakeDamage_SPMP
global function GetTitanBuildTime
global function TitanPlayerHotDropsIntoLevel
+global function SetRecalculateRespawnAsTitanStartPointCallback
+
struct {
bool killcamsEnabled = true
bool playerDeathsHidden = false
@@ -26,6 +28,8 @@ struct {
entity intermissionCamera
array<entity> specCams
+
+ entity functionref( entity player, entity basePoint ) recalculateRespawnAsTitanStartPointCallback
} file
void function BaseGametype_Init_MPSP()
@@ -39,6 +43,8 @@ void function BaseGametype_Init_MPSP()
AddCallback_OnPlayerKilled( CheckForAutoTitanDeath )
RegisterSignal( "PlayerRespawnStarted" )
RegisterSignal( "KillCamOver" )
+
+ FlagInit( "WeaponDropsAllowed", true )
}
void function SetIntermissionCamera( entity camera )
@@ -130,6 +136,15 @@ void function CodeCallback_OnClientConnectionCompleted( entity player )
Lobby_OnClientConnectionCompleted( player )
return
}
+ else if ( !IsFDMode( GAMETYPE ) )
+ {
+ // reset this for non-fd modes
+ // for some reason the postgame scoreboard uses this to
+ // determine if it should show the FD aegis rank one
+ // FD should either set this in their own mode, or add an else
+ // to this if statement when it releases
+ player.SetPersistentVar( "lastFDTitanRef", "" )
+ }
player.hasConnected = true
@@ -270,6 +285,9 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
ClearRespawnAvailable( player )
+ // reset this so that we default to pilot spawn
+ player.SetPersistentVar( "spawnAsTitan", false )
+
OnThreadEnd( function() : ( player )
{
if ( !IsValid( player ) )
@@ -279,6 +297,10 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
})
entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ int eHandle = attacker.GetEncodedEHandle()
+ if ( inflictor && ShouldTryUseProjectileReplay( player, attacker, damageInfo, false ) )
+ eHandle = inflictor.GetEncodedEHandle()
int methodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
table<int, bool> alreadyAssisted
@@ -298,6 +320,9 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
attackerInfo.attacker.AddToPlayerGameStat( PGS_ASSISTS, 1 )
}
}
+
+ if( attacker.IsPlayer() )
+ Highlight_SetDeathRecapHighlight( attacker, "killer_outline" )
}
player.p.rematchOrigin = player.p.deathOrigin
@@ -355,7 +380,7 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
if ( "respawnTime" in attacker.s )
respawnTime = Time() - expect float ( attacker.s.respawnTime )
- thread PlayerWatchesKillReplayWrapper( player, attacker, respawnTime, timeOfDeath, beforeTime, replayTracker )
+ thread PlayerWatchesKillReplayWrapper( player, attacker, eHandle, respawnTime, timeOfDeath, beforeTime, replayTracker )
}
player.SetPlayerSettings( "spectator" ) // prevent a crash with going from titan => pilot on respawn
@@ -372,6 +397,13 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
SetRespawnAvailable( player )
wait respawnDelay
+
+ int forceRespawn = GetCurrentPlaylistVarInt( "player_force_respawn", -1 )
+
+ // -1 is disabled, anything over is the time we wait in seconds
+ // before respawning the player
+ if( forceRespawn >= 0 )
+ thread ForceRespawnMeSignalAfterDelay( player, forceRespawn )
player.WaitSignal( "RespawnMe" ) // set in base_gametype: ClientCommand_RespawnPlayer
@@ -391,7 +423,22 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga
}
}
-void function PlayerWatchesKillReplayWrapper( entity player, entity attacker, float timeSinceAttackerSpawned, float timeOfDeath, float beforeTime, table replayTracker )
+// idk if this is a good delay or if it matches vanilla
+void function ForceRespawnMeSignalAfterDelay( entity player, int delay = 5 )
+{
+ player.EndSignal( "RespawnMe" )
+ player.EndSignal( "OnDestroy" )
+
+ if( player.IsWatchingKillReplay() )
+ player.WaitSignal( "KillCamOver" )
+
+ wait delay
+
+ printt( format( "Forcing player respawn for player %s (took >%d seconds to respawn)", player.GetPlayerName(), delay ) )
+ player.Signal( "RespawnMe" )
+}
+
+void function PlayerWatchesKillReplayWrapper( entity player, entity attacker, int eHandle, float timeSinceAttackerSpawned, float timeOfDeath, float beforeTime, table replayTracker )
{
player.EndSignal( "RespawnMe" )
player.EndSignal( "OnRespawned" )
@@ -414,7 +461,7 @@ void function PlayerWatchesKillReplayWrapper( entity player, entity attacker, fl
})
player.SetPredictionEnabled( false )
- PlayerWatchesKillReplay( player, attacker.GetEncodedEHandle(), attacker.GetIndexForEntity(), timeSinceAttackerSpawned, timeOfDeath, beforeTime, replayTracker )
+ PlayerWatchesKillReplay( player, eHandle, attacker.GetIndexForEntity(), timeSinceAttackerSpawned, timeOfDeath, beforeTime, replayTracker )
}
void function DecideRespawnPlayer( entity player )
@@ -424,16 +471,25 @@ void function DecideRespawnPlayer( entity player )
void function RespawnAsPilot( entity player )
{
+ // respawn crash exploit hotfix
+ if(IsAlive( player )) return
+
player.RespawnPlayer( FindSpawnPoint( player, false, ( ShouldStartSpawn( player ) || Flag( "ForceStartSpawn" ) ) && !IsFFAGame() ) )
}
void function RespawnAsTitan( entity player, bool manualPosition = false )
{
+ // respawn crash exploit hotfix
+ if(IsAlive( player )) return
+
player.Signal( "PlayerRespawnStarted" )
player.isSpawning = true
entity spawnpoint = FindSpawnPoint( player, true, ( ShouldStartSpawn( player ) || Flag( "ForceStartSpawn" ) ) && !IsFFAGame() )
+ if ( file.recalculateRespawnAsTitanStartPointCallback != null )
+ spawnpoint = file.recalculateRespawnAsTitanStartPointCallback( player, spawnpoint )
+
TitanLoadoutDef titanLoadout = GetTitanLoadoutForPlayer( player )
asset model = GetPlayerSettingsAssetForClassName( titanLoadout.setFile, "bodymodel" )
@@ -447,7 +503,20 @@ void function RespawnAsTitan( entity player, bool manualPosition = false )
AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) // hide hud
// do titanfall scoreevent
- AddPlayerScore( player, "Titanfall", player )
+ if ( !level.firstTitanfall )
+ {
+ AddPlayerScore( player, "FirstTitanfall", player )
+
+ #if HAS_STATS
+ UpdatePlayerStat( player, "misc_stats", "titanFallsFirst" )
+ #endif
+
+ level.firstTitanfall = true
+ }
+ else
+ {
+ AddPlayerScore( player, "Titanfall", player )
+ }
entity camera = CreateTitanDropCamera( spawnpoint.GetAngles(), < 90, titan.GetAngles().y, 0 > )
camera.SetParent( titan )
@@ -478,7 +547,7 @@ void function RespawnAsTitan( entity player, bool manualPosition = false )
titan.Destroy() // pilotbecomestitan leaves an npc titan that we need to delete
else
RespawnAsPilot( player ) // this is 100% an edgecase, just avoid softlocking if we ever hit it in playable gamestates
-
+
camera.Fire( "Disable", "!activator", 0, player )
camera.Destroy()
})
@@ -487,6 +556,7 @@ void function RespawnAsTitan( entity player, bool manualPosition = false )
player.RespawnPlayer( null ) // spawn player as pilot so they get their pilot loadout on embark
player.SetOrigin( titan.GetOrigin() )
+ ClearTitanAvailable( player ) // titanfall succeed, clear titan availability
// don't make player titan when entity batteryContainer is not valid.
// This will prevent a servercrash that sometimes occur when evac is disabled and somebody is calling a titan in the defeat screen.
@@ -555,10 +625,19 @@ void function CheckForAutoTitanDeath( entity victim, entity attacker, var damage
}
}
+void function SetRecalculateRespawnAsTitanStartPointCallback( entity functionref( entity player, entity basePoint ) callbackFunc )
+{
+ file.recalculateRespawnAsTitanStartPointCallback = callbackFunc
+}
+
// stuff to change later
bool function ShouldEntTakeDamage_SPMP( entity ent, var damageInfo )
{
+ // dropships are immune to being crushed
+ if ( ( IsDropship( ent ) || IsEvacDropship( ent ) ) && IsTitanCrushDamage( damageInfo ) )
+ return false
+
return true
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_battery_port.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_battery_port.gnut
index 37b89169..ea88c1bc 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_battery_port.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_battery_port.gnut
@@ -1 +1,219 @@
-//fuck \ No newline at end of file
+untyped
+global function InitTurretBatteryPort // only for fw turrets!
+
+void function InitTurretBatteryPort( entity batteryPort )
+{
+
+ batteryPort.s.beingUsed <- false // bool
+ batteryPort.s.hackAvaliable <- true // bool, for controlling hacking avaliablity
+
+ // SetUsableByGroup() updates is done in TurretStateWatcher()
+ batteryPort.SetUsableByGroup( "pilot" ) // show hind to any pilots
+ batteryPort.SetUsePrompts( "#RODEO_APPLY_BATTERY_HINT", "#RODEO_APPLY_BATTERY_HINT" ) // don't know what to use
+ AddCallback_OnUseEntity( batteryPort, OnUseTurretBatteryPort )
+}
+
+function OnUseTurretBatteryPort( entBeingUse, user )
+{
+ expect entity( entBeingUse )
+ expect entity( user )
+
+ //print( "try to use batteryPort" )
+ thread TryUseTurretBatteryPort( user, entBeingUse )
+}
+
+void function TryUseTurretBatteryPort( entity player, entity batteryPort )
+{
+ if( batteryPort.s.beingUsed ) // already being using
+ return
+
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "ScriptAnimStop" ) // so you can jump off animation
+ AddButtonPressedPlayerInputCallback( player, IN_JUMP, ForceStopUseBatteryPort )
+
+ OnThreadEnd(
+ function():( player )
+ {
+ RemoveButtonPressedPlayerInputCallback( player, IN_JUMP, ForceStopUseBatteryPort )
+ }
+ )
+
+
+ var BatteryPortUsable = batteryPort.s.isUsable
+
+ if( expect bool( BatteryPortUsable( batteryPort, player ) ) )
+ {
+ // friendly try to apply one, or enemy try to hack this turret
+ waitthread PlayerApplesBatteryPackToPort( player, batteryPort )
+ }
+}
+
+void function ForceStopUseBatteryPort( entity player )
+{
+ player.Signal( "ScriptAnimStop" )
+}
+
+void function PlayerApplesBatteryPackToPort( entity player, entity batteryPort )
+{
+ table result = {}
+ result.success <- false
+ batteryPort.s.beingUsed = true
+
+ BatteryPortSequenceStruct dataStruct = DisableCloakBeforeBatteryPortSequence( player )
+
+ // these are from _rodeo_titan.gnut
+ entity battery = GetBatteryOnBack( player )
+ battery.Hide() //Hide it because the animation has a battery model already
+ Battery_StopFX( battery )
+
+ entity tempBattery3p
+ tempBattery3p = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ tempBattery3p.SetParent( player, "R_HAND", false, 0.0 )
+ tempBattery3p.RemoveFromSpatialPartition()
+
+ entity tempBattery1p
+ tempBattery1p = CreatePropDynamic( RODEO_BATTERY_MODEL_FOR_RODEO_ANIMS )
+ tempBattery1p.SetParent( player.GetFirstPersonProxy(), "R_HAND", false, 0.0 )
+ tempBattery1p.RemoveFromSpatialPartition()
+
+ player.p.rodeoAnimTempProps.append( tempBattery3p )
+ player.p.rodeoAnimTempProps.append( tempBattery1p )
+
+ OnThreadEnd(
+ function() : ( battery, batteryPort, player, result, dataStruct )
+ {
+ if ( IsValid( battery ) ) // animation interrupted, otherwise the battery will be destroyed
+ {
+ battery.Show()
+ Battery_StartFX( battery )
+ }
+
+ if ( IsValid( batteryPort ) )
+ {
+ batteryPort.s.beingUsed = false
+ batteryPort.Anim_Stop()
+ }
+
+ if ( IsValid( player ) )
+ {
+ // restore control
+ DeployAndEnableWeapons( player )
+ //ViewConeFree( player ) // no need to lock viewcone
+
+ // clean up
+ ClearBatteryAnimTempProps( player )
+ PutEntityInSafeSpot( player, player, null, player.GetOrigin() + <0, 0, 32>, player.GetOrigin() )
+
+ CleanUpBatterySequenceForPlayer( player )
+ RestoreCloakAfterBatteryPortSequence( player, dataStruct )
+ }
+ }
+ )
+
+ FirstPersonSequenceStruct sequence
+ sequence.attachment = "REF" // only ref the batteryPort has
+
+ sequence.thirdPersonAnim = "pt_mp_battery_port_insert" //"pt_rodeo_ride_r_return_battery"
+ sequence.firstPersonAnim = "ptpov_mp_battery_port_insert" //"ptpov_rodeo_ride_r_return_battery"
+
+ // player stats
+ HolsterAndDisableWeapons( player )
+ //ViewConeZero( player ) // no need to lock viewcone
+
+ batteryPort.Anim_Play( "bp_mp_battery_port_insert" )
+
+ thread WaitForActivateBattery( player, battery, batteryPort )
+ waitthread FirstPersonSequence( sequence, player, batteryPort )
+}
+
+void function WaitForActivateBattery( entity player, entity battery, entity batteryPort )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "ScriptAnimStop" ) // so you can jump off animation
+ battery.EndSignal( "OnDestroy" )
+
+ player.WaitSignal( "BatteryActivate" ) // this is registered in _gamemode_fw.nut!
+ ApplyBatteryToBatteryPort( player, batteryPort )
+}
+
+void function ApplyBatteryToBatteryPort( entity player, entity batteryPort )
+{
+ if ( player.GetPlayerNetInt( "batteryCount" ) <= 0 ) // player actually not carrying a battery
+ return
+
+ entity battery = Rodeo_TakeBatteryAwayFromPilot( player )
+ if ( !IsValid( battery ) )
+ return
+
+ // player can apply battery
+
+ // disable hacking
+ batteryPort.s.hackAvaliable = false // can't be hacked again until completely killed
+
+
+ var useBatteryPort = batteryPort.s.useBattery
+ useBatteryPort( batteryPort, player )
+
+ // all things done, destroy this batt
+ battery.Destroy()
+}
+
+// for disabling cloak
+struct BatteryPortSequenceStruct
+{
+ bool wasCloaked = false
+ float cloakEndTime = 0.0
+}
+
+BatteryPortSequenceStruct function DisableCloakBeforeBatteryPortSequence( entity player )
+{
+ BatteryPortSequenceStruct dataStruct
+ if ( !IsCloaked( player ) )
+ return dataStruct // empty struct!
+
+ dataStruct.wasCloaked = true
+ dataStruct.cloakEndTime = player.GetCloakEndTime()
+ DisableCloak( player, 0.0 )
+
+ return dataStruct
+}
+
+bool function RestoreCloakAfterBatteryPortSequence( entity player, BatteryPortSequenceStruct dataStruct )
+{
+ if ( !IsAlive( player ) )
+ return false
+
+ if ( !dataStruct.wasCloaked )
+ return false
+
+ if ( dataStruct.cloakEndTime <= 0.0 )
+ return false
+
+ 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
+ return false
+
+ EnableCloak( player, remainingCloakDuration, CLOAK_FADE_IN )
+ return true
+}
+
+void function CleanUpBatterySequenceForPlayer( entity player )
+{
+ ClearPlayerAnimViewEntity( player )
+ player.AnimViewEntity_SetLerpOutTime( 0.4 ) // blend out the clear anim view entity
+ player.ClearParent()
+ player.Anim_Stop()
+}
+
+void function ClearBatteryAnimTempProps( entity player )
+{
+ foreach( tempProp in player.p.rodeoAnimTempProps )
+ {
+ if ( IsValid( tempProp ) )
+ tempProp.Destroy()
+ }
+
+ player.p.rodeoAnimTempProps.clear()
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut
index 466a5042..016097f2 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut
@@ -1,6 +1,276 @@
global function InitChallenges
+global function SetPlayerChallengeEvacState //Hooked in _evac.gnut
+global function SetPlayerChallengeMatchWon //Hooked in _score.nut
+global function SetPlayerChallengeMatchComplete //Hooked in _score.nut
+global function SetPlayerChallengeMeritScore //Up to gamemodes to use this directly if needed
+global function IncrementPlayerChallengeTitanLeveledUp //Hooked in titan_xp.gnut
+global function IncrementPlayerChallengeWeaponLeveledUp //Hooked in weapon_xp.gnut
+global function IncrementPlayerChallengeFactionLeveledUp //Hooked in faction_xp.gnut (invisible but necessary for post-summary menu)
+global function RegisterChallenges_OnMatchEnd //Hooked in _gamestate_mp.gnut
+
+global function HasPlayerCompletedMeritScore //Check from gamemodes to not reapply SetPlayerChallengeMeritScore
+global function SetupGenericTDMChallenge //Used by gamemodes which simply adopts the: "Kill 3 Pilots without dying." Challenge
+global function SetupGenericFFAChallenge //Used by gamemodes which simply adopts the: "Kill 5 Pilots." Challenge
+
+struct
+{
+ table< entity, int > playerTotalMeritCount
+ table< entity, bool > playerChallenge
+ table< entity, int > pilotstreak
+ bool isHappyHourActive
+} file
+
+
+
+
+
+
+/*=============================================================================================================
+ __ __ _ _ ____ _ _ _
+ | \/ | __ _ | |_ ___ | |__ / ___|| |__ __ _ | || | ___ _ __ __ _ ___ ___
+ | |\/| | / _` || __|/ __|| '_ \ | | | '_ \ / _` || || | / _ \| '_ \ / _` | / _ \/ __|
+ | | | || (_| || |_| (__ | | | | | |___ | | | || (_| || || || __/| | | || (_| || __/\__ \
+ |_| |_| \__,_| \__|\___||_| |_| \____||_| |_| \__,_||_||_| \___||_| |_| \__, | \___||___/
+ |___/
+=============================================================================================================*/
void function InitChallenges()
{
+#if (UI && CLIENT)
+
+ SCB_SetCompleteMeritState( 4 )
+ SCB_SetEvacMeritState( 4 )
+ SCB_SetMeritCount( 4 )
+ SCB_SetScoreMeritState( 4 )
+ SCB_SetWinMeritState( 4 )
+ SCB_SetWeaponMeritCount( -1 )
+ SCB_SetTitanMeritCount( -1 )
+
+#elseif (SERVER && MP)
+
+ AddCallback_OnClientConnected( SetupPlayerMenuChallenges )
+ AddCallback_OnClientDisconnected( RemovePlayerFromChallengePool )
+
+#endif
+}
+
+void function SetupPlayerMenuChallenges( entity player )
+{
+ file.playerTotalMeritCount[ player ] <- 0
+ file.pilotstreak[ player ] <- 0
+ file.playerChallenge[ player ] <- false
+
+ thread SetupChallenges_Threaded( player )
+}
+void function SetupChallenges_Threaded( entity player )
+{
+ player.EndSignal( "OnDestroy" )
+
+ WaitFrame()
+
+ Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 4 ) //4 tells RUI to hide it
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetScoreMeritState", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetWinMeritState", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetWeaponMeritCount", 0 )
+ Remote_CallFunction_UI( player, "SCB_SetTitanMeritCount", 0 )
+}
+
+void function SetupGenericTDMChallenge()
+{
+ AddCallback_OnPlayerKilled( TDMChallenges_OnPlayerKilled )
+}
+
+void function SetupGenericFFAChallenge()
+{
+ AddCallback_OnPlayerKilled( FFAChallenges_OnPlayerKilled )
+}
+
+void function RemovePlayerFromChallengePool( entity player )
+{
+ if( player in file.playerChallenge )
+ delete file.playerChallenge[ player ]
+ if( player in file.playerTotalMeritCount )
+ delete file.playerTotalMeritCount[ player ]
+ if( player in file.pilotstreak )
+ delete file.pilotstreak[ player ]
+}
+
+void function RegisterChallenges_OnMatchEnd()
+{
+ bool eliteWarpaintRNG = false
+
+ if( RandomIntRange( 0, 100 ) <= 30 ) //30% Chance to trigger akin to vanilla, apply always since all players have paid cosmetics unlocked
+ eliteWarpaintRNG = true
+
+ foreach( player in GetPlayerArray() )
+ {
+ player.SetPersistentVar( "isPostGameScoreboardValid", true )
+ player.SetPersistentVar( "isFDPostGameScoreboardValid", false ) //FD itself overrides this right after when match ends
+ SetUIVar( level, "showGameSummary", true )
+
+ if( eliteWarpaintRNG )
+ SetPlayerChallengeSquadLeader( player )
+
+ if( ShouldAwardHappyHourBonus( player ) )
+ {
+ AddPlayerScore( player, "HappyHourBonus" )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.HAPPY_HOUR + "]", 5 ) //The XP Given from Happy Hour Score is 5 merits
+ }
+ }
+}
+
+void function TDMChallenges_OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( victim.IsPlayer() )
+ {
+ if( victim in file.pilotstreak )
+ file.pilotstreak[victim] = 0
+ if( attacker in file.pilotstreak )
+ {
+ file.pilotstreak[attacker]++
+ if( file.pilotstreak[attacker] >= 3 && !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengeTDM" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
+ }
+}
+
+void function FFAChallenges_OnPlayerKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing )
+ return
+
+ if ( victim.IsPlayer() && attacker in file.pilotstreak )
+ {
+ file.pilotstreak[attacker]++
+ if( file.pilotstreak[attacker] >= 5 && !HasPlayerCompletedMeritScore( attacker ) )
+ {
+ AddPlayerScore( attacker, "ChallengePVPKillCount" )
+ SetPlayerChallengeMeritScore( attacker )
+ }
+ }
+}
+
+bool function HasPlayerCompletedMeritScore( entity player )
+{
+ Assert( player in file.playerChallenge, player + " is not registered in the challenge pool hooks." )
+ return file.playerChallenge[ player ]
+}
+
+
+
+
+
+
+
+/*=============================================================================================================
+ ____ _ _ _ _
+ / ___| __ _ _ __ ___ ___ _ __ ___ ___ __| | ___ | | | | ___ ___ | | __ ___
+ | | _ / _` || '_ ` _ \ / _ \| '_ ` _ \ / _ \ / _` | / _ \ | |_| | / _ \ / _ \ | |/ // __|
+ | |_| || (_| || | | | | || __/| | | | | || (_) || (_| || __/ | _ || (_) || (_) || < \__ \
+ \____| \__,_||_| |_| |_| \___||_| |_| |_| \___/ \__,_| \___| |_| |_| \___/ \___/ |_|\_\|___/
+
+=============================================================================================================*/
+
+void function SetPlayerChallengeEvacState( entity player, int successEvac = 0 )
+{
+ if( successEvac == 0 ) //Evac Ship destroyed
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 2 )
+
+ else if( successEvac == 1 ) //Player itself managed to evac
+ {
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 1 )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.EVAC + "]", 1 )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+ }
+
+ else if( successEvac == 2 ) //Team managed to evac
+ Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 3 )
+}
+
+void function SetPlayerChallengeMatchWon( entity player, bool playerWon )
+{
+ if( playerWon )
+ {
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetWinMeritState", 1 )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.MATCH_VICTORY + "]", 1 )
+ player.SetPersistentVar( "matchWin", true )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+ }
+ else
+ Remote_CallFunction_UI( player, "SCB_SetWinMeritState", -1 )
+}
+
+void function SetPlayerChallengeMatchComplete( entity player )
+{
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 1 )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.MATCH_COMPLETED + "]", 1 )
+ player.SetPersistentVar( "matchComplete", true )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+}
+
+void function SetPlayerChallengeSquadLeader( entity player )
+{
+ if( !ProgressionEnabledForPlayer( player ) )
+ return
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderDoubleXP" )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderBonus", player.GetEncodedEHandle() )
+ player.SetPersistentVar( "matchSquadBonus", true )
+ Player_GiveDoubleXP( player, 1 )
+ foreach( entity teamplayer in GetPlayerArrayOfTeam( player.GetTeam() ) )
+ {
+ if( teamplayer == player )
+ continue
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderBonus", teamplayer.GetEncodedEHandle() )
+ }
+}
+
+void function SetPlayerChallengeMeritScore( entity player )
+{
+ if( !HasPlayerCompletedMeritScore( player ) )
+ {
+ file.playerChallenge[ player ] = true
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetScoreMeritState", 1 )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.SCORE_MILESTONE + "]", 1 )
+ player.SetPersistentVar( "matchScoreEvent", true )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+ }
+}
+
+void function IncrementPlayerChallengeTitanLeveledUp( entity player )
+{
+ player.p.meritData.titanMerits++
+ file.playerTotalMeritCount[ player ]++
+
+ Remote_CallFunction_UI( player, "SCB_SetTitanMeritCount", player.p.meritData.titanMerits++ )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+}
+
+void function IncrementPlayerChallengeWeaponLeveledUp( entity player )
+{
+ player.p.meritData.weaponMerits++
+ file.playerTotalMeritCount[ player ]++
+
+ Remote_CallFunction_UI( player, "SCB_SetWeaponMeritCount", player.p.meritData.weaponMerits )
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
+}
+
+void function IncrementPlayerChallengeFactionLeveledUp( entity player )
+{
+ file.playerTotalMeritCount[ player ]++
+ Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] )
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut
index 16a3ce92..0ababfc7 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut
@@ -58,7 +58,9 @@ void function CodeCallback_MatchIsOver()
#if MP
void function PopulatePostgameData()
{
- // something's busted here because this isn't showing automatically on match end, ag
+ // show the postgame scoreboard summary
+ SetUIVar( level, "showGameSummary", true )
+
foreach ( entity player in GetPlayerArray() )
{
int teams = GetCurrentPlaylistVarInt( "max_teams", 2 )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
index 0555df9b..c3bdf01c 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut
@@ -26,18 +26,9 @@ const int MAX_DROPSHIP_PLAYERS = 4
global const float DROPSHIP_INTRO_LENGTH = 15.0 // TODO tweak this
-struct IntroDropship
-{
- entity dropship
-
- int playersInDropship
- entity[MAX_DROPSHIP_PLAYERS] players
-}
-
struct {
- // these used to be IntroDropship[2]s but i wanted to be able to use array.getrandom so they have to be actual arrays
- array<IntroDropship> militiaDropships
- array<IntroDropship> imcDropships
+ table< entity, array<entity> > militiaDropships
+ table< entity, array<entity> > imcDropships
float introStartTime
} file
@@ -52,7 +43,12 @@ void function ClassicMP_DefaultDropshipIntro_Setup()
void function DropshipIntro_OnClientConnected( entity player )
{
if ( GetGameState() == eGameState.Prematch )
- thread SpawnPlayerIntoDropship( player )
+ {
+ if( PlayerCanSpawn( player ) )
+ DoRespawnPlayer( player, null )
+
+ PutPlayerInDropship( player )
+ }
}
void function OnPrematchStart()
@@ -62,11 +58,11 @@ void function OnPrematchStart()
print( "starting dropship intro!" )
file.introStartTime = Time()
- // make 2 empty dropship structs per team
- IntroDropship emptyDropship
+ // Clear Dropship arrays of Teams for Match Restarts (i.e Half-Times)
file.militiaDropships.clear()
file.imcDropships.clear()
+ // Try to gather all possible Dropship spawn points for Team
array<entity> validDropshipSpawns
array<entity> dropshipSpawns = GetEntArrayByClass_Expensive( "info_spawnpoint_dropship_start" )
foreach ( entity dropshipSpawn in dropshipSpawns )
@@ -78,47 +74,47 @@ void function OnPrematchStart()
validDropshipSpawns.append( dropshipSpawn )
}
- // if no dropship spawns for this mode, just allow any dropship spawns
+ // Use any spawn point if not enough valid for this Gamemode exists
if ( validDropshipSpawns.len() < 2 )
validDropshipSpawns = dropshipSpawns
// spawn dropships
foreach ( entity dropshipSpawn in validDropshipSpawns )
{
- // todo: possibly make this only spawn dropships if we've got enough players to need them
int createTeam = HasSwitchedSides() ? GetOtherTeam( dropshipSpawn.GetTeam() ) : dropshipSpawn.GetTeam()
- array<IntroDropship> teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
+ table< entity, array<entity> > teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships
if ( teamDropships.len() >= 2 )
- continue
+ break
- // create entity
entity dropship = CreateDropship( createTeam, dropshipSpawn.GetOrigin(), dropshipSpawn.GetAngles() )
-
- teamDropships.append( clone emptyDropship )
- teamDropships[ teamDropships.len() - 1 ].dropship = dropship
-
AddAnimEvent( dropship, "dropship_warpout", WarpoutEffect )
+
dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
- dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+ if ( dropshipSpawn.GetTeam() == TEAM_IMC )
+ dropship.SetValueForModelKey( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" )
DispatchSpawn( dropship )
- // have to do this after dispatch otherwise it won't work for some reason
- // weirdly enough, tf2 actually does use different dropships for imc and militia, despite these concepts not really being a thing for players in tf2
- // probably was just missed by devs, but keeping it in for accuracy
+ dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
if ( dropshipSpawn.GetTeam() == TEAM_IMC )
dropship.SetModel( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" )
- else
- dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" )
+
+ teamDropships[ dropship ] <- [ null, null, null, null ]
thread PlayAnim( dropship, "dropship_classic_mp_flyin" )
}
+ // Populate Dropships
foreach ( entity player in GetPlayerArray() )
{
if ( !IsPrivateMatchSpectator( player ) )
- thread SpawnPlayerIntoDropship( player )
+ {
+ if( PlayerCanSpawn( player ) )
+ DoRespawnPlayer( player, null )
+
+ PutPlayerInDropship( player )
+ }
else
RespawnPrivateMatchSpectator( player )
}
@@ -128,75 +124,79 @@ void function OnPrematchStart()
void function EndIntroWhenFinished()
{
- wait 15.0
+ wait DROPSHIP_INTRO_LENGTH
ClassicMP_OnIntroFinished()
}
-void function SpawnPlayerIntoDropship( entity player )
+void function PutPlayerInDropship( entity player )
{
- player.EndSignal( "OnDestroy" )
+ //Find the player's dropship and seat
+ table< entity, array<entity> > teamDropships
+ if ( player.GetTeam() == TEAM_MILITIA )
+ teamDropships = file.militiaDropships
+ else
+ teamDropships = file.imcDropships
+
+ entity playerDropship
+ array< int > availableShipSlots
+ array< entity > introDropships
+ int playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS )
+ foreach( dropship, playerslot in teamDropships )
+ {
+ introDropships.append( dropship )
+ for ( int i = 0; i < MAX_DROPSHIP_PLAYERS; i++ )
+ {
+ if ( !IsValidPlayer( playerslot[i] ) )
+ availableShipSlots.append( i )
+ }
+
+ if( !availableShipSlots.len() )
+ continue
+
+ int slotPick = availableShipSlots.getrandom()
+ playerslot[slotPick] = player
+ playerDropship = dropship
+ playerDropshipIndex = slotPick
+ break
+ }
+
+ if( !IsAlive( playerDropship ) ) //If we're at this point, we have more players than we do dropships, so just pick a random one
+ playerDropship = introDropships.getrandom()
+
+ thread SpawnPlayerIntoDropship( player, playerDropshipIndex, playerDropship )
+}
- if ( IsAlive( player ) )
- player.Die() // kill them so we don't have any issues respawning them later
+void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, entity playerDropship )
+{
+ player.EndSignal( "OnDestroy" )
+ player.EndSignal( "OnDeath" )
- player.s.dropshipIntroIsJumping <- false
- OnThreadEnd( function() : ( player )
+ OnThreadEnd( function() : ( player, playerDropshipIndex, playerDropship )
{
if ( IsValid( player ) )
{
player.ClearParent()
ClearPlayerAnimViewEntity( player )
-
- if ( !player.s.dropshipIntroIsJumping )
- {
- player.MovementEnable()
- player.EnableWeaponViewModel()
- RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
- }
+ }
+ if( IsAlive( playerDropship ) )
+ {
+ if ( playerDropship.GetTeam() == TEAM_MILITIA )
+ file.militiaDropships[ playerDropship ][ playerDropshipIndex ] = null
+ else
+ file.imcDropships[ playerDropship ][ playerDropshipIndex ] = null
}
})
- WaitFrame()
-
- player.EndSignal( "OnDeath" )
-
- // find the player's dropship and seat
- array<IntroDropship> teamDropships
- if ( player.GetTeam() == TEAM_MILITIA )
- teamDropships = file.militiaDropships
- else
- teamDropships = file.imcDropships
-
- IntroDropship playerDropship
- int playerDropshipIndex = -1
- foreach ( IntroDropship dropship in teamDropships )
- for ( int i = 0; i < dropship.players.len(); i++ )
- if ( dropship.players[ i ] == null )
- {
- playerDropship = dropship
- playerDropshipIndex = i
-
- dropship.players[ i ] = player
- break
- }
-
- if ( playerDropship.dropship == null )
- {
- // if we're at this point, we have more players than we do dropships, so just pick a random one
- playerDropship = teamDropships.getrandom()
- playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS )
- }
-
- // respawn player and holster their weapons so they aren't out
- player.RespawnPlayer( null )
- HolsterAndDisableWeapons(player)
+ HolsterAndDisableWeapons( player )
player.DisableWeaponViewModel()
+ UnMuteAll( player )
+ StopSoundOnEntity( player, "Duck_For_FrontierDefenseTitanSelectScreen" )
// 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 )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.GetEncodedEHandle(), file.introStartTime )
// do firstperson sequence
FirstPersonSequenceStruct idleSequence
@@ -207,9 +207,7 @@ void function SpawnPlayerIntoDropship( entity player )
idleSequence.viewConeFunction = ViewConeRampFree
idleSequence.hideProxy = true
idleSequence.setInitialTime = Time() - file.introStartTime
- thread FirstPersonSequence( idleSequence, player, playerDropship.dropship )
- WaittillAnimDone( player )
-
+ waitthread FirstPersonSequence( idleSequence, player, playerDropship )
// 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
// jump sequence
@@ -217,13 +215,17 @@ void function SpawnPlayerIntoDropship( entity player )
jumpSequence.firstPersonAnim = DROPSHIP_JUMP_ANIMS_POV[ playerDropshipIndex ]
jumpSequence.thirdPersonAnim = DROPSHIP_JUMP_ANIMS[ playerDropshipIndex ]
jumpSequence.attachment = "ORIGIN"
+ jumpSequence.viewConeFunction = ViewConeFree
jumpSequence.setInitialTime = max( 0.0, Time() - ( file.introStartTime + 11.0 ) ) // 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 ) // somehow this is better than just waiting for the blocking FirstPersonSequence call?
+ #if BATTLECHATTER_ENABLED
+ if( playerDropshipIndex == 0 )
+ PlayBattleChatterLine( player, "bc_pIntroChat" )
+ #endif
+
+ waitthread FirstPersonSequence( jumpSequence, player, playerDropship )
- player.s.dropshipIntroIsJumping <- true
thread PlayerJumpsFromDropship( player )
}
@@ -239,20 +241,21 @@ void function PlayerJumpsFromDropship( entity player )
// show weapon viewmodel and hud and let them move again
player.MovementEnable()
player.EnableWeaponViewModel()
- DeployAndEnableWeapons(player)
+ DeployAndEnableWeapons( player )
RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING )
}
})
-
- // wait for intro timer to be fully done
- wait ( file.introStartTime + DROPSHIP_INTRO_LENGTH ) - Time()
- 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
- wait 0.1 // assume players will never actually hit ground before this
+ player.ClearParent()
+ WaitFrame()
+ player.SetVelocity( < 0, 0, -100 > ) // Toss players a bit down so it makes a smoother transition when jumping off the Dropship
+ player.MovementDisable() // Disable all movement but let them look around still
+ player.ConsumeDoubleJump() // MovementDisable doesn't prevent double jumps
+ WaitFrame()
while ( !player.IsOnGround() && !player.IsWallRunning() && !player.IsWallHanging() ) // todo this needs tweaking
WaitFrame()
- TryGameModeAnnouncement( player )
-} \ No newline at end of file
+ if ( GetRoundsPlayed() == 0 ) //Intro is announced only for the first round in Vanilla as certain gamemodes have different announcements for rounds restarts
+ TryGameModeAnnouncement( player )
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
index 425a8b8b..0d1b42b7 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
@@ -21,6 +21,8 @@ global function SetTitanMeterGainScale
#if MP
global function CodeCallback_OnServerAnimEvent
+global function CodeCallback_WeaponDropped
+global function AddCallback_OnWeaponDropped
#endif
struct AccumulatedDamageData
@@ -43,6 +45,7 @@ struct
]
table<entity, AccumulatedDamageData> playerAccumulatedDamageData
+ array< void functionref( entity ) > weaponDroppedCallbacks
} file
void function CodeCallback_Init()
@@ -283,12 +286,26 @@ void function CodeCallback_DamagePlayerOrNPC( entity ent, var damageInfo )
return
RunClassDamageFinalCallbacks( ent, damageInfo )
+
#if VERBOSE_DAMAGE_PRINTOUTS
printt( " after class damage final callbacks:", DamageInfo_GetDamage( damageInfo ) )
#endif
if ( DamageInfo_GetDamage( damageInfo ) == 0 )
return
+ // Added via AddEntityCallback_OnFinalDamaged
+ foreach ( callbackFunc in ent.e.entFinalDamageCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " afterAddEntityCallback_OnFinalDamaged callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
DamageInfo_AddDamageFlags( damageInfo, DAMAGEFLAG_NOPAIN )
@@ -1016,4 +1033,26 @@ void function CodeCallback_OnServerAnimEvent( entity ent, string eventName )
PerfEnd( PerfIndexServer.CB_OnServerAnimEvent )
}
+
+void function AddCallback_OnWeaponDropped( void functionref( entity ) callback )
+{
+ file.weaponDroppedCallbacks.append( callback )
+}
+
+void function CodeCallback_WeaponDropped( entity weapon )
+{
+ // shamelessly taken form SP
+ if ( !IsValid( weapon ) )
+ return
+
+ // Might look a bit hacky to put it here, but thats how SP does it
+ if ( !Flag( "WeaponDropsAllowed" ) )
+ {
+ weapon.Destroy()
+ return
+ }
+
+ foreach( callback in file.weaponDroppedCallbacks )
+ callback( weapon )
+}
#endif // #if MP \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
index bfcd23e0..f7c398d9 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut
@@ -13,6 +13,8 @@ global function SetTimerBased
global function SetShouldUseRoundWinningKillReplay
global function SetRoundWinningKillReplayKillClasses
global function SetRoundWinningKillReplayAttacker
+global function SetCallback_TryUseProjectileReplay
+global function ShouldTryUseProjectileReplay
global function SetWinner
global function SetTimeoutWinnerDecisionFunc
global function AddTeamScore
@@ -48,13 +50,28 @@ struct {
float roundWinningKillReplayTime
entity roundWinningKillReplayVictim
entity roundWinningKillReplayAttacker
+ int roundWinningKillReplayInflictorEHandle // this is either the inflictor or the attacker
int roundWinningKillReplayMethodOfDeath
float roundWinningKillReplayTimeOfDeath
float roundWinningKillReplayHealthFrac
array<void functionref()> roundEndCleanupCallbacks
+ bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) shouldTryUseProjectileReplayCallback
} file
+void function SetCallback_TryUseProjectileReplay( bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) callback )
+{
+ file.shouldTryUseProjectileReplayCallback = callback
+}
+
+bool function ShouldTryUseProjectileReplay( entity victim, entity attacker, var damageInfo, bool isRoundEnd )
+{
+ if ( file.shouldTryUseProjectileReplayCallback != null )
+ return file.shouldTryUseProjectileReplayCallback( victim, attacker, damageInfo, isRoundEnd )
+ // default to true (vanilla behaviour)
+ return true
+}
+
void function PIN_GameStart()
{
// todo: using the pin telemetry function here, weird and was done veeery early on before i knew how this all worked, should use a different one
@@ -79,6 +96,7 @@ void function PIN_GameStart()
AddCallback_OnPlayerKilled( OnPlayerKilled )
AddDeathCallback( "npc_titan", OnTitanKilled )
+ AddCallback_EntityChangedTeam( "player", OnPlayerChangedTeam )
RegisterSignal( "CleanUpEntitiesForRoundEnd" )
}
@@ -184,6 +202,14 @@ void function GameStateEnter_Prematch()
if ( !GetClassicMPMode() && !ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() )
thread StartGameWithoutClassicMP()
+
+ // Initialise any spectators. Hopefully they are all initialised already in CodeCallback_OnClientConnectionCompleted
+ // (_base_gametype_mp.gnut) but for modes like LTS this doesn't seem to happen late enough to work properly.
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( IsPrivateMatchSpectator( player ) )
+ InitialisePrivateMatchSpectatorPlayer( player )
+ }
}
void function StartGameWithoutClassicMP()
@@ -220,6 +246,8 @@ void function GameStateEnter_Playing_Threaded()
{
WaitFrame() // ensure timelimits are all properly set
+ thread DialoguePlayNormal() // runs dialogue play function
+
while ( GetGameState() == eGameState.Playing )
{
// could cache these, but what if we update it midgame?
@@ -268,6 +296,8 @@ void function GameStateEnter_WinnerDetermined_Threaded()
// do win announcement
int winningTeam = GetWinningTeamWithFFASupport()
+ DialoguePlayWinnerDetermined() // play a faction dialogue when winner is determined
+
foreach ( entity player in GetPlayerArray() )
{
int announcementSubstr
@@ -278,6 +308,9 @@ void function GameStateEnter_WinnerDetermined_Threaded()
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 ( player.GetTeam() == winningTeam )
+ UnlockAchievement( player, achievements.MP_WIN )
}
WaitFrame() // wait a frame so other scripts can setup killreplay stuff
@@ -312,6 +345,7 @@ void function GameStateEnter_WinnerDetermined_Threaded()
WaitFrame() // prevent a race condition with PlayerWatchesRoundWinningKillReplay
file.roundWinningKillReplayAttacker = null // clear this
+ file.roundWinningKillReplayInflictorEHandle = -1
if ( killcamsWereEnabled )
SetKillcamsEnabled( true )
@@ -362,6 +396,7 @@ void function GameStateEnter_WinnerDetermined_Threaded()
}
else
{
+ RegisterChallenges_OnMatchEnd()
if ( ClassicMP_ShouldRunEpilogue() )
{
ClassicMP_SetupEpilogue()
@@ -384,11 +419,14 @@ void function PlayerWatchesRoundWinningKillReplay( entity player, float replayLe
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 ( IsValid( attacker ) )
+ {
+ player.SetKillReplayDelay( Time() - replayLength, THIRD_PERSON_KILL_REPLAY_ALWAYS )
+ player.SetKillReplayInflictorEHandle( file.roundWinningKillReplayInflictorEHandle )
+ 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
{
@@ -450,6 +488,7 @@ void function GameStateEnter_SwitchingSides_Threaded()
svGlobal.levelEnt.Signal( "RoundEnd" ) // might be good to get a new signal for this? not 100% necessary tho i think
SetServerVar( "switchedSides", 1 )
file.roundWinningKillReplayAttacker = null // reset this after replay
+ file.roundWinningKillReplayInflictorEHandle = -1
if ( file.usePickLoadoutScreen )
SetGameState( eGameState.PickLoadout )
@@ -473,7 +512,7 @@ void function PlayerWatchesSwitchingSidesKillReplay( entity player, bool doRepla
entity attacker = file.roundWinningKillReplayAttacker
player.SetKillReplayDelay( Time() - replayLength, THIRD_PERSON_KILL_REPLAY_ALWAYS )
- player.SetKillReplayInflictorEHandle( attacker.GetEncodedEHandle() )
+ player.SetKillReplayInflictorEHandle( file.roundWinningKillReplayInflictorEHandle )
player.SetKillReplayVictim( file.roundWinningKillReplayVictim )
player.SetViewIndex( attacker.GetIndexForEntity() )
player.SetIsReplayRoundWinning( true )
@@ -503,6 +542,20 @@ void function GameStateEnter_SuddenDeath()
{
// disable respawns, suddendeath calling is done on a kill callback
SetRespawnsEnabled( false )
+
+ // defensive fixes, so game won't stuck in SuddenDeath forever
+ bool mltElimited = false
+ bool imcElimited = false
+ if( GetPlayerArrayOfTeam_Alive( TEAM_MILITIA ).len() < 1 )
+ mltElimited = true
+ if( GetPlayerArrayOfTeam_Alive( TEAM_IMC ).len() < 1 )
+ imcElimited = true
+ if( mltElimited && imcElimited )
+ SetWinner( TEAM_UNASSIGNED )
+ else if( mltElimited )
+ SetWinner( TEAM_IMC )
+ else if( imcElimited )
+ SetWinner( TEAM_MILITIA )
}
@@ -554,6 +607,9 @@ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
return
}
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ bool shouldUseInflictor = IsValid( inflictor ) && ShouldTryUseProjectileReplay( victim, attacker, damageInfo, true )
+
// set round winning killreplay info here if we're tracking pilot kills
// todo: make this not count environmental deaths like falls, unsure how to prevent this
if ( file.roundWinningKillReplayTrackPilotKills && victim != attacker && attacker != svGlobal.worldspawn && IsValid( attacker ) )
@@ -563,6 +619,7 @@ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo )
file.roundWinningKillReplayTime = Time()
file.roundWinningKillReplayVictim = victim
file.roundWinningKillReplayAttacker = attacker
+ file.roundWinningKillReplayInflictorEHandle = ( shouldUseInflictor ? inflictor : attacker ).GetEncodedEHandle()
file.roundWinningKillReplayMethodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
file.roundWinningKillReplayTimeOfDeath = Time()
file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker )
@@ -613,6 +670,9 @@ void function OnTitanKilled( entity victim, var damageInfo )
return
}
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ bool shouldUseInflictor = IsValid( inflictor ) && ShouldTryUseProjectileReplay( victim, DamageInfo_GetAttacker( damageInfo ), damageInfo, true )
+
// set round winning killreplay info here if we're tracking titan kills
// todo: make this not count environmental deaths like falls, unsure how to prevent this
entity attacker = DamageInfo_GetAttacker( damageInfo )
@@ -623,6 +683,7 @@ void function OnTitanKilled( entity victim, var damageInfo )
file.roundWinningKillReplayTime = Time()
file.roundWinningKillReplayVictim = victim
file.roundWinningKillReplayAttacker = attacker
+ file.roundWinningKillReplayInflictorEHandle = ( shouldUseInflictor ? inflictor : attacker ).GetEncodedEHandle()
file.roundWinningKillReplayMethodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo )
file.roundWinningKillReplayTimeOfDeath = Time()
file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker )
@@ -737,11 +798,12 @@ void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan )
file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this
}
-void function SetRoundWinningKillReplayAttacker( entity attacker )
+void function SetRoundWinningKillReplayAttacker( entity attacker, int inflictorEHandle = -1 )
{
file.roundWinningKillReplayTime = Time()
file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker )
file.roundWinningKillReplayAttacker = attacker
+ file.roundWinningKillReplayInflictorEHandle = inflictorEHandle == -1 ? attacker.GetEncodedEHandle() : inflictorEHandle
file.roundWinningKillReplayTimeOfDeath = Time()
}
@@ -773,9 +835,45 @@ void function SetWinner( int team, string winningReason = "", string losingReaso
}
SetGameState( eGameState.WinnerDetermined )
+ ScoreEvent_RoundComplete( team )
}
else
+ {
SetGameState( eGameState.WinnerDetermined )
+ ScoreEvent_MatchComplete( team )
+
+ array<entity> players = GetPlayerArray()
+ int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( GAMETYPE )
+ if ( compareFunc != null )
+ {
+ players.sort( compareFunc )
+ int playerCount = players.len()
+ int currentPlace = 1
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( i >= playerCount )
+ continue
+
+ if ( i > 0 && compareFunc( players[i - 1], players[i] ) != 0 )
+ currentPlace += 1
+
+ switch( currentPlace )
+ {
+ case 1:
+ UpdatePlayerStat( players[i], "game_stats", "mvp" )
+ UpdatePlayerStat( players[i], "game_stats", "mvp_total" )
+ UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" )
+ break
+ case 2:
+ UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" )
+ break
+ case 3:
+ UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" )
+ break
+ }
+ }
+ }
+ }
}
}
@@ -868,3 +966,108 @@ float function GetTimeLimit_ForGameMode()
// default to 10 mins, because that seems reasonable
return GetCurrentPlaylistVarFloat( playlistString, 10 )
}
+
+// faction dialogue
+
+void function DialoguePlayNormal()
+{
+ int totalScore = GameMode_GetScoreLimit( GameRules_GetGameMode() )
+ int winningTeam
+ int losingTeam
+ float diagIntervel = 71 // play a faction dailogue every 70 + 1s to prevent play together with winner dialogue
+
+ while( GetGameState() == eGameState.Playing )
+ {
+ wait diagIntervel
+ if( GameRules_GetTeamScore( TEAM_MILITIA ) < GameRules_GetTeamScore( TEAM_IMC ) )
+ {
+ winningTeam = TEAM_IMC
+ losingTeam = TEAM_MILITIA
+ }
+ if( GameRules_GetTeamScore( TEAM_MILITIA ) > GameRules_GetTeamScore( TEAM_IMC ) )
+ {
+ winningTeam = TEAM_MILITIA
+ losingTeam = TEAM_IMC
+ }
+ if( GameRules_GetTeamScore( winningTeam ) - GameRules_GetTeamScore( losingTeam ) >= totalScore * 0.4 )
+ {
+ PlayFactionDialogueToTeam( "scoring_winningLarge", winningTeam )
+ PlayFactionDialogueToTeam( "scoring_losingLarge", losingTeam )
+ }
+ else if( GameRules_GetTeamScore( winningTeam ) - GameRules_GetTeamScore( losingTeam ) <= totalScore * 0.2 )
+ {
+ PlayFactionDialogueToTeam( "scoring_winningClose", winningTeam )
+ PlayFactionDialogueToTeam( "scoring_losingClose", losingTeam )
+ }
+ else if( GameRules_GetTeamScore( winningTeam ) == GameRules_GetTeamScore( losingTeam ) )
+ {
+ continue
+ }
+ else
+ {
+ PlayFactionDialogueToTeam( "scoring_winning", winningTeam )
+ PlayFactionDialogueToTeam( "scoring_losing", losingTeam )
+ }
+ }
+}
+
+void function DialoguePlayWinnerDetermined()
+{
+ int totalScore = GameMode_GetScoreLimit( GameRules_GetGameMode() )
+ int winningTeam
+ int losingTeam
+
+ if( GameRules_GetTeamScore( TEAM_MILITIA ) < GameRules_GetTeamScore( TEAM_IMC ) )
+ {
+ winningTeam = TEAM_IMC
+ losingTeam = TEAM_MILITIA
+ }
+ if( GameRules_GetTeamScore( TEAM_MILITIA ) > GameRules_GetTeamScore( TEAM_IMC ) )
+ {
+ winningTeam = TEAM_MILITIA
+ losingTeam = TEAM_IMC
+ }
+ if( IsRoundBased() ) // check for round based modes
+ {
+ if( GameRules_GetTeamScore( winningTeam ) != GameMode_GetRoundScoreLimit( GAMETYPE ) ) // no winner dialogue till game really ends
+ return
+ }
+ if( GameRules_GetTeamScore( winningTeam ) - GameRules_GetTeamScore( losingTeam ) >= totalScore * 0.4 )
+ {
+ PlayFactionDialogueToTeam( "scoring_wonMercy", winningTeam )
+ PlayFactionDialogueToTeam( "scoring_lostMercy", losingTeam )
+ }
+ else if( GameRules_GetTeamScore( winningTeam ) - GameRules_GetTeamScore( losingTeam ) <= totalScore * 0.2 )
+ {
+ PlayFactionDialogueToTeam( "scoring_wonClose", winningTeam )
+ PlayFactionDialogueToTeam( "scoring_lostClose", losingTeam )
+ }
+ else if( GameRules_GetTeamScore( winningTeam ) == GameRules_GetTeamScore( losingTeam ) )
+ {
+ PlayFactionDialogueToTeam( "scoring_tied", winningTeam )
+ PlayFactionDialogueToTeam( "scoring_tied", losingTeam )
+ }
+ else
+ {
+ PlayFactionDialogueToTeam( "scoring_won", winningTeam )
+ PlayFactionDialogueToTeam( "scoring_lost", losingTeam )
+ }
+}
+
+/// This is to move all NPCs that a player owns from one team to the other during a match
+/// Auto-Titans, Turrets, Ticks and Hacked Spectres will all move along together with the player to the new Team
+/// Also possibly prevents mods that spawns other types of NPCs that players can own from breaking when switching (i.e Drones, Hacked Reapers)
+void function OnPlayerChangedTeam( entity player )
+{
+ if ( !player.hasConnected ) // Prevents players who just joined to trigger below code, as server always pre setups their teams
+ return
+
+ NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() )
+
+ foreach( npc in GetNPCArray() )
+ {
+ entity bossPlayer = npc.GetBossPlayer()
+ if ( IsValidPlayer( bossPlayer ) && bossPlayer == player && IsAlive( npc ) )
+ SetTeam( npc, player.GetTeam() )
+ }
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_model_viewer.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_model_viewer.nut
new file mode 100644
index 00000000..c33f4ef0
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_model_viewer.nut
@@ -0,0 +1,180 @@
+untyped
+
+
+global function ModelViewer_Init
+
+global function ToggleModelViewer
+
+global modelViewerModels = []
+
+#if DEV
+struct
+{
+ bool initialized
+ bool active
+ entity gameUIFreezeControls
+ array<string> playerWeapons
+ array<string> playerOffhands
+ bool dpadUpPressed = true
+ bool dpadDownPressed = true
+ var lastTitanAvailability
+} file
+#endif // DEV
+
+function ModelViewer_Init()
+{
+ #if DEV
+ if ( reloadingScripts )
+ return
+ AddClientCommandCallback( "ModelViewer", ClientCommand_ModelViewer )
+ #endif
+}
+
+function ToggleModelViewer()
+{
+ #if DEV
+ entity player = GetPlayerArray()[ 0 ]
+ if ( !file.active )
+ {
+ file.active = true
+
+ DisablePrecacheErrors()
+ wait 0.5
+
+ ModelViewerDisableConflicts()
+ Remote_CallFunction_NonReplay( player, "ServerCallback_ModelViewerDisableConflicts" )
+
+ ReloadShared()
+
+ if ( !file.initialized )
+ {
+ file.initialized = true
+ ControlsInit()
+ }
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_MVEnable" )
+
+ file.lastTitanAvailability = level.nv.titanAvailability
+ Riff_ForceTitanAvailability( eTitanAvailability.Never )
+
+ WeaponsRemove()
+ thread UpdateModelBounds()
+ }
+ else
+ {
+ file.active = false
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_MVDisable" )
+ RestorePrecacheErrors()
+
+ Riff_ForceTitanAvailability( file.lastTitanAvailability )
+
+ WeaponsRestore()
+ }
+ #endif
+}
+
+#if DEV
+function ModelViewerDisableConflicts()
+{
+ disable_npcs() //Just disable_npcs() for now, will probably add things later
+}
+
+function ReloadShared()
+{
+ modelViewerModels = GetModelViewerList()
+}
+
+function ControlsInit()
+{
+ file.gameUIFreezeControls = CreateEntity( "game_ui" )
+ file.gameUIFreezeControls.kv.spawnflags = 32
+ file.gameUIFreezeControls.kv.FieldOfView = -1.0
+
+ DispatchSpawn( file.gameUIFreezeControls )
+}
+
+bool function ClientCommand_ModelViewer( entity player, array<string> args )
+{
+ string command = args[ 0 ]
+ switch ( command )
+ {
+ case "freeze_player":
+ file.gameUIFreezeControls.Fire( "Activate", "!player", 0 )
+ break
+
+ case "unfreeze_player":
+ file.gameUIFreezeControls.Fire( "Deactivate", "!player", 0 )
+ break
+ }
+
+ return true
+}
+
+function UpdateModelBounds()
+{
+ wait( 0.3 )
+
+ foreach ( index, modelName in modelViewerModels )
+ {
+ entity model = CreatePropDynamic( expect asset( modelName ) )
+ local mins = model.GetBoundingMins()
+ local maxs = model.GetBoundingMaxs()
+
+ mins.x = min( -8.0, mins.x )
+ mins.y = min( -8.0, mins.y )
+ mins.z = min( -8.0, mins.z )
+
+ maxs.x = max( 8.0, maxs.x )
+ maxs.y = max( 8.0, maxs.y )
+ maxs.z = max( 8.0, maxs.z )
+
+ Remote_CallFunction_NonReplay( GetPlayerArray()[ 0 ], "ServerCallback_MVUpdateModelBounds", index, mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z )
+ model.Destroy()
+ }
+}
+
+function WeaponsRemove()
+{
+ entity player = GetPlayerArray()[0]
+ if ( !IsValid( player ) )
+ return
+
+ file.playerWeapons.clear()
+ file.playerOffhands.clear()
+
+ array<entity> weapons = player.GetMainWeapons()
+ foreach ( weaponEnt in weapons )
+ {
+ string weapon = weaponEnt.GetWeaponClassName()
+ file.playerWeapons.append( weapon )
+ player.TakeWeapon( weapon )
+ }
+
+ array<entity> offhands = player.GetOffhandWeapons()
+ foreach ( index, offhandEnt in offhands )
+ {
+ string offhand = offhandEnt.GetWeaponClassName()
+ file.playerOffhands.append( offhand )
+ player.TakeOffhandWeapon( index )
+ }
+}
+
+function WeaponsRestore()
+{
+ entity player = GetPlayerArray()[0]
+ if ( !IsValid( player ) )
+ return
+
+ foreach ( weapon in file.playerWeapons )
+ {
+ player.GiveWeapon( weapon )
+ }
+
+ foreach ( index, offhand in file.playerOffhands )
+ {
+ player.GiveOffhandWeapon( offhand, index )
+ }
+}
+
+#endif // DEV
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
index 2d1ff074..2a4c4282 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut
@@ -7,6 +7,8 @@ global function ScoreEvent_PlayerKilled
global function ScoreEvent_TitanDoomed
global function ScoreEvent_TitanKilled
global function ScoreEvent_NPCKilled
+global function ScoreEvent_MatchComplete
+global function ScoreEvent_RoundComplete
global function ScoreEvent_SetEarnMeterValues
global function ScoreEvent_SetupEarnMeterValuesForMixedModes
@@ -27,6 +29,10 @@ void function InitPlayerForScoreEvents( entity player )
player.s.currentKillstreak <- 0
player.s.lastKillTime <- 0.0
player.s.currentTimedKillstreak <- 0
+ player.s.lastKillTime_Mayhem <- 0.0
+ player.s.currentTimedKillstreak_Mayhem <- 0
+ player.s.lastKillTime_Onslaught <- 0.0
+ player.s.currentTimedKillstreak_Onslaught <- 0
}
void function AddPlayerScore( entity targetPlayer, string scoreEventName, entity associatedEnt = null, string noideawhatthisis = "", int pointValueOverride = -1 )
@@ -92,6 +98,7 @@ void function ScoreEvent_PlayerKilled( entity victim, entity attacker, var damag
victim.s.currentTimedKillstreak = 0
victim.p.numberOfDeathsSinceLastKill++ // this is reset on kill
+ victim.p.lastKiller = attacker
// have to do this early before we reset victim's player killstreaks
// nemesis when you kill a player that is dominating you
@@ -130,12 +137,20 @@ void function ScoreEvent_PlayerKilled( entity victim, entity attacker, var damag
attacker.p.numberOfDeathsSinceLastKill = 0
}
+ // revenge + quick revenge
+ if ( attacker.p.lastKiller == victim )
+ {
+ if ( Time() - GetPlayerLastRespawnTime( attacker ) < QUICK_REVENGE_TIME_LIMIT )
+ AddPlayerScore( attacker, "QuickRevenge" )
+ else
+ AddPlayerScore( attacker, "Revenge" )
+ }
// untimed killstreaks
attacker.s.currentKillstreak++
- if ( attacker.s.currentKillstreak == 3 )
+ if ( attacker.s.currentKillstreak == KILLINGSPREE_KILL_REQUIREMENT )
AddPlayerScore( attacker, "KillingSpree" )
- else if ( attacker.s.currentKillstreak == 5 )
+ else if ( attacker.s.currentKillstreak == RAMPAGE_KILL_REQUIREMENT )
AddPlayerScore( attacker, "Rampage" )
// increment untimed killstreaks against specific players
@@ -187,24 +202,42 @@ void function ScoreEvent_TitanKilled( entity victim, entity attacker, var damage
return
if ( attacker.IsTitan() )
- AddPlayerScore( attacker, "TitanKillTitan", victim.GetTitanSoul().GetOwner() )
+ {
+ if( victim.GetBossPlayer() || victim.IsPlayer() ) // to confirm this is a pet titan or player titan
+ AddPlayerScore( attacker, "TitanKillTitan", attacker ) // this will show the "Titan Kill" callsign event
+ else
+ AddPlayerScore( attacker, "TitanKillTitan" )
+ }
else
- AddPlayerScore( attacker, "KillTitan", victim.GetTitanSoul().GetOwner() )
+ {
+ if( victim.GetBossPlayer() || victim.IsPlayer() )
+ AddPlayerScore( attacker, "KillTitan", attacker )
+ else
+ AddPlayerScore( attacker, "KillTitan" )
+ }
- table<int, bool> alreadyAssisted
- foreach( DamageHistoryStruct attackerInfo in victim.e.recentDamageHistory )
+ entity soul = victim.GetTitanSoul()
+ if ( IsValid( soul ) )
{
- if ( !IsValid( attackerInfo.attacker ) || !attackerInfo.attacker.IsPlayer() || attackerInfo.attacker == victim )
- continue
-
- bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false
- if( attackerInfo.attacker != attacker && !exists )
+ table<int, bool> alreadyAssisted
+
+ foreach( DamageHistoryStruct attackerInfo in soul.e.recentDamageHistory )
{
- alreadyAssisted[attackerInfo.attacker.GetEncodedEHandle()] <- true
- AddPlayerScore(attackerInfo.attacker, "TitanAssist" )
- Remote_CallFunction_NonReplay( attackerInfo.attacker, "ServerCallback_SetAssistInformation", attackerInfo.damageSourceId, attacker.GetEncodedEHandle(), victim.GetEncodedEHandle(), attackerInfo.time )
+ if ( !IsValid( attackerInfo.attacker ) || !attackerInfo.attacker.IsPlayer() || attackerInfo.attacker == soul )
+ continue
+
+ bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false
+ if( attackerInfo.attacker != attacker && !exists )
+ {
+ alreadyAssisted[attackerInfo.attacker.GetEncodedEHandle()] <- true
+ AddPlayerScore(attackerInfo.attacker, "TitanAssist" )
+ Remote_CallFunction_NonReplay( attackerInfo.attacker, "ServerCallback_SetAssistInformation", attackerInfo.damageSourceId, attacker.GetEncodedEHandle(), soul.GetEncodedEHandle(), attackerInfo.time )
+ }
}
}
+
+ if( !victim.IsNPC() ) // don't let killing a npc titan plays dialogue
+ KilledPlayerTitanDialogue( attacker, victim )
}
void function ScoreEvent_NPCKilled( entity victim, entity attacker, var damageInfo )
@@ -215,9 +248,66 @@ void function ScoreEvent_NPCKilled( entity victim, entity attacker, var damageIn
AddPlayerScore( attacker, ScoreEventForNPCKilled( victim, damageInfo ), victim )
}
catch ( ex ) {}
+
+ if ( !attacker.IsPlayer() )
+ return
+
+ // mayhem/onslaught (timed killstreaks vs AI)
+
+ // reset before checking
+ if ( Time() - attacker.s.lastKillTime_Mayhem > MAYHEM_REQUIREMENT_TIME )
+ {
+ attacker.s.currentTimedKillstreak_Mayhem = 0
+ attacker.s.lastKillTime_Mayhem = Time()
+ }
+ if ( Time() - attacker.s.lastKillTime_Mayhem <= MAYHEM_REQUIREMENT_TIME )
+ {
+ attacker.s.currentTimedKillstreak_Mayhem++
+
+ if ( attacker.s.currentTimedKillstreak_Mayhem == MAYHEM_REQUIREMENT_KILLS )
+ AddPlayerScore( attacker, "Mayhem" )
+ }
+
+ // reset before checking
+ if ( Time() - attacker.s.lastKillTime_Onslaught > ONSLAUGHT_REQUIREMENT_TIME )
+ {
+ attacker.s.currentTimedKillstreak_Onslaught = 0
+ attacker.s.lastKillTime_Onslaught = Time()
+ }
+ if ( Time() - attacker.s.lastKillTime_Onslaught <= ONSLAUGHT_REQUIREMENT_TIME )
+ {
+ attacker.s.currentTimedKillstreak_Onslaught++
+
+ if ( attacker.s.currentTimedKillstreak_Onslaught == ONSLAUGHT_REQUIREMENT_KILLS )
+ AddPlayerScore( attacker, "Onslaught" )
+ }
}
+void function ScoreEvent_MatchComplete( int winningTeam )
+{
+ foreach( entity player in GetPlayerArray() )
+ {
+ AddPlayerScore( player, "MatchComplete" )
+ SetPlayerChallengeMatchComplete( player )
+ if ( player.GetTeam() == winningTeam )
+ {
+ AddPlayerScore( player, "MatchVictory" )
+ SetPlayerChallengeMatchWon( player, true )
+ }
+ else
+ SetPlayerChallengeMatchWon( player, false )
+ }
+}
+void function ScoreEvent_RoundComplete( int winningTeam )
+{
+ foreach( entity player in GetPlayerArray() )
+ {
+ AddPlayerScore( player, "RoundComplete" )
+ if ( player.GetTeam() == winningTeam )
+ AddPlayerScore( player, "RoundVictory" )
+ }
+}
void function ScoreEvent_SetEarnMeterValues( string eventName, float earned, float owned, float coreScale = 1.0 )
{
@@ -231,7 +321,7 @@ void function ScoreEvent_SetupEarnMeterValuesForMixedModes() // mixed modes in t
{
// todo needs earn/overdrive values
// player-controlled stuff
- ScoreEvent_SetEarnMeterValues( "KillPilot", 0.07, 0.15 )
+ ScoreEvent_SetEarnMeterValues( "KillPilot", 0.07, 0.15, 0.33 ) // 5% for titan cores
ScoreEvent_SetEarnMeterValues( "KillTitan", 0.0, 0.15 )
ScoreEvent_SetEarnMeterValues( "TitanKillTitan", 0.0, 0.0 ) // unsure
ScoreEvent_SetEarnMeterValues( "PilotBatteryStolen", 0.0, 0.35 ) // this actually just doesn't have overdrive in vanilla even
@@ -251,3 +341,42 @@ void function ScoreEvent_SetupEarnMeterValuesForTitanModes()
{
// relatively sure we don't have to do anything here but leaving this function for consistency
}
+
+// faction dialogue
+void function KilledPlayerTitanDialogue( entity attacker, entity victim )
+{
+ if( !attacker.IsPlayer() )
+ return
+ entity titan
+ if ( victim.IsTitan() )
+ titan = victim
+
+ if( !IsValid( titan ) )
+ return
+ string titanCharacterName = GetTitanCharacterName( titan )
+
+ switch( titanCharacterName )
+ {
+ case "ion":
+ PlayFactionDialogueToPlayer( "kc_pilotkillIon", attacker )
+ return
+ case "tone":
+ PlayFactionDialogueToPlayer( "kc_pilotkillTone", attacker )
+ return
+ case "legion":
+ PlayFactionDialogueToPlayer( "kc_pilotkillLegion", attacker )
+ return
+ case "scorch":
+ PlayFactionDialogueToPlayer( "kc_pilotkillScorch", attacker )
+ return
+ case "ronin":
+ PlayFactionDialogueToPlayer( "kc_pilotkillRonin", attacker )
+ return
+ case "northstar":
+ PlayFactionDialogueToPlayer( "kc_pilotkillNorthstar", attacker )
+ return
+ default:
+ PlayFactionDialogueToPlayer( "kc_pilotkilltitan", attacker )
+ return
+ }
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut
index aa2fc108..510a9b7e 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut
@@ -170,6 +170,9 @@ void function SpectatorFunc_Default( entity player )
{
player.SetObserverTarget( target )
player.StartObserverMode( OBS_MODE_CHASE )
+ // the delay of 0.1 seems to fix the spec_mode command not working
+ // when using the keybind
+ player.SetSpecReplayDelay( 0.1 )
}
catch ( ex ) { }
}
@@ -215,9 +218,12 @@ bool function ClientCommandCallback_spec_mode( entity player, array<string> args
else if ( player.GetObserverMode() == OBS_MODE_IN_EYE )
{
// set to third person spectate
- player.SetSpecReplayDelay( 0.0 )
+
+ // the delay of 0.1 seems to fix the spec_mode command not working
+ // when using the keybind
+ player.SetSpecReplayDelay( 0.1 )
player.StartObserverMode( OBS_MODE_CHASE )
}
return true
-} \ No newline at end of file
+}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
index 0e8b58f4..74a9088b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut
@@ -1,3 +1,5 @@
+untyped // because entity.s
+
global function Stats_Init
global function AddStatCallback
global function Stats_SaveStatDelayed
@@ -12,67 +14,1105 @@ global function PreScoreEventUpdateStats
global function PostScoreEventUpdateStats
global function Stats_OnPlayerDidDamage
+struct {
+ table< string, array<string> > refs
+ table< string, array< void functionref( entity, float, string ) > > callbacks
+
+ table< entity, table< string, int > > cachedIntStatChanges
+ table< table< string, float > > cachedFloatStatChanges
+
+ table< entity, float > playerKills
+ table< entity, float > playerKillsPvp
+ table< entity, float > playerDeaths
+ table< entity, float > playerDeathsPvp
+
+ bool isFirstStrike = true
+} file
+
void function Stats_Init()
{
+ AddCallback_OnPlayerKilled( OnPlayerOrNPCKilled )
+ AddCallback_OnNPCKilled( OnPlayerOrNPCKilled )
+ AddCallback_OnPlayerRespawned( OnPlayerRespawned )
+ AddCallback_OnClientConnected( OnClientConnected )
+ AddCallback_OnClientDisconnected( OnClientDisconnected )
+ AddCallback_GameStateEnter( eGameState.WinnerDetermined, OnWinnerDetermined )
+ thread HandleDistanceAndTimeStats_Threaded()
+ thread SaveStatsPeriodically_Threaded()
}
-void function AddStatCallback(string statCategory, string statAlias, string statSubAlias, void functionref(entity, float, string) callback, string subRef)
+void function AddStatCallback( string statCategory, string statAlias, string statSubAlias, void functionref( entity, float, string ) callback, string subRef )
{
+ if ( !IsValidStat( statCategory, statAlias, statSubAlias ) )
+ throw format( "INVALID STAT: %s : %s : %s", statCategory, statAlias, statSubAlias )
+
+
+ string statVar = GetStatVar( statCategory, statAlias, statSubAlias )
+ if ( statVar in file.refs )
+ {
+ file.refs[ statVar ].append( subRef )
+ file.callbacks[ statVar ].append( callback )
+ }
+ else
+ {
+ file.refs[ statVar ] <- [ subRef ]
+ file.callbacks[ statVar ] <- [ callback ]
+ }
}
-void function Stats_SaveStatDelayed(entity player, string statCategory, string statAlias, string statSubAlias)
+// a lot of this file seems to be doing caching of stats in some way
+void function Stats_SaveStatDelayed( entity player, string statCategory, string statAlias, string statSubAlias, float delay = 0.1 )
{
+ // idk how long the delay is meant to be but whatever
+ wait delay
+
+ if ( !IsValid( player ) )
+ return
+
+ Stats_SaveStat( player, statCategory, statAlias, statSubAlias )
+}
+
+void function Stats_SaveAllStats( entity player )
+{
+ if ( player in file.cachedIntStatChanges )
+ {
+ foreach( string key, int val in file.cachedIntStatChanges[ player ] )
+ {
+ player.SetPersistentVar( key, player.GetPersistentVarAsInt( key ) + val )
+ }
+
+ delete file.cachedIntStatChanges[ player ]
+ }
+ // save cached float stat change
+ if ( player in file.cachedFloatStatChanges )
+ {
+ foreach( string key, float val in file.cachedFloatStatChanges[ player ] )
+ {
+ player.SetPersistentVar( key, expect float( player.GetPersistentVar( key ) ) + val )
+ }
+ delete file.cachedFloatStatChanges[ player ]
+ }
}
-int function PlayerStat_GetCurrentInt(entity player, string statCategory, string statAlias, string statSubAlias)
+void function Stats_SaveStat( entity player, string statCategory, string statAlias, string statSubAlias )
{
+ string stat = GetStatVar( statCategory, statAlias, statSubAlias )
+ // save cached int stat change
+ if ( player in file.cachedIntStatChanges && stat in file.cachedIntStatChanges[ player ] )
+ {
+ player.SetPersistentVar( stat, player.GetPersistentVarAsInt( stat ) + file.cachedIntStatChanges[ player ][ stat ] )
+ delete file.cachedIntStatChanges[ player ][ stat ]
+ return
+ }
+ // save cached float stat change
+ if ( player in file.cachedFloatStatChanges && stat in file.cachedFloatStatChanges[ player ] )
+ {
+ player.SetPersistentVar( stat, expect float( player.GetPersistentVar( stat ) ) + file.cachedFloatStatChanges[ player ][ stat ] )
+ delete file.cachedFloatStatChanges[ player ][ stat ]
+ return
+ }
+}
+
+// this gets the cached change, not the actual value
+int function PlayerStat_GetCurrentInt( entity player, string statCategory, string statAlias, string statSubAlias )
+{
+ string str = GetStatVar( statCategory, statAlias, statSubAlias )
+
+ if ( player in file.cachedIntStatChanges && str in file.cachedIntStatChanges[ player ] )
+ return file.cachedIntStatChanges[ player ][ str ]
return 0
}
-float function PlayerStat_GetCurrentFloat(entity player, string statCategory, string statAlias, string statSubAlias)
+// this gets the cached change, not the actual value
+float function PlayerStat_GetCurrentFloat( entity player, string statCategory, string statAlias, string statSubAlias )
{
+ string str = GetStatVar( statCategory, statAlias, statSubAlias )
+
+ if ( player in file.cachedFloatStatChanges && str in file.cachedFloatStatChanges[ player ] )
+ return file.cachedFloatStatChanges[ player ][ str ]
return 0
}
-void function UpdatePlayerStat(entity player, string statCategory, string subStat, int count = 0)
+void function UpdatePlayerStat( entity player, string statCategory, string subStat, int count = 1, string statAlias = "" )
{
+ if ( !IsValid( player ) )
+ return
+
+ Stats_IncrementStat( player, statCategory, subStat, statAlias, count.tofloat() )
+}
+
+void function IncrementPlayerDidPilotExecutionWhileCloaked( entity player )
+{
+ UpdatePlayerStat( player, "kills_stats", "pilotExecutePilotWhileCloaked" )
+}
+
+void function UpdateTitanDamageStat( entity attacker, float savedDamage, var damageInfo )
+{
+ if ( !IsValid( attacker ) )
+ return
+
+ Stats_IncrementStat( attacker, "titan_stats", "titanDamage", GetTitanCharacterName( attacker ), savedDamage )
+}
+
+void function UpdateTitanWeaponDamageStat( entity attacker, float savedDamage, var damageInfo )
+{
+ if ( !IsValid( attacker ) )
+ return
+
+ string weaponName = GetPersistenceRefFromDamageInfo( damageInfo )
+ if ( weaponName == "" )
+ return
+
+ Stats_IncrementStat( attacker, "weapon_stats", "titanDamage", weaponName, savedDamage )
+}
+
+void function UpdateTitanCoreEarnedStat( entity player, entity titan, int count = 1 )
+{
+ if ( !IsValid( player ) )
+ return
+
+ if ( !IsValid( titan ) )
+ return
+
+ Stats_IncrementStat( player, "titan_stats", "coresEarned", GetTitanCharacterName( titan ), count.tofloat() )
+}
+
+void function PreScoreEventUpdateStats( entity attacker, entity ent )
+{
+ // used to track kill streaks ending i think ( that stuff gets reset during score event update )
+}
+
+void function PostScoreEventUpdateStats( entity attacker, entity ent )
+{
+ if ( !attacker.IsPlayer() )
+ return
+ // used to track kill streaks starting maybe
+ if ( attacker.s.currentKillstreak == KILLINGSPREE_KILL_REQUIREMENT )
+ {
+ // killingSpressAs_<chassis>
+ if ( attacker.IsTitan() )
+ Stats_IncrementStat( attacker, "kills_stats", "killingSpressAs_" + GetTitanCharacterName( attacker ), "", 1.0 )
+
+ entity weapon = attacker.GetActiveWeapon()
+ // I guess if you dont have a valid active weapon when you get awarded a killing spree
+ // you dont get the stat. Too bad!
+ if ( !IsValid( weapon ) )
+ return
+ Stats_IncrementStat( attacker, "weapon_kill_stats", "killingSprees", weapon.GetWeaponClassName(), 1.0 )
+ }
+}
+
+void function Stats_OnPlayerDidDamage( entity victim, var damageInfo )
+{
+ // try and get the player
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ // get the player from their titan
+ if ( attacker.IsTitan() && IsPetTitan( attacker ) )
+ attacker = attacker.GetTitanSoul().GetBossPlayer()
+
+ if ( !attacker.IsPlayer() )
+ return
+
+ string weaponName = GetPersistenceRefFromDamageInfo( damageInfo )
+ if ( weaponName == "" )
+ return
+
+ Stats_IncrementStat( attacker, "weapon_stats", "shotsHit", weaponName, 1.0 )
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_CRITICAL )
+ Stats_IncrementStat( attacker, "weapon_stats", "critHits", weaponName, 1.0 )
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_HEADSHOT )
+ Stats_IncrementStat( attacker, "weapon_stats", "headshots", weaponName, 1.0 )
+}
+
+void function Stats_IncrementStat( entity player, string statCategory, string statAlias, string statSubAlias, float amount )
+{
+ if ( !IsValidStat( statCategory, statAlias, statSubAlias ) )
+ {
+ printt( "invalid stat: " + statCategory + " : " + statAlias + " : " + statSubAlias )
+ return
+ }
+
+ string str = GetStatVar( statCategory, statAlias, statSubAlias )
+ int type = GetStatVarType( statCategory, statAlias, statSubAlias )
+
+ // stupid exception because respawn set this up as an int in script
+ // but it is actually a float, so the game will crash if we don't fix it somewhere
+ // i dont feel like committing all of sh_stats.gnut so im doing this instead
+ if ( str == "mapStats[%mapname%].hoursPlayed[%gamemode%]" )
+ type = ePlayerStatType.FLOAT
+
+ // this is rather hacky
+ string mode = GAMETYPE
+ int difficulty = GetDifficultyLevel()
+ if ( difficulty >= 5 )
+ return
+
+ string saveVar = Stats_GetFixedSaveVar( str, GetMapName(), mode, difficulty.tostring() )
+ // check if the map and mode exist in persistence
+ try
+ {
+ PersistenceGetEnumIndexForItemName( "gamemodes", mode )
+ PersistenceGetEnumIndexForItemName( "maps", GetMapName() )
+ }
+ catch( ex )
+ {
+ // if we have an invalid mode or map for persistence, and it is used in the
+ // persistence string, we can't save the persistence so we have to just return
+ if ( str != saveVar )
+ {
+ //printt( ex, str, GetMapName(), mode ) // Commented out due to spamming logs on invalid modes (e.g. Gun Game, Infection, ...)
+ return
+ }
+ }
+ str = saveVar
+
+ switch ( type )
+ {
+ case ePlayerStatType.INT:
+ // populate table if needed
+ if ( !( player in file.cachedIntStatChanges ) )
+ file.cachedIntStatChanges[ player ] <- {}
+ if ( !( str in file.cachedIntStatChanges[ player ] ) )
+ file.cachedIntStatChanges[ player ][ str ] <- 0
+
+ file.cachedIntStatChanges[ player ][ str ] += amount.tointeger()
+ break
+ case ePlayerStatType.FLOAT:
+ // populate table if needed
+ if ( !( player in file.cachedFloatStatChanges ) )
+ file.cachedFloatStatChanges[ player ] <- {}
+ if ( !( str in file.cachedFloatStatChanges[ player ] ) )
+ file.cachedFloatStatChanges[ player ][ str ] <- 0.0
+
+ file.cachedFloatStatChanges[ player ][ str ] += amount
+ break
+ default:
+ throw "UNIMPLEMENTED STAT TYPE: " + type
+ }
+
+ // amount here is never used
+ Stats_RunCallbacks( str, player, amount )
+}
+
+void function Stats_RunCallbacks( string statVar, entity player, float change )
+{
+ if ( !( statVar in file.refs ) )
+ return
+
+ for( int i = 0; i < file.refs[ statVar ].len(); i++ )
+ {
+ string ref = file.refs[ statVar ][ i ]
+ void functionref( entity, float, string ) callback = file.callbacks[ statVar ][ i ]
+
+ callback( player, change, ref )
+ }
+}
+
+void function OnClientConnected( entity player )
+{
+ Stats_IncrementStat( player, "game_stats", "game_joined", "", 1.0 )
+}
+
+void function OnClientDisconnected( entity player )
+{
+ Stats_SaveAllStats( player )
+ // maybe we can save this stuff, but idk if we can access persistence in this callback
+ if ( player in file.cachedIntStatChanges )
+ delete file.cachedIntStatChanges[ player ]
+
+ if ( player in file.cachedFloatStatChanges )
+ delete file.cachedFloatStatChanges[ player ]
+}
+
+void function OnPlayerOrNPCKilled( entity victim, entity attacker, var damageInfo )
+{
+ if ( victim.IsPlayer() )
+ thread SetLastPosForDistanceStatValid_Threaded( victim, false )
+
+ HandleDeathStats( victim, attacker, damageInfo )
+
+ if( victim == attacker ) //Suicides are registering stats, afaik vanilla ignores them
+ return
+
+ HandleKillStats( victim, attacker, damageInfo )
+ HandleWeaponKillStats( victim, attacker, damageInfo )
+ HandleTitanStats( victim, attacker, damageInfo )
+}
+
+void function HandleDeathStats( entity player, entity attacker, var damageInfo )
+{
+ if ( !IsValid( player ) || !player.IsPlayer() )
+ return
+
+ if ( player in file.playerDeaths )
+ file.playerDeaths[ player ]++
+ else
+ file.playerDeaths[ player ] <- 1.0
+ // total
+ Stats_IncrementStat( player, "deaths_stats", "total", "", 1.0 )
+
+ // these all rely on the attacker being valid
+ if ( IsValid( attacker ) )
+ {
+ // totalPVP
+ // note: I'm not sure if owned entities count towards totalPVP
+ // such as auto-titans, turrets, etc.
+ if ( attacker.IsPlayer() || attacker.GetBossPlayer() )
+ {
+ if ( player in file.playerDeathsPvp )
+ file.playerDeathsPvp[ player ]++
+ else
+ file.playerDeathsPvp[ player ] <- 1.0
+ Stats_IncrementStat( player, "deaths_stats", "totalPVP", "", 1.0 )
+ }
+
+ // byPilots
+ if ( IsPilot( attacker ) )
+ Stats_IncrementStat( player, "deaths_stats", "byPilots", "", 1.0 )
+
+ // byTitan_<chassis>
+ if ( attacker.IsTitan() && attacker.IsPlayer() )
+ Stats_IncrementStat( player, "deaths_stats", "byTitan_" + GetTitanCharacterName( attacker ), "", 1.0 )
+
+ // bySpectres
+ if ( IsSpectre( attacker ) )
+ Stats_IncrementStat( player, "deaths_stats", "bySpectres", "", 1.0 )
+
+ // byGrunts
+ if ( IsGrunt( attacker ) )
+ Stats_IncrementStat( player, "deaths_stats", "byGrunts", "", 1.0 )
+
+ // byNPCTitans_<chassis>
+ if ( attacker.IsTitan() && attacker.IsNPC() )
+ Stats_IncrementStat( player, "deaths_stats", "byNPCTitans_" + GetTitanCharacterName( attacker ), "", 1.0 )
+ }
+
+ // asPilot
+ if ( IsPilot( player ) )
+ Stats_IncrementStat( player, "deaths_stats", "asPilot", "", 1.0 )
+
+ // asTitan_<chassis>
+ if ( player.IsTitan() )
+ Stats_IncrementStat( player, "deaths_stats", "asTitan_" + GetTitanCharacterName( player ), "", 1.0 )
+
+ // suicides
+ if ( IsSuicide( attacker, player, DamageInfo_GetDamageSourceIdentifier( damageInfo ) ) )
+ Stats_IncrementStat( player, "deaths_stats", "suicides", "", 1.0 )
+
+ // whileEjecting
+ if ( player.p.pilotEjecting )
+ Stats_IncrementStat( player, "deaths_stats", "whileEjecting", "", 1.0 )
+}
+
+void function HandleWeaponKillStats( entity victim, entity attacker, var damageInfo )
+{
+ if ( !IsValid( attacker ) )
+ return
+
+ // get the player and it's pet titan
+ entity player
+ entity playerPetTitan
+ if ( attacker.IsPlayer() )
+ {
+ // the player is just the attacker
+ player = attacker
+ playerPetTitan = player.GetPetTitan()
+ }
+ else if ( attacker.IsTitan() && IsPetTitan( attacker ) )
+ {
+ // the attacker is the player's auto titan
+ player = attacker.GetTitanSoul().GetBossPlayer()
+ playerPetTitan = attacker
+ }
+ else
+ {
+ // attacker could be something like an NPC, or worldspawn
+ return
+ }
+
+ string damageSourceStr = GetPersistenceRefFromDamageInfo( damageInfo )
+ // cant do weapon stats for no weapon
+ if ( damageSourceStr == "" )
+ return
+
+ // check things once, for performance
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ bool victimIsPlayer = victim.IsPlayer()
+ bool victimIsNPC = victim.IsNPC()
+ bool victimIsPilot = IsPilot( victim )
+ bool victimIsTitan = victim.IsTitan()
+
+ // total
+ Stats_IncrementStat( player, "weapon_kill_stats", "total", damageSourceStr, 1.0 )
+
+ // pilots
+ if ( victimIsPilot )
+ Stats_IncrementStat( player, "weapon_kill_stats", "pilots", damageSourceStr, 1.0 )
+
+ // ejecting_pilots
+ if ( victimIsPilot && victim.p.pilotEjecting )
+ Stats_IncrementStat( player, "weapon_kill_stats", "ejecting_pilots", damageSourceStr, 1.0 )
+
+ // titansTotal
+ if ( victimIsTitan )
+ Stats_IncrementStat( player, "weapon_kill_stats", "titansTotal", damageSourceStr, 1.0 )
+ // spectres
+ if ( IsSpectre( victim ) )
+ Stats_IncrementStat( player, "weapon_kill_stats", "spectres", damageSourceStr, 1.0 )
+
+ // marvins
+ if ( IsMarvin( victim ) )
+ Stats_IncrementStat( player, "weapon_kill_stats", "marvins", damageSourceStr, 1.0 )
+
+ // grunts
+ if ( IsGrunt( victim ) )
+ Stats_IncrementStat( player, "weapon_kill_stats", "grunts", damageSourceStr, 1.0 )
+
+ // ai
+ if ( victimIsNPC )
+ Stats_IncrementStat( player, "weapon_kill_stats", "ai", damageSourceStr, 1.0 )
+
+ // titans_<chassis>
+ if ( victimIsPlayer && victimIsTitan )
+ Stats_IncrementStat( player, "weapon_kill_stats", "titans_" + GetTitanCharacterName( victim ), damageSourceStr, 1.0 )
+
+ // npcTitans_<chassis>
+ if ( victimIsNPC && victimIsTitan )
+ Stats_IncrementStat( player, "weapon_kill_stats", "npcTitans_" + GetTitanCharacterName( victim ), damageSourceStr, 1.0 )
}
-void function IncrementPlayerDidPilotExecutionWhileCloaked(entity player)
+void function HandleKillStats( entity victim, entity attacker, var damageInfo )
{
+ if ( !IsValid( attacker ) )
+ return
+ // get the player and it's pet titan
+ entity player
+ entity playerPetTitan
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+
+ if ( IsValid( inflictor ) )
+ {
+ if ( inflictor.IsProjectile() && IsValid( inflictor.GetOwner() ) ) // Attackers are always the final entity in the owning hierarchy, projectile owners though migh be a player's NPC minion (i.e Auto-Titans)
+ attacker = inflictor.GetOwner()
+
+ else if ( inflictor.IsNPC() ) // NPCs are bypassed as Attackers if they are owned by players, instead they become just inflictors
+ attacker = inflictor
+ }
+
+ if ( attacker.IsNPC() )
+ {
+ if ( !attacker.IsTitan() ) // Normal NPCs case
+ return
+
+ if ( !IsPetTitan( attacker ) ) // NPC Titans case
+ return
+
+ player = attacker.GetTitanSoul().GetBossPlayer()
+ playerPetTitan = attacker
+ }
+ else if ( attacker.IsPlayer() ) // Still checks this because worldspawn might be the attacker
+ player = attacker
+ else
+ return
+
+ // check things once, for performance
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ bool victimIsPlayer = victim.IsPlayer()
+ bool victimIsNPC = victim.IsNPC()
+ bool victimIsPilot = IsPilot( victim )
+ bool victimIsTitan = victim.IsTitan()
+
+ if ( player in file.playerKills )
+ file.playerKills[ player ]++
+ else
+ file.playerKills[ player ] <- 1.0
+ // total
+ Stats_IncrementStat( player, "kills_stats", "total", "", 1.0 )
+
+ // totalPVP
+ if ( victimIsPlayer )
+ {
+ if ( player in file.playerKillsPvp )
+ file.playerKillsPvp[ player ]++
+ else
+ file.playerKillsPvp[ player ] <- 1.0
+ Stats_IncrementStat( player, "kills_stats", "totalPVP", "", 1.0 )
+ }
+
+ // pilots
+ if ( victimIsPilot )
+ Stats_IncrementStat( player, "kills_stats", "pilots", "", 1.0 )
+
+ // spectres
+ if ( IsSpectre( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "spectres", "", 1.0 )
+
+ // marvins
+ if ( IsMarvin( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "marvins", "", 1.0 )
+
+ // grunts
+ if ( IsGrunt( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "grunts", "", 1.0 )
+
+ // totalTitans
+ if ( victimIsTitan )
+ Stats_IncrementStat( player, "kills_stats", "totalTitans", "", 1.0 )
+
+ // totalPilots
+ if ( victimIsPilot )
+ Stats_IncrementStat( player, "kills_stats", "totalPilots", "", 1.0 )
+
+ // totalNPC
+ if ( victimIsNPC )
+ Stats_IncrementStat( player, "kills_stats", "totalNPC", "", 1.0 )
+
+ // totalTitansWhileDoomed
+ if ( victimIsTitan && attacker.IsTitan() && GetDoomedState( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "totalTitansWhileDoomed", "", 1.0 )
+
+ // asPilot
+ if ( IsPilot( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "asPilot", "", 1.0 )
+
+ // totalAssists
+ // assistsTotal ( weapon_kill_stats )
+ // note: eww
+ table<int, bool> alreadyAssisted
+ // titans store their recentDamageHistory in the soul
+ entity assistVictim = ( victim.IsTitan() && IsValid( victim.GetTitanSoul() ) ) ? victim.GetTitanSoul() : victim
+ foreach( DamageHistoryStruct attackerInfo in assistVictim.e.recentDamageHistory )
+ {
+ if ( !IsValid( attackerInfo.attacker ) || !attackerInfo.attacker.IsPlayer() || attackerInfo.attacker == assistVictim )
+ continue
+
+ bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false
+ if( attackerInfo.attacker != attacker && !exists )
+ {
+ alreadyAssisted[ attackerInfo.attacker.GetEncodedEHandle() ] <- true
+ Stats_IncrementStat( attackerInfo.attacker, "kills_stats", "totalAssists", "", 1.0 )
+
+ string source = DamageSourceIDToString( attackerInfo.damageSourceId )
+
+ if ( IsValidStatItemString( source ) )
+ Stats_IncrementStat( attackerInfo.attacker, "weapon_kill_stats", "assistsTotal", source, 1.0 )
+ }
+ }
+
+ // asTitan_<chassis>
+ if ( player.IsTitan() )
+ Stats_IncrementStat( player, "kills_stats", "asTitan_" + GetTitanCharacterName( player ), "", 1.0 )
+
+ // firstStrikes
+ if ( file.isFirstStrike && attacker.IsPlayer() && victimIsPlayer )
+ {
+ Stats_IncrementStat( player, "kills_stats", "firstStrikes", "", 1.0 )
+ file.isFirstStrike = false
+ }
+
+ // ejectingPilots
+ if ( victimIsPilot && victim.p.pilotEjecting )
+ Stats_IncrementStat( player, "kills_stats", "ejectingPilots", "", 1.0 )
+
+ // whileEjecting
+ if ( attacker.IsPlayer() && attacker.p.pilotEjecting )
+ Stats_IncrementStat( player, "kills_stats", "whileEjecting", "", 1.0 )
+
+ // cloakedPilots
+ if ( victimIsPilot && IsCloaked( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "cloakedPilots", "", 1.0 )
+
+ // whileCloaked
+ if ( attacker == player && IsCloaked( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "whileCloaked", "", 1.0 )
+
+ // wallrunningPilots
+ if ( victimIsPilot && victim.IsWallRunning() )
+ Stats_IncrementStat( player, "kills_stats", "wallrunningPilots", "", 1.0 )
+
+ // whileWallrunning
+ if ( attacker == player && attacker.IsWallRunning() )
+ Stats_IncrementStat( player, "kills_stats", "whileWallrunning", "", 1.0 )
+
+ // wallhangingPilots
+ if ( victimIsPilot && victim.IsWallHanging() )
+ Stats_IncrementStat( player, "kills_stats", "wallhangingPilots", "", 1.0 )
+
+ // whileWallhanging
+ if ( attacker == player && attacker.IsWallHanging() )
+ Stats_IncrementStat( player, "kills_stats", "whileWallhanging", "", 1.0 )
+
+ // pilotExecution
+ if ( damageSource == eDamageSourceId.human_execution )
+ Stats_IncrementStat( player, "kills_stats", "pilotExecution", "", 1.0 )
+
+ // pilotExecutePilot
+ if ( victimIsPilot && damageSource == eDamageSourceId.human_execution )
+ Stats_IncrementStat( player, "kills_stats", "pilotExecutePilot", "", 1.0 )
+
+ // pilotKillsWithHoloPilotActive
+ if ( victimIsPilot && GetDecoyActiveCountForPlayer( player ) > 0 )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsWithHoloPilotActive", "", 1.0 )
+
+ // pilotKillsWithAmpedWallActive
+ if ( victimIsPilot && GetAmpedWallsActiveCountForPlayer( player ) > 0 )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsWithAmpedWallActive", "", 1.0 )
+
+ // pilotExecutePilotUsing_<execution>
+ if ( victimIsPilot && damageSource == eDamageSourceId.human_execution )
+ Stats_IncrementStat( player, "kills_stats", "pilotExecutePilotUsing_" + player.p.lastExecutionUsed, "", 1.0 )
+
+ // pilotKickMelee
+ if ( damageSource == eDamageSourceId.human_melee )
+ Stats_IncrementStat( player, "kills_stats", "pilotKickMelee", "", 1.0 )
+
+ // pilotKickMeleePilot
+ if ( victimIsPilot && damageSource == eDamageSourceId.human_melee )
+ Stats_IncrementStat( player, "kills_stats", "pilotKickMeleePilot", "", 1.0 )
+
+ // titanMelee
+ if ( DamageIsTitanMelee( damageSource ) )
+ Stats_IncrementStat( player, "kills_stats", "titanMelee", "", 1.0 )
+
+ // titanMeleePilot
+ if ( victimIsPilot && DamageIsTitanMelee( damageSource ) )
+ Stats_IncrementStat( player, "kills_stats", "titanMeleePilot", "", 1.0 )
+
+ // titanStepCrush
+ if ( IsTitanCrushDamage( damageInfo ) )
+ Stats_IncrementStat( player, "kills_stats", "titanStepCrush", "", 1.0 )
+
+ // titanStepCrushPilot
+ if ( victimIsPilot && IsTitanCrushDamage( damageInfo ) )
+ Stats_IncrementStat( player, "kills_stats", "titanStepCrushPilot", "", 1.0 )
+
+ // titanExocution<capitalisedChassis>
+ // note: RESPAWN WHY? EXPLAIN
+ if ( damageSource == eDamageSourceId.titan_execution )
+ {
+ string titanName = GetTitanCharacterName( player )
+ titanName = titanName.slice( 0, 1 ).toupper() + titanName.slice( 1, titanName.len() )
+ Stats_IncrementStat( player, "kills_stats", "titanExocution" + titanName, "", 1.0 )
+ }
+
+ // titanFallKill
+ if ( damageSource == eDamageSourceId.damagedef_titan_fall )
+ Stats_IncrementStat( player, "kills_stats", "titanFallKill", "", 1.0 )
+
+ // petTitanKillsFollowMode
+ if ( attacker == playerPetTitan && player.GetPetTitanMode() == eNPCTitanMode.FOLLOW )
+ Stats_IncrementStat( player, "kills_stats", "petTitanKillsFollowMode", "", 1.0 )
+
+ // petTitanKillsGuardMode
+ if ( attacker == playerPetTitan && player.GetPetTitanMode() == eNPCTitanMode.STAY )
+ Stats_IncrementStat( player, "kills_stats", "petTitanKillsGuardMode", "", 1.0 )
+
+ // rodeo_total
+ if ( damageSource == eDamageSourceId.rodeo_battery_removal )
+ Stats_IncrementStat( player, "kills_stats", "rodeo_total", "", 1.0 )
+
+ // pilot_headshots_total
+ if ( victimIsPilot && DamageInfo_GetCustomDamageType( damageInfo ) & DF_HEADSHOT )
+ Stats_IncrementStat( player, "kills_stats", "pilot_headshots_total", "", 1.0 )
+
+ // evacShips
+ if ( IsEvacDropship( victim ) )
+ Stats_IncrementStat( player, "kills_stats", "evacShips", "", 1.0 )
+
+ // nuclearCore
+ if ( damageSource == eDamageSourceId.damagedef_nuclear_core )
+ Stats_IncrementStat( player, "kills_stats", "nuclearCore", "", 1.0 )
+
+ // meleeWhileCloaked
+ if ( IsCloaked( attacker ) && damageSource == eDamageSourceId.human_melee )
+ Stats_IncrementStat( player, "kills_stats", "meleeWhileCloaked", "", 1.0 )
+
+ // titanKillsAsPilot
+ if ( victimIsTitan && IsPilot( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "titanKillsAsPilot", "", 1.0 )
+
+ // pilotKillsWhileStimActive
+ if ( victimIsPilot && StatusEffect_Get( attacker, eStatusEffect.stim_visual_effect ) <= 0 )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsWhileStimActive", "", 1.0 )
+
+ // pilotKillsAsTitan
+ if ( victimIsPilot && attacker.IsTitan() )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsAsTitan", "", 1.0 )
+
+ // pilotKillsAsPilot
+ if ( victimIsPilot && IsPilot( attacker ) )
+ Stats_IncrementStat( player, "kills_stats", "pilotKillsAsPilot", "", 1.0 )
+
+ // titanKillsAsTitan
+ if ( victimIsTitan && attacker.IsTitan() )
+ Stats_IncrementStat( player, "kills_stats", "titanKillsAsTitan", "", 1.0 )
}
-void function UpdateTitanDamageStat(entity attacker, float savedDamage, var damageInfo)
+void function HandleTitanStats( entity victim, entity attacker, var damageInfo )
{
+ if ( !IsValid( attacker ) )
+ return
+
+ // get the player and it's pet titan
+ entity player
+ entity playerPetTitan
+ if ( attacker.IsPlayer() )
+ {
+ // the player is just the attacker
+ player = attacker
+ playerPetTitan = player.GetPetTitan()
+ }
+ else if ( attacker.IsTitan() && IsPetTitan( attacker ) )
+ {
+ // the attacker is the player's auto titan
+ player = attacker.GetTitanSoul().GetBossPlayer()
+ playerPetTitan = attacker
+ }
+ else
+ {
+ // attacker could be something like an NPC, or worldspawn
+ return
+ }
+
+ int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ bool victimIsPlayer = victim.IsPlayer()
+ bool victimIsNPC = victim.IsNPC()
+ bool victimIsPilot = IsPilot( victim )
+ bool victimIsTitan = victim.IsTitan()
+ bool titanIsPrime = IsTitanPrimeTitan( player )
+ // pilots
+ if ( victimIsPilot && attacker.IsTitan() )
+ Stats_IncrementStat( player, "titan_stats", "pilots", GetTitanCharacterName( attacker ), 1.0 )
+
+ // titansTotal
+ if ( victimIsTitan && attacker.IsTitan() )
+ Stats_IncrementStat( player, "titan_stats", "titansTotal", GetTitanCharacterName( attacker ), 1.0 )
+
+ // pilotsAsPrime
+ if ( victimIsPilot && attacker.IsTitan() && titanIsPrime )
+ Stats_IncrementStat( player, "titan_stats", "pilotsAsPrime", GetTitanCharacterName( attacker ), 1.0 )
+
+ // titansAsPrime
+ if ( victimIsTitan && attacker.IsTitan() && titanIsPrime )
+ Stats_IncrementStat( player, "titan_stats", "titansAsPrime", GetTitanCharacterName( attacker ), 1.0 )
+
+ // executionsAsPrime
+ if ( damageSource == eDamageSourceId.titan_execution && attacker.IsTitan() && titanIsPrime )
+ Stats_IncrementStat( player, "titan_stats", "executionsAsPrime", GetTitanCharacterName( attacker ), 1.0 )
}
-void function UpdateTitanWeaponDamageStat(entity attacker, float savedDamage, var damageInfo)
+void function OnPlayerRespawned( entity player )
{
+ thread SetLastPosForDistanceStatValid_Threaded( player, true )
+}
+
+void function OnWinnerDetermined()
+{
+ // award players for match completed, wins, and losses
+ foreach ( entity player in GetPlayerArray() )
+ {
+ Stats_IncrementStat( player, "game_stats", "game_completed", "", 1.0 )
+
+ if ( player.GetTeam() == GetWinningTeam() )
+ Stats_IncrementStat( player, "game_stats", "game_won", "", 1.0 )
+ else
+ Stats_IncrementStat( player, "game_stats", "game_lost", "", 1.0 )
+ }
+
+ if ( IsValidGamemodeString( GAMETYPE ) )
+ {
+ // award players with matches played on the mode
+ foreach ( entity player in GetPlayerArray() )
+ {
+ Stats_IncrementStat( player, "game_stats", "mode_played", GAMETYPE, 1.0 )
+
+ if ( player.GetTeam() == GetWinningTeam() )
+ Stats_IncrementStat( player, "game_stats", "mode_won", GAMETYPE, 1.0 )
+ }
+ }
+
+ // update player's KD
+ foreach ( entity player in GetPlayerArray() )
+ {
+ // kd stats
+ // index 0 is most recent game
+ // index 9 is least recent game
+ float playerKills = ( player in file.playerKills ) ? file.playerKills[ player ] : 0.0
+ float playerDeaths = ( player in file.playerDeaths ) ? file.playerDeaths[ player ] : 0.0
+ float kdratio_match
+ if ( playerDeaths == 0.0 )
+ kdratio_match = playerKills
+ else
+ kdratio_match = playerKills / playerDeaths
+
+ float playerKillsPvp = ( player in file.playerKillsPvp ) ? file.playerKillsPvp[ player ] : 0.0
+ float playerDeathsPvp = ( player in file.playerDeathsPvp ) ? file.playerDeathsPvp[ player ] : 0.0
+ float kdratiopvp_match
+ if ( playerDeathsPvp == 0.0 )
+ kdratiopvp_match = playerKillsPvp
+ else
+ kdratiopvp_match = playerKillsPvp / playerDeathsPvp
+
+ float totalDeaths = player.GetPersistentVarAsInt( "deathStats.total" ).tofloat()
+ float totalKills = player.GetPersistentVarAsInt( "killStats.total" ).tofloat()
+ float totalDeathsPvp = player.GetPersistentVarAsInt( "deathStats.totalPVP" ).tofloat()
+ float totalKillsPvp = player.GetPersistentVarAsInt( "killStats.totalPVP" ).tofloat()
+ float kdratio_lifetime
+ if ( totalDeaths == 0.0 )
+ kdratio_lifetime = totalKills
+ else
+ kdratio_lifetime = totalKills / totalDeaths
+ float kdratio_lifetimepvp
+ if ( totalDeathsPvp == 0.0 )
+ kdratio_lifetimepvp = totalKillsPvp
+ else
+ kdratio_lifetimepvp = totalKillsPvp / totalDeathsPvp
+
+ // shift stats by 1 to make room for new game data
+ for ( int i = NUM_GAMES_TRACK_KDRATIO - 2; i >= 0; --i )
+ {
+ player.SetPersistentVar( format( "kdratio_match[%i]", ( i + 1 ) ), player.GetPersistentVar( format("kdratio_match[%i]", i ) ) )
+ player.SetPersistentVar( format( "kdratiopvp_match[%i]", ( i + 1 ) ), player.GetPersistentVar( format( "kdratiopvp_match[%i]", i ) ) )
+ }
+ // add new game data
+ player.SetPersistentVar( "kdratio_match[0]", kdratio_match )
+ player.SetPersistentVar( "kdratiopvp_match[0]", kdratiopvp_match )
+ player.SetPersistentVar( "kdratio_lifetime", kdratio_lifetime )
+ player.SetPersistentVar( "kdratio_lifetime_pvp", kdratio_lifetimepvp )
+ }
+
+ // award mvp and top 3 in each team
+ if ( !IsFFAGame() )
+ {
+ string gamemode = GameRules_GetGameMode()
+ int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( gamemode )
+
+ for( int team = 0; team < MAX_TEAMS; team++ )
+ {
+ array<entity> players = GetPlayerArrayOfTeam( team )
+ if ( compareFunc == null )
+ {
+ printt( "gamemode doesn't have a compare func to get the top 3" )
+ return
+ }
+ players.sort( compareFunc )
+ int maxAwards = int( min( players.len(), 3 ) )
+ for ( int i = 0; i < maxAwards; i++ )
+ {
+ if ( i == 0 )
+ Stats_IncrementStat( players[ i ], "game_stats", "mvp", "", 1.0 )
+ Stats_IncrementStat( players[ i ], "game_stats", "top3OnTeam", "", 1.0 )
+ }
+ }
+
+ }
+}
+void function SetLastPosForDistanceStatValid_Threaded( entity player, bool val )
+{
+ WaitFrame()
+ if ( !IsValid( player ) )
+ return
+ player.p.lastPosForDistanceStatValid = val
}
-void function UpdateTitanCoreEarnedStat( entity player, entity titan )
+// Respawn did this through stuff found in _entitystructs.gnut (stuff like stats_wallrunTime)
+// but their implementation seems kinda bad. The advantage it has over this method is not polling
+// every 0.25 seconds, and using movement callbacks and stuff instead. However, since i can't find
+// callbacks for things like changing weapon, i would have to poll for that *anyway* and thus,
+// there is no point in doing things Respawn's way here
+void function HandleDistanceAndTimeStats_Threaded()
{
+ // just to be safe
+ if ( IsLobby() )
+ return
+
+ while ( GetGameState() < eGameState.Playing )
+ WaitFrame()
+
+ float lastTickTime = Time()
+
+ while( true )
+ {
+ // track distance stats
+ foreach ( entity player in GetPlayerArray() )
+ {
+ if ( !IsValid( player ) )
+ continue
+
+ if ( player.p.lastPosForDistanceStatValid )
+ {
+ // not 100% sure on using Distance2D over Distance tbh
+ float distInches = Distance2D( player.p.lastPosForDistanceStat, player.GetOrigin() )
+ float distMiles = distInches / 63360.0
+
+ // more generic distance stats
+ Stats_IncrementStat( player, "distance_stats", "total", "", distMiles )
+ if ( player.IsTitan() )
+ {
+ Stats_IncrementStat( player, "distance_stats", "asTitan_" + GetTitanCharacterName( player ), "", distMiles )
+ Stats_IncrementStat( player, "distance_stats", "asTitan", "", distMiles )
+ }
+ else
+ Stats_IncrementStat( player, "distance_stats", "asPilot", "", distMiles )
+
+
+ string state = ""
+ // specific distance stats
+ if ( player.IsWallRunning() )
+ state = "wallrunning"
+ else if ( PlayerIsRodeoingTitan( player ) )
+ {
+ if ( player.GetTitanSoulBeingRodeoed().GetTeam() == player.GetTeam() )
+ state = "onFriendlyTitan"
+ else
+ state = "onEnemyTitan"
+ }
+ else if ( player.IsZiplining() )
+ state = "ziplining"
+ else if ( !player.IsOnGround() )
+ state = "inAir"
+
+ if ( state != "" )
+ Stats_IncrementStat( player, "distance_stats", state, "", distMiles )
+ }
+
+ player.p.lastPosForDistanceStat = player.GetOrigin()
+ }
+
+ float timeSeconds = Time() - lastTickTime
+ float timeHours = timeSeconds / 3600.0
+
+ // track time stats
+ foreach ( entity player in GetPlayerArray() )
+ {
+ // first tick i dont count
+ if ( timeSeconds == 0 )
+ break
+
+ // more generic time stats
+ Stats_IncrementStat( player, "time_stats", "hours_total", "", timeHours )
+ if ( player.IsTitan() )
+ {
+ Stats_IncrementStat( player, "time_stats", "hours_as_titan_" + GetTitanCharacterName( player ), "", timeHours )
+ Stats_IncrementStat( player, "time_stats", "hours_as_titan", "", timeHours )
+ }
+ else
+ Stats_IncrementStat( player, "time_stats", "hours_as_pilot", "", timeHours )
+
+ string state = ""
+ // specific time stats
+ if ( !IsAlive( player ) )
+ state = "hours_dead"
+ else if ( player.IsWallHanging() )
+ state = "hours_wallhanging"
+ else if ( player.IsWallRunning() )
+ state = "hours_wallrunning"
+ else if ( !player.IsOnGround() )
+ state = "hours_inAir"
+ if ( state != "" )
+ Stats_IncrementStat( player, "time_stats", state, "", timeHours )
+
+ // weapon time stats
+ entity activeWeapon = player.GetActiveWeapon()
+ if ( IsValid( activeWeapon ) )
+ {
+ if ( IsValidStatItemString( activeWeapon.GetWeaponClassName() ) )
+ Stats_IncrementStat( player, "weapon_stats", "hoursUsed", activeWeapon.GetWeaponClassName(), timeHours )
+
+ foreach( entity weapon in player.GetMainWeapons() )
+ {
+ if ( IsValidStatItemString( weapon.GetWeaponClassName() ) )
+ Stats_IncrementStat( player, "weapon_stats", "hoursEquipped", weapon.GetWeaponClassName(), timeHours )
+ }
+ }
+
+ // map time stats
+ Stats_IncrementStat( player, "game_stats", "hoursPlayed", "", timeHours )
+ }
+
+ lastTickTime = Time()
+ // not rly worth doing this every frame, just a couple of times per second should be fine
+ wait 0.25
+ }
+}
+
+// this is kinda shit
+void function SaveStatsPeriodically_Threaded()
+{
+ while( true )
+ {
+ foreach( entity player in GetPlayerArray() )
+ {
+ if ( IsValid( player ) )
+ Stats_SaveAllStats( player )
+ }
+ wait 5
+ }
}
-void function PreScoreEventUpdateStats(entity attacker, entity ent)
+bool function IsValidGamemodeString( string mode )
{
+ int gameModeCount = PersistenceGetEnumCount( "gameModes" )
+ for ( int modeIndex = 0; modeIndex < gameModeCount; modeIndex++ )
+ {
+ string gameModeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeIndex )
+
+ if ( gameModeName == mode )
+ return true
+ }
+ return false
}
-void function PostScoreEventUpdateStats(entity attacker, entity ent)
+bool function IsValidStatItemString( string item )
{
+ foreach( str in shGlobalMP.statsItemsList )
+ {
+ if ( str == item )
+ return true
+ }
+ return false
}
-void function Stats_OnPlayerDidDamage(entity player, var damageInfo)
+string function GetPersistenceRefFromDamageInfo( var damageInfo )
{
+ string damageSourceString = DamageSourceIDToString( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+
+ foreach( str in shGlobalMP.statsItemsList )
+ {
+ if ( str == damageSourceString )
+ return damageSourceString
+ }
+ return ""
+}
+
+bool function DamageIsTitanMelee( int damageSourceId )
+{
+ switch( damageSourceId )
+ {
+ case eDamageSourceId.melee_titan_punch:
+ case eDamageSourceId.melee_titan_punch_ion:
+ case eDamageSourceId.melee_titan_punch_legion:
+ case eDamageSourceId.melee_titan_punch_tone:
+ case eDamageSourceId.melee_titan_punch_scorch:
+ case eDamageSourceId.melee_titan_punch_northstar:
+ case eDamageSourceId.melee_titan_punch_fighter:
+ case eDamageSourceId.melee_titan_sword:
+ case eDamageSourceId.melee_titan_sword_aoe:
+ return true
+ default:
+ return false
+ }
+ unreachable
}
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
index 341493ba..8d859ba6 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut
@@ -339,7 +339,8 @@ void function PlayerWatchesWargamesIntro( entity player )
void function DelayedGamemodeAnnouncement( entity player )
{
wait 1.0
- TryGameModeAnnouncement( player )
+ if ( IsValid( player ) && IsAlive( player ) )
+ TryGameModeAnnouncement( player )
}
void function PlaySound_SimPod_DoorShut( entity playerFirstPersonProxy ) // stolen from sp_training
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
index 5bf150c0..d64e3a5b 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut
@@ -130,7 +130,7 @@ entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnp
{
int team = player.GetTeam()
if ( HasSwitchedSides() )
- team = GetOtherTeam( team )
+ team = ( team == TEAM_MILITIA ) ? TEAM_IMC : TEAM_MILITIA
array<entity> spawnpoints
if ( useStartSpawnpoint )
@@ -181,29 +181,19 @@ entity function GetBestSpawnpoint( entity player, array<entity> spawnpoints )
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 )
+ if ( !validSpawns.len() )
{
// 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
- }
}
// last resort
- if ( validSpawns.len() == 0 )
+ if ( !validSpawns.len() )
{
print( "map has literally 0 spawnpoints, as such everything is fucked probably, attempting to use info_player_start if present" )
entity start = GetEnt( "info_player_start" )
@@ -215,7 +205,7 @@ entity function GetBestSpawnpoint( entity player, array<entity> spawnpoints )
}
}
- return validSpawns[ RandomInt( validSpawns.len() ) ] // slightly randomize it
+ return validSpawns.getrandom() // slightly randomize it
}
bool function IsSpawnpointValid( entity spawnpoint, int team )
@@ -232,15 +222,18 @@ bool function IsSpawnpointValid( entity spawnpoint, int team )
return false
}
+ if( IsFFAGame() && !spawnpoint.IsVisibleToEnemies( team ) )
+ return true
+
int compareTeam = spawnpoint.GetTeam()
- if ( HasSwitchedSides() && ( compareTeam == TEAM_MILITIA || compareTeam == TEAM_IMC ) )
- compareTeam = GetOtherTeam( compareTeam )
-
+ if ( HasSwitchedSides() )
+ compareTeam = ( compareTeam == TEAM_MILITIA ) ? TEAM_IMC : TEAM_MILITIA
+
foreach ( bool functionref( entity, int ) customValidationRule in file.customSpawnpointValidationRules )
if ( !customValidationRule( spawnpoint, team ) )
return false
- if ( spawnpoint.GetTeam() > 0 && compareTeam != team && !IsFFAGame() )
+ if ( spawnpoint.GetTeam() > 0 && compareTeam != team )
return false
if ( spawnpoint.IsOccupied() )
@@ -261,10 +254,12 @@ bool function IsSpawnpointValid( entity spawnpoint, int team )
return false
}
- array<entity> projectiles = GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), 600 )
- foreach ( entity projectile in projectiles )
- if ( projectile.GetTeam() != team )
- return false
+ const minEnemyDist = 1200.0
+ array< entity > spawnBlockers = GetPlayerArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist )
+ spawnBlockers.extend( GetProjectileArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist ) )
+ spawnBlockers.extend( GetNPCArrayEx( "any", TEAM_ANY, spawnpoint.GetTeam(), spawnpoint.GetOrigin(), minEnemyDist ) )
+ if ( spawnBlockers.len() )
+ return false
// los check
return !spawnpoint.IsVisibleToEnemies( team )
@@ -396,11 +391,9 @@ void function InitPreferSpawnNodes()
// frontline
void function RateSpawnpoints_Frontline( int checkClass, array<entity> spawnpoints, int team, entity player )
{
+ float rating = RandomFloatRange( 0.0, 100.0 )
foreach ( entity spawnpoint in spawnpoints )
- {
- float rating = spawnpoint.CalculateFrontlineRating()
- spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating > 0 ? rating * 0.25 : rating )
- }
+ spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating )
}
// spawnzones
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut b/Northstar.CustomServers/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut
index 9f05a0cd..78cfdb27 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/rodeo/_rodeo_titan.gnut
@@ -41,6 +41,9 @@ 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.
+// needs these
+global function Rodeo_TakeBatteryAwayFromPilot
+
#if DEV
global function SetDebugRodeoPrint
global function GetDebugRodeoPrint
@@ -336,7 +339,7 @@ void function RodeoBatteryRemoval( entity pilot )
if ( !playerHadBattery )
{
- AddPlayerScore( pilot, "PilotBatteryStolen" )
+ AddPlayerScore( pilot, "PilotBatteryStolen", pilot )
entity battery = Rodeo_CreateBatteryPack( titan )
Rodeo_PilotPicksUpBattery( pilot, battery )
thread BatteryThiefHighlight( pilot )
@@ -1853,7 +1856,7 @@ void function Rodeo_OnTouchBatteryPack_Internal( entity player, entity batteryPa
Battery_StopFX( batteryPack ) //Will be turned on again when player loses cloak
Rodeo_PilotPicksUpBattery( player, batteryPack )
- AddPlayerScore( player, "PilotBatteryPickup" )
+ AddPlayerScore( player, "PilotBatteryPickup", player )
// MessageToPlayer( player, eEventNotifications.Rodeo_PilotPickedUpBattery )
return
}
@@ -1878,7 +1881,7 @@ void function Rodeo_PilotAddsBatteryToFriendlyTitan( entity rider, entity titan
Rodeo_ApplyBatteryToTitan( battery, titan ) //This destroys the battery
- AddPlayerScore( rider, "PilotBatteryApplied" )
+ AddPlayerScore( rider, "PilotBatteryApplied", rider )
EmitSoundOnEntityOnlyToPlayer( rider, rider, PILOT_APPLIES_BATTERY_TO_TITAN_HEALTH_RESTORED_SOUND )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut
index d15220e4..7a7498b8 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut
@@ -785,7 +785,7 @@ bool function IsSettingPrimeTitanWithoutSetFile( entity player, string loadoutTy
bool function SkipItemLockedCheck( entity player, string ref, string parentRef, string loadoutProperty ) //Hack: Skip entitlement related unlock checks for now. Can fail.
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return true
//if ( IsItemInEntitlementUnlock( ref ) && IsLobby() ) //TODO: Look into restricting this to lobby only? But entitlement checks can fail randomly...
@@ -2379,10 +2379,8 @@ bool function IsValidPilotLoadoutProperty( string propertyName )
case "weapon3Mod2":
case "weapon3Mod3":
case "ordnance":
- case "special":
case "passive1":
case "passive2":
- case "melee":
case "skinIndex":
case "camoIndex":
case "primarySkinIndex":
@@ -2403,7 +2401,6 @@ bool function IsValidTitanLoadoutProperty( string propertyName )
{
case "name":
case "titanClass":
- case "setFile":
case "primaryMod":
case "special":
case "antirodeo":
@@ -3266,6 +3263,24 @@ string function Loadouts_GetSetFileForRequestedClass( entity player )
return loadout.race
}
+ #if DEV
+ // these are #if DEV'd until they work as their function names describe they should
+ // atm these only exist to allow the #if DEV'd calls to them for bot code in this file to compile on retail
+ // bots don't work in retail at all, so this doesn't matter for us really, but these should be unDEV'd and api'd properly once they are functional
+
+ PilotLoadoutDef function GetRandomPilotLoadout()
+ {
+ PilotLoadoutDef loadout
+ return loadout
+ }
+
+ TitanLoadoutDef function GetRandomTitanLoadout( string setFile )
+ {
+ TitanLoadoutDef loadout
+ return loadout
+ }
+ #endif
+
bool function Loadouts_TryGivePilotLoadout( entity player )
{
if ( !Loadouts_CanGivePilotLoadout( player ) )
@@ -3978,7 +3993,7 @@ bool function IsValidTitanLoadoutIndex( int loadoutIndex )
bool function HasPrimeToMatchExecutionType( entity player, int itemType )
{
- if ( DevEverythingUnlocked() )
+ if ( DevEverythingUnlocked( player ) )
return true
switch( itemType )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut
index b26e48ca..f8597744 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_northstar_utils.gnut
@@ -1,11 +1,5 @@
globalize_all_functions
-// whether the server is a modded, northstar server
-bool function IsNorthstarServer()
-{
- return GetConVarBool( "ns_is_modded_server" )
-}
-
// whether the game should return to the lobby on GameRules_EndMatch()
bool function ShouldReturnToLobby()
{
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_powerup.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_powerup.gnut
new file mode 100644
index 00000000..c390b5f5
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_powerup.gnut
@@ -0,0 +1,294 @@
+global function SH_PowerUp_Init
+global function GetPowerUpFromIndex
+global function GetPowerUpFromItemRef
+
+//Proto Use Functions
+global function PowerUp_Func_GiveEPG
+global function PowerUp_Func_GiveHELL
+global function PowerUp_Func_GiveLSTAR
+global function PowerUp_Func_GiveSHOTGUN
+global function PowerUp_Func_GiveArmorSmall
+global function PowerUp_Func_GiveArmorMedium
+global function PowerUp_Func_GiveArmorLarge
+global function PowerUp_Func_TitanBuildTime
+global function PowerUp_Func_PilotUpgrade
+global function PowerUp_Func_GiveTicks
+
+global struct PowerUp
+{
+ int index
+ string name
+ asset icon
+ asset model
+ asset baseModel
+ string itemRef
+ vector modelOffset
+ vector modelAngles
+ float respawnDelay
+ vector glowColor
+ bool titanPickup
+ int maxInWorld
+ void functionref( entity ) destroyFunc
+ bool functionref() spawnFunc
+}
+
+const bool TITAN_PICKUP = true
+const bool PILOT_PICKUP = false
+
+struct
+{
+ array<PowerUp> powerUps
+}file
+
+const TEST_MODEL = $"models/communication/terminal_com_station.mdl"
+const TEST_ICON = $"vgui/HUD/coop/minimap_coop_nuke_titan"
+
+void function SH_PowerUp_Init()
+{
+ #if SERVER || CLIENT
+ PrecacheWeapon( "mp_weapon_epg" )
+ PrecacheWeapon( "mp_weapon_arena1" )
+ PrecacheWeapon( "mp_weapon_arena2" )
+ PrecacheWeapon( "mp_weapon_arena3" )
+ PrecacheWeapon( "mp_weapon_lstar" )
+ PrecacheWeapon( "mp_weapon_shotgun_doublebarrel" )
+ PrecacheWeapon( "mp_weapon_frag_drone" )
+ #endif
+
+ file.powerUps.resize( ePowerUps.count )
+ CreatePowerUp( ePowerUps.weaponEPG, "mp_weapon_epg", "EPG", 60.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_GiveEPG, <255,0,0>, PILOT_PICKUP, $"vgui/HUD/op_ammo_mini", $"models/weapons/auto_rocket_launcher_ARL/w_ARL.mdl", $"models/communication/flag_base.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.weaponHELL, "mp_weapon_arena3", "HELL", 90.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_GiveHELL, <255,0,0>, PILOT_PICKUP, $"vgui/HUD/op_ammo_mini", $"models/weapons/defender/w_defender.mdl", $"models/communication/flag_base.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.weaponLSTAR, "mp_weapon_lstar", "LSTAR", 45.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_GiveLSTAR, <255,0,0>, PILOT_PICKUP, $"vgui/HUD/op_ammo_mini", $"models/weapons/lstar/w_lstar.mdl", $"models/communication/flag_base.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.weaponSHOTGUN, "mp_weapon_shotgun_doublebarrel", "Shrapnel Shotgun", 30.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_GiveSHOTGUN, <255,0,0>, PILOT_PICKUP, $"vgui/HUD/op_ammo_mini", $"models/weapons/mastiff_stgn/w_mastiff.mdl", $"models/communication/flag_base.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.armorSmall, "mp_loot_armor_small", "Armor +5", 30.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_GiveArmorSmall, <0,0,255>, PILOT_PICKUP, $"vgui/HUD/op_health_mini", $"models/gameplay/health_pickup_small.mdl", $"models/containers/plastic_pallet_01.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.armorMedium, "mp_loot_armor_medium", "Armor +25", 60.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_GiveArmorMedium, <0,0,255>, PILOT_PICKUP, $"vgui/HUD/op_health_mini", $"models/gameplay/health_pickup_small.mdl", $"models/containers/plastic_pallet_01.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.armorLarge, "mp_loot_armor_large", "Armor +50", 120.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_GiveArmorLarge, <0,0,255>, PILOT_PICKUP, $"vgui/HUD/op_health_mini", $"models/gameplay/health_pickup_large.mdl", $"models/containers/plastic_pallet_01.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.titanTimeReduction, "mp_loot_titan_build_credit", "Titan Build Time", 20.0, 2, FRAShouldSpawnPowerUp, PowerUp_Func_TitanBuildTime, <0,255,0>, PILOT_PICKUP, $"vgui/HUD/op_drone_mini", $"models/titans/medium/titan_medium_battery_static.mdl", $"models/communication/flag_base.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.LTS_TitanTimeReduction, "mp_loot_titan_build_credit_lts", "Titan Build Time", 60.0, 0, LTSShouldSpawnPowerUp, PowerUp_Func_TitanBuildTime, <0,255,0>, PILOT_PICKUP, $"vgui/HUD/op_drone_mini", $"models/titans/medium/titan_medium_battery_static.mdl", $"models/communication/flag_base.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.pilotUpgrade, "mp_loot_pilot_upgrade", "Can of Spinach", 120.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_PilotUpgrade, <0,255,0>, PILOT_PICKUP, $"vgui/HUD/op_drone_mini", $"models/humans/pilots/pilot_light_ged_m.mdl", $"models/communication/flag_base.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+ CreatePowerUp( ePowerUps.ticks, "mp_weapon_frag_drone", "Ticks", 60.0, 0, DefaultShouldSpawnPowerUp, PowerUp_Func_GiveTicks, <255,0,0>, PILOT_PICKUP, $"vgui/HUD/op_ammo_mini", $"models/robots/drone_frag/frag_drone_proj.mdl", $"models/robots/drone_frag/frag_drone_proj.mdl", < 0, 0, 32 >, < 0, 0, 0 > )
+}
+
+bool function FRAShouldSpawnPowerUp()
+{
+ return GAMETYPE == FREE_AGENCY
+}
+
+bool function LTSShouldSpawnPowerUp()
+{
+ if ( HasIronRules() )
+ return false
+
+ // modified for fw
+ //return ( GAMETYPE == LAST_TITAN_STANDING || GAMETYPE == LTS_BOMB )
+ return ( GAMETYPE == LAST_TITAN_STANDING || GAMETYPE == LTS_BOMB || GAMETYPE == FORT_WAR )
+}
+
+bool function DefaultShouldSpawnPowerUp()
+{
+ return GetCurrentPlaylistVarInt( "power_ups_enabled", 0 ) == 1
+}
+
+void function CreatePowerUp( int enumIndex, string item, string displayName, float respawnTime, int worldLimit, bool functionref() shouldSpawnFunction, void functionref( entity ) destroyFunction, vector color, bool canTitanPickup, asset worldIcon, asset worldModel, asset worldBase, vector worldModelOffset, vector worldModelAngle )
+{
+ PowerUp power
+ power.index = enumIndex
+ power.name = displayName
+ power.icon = worldIcon
+ power.model = worldModel
+ power.baseModel = worldBase
+ power.itemRef = item
+ power.modelOffset = worldModelOffset
+ power.modelAngles = worldModelAngle
+ power.respawnDelay = respawnTime
+ power.destroyFunc = destroyFunction
+ power.spawnFunc = shouldSpawnFunction
+ power.glowColor = color
+ power.titanPickup = canTitanPickup
+ power.maxInWorld = worldLimit
+ file.powerUps[enumIndex] = power
+
+ #if CLIENT
+ PrecacheHUDMaterial( worldIcon )
+ #else
+ PrecacheModel( worldModel )
+ PrecacheModel( worldBase )
+ #if R1_VGUI_MINIMAP
+ Minimap_PrecacheMaterial( worldIcon )
+ #endif
+ #endif
+}
+
+PowerUp function GetPowerUpFromIndex( int index )
+{
+ return file.powerUps[index]
+}
+
+PowerUp function GetPowerUpFromItemRef( string ref )
+{
+ foreach( power in file.powerUps )
+ {
+ if ( power.itemRef == ref )
+ return power
+ }
+
+ Assert( false, "Power Up not found")
+ unreachable
+}
+
+//////////////////////////////////////////////
+// PROTO USE FUNCTIONS - Maybe should be a bunch of new item_ classes with their own healthkit callbacks?
+//////////////////////////////////////////////
+void function PowerUp_Func_GiveEPG( entity player )
+{
+ #if SERVER
+ if ( player.IsTitan() )
+ return
+ GiveWeaponPowerUp( player, "mp_weapon_arena2" )
+ #endif
+}
+
+void function PowerUp_Func_GiveHELL( entity player )
+{
+ #if SERVER
+ if ( player.IsTitan() )
+ return
+ GiveWeaponPowerUp( player, "mp_weapon_arena3" )
+ #endif
+}
+
+void function PowerUp_Func_GiveLSTAR( entity player )
+{
+ #if SERVER
+ if ( player.IsTitan() )
+ return
+ GiveWeaponPowerUp( player, "mp_weapon_arena1" )
+ #endif
+}
+
+void function PowerUp_Func_GiveSHOTGUN( entity player )
+{
+ #if SERVER
+ if ( player.IsTitan() )
+ return
+ GiveWeaponPowerUp( player, "mp_weapon_shotgun_doublebarrel" )
+ #endif
+}
+
+void function PowerUp_Func_GiveTicks( entity player )
+{
+ #if SERVER
+ if ( player.IsTitan() )
+ return
+ player.TakeOffhandWeapon( OFFHAND_ORDNANCE )
+ player.GiveOffhandWeapon( "mp_weapon_frag_drone", OFFHAND_ORDNANCE )
+ thread RestoreDefaultOffhandWeapon( player )
+ #endif
+}
+
+#if SERVER
+void function RestoreDefaultOffhandWeapon( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDestroy" )
+
+ while( true )
+ {
+ player.WaitSignal( "ThrowGrenade" )
+
+ if ( player.IsTitan() )
+ continue
+
+ entity weapon = player.GetOffhandWeapon( OFFHAND_ORDNANCE )
+ if ( weapon.GetWeaponPrimaryClipCount() == 0 )
+ {
+ player.TakeOffhandWeapon( OFFHAND_ORDNANCE )
+ int loadoutIndex = GetActivePilotLoadoutIndex( player )
+ PilotLoadoutDef loadout = GetPilotLoadoutFromPersistentData( player, loadoutIndex )
+ player.GiveOffhandWeapon( loadout.ordnance, OFFHAND_ORDNANCE )
+ return
+ }
+ }
+}
+
+void function GiveWeaponPowerUp( entity player, string newWeapon )
+{
+ array<entity> weapons = player.GetMainWeapons()
+ string weaponToSwitch = player.GetLatestPrimaryWeapon().GetWeaponClassName()
+
+ if ( player.GetActiveWeapon() != player.GetAntiTitanWeapon() )
+ {
+ foreach ( weapon in weapons )
+ {
+ string weaponClassName = weapon.GetWeaponClassName()
+ if ( weaponClassName == newWeapon )
+ {
+ weaponToSwitch = weaponClassName
+ break
+ }
+ }
+ }
+
+ player.TakeWeaponNow( weaponToSwitch )
+ player.GiveWeapon( newWeapon )
+ player.SetActiveWeaponByName( newWeapon )
+}
+#endif
+
+void function PowerUp_Func_GiveArmorSmall( entity player )
+{
+ GiveArmor( player, 5 )
+}
+
+void function PowerUp_Func_GiveArmorMedium( entity player )
+{
+ GiveArmor( player, 25 )
+}
+
+void function PowerUp_Func_GiveArmorLarge( entity player )
+{
+ GiveArmor( player, 50 )
+}
+
+void function GiveArmor( entity player, int amount )
+{
+ #if SERVER
+ if ( player.IsTitan() )
+ return
+ int currentShieldHealth = player.GetShieldHealth()
+ int currentMaxShieldHealth = player.GetShieldHealthMax()
+ player.SetShieldHealth( min( 200, amount + currentShieldHealth ) )
+ player.SetShieldHealthMax( min( 200, amount + currentMaxShieldHealth ) )
+ #endif
+}
+
+void function PowerUp_Func_TitanBuildTime( entity player )
+{
+ #if SERVER
+ entity battery = Rodeo_CreateBatteryPack()
+ battery.SetOrigin( player.GetOrigin() )
+ #endif
+}
+
+
+
+void function PowerUp_Func_PilotUpgrade( entity player )
+{
+ #if SERVER
+ if ( player.IsTitan() )
+ return
+
+ int loadoutIndex = GetPersistentSpawnLoadoutIndex( player, "pilot" )
+
+ PilotLoadoutDef loadout = GetPilotLoadoutFromPersistentData( player, loadoutIndex )
+
+ loadout.primary = "mp_weapon_arena2"
+ loadout.secondary = "mp_weapon_mgl"
+ loadout.ordnance = "mp_weapon_grenade_emp"
+
+ UpdateDerivedPilotLoadoutData( loadout )
+
+ GivePilotLoadout( player, loadout )
+ SetActivePilotLoadoutIndex( player, loadoutIndex )
+ #endif
+} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut
new file mode 100644
index 00000000..3297643e
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut
@@ -0,0 +1,1154 @@
+global function Progression_Init
+global function ProgressionEnabledForPlayer
+#if CLIENT || UI
+global function Progression_SetPreference
+global function Progression_GetPreference
+global function UpdateCachedLoadouts_Delayed
+#endif
+
+#if SP // literally just stub the global functions and call it a day
+
+void function Progression_Init() {}
+bool function ProgressionEnabledForPlayer( entity player ) { return false }
+#if CLIENT || UI
+void function Progression_SetPreference( bool enabled ) {}
+bool function Progression_GetPreference() { return false }
+void function UpdateCachedLoadouts_Delayed() {}
+#endif // CLIENT || UI
+
+#else // MP || UI basically
+
+// SO FOR SOME GOD DAMN REASON, PUTTING THESE INTO ONE STRUCT
+// AND PUTTING THE #if STUFF AROUND THE VARS CAUSES A COMPILE
+// ERROR, SO I HAVE TO DO THIS AWFULNESS
+
+#if SERVER
+struct {
+ table<entity, bool> progressionEnabled
+} file
+#else // UI || CLIENT
+struct {
+ bool isUpdatingCachedLoadouts = false
+} file
+#endif
+
+
+void function Progression_Init()
+{
+ #if SERVER
+ AddCallback_OnClientDisconnected( OnClientDisconnected )
+ AddClientCommandCallback( "ns_progression", ClientCommand_SetProgression )
+ AddClientCommandCallback( "ns_resettitanaegis", ClientCommand_ResetTitanAegis )
+ AddCallback_GameStateEnter( eGameState.Playing, OnPlaying )
+ #elseif CLIENT
+ AddCallback_OnClientScriptInit( OnClientScriptInit )
+ #endif
+}
+
+bool function ProgressionEnabledForPlayer( entity player )
+{
+ #if SERVER
+ if ( player in file.progressionEnabled )
+ return file.progressionEnabled[player]
+
+ return false
+ #else // CLIENT || UI
+ return GetConVarBool( "ns_progression_enabled" )
+ #endif
+}
+
+#if SERVER
+void function OnPlaying()
+{
+ SetUIVar( level, "penalizeDisconnect", false ) // dont show the "you will lose merits thing"
+}
+
+void function OnClientDisconnected( entity player )
+{
+ // cleanup table when player leaves
+ if ( player in file.progressionEnabled )
+ delete file.progressionEnabled[player]
+}
+
+bool function ClientCommand_SetProgression( entity player, array<string> args )
+{
+ if ( args.len() != 1 )
+ return false
+ if ( args[0] != "0" && args[0] != "1" )
+ return false
+
+ file.progressionEnabled[player] <- args[0] == "1"
+
+ // loadout validation when progression is turned on
+ if ( file.progressionEnabled[player] )
+ ValidateEquippedItems( player )
+
+ return true
+}
+
+/// Resets a specific Titan's Aegis rank back to `0`
+/// * `player` - The player entity to perform the action on
+/// * `args` - The arguments passed from the client command. `args[0]` should be a string corresponding to the chassis name of the Titan to reset.
+/// Valid chassis are: ion, tone, vanguard, northstar, ronin, legion, and scorch.
+///
+/// Returns `true` on success and `false` on missing args.
+bool function ClientCommand_ResetTitanAegis( entity player, array<string> args )
+{
+ if ( !args.len() )
+ return false
+
+ string titanRef = args[0].tolower()
+ if( !PersistenceEnumValueIsValid( "titanClasses", titanRef ) )
+ return false
+
+ int suitIndex = PersistenceGetEnumIndexForItemName( "titanClasses", titanRef )
+
+ player.SetPersistentVar( "titanFDUnlockPoints[" + suitIndex + "]", 0 )
+ player.SetPersistentVar( "previousFDUnlockPoints[" + suitIndex + "]", 0 )
+ player.SetPersistentVar( "fdTitanXP[" + suitIndex + "]", 0 )
+ player.SetPersistentVar( "fdPreviousTitanXP[" + suitIndex + "]", 0 )
+
+ // Refresh Highest Aegis Titan since we might get all of them back to 1 if players wants
+ RecalculateHighestTitanFDLevel( player )
+
+ return true
+}
+#endif
+
+#if CLIENT
+void function OnClientScriptInit( entity player )
+{
+ // unsure if this is needed, just being safe
+ if ( player != GetLocalClientPlayer() )
+ return
+
+ Progression_SetPreference( GetConVarBool( "ns_progression_enabled" ) )
+ UpdateCachedLoadouts_Delayed()
+}
+#endif
+
+#if CLIENT || UI
+void function Progression_SetPreference( bool enabled )
+{
+ SetConVarBool( "ns_progression_enabled", enabled )
+
+ #if CLIENT
+ GetLocalClientPlayer().ClientCommand( "ns_progression " + enabled.tointeger() )
+ #else // UI
+ ClientCommand( "ns_progression " + enabled.tointeger() )
+ #endif
+}
+
+bool function Progression_GetPreference()
+{
+ return GetConVarBool( "ns_progression_enabled" )
+}
+
+void function UpdateCachedLoadouts_Delayed()
+{
+ if ( file.isUpdatingCachedLoadouts )
+ return
+
+ file.isUpdatingCachedLoadouts = true
+
+ #if UI
+ RunClientScript( "UpdateCachedLoadouts_Delayed" ) // keep client and UI synced
+ #else // CLIENT
+ RunUIScript( "UpdateCachedLoadouts_Delayed" ) // keep client and UI synced
+ #endif
+
+ thread UpdateCachedLoadouts_Threaded()
+}
+
+void function UpdateCachedLoadouts_Threaded()
+{
+ wait 1.0 // give the server time to network our new persistence
+
+ UpdateCachedLoadouts()
+
+ // below here is just making all the menu models update properly and such
+
+ #if UI
+ uiGlobal.pilotSpawnLoadoutIndex = GetPersistentSpawnLoadoutIndex( GetUIPlayer(), "pilot" )
+ uiGlobal.titanSpawnLoadoutIndex = GetPersistentSpawnLoadoutIndex( GetUIPlayer(), "titan" )
+ #endif
+
+ #if CLIENT
+ entity player = GetLocalClientPlayer()
+ ClearAllTitanPreview( player )
+ ClearAllPilotPreview( player )
+ UpdateTitanModel( player, GetPersistentSpawnLoadoutIndex( player, "titan" ) )
+ UpdatePilotModel( player, GetPersistentSpawnLoadoutIndex( player, "pilot" ) )
+ #endif
+
+ file.isUpdatingCachedLoadouts = false
+}
+#endif
+
+#if SERVER
+void function ValidateEquippedItems( entity player )
+{
+ printt( "VALIDATING EQUIPPED ITEMS FOR PLAYER: " + player.GetPlayerName() )
+
+ // banner
+ CallingCard card = PlayerCallingCard_GetActive( player )
+ if ( IsItemLocked( player, card.ref ) )
+ {
+ printt( "- BANNER CARD IS LOCKED, RESETTING" )
+ PlayerCallingCard_SetActiveByRef( player, "callsign_16_col" ) // copied from _persistentdata.gnut
+ }
+
+ // patch
+ CallsignIcon icon = PlayerCallsignIcon_GetActive( player )
+ if ( IsItemLocked( player, icon.ref ) )
+ {
+ printt( "- BANNER PATCH IS LOCKED, RESETTING" )
+ PlayerCallsignIcon_SetActiveByRef( player, "gc_icon_titanfall" ) // copied from _persistentdata.gnut
+ }
+
+ // faction
+ int factionIndex = player.GetPersistentVarAsInt( "factionChoice" )
+ string factionRef = PersistenceGetEnumItemNameForIndex( "faction", factionIndex )
+ if ( IsItemLocked( player, factionRef ) )
+ {
+ printt( "- FACTION IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "factionChoice", "faction_marauder" ) // im so sorry that i am setting you to use sarah, you don't deserve this
+ }
+
+ // boost
+ BurnReward reward = BurnReward_GetById( player.GetPersistentVarAsInt( "burnmeterSlot" ) )
+ if ( IsItemLocked( player, reward.ref ) )
+ {
+ printt( "- BOOST IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "burnmeterSlot", BurnReward_GetByRef( "burnmeter_amped_weapons" ).id )
+ }
+
+ // titan loadouts
+ int selectedTitanLoadoutIndex = player.GetPersistentVarAsInt( "titanSpawnLoadout.index" )
+ for ( int titanLoadoutIndex = 0; titanLoadoutIndex < NUM_PERSISTENT_TITAN_LOADOUTS; titanLoadoutIndex++ )
+ {
+ printt( "- VALIDATING TITAN LOADOUT: " + titanLoadoutIndex )
+
+ bool isSelected = titanLoadoutIndex == selectedTitanLoadoutIndex
+ TitanLoadoutDef loadout = GetTitanLoadout( player, titanLoadoutIndex )
+ TitanLoadoutDef defaultLoadout = shGlobal.defaultTitanLoadouts[titanLoadoutIndex]
+
+ printt( " - CHASSIS: " + loadout.titanClass )
+
+ // passive1 - "Titan Kit" (things like overcore)
+ if ( loadout.passive1 != defaultLoadout.passive1 && IsSubItemLocked( player, loadout.passive1, loadout.titanClass ) )
+ {
+ printt( " - TITAN KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive1", defaultLoadout.passive1 )
+ }
+
+ // passive2 - "<chassis> Kit" (things like zero point tripwire)
+ if ( loadout.passive2 != defaultLoadout.passive2 && IsSubItemLocked( player, loadout.passive2, loadout.titanClass ) )
+ {
+ printt( " - CHASSIS KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive2", defaultLoadout.passive2 )
+ }
+
+ // passive3 - "Titanfall Kit" (warpfall/dome shield)
+ if ( loadout.passive3 != defaultLoadout.passive3 && IsSubItemLocked( player, loadout.passive3, loadout.titanClass ) )
+ {
+ printt( " - TITANFALL KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive3", defaultLoadout.passive3 )
+ }
+
+ // passive4 - monarch core 1
+ if ( loadout.passive4 != defaultLoadout.passive4 && IsSubItemLocked( player, loadout.passive4, loadout.titanClass ) )
+ {
+ printt( " - MONARCH CORE 1 KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive4", defaultLoadout.passive4 )
+ }
+
+ // passive5 - monarch core 2
+ if ( loadout.passive5 != defaultLoadout.passive5 && IsSubItemLocked( player, loadout.passive5, loadout.titanClass ) )
+ {
+ printt( " - MONARCH CORE 2 KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive5", defaultLoadout.passive5 )
+ }
+
+ // passive6 - monarch core 3
+ if ( loadout.passive6 != defaultLoadout.passive6 && IsSubItemLocked( player, loadout.passive6, loadout.titanClass ) )
+ {
+ printt( " - MONARCH CORE 3 KIT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].passive6", defaultLoadout.passive6 )
+ }
+
+ // titanExecution
+ if ( !IsRefValid( loadout.titanExecution ) || !IsValidTitanExecution( titanLoadoutIndex, "titanExecution", "", loadout.titanExecution ) )
+ {
+ printt( " - TITAN EXECUTION IS INVALID FOR CHASSIS, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].titanExecution", defaultLoadout.titanExecution )
+ }
+ else if ( IsItemLocked( player, loadout.titanExecution ) )
+ {
+ printt( " - TITAN EXECUTION EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].titanExecution", defaultLoadout.titanExecution )
+ }
+ else if ( GetItemData( loadout.titanExecution ).reqPrime && IsItemLocked( player, loadout.primeTitanRef ) )
+ {
+ printt( " - PRIME TITAN EXECUTION EQUIPPED WHEN PRIME TITAN IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].titanExecution", defaultLoadout.titanExecution )
+ }
+
+ // skinIndex
+ // camoIndex
+ if ( loadout.skinIndex == TITAN_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN_TITAN )
+ if ( loadout.camoIndex >= camoSkins.len() || loadout.camoIndex < 0 )
+ {
+ printt( " - INVALID TITAN CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.camoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.titanClass ) )
+ {
+ printt( " - TITAN CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+ }
+ else if ( loadout.skinIndex == 0 )
+ {
+ if ( loadout.camoIndex != 0 )
+ {
+ printt( " - INVALID TITAN CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+ else
+ {
+ string ref = GetSkinRefFromTitanClassAndPersistenceValue( loadout.titanClass, loadout.skinIndex )
+ if ( ref == INVALID_REF )
+ {
+ printt( " - INVALID TITAN WARPAINT, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ else if ( IsSubItemLocked( player, ref, loadout.titanClass ) )
+ {
+ printt( " - TITAN WARPAINT EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+
+ // decalIndex
+ string noseArtRef = GetNoseArtRefFromTitanClassAndPersistenceValue( loadout.titanClass, loadout.decalIndex )
+ if ( loadout.decalIndex != defaultLoadout.decalIndex && IsSubItemLocked( player, noseArtRef, loadout.titanClass ) )
+ {
+ printt( " - NOSE ART EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].decalIndex", defaultLoadout.decalIndex )
+ }
+
+ // primarySkinIndex
+ // primaryCamoIndex
+ if ( loadout.primarySkinIndex == WEAPON_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.primaryCamoIndex >= camoSkins.len() || loadout.primaryCamoIndex < 0 )
+ {
+ printt( " - INVALID WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.primaryCamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.titanClass ) )
+ {
+ printt( " - WEAPON CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ }
+ }
+ else if ( loadout.primarySkinIndex == 0 && loadout.primaryCamoIndex != 0 )
+ {
+ // titan weapons do not have skins, if we ever do add them lots of stuff will
+ //need a refactor outside of here so with that being said, i cannot be bothered
+ printt( " - INVALID WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+
+
+ // isPrime
+ if ( loadout.isPrime == "titan_is_prime" && IsItemLocked( player, loadout.primeTitanRef ) )
+ {
+ printt( " - PRIME TITAN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].isPrime", defaultLoadout.isPrime )
+ }
+
+ // primeSkinIndex
+ // primeCamoIndex
+ if ( loadout.primeSkinIndex == TITAN_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN_TITAN )
+ if ( loadout.primeCamoIndex >= camoSkins.len() || loadout.primeCamoIndex < 0 )
+ {
+ printt( " - INVALID TITAN CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeSkinIndex", defaultLoadout.primeSkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeCamoIndex", defaultLoadout.primeCamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.primeCamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.titanClass ) )
+ {
+ printt( " - TITAN CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeSkinIndex", defaultLoadout.primeSkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeCamoIndex", defaultLoadout.primeCamoIndex )
+ }
+ }
+ }
+ else if ( loadout.primeSkinIndex == 0 )
+ {
+ if ( loadout.primeCamoIndex != 0 )
+ {
+ printt( " - INVALID TITAN CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeSkinIndex", defaultLoadout.primeSkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeCamoIndex", defaultLoadout.primeCamoIndex )
+ }
+ }
+ else
+ {
+ printt( " - INVALID PRIME TITAN SKIN, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeSkinIndex", defaultLoadout.primeSkinIndex )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeCamoIndex", defaultLoadout.primeCamoIndex )
+ }
+
+ // primeDecalIndex
+ string primeNoseArtRef = GetNoseArtRefFromTitanClassAndPersistenceValue( loadout.titanClass, loadout.primeDecalIndex )
+ if ( loadout.primeDecalIndex != defaultLoadout.primeDecalIndex && IsSubItemLocked( player, primeNoseArtRef, loadout.titanClass ) )
+ {
+ printt( " - PRIME NOSE ART EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].primeDecalIndex", defaultLoadout.primeDecalIndex )
+ }
+
+ // showArmBadge - equipped and shouldnt be able to
+ if ( loadout.showArmBadge && !CanEquipArmBadge( player, loadout.titanClass ) )
+ {
+ printt( " - ARM BADGE EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanLoadouts[" + titanLoadoutIndex + "].showArmBadge", defaultLoadout.showArmBadge )
+ }
+
+ // equipped titan loadout - equipped titan class is locked
+ if ( isSelected && IsItemLocked( player, loadout.titanClass ) )
+ {
+ printt( " - SELECTED TITAN CLASS IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "titanSpawnLoadout.index", 0 )
+ selectedTitanLoadoutIndex = 0
+ }
+ }
+
+ if ( selectedTitanLoadoutIndex < 0 || selectedTitanLoadoutIndex >= NUM_PERSISTENT_TITAN_LOADOUTS )
+ {
+ printt( "- SELECTED TITAN CLASS IS INVALID, RESETTING" )
+ player.SetPersistentVar( "titanSpawnLoadout.index", 0 )
+ selectedTitanLoadoutIndex = 0
+ }
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdateTitanModel", selectedTitanLoadoutIndex )
+
+ // pilot loadouts
+ for ( int pilotLoadoutIndex = 0; pilotLoadoutIndex < NUM_PERSISTENT_PILOT_LOADOUTS; pilotLoadoutIndex++ )
+ {
+ printt( "- VALIDATING PILOT LOADOUT: " + pilotLoadoutIndex )
+
+ bool isSelected = pilotLoadoutIndex == player.GetPersistentVarAsInt( "pilotSpawnLoadout.index" )
+ PilotLoadoutDef loadout = GetPilotLoadout( player, pilotLoadoutIndex )
+ PilotLoadoutDef defaultLoadout = shGlobal.defaultPilotLoadouts[pilotLoadoutIndex]
+
+ // note: for readability, I have added {} around the different items,
+ // so that you can collapse them in visual studio code (and other good IDEs)
+
+ // tactical
+ {
+ if ( !IsRefValid( loadout.suit ) )
+ {
+ printt( " - TACTICAL IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].suit", defaultLoadout.suit )
+ }
+ else if ( IsItemLocked( player, loadout.suit ) )
+ {
+ printt( " - TACTICAL IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].suit", defaultLoadout.suit )
+ }
+ }
+
+ // ordnance
+ {
+ if ( !IsRefValid( loadout.ordnance ) )
+ {
+ printt( " - ORDNANCE IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].ordnance", defaultLoadout.ordnance )
+ }
+ else if ( IsItemLocked( player, loadout.ordnance ) )
+ {
+ printt( " - ORDNANCE IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].ordnance", defaultLoadout.ordnance )
+ }
+ }
+
+ // race ( gender )
+ {
+ if ( !IsRefValid( loadout.race ) )
+ {
+ printt( " - GENDER IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].race", defaultLoadout.race )
+ }
+ else if ( IsItemLocked( player, loadout.race ) )
+ {
+ printt( " - GENDER IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].race", defaultLoadout.race )
+ }
+ }
+
+ // camoIndex
+ // skinIndex
+ {
+ if ( loadout.skinIndex == PILOT_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN_PILOT )
+ if ( loadout.camoIndex >= camoSkins.len() || loadout.camoIndex < 0 )
+ {
+ printt( " - INVALID PILOT CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.camoIndex]
+ if ( IsItemLocked( player, camoSkin.ref ) )
+ {
+ printt( " - PILOT CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+ }
+ else if ( loadout.skinIndex == 0 )
+ {
+ if ( loadout.camoIndex != 0 )
+ {
+ printt( " - INVALID PILOT CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+ else
+ {
+ // pilots can't have skins other than 0 and 1 right?
+ printt( " - INVALID PILOT SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].skinIndex", defaultLoadout.skinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].camoIndex", defaultLoadout.camoIndex )
+ }
+ }
+
+ // primary weapon
+ {
+ if ( !IsRefValid( loadout.primary ) || GetItemType( loadout.primary ) != eItemTypes.PILOT_PRIMARY )
+ {
+ printt( " - PRIMARY WEAPON IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primary", defaultLoadout.primary )
+ }
+ else if ( IsItemLocked( player, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primary", defaultLoadout.primary )
+ }
+ }
+
+ // primary weapon mods
+ {
+ // mod1
+ if ( loadout.primaryMod1 == "" )
+ {
+ // do nothing
+ }
+ else if ( !HasSubitem( loadout.primary, loadout.primaryMod1 ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 1 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod1", defaultLoadout.primaryMod1 )
+ }
+ else if ( IsSubItemLocked( player, loadout.primaryMod1, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 1 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod1", defaultLoadout.primaryMod1 )
+ }
+ // mod2
+ if ( loadout.primaryMod2 == "" )
+ {
+ // do nothing
+ }
+ else if ( IsSubItemLocked( player, "primarymod2", loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 SLOT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ else if ( !HasSubitem( loadout.primary, loadout.primaryMod2 ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ else if ( IsSubItemLocked( player, loadout.primaryMod2, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ else if ( loadout.primaryMod2 == loadout.primaryMod1 && loadout.primaryMod2 != "" )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 IS DUPLICATE, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ else if ( loadout.primaryAttachment == "threat_scope" )
+ {
+ printt( " - PRIMARY WEAPON MOD 2 IS SET WITH THREAT SCOPE, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod2", defaultLoadout.primaryMod2 )
+ }
+ // attachment
+ if ( loadout.primaryAttachment == "" )
+ {
+ // do nothing
+ }
+ else if ( !HasSubitem( loadout.primary, loadout.primaryAttachment ) )
+ {
+ printt( " - PRIMARY WEAPON ATTACHMENT IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryAttachment", defaultLoadout.primaryAttachment )
+ }
+ else if ( IsSubItemLocked( player, loadout.primaryAttachment, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON ATTACHMENT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryAttachment", defaultLoadout.primaryAttachment )
+ }
+ // mod3 (pro screen)
+ if ( loadout.primaryMod3 == "" )
+ {
+ // do nothing
+ }
+ else if ( loadout.primaryMod3 == "pro_screen" )
+ {
+ // fuck you and your three mod slot stuff
+ printt( " - PRIMARY WEAPON PRO SCREEN IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod3", defaultLoadout.primaryMod3 )
+ }
+ else if ( IsSubItemLocked( player, loadout.primaryMod3, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON PRO SCREEN IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryMod3", defaultLoadout.primaryMod3 )
+ }
+ }
+
+ // primary weapon camoIndex
+ // primary weapon skinIndex
+ {
+ if ( loadout.primarySkinIndex == WEAPON_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.primaryCamoIndex >= camoSkins.len() || loadout.primaryCamoIndex < 0 )
+ {
+ printt( " - INVALID PRIMARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.primaryCamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ }
+ }
+ else if ( loadout.primarySkinIndex == 0 )
+ {
+ if ( loadout.primaryCamoIndex != 0 )
+ {
+ printt( " - INVALID PRIMARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ }
+ else
+ {
+ string warpaintRef = GetWeaponWarpaintRefByIndex( loadout.primarySkinIndex, loadout.primary )
+ if ( warpaintRef == INVALID_REF || IsSubItemLocked( player, warpaintRef, loadout.primary ) )
+ {
+ printt( " - PRIMARY WEAPON SKIN LOCKED/INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primarySkinIndex", defaultLoadout.primarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].primaryCamoIndex", defaultLoadout.primaryCamoIndex )
+ }
+ }
+ }
+
+ // secondary weapon
+ {
+ if ( !IsRefValid( loadout.secondary ) || GetItemType( loadout.secondary ) != eItemTypes.PILOT_SECONDARY )
+ {
+ printt( " - SECONDARY WEAPON IS LOCKED, RESETTING" )
+ string ref = defaultLoadout.secondary
+ if ( loadout.secondary == ref ) // item dupes swap
+ {
+ ref = defaultLoadout.weapon3
+ }
+ else if ( ItemsInSameMenuCategory( loadout.secondary, ref ) ) // category dupes assign value to other slot and swap
+ {
+ ref = defaultLoadout.weapon3
+ }
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondary", ref )
+ }
+ else if ( IsItemLocked( player, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON IS LOCKED, RESETTING" )
+ string ref = defaultLoadout.secondary
+ if ( loadout.weapon3 == ref ) // item dupes swap
+ {
+ ref = defaultLoadout.weapon3
+ }
+ else if ( ItemsInSameMenuCategory( loadout.weapon3, ref ) ) // category dupes assign value to other slot and swap
+ {
+ ref = defaultLoadout.weapon3
+ }
+
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondary", ref )
+ }
+ }
+
+ // secondary weapon mods
+ {
+ // mod1
+ if ( loadout.secondaryMod1 == "" )
+ {
+ // do nothing
+ }
+ else if ( !HasSubitem( loadout.secondary, loadout.secondaryMod1 ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 1 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod1", defaultLoadout.secondaryMod1 )
+ }
+ else if ( IsSubItemLocked( player, loadout.secondaryMod1, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 1 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod1", defaultLoadout.secondaryMod1 )
+ }
+ // mod2
+ if ( loadout.secondaryMod2 == "" )
+ {
+ // do nothing
+ }
+ else if ( IsSubItemLocked( player, "secondarymod2", loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 2 SLOT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod2", defaultLoadout.secondaryMod2 )
+ }
+ else if ( !HasSubitem( loadout.secondary, loadout.secondaryMod2 ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 2 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod2", defaultLoadout.secondaryMod2 )
+ }
+ else if ( IsSubItemLocked( player, loadout.secondaryMod2, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON MOD 2 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod2", defaultLoadout.secondaryMod2 )
+ }
+ else if ( loadout.secondaryMod2 == loadout.secondaryMod1 && loadout.secondaryMod2 != "" )
+ {
+ printt( " - SECONDARY WEAPON MOD 2 IS DUPLICATE, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod2", defaultLoadout.secondaryMod2 )
+ }
+ // mod3 (pro screen)
+ if ( loadout.secondaryMod3 == "" )
+ {
+ // do nothing
+ }
+ else if ( loadout.secondaryMod3 == "pro_screen" )
+ {
+ // fuck you and your three mod slot stuff
+ printt( " - SECONDARY WEAPON PRO SCREEN IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod3", defaultLoadout.secondaryMod3 )
+ }
+ else if ( IsSubItemLocked( player, "secondarymod3", loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON PRO SCREEN IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryMod3", defaultLoadout.secondaryMod3 )
+ }
+ }
+
+ // secondary weapon camoIndex
+ // secondary weapon skinIndex
+ {
+ if ( loadout.secondarySkinIndex == WEAPON_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.secondaryCamoIndex >= camoSkins.len() || loadout.secondaryCamoIndex < 0 )
+ {
+ printt( " - INVALID SECONDARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondarySkinIndex", defaultLoadout.secondarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryCamoIndex", defaultLoadout.secondaryCamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.secondaryCamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondarySkinIndex", defaultLoadout.secondarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryCamoIndex", defaultLoadout.secondaryCamoIndex )
+ }
+ }
+ }
+ else if ( loadout.secondarySkinIndex == 0 )
+ {
+ if ( loadout.secondaryCamoIndex != 0 )
+ {
+ printt( " - INVALID SECONDARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondarySkinIndex", defaultLoadout.secondarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryCamoIndex", defaultLoadout.secondaryCamoIndex )
+ }
+ }
+ else
+ {
+ string warpaintRef = GetWeaponWarpaintRefByIndex( loadout.secondarySkinIndex, loadout.secondary )
+ if ( warpaintRef == INVALID_REF || IsSubItemLocked( player, warpaintRef, loadout.secondary ) )
+ {
+ printt( " - SECONDARY WEAPON SKIN LOCKED/INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondarySkinIndex", defaultLoadout.secondarySkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].secondaryCamoIndex", defaultLoadout.secondaryCamoIndex )
+ }
+ }
+ }
+
+ // weapon3
+ // note: these are always eItemTypes.PILOT_SECONDARY
+ {
+ if ( !IsRefValid( loadout.weapon3 ) || GetItemType( loadout.weapon3 ) != eItemTypes.PILOT_SECONDARY )
+ {
+ printt( " - WEAPON3 WEAPON IS LOCKED, RESETTING" )
+ string ref = defaultLoadout.weapon3
+ if ( loadout.weapon3 == ref ) // item dupes swap
+ {
+ ref = defaultLoadout.secondary
+ }
+ else if ( ItemsInSameMenuCategory( loadout.weapon3, ref ) ) // category dupes assign value to other slot and swap
+ {
+ ref = defaultLoadout.secondary
+ }
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3", ref )
+ }
+ else if ( IsItemLocked( player, loadout.weapon3 ) )
+ {
+ printt( " - TERTIARY WEAPON IS LOCKED, RESETTING" )
+ string ref = defaultLoadout.weapon3
+ if ( loadout.secondary == ref ) // item dupes swap
+ {
+ ref = defaultLoadout.secondary
+ }
+ else if ( ItemsInSameMenuCategory( loadout.secondary, ref ) ) // category dupes assign value to other slot and swap
+ {
+ ref = defaultLoadout.secondary
+ }
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3", ref )
+ }
+ }
+
+ // weapon3 mods
+ {
+ // mod1
+ if ( loadout.weapon3Mod1 == "" )
+ {
+ // do nothing
+ }
+ else if ( !HasSubitem( loadout.weapon3, loadout.weapon3Mod1 ) )
+ {
+ printt( " - WEAPON3 MOD 1 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod1", defaultLoadout.weapon3Mod1 )
+ }
+ else if ( IsSubItemLocked( player, loadout.weapon3Mod1, loadout.weapon3 ) )
+ {
+ printt( " - WEAPON3 MOD 1 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod1", defaultLoadout.weapon3Mod1 )
+ }
+ // mod2
+ if ( loadout.weapon3Mod2 == "" )
+ {
+ // do nothing
+ }
+ else if ( IsSubItemLocked( player, "secondarymod2", loadout.weapon3 ) )
+ {
+ printt( " - WEAPON3 MOD 2 SLOT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod2", defaultLoadout.weapon3Mod2 )
+ }
+ else if ( !HasSubitem( loadout.weapon3, loadout.weapon3Mod2 ) )
+ {
+ printt( " - WEAPON3 MOD 2 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod2", defaultLoadout.weapon3Mod2 )
+ }
+ else if ( IsSubItemLocked( player, loadout.weapon3Mod2, loadout.weapon3 ) )
+ {
+ printt( " - WEAPON3 MOD 2 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod2", defaultLoadout.weapon3Mod2 )
+ }
+ else if ( loadout.weapon3Mod2 == loadout.weapon3Mod1 && loadout.weapon3Mod2 != "" )
+ {
+ printt( " - WEAPON3 MOD 2 IS DUPLICATE, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod2", defaultLoadout.weapon3Mod2 )
+ }
+ // mod3 (pro screen)
+ if ( loadout.weapon3Mod3 == "" )
+ {
+ // do nothing
+ }
+ else if ( loadout.weapon3Mod3 != "pro_screen" )
+ {
+ // fuck you and your three mod slot stuff
+ printt( " - WEAPON3 PRO SCREEN IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod3", defaultLoadout.weapon3Mod3 )
+ }
+ else if ( IsSubItemLocked( player, "secondarymod3", loadout.weapon3 ) )
+ {
+ printt( " - WEAPON3 PRO SCREEN IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3Mod3", defaultLoadout.weapon3Mod3 )
+ }
+ }
+
+ // weapon3 camoIndex
+ // weapon3 skinIndex
+ {
+ if ( loadout.weapon3SkinIndex == WEAPON_SKIN_INDEX_CAMO )
+ {
+ array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN )
+ if ( loadout.weapon3CamoIndex >= camoSkins.len() || loadout.weapon3CamoIndex < 0 )
+ {
+ printt( " - INVALID TERTIARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3SkinIndex", defaultLoadout.weapon3SkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3CamoIndex", defaultLoadout.weapon3CamoIndex )
+ }
+ else
+ {
+ ItemData camoSkin = camoSkins[loadout.weapon3CamoIndex]
+ if ( IsSubItemLocked( player, camoSkin.ref, loadout.weapon3 ) )
+ {
+ printt( " - TERTIARY WEAPON CAMO/SKIN EQUIPPED WHEN LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3SkinIndex", defaultLoadout.weapon3SkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3CamoIndex", defaultLoadout.weapon3CamoIndex )
+ }
+ }
+ }
+ else if ( loadout.weapon3SkinIndex == 0 )
+ {
+ if ( loadout.weapon3CamoIndex != 0 )
+ {
+ printt( " - INVALID TERTIARY WEAPON CAMO/SKIN, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3SkinIndex", defaultLoadout.weapon3SkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3CamoIndex", defaultLoadout.weapon3CamoIndex )
+ }
+ }
+ else
+ {
+ string warpaintRef = GetWeaponWarpaintRefByIndex( loadout.weapon3SkinIndex, loadout.weapon3 )
+ if ( warpaintRef == INVALID_REF || IsSubItemLocked( player, warpaintRef, loadout.weapon3 ) )
+ {
+ printt( " - TERTIARY WEAPON SKIN LOCKED/INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3SkinIndex", defaultLoadout.weapon3SkinIndex )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].weapon3CamoIndex", defaultLoadout.weapon3CamoIndex )
+ }
+ }
+ }
+
+ // kit 1
+ {
+ if ( !IsRefValid( loadout.passive1 ) || GetItemType( loadout.passive1 ) != eItemTypes.PILOT_PASSIVE1 )
+ {
+ printt( " - KIT 1 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].passive1", defaultLoadout.passive1 )
+ }
+ else if ( IsItemLocked( player, loadout.passive1 ) )
+ {
+ printt( " - KIT 1 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].passive1", defaultLoadout.passive1 )
+ }
+ }
+
+ // kit 2
+ {
+ if ( !IsRefValid( loadout.passive2 ) || GetItemType( loadout.passive2 ) != eItemTypes.PILOT_PASSIVE2 )
+ {
+ printt( " - KIT 2 IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].passive2", defaultLoadout.passive2 )
+ }
+ else if ( IsItemLocked( player, loadout.passive2 ) )
+ {
+ printt( " - KIT 2 IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].passive2", defaultLoadout.passive2 )
+ }
+ }
+
+ // execution
+ // note: not sure why defaultLoadout has this set to "", but neck snap should be default
+ {
+ if ( !IsRefValid( loadout.execution ) || GetItemType( loadout.execution ) != eItemTypes.PILOT_EXECUTION )
+ {
+ printt( " - EXECUTION IS INVALID, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].execution", "execution_neck_snap" )
+ }
+ else if ( IsItemLocked( player, loadout.execution ) )
+ {
+ printt( " - EXECUTION IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotLoadouts[" + pilotLoadoutIndex + "].execution", "execution_neck_snap" )
+ }
+ }
+
+ // equipped pilot loadout
+ {
+ if ( isSelected && IsItemLocked( player, "pilot_loadout_" + ( pilotLoadoutIndex + 1 ) ) )
+ {
+ printt( " - SELECTED PILOT LOADOUT IS LOCKED, RESETTING" )
+ player.SetPersistentVar( "pilotSpawnLoadout.index", 0 )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdatePilotModel", 0 )
+ }
+ }
+ }
+
+ Remote_CallFunction_NonReplay( player, "ServerCallback_UpdatePilotModel", player.GetPersistentVarAsInt( "pilotSpawnLoadout.index" ) )
+
+ printt( "ITEM VALIDATION COMPLETE FOR PLAYER: " + player.GetPlayerName() )
+}
+
+// basically just PopulateTitanLoadoutFromPersistentData but without validation, we are doing the validation in a better way
+// that doesnt just kick the player and reset the entire loadout, since we want to only reset parts of the loadout that we need
+TitanLoadoutDef function GetTitanLoadout( entity player, int loadoutIndex )
+{
+ TitanLoadoutDef loadout
+
+ loadout.name = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "name" )
+ loadout.titanClass = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "titanClass" )
+ loadout.primaryMod = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "primaryMod" )
+ loadout.special = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "special" )
+ loadout.antirodeo = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "antirodeo" )
+ loadout.passive1 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive1" )
+ loadout.passive2 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive2" )
+ loadout.passive3 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive3" )
+ loadout.passive4 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive4" )
+ loadout.passive5 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive5" )
+ loadout.passive6 = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "passive6" )
+ loadout.camoIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "camoIndex" )
+ loadout.skinIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "skinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.decalIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "decalIndex" )
+ loadout.primaryCamoIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primaryCamoIndex" )
+ loadout.primarySkinIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primarySkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.titanExecution = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "titanExecution" )
+ loadout.showArmBadge = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "showArmBadge" )
+
+ //Prime Titan related vars
+ loadout.isPrime = GetPersistentLoadoutValue( player, "titan", loadoutIndex, "isPrime" )
+ loadout.primeCamoIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primeCamoIndex" )
+ loadout.primeSkinIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primeSkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.primeDecalIndex = GetPersistentLoadoutValueInt( player, "titan", loadoutIndex, "primeDecalIndex" )
+
+ UpdateDerivedTitanLoadoutData( loadout )
+ OverwriteLoadoutWithDefaultsForSetFile( loadout )
+
+ return loadout
+}
+
+// basically just PopulatePilotLoadoutFromPersistentData but without validation, we are doing the validation in a better way
+// that doesnt just kick the player and reset the entire loadout, since we want to only reset parts of the loadout that we need
+PilotLoadoutDef function GetPilotLoadout( entity player, int loadoutIndex )
+{
+ PilotLoadoutDef loadout
+
+ loadout.name = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "name" )
+ loadout.suit = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "suit" )
+ loadout.race = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "race" )
+ loadout.execution = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "execution" )
+ loadout.primary = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primary" )
+ loadout.primaryAttachment = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primaryAttachment" )
+ loadout.primaryMod1 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primaryMod1" )
+ loadout.primaryMod2 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primaryMod2" )
+ loadout.primaryMod3 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "primaryMod3" )
+ loadout.secondary = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "secondary" )
+ loadout.secondaryMod1 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "secondaryMod1" )
+ loadout.secondaryMod2 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "secondaryMod2" )
+ loadout.secondaryMod3 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "secondaryMod3" )
+ loadout.weapon3 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "weapon3" )
+ loadout.weapon3Mod1 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "weapon3Mod1" )
+ loadout.weapon3Mod2 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "weapon3Mod2" )
+ loadout.weapon3Mod3 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "weapon3Mod3" )
+ loadout.ordnance = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "ordnance" )
+ loadout.passive1 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "passive1" )
+ loadout.passive2 = GetPersistentLoadoutValue( player, "pilot", loadoutIndex, "passive2" )
+ loadout.camoIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "camoIndex" )
+ loadout.skinIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "skinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.primaryCamoIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "primaryCamoIndex" )
+ loadout.primarySkinIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "primarySkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.secondaryCamoIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "secondaryCamoIndex" )
+ loadout.secondarySkinIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "secondarySkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+ loadout.weapon3CamoIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "weapon3CamoIndex" )
+ loadout.weapon3SkinIndex = GetPersistentLoadoutValueInt( player, "pilot", loadoutIndex, "weapon3SkinIndex" ) //Important: Skin index needs to be gotten after camoIndex for loadout validation purposes
+
+ UpdateDerivedPilotLoadoutData( loadout )
+
+ return loadout
+}
+
+bool function CanEquipArmBadge( entity player, string titanClass )
+{
+ string skinRef
+ switch ( titanClass )
+ {
+ case "ion":
+ skinRef = "ion_skin_fd"
+ break
+ case "scorch":
+ skinRef = "scorch_skin_fd"
+ break
+ case "northstar":
+ skinRef = "northstar_skin_fd"
+ break
+ case "ronin":
+ skinRef = "ronin_skin_fd"
+ break
+ case "tone":
+ skinRef = "tone_skin_fd"
+ break
+ case "legion":
+ skinRef = "legion_skin_fd"
+ break
+ case "vanguard":
+ skinRef = "monarch_skin_fd"
+ break
+ }
+
+ return !IsSubItemLocked( player, skinRef, titanClass )
+}
+
+string function GetWeaponWarpaintRefByIndex( int skinIndex, string parentRef )
+{
+ ItemData parentItem = GetItemData( parentRef )
+ foreach ( subItem in parentItem.subitems )
+ {
+ if ( GetSubitemType( parentRef, subItem.ref ) != eItemTypes.WEAPON_SKIN )
+ continue
+ if ( subItem.i.skinIndex != skinIndex )
+ continue
+
+ return subItem.ref
+ }
+
+ return INVALID_REF
+}
+#endif // SERVER
+
+#endif // MP
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_server_to_client_stringcommands.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_server_to_client_stringcommands.gnut
index a51e528f..18df6a6f 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_server_to_client_stringcommands.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_server_to_client_stringcommands.gnut
@@ -1,6 +1,4 @@
#if CLIENT
-global function ServerToClientStringCommands_Init
-
global function AddServerToClientStringCommandCallback
global function NSClientCodeCallback_RecievedServerToClientStringCommand
#endif
@@ -14,11 +12,6 @@ struct {
table< string, array< void functionref( array<string> args ) > > callbacks
} file
-void function ServerToClientStringCommands_Init()
-{
- getroottable().rawset( "NSClientCodeCallback_RecievedServerToClientStringCommand", NSClientCodeCallback_RecievedServerToClientStringCommand )
-}
-
void function AddServerToClientStringCommandCallback( string command, void functionref( array<string> args ) callback )
{
if ( !( command in file.callbacks ) )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_store.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_store.gnut
new file mode 100644
index 00000000..371cc1c7
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_store.gnut
@@ -0,0 +1,933 @@
+
+global function Store_Init
+global function Store_GetCustomizationRefs
+global function Store_GetPatchRefs
+global function Store_GetBannerRefs
+global function Store_GetCamoRefs
+
+global struct CamoRef
+{
+ string ref
+ string pilotRef
+ string titanRef
+}
+
+struct RefData
+{
+ string ref
+ string parentRef
+}
+
+struct
+{
+ table< int, array<string> > customizationRefs
+ table< int, array<string> > patchRefs
+ table< int, array<string> > bannerRefs
+ table< int, array<CamoRef> > camoRefs
+ table< int, array<RefData> > limitedEditionFDRefData
+} file
+
+void function Store_Init()
+{
+ #if SERVER
+ AddClientCommandCallback( "SetHasSeenStore", ClientCommand_SetHasSeenStore )
+ AddClientCommandCallback( "StoreSetNewItemStatus", ClientCommand_StoreSetNewItemStatus )
+ #endif
+
+ file.customizationRefs[ ET_DLC1_ION ] <- []
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_skin_10" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_20" )
+ file.customizationRefs[ ET_DLC1_ION ].append( "ion_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_ION ] <- []
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_skin_11" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_25" )
+ file.customizationRefs[ ET_DLC3_ION ].append( "ion_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_ION ] <- []
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_skin_07" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_30" )
+ file.customizationRefs[ ET_DLC5_ION ].append( "ion_nose_art_31" )
+
+ file.customizationRefs[ ET_DLC1_SCORCH ] <- []
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_skin_07" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_15" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_16" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_SCORCH ].append( "scorch_nose_art_19" )
+ file.customizationRefs[ ET_DLC3_SCORCH ] <- []
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_skin_08" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_20" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_SCORCH ].append( "scorch_nose_art_24" )
+ file.customizationRefs[ ET_DLC5_SCORCH ] <- []
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_skin_06" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_25" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_SCORCH ].append( "scorch_nose_art_29" )
+
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ] <- []
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_skin_10" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_20" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_21" )
+ file.customizationRefs[ ET_DLC1_NORTHSTAR ].append( "northstar_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ] <- []
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_skin_11" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_25" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_26" )
+ file.customizationRefs[ ET_DLC3_NORTHSTAR ].append( "northstar_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ] <- []
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_skin_06" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_30" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_31" )
+ file.customizationRefs[ ET_DLC5_NORTHSTAR ].append( "northstar_nose_art_32" )
+
+ file.customizationRefs[ ET_DLC1_RONIN ] <- []
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_skin_10" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_16" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_RONIN ].append( "ronin_nose_art_20" )
+ file.customizationRefs[ ET_DLC3_RONIN ] <- []
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_skin_11" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_RONIN ].append( "ronin_nose_art_25" )
+ file.customizationRefs[ ET_DLC5_RONIN ] <- []
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_skin_07" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_RONIN ].append( "ronin_nose_art_30" )
+
+ file.customizationRefs[ ET_DLC1_TONE ] <- []
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_skin_06" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_20" )
+ file.customizationRefs[ ET_DLC1_TONE ].append( "tone_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_TONE ] <- []
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_skin_07" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_25" )
+ file.customizationRefs[ ET_DLC3_TONE ].append( "tone_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_TONE ] <- []
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_skin_08" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_30" )
+ file.customizationRefs[ ET_DLC5_TONE ].append( "tone_nose_art_31" )
+
+ file.customizationRefs[ ET_DLC1_LEGION ] <- []
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_skin_07" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_17" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_18" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_19" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_20" )
+ file.customizationRefs[ ET_DLC1_LEGION ].append( "legion_nose_art_21" )
+ file.customizationRefs[ ET_DLC3_LEGION ] <- []
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_skin_08" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_22" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_23" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_24" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_25" )
+ file.customizationRefs[ ET_DLC3_LEGION ].append( "legion_nose_art_26" )
+ file.customizationRefs[ ET_DLC5_LEGION ] <- []
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_skin_09" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_27" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_28" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_29" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_30" )
+ file.customizationRefs[ ET_DLC5_LEGION ].append( "legion_nose_art_31" )
+
+ file.patchRefs[ ET_DLC1_CALLSIGN ] <- []
+ file.patchRefs[ ET_DLC3_CALLSIGN ] <- []
+ file.patchRefs[ ET_DLC5_CALLSIGN ] <- []
+
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_64" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_aces" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_alien" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_apex" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_ares" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_controller" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_drone" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_heartbreaker" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_hexes" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_kodai" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_lastimosa" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_lawai" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_mcor" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_phoenix" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_pilot" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_robot" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_sentry" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_super_spectre" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_vinson" )
+ file.patchRefs[ ET_DLC1_CALLSIGN ].append( "gc_icon_wonyeon" )
+
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_balance" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_boot" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_bt_eye" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_buzzsaw" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_crossed_lighting" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_flying_bullet" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_hammer2" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_keyboard" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_lightbulb" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_narwhal" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_peace" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_pilot2" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_robot_eye" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_srs" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_starline" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_taco" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_thumbdown" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_thumbup" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_treble" )
+ file.patchRefs[ ET_DLC3_CALLSIGN ].append( "gc_icon_vanguard" )
+
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_monarch_dlc5" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_militia" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_militia_alt" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_imc" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_hammond" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_tri_chevron" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_pilot_circle" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_x" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_nessie" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_spicy" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_crown" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_pawn" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_excite" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_duck" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_sock" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_rabbit" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_peanut" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_clock" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_shamrock" )
+ file.patchRefs[ ET_DLC5_CALLSIGN ].append( "gc_icon_trident" )
+
+ file.bannerRefs[ ET_DLC1_CALLSIGN ] <- []
+ file.bannerRefs[ ET_DLC3_CALLSIGN ] <- []
+ file.bannerRefs[ ET_DLC5_CALLSIGN ] <- []
+
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_106_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_107_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_108_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_109_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_110_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_111_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_112_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_113_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_114_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_115_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_116_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_117_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_118_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_119_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_120_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_121_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_122_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_123_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_124_col" )
+ file.bannerRefs[ ET_DLC1_CALLSIGN ].append( "callsign_125_col" )
+
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_143_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_144_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_145_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_146_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_147_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_148_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_149_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_150_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_151_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_152_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_153_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_154_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_155_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_156_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_157_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_158_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_159_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_160_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_161_col" )
+ file.bannerRefs[ ET_DLC3_CALLSIGN ].append( "callsign_162_col" )
+
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_166_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_167_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_168_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_169_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_170_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_171_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_172_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_173_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_174_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_175_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_176_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_177_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_178_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_179_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_180_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_181_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_182_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_183_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_184_col" )
+ file.bannerRefs[ ET_DLC5_CALLSIGN ].append( "callsign_185_col" )
+
+ file.camoRefs[ ET_DLC1_CAMO ] <- []
+ file.camoRefs[ ET_DLC3_CAMO ] <- []
+ file.camoRefs[ ET_DLC5_CAMO ] <- []
+
+ for ( int i = 101; i <= 120; i++ )
+ {
+ AddCamoRef( ET_DLC1_CAMO, i )
+ }
+
+ for ( int i = 121; i <= 140; i++ )
+ {
+ AddCamoRef( ET_DLC3_CAMO, i )
+ }
+
+ // You did it reddit!
+ int numRefs = file.camoRefs[ ET_DLC3_CAMO ].len()
+ CamoRef tempRef = file.camoRefs[ ET_DLC3_CAMO ][numRefs - 1]
+ file.camoRefs[ ET_DLC3_CAMO ][numRefs - 1] = file.camoRefs[ ET_DLC3_CAMO ][numRefs - 2]
+ file.camoRefs[ ET_DLC3_CAMO ][numRefs - 2] = tempRef
+
+ for ( int i = 141; i <= 160; i++ )
+ {
+ AddCamoRef( ET_DLC5_CAMO, i )
+ }
+
+ file.limitedEditionFDRefData[ ET_DLC7_ION_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_ION_WARPAINT ].append( CreateRefData( "ion_skin_fd", "ion" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_ION_WARPAINT ].append( CreateRefData( "callsign_fd_ion_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_SCORCH_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_SCORCH_WARPAINT ].append( CreateRefData( "scorch_skin_fd", "scorch" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_SCORCH_WARPAINT ].append( CreateRefData( "callsign_fd_scorch_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_NORTHSTAR_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_NORTHSTAR_WARPAINT ].append( CreateRefData( "northstar_skin_fd", "northstar" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_NORTHSTAR_WARPAINT ].append( CreateRefData( "callsign_fd_northstar_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_RONIN_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_RONIN_WARPAINT ].append( CreateRefData( "ronin_skin_fd", "ronin" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_RONIN_WARPAINT ].append( CreateRefData( "callsign_fd_ronin_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_TONE_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_TONE_WARPAINT ].append( CreateRefData( "tone_skin_fd", "tone" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_TONE_WARPAINT ].append( CreateRefData( "callsign_fd_tone_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_LEGION_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_LEGION_WARPAINT ].append( CreateRefData( "legion_skin_fd", "legion" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_LEGION_WARPAINT ].append( CreateRefData( "callsign_fd_legion_dynamic" ) )
+
+ file.limitedEditionFDRefData[ ET_DLC7_MONARCH_WARPAINT ] <- []
+ file.limitedEditionFDRefData[ ET_DLC7_MONARCH_WARPAINT ].append( CreateRefData( "monarch_skin_fd", "vanguard" ) )
+ file.limitedEditionFDRefData[ ET_DLC7_MONARCH_WARPAINT ].append( CreateRefData( "callsign_fd_monarch_dynamic" ) )
+}
+
+RefData function CreateRefData( string ref, string parentRef = "" )
+{
+ RefData data
+ data.ref = ref
+ data.parentRef = parentRef
+
+ return data
+}
+
+array<string> function Store_GetCustomizationRefs( int entitlementId )
+{
+ return file.customizationRefs[ entitlementId ]
+}
+
+array<string> function Store_GetPatchRefs( int entitlementId )
+{
+ return file.patchRefs[ entitlementId ]
+}
+
+array<string> function Store_GetBannerRefs( int entitlementId )
+{
+ return file.bannerRefs[ entitlementId ]
+}
+
+array<CamoRef> function Store_GetCamoRefs( int entitlementId )
+{
+ return file.camoRefs[ entitlementId ]
+}
+
+void function AddCamoRef( int entitlementId, int index )
+{
+ CamoRef cref
+ cref.ref = "camo_skin" + index
+ cref.pilotRef = "pilot_camo_skin" + index
+ cref.titanRef = "titan_camo_skin" + index
+ file.camoRefs[ entitlementId ].append( cref )
+}
+
+#if SERVER
+bool function ClientCommand_SetHasSeenStore( entity player, array<string> args )
+{
+ player.SetPersistentVar( "hasSeenStore", true )
+
+ return true
+}
+
+// TODO: refParam is problematic, because it assumes an entitlement maps to a single ref which is not the case for limited edition frontier defense entitlements
+void function StoreUpdatePIN( entity player, int entitlementId, string refParam )
+{
+ printt( "StoreUpdatePIN", entitlementId, refParam)
+
+ switch ( entitlementId )
+ {
+ case ET_DLC1_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc1_bundle", 0 )
+ break
+
+ case ET_DLC3_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc3_bundle", 0 )
+ break
+
+ case ET_DLC5_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc5_bundle", 0 )
+ break
+
+ case ET_PRIME_TITANS_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "sprime_titans_bundle", 0 )
+ break
+
+ case ET_DLC1_PRIME_ION:
+ case ET_DLC1_PRIME_SCORCH:
+ case ET_DLC3_PRIME_NORTHSTAR:
+ case ET_DLC3_PRIME_LEGION:
+ case ET_DLC5_PRIME_TONE:
+ case ET_DLC5_PRIME_RONIN:
+ Assert( refParam != "" )
+ if ( refParam == "" || !ItemDefined( refParam ) )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, refParam, 0 )
+ break
+
+ case ET_DLC1_ION:
+ case ET_DLC1_SCORCH:
+ case ET_DLC1_NORTHSTAR:
+ case ET_DLC1_RONIN:
+ case ET_DLC1_TONE:
+ case ET_DLC1_LEGION:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "customization_" + refParam, 0 )
+ break
+
+ case ET_DLC3_ION:
+ case ET_DLC3_SCORCH:
+ case ET_DLC3_NORTHSTAR:
+ case ET_DLC3_RONIN:
+ case ET_DLC3_TONE:
+ case ET_DLC3_LEGION:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc3_customization_" + refParam, 0 )
+ break
+
+ case ET_DLC5_ION:
+ case ET_DLC5_SCORCH:
+ case ET_DLC5_NORTHSTAR:
+ case ET_DLC5_RONIN:
+ case ET_DLC5_TONE:
+ case ET_DLC5_LEGION:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc5_customization_" + refParam, 0 )
+ break
+
+ case ET_DLC1_CAMO:
+ PIN_BuyItemWithRealMoney( player, false, "dlc_camos", 0 )
+ break
+
+ case ET_DLC3_CAMO:
+ PIN_BuyItemWithRealMoney( player, false, "dlc3_camos", 0 )
+ break
+
+ case ET_DLC5_CAMO:
+ PIN_BuyItemWithRealMoney( player, false, "dlc5_camos", 0 )
+ break
+
+ case ET_DLC1_CALLSIGN:
+ PIN_BuyItemWithRealMoney( player, false, "dlc_callsigns", 0 )
+ break
+
+ case ET_DLC3_CALLSIGN:
+ PIN_BuyItemWithRealMoney( player, false, "dlc3_callsigns", 0 )
+ break
+
+ case ET_DLC5_CALLSIGN:
+ PIN_BuyItemWithRealMoney( player, false, "dlc5_callsigns", 0 )
+ break
+
+ case ET_DLC7_TITAN_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc7_frontier_titan_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC7_ION_WARPAINT:
+ case ET_DLC7_SCORCH_WARPAINT:
+ case ET_DLC7_NORTHSTAR_WARPAINT:
+ case ET_DLC7_RONIN_WARPAINT:
+ case ET_DLC7_TONE_WARPAINT:
+ case ET_DLC7_LEGION_WARPAINT:
+ case ET_DLC7_MONARCH_WARPAINT:
+ PIN_BuyItemWithRealMoney( player, false, "dlc7_frontier_titan_warpaint_" + file.limitedEditionFDRefData[ entitlementId ][0].ref, 0 )
+ break
+
+ case ET_DLC7_WEAPON_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc7_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC7_R201_WARPAINT:
+ case ET_DLC7_G2A5_WARPAINT:
+ case ET_DLC7_FLATLINE_WARPAINT:
+ case ET_DLC7_CAR_WARPAINT:
+ case ET_DLC7_ALTERNATOR_WARPAINT:
+ case ET_DLC7_EVA8_WARPAINT:
+ case ET_DLC7_WINGMAN_WARPAINT:
+ case ET_DLC7_ARCHER_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc7_weapon_warpaint_" + refParam, 0 )
+ break
+
+ case ET_DLC8_WEAPON_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc8_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC8_R201_WARPAINT:
+ case ET_DLC8_HEMLOK_WARPAINT:
+ case ET_DLC8_R97_WARPAINT:
+ case ET_DLC8_KRABER_WARPAINT:
+ case ET_DLC8_SPITFIRE_WARPAINT:
+ case ET_DLC8_DEVOTION_WARPAINT:
+ case ET_DLC8_MOZAMBIQUE_WARPAINT:
+ case ET_DLC8_THUNDERBOLT_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc8_weapon_warpaint_" + refParam, 0 )
+ break
+
+ case ET_JUMPSTARTERBUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc10_jump_starter_pack", 0 )
+
+ case ET_DLC9_WEAPON_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc9_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC9_LSTAR_WARPAINT:
+ case ET_DLC9_MASTIFF_WARPAINT:
+ case ET_DLC9_SIDEWINDER_WARPAINT:
+ case ET_DLC9_R201_WARPAINT:
+ case ET_DLC9_CAR_WARPAINT:
+ case ET_DLC9_SPITFIRE_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc9_weapon_warpaint_" + refParam, 0 )
+ break
+
+ case ET_DLC10_WEAPON_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc10_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC10_R101_WARPAINT:
+ case ET_DLC10_FLATLINE_WARPAINT:
+ case ET_DLC10_VOLT_WARPAINT:
+ case ET_DLC10_ALTERNATOR_WARPAINT:
+ case ET_DLC10_SOFTBALL_WARPAINT:
+ case ET_DLC10_EPG1_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc10_weapon_warpaint_" + refParam, 0 )
+ break
+
+ case ET_DLC11_WEAPON_WARPAINT_BUNDLE:
+ PIN_BuyItemWithRealMoney( player, false, "dlc11_weapon_warpaint_bundle", 0 )
+ break
+
+ case ET_DLC11_DMR_WARPAINT:
+ case ET_DLC11_DOUBLETAKE_WARPAINT:
+ case ET_DLC11_G2A5_WARPAINT:
+ case ET_DLC11_COLDWAR_WARPAINT:
+ case ET_DLC11_R97_WARPAINT:
+ case ET_DLC11_R101_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return
+
+ PIN_BuyItemWithRealMoney( player, false, "dlc11_weapon_warpaint_" + refParam, 0 )
+ break
+ }
+}
+
+bool function ClientCommand_StoreSetNewItemStatus( entity player, array<string> args )
+{
+ // fix crash
+ if( args.len() < 1 )
+ return true
+
+ int entitlementId = int( args[0] )
+ string refParam
+ string parentRefParam
+
+ if ( args.len() >= 2 )
+ refParam = args[1]
+
+ if ( args.len() >= 3 )
+ parentRefParam = args[2]
+
+ StoreUpdatePIN( player, entitlementId, refParam )
+ StoreSetNewItemStatus( player, entitlementId, refParam, parentRefParam )
+
+ return true
+}
+
+bool function StoreSetNewItemStatus( entity player, int entitlementId, string refParam, string parentRefParam )
+{
+ string e = entitlementId == ET_JUMPSTARTERBUNDLE ? "ET_JUMPSTARTERBUNDLE" : string( entitlementId )
+ printt( "!!!!!!!!!!! StoreSetNewItemStatus() running for entitlement:", e )
+
+ switch ( entitlementId )
+ {
+ case 3:
+ // Prime Titans
+ SetItemNewStatus( player, "ion_prime", "", true )
+ SetItemNewStatus( player, "scorch_prime", "", true )
+
+ // Customization Packs
+ table< string, array<string> > dlc1BundleCustomizationRefs
+ dlc1BundleCustomizationRefs[ "ion" ] <- file.customizationRefs[ ET_DLC1_ION ]
+ dlc1BundleCustomizationRefs[ "tone" ] <- file.customizationRefs[ ET_DLC1_TONE ]
+ dlc1BundleCustomizationRefs[ "scorch" ] <- file.customizationRefs[ ET_DLC1_SCORCH ]
+ dlc1BundleCustomizationRefs[ "legion" ] <- file.customizationRefs[ ET_DLC1_LEGION ]
+ dlc1BundleCustomizationRefs[ "ronin" ] <- file.customizationRefs[ ET_DLC1_RONIN ]
+ dlc1BundleCustomizationRefs[ "northstar" ] <- file.customizationRefs[ ET_DLC1_NORTHSTAR ]
+
+ foreach ( parentRef, childRefs in dlc1BundleCustomizationRefs )
+ {
+ foreach ( ref in childRefs )
+ {
+ if ( !SubitemDefined( parentRef, ref ) )
+ return false
+
+ SetItemNewStatus( player, ref, parentRef, true )
+ }
+ }
+
+ // Callsigns
+ foreach ( ref in file.patchRefs[ ET_DLC1_CALLSIGN ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+
+ break
+
+ case ET_DLC3_BUNDLE:
+ // Prime Titans
+ SetItemNewStatus( player, "northstar_prime", "", true )
+ SetItemNewStatus( player, "legion_prime", "", true )
+
+ // Customization Packs
+ table< string, array<string> > dlc3BundleCustomizationRefs
+ dlc3BundleCustomizationRefs[ "ion" ] <- file.customizationRefs[ ET_DLC3_ION ]
+ dlc3BundleCustomizationRefs[ "tone" ] <- file.customizationRefs[ ET_DLC3_TONE ]
+ dlc3BundleCustomizationRefs[ "scorch" ] <- file.customizationRefs[ ET_DLC3_SCORCH ]
+ dlc3BundleCustomizationRefs[ "legion" ] <- file.customizationRefs[ ET_DLC3_LEGION ]
+ dlc3BundleCustomizationRefs[ "ronin" ] <- file.customizationRefs[ ET_DLC3_RONIN ]
+ dlc3BundleCustomizationRefs[ "northstar" ] <- file.customizationRefs[ ET_DLC3_NORTHSTAR ]
+
+ foreach ( parentRef, childRefs in dlc3BundleCustomizationRefs )
+ {
+ foreach ( ref in childRefs )
+ {
+ if ( !SubitemDefined( parentRef, ref ) )
+ return false
+
+ SetItemNewStatus( player, ref, parentRef, true )
+ }
+ }
+
+ // Callsigns
+ foreach ( ref in file.patchRefs[ ET_DLC3_CALLSIGN ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+
+ break
+
+ case ET_DLC5_BUNDLE:
+ // Prime Titans
+ SetItemNewStatus( player, "ronin_prime", "", true )
+ SetItemNewStatus( player, "tone_prime", "", true )
+
+ // Customization Packs
+ table< string, array<string> > dlc5BundleCustomizationRefs
+ dlc5BundleCustomizationRefs[ "ion" ] <- file.customizationRefs[ ET_DLC5_ION ]
+ dlc5BundleCustomizationRefs[ "tone" ] <- file.customizationRefs[ ET_DLC5_TONE ]
+ dlc5BundleCustomizationRefs[ "scorch" ] <- file.customizationRefs[ ET_DLC5_SCORCH ]
+ dlc5BundleCustomizationRefs[ "legion" ] <- file.customizationRefs[ ET_DLC5_LEGION ]
+ dlc5BundleCustomizationRefs[ "ronin" ] <- file.customizationRefs[ ET_DLC5_RONIN ]
+ dlc5BundleCustomizationRefs[ "northstar" ] <- file.customizationRefs[ ET_DLC5_NORTHSTAR ]
+
+ foreach ( parentRef, childRefs in dlc5BundleCustomizationRefs )
+ {
+ foreach ( ref in childRefs )
+ {
+ if ( !SubitemDefined( parentRef, ref ) )
+ return false
+
+ SetItemNewStatus( player, ref, parentRef, true )
+ }
+ }
+
+ // Callsigns
+ foreach ( ref in file.patchRefs[ ET_DLC5_CALLSIGN ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+
+ break
+
+ case ET_PRIME_TITANS_BUNDLE:
+ SetItemNewStatus( player, "ion_prime", "", true )
+ SetItemNewStatus( player, "scorch_prime", "", true )
+ SetItemNewStatus( player, "northstar_prime", "", true )
+ SetItemNewStatus( player, "legion_prime", "", true )
+ SetItemNewStatus( player, "ronin_prime", "", true )
+ SetItemNewStatus( player, "tone_prime", "", true )
+ break
+
+ case ET_DLC1_PRIME_ION:
+ case ET_DLC1_PRIME_SCORCH:
+ case ET_DLC3_PRIME_NORTHSTAR:
+ case ET_DLC3_PRIME_LEGION:
+ case ET_DLC5_PRIME_TONE:
+ case ET_DLC5_PRIME_RONIN:
+ Assert( refParam != "" )
+ if ( refParam == "" || !ItemDefined( refParam ) )
+ return false
+
+ SetItemNewStatus( player, refParam, "", true )
+ break
+
+ case ET_DLC1_ION:
+ case ET_DLC3_ION:
+ case ET_DLC5_ION:
+ case ET_DLC1_SCORCH:
+ case ET_DLC3_SCORCH:
+ case ET_DLC5_SCORCH:
+ case ET_DLC1_NORTHSTAR:
+ case ET_DLC3_NORTHSTAR:
+ case ET_DLC5_NORTHSTAR:
+ case ET_DLC1_RONIN:
+ case ET_DLC3_RONIN:
+ case ET_DLC5_RONIN:
+ case ET_DLC1_TONE:
+ case ET_DLC3_TONE:
+ case ET_DLC5_TONE:
+ case ET_DLC1_LEGION:
+ case ET_DLC3_LEGION:
+ case ET_DLC5_LEGION:
+ Assert( refParam != "" )
+ if ( refParam == "" )
+ return false
+
+ foreach ( ref in file.customizationRefs[ entitlementId ] )
+ {
+ if ( !SubitemDefined( refParam, ref ) )
+ return false
+
+ SetItemNewStatus( player, ref, refParam, true )
+ }
+ break
+
+ case ET_DLC1_CAMO:
+ case ET_DLC3_CAMO:
+ case ET_DLC5_CAMO:
+ // Not implemented, way too many camos would show as new
+ break
+
+ case ET_DLC1_CALLSIGN:
+ case ET_DLC3_CALLSIGN:
+ case ET_DLC5_CALLSIGN:
+ foreach ( ref in file.patchRefs[ entitlementId ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+
+ foreach ( ref in file.bannerRefs[ entitlementId ] )
+ {
+ SetItemNewStatus( player, ref, "", true )
+ }
+ break
+
+ case ET_DLC7_TITAN_WARPAINT_BUNDLE:
+ array<int> childEntitlements = GetChildEntitlements( entitlementId )
+
+ foreach ( entitlement in childEntitlements )
+ {
+ foreach ( data in file.limitedEditionFDRefData[ entitlement ] )
+ SetItemNewStatus( player, data.ref, data.parentRef, true )
+ }
+ break
+
+ case ET_DLC7_ION_WARPAINT:
+ case ET_DLC7_SCORCH_WARPAINT:
+ case ET_DLC7_NORTHSTAR_WARPAINT:
+ case ET_DLC7_RONIN_WARPAINT:
+ case ET_DLC7_TONE_WARPAINT:
+ case ET_DLC7_LEGION_WARPAINT:
+ case ET_DLC7_MONARCH_WARPAINT:
+ foreach ( data in file.limitedEditionFDRefData[ entitlementId ] )
+ SetItemNewStatus( player, data.ref, data.parentRef, true )
+ break
+
+ case ET_DLC7_R201_WARPAINT:
+ case ET_DLC7_G2A5_WARPAINT:
+ case ET_DLC7_FLATLINE_WARPAINT:
+ case ET_DLC7_CAR_WARPAINT:
+ case ET_DLC7_ALTERNATOR_WARPAINT:
+ case ET_DLC7_EVA8_WARPAINT:
+ case ET_DLC7_WINGMAN_WARPAINT:
+ case ET_DLC7_ARCHER_WARPAINT:
+ case ET_DLC8_R201_WARPAINT:
+ case ET_DLC8_HEMLOK_WARPAINT:
+ case ET_DLC8_R97_WARPAINT:
+ case ET_DLC8_KRABER_WARPAINT:
+ case ET_DLC8_SPITFIRE_WARPAINT:
+ case ET_DLC8_DEVOTION_WARPAINT:
+ case ET_DLC8_MOZAMBIQUE_WARPAINT:
+ case ET_DLC8_THUNDERBOLT_WARPAINT:
+ case ET_DLC9_LSTAR_WARPAINT:
+ case ET_DLC9_MASTIFF_WARPAINT:
+ case ET_DLC9_SIDEWINDER_WARPAINT:
+ case ET_DLC9_R201_WARPAINT:
+ case ET_DLC9_CAR_WARPAINT:
+ case ET_DLC9_SPITFIRE_WARPAINT:
+ case ET_DLC10_R101_WARPAINT:
+ case ET_DLC10_FLATLINE_WARPAINT:
+ case ET_DLC10_VOLT_WARPAINT:
+ case ET_DLC10_ALTERNATOR_WARPAINT:
+ case ET_DLC10_SOFTBALL_WARPAINT:
+ case ET_DLC10_EPG1_WARPAINT:
+ case ET_DLC11_DMR_WARPAINT:
+ case ET_DLC11_DOUBLETAKE_WARPAINT:
+ case ET_DLC11_G2A5_WARPAINT:
+ case ET_DLC11_COLDWAR_WARPAINT:
+ case ET_DLC11_R97_WARPAINT:
+ case ET_DLC11_R101_WARPAINT:
+ Assert( refParam != "" )
+ if ( refParam == "" || !ItemDefined( refParam ) )
+ return false
+
+ Assert( parentRefParam != "" )
+ if ( parentRefParam == "" || !ItemDefined( parentRefParam ) )
+ return false
+
+ SetItemNewStatus( player, refParam, parentRefParam, true )
+ break
+
+ case ET_DLC7_WEAPON_BUNDLE:
+ SetItemNewStatus( player, "skin_rspn101_wasteland", "mp_weapon_rspn101", true )
+ SetItemNewStatus( player, "skin_g2_masterwork", "mp_weapon_g2", true )
+ SetItemNewStatus( player, "skin_vinson_blue_fade", "mp_weapon_vinson", true )
+ SetItemNewStatus( player, "skin_car_crimson_fury", "mp_weapon_car", true )
+ SetItemNewStatus( player, "skin_alternator_patriot", "mp_weapon_alternator_smg", true )
+ SetItemNewStatus( player, "skin_shotgun_badlands", "mp_weapon_shotgun", true )
+ SetItemNewStatus( player, "skin_wingman_aqua_fade", "mp_weapon_wingman", true )
+ SetItemNewStatus( player, "skin_rocket_launcher_psych_spectre", "mp_weapon_rocket_launcher", true )
+ break
+
+ case ET_DLC8_WEAPON_WARPAINT_BUNDLE:
+ SetItemNewStatus( player, "skin_rspn101_patriot", "mp_weapon_rspn101", true )
+ SetItemNewStatus( player, "skin_hemlok_mochi", "mp_weapon_hemlok", true )
+ SetItemNewStatus( player, "skin_r97_purple_fade", "mp_weapon_r97", true )
+ SetItemNewStatus( player, "skin_kraber_masterwork", "mp_weapon_sniper", true )
+ SetItemNewStatus( player, "skin_spitfire_lead_farmer", "mp_weapon_lmg", true )
+ SetItemNewStatus( player, "skin_devotion_rspn_customs", "mp_weapon_esaw", true )
+ SetItemNewStatus( player, "skin_mozambique_crimson_fury", "mp_weapon_shotgun_pistol", true )
+ SetItemNewStatus( player, "skin_thunderbolt_8bit", "mp_weapon_arc_launcher", true )
+ break
+
+ case ET_DLC9_WEAPON_WARPAINT_BUNDLE:
+ SetItemNewStatus( player, "skin_lstar_heatsink", "mp_weapon_lstar", true )
+ SetItemNewStatus( player, "skin_mastiff_crimson_fury", "mp_weapon_mastiff", true )
+ SetItemNewStatus( player, "skin_sidewinder_masterwork", "mp_weapon_smr", true )
+ SetItemNewStatus( player, "skin_rspn101_halloween", "mp_weapon_rspn101", true )
+ SetItemNewStatus( player, "skin_car_halloween", "mp_weapon_car", true )
+ SetItemNewStatus( player, "skin_spitfire_halloween", "mp_weapon_lmg", true )
+ break
+
+ case ET_DLC10_WEAPON_WARPAINT_BUNDLE:
+ SetItemNewStatus( player, "skin_rspn101_og_blue_fade", "mp_weapon_rspn101_og", true )
+ SetItemNewStatus( player, "skin_vinson_badlands", "mp_weapon_vinson", true )
+ SetItemNewStatus( player, "skin_volt_heatsink", "mp_weapon_hemlok_smg", true )
+ SetItemNewStatus( player, "skin_alternator_headhunter", "mp_weapon_alternator_smg", true )
+ SetItemNewStatus( player, "skin_softball_masterwork", "mp_weapon_softball", true )
+ SetItemNewStatus( player, "skin_epg_mrvn", "mp_weapon_epg", true )
+ break
+
+ case ET_DLC11_WEAPON_WARPAINT_BUNDLE:
+ SetItemNewStatus( player, "skin_dmr_phantom", "mp_weapon_dmr", true )
+ SetItemNewStatus( player, "skin_doubletake_masterwork", "mp_weapon_doubletake", true )
+ SetItemNewStatus( player, "skin_g2_purple_fade", "mp_weapon_g2", true )
+ SetItemNewStatus( player, "skin_coldwar_heatsink", "mp_weapon_pulse_lmg", true )
+ SetItemNewStatus( player, "skin_r97_sky", "mp_weapon_r97", true )
+ SetItemNewStatus( player, "skin_rspn101_crimson_fury", "mp_weapon_rspn101", true )
+ break
+
+ case ET_JUMPSTARTERBUNDLE:
+ UnlockUltimateEdition( player )
+ break
+ }
+
+ return true
+}
+#endif
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
index 7f356a18..2ca051cf 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut
@@ -348,6 +348,14 @@ string function GetMapDisplayDesc( string mapname )
return "#" + mapname + "_CLASSIC_DESC"
}
+/// Sends a string message to player
+/// * `baseString` - The input string to search through
+/// * `searchString` - Find this substring...
+/// * `replaceString` - ...and replace with this substring
+/// * `replaceAll` - Whether to replace all occurences or just the first
+/// * `caseInsensitive` - Whether to consider casing (upper/lower)
+///
+/// Returns the updated string
string function StringReplace( string baseString, string searchString, string replaceString, bool replaceAll = false, bool caseInsensitive = false )
{
bool loopedOnce = false
@@ -362,7 +370,7 @@ string function StringReplace( string baseString, string searchString, string re
source = part1 + replaceString + part2
loopedOnce = true
- findResult = source.find( searchString )
+ findResult = source.find( searchString, findResult + replaceString.len() )
}
return baseString
@@ -386,8 +394,12 @@ float function RoundToNearestMultiplier( float value, float multiplier )
return value
}
-function DevEverythingUnlocked()
+function DevEverythingUnlocked( entity player = null )
{
+ // check if player has opted into progression or not
+ if ( player != null && ProgressionEnabledForPlayer( player ) )
+ return false
+
return EverythingUnlockedConVarEnabled()
}
@@ -1528,7 +1540,23 @@ array<string> function GetAvailableTitanRefs( entity player )
return availableTitanRefs
}
+/// Gets the highest Titan FD level and stores it in the corresponding persistent var.
+/// * `player` - The player entity to perform the action on
#if MP
+void function RecalculateHighestTitanFDLevel( entity player )
+{
+ int enumCount = PersistenceGetEnumCount( "titanClasses" )
+ int highestAegis = 0
+ for ( int i = 0; i < enumCount; i++ )
+ {
+ string enumName = PersistenceGetEnumItemNameForIndex( "titanClasses", i )
+ int aegisLevel = FD_TitanGetLevelForXP( enumName, FD_TitanGetXP( player, enumName ) )
+ if ( highestAegis < aegisLevel )
+ highestAegis = aegisLevel
+ }
+ player.SetPersistentVar( "fdStats.highestTitanFDLevel", highestAegis )
+}
+
string function GetTitanRefForLoadoutIndex( entity player, int loadoutIndex )
{
TitanLoadoutDef loadout = GetTitanLoadoutFromPersistentData( player, loadoutIndex )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans.gnut
index c9d986bc..57361362 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans.gnut
@@ -21,7 +21,6 @@ global function ReplacementTitan
global function TryAnnounceTitanfallWarningToEnemyTeam
global function GetTitanForPlayer
-
global function ShouldSetTitanRespawnTimer
global function PauseTitanTimers
@@ -33,6 +32,7 @@ global function SetReplacementTitanGamemodeRules
global function SetRequestTitanGamemodeRules
global function CreateTitanForPlayerAndHotdrop
+global function SetRequestTitanAllowedCallback
struct {
array<int> ETATimeThresholds = [ 120, 60, 30, 15 ]
@@ -53,6 +53,8 @@ struct {
bool functionref( entity ) ReplacementTitanGamemodeRules
bool functionref( entity, vector ) RequestTitanGamemodeRules
+ bool functionref( entity player, array< string > args ) RequestTitanAllowedCallback
+
} file
const nagInterval = 40
@@ -87,6 +89,10 @@ function ReplacementTitans_Init()
FlagInit( "LevelHasRoof" )
}
+void function SetRequestTitanAllowedCallback( bool functionref( entity player, array<string> args ) RequestTitanAllowedCallback )
+{
+ file.RequestTitanAllowedCallback = RequestTitanAllowedCallback
+}
void function ReplacementTitan_InitPlayer( entity player )
{
@@ -424,6 +430,7 @@ function TryETATitanReadyAnnouncement( entity player )
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
@@ -524,6 +531,9 @@ function req()
bool function ClientCommand_RequestTitan( entity player, array<string> args )
{
+ if( file.RequestTitanAllowedCallback != null && !file.RequestTitanAllowedCallback( player, args ) )
+ return true
+
ReplacementTitan( player ) //Separate function because other functions will call ReplacementTitan
return true
}
@@ -877,6 +887,20 @@ void function CreateTitanForPlayerAndHotdrop( entity player, Point spawnPoint, T
player.Signal( "titan_impact" )
thread TitanNPC_WaitForBubbleShield_StartAutoTitanBehavior( titan )
+ thread PlayerEarnMeter_ReplacementTitanThink( player, titan )
+}
+
+void function PlayerEarnMeter_ReplacementTitanThink( entity player, entity titan )
+{
+ player.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function(): ( player )
+ {
+ if( IsValid( player ) )
+ PlayerEarnMeter_Reset( player )
+ }
+ )
+ titan.WaitSignal( "OnDestroy" )
}
void function CleanupTitanFallDisablingEntity( entity titanFallDisablingEntity, entity titan )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans_drop.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans_drop.gnut
index 933e9988..6972d5ff 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans_drop.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/titan/_replacement_titans_drop.gnut
@@ -4,6 +4,7 @@ global function HullTraceDropPoint
global function DebugTitanfall
global function TitanFindDropNodes
global function TitanHulldropSpawnpoint
+global function SetRecalculateTitanReplacementPointCallback
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
@@ -19,8 +20,15 @@ global const TITANDROP_FALLBACK_DIST = 150 // if the ground search hits, we go t
struct
{
int replacementSpawnpointsID
+ Point functionref(Point originalPoint, entity player) recalculateTitanReplacementPointCallback
} file
+
+void function SetRecalculateTitanReplacementPointCallback(Point functionref(Point originalPoint, entity player) recalculateTitanReplacementPointCallback)
+{
+ file.recalculateTitanReplacementPointCallback = recalculateTitanReplacementPointCallback
+}
+
void function ReplacementTitansDrop_Init()
{
AddSpawnCallback( "info_spawnpoint_titan", AddDroppoint )
@@ -117,7 +125,10 @@ Point function GetTitanReplacementPoint( entity player, bool forDebugging = fals
vector playerEyeAngles = player.EyeAngles()
vector playerOrg = player.GetOrigin()
- return CalculateTitanReplacementPoint( playerOrg, playerEyePos, playerEyeAngles, forDebugging )
+ Point tempPoint = CalculateTitanReplacementPoint( playerOrg, playerEyePos, playerEyeAngles, forDebugging)
+ if( file.recalculateTitanReplacementPointCallback != null )
+ tempPoint = file.recalculateTitanReplacementPointCallback( tempPoint, player )
+ return tempPoint
}
Point function CalculateTitanReplacementPoint( vector playerOrg, vector playerEyePos, vector playerEyeAngles, bool forDebugging = false )
@@ -165,6 +176,7 @@ Point function CalculateTitanReplacementPoint( vector playerOrg, vector playerEy
Point point
point.origin = dropPoint
point.angles = yawAngles
+
return point
}
}
@@ -215,7 +227,8 @@ Point function CalculateTitanReplacementPoint( vector playerOrg, vector playerEy
Point point
point.origin = nodeOrigin
point.angles = Vector( 0, yaw, 0 )
- return point
+
+ return point
}
vector function GetPathNodeSearchPosWithLookPos( vector playerOrg, vector playerEyePos, vector playerEyeForward, vector playerLookPos, bool debug )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut
index d600cb03..396d5624 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut
@@ -1010,7 +1010,7 @@ void function AddCreditToTitanCoreBuilder( entity titan, float credit )
if ( IsValid( bossPlayer ) && !coreWasAvailable && IsCoreChargeAvailable( bossPlayer, soul ) )
{
- AddPlayerScore( bossPlayer, "TitanCoreEarned" )
+ AddPlayerScore( bossPlayer, "TitanCoreEarned", bossPlayer ) // this will show the "Core Earned" callsign event
#if MP
UpdateTitanCoreEarnedStat( bossPlayer, titan )
PIN_PlayerAbilityReady( bossPlayer, "core" )
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan/class_titan.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan/class_titan.gnut
index 5f72385e..d0a2d5e4 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/titan/class_titan.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/titan/class_titan.gnut
@@ -68,6 +68,11 @@ bool function ClientCommand_TitanEject( entity player, array<string> args )
if ( !PlayerCanEject( player ) )
return true
+ // check array length before accessing index to avoid oob access
+ // prevents crashing a server by just calling `TitanEject` without arguments
+ if( args.len() < 1 )
+ return true
+
int ejectPressCount = args[ 0 ].tointeger()
if ( ejectPressCount < 3 )
return true
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
index 4bfeb4f8..847881b5 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut
@@ -1,14 +1,39 @@
global function AddTitanXP
+global function AddFDTitanXP
void function AddTitanXP( entity player, int amount )
{
string titan = GetActiveTitanLoadout( player ).titanClass
int oldLevel = TitanGetLevel( player, titan )
+ int TitanXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.TITAN_LEVELED + "]" )
// increment xp
player.SetPersistentVar( "titanXP[" + titan + "]", min( TitanGetXP( player, titan ) + amount, TitanGetMaxXP( titan ) ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_TitanXPAdded", shTitanXP.titanClasses.find( titan ), TitanGetXP( player, titan ), amount )
// level up notif
if ( TitanGetLevel( player, titan ) != oldLevel )
+ {
Remote_CallFunction_NonReplay( player, "ServerCallback_TitanLeveledUp", shTitanXP.titanClasses.find( titan ), TitanGetGen( player, titan ), TitanGetLevel( player, titan ) )
+ AddPlayerScore( player, "TitanLevelUp" )
+ IncrementPlayerChallengeTitanLeveledUp( player )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.TITAN_LEVELED + "]", TitanXPMatch + 1 )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForTitanLevels( player, titan, oldLevel, TitanGetLevel( player, titan ) )
+ }
+}
+
+void function AddFDTitanXP( entity player, int fdXPamount )
+{
+ string titanRef = GetActiveTitanLoadout( player ).titanClass
+
+ player.SetPersistentVar( "fdTitanXP[" + titanRef + "]", FD_TitanGetPreviousXP( player, titanRef ) + fdXPamount )
+ int startingLevel = FD_TitanGetLevelForXP( titanRef, FD_TitanGetPreviousXP( player, titanRef ) )
+ int endingLevel = FD_TitanGetLevelForXP( titanRef, FD_TitanGetXP( player, titanRef ) )
+
+ Player_GiveFDUnlockPoints( player, endingLevel - startingLevel )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForFDTitanLevels( player, titanRef, startingLevel, endingLevel )
} \ No newline at end of file
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut
index 8e100257..0b0084b3 100644
--- a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut
+++ b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut
@@ -6,14 +6,24 @@ void function AddWeaponXP( entity player, int amount )
entity activeWeapon = player.GetActiveWeapon()
string weaponClassname = activeWeapon.GetWeaponClassName()
int oldLevel = WeaponGetLevel( player, weaponClassname )
+ int WeaponXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.WEAPON_LEVELED + "]" )
// increment xp
player.SetPersistentVar( GetItemPersistenceStruct( weaponClassname ) + ".weaponXP", min( WeaponGetXP( player, weaponClassname ) + amount, WeaponGetMaxXP( weaponClassname ) ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_WeaponXPAdded", shWeaponXP.weaponClassNames.find( weaponClassname ), WeaponGetXP( player, weaponClassname ), amount )
// level up notif
if ( WeaponGetLevel( player, weaponClassname ) != oldLevel )
+ {
Remote_CallFunction_NonReplay( player, "ServerCallback_WeaponLeveledUp", shWeaponXP.weaponClassNames.find( weaponClassname ), WeaponGetGen( player, weaponClassname ), WeaponGetLevel( player, weaponClassname ) )
-
+ AddPlayerScore( player, "WeaponLevelUp" )
+ IncrementPlayerChallengeWeaponLeveledUp( player )
+ player.SetPersistentVar( "xp_match[" + XP_TYPE.WEAPON_LEVELED + "]", WeaponXPMatch + 1 )
+
+ if( ProgressionEnabledForPlayer( player ) )
+ AwardRandomItemsForWeaponLevels( player, weaponClassname, oldLevel, WeaponGetLevel( player, weaponClassname ) )
+ }
+
// proscreen
if ( player == activeWeapon.GetProScreenOwner() )
{
diff --git a/README.md b/README.md
index 5a180df9..7b6dfaf0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
# NorthstarMods
+<a href="https://translate.harmony.tf/engage/northstar/">
+<img src="https://translate.harmony.tf/widgets/northstar/-/client/svg-badge.svg" alt="Translation status" />
+</a>
[Squirrel](http://www.squirrel-lang.org/squirreldoc/reference/index.html) scripts used to recreate server-side gamelogic and add [custom content](https://r2northstar.gitbook.io/r2northstar-wiki/using-northstar/gamemodes) to the game.
@@ -9,3 +12,11 @@ Issues in this repository should be created if they are related to these domains
- `Northstar.Coop` - Soon™.
- `Northstar.Custom` - Northstar custom content.
- `Northstar.CustomServer` - Server config files and scripts necessary for multiplayer.
+
+### Translating
+
+Translations can be submitted via [weblate](https://translate.harmony.tf/projects/northstar/client/).
+
+<a href="https://translate.harmony.tf/engage/northstar/">
+<img src="https://translate.harmony.tf/widgets/northstar/-/client/multi-auto.svg" alt="Translation status" />
+</a>